From 37162cac428e4cbf04ee8fd8cc00579a6593a9da Mon Sep 17 00:00:00 2001 From: Robin Jones Date: Sat, 5 Aug 2023 14:56:10 +0100 Subject: [PATCH] Add emulator loading and screen (#20) ## Description This PR shows a possible way forward for adding support for emulators. Superseeds #13 ## Motivation and Context The ability to load partial ROM's and/or overwrite parts of them is required by emulators, patches and potentially gameshark/action replay. ## 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. - [x] 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 --------- Co-authored-by: Mateusz Faderewski --- Makefile | 1 + README.md | 7 ++ libdragon | 2 +- src/flashcart/flashcart.c | 8 ++ src/flashcart/flashcart.h | 2 + src/flashcart/sc64/sc64.c | 29 +++++ src/menu/menu.c | 8 ++ src/menu/menu_state.h | 2 + src/menu/views/browser.c | 10 ++ src/menu/views/load_emulator.c | 197 +++++++++++++++++++++++++++++++++ src/menu/views/views.h | 3 + 11 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 src/menu/views/load_emulator.c diff --git a/Makefile b/Makefile index 062d2848..9e30b4d7 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,7 @@ SRCS = \ menu/views/file_info.c \ menu/views/image_viewer.c \ menu/views/load.c \ + menu/views/load_emulator.c \ menu/views/music_player.c \ menu/views/startup.c \ menu/views/system_info.c \ diff --git a/README.md b/README.md index 1508fe0f..c3300e41 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,13 @@ To use boxart, you need to place png files of size 158x112 in the folder `sd://m Each file must be named according to the 2 letter ROM ID. e.g. for goldeneye, this would be `GE.png` A known set of PNG files can be downloaded from https://mega.nz/file/6cNGwSqI#8X5ukb65n3YMlGaUtSOGXkKo9HxVnnMOgqn94Epcr7w +### Emulator support +Emulators should be added to the `sd:/emulators/` folder + +The menu currently supports the following emulators and associated ROM's: +* neon64v2 (https://github.com/hcs64/neon64v2) - emu.nes +* gb64 (https://lambertjamesd.github.io/gb64/romwrapper/romwrapper.html) - emu.gb, emu.gbc + # Developer documentation **Work in progress!** diff --git a/libdragon b/libdragon index 4409fe77..e37421e8 160000 --- a/libdragon +++ b/libdragon @@ -1 +1 @@ -Subproject commit 4409fe77e208aaef77800b684602d4ea5a74da0b +Subproject commit e37421e8e392e51470bb9dc6b5cd7a7419eeb10c diff --git a/src/flashcart/flashcart.c b/src/flashcart/flashcart.c index 3ed28eff..693c761e 100644 --- a/src/flashcart/flashcart.c +++ b/src/flashcart/flashcart.c @@ -32,6 +32,7 @@ static flashcart_t *flashcart = &((flashcart_t) { .init = dummy_init, .deinit = NULL, .load_rom = NULL, + .load_file = NULL, .load_save = NULL, .set_save_type = NULL, .set_save_writeback = NULL, @@ -111,6 +112,13 @@ flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_ return error; } +flashcart_error_t flashcart_load_file (char *file_path, uint32_t start_offset_address) { + if ((file_path == NULL) || (!file_exists(file_path))) { + return FLASHCART_ERROR_ARGS; + } + return flashcart->load_file(file_path, start_offset_address); +} + flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type) { flashcart_error_t error; uint32_t sectors[WRITEBACK_MAX_SECTORS] __attribute__((aligned(8))); diff --git a/src/flashcart/flashcart.h b/src/flashcart/flashcart.h index 917b6075..6f99f45a 100644 --- a/src/flashcart/flashcart.h +++ b/src/flashcart/flashcart.h @@ -42,6 +42,7 @@ typedef struct { flashcart_error_t (*init) (void); flashcart_error_t (*deinit) (void); flashcart_error_t (*load_rom) (char *rom_path, flashcart_progress_callback_t *progress); + flashcart_error_t (*load_file) (char *file_path, uint32_t start_offset_address); flashcart_error_t (*load_save) (char *save_path); flashcart_error_t (*set_save_type) (flashcart_save_type_t save_type); flashcart_error_t (*set_save_writeback) (uint32_t *sectors); @@ -51,6 +52,7 @@ typedef struct { flashcart_error_t flashcart_init (void); flashcart_error_t flashcart_deinit (void); flashcart_error_t flashcart_load_rom (char *rom_path, bool byte_swap, flashcart_progress_callback_t *progress); +flashcart_error_t flashcart_load_file (char *file_path, uint32_t start_offset_address); flashcart_error_t flashcart_load_save (char *save_path, flashcart_save_type_t save_type); diff --git a/src/flashcart/sc64/sc64.c b/src/flashcart/sc64/sc64.c index 0f5efc97..7151c328 100644 --- a/src/flashcart/sc64/sc64.c +++ b/src/flashcart/sc64/sc64.c @@ -193,6 +193,34 @@ static flashcart_error_t sc64_load_rom (char *rom_path, flashcart_progress_callb return FLASHCART_OK; } +static flashcart_error_t sc64_load_file (char *file_path, uint32_t start_offset_address) { + FIL fil; + UINT br; + + if (f_open(&fil, file_path, FA_READ) != FR_OK) { + return FLASHCART_ERROR_LOAD; + } + + fix_file_size(&fil); + + size_t file_size = f_size(&fil); + + if (file_size > MiB(8)) { // FIXME: should be checked with the start offset address. + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + + if (f_read(&fil, (void *) (ROM_ADDRESS + start_offset_address), file_size, &br) != FR_OK) { + f_close(&fil); + return FLASHCART_ERROR_LOAD; + } + + if (f_close(&fil) != FR_OK) { + return FLASHCART_ERROR_LOAD; + } + return FLASHCART_OK; +} + static flashcart_error_t sc64_load_save (char *save_path) { void *address = NULL; uint32_t value; @@ -294,6 +322,7 @@ static flashcart_t flashcart_sc64 = { .init = sc64_init, .deinit = sc64_deinit, .load_rom = sc64_load_rom, + .load_file = sc64_load_file, .load_save = sc64_load_save, .set_save_type = sc64_set_save_type, .set_save_writeback = sc64_set_save_writeback, diff --git a/src/menu/menu.c b/src/menu/menu.c index 1cc9783b..0672cf56 100644 --- a/src/menu/menu.c +++ b/src/menu/menu.c @@ -141,6 +141,10 @@ void menu_run (boot_params_t *boot_params) { view_load_display(menu, display); break; + case MENU_MODE_EMULATOR_LOAD: + view_load_emulator_display(menu, display); + break; + case MENU_MODE_ERROR: view_error_display(menu, display); break; @@ -191,6 +195,10 @@ void menu_run (boot_params_t *boot_params) { view_load_init(menu); break; + case MENU_MODE_EMULATOR_LOAD: + view_load_emulator_init(menu); + break; + case MENU_MODE_ERROR: view_error_init(menu); break; diff --git a/src/menu/menu_state.h b/src/menu/menu_state.h index 48a4c45d..4088bcb3 100644 --- a/src/menu/menu_state.h +++ b/src/menu/menu_state.h @@ -28,6 +28,7 @@ typedef enum { MENU_MODE_MUSIC_PLAYER, MENU_MODE_CREDITS, MENU_MODE_LOAD, + MENU_MODE_EMULATOR_LOAD, MENU_MODE_ERROR, MENU_MODE_FAULT, MENU_MODE_BOOT, @@ -37,6 +38,7 @@ typedef enum { typedef enum { ENTRY_TYPE_DIR, ENTRY_TYPE_ROM, + ENTRY_TYPE_EMULATOR, ENTRY_TYPE_SAVE, ENTRY_TYPE_IMAGE, ENTRY_TYPE_MUSIC, diff --git a/src/menu/views/browser.c b/src/menu/views/browser.c index 559147ea..5288d396 100644 --- a/src/menu/views/browser.c +++ b/src/menu/views/browser.c @@ -10,6 +10,7 @@ static const char *rom_extensions[] = { "z64", "n64", "v64", NULL }; +static const char *emulator_extensions[] = { "nes", "gb", "gbc", "smc", "gen", "smd", NULL }; static const char *save_extensions[] = { "sav", NULL }; // TODO: "eep", "sra", "srm", "fla" could be used if transfered from different flashcarts. static const char *image_extensions[] = { "png", NULL }; static const char *music_extensions[] = { "mp3", NULL }; @@ -28,6 +29,10 @@ static int compare_entry (const void *pa, const void *pb) { return -1; } else if (b->type == ENTRY_TYPE_ROM) { return 1; + } else if (a->type == ENTRY_TYPE_EMULATOR) { + return -1; + } else if (b->type == ENTRY_TYPE_EMULATOR) { + return 1; } else if (a->type == ENTRY_TYPE_SAVE) { return -1; } else if (b->type == ENTRY_TYPE_SAVE) { @@ -90,6 +95,8 @@ static bool load_directory (menu_t *menu) { 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, 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)) { @@ -188,6 +195,9 @@ static void process (menu_t *menu) { case ENTRY_TYPE_ROM: menu->next_mode = MENU_MODE_LOAD; break; + case ENTRY_TYPE_EMULATOR: + menu->next_mode = MENU_MODE_EMULATOR_LOAD; + break; case ENTRY_TYPE_IMAGE: menu->next_mode = MENU_MODE_IMAGE_VIEWER; break; diff --git a/src/menu/views/load_emulator.c b/src/menu/views/load_emulator.c new file mode 100644 index 00000000..740d39d3 --- /dev/null +++ b/src/menu/views/load_emulator.c @@ -0,0 +1,197 @@ +#include + +#include "boot/boot.h" +#include "flashcart/flashcart.h" +#include "views.h" +#include "utils/fs.h" + +#ifndef EMULATOR_FOLDER +#define EMULATOR_FOLDER "/emulators/" +#endif + +static const char *emu_nes_rom_extensions[] = { "nes", NULL }; +static const char *emu_gameboy_rom_extensions[] = { "gb", NULL }; +static const char *emu_gameboy_color_rom_extensions[] = { "gbc", NULL }; +static const char *emu_sega_rom_extensions[] = {"smc", "gen", "smd", NULL }; + +const uint32_t eum_rom_start_address = 0x200000; + +static bool load_pending; + +static void draw_progress (float progress) { + surface_t *d = display_try_get(); + + if (d) { + rdpq_attach(d, NULL); + + component_background_draw(); + + component_loader_draw(progress); + + rdpq_detach_show(); + } +} + + +static void load_emulator_nes_rom (path_t *path, menu_t *menu) { + + if (file_exists(EMULATOR_FOLDER"emu.nes")) { // || neon64bu.rom + + menu->flashcart_error = flashcart_load_rom(EMULATOR_FOLDER"emu.nes", false, draw_progress); + /* Combine EMU and ROM before loading. See https://github.com/hcs64/neon64v2/tree/master/pkg */ + menu->flashcart_error = flashcart_load_file(path_get(path), eum_rom_start_address); + if (menu->flashcart_error != FLASHCART_OK) { + menu->next_mode = MENU_MODE_FAULT; + path_free(path); + return; + } + + path_ext_replace(path, "sav"); + menu->flashcart_error = flashcart_load_save(path_get(path), FLASHCART_SAVE_TYPE_SRAM_BANKED); + if (menu->flashcart_error != FLASHCART_OK) { + menu->next_mode = MENU_MODE_FAULT; + path_free(path); + return; + } + + } + +} + +static void load_emulator_gameboy_rom (path_t *path, menu_t *menu) { + + if (file_exists(EMULATOR_FOLDER"emu.gb")) { // || gb.v64 + + menu->flashcart_error = flashcart_load_rom(EMULATOR_FOLDER"emu.gb", false, draw_progress); + /* Combine EMU and ROM before loading. */ + menu->flashcart_error = flashcart_load_file(path_get(path), eum_rom_start_address); + if (menu->flashcart_error != FLASHCART_OK) { + menu->next_mode = MENU_MODE_FAULT; + path_free(path); + return; + } + + path_ext_replace(path, "sav"); + menu->flashcart_error = flashcart_load_save(path_get(path), FLASHCART_SAVE_TYPE_FLASHRAM); + if (menu->flashcart_error != FLASHCART_OK) { + menu->next_mode = MENU_MODE_FAULT; + path_free(path); + return; + } + + } + +} + +static void load_emulator_gameboy_color_rom (path_t *path, menu_t *menu) { + + if (file_exists(EMULATOR_FOLDER"emu.gbc")) { // || gbc.v64 + + menu->flashcart_error = flashcart_load_rom(EMULATOR_FOLDER"emu.gbc", false, draw_progress); + /* Combine EMU and ROM before loading. */ + menu->flashcart_error = flashcart_load_file(path_get(path), eum_rom_start_address); + if (menu->flashcart_error != FLASHCART_OK) { + menu->next_mode = MENU_MODE_FAULT; + path_free(path); + return; + } + + path_ext_replace(path, "sav"); + menu->flashcart_error = flashcart_load_save(path_get(path), FLASHCART_SAVE_TYPE_FLASHRAM); + if (menu->flashcart_error != FLASHCART_OK) { + menu->next_mode = MENU_MODE_FAULT; + path_free(path); + return; + } + + } + +} + + +static void load (menu_t *menu) { + menu->next_mode = MENU_MODE_BOOT; + + path_t *path = path_clone(menu->browser.directory); + path_push(path, menu->browser.list[menu->browser.selected].name); + + if (file_has_extensions (path_get(path), emu_nes_rom_extensions)) { + load_emulator_nes_rom(path, menu); + } + else if (file_has_extensions (path_get(path), emu_gameboy_rom_extensions)) { + load_emulator_gameboy_rom(path, menu); + } + else if (file_has_extensions (path_get(path), emu_gameboy_color_rom_extensions)) { + load_emulator_gameboy_color_rom(path, menu); + } + else if (file_has_extensions (path_get(path), emu_sega_rom_extensions)) { + //load_emulator_sega_rom(path, menu); + } + + path_free(path); + + menu->boot_params->device_type = BOOT_DEVICE_TYPE_ROM; + menu->boot_params->tv_type = BOOT_TV_TYPE_PASSTHROUGH; + menu->boot_params->detect_cic_seed = true; +} + + +static void process (menu_t *menu) { + if (menu->actions.enter) { + load_pending = true; + } else if (menu->actions.back) { + menu->next_mode = MENU_MODE_BROWSER; + } +} + +static void draw (menu_t *menu, surface_t *d) { + rdpq_attach(d, NULL); + + component_background_draw(); + + if (load_pending) { + component_loader_draw(0.0f); + } else { + component_layout_draw(); + + component_main_text_draw( + ALIGN_CENTER, VALIGN_TOP, + "Emulator information\n" + "THE EMULATOR\n" + "Rom Name\n" + "\n" + "%s", + menu->browser.list[menu->browser.selected].name + ); + + + + component_actions_bar_text_draw( + ALIGN_LEFT, VALIGN_TOP, + "A: Load and run Emulator ROM\n" + "B: Exit" + ); + + } + + rdpq_detach_show(); +} + + +void view_load_emulator_init (menu_t *menu) { + load_pending = false; + + path_t *path = path_clone(menu->browser.directory); + path_push(path, menu->browser.list[menu->browser.selected].name); + + path_free(path); +} + +void view_load_emulator_display (menu_t *menu, surface_t *display) { + process(menu); + draw(menu, display); + if (load_pending) { + load_pending = false; + load(menu); + } +} diff --git a/src/menu/views/views.h b/src/menu/views/views.h index 1e2c29bd..0c02ce06 100644 --- a/src/menu/views/views.h +++ b/src/menu/views/views.h @@ -41,6 +41,9 @@ void view_credits_display (menu_t *menu, surface_t *display); void view_load_init (menu_t *menu); void view_load_display (menu_t *menu, surface_t *display); +void view_load_emulator_init (menu_t *menu); +void view_load_emulator_display (menu_t *menu, surface_t *display); + void view_error_init (menu_t *menu); void view_error_display (menu_t *menu, surface_t *display);