From 7512aaaf3133b012c6aaa93e708a214442288d32 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Aug 2022 13:50:32 -0500 Subject: [PATCH 01/22] add scan_all_buttons_held function - Condense 2 instances of scanning and assigning `all_buttons_held` into a single function. --- source/main.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/source/main.c b/source/main.c index 53e782f..7ca1b16 100644 --- a/source/main.c +++ b/source/main.c @@ -20,7 +20,6 @@ u8 *dol = NULL; int dol_argc = 0; #define MAX_NUM_ARGV 1024 char *dol_argv[MAX_NUM_ARGV]; -u16 all_buttons_held; char *default_path = "/ipl.dol"; struct shortcut { @@ -38,6 +37,15 @@ struct shortcut { }; int num_shortcuts = sizeof(shortcuts) / sizeof(shortcuts[0]); +u16 all_buttons_held; +void +scan_all_buttons_held() { + PAD_ScanPads(); + all_buttons_held = + (PAD_ButtonsHeld(PAD_CHAN0) | PAD_ButtonsHeld(PAD_CHAN1) + | PAD_ButtonsHeld(PAD_CHAN2) | PAD_ButtonsHeld(PAD_CHAN3)); +} + void dol_alloc(int size) { int mram_size = (SYS_GetArenaHi() - SYS_GetArenaLo()); @@ -297,10 +305,7 @@ delay_exit() { while (all_buttons_held & PAD_BUTTON_DOWN || SYS_ResetButtonDown()) { VIDEO_WaitVSync(); - PAD_ScanPads(); - all_buttons_held = - (PAD_ButtonsHeld(PAD_CHAN0) | PAD_ButtonsHeld(PAD_CHAN1) - | PAD_ButtonsHeld(PAD_CHAN2) | PAD_ButtonsHeld(PAD_CHAN3)); + scan_all_buttons_held(); } } @@ -347,11 +352,7 @@ main() { EXI_Deselect(EXI_CHANNEL_0); EXI_Unlock(EXI_CHANNEL_0); - PAD_ScanPads(); - - all_buttons_held = - (PAD_ButtonsHeld(PAD_CHAN0) | PAD_ButtonsHeld(PAD_CHAN1) - | PAD_ButtonsHeld(PAD_CHAN2) | PAD_ButtonsHeld(PAD_CHAN3)); + scan_all_buttons_held(); if (all_buttons_held & PAD_BUTTON_LEFT || SYS_ResetButtonDown()) { // Since we've disabled the Qoob, we wil reboot to the Nintendo IPL From 7790c7404206e707aeeecc9e274c950f18a56b10 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Aug 2022 14:27:02 -0500 Subject: [PATCH 02/22] move shortcuts to file - Move shortcut definitions to its own file. --- meson.build | 1 + source/main.c | 17 ++--------------- source/shortcut.c | 13 +++++++++++++ source/shortcut.h | 14 ++++++++++++++ 4 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 source/shortcut.c create mode 100644 source/shortcut.h diff --git a/meson.build b/meson.build index 3a12cab..da6447a 100644 --- a/meson.build +++ b/meson.build @@ -53,6 +53,7 @@ gekkoboot = executable( 'gekkoboot', 'source/main.c', 'source/utils.c', + 'source/shortcut.c', 'source/fatfs/ff.c', 'source/fatfs/ffsystem.c', 'source/fatfs/ffunicode.c', diff --git a/source/main.c b/source/main.c index 7ca1b16..bc0ec89 100644 --- a/source/main.c +++ b/source/main.c @@ -1,5 +1,6 @@ #include "fatfs/ff.h" #include "ffshim.h" +#include "shortcut.h" #include "utils.h" #include "version.h" #include @@ -22,20 +23,6 @@ int dol_argc = 0; char *dol_argv[MAX_NUM_ARGV]; char *default_path = "/ipl.dol"; -struct shortcut { - u16 pad_buttons; - char *path; -} shortcuts[] = { - {PAD_BUTTON_A, "/a.dol"}, - {PAD_BUTTON_B, "/b.dol"}, - {PAD_BUTTON_X, "/x.dol"}, - {PAD_BUTTON_Y, "/y.dol"}, - {PAD_TRIGGER_Z, "/z.dol"}, - {PAD_BUTTON_START, "/start.dol"}, - // NOTE: Shouldn't use L, R or Joysticks as analog inputs are calibrated on boot. - // Should also avoid D-Pad as it is used for special functionality. -}; -int num_shortcuts = sizeof(shortcuts) / sizeof(shortcuts[0]); u16 all_buttons_held; void @@ -364,7 +351,7 @@ main() { char *paths[2]; int num_paths = 0; - for (int i = 0; i < num_shortcuts; i++) { + for (int i = 0; i < NUM_SHORTCUTS; i++) { if (all_buttons_held & shortcuts[i].pad_buttons) { paths[num_paths++] = shortcuts[i].path; break; diff --git a/source/shortcut.c b/source/shortcut.c new file mode 100644 index 0000000..d3f206e --- /dev/null +++ b/source/shortcut.c @@ -0,0 +1,13 @@ +#include "shortcut.h" +#include + +SHORTCUT shortcuts[NUM_SHORTCUTS] = { + {PAD_BUTTON_A, "/a.dol"}, + {PAD_BUTTON_B, "/b.dol"}, + {PAD_BUTTON_X, "/x.dol"}, + {PAD_BUTTON_Y, "/y.dol"}, + {PAD_TRIGGER_Z, "/z.dol"}, + {PAD_BUTTON_START, "/start.dol"}, + // NOTE: Shouldn't use L, R or Joysticks as analog inputs are calibrated on boot. + // Should also avoid D-Pad as it is used for special functionality. +}; diff --git a/source/shortcut.h b/source/shortcut.h new file mode 100644 index 0000000..fb26bf6 --- /dev/null +++ b/source/shortcut.h @@ -0,0 +1,14 @@ +#ifndef INC_SHORTCUT_H +#define INC_SHORTCUT_H +#include + +#define NUM_SHORTCUTS 6 + +typedef struct { + u16 pad_buttons; + char *path; +} SHORTCUT; + +extern SHORTCUT shortcuts[NUM_SHORTCUTS]; + +#endif From fc572ad0ace098983c253e5cdc0cb7f75100b623 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Aug 2022 16:39:30 -0500 Subject: [PATCH 03/22] use shortcut for default - Add a special shortcut definition for default `ipl.dol`. - This prepares for future in which shortcuts are referenced rather than just paths and prevents requiring explicit handling of the special no-shortcut/default case. --- source/main.c | 6 ++---- source/shortcut.c | 1 + source/shortcut.h | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/source/main.c b/source/main.c index bc0ec89..11ca83a 100644 --- a/source/main.c +++ b/source/main.c @@ -22,8 +22,6 @@ int dol_argc = 0; #define MAX_NUM_ARGV 1024 char *dol_argv[MAX_NUM_ARGV]; -char *default_path = "/ipl.dol"; - u16 all_buttons_held; void scan_all_buttons_held() { @@ -351,14 +349,14 @@ main() { char *paths[2]; int num_paths = 0; - for (int i = 0; i < NUM_SHORTCUTS; i++) { + for (int i = 1; i < NUM_SHORTCUTS; i++) { if (all_buttons_held & shortcuts[i].pad_buttons) { paths[num_paths++] = shortcuts[i].path; break; } } - paths[num_paths++] = default_path; + paths[num_paths++] = shortcuts[0].path; if (load_usb('B')) { goto load; diff --git a/source/shortcut.c b/source/shortcut.c index d3f206e..28803e8 100644 --- a/source/shortcut.c +++ b/source/shortcut.c @@ -2,6 +2,7 @@ #include SHORTCUT shortcuts[NUM_SHORTCUTS] = { + {0, "/ipl.dol"}, {PAD_BUTTON_A, "/a.dol"}, {PAD_BUTTON_B, "/b.dol"}, {PAD_BUTTON_X, "/x.dol"}, diff --git a/source/shortcut.h b/source/shortcut.h index fb26bf6..57fb752 100644 --- a/source/shortcut.h +++ b/source/shortcut.h @@ -2,7 +2,7 @@ #define INC_SHORTCUT_H #include -#define NUM_SHORTCUTS 6 +#define NUM_SHORTCUTS 7 typedef struct { u16 pad_buttons; From 1634ea883c0d2fc954150bd97161f11ee7b4ca7e Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 7 Sep 2022 22:30:11 -0500 Subject: [PATCH 04/22] use BOOT_PAYLOAD struct - Defines the `BOOT_PAYLOAD` struct for containerizing boot data (`dol` and `dol_argc`). - Removes boot data from global scope. --- source/main.c | 82 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 53 insertions(+), 29 deletions(-) diff --git a/source/main.c b/source/main.c index 11ca83a..4308099 100644 --- a/source/main.c +++ b/source/main.c @@ -17,10 +17,13 @@ #define VERBOSE_LOGGING 0 -u8 *dol = NULL; -int dol_argc = 0; #define MAX_NUM_ARGV 1024 -char *dol_argv[MAX_NUM_ARGV]; + +typedef struct { + u8 *dol; + int dol_argc; + char *dol_argv[MAX_NUM_ARGV]; +} BOOT_PAYLOAD; u16 all_buttons_held; void @@ -32,7 +35,7 @@ scan_all_buttons_held() { } void -dol_alloc(int size) { +dol_alloc(u8 **_dol, int size) { int mram_size = (SYS_GetArenaHi() - SYS_GetArenaLo()); kprintf("Memory available: %iB\n", mram_size); @@ -43,15 +46,17 @@ dol_alloc(int size) { return; } - dol = (u8 *) memalign(32, size); + u8 *dol = (u8 *) memalign(32, size); if (!dol) { kprintf("Couldn't allocate memory\n"); } + + *_dol = dol; } void -load_parse_cli(char *path) { +load_parse_cli(char **_dol_argv, int *_dol_argc, char *path) { int path_length = strlen(path); path[path_length - 3] = 'c'; path[path_length - 2] = 'l'; @@ -95,6 +100,9 @@ load_parse_cli(char *path) { // Parse CLI file // https://github.com/emukidid/swiss-gc/blob/a0fa06d81360ad6d173acd42e4dd5495e268de42/cube/swiss/source/swiss.c#L1236 + char **dol_argv = _dol_argv; + int dol_argc = 0; + dol_argv[dol_argc] = path; dol_argc++; @@ -125,10 +133,18 @@ load_parse_cli(char *path) { kprintf("arg%i: %s\n", i, dol_argv[i]); } #endif + + *_dol_argc = dol_argc; } int -load_fat(const char *slot_name, const DISC_INTERFACE *iface_, char **paths, int num_paths) { +load_fat( + BOOT_PAYLOAD *payload, + const char *slot_name, + const DISC_INTERFACE *iface_, + char **paths, + int num_paths +) { int res = 0; kprintf("Trying %s\n", slot_name); @@ -156,16 +172,16 @@ load_fat(const char *slot_name, const DISC_INTERFACE *iface_, char **paths, int } size_t size = f_size(&file); - dol_alloc(size); - if (!dol) { + dol_alloc(&payload->dol, size); + if (!payload->dol) { continue; } UINT _; - f_read(&file, dol, size, &_); + f_read(&file, payload->dol, size, &_); f_close(&file); // Attempt to load and parse CLI file - load_parse_cli(path); + load_parse_cli(payload->dol_argv, &payload->dol_argc, path); res = 1; break; @@ -197,7 +213,7 @@ convert_int(unsigned int in) { #define GC_OK 0x89 int -load_usb(char slot) { +load_usb(BOOT_PAYLOAD *payload, char slot) { kprintf("Trying USB Gecko in slot %c\n", slot); int channel, res = 1; @@ -254,10 +270,10 @@ load_usb(char slot) { usb_recvbuffer_safe(channel, &size, 4); size = convert_int(size); - dol_alloc(size); - unsigned char *pointer = dol; + dol_alloc(&payload->dol, size); + unsigned char *pointer = payload->dol; - if (!dol) { + if (!payload->dol) { res = 0; goto end; } @@ -358,28 +374,34 @@ main() { paths[num_paths++] = shortcuts[0].path; - if (load_usb('B')) { + // Init payload. + BOOT_PAYLOAD payload; + payload.dol = NULL; + payload.dol_argc = 0; + + // Attempt to load from each device. + if (load_usb(&payload, 'B')) { goto load; } - if (load_fat("sdb", &__io_gcsdb, paths, num_paths)) { + if (load_fat(&payload, "sdb", &__io_gcsdb, paths, num_paths)) { goto load; } - if (load_usb('A')) { + if (load_usb(&payload, 'A')) { goto load; } - if (load_fat("sda", &__io_gcsda, paths, num_paths)) { + if (load_fat(&payload, "sda", &__io_gcsda, paths, num_paths)) { goto load; } - if (load_fat("sd2", &__io_gcsd2, paths, num_paths)) { + if (load_fat(&payload, "sd2", &__io_gcsd2, paths, num_paths)) { goto load; } load: - if (!dol) { + if (!payload.dol) { kprintf("No DOL found! Halting."); while (true) { VIDEO_WaitVSync(); @@ -391,13 +413,13 @@ main() { dolargs.length = 0; // https://github.com/emukidid/swiss-gc/blob/f5319aab248287c847cb9468325ebcf54c993fb1/cube/swiss/source/aram/sidestep.c#L350 - if (dol_argc) { + if (payload.dol_argc) { dolargs.argvMagic = ARGV_MAGIC; - dolargs.argc = dol_argc; + dolargs.argc = payload.dol_argc; dolargs.length = 1; - for (int i = 0; i < dol_argc; i++) { - size_t arg_length = strlen(dol_argv[i]) + 1; + for (int i = 0; i < payload.dol_argc; i++) { + size_t arg_length = strlen(payload.dol_argv[i]) + 1; dolargs.length += arg_length; } @@ -409,9 +431,11 @@ main() { dolargs.length = 0; } else { unsigned int position = 0; - for (int i = 0; i < dol_argc; i++) { - size_t arg_length = strlen(dol_argv[i]) + 1; - memcpy(dolargs.commandLine + position, dol_argv[i], arg_length); + for (int i = 0; i < payload.dol_argc; i++) { + size_t arg_length = strlen(payload.dol_argv[i]) + 1; + memcpy(dolargs.commandLine + position, + payload.dol_argv[i], + arg_length); position += arg_length; } dolargs.commandLine[dolargs.length - 1] = '\0'; @@ -426,7 +450,7 @@ main() { SYS_ResetSystem(SYS_SHUTDOWN, 0, FALSE); SYS_SwitchFiber( - (intptr_t) dol, + (intptr_t) payload.dol, 0, (intptr_t) dolargs.commandLine, dolargs.length, From 501009e3916b05f48dd2247b845e4758a4b25620 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 7 Sep 2022 23:10:58 -0500 Subject: [PATCH 05/22] split reading and parsing of files - Splits `load_parse_cli` into `read_cli_file` and `parse_cli_file`. - Moves DOL file reading to `read_dol_file`. --- source/main.c | 62 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/source/main.c b/source/main.c index 4308099..9ebfb27 100644 --- a/source/main.c +++ b/source/main.c @@ -56,7 +56,35 @@ dol_alloc(u8 **_dol, int size) { } void -load_parse_cli(char **_dol_argv, int *_dol_argc, char *path) { +read_dol_file(u8 **_dol, char *path) { + *_dol = NULL; + + kprintf("Reading %s\n", path); + FIL file; + FRESULT open_result = f_open(&file, path, FA_READ); + if (open_result != FR_OK) { + kprintf("Failed to open file: %s\n", get_fresult_message(open_result)); + return; + } + + size_t size = f_size(&file); + u8 *dol; + dol_alloc(&dol, size); + if (!dol) { + return; + } + UINT _; + f_read(&file, dol, size, &_); + f_close(&file); + + *_dol = dol; +} + +void +read_cli_file(char **_cli, int *_size, char *path) { + *_cli = NULL; + *_size = 0; + int path_length = strlen(path); path[path_length - 3] = 'c'; path[path_length - 2] = 'l'; @@ -98,14 +126,17 @@ load_parse_cli(char **_dol_argv, int *_dol_argc, char *path) { size++; } + *_cli = cli; + *_size = size; +} + +void +parse_cli_file(char **_dol_argv, int *_dol_argc, char *cli, int size) { // Parse CLI file // https://github.com/emukidid/swiss-gc/blob/a0fa06d81360ad6d173acd42e4dd5495e268de42/cube/swiss/source/swiss.c#L1236 char **dol_argv = _dol_argv; int dol_argc = 0; - dol_argv[dol_argc] = path; - dol_argc++; - // First argument is at the beginning of the file if (cli[0] != '\r' && cli[0] != '\n') { dol_argv[dol_argc] = cli; @@ -163,25 +194,20 @@ load_fat( for (int i = 0; i < num_paths; ++i) { char *path = paths[i]; - kprintf("Reading %s\n", path); - FIL file; - FRESULT open_result = f_open(&file, path, FA_READ); - if (open_result != FR_OK) { - kprintf("Failed to open file: %s\n", get_fresult_message(open_result)); - continue; - } - - size_t size = f_size(&file); - dol_alloc(&payload->dol, size); + read_dol_file(&payload->dol, path); if (!payload->dol) { continue; } - UINT _; - f_read(&file, payload->dol, size, &_); - f_close(&file); // Attempt to load and parse CLI file - load_parse_cli(payload->dol_argv, &payload->dol_argc, path); + char *cli; + int cli_size; + read_cli_file(&cli, &cli_size, path); + + // Parse CLI file. + if (cli) { + parse_cli_file(payload->dol_argv, &payload->dol_argc, cli, cli_size); + } res = 1; break; From 3e3746187af5b3593a215fd6a1a3b98ff240f1e5 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 7 Sep 2022 23:21:02 -0500 Subject: [PATCH 06/22] pass shortcut index instead of paths array - Reference shortcuts rather just the shortcut's paths. - Prepares for future in which shortcuts define more than just a path. --- meson.build | 1 + source/main.c | 51 ++++++++++++++++++++++++--------------------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/meson.build b/meson.build index da6447a..64c979d 100644 --- a/meson.build +++ b/meson.build @@ -54,6 +54,7 @@ gekkoboot = executable( 'source/main.c', 'source/utils.c', 'source/shortcut.c', + 'source/cli_args.c', 'source/fatfs/ff.c', 'source/fatfs/ffsystem.c', 'source/fatfs/ffunicode.c', diff --git a/source/main.c b/source/main.c index 9ebfb27..d7bff1b 100644 --- a/source/main.c +++ b/source/main.c @@ -173,8 +173,7 @@ load_fat( BOOT_PAYLOAD *payload, const char *slot_name, const DISC_INTERFACE *iface_, - char **paths, - int num_paths + int shortcut_index ) { int res = 0; @@ -192,27 +191,28 @@ load_fat( f_getlabel(slot_name, name, NULL); kprintf("Mounted %s as %s\n", name, slot_name); - for (int i = 0; i < num_paths; ++i) { - char *path = paths[i]; + char *path = shortcuts[shortcut_index].path; + read_dol_file(&payload->dol, path); + if (!payload->dol && shortcut_index != 0) { + shortcut_index = 0; + path = shortcuts[shortcut_index].path; read_dol_file(&payload->dol, path); - if (!payload->dol) { - continue; - } + } + if (!payload->dol) { + goto unmount; + } - // Attempt to load and parse CLI file - char *cli; - int cli_size; - read_cli_file(&cli, &cli_size, path); + // Attempt to load and parse CLI file + char *cli; + int cli_size; + read_cli_file(&cli, &cli_size, path); - // Parse CLI file. - if (cli) { - parse_cli_file(payload->dol_argv, &payload->dol_argc, cli, cli_size); - } - - res = 1; - break; + // Parse CLI file. + if (cli) { + parse_cli_file(payload->dol_argv, &payload->dol_argc, cli, cli_size); } +unmount: kprintf("Unmounting %s\n", slot_name); iface->shutdown(); iface = NULL; @@ -388,18 +388,15 @@ main() { return 0; } - char *paths[2]; - int num_paths = 0; - + // Detect selected shortcut. + int shortcut_index = 0; for (int i = 1; i < NUM_SHORTCUTS; i++) { if (all_buttons_held & shortcuts[i].pad_buttons) { - paths[num_paths++] = shortcuts[i].path; + shortcut_index = i; break; } } - paths[num_paths++] = shortcuts[0].path; - // Init payload. BOOT_PAYLOAD payload; payload.dol = NULL; @@ -410,7 +407,7 @@ main() { goto load; } - if (load_fat(&payload, "sdb", &__io_gcsdb, paths, num_paths)) { + if (load_fat(&payload, "sdb", &__io_gcsdb, shortcut_index)) { goto load; } @@ -418,11 +415,11 @@ main() { goto load; } - if (load_fat(&payload, "sda", &__io_gcsda, paths, num_paths)) { + if (load_fat(&payload, "sda", &__io_gcsda, shortcut_index)) { goto load; } - if (load_fat(&payload, "sd2", &__io_gcsd2, paths, num_paths)) { + if (load_fat(&payload, "sd2", &__io_gcsd2, shortcut_index)) { goto load; } From d990442fb1cf77800ba953813bf75e781284ba1c Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 7 Sep 2022 23:30:09 -0500 Subject: [PATCH 07/22] split load_shortcut_files function - Moves finding and reading of shortcut DOL and CLI files to `load_shortcut_files`. --- source/main.c | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/source/main.c b/source/main.c index d7bff1b..34067b4 100644 --- a/source/main.c +++ b/source/main.c @@ -168,6 +168,32 @@ parse_cli_file(char **_dol_argv, int *_dol_argc, char *cli, int size) { *_dol_argc = dol_argc; } +int +load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { + char *path = shortcuts[shortcut_index].path; + read_dol_file(&payload->dol, path); + if (!payload->dol && shortcut_index != 0) { + shortcut_index = 0; + path = shortcuts[shortcut_index].path; + read_dol_file(&payload->dol, path); + } + if (!payload->dol) { + return 0; + } + + // Attempt to load and parse CLI file + char *cli; + int cli_size; + read_cli_file(&cli, &cli_size, path); + + // Parse CLI file. + if (cli) { + parse_cli_file(payload->dol_argv, &payload->dol_argc, cli, cli_size); + } + + return 1; +} + int load_fat( BOOT_PAYLOAD *payload, @@ -191,28 +217,8 @@ load_fat( f_getlabel(slot_name, name, NULL); kprintf("Mounted %s as %s\n", name, slot_name); - char *path = shortcuts[shortcut_index].path; - read_dol_file(&payload->dol, path); - if (!payload->dol && shortcut_index != 0) { - shortcut_index = 0; - path = shortcuts[shortcut_index].path; - read_dol_file(&payload->dol, path); - } - if (!payload->dol) { - goto unmount; - } - - // Attempt to load and parse CLI file - char *cli; - int cli_size; - read_cli_file(&cli, &cli_size, path); - - // Parse CLI file. - if (cli) { - parse_cli_file(payload->dol_argv, &payload->dol_argc, cli, cli_size); - } + res = load_shortcut_files(payload, shortcut_index); -unmount: kprintf("Unmounting %s\n", slot_name); iface->shutdown(); iface = NULL; From dfc3fd6b85a8211abbe3388beb4e42ba8c2c647f Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 7 Sep 2022 23:55:39 -0500 Subject: [PATCH 08/22] move file handeling to filesystem - Moves all filesystem reading and handling code to its own `filesystem.c` file. - `FS_RESULT` enum extends and replaces `FRESULT` enum. - Colapses `utils.c` into `filesystem.c`. - `dol_alloc` function removed. Filesystem and USB geko routines handle malloc independently. - Dropped `_size` param from `read_cli_file` in favor of `strenlen` --- meson.build | 2 +- source/filesystem.c | 115 +++++++++++++++++++++++++++++++++++++++++ source/filesystem.h | 45 ++++++++++++++++ source/main.c | 121 ++++++++++---------------------------------- source/utils.c | 36 ------------- source/utils.h | 9 ---- 6 files changed, 187 insertions(+), 141 deletions(-) create mode 100644 source/filesystem.c create mode 100644 source/filesystem.h delete mode 100644 source/utils.c delete mode 100644 source/utils.h diff --git a/meson.build b/meson.build index 64c979d..e9944b4 100644 --- a/meson.build +++ b/meson.build @@ -52,7 +52,7 @@ font = custom_target( gekkoboot = executable( 'gekkoboot', 'source/main.c', - 'source/utils.c', + 'source/filesystem.c', 'source/shortcut.c', 'source/cli_args.c', 'source/fatfs/ff.c', diff --git a/source/filesystem.c b/source/filesystem.c new file mode 100644 index 0000000..afb69ee --- /dev/null +++ b/source/filesystem.c @@ -0,0 +1,115 @@ +#include "filesystem.h" +#include "fatfs/ff.h" +#include "ffshim.h" +#include +#include + +FATFS fs; +FS_RESULT +fs_mount(const DISC_INTERFACE *iface_) { + iface = iface_; + return f_mount(&fs, "", 1); +} + +void +fs_unmount() { + f_unmount(""); + iface->shutdown(); + iface = NULL; +} + +void +fs_get_volume_label(const char *path, char *label) { + FRESULT res = f_getlabel(path, label, NULL); + if (res != FR_OK) { + *label = '\0'; + } +} + +FS_RESULT +_fs_read_file(void **contents_, const char *path, int is_string) { + FIL file; + FRESULT result = f_open(&file, path, FA_READ); + if (result != FR_OK) { + kprintf("Failed to open file: %s\n", get_fs_result_message(result)); + return result; + } + + size_t size = f_size(&file); + if (size <= 0) { + kprintf("File is empty\n"); + return FS_FILE_EMPTY; + } + kprintf("File size: %iB\n", size); + + // Malloc an extra byte if we are reading as a string incase we need to add NUL character. + void *contents = malloc(size + (is_string ? 1 : 0)); + if (!contents) { + kprintf("Couldn't allocate memory for file\n"); + return FS_NOT_ENOUGH_MEMORY; + } + + kprintf("Reading file...\n"); + UINT _; + result = f_read(&file, contents, size, &_); + if (result != FR_OK) { + kprintf("Failed to read file: %s\n", get_fs_result_message(result)); + return result; + } + + f_close(&file); + + // Ensure files read as strings end with NUL character. + if (is_string) { + // This is safe because we malloc an extra byte above if reading as string. + ((char *) contents)[size] = '\0'; + } + + *contents_ = contents; + return FS_OK; +} + +FS_RESULT +fs_read_file(void **contents, const char *path) { + return _fs_read_file(contents, path, false); +} +FS_RESULT +fs_read_file_string(const char **contents, const char *path) { + return _fs_read_file((void **) contents, path, true); +} + +#define NUM_FS_RESULT_MSGS 22 +const char *fs_result_msgs[NUM_FS_RESULT_MSGS] = { + /*FS_OK ( 0)*/ "Succeeded", + /*FS_DISK_ERR ( 1)*/ "A hard error occurred in the low level disk I/O layer", + /*FS_INT_ERR ( 2)*/ "Assertion failed", + /*FS_NOT_READY ( 3)*/ "Device not ready", + /*FS_NO_FILE ( 4)*/ "Could not find the file", + /*FS_NO_PATH ( 5)*/ "Could not find the path", + /*FS_INVALID_NAME ( 6)*/ "The path name format is invalid", + /*FS_DENIED ( 7)*/ "Access denied due to prohibited access or directory full", + /*FS_EXIST ( 8)*/ "Access denied due to prohibited access", + /*FS_INVALID_OBJECT ( 9)*/ "The file/directory object is invalid", + /*FS_WRITE_PROTECTED (10)*/ "The physical drive is write protected", + /*FS_INVALID_DRIVE (11)*/ "The logical drive number is invalid", + /*FS_NOT_ENABLED (12)*/ "The volume has no work area", + /*FS_NO_FILESYSTEM (13)*/ "There is no valid FAT volume", + /*FS_MKFS_ABORTED (14)*/ "The f_mkfs() aborted due to any problem", + /*FS_TIMEOUT (15)*/ + "Could not get a grant to access the volume within defined period", + /*FS_LOCKED (16)*/ + "The operation is rejected according to the file sharing policy", + /*FS_NOT_ENOUGH_CORE (17)*/ "LFN working buffer could not be allocated", + /*FS_TOO_MANY_OPEN_FILES (18)*/ "Number of open files > FF_FS_LOCK", + /*FS_INVALID_PARAMETER (19)*/ "Given parameter is invalid", + /*FS_FILE_EMPTY (20)*/ "File is empty", + /*FS_NOT_ENOUGH_MEMORY (21)*/ "Not enough memory", +}; + +const char * +get_fs_result_message(FS_RESULT result) { + if (result < 0 || result >= NUM_FS_RESULT_MSGS) { + return "Unknown"; + } + return fs_result_msgs[result]; +} diff --git a/source/filesystem.h b/source/filesystem.h new file mode 100644 index 0000000..4ae983e --- /dev/null +++ b/source/filesystem.h @@ -0,0 +1,45 @@ +#ifndef INC_FILESYSTEM_H +#define INC_FILESYSTEM_H +#include + +// See ./fatfs/ff.h:276 +typedef enum { + FS_OK = 0, /* ( 0) Succeeded */ + FS_DISK_ERR, /* ( 1) A hard error occurred in the low level disk I/O layer */ + FS_INT_ERR, /* ( 2) Assertion failed */ + FS_NOT_READY, /* ( 3) The physical drive cannot work */ + FS_NO_FILE, /* ( 4) Could not find the file */ + FS_NO_PATH, /* ( 5) Could not find the path */ + FS_INVALID_NAME, /* ( 6) The path name format is invalid */ + FS_DENIED, /* ( 7) Access denied due to prohibited access or directory full */ + FS_EXIST, /* ( 8) Access denied due to prohibited access */ + FS_INVALID_OBJECT, /* ( 9) The file/directory object is invalid */ + FS_WRITE_PROTECTED, /* (10) The physical drive is write protected */ + FS_INVALID_DRIVE, /* (11) The logical drive number is invalid */ + FS_NOT_ENABLED, /* (12) The volume has no work area */ + FS_NO_FILESYSTEM, /* (13) There is no valid FAT volume */ + FS_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */ + FS_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */ + FS_LOCKED, /* (16) The operation is rejected according to the file sharing policy */ + FS_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */ + FS_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */ + FS_INVALID_PARAMETER, /* (19) Given parameter is invalid */ + FS_FILE_EMPTY, /* (20) File is empty */ + FS_NOT_ENOUGH_MEMORY, /* (21) Not enough memory to malloc file */ +} FS_RESULT; +// Changes to this enum should also be made to fs_result_msgs in filesystem.c + +FS_RESULT +fs_mount(const DISC_INTERFACE *iface_); +void +fs_unmount(); +void +fs_get_volume_label(const char *path, char *label); +FS_RESULT +fs_read_file(void **contents, const char *path); +FS_RESULT +fs_read_file_string(const char **contents, const char *path); +const char * +get_fs_result_message(FS_RESULT result); + +#endif diff --git a/source/main.c b/source/main.c index 34067b4..8ea5e01 100644 --- a/source/main.c +++ b/source/main.c @@ -1,9 +1,8 @@ -#include "fatfs/ff.h" -#include "ffshim.h" +#include "filesystem.h" #include "shortcut.h" -#include "utils.h" #include "version.h" #include +#include #include #include #include @@ -34,56 +33,17 @@ scan_all_buttons_held() { | PAD_ButtonsHeld(PAD_CHAN2) | PAD_ButtonsHeld(PAD_CHAN3)); } -void -dol_alloc(u8 **_dol, int size) { - int mram_size = (SYS_GetArenaHi() - SYS_GetArenaLo()); - kprintf("Memory available: %iB\n", mram_size); - - kprintf("DOL size is %iB\n", size); - - if (size <= 0) { - kprintf("Empty DOL\n"); - return; - } - - u8 *dol = (u8 *) memalign(32, size); - - if (!dol) { - kprintf("Couldn't allocate memory\n"); - } - - *_dol = dol; -} - void read_dol_file(u8 **_dol, char *path) { *_dol = NULL; kprintf("Reading %s\n", path); - FIL file; - FRESULT open_result = f_open(&file, path, FA_READ); - if (open_result != FR_OK) { - kprintf("Failed to open file: %s\n", get_fresult_message(open_result)); - return; - } - - size_t size = f_size(&file); - u8 *dol; - dol_alloc(&dol, size); - if (!dol) { - return; - } - UINT _; - f_read(&file, dol, size, &_); - f_close(&file); - - *_dol = dol; + fs_read_file((void **) _dol, path); } void -read_cli_file(char **_cli, int *_size, char *path) { +read_cli_file(char **_cli, char *path) { *_cli = NULL; - *_size = 0; int path_length = strlen(path); path[path_length - 3] = 'c'; @@ -91,43 +51,7 @@ read_cli_file(char **_cli, int *_size, char *path) { path[path_length - 1] = 'i'; kprintf("Reading %s\n", path); - FIL file; - FRESULT result = f_open(&file, path, FA_READ); - if (result != FR_OK) { - if (result == FR_NO_FILE) { - kprintf("CLI file not found\n"); - } else { - kprintf("Failed to open CLI file: %s\n", get_fresult_message(result)); - } - return; - } - - size_t size = f_size(&file); - kprintf("CLI file size is %iB\n", size); - - if (size <= 0) { - kprintf("Empty CLI file\n"); - return; - } - - char *cli = (char *) malloc(size + 1); - - if (!cli) { - kprintf("Couldn't allocate memory for CLI file\n"); - return; - } - - UINT _; - f_read(&file, cli, size, &_); - f_close(&file); - - if (cli[size - 1] != '\0') { - cli[size] = '\0'; - size++; - } - - *_cli = cli; - *_size = size; + fs_read_file_string((const char **) _cli, path); } void @@ -183,12 +107,11 @@ load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { // Attempt to load and parse CLI file char *cli; - int cli_size; - read_cli_file(&cli, &cli_size, path); + read_cli_file(&cli, path); // Parse CLI file. if (cli) { - parse_cli_file(payload->dol_argv, &payload->dol_argc, cli, cli_size); + parse_cli_file(payload->dol_argv, &payload->dol_argc, cli, strlen(cli)); } return 1; @@ -205,23 +128,20 @@ load_fat( kprintf("Trying %s\n", slot_name); - FATFS fs; - iface = iface_; - FRESULT mount_result = f_mount(&fs, "", 1); - if (mount_result != FR_OK) { - kprintf("Couldn't mount %s: %s\n", slot_name, get_fresult_message(mount_result)); + FS_RESULT mount_result = fs_mount(iface_); + if (mount_result != FS_OK) { + kprintf("Couldn't mount %s: %s\n", slot_name, get_fs_result_message(mount_result)); goto end; } char name[256]; - f_getlabel(slot_name, name, NULL); + fs_get_volume_label(slot_name, name); kprintf("Mounted %s as %s\n", name, slot_name); res = load_shortcut_files(payload, shortcut_index); kprintf("Unmounting %s\n", slot_name); - iface->shutdown(); - iface = NULL; + fs_unmount(); end: return res; @@ -302,15 +222,21 @@ load_usb(BOOT_PAYLOAD *payload, char slot) { usb_recvbuffer_safe(channel, &size, 4); size = convert_int(size); - dol_alloc(&payload->dol, size); - unsigned char *pointer = payload->dol; + if (size <= 0) { + kprintf("DOL is empty\n"); + return res; + } + kprintf("DOL size is %iB\n", size); - if (!payload->dol) { + u8 *dol = (u8 *) malloc(size); + + if (!dol) { res = 0; goto end; } kprintf("Receiving file...\n"); + unsigned char *pointer = dol; while (size > 0xF7D8) { usb_recvbuffer_safe(channel, (void *) pointer, 0xF7D8); size -= 0xF7D8; @@ -320,6 +246,8 @@ load_usb(BOOT_PAYLOAD *payload, char slot) { usb_recvbuffer_safe(channel, (void *) pointer, size); } + payload->dol = dol; + end: return res; } @@ -394,6 +322,9 @@ main() { return 0; } + int mram_size = SYS_GetArenaHi() - SYS_GetArenaLo(); + kprintf("Memory available: %iB\n", mram_size); + // Detect selected shortcut. int shortcut_index = 0; for (int i = 1; i < NUM_SHORTCUTS; i++) { diff --git a/source/utils.c b/source/utils.c deleted file mode 100644 index c52e4c7..0000000 --- a/source/utils.c +++ /dev/null @@ -1,36 +0,0 @@ -#include "fatfs/ff.h" - -// See ./fatfs/ff.h:276 -char *fresult_msgs[] = { - /*FR_OK ( 0)*/ "Succeeded", - /*FR_DISK_ERR ( 1)*/ "A hard error occurred in the low level disk I/O layer", - /*FR_INT_ERR ( 2)*/ "Assertion failed", - /*FR_NOT_READY ( 3)*/ "Device not ready", - /*FR_NO_FILE ( 4)*/ "Could not find the file", - /*FR_NO_PATH ( 5)*/ "Could not find the path", - /*FR_INVALID_NAME ( 6)*/ "The path name format is invalid", - /*FR_DENIED ( 7)*/ "Access denied due to prohibited access or directory full", - /*FR_EXIST ( 8)*/ "Access denied due to prohibited access", - /*FR_INVALID_OBJECT ( 9)*/ "The file/directory object is invalid", - /*FR_WRITE_PROTECTED (10)*/ "The physical drive is write protected", - /*FR_INVALID_DRIVE (11)*/ "The logical drive number is invalid", - /*FR_NOT_ENABLED (12)*/ "The volume has no work area", - /*FR_NO_FILESYSTEM (13)*/ "There is no valid FAT volume", - /*FR_MKFS_ABORTED (14)*/ "The f_mkfs() aborted due to any problem", - /*FR_TIMEOUT (15)*/ - "Could not get a grant to access the volume within defined period", - /*FR_LOCKED (16)*/ - "The operation is rejected according to the file sharing policy", - /*FR_NOT_ENOUGH_CORE (17)*/ "LFN working buffer could not be allocated", - /*FR_TOO_MANY_OPEN_FILES (18)*/ "Number of open files > FF_FS_LOCK", - /*FR_INVALID_PARAMETER (19)*/ "Given parameter is invalid", -}; -int num_fresult_msgs = sizeof(fresult_msgs) / sizeof(fresult_msgs[0]); - -char * -get_fresult_message(FRESULT result) { - if (result < 0 || result >= num_fresult_msgs) { - return "Unknown"; - } - return fresult_msgs[result]; -} diff --git a/source/utils.h b/source/utils.h deleted file mode 100644 index 9fda394..0000000 --- a/source/utils.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef INC_UTILS_H -#define INC_UTILS_H - -#include "fatfs/ff.h" - -extern char * -get_fresult_message(FRESULT result); - -#endif From 91e86cc42e354c81845815cd58b7feef38b51bd7 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 01:39:57 -0500 Subject: [PATCH 09/22] pass argv struct instead of arg array - Rather than track `argc` and `argv` array and combine into `__argv` struct later, create `__argv` struct immediately. - Moves CLI parsing to its own file. - Renames `parse_cli_file` to `parse_cli_args` as it is source agnostic. --- source/cli_args.c | 78 +++++++++++++++++++++++++++++++++++ source/cli_args.h | 8 ++++ source/main.c | 103 ++++++++++++---------------------------------- 3 files changed, 112 insertions(+), 77 deletions(-) create mode 100644 source/cli_args.c create mode 100644 source/cli_args.h diff --git a/source/cli_args.c b/source/cli_args.c new file mode 100644 index 0000000..fd30424 --- /dev/null +++ b/source/cli_args.c @@ -0,0 +1,78 @@ +#include "cli_args.h" +#include +#include +#include + +#define MAX_NUM_ARGV 1024 + +// 0 - Failure +// 1 - OK/Empty +int +parse_cli_args(struct __argv *argv, char *cli_option_str) { + kprintf("Parsing CLI args...\n"); + + argv->argc = 0; + argv->length = 0; + argv->commandLine = NULL; + + char *cli = cli_option_str; + char *dol_argv[MAX_NUM_ARGV]; + int dol_argc = 0; + int size = strlen(cli_option_str); + + // Parse CLI file + // https://github.com/emukidid/swiss-gc/blob/a0fa06d81360ad6d173acd42e4dd5495e268de42/cube/swiss/source/swiss.c#L1236 + // First argument is at the beginning of the file + if (cli[0] != '\r' && cli[0] != '\n') { + dol_argv[dol_argc] = cli; + dol_argc++; + } + + // Search for the others after each newline + for (int i = 0; i < size; i++) { + if (cli[i] == '\r' || cli[i] == '\n') { + cli[i] = '\0'; + } else if (cli[i - 1] == '\0') { + dol_argv[dol_argc] = cli + i; + dol_argc++; + if (dol_argc >= MAX_NUM_ARGV) { + kprintf("Reached max of %i args.\n", MAX_NUM_ARGV); + break; + } + } + } + + kprintf("Found %i CLI args\n", dol_argc); + + if (!dol_argc) { + return 0; + } + + // https://github.com/emukidid/swiss-gc/blob/f5319aab248287c847cb9468325ebcf54c993fb1/cube/swiss/source/aram/sidestep.c#L350 + argv->argc = dol_argc; + argv->length = 1; + + for (int i = 0; i < dol_argc; i++) { + size_t arg_length = strlen(dol_argv[i]) + 1; + argv->length += arg_length; + } + + kprintf("CLI argv size is %iB\n", argv->length); + argv->commandLine = (char *) malloc(argv->length); + + if (!argv->commandLine) { + kprintf("Couldn't allocate memory for CLI argv\n"); + argv->length = 0; + return 0; + } + + unsigned int position = 0; + for (int i = 0; i < dol_argc; i++) { + size_t arg_length = strlen(dol_argv[i]) + 1; + memcpy(argv->commandLine + position, dol_argv[i], arg_length); + position += arg_length; + } + argv->commandLine[argv->length - 1] = '\0'; + + return 1; +} \ No newline at end of file diff --git a/source/cli_args.h b/source/cli_args.h new file mode 100644 index 0000000..fc97273 --- /dev/null +++ b/source/cli_args.h @@ -0,0 +1,8 @@ +#ifndef INC_CLI_ARGS_H +#define INC_CLI_ARGS_H +#include + +int +parse_cli_args(struct __argv *argv, char *cli_option_str); + +#endif diff --git a/source/main.c b/source/main.c index 8ea5e01..4e066c4 100644 --- a/source/main.c +++ b/source/main.c @@ -1,3 +1,4 @@ +#include "cli_args.h" #include "filesystem.h" #include "shortcut.h" #include "version.h" @@ -16,12 +17,9 @@ #define VERBOSE_LOGGING 0 -#define MAX_NUM_ARGV 1024 - typedef struct { u8 *dol; - int dol_argc; - char *dol_argv[MAX_NUM_ARGV]; + struct __argv argv; } BOOT_PAYLOAD; u16 all_buttons_held; @@ -54,44 +52,6 @@ read_cli_file(char **_cli, char *path) { fs_read_file_string((const char **) _cli, path); } -void -parse_cli_file(char **_dol_argv, int *_dol_argc, char *cli, int size) { - // Parse CLI file - // https://github.com/emukidid/swiss-gc/blob/a0fa06d81360ad6d173acd42e4dd5495e268de42/cube/swiss/source/swiss.c#L1236 - char **dol_argv = _dol_argv; - int dol_argc = 0; - - // First argument is at the beginning of the file - if (cli[0] != '\r' && cli[0] != '\n') { - dol_argv[dol_argc] = cli; - dol_argc++; - } - - // Search for the others after each newline - for (int i = 0; i < size; i++) { - if (cli[i] == '\r' || cli[i] == '\n') { - cli[i] = '\0'; - } else if (cli[i - 1] == '\0') { - dol_argv[dol_argc] = cli + i; - dol_argc++; - if (dol_argc >= MAX_NUM_ARGV) { - kprintf("Reached max of %i args.\n", MAX_NUM_ARGV); - break; - } - } - } - - kprintf("Found %i CLI args\n", dol_argc); - -#if VERBOSE_LOGGING - for (int i = 0; i < dol_argc; ++i) { - kprintf("arg%i: %s\n", i, dol_argv[i]); - } -#endif - - *_dol_argc = dol_argc; -} - int load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { char *path = shortcuts[shortcut_index].path; @@ -111,7 +71,8 @@ load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { // Parse CLI file. if (cli) { - parse_cli_file(payload->dol_argv, &payload->dol_argc, cli, strlen(cli)); + parse_cli_args(&payload->argv, cli); + free((void *) cli); } return 1; @@ -337,7 +298,10 @@ main() { // Init payload. BOOT_PAYLOAD payload; payload.dol = NULL; - payload.dol_argc = 0; + payload.argv.argc = 0; + payload.argv.length = 0; + payload.argv.commandLine = NULL; + payload.argv.argvMagic = ARGV_MAGIC; // Attempt to load from each device. if (load_usb(&payload, 'B')) { @@ -368,39 +332,24 @@ main() { } } - struct __argv dolargs; - dolargs.commandLine = (char *) NULL; - dolargs.length = 0; - - // https://github.com/emukidid/swiss-gc/blob/f5319aab248287c847cb9468325ebcf54c993fb1/cube/swiss/source/aram/sidestep.c#L350 - if (payload.dol_argc) { - dolargs.argvMagic = ARGV_MAGIC; - dolargs.argc = payload.dol_argc; - dolargs.length = 1; - - for (int i = 0; i < payload.dol_argc; i++) { - size_t arg_length = strlen(payload.dol_argv[i]) + 1; - dolargs.length += arg_length; + // Print DOL args. +#if VERBOSE_LOGGING + if (payload.argv.length > 0) { + kprintf("----------\n"); + size_t position = 0; + for (int i = 0; i < payload.argv.argc; ++i) { + kprintf("arg%i: %s\n", i, payload.argv.commandLine + position); + position += strlen(payload.argv.commandLine + position) + 1; } + kprintf("----------\n\n"); + } else { + kprintf("No CLI args\n"); + } +#endif - kprintf("CLI argv size is %iB\n", dolargs.length); - dolargs.commandLine = (char *) malloc(dolargs.length); - - if (!dolargs.commandLine) { - kprintf("Couldn't allocate memory for CLI argv\n"); - dolargs.length = 0; - } else { - unsigned int position = 0; - for (int i = 0; i < payload.dol_argc; i++) { - size_t arg_length = strlen(payload.dol_argv[i]) + 1; - memcpy(dolargs.commandLine + position, - payload.dol_argv[i], - arg_length); - position += arg_length; - } - dolargs.commandLine[dolargs.length - 1] = '\0'; - DCStoreRange(dolargs.commandLine, dolargs.length); - } + // Prepare DOL argv. + if (payload.argv.length > 0) { + DCStoreRange(payload.argv.commandLine, payload.argv.length); } memcpy((void *) STUB_ADDR, stub, (size_t) stub_size); @@ -412,8 +361,8 @@ main() { SYS_SwitchFiber( (intptr_t) payload.dol, 0, - (intptr_t) dolargs.commandLine, - dolargs.length, + (intptr_t) payload.argv.commandLine, + payload.argv.length, STUB_ADDR, STUB_STACK ); From 87fd89a0695ff297553e9a94bf98671be30ec2f9 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 00:34:02 -0500 Subject: [PATCH 10/22] code cleanup - Silence erroneous `TB_BUS_CLOCK` undefined error in some IDEs. - Group global state to top of `main.c`. - Copy and modify rather than overwriting DOL path for CLI path in `read_cli_file`. - `dol` -> `dol_file`, `cli` -> `cli_file`, and other variable name clarifications. - Add comments to describe function return codes. - Add various comments. - Various stylistic changes and delinting for consistency. --- source/main.c | 157 ++++++++++++++++++++++------------------------ source/shortcut.h | 4 +- 2 files changed, 77 insertions(+), 84 deletions(-) diff --git a/source/main.c b/source/main.c index 4e066c4..174f551 100644 --- a/source/main.c +++ b/source/main.c @@ -17,12 +17,17 @@ #define VERBOSE_LOGGING 0 +// Global State +// -------------------- +u16 all_buttons_held; +extern u8 __xfb[]; +// -------------------- + typedef struct { - u8 *dol; + u8 *dol_file; struct __argv argv; } BOOT_PAYLOAD; -u16 all_buttons_held; void scan_all_buttons_held() { PAD_ScanPads(); @@ -32,73 +37,84 @@ scan_all_buttons_held() { } void -read_dol_file(u8 **_dol, char *path) { - *_dol = NULL; +read_dol_file(u8 **dol_file, const char *path) { + *dol_file = NULL; kprintf("Reading %s\n", path); - fs_read_file((void **) _dol, path); + fs_read_file((void **) dol_file, path); } void -read_cli_file(char **_cli, char *path) { - *_cli = NULL; +read_cli_file(char **cli_file, const char *dol_path) { + *cli_file = NULL; - int path_length = strlen(path); + int path_length = strlen(dol_path); + char path[path_length + 1]; + strcpy(path, dol_path); path[path_length - 3] = 'c'; path[path_length - 2] = 'l'; path[path_length - 1] = 'i'; kprintf("Reading %s\n", path); - fs_read_file_string((const char **) _cli, path); + fs_read_file_string((const char **) cli_file, path); } +// 0 - Device should not be used. +// 1 - Device should be used. int load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { - char *path = shortcuts[shortcut_index].path; - read_dol_file(&payload->dol, path); - if (!payload->dol && shortcut_index != 0) { + // Attempt to read shortcut paths from from mounted FAT device. + u8 *dol_file = NULL; + const char *dol_path = shortcuts[shortcut_index].path; + read_dol_file(&dol_file, dol_path); + if (!dol_file && shortcut_index != 0) { shortcut_index = 0; - path = shortcuts[shortcut_index].path; - read_dol_file(&payload->dol, path); + dol_path = shortcuts[shortcut_index].path; + read_dol_file(&dol_file, dol_path); } - if (!payload->dol) { + if (!dol_file) { return 0; } - // Attempt to load and parse CLI file - char *cli; - read_cli_file(&cli, path); + // Attempt to read CLI file. + char *cli_file; + read_cli_file(&cli_file, dol_path); // Parse CLI file. - if (cli) { - parse_cli_args(&payload->argv, cli); - free((void *) cli); + if (cli_file) { + parse_cli_args(&payload->argv, cli_file); + free((void *) cli_file); } + payload->dol_file = dol_file; return 1; } +// 0 - Device should not be used. +// 1 - Device should be used. int load_fat( BOOT_PAYLOAD *payload, const char *slot_name, - const DISC_INTERFACE *iface_, + const DISC_INTERFACE *iface, int shortcut_index ) { int res = 0; kprintf("Trying %s\n", slot_name); - FS_RESULT mount_result = fs_mount(iface_); - if (mount_result != FS_OK) { - kprintf("Couldn't mount %s: %s\n", slot_name, get_fs_result_message(mount_result)); + // Mount device. + FS_RESULT result = fs_mount(iface); + if (result != FS_OK) { + kprintf("Couldn't mount %s: %s\n", slot_name, get_fs_result_message(result)); goto end; } - char name[256]; - fs_get_volume_label(slot_name, name); - kprintf("Mounted %s as %s\n", name, slot_name); + char volume_label[256]; + fs_get_volume_label(slot_name, volume_label); + kprintf("Mounted %s as %s\n", volume_label, slot_name); + // Attempt to load shortcut files. res = load_shortcut_files(payload, shortcut_index); kprintf("Unmounting %s\n", slot_name); @@ -125,27 +141,18 @@ convert_int(unsigned int in) { #define GC_READY 0x88 #define GC_OK 0x89 +// 0 - Device should not be used. +// 1 - Device should be used. int load_usb(BOOT_PAYLOAD *payload, char slot) { - kprintf("Trying USB Gecko in slot %c\n", slot); - - int channel, res = 1; - - switch (slot) { - case 'B': - channel = 1; - break; + int res = 0; + int channel = slot == 'B' ? 1 : 0; - case 'A': - default: - channel = 0; - break; - } + kprintf("Trying USB Gecko in slot %c\n", slot); if (!usb_isgeckoalive(channel)) { kprintf("Not present\n"); - res = 0; - goto end; + return res; } usb_flush(channel); @@ -163,13 +170,14 @@ load_usb(BOOT_PAYLOAD *payload, char slot) { current_time = gettime(); if (diff_sec(start_time, current_time) >= 5) { kprintf("PC did not respond in time\n"); - res = 0; - goto end; + return res; } usb_recvbuffer_safe_ex(channel, &data, 1, 10); // 10 retries } + res = 1; + if (data == PC_READY) { kprintf("Respond with OK\n"); // Sometimes the PC can fail to receive the byte, this helps @@ -189,15 +197,14 @@ load_usb(BOOT_PAYLOAD *payload, char slot) { } kprintf("DOL size is %iB\n", size); - u8 *dol = (u8 *) malloc(size); - - if (!dol) { - res = 0; - goto end; + u8 *dol_file = (u8 *) malloc(size); + if (!dol_file) { + kprintf("Couldn't allocate memory for DOL file\n"); + return res; } kprintf("Receiving file...\n"); - unsigned char *pointer = dol; + unsigned char *pointer = dol_file; while (size > 0xF7D8) { usb_recvbuffer_safe(channel, (void *) pointer, 0xF7D8); size -= 0xF7D8; @@ -207,14 +214,10 @@ load_usb(BOOT_PAYLOAD *payload, char slot) { usb_recvbuffer_safe(channel, (void *) pointer, size); } - payload->dol = dol; - -end: + payload->dol_file = dol_file; return res; } -extern u8 __xfb[]; - void delay_exit() { // Wait while the d-pad down direction or reset button is held. @@ -273,11 +276,11 @@ main() { EXI_Sync(EXI_CHANNEL_0); EXI_Deselect(EXI_CHANNEL_0); EXI_Unlock(EXI_CHANNEL_0); + // Since we've disabled the Qoob, we wil reboot to the Nintendo IPL scan_all_buttons_held(); if (all_buttons_held & PAD_BUTTON_LEFT || SYS_ResetButtonDown()) { - // Since we've disabled the Qoob, we wil reboot to the Nintendo IPL kprintf("Skipped. Rebooting into original IPL...\n"); delay_exit(); return 0; @@ -297,36 +300,22 @@ main() { // Init payload. BOOT_PAYLOAD payload; - payload.dol = NULL; + payload.dol_file = NULL; payload.argv.argc = 0; payload.argv.length = 0; payload.argv.commandLine = NULL; payload.argv.argvMagic = ARGV_MAGIC; // Attempt to load from each device. - if (load_usb(&payload, 'B')) { - goto load; - } - - if (load_fat(&payload, "sdb", &__io_gcsdb, shortcut_index)) { - goto load; - } - - if (load_usb(&payload, 'A')) { - goto load; - } - - if (load_fat(&payload, "sda", &__io_gcsda, shortcut_index)) { - goto load; - } - - if (load_fat(&payload, "sd2", &__io_gcsd2, shortcut_index)) { - goto load; - } - -load: - if (!payload.dol) { - kprintf("No DOL found! Halting."); + int res = + (load_usb(&payload, 'B') || load_fat(&payload, "sdb", &__io_gcsdb, shortcut_index) + || load_usb(&payload, 'A') + || load_fat(&payload, "sda", &__io_gcsda, shortcut_index) + || load_fat(&payload, "sd2", &__io_gcsd2, shortcut_index)); + + if (!res || !payload.dol_file) { + // If we reach here, all attempts to load a DOL failed + kprintf("No DOL loaded! Halting."); while (true) { VIDEO_WaitVSync(); } @@ -352,19 +341,23 @@ main() { DCStoreRange(payload.argv.commandLine, payload.argv.length); } + // Load stub. memcpy((void *) STUB_ADDR, stub, (size_t) stub_size); DCStoreRange((void *) STUB_ADDR, (u32) stub_size); delay_exit(); + // Boot DOL. SYS_ResetSystem(SYS_SHUTDOWN, 0, FALSE); SYS_SwitchFiber( - (intptr_t) payload.dol, + (intptr_t) payload.dol_file, 0, (intptr_t) payload.argv.commandLine, payload.argv.length, STUB_ADDR, STUB_STACK ); + + // Will never reach here. return 0; } diff --git a/source/shortcut.h b/source/shortcut.h index 57fb752..a56b34b 100644 --- a/source/shortcut.h +++ b/source/shortcut.h @@ -5,8 +5,8 @@ #define NUM_SHORTCUTS 7 typedef struct { - u16 pad_buttons; - char *path; + const u16 pad_buttons; + const char *path; } SHORTCUT; extern SHORTCUT shortcuts[NUM_SHORTCUTS]; From bd5a0119fa5cdf206d95d9e615759b25fa1f96b9 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 01:31:27 -0500 Subject: [PATCH 11/22] better CLI arg parsing - Better parsing algo which ignores insignificant white space in CLI args. --- source/cli_args.c | 104 ++++++++++++++++++++++++++-------------------- source/cli_args.h | 2 +- source/main.c | 6 +-- 3 files changed, 62 insertions(+), 50 deletions(-) diff --git a/source/cli_args.c b/source/cli_args.c index fd30424..916c791 100644 --- a/source/cli_args.c +++ b/source/cli_args.c @@ -8,71 +8,83 @@ // 0 - Failure // 1 - OK/Empty int -parse_cli_args(struct __argv *argv, char *cli_option_str) { +parse_cli_args(struct __argv *argv, const char *cli_option_str) { kprintf("Parsing CLI args...\n"); argv->argc = 0; argv->length = 0; argv->commandLine = NULL; - char *cli = cli_option_str; - char *dol_argv[MAX_NUM_ARGV]; - int dol_argc = 0; - int size = strlen(cli_option_str); + const char *args[MAX_NUM_ARGV]; + size_t arg_lengths[MAX_NUM_ARGV]; + int argc = 0; + size_t argv_size = 1; - // Parse CLI file - // https://github.com/emukidid/swiss-gc/blob/a0fa06d81360ad6d173acd42e4dd5495e268de42/cube/swiss/source/swiss.c#L1236 - // First argument is at the beginning of the file - if (cli[0] != '\r' && cli[0] != '\n') { - dol_argv[dol_argc] = cli; - dol_argc++; - } + int found_arg_start = false; + size_t arg_start_index = 0; + size_t arg_end_index = 0; - // Search for the others after each newline - for (int i = 0; i < size; i++) { - if (cli[i] == '\r' || cli[i] == '\n') { - cli[i] = '\0'; - } else if (cli[i - 1] == '\0') { - dol_argv[dol_argc] = cli + i; - dol_argc++; - if (dol_argc >= MAX_NUM_ARGV) { - kprintf("Reached max of %i args.\n", MAX_NUM_ARGV); - break; - } - } - } + int eof = false; + for (size_t i = 0; !eof; i++) { + eof = cli_option_str[i] == '\0'; - kprintf("Found %i CLI args\n", dol_argc); + // Check if we are at the end of a line. + if (cli_option_str[i] == '\n' || eof) { + // Check if we ever found a start to the arg. + if (found_arg_start) { + // Record the arg. + size_t line_len = (arg_end_index - arg_start_index) + 1; + args[argc] = cli_option_str + arg_start_index; + arg_lengths[argc] = line_len; + argc++; + argv_size += line_len + 1; - if (!dol_argc) { - return 0; - } + if (argc == MAX_NUM_ARGV) { + kprintf("Reached max of %i args.\n", MAX_NUM_ARGV); + break; + } - // https://github.com/emukidid/swiss-gc/blob/f5319aab248287c847cb9468325ebcf54c993fb1/cube/swiss/source/aram/sidestep.c#L350 - argv->argc = dol_argc; - argv->length = 1; + // Reset. + found_arg_start = false; + } + } + // Check if we have a non-whitespace character. + else if (cli_option_str[i] != ' ' && cli_option_str[i] != '\t' + && cli_option_str[i] != '\r') { + // Record the start and end of the arg. + if (!found_arg_start) { + found_arg_start = true; + arg_start_index = i; + } + arg_end_index = i; + } + } - for (int i = 0; i < dol_argc; i++) { - size_t arg_length = strlen(dol_argv[i]) + 1; - argv->length += arg_length; + if (argc == 0) { + kprintf("No args found\n"); + return 1; } - kprintf("CLI argv size is %iB\n", argv->length); - argv->commandLine = (char *) malloc(argv->length); + kprintf("Found %i args. Size is %iB\n", argc, argv_size); - if (!argv->commandLine) { - kprintf("Couldn't allocate memory for CLI argv\n"); - argv->length = 0; + char *command_line = (char *) malloc(argv_size); + if (!command_line) { + kprintf("Couldn't allocate memory for args\n"); return 0; } - unsigned int position = 0; - for (int i = 0; i < dol_argc; i++) { - size_t arg_length = strlen(dol_argv[i]) + 1; - memcpy(argv->commandLine + position, dol_argv[i], arg_length); - position += arg_length; + size_t position = 0; + for (int i = 0; i < argc; i++) { + memcpy(command_line + position, args[i], arg_lengths[i]); + position += arg_lengths[i]; + command_line[position] = '\0'; + position += 1; } - argv->commandLine[argv->length - 1] = '\0'; + command_line[position] = '\0'; + + argv->argc = argc; + argv->length = argv_size; + argv->commandLine = command_line; return 1; } \ No newline at end of file diff --git a/source/cli_args.h b/source/cli_args.h index fc97273..a1f8d23 100644 --- a/source/cli_args.h +++ b/source/cli_args.h @@ -3,6 +3,6 @@ #include int -parse_cli_args(struct __argv *argv, char *cli_option_str); +parse_cli_args(struct __argv *argv, const char *cli_option_str); #endif diff --git a/source/main.c b/source/main.c index 174f551..3a59c33 100644 --- a/source/main.c +++ b/source/main.c @@ -45,7 +45,7 @@ read_dol_file(u8 **dol_file, const char *path) { } void -read_cli_file(char **cli_file, const char *dol_path) { +read_cli_file(const char **cli_file, const char *dol_path) { *cli_file = NULL; int path_length = strlen(dol_path); @@ -56,7 +56,7 @@ read_cli_file(char **cli_file, const char *dol_path) { path[path_length - 1] = 'i'; kprintf("Reading %s\n", path); - fs_read_file_string((const char **) cli_file, path); + fs_read_file_string(cli_file, path); } // 0 - Device should not be used. @@ -77,7 +77,7 @@ load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { } // Attempt to read CLI file. - char *cli_file; + const char *cli_file; read_cli_file(&cli_file, dol_path); // Parse CLI file. From 7cdbc5a7eab601cc3c0bf576ae98681b39036d9a Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 01:34:52 -0500 Subject: [PATCH 12/22] smarter cli file path finding - Will only attempt to rewrite DOL path to CLI path if it has `.dol` extension. --- source/main.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/source/main.c b/source/main.c index 3a59c33..f81fe25 100644 --- a/source/main.c +++ b/source/main.c @@ -48,12 +48,18 @@ void read_cli_file(const char **cli_file, const char *dol_path) { *cli_file = NULL; - int path_length = strlen(dol_path); - char path[path_length + 1]; - strcpy(path, dol_path); - path[path_length - 3] = 'c'; - path[path_length - 2] = 'l'; - path[path_length - 1] = 'i'; + size_t path_len = strlen(dol_path); + if (path_len < 5 || strncmp(dol_path + path_len - 4, ".dol", 4) != 0) { + kprintf("Not reading CLI file: DOL path does not end in \".dol\"\n"); + return; + } + + char path[path_len + 1]; + memcpy(path, dol_path, path_len - 3); + path[path_len - 3] = 'c'; + path[path_len - 2] = 'l'; + path[path_len - 1] = 'i'; + path[path_len] = '\0'; kprintf("Reading %s\n", path); fs_read_file_string(cli_file, path); From 111ff1c40b66b4e08cf7df7e297772b88ded306e Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 02:03:02 -0500 Subject: [PATCH 13/22] added debug mode - Debug mode replaces "hold Dpad-Down to delay exit" for better debugging experience. - Debug mode is enabled when Dpad-Down or Reset is held on boot. - Debug mode always prompts before exiting. - Debug mode increases logging verbosity. - Replaces `VERBOSE_LOGGING` define with `debug_enabled` global state flag. --- README.md | 5 +--- source/main.c | 71 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index ac78819..6a222ca 100644 --- a/README.md +++ b/README.md @@ -153,10 +153,7 @@ It will be saved as `boot.dol` and can be used in conjunction with the various ## Troubleshooting -gekkoboot displays useful diagnostic messages as it attempts to load the selected DOL. -But it's so fast you may not have time to read or even see them. -If you hold the down direction on the D-Pad, the messages will remain on screen -until you let go. +Enable debug mode by holding d-pad in the down direction. This will allow you to read the diagnostic messages as well as enable more verbose output. Look for warning messages about unrecognized configuration parameters, file read failures, etc. When choosing a shortcut button, beware that some software checks for buttons held at boot to alter certain behaviors. diff --git a/source/main.c b/source/main.c index f81fe25..6d1745d 100644 --- a/source/main.c +++ b/source/main.c @@ -15,10 +15,9 @@ #include "stub.h" -#define VERBOSE_LOGGING 0 - // Global State // -------------------- +int debug_enabled = false; u16 all_buttons_held; extern u8 __xfb[]; // -------------------- @@ -36,6 +35,28 @@ scan_all_buttons_held() { | PAD_ButtonsHeld(PAD_CHAN2) | PAD_ButtonsHeld(PAD_CHAN3)); } +void +wait_for_confirmation() { + // Wait until the A button or reset button is pressed. + int cur_state = true; + int last_state; + do { + VIDEO_WaitVSync(); + scan_all_buttons_held(); + last_state = cur_state; + cur_state = all_buttons_held & PAD_BUTTON_A; + } while (last_state || !cur_state); +} + +void +delay_exit() { + if (debug_enabled) { + // When debug is enabled, always wait for confirmation before exit. + kprintf("\nDEBUG: Press A to continue...\n"); + wait_for_confirmation(); + } +} + void read_dol_file(u8 **dol_file, const char *path) { *dol_file = NULL; @@ -224,22 +245,6 @@ load_usb(BOOT_PAYLOAD *payload, char slot) { return res; } -void -delay_exit() { - // Wait while the d-pad down direction or reset button is held. - if (all_buttons_held & PAD_BUTTON_DOWN) { - kprintf("(release d-pad down to continue)\n"); - } - if (SYS_ResetButtonDown()) { - kprintf("(release reset button to continue)\n"); - } - - while (all_buttons_held & PAD_BUTTON_DOWN || SYS_ResetButtonDown()) { - VIDEO_WaitVSync(); - scan_all_buttons_held(); - } -} - int main() { // GCVideo takes a while to boot up. @@ -286,6 +291,12 @@ main() { scan_all_buttons_held(); + // Check if d-pad down direction or reset button is held. + if (all_buttons_held & PAD_BUTTON_DOWN || SYS_ResetButtonDown()) { + kprintf("DEBUG: Debug enabled.\n"); + debug_enabled = true; + } + if (all_buttons_held & PAD_BUTTON_LEFT || SYS_ResetButtonDown()) { kprintf("Skipped. Rebooting into original IPL...\n"); delay_exit(); @@ -328,19 +339,21 @@ main() { } // Print DOL args. -#if VERBOSE_LOGGING - if (payload.argv.length > 0) { - kprintf("----------\n"); - size_t position = 0; - for (int i = 0; i < payload.argv.argc; ++i) { - kprintf("arg%i: %s\n", i, payload.argv.commandLine + position); - position += strlen(payload.argv.commandLine + position) + 1; + if (debug_enabled) { + if (payload.argv.length > 0) { + kprintf("\nDEBUG: About to print CLI args. Press A to continue...\n"); + wait_for_confirmation(); + kprintf("----------\n"); + size_t position = 0; + for (int i = 0; i < payload.argv.argc; ++i) { + kprintf("arg%i: %s\n", i, payload.argv.commandLine + position); + position += strlen(payload.argv.commandLine + position) + 1; + } + kprintf("----------\n\n"); + } else { + kprintf("DEBUG: No CLI args\n"); } - kprintf("----------\n\n"); - } else { - kprintf("No CLI args\n"); } -#endif // Prepare DOL argv. if (payload.argv.length > 0) { From 6e79a9bdcf53b396f415698c6b11b7cac43a0a2a Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 02:10:14 -0500 Subject: [PATCH 14/22] improved logging - Added additional logging and improved specificity of existing. --- source/filesystem.c | 12 ++++++++---- source/main.c | 29 +++++++++++++++++++++++++---- source/shortcut.c | 14 +++++++------- source/shortcut.h | 1 + 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/source/filesystem.c b/source/filesystem.c index afb69ee..973878e 100644 --- a/source/filesystem.c +++ b/source/filesystem.c @@ -31,13 +31,17 @@ _fs_read_file(void **contents_, const char *path, int is_string) { FIL file; FRESULT result = f_open(&file, path, FA_READ); if (result != FR_OK) { - kprintf("Failed to open file: %s\n", get_fs_result_message(result)); + if (result == FR_NO_FILE || result == FR_NO_PATH) { + kprintf("File not found\n"); + return FS_NO_FILE; + } + kprintf("->> !! Failed to open file: %s\n", get_fs_result_message(result)); return result; } size_t size = f_size(&file); if (size <= 0) { - kprintf("File is empty\n"); + kprintf("->> !! File is empty\n"); return FS_FILE_EMPTY; } kprintf("File size: %iB\n", size); @@ -45,7 +49,7 @@ _fs_read_file(void **contents_, const char *path, int is_string) { // Malloc an extra byte if we are reading as a string incase we need to add NUL character. void *contents = malloc(size + (is_string ? 1 : 0)); if (!contents) { - kprintf("Couldn't allocate memory for file\n"); + kprintf("->> !! Couldn't allocate memory for file\n"); return FS_NOT_ENOUGH_MEMORY; } @@ -53,7 +57,7 @@ _fs_read_file(void **contents_, const char *path, int is_string) { UINT _; result = f_read(&file, contents, size, &_); if (result != FR_OK) { - kprintf("Failed to read file: %s\n", get_fs_result_message(result)); + kprintf("->> !! Failed to read file: %s\n", get_fs_result_message(result)); return result; } diff --git a/source/main.c b/source/main.c index 6d1745d..961995b 100644 --- a/source/main.c +++ b/source/main.c @@ -61,8 +61,11 @@ void read_dol_file(u8 **dol_file, const char *path) { *dol_file = NULL; - kprintf("Reading %s\n", path); + kprintf("Trying DOL file: %s\n", path); fs_read_file((void **) dol_file, path); + if (dol_file) { + kprintf("->> DOL loaded\n"); + } } void @@ -82,8 +85,11 @@ read_cli_file(const char **cli_file, const char *dol_path) { path[path_len - 1] = 'i'; path[path_len] = '\0'; - kprintf("Reading %s\n", path); + kprintf("Trying CLI file: %s\n", path); fs_read_file_string(cli_file, path); + if (cli_file) { + kprintf("->> CLI file loaded\n"); + } } // 0 - Device should not be used. @@ -103,9 +109,14 @@ load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { return 0; } + kprintf("Will boot DOL\n"); + // Attempt to read CLI file. const char *cli_file; read_cli_file(&cli_file, dol_path); + if (!cli_file) { + kprintf("->> No CLI file\n"); + } // Parse CLI file. if (cli_file) { @@ -139,7 +150,7 @@ load_fat( char volume_label[256]; fs_get_volume_label(slot_name, volume_label); - kprintf("Mounted %s as %s\n", volume_label, slot_name); + kprintf("Mounted \"%s\" volume from %s\n", volume_label, slot_name); // Attempt to load shortcut files. res = load_shortcut_files(payload, shortcut_index); @@ -298,7 +309,7 @@ main() { } if (all_buttons_held & PAD_BUTTON_LEFT || SYS_ResetButtonDown()) { - kprintf("Skipped. Rebooting into original IPL...\n"); + kprintf("Skip enabled. Rebooting into original IPL...\n\n"); delay_exit(); return 0; } @@ -310,10 +321,14 @@ main() { int shortcut_index = 0; for (int i = 1; i < NUM_SHORTCUTS; i++) { if (all_buttons_held & shortcuts[i].pad_buttons) { + kprintf("->> \"%s\" shortcut selected\n", shortcuts[i].name); shortcut_index = i; break; } } + if (shortcut_index == 0) { + kprintf("->> Using default shortcut\n"); + } // Init payload. BOOT_PAYLOAD payload; @@ -360,12 +375,18 @@ main() { DCStoreRange(payload.argv.commandLine, payload.argv.length); } + kprintf("Booting DOL...\n"); + // Load stub. memcpy((void *) STUB_ADDR, stub, (size_t) stub_size); DCStoreRange((void *) STUB_ADDR, (u32) stub_size); delay_exit(); + if (debug_enabled) { + kprintf("DEBUG: Loading DOL...\n"); + } + // Boot DOL. SYS_ResetSystem(SYS_SHUTDOWN, 0, FALSE); SYS_SwitchFiber( diff --git a/source/shortcut.c b/source/shortcut.c index 28803e8..78b4eb7 100644 --- a/source/shortcut.c +++ b/source/shortcut.c @@ -2,13 +2,13 @@ #include SHORTCUT shortcuts[NUM_SHORTCUTS] = { - {0, "/ipl.dol"}, - {PAD_BUTTON_A, "/a.dol"}, - {PAD_BUTTON_B, "/b.dol"}, - {PAD_BUTTON_X, "/x.dol"}, - {PAD_BUTTON_Y, "/y.dol"}, - {PAD_TRIGGER_Z, "/z.dol"}, - {PAD_BUTTON_START, "/start.dol"}, + {"default", 0, "/ipl.dol"}, + {"A", PAD_BUTTON_A, "/a.dol"}, + {"B", PAD_BUTTON_B, "/b.dol"}, + {"X", PAD_BUTTON_X, "/x.dol"}, + {"Y", PAD_BUTTON_Y, "/y.dol"}, + {"Z", PAD_TRIGGER_Z, "/z.dol"}, + {"START", PAD_BUTTON_START, "/start.dol"}, // NOTE: Shouldn't use L, R or Joysticks as analog inputs are calibrated on boot. // Should also avoid D-Pad as it is used for special functionality. }; diff --git a/source/shortcut.h b/source/shortcut.h index a56b34b..ef652f4 100644 --- a/source/shortcut.h +++ b/source/shortcut.h @@ -5,6 +5,7 @@ #define NUM_SHORTCUTS 7 typedef struct { + const char *const name; const u16 pad_buttons; const char *path; } SHORTCUT; From b8dca294cc27e43394e7675e9042340f5d10e14d Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 02:15:16 -0500 Subject: [PATCH 15/22] friendlier device names - Use more verbose device names in logging. --- source/main.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/source/main.c b/source/main.c index 961995b..bfe2c2f 100644 --- a/source/main.c +++ b/source/main.c @@ -133,29 +133,29 @@ load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { int load_fat( BOOT_PAYLOAD *payload, - const char *slot_name, + const char *device_name, const DISC_INTERFACE *iface, int shortcut_index ) { int res = 0; - kprintf("Trying %s\n", slot_name); + kprintf("Trying %s\n", device_name); // Mount device. FS_RESULT result = fs_mount(iface); if (result != FS_OK) { - kprintf("Couldn't mount %s: %s\n", slot_name, get_fs_result_message(result)); + kprintf("Couldn't mount %s: %s\n", device_name, get_fs_result_message(result)); goto end; } char volume_label[256]; - fs_get_volume_label(slot_name, volume_label); - kprintf("Mounted \"%s\" volume from %s\n", volume_label, slot_name); + fs_get_volume_label(device_name, volume_label); + kprintf("Mounted \"%s\" volume from %s\n", volume_label, device_name); // Attempt to load shortcut files. res = load_shortcut_files(payload, shortcut_index); - kprintf("Unmounting %s\n", slot_name); + kprintf("Unmounting %s\n", device_name); fs_unmount(); end: @@ -340,10 +340,11 @@ main() { // Attempt to load from each device. int res = - (load_usb(&payload, 'B') || load_fat(&payload, "sdb", &__io_gcsdb, shortcut_index) + (load_usb(&payload, 'B') + || load_fat(&payload, "SD Gecko in slot B", &__io_gcsdb, shortcut_index) || load_usb(&payload, 'A') - || load_fat(&payload, "sda", &__io_gcsda, shortcut_index) - || load_fat(&payload, "sd2", &__io_gcsd2, shortcut_index)); + || load_fat(&payload, "SD Gecko in slot A", &__io_gcsda, shortcut_index) + || load_fat(&payload, "SD2SP2", &__io_gcsd2, shortcut_index)); if (!res || !payload.dol_file) { // If we reach here, all attempts to load a DOL failed From 60731795c8c6e9db69cab3f5ea00d0fdd69bdf31 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 02:29:41 -0500 Subject: [PATCH 16/22] stop searching devices on file failure - Handle failure to find shortcuts and failure to load shortcuts separately. - We can now explicitly decide when loading attempts should stop. - This change decides to stop loading attempts after a shortcut was found but failed to load. --- source/main.c | 58 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/source/main.c b/source/main.c index bfe2c2f..b4679c9 100644 --- a/source/main.c +++ b/source/main.c @@ -57,25 +57,30 @@ delay_exit() { } } -void +// 0 - Failure +// 1 - OK/Does not exist +int read_dol_file(u8 **dol_file, const char *path) { *dol_file = NULL; kprintf("Trying DOL file: %s\n", path); - fs_read_file((void **) dol_file, path); - if (dol_file) { + FS_RESULT result = fs_read_file((void **) dol_file, path); + if (result == FS_OK) { kprintf("->> DOL loaded\n"); } + return (result == FS_OK || result == FS_NO_FILE || result == FS_FILE_EMPTY); } -void +// 0 - Failure +// 1 - OK/Does not exist/Skipped +int read_cli_file(const char **cli_file, const char *dol_path) { *cli_file = NULL; size_t path_len = strlen(dol_path); if (path_len < 5 || strncmp(dol_path + path_len - 4, ".dol", 4) != 0) { kprintf("Not reading CLI file: DOL path does not end in \".dol\"\n"); - return; + return 1; } char path[path_len + 1]; @@ -86,10 +91,11 @@ read_cli_file(const char **cli_file, const char *dol_path) { path[path_len] = '\0'; kprintf("Trying CLI file: %s\n", path); - fs_read_file_string(cli_file, path); - if (cli_file) { + FS_RESULT result = fs_read_file_string(cli_file, path); + if (result == FS_OK) { kprintf("->> CLI file loaded\n"); } + return (result == FS_OK || result == FS_NO_FILE || result == FS_FILE_EMPTY); } // 0 - Device should not be used. @@ -99,11 +105,15 @@ load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { // Attempt to read shortcut paths from from mounted FAT device. u8 *dol_file = NULL; const char *dol_path = shortcuts[shortcut_index].path; - read_dol_file(&dol_file, dol_path); + if (!read_dol_file(&dol_file, dol_path)) { + return 1; + } if (!dol_file && shortcut_index != 0) { shortcut_index = 0; dol_path = shortcuts[shortcut_index].path; - read_dol_file(&dol_file, dol_path); + if (!read_dol_file(&dol_file, dol_path)) { + return 1; + } } if (!dol_file) { return 0; @@ -113,15 +123,20 @@ load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { // Attempt to read CLI file. const char *cli_file; - read_cli_file(&cli_file, dol_path); + if (!read_cli_file(&cli_file, dol_path)) { + return 1; + } if (!cli_file) { kprintf("->> No CLI file\n"); } // Parse CLI file. if (cli_file) { - parse_cli_args(&payload->argv, cli_file); + int res = parse_cli_args(&payload->argv, cli_file); free((void *) cli_file); + if (!res) { + return 1; + } } payload->dol_file = dol_file; @@ -346,12 +361,21 @@ main() { || load_fat(&payload, "SD Gecko in slot A", &__io_gcsda, shortcut_index) || load_fat(&payload, "SD2SP2", &__io_gcsd2, shortcut_index)); - if (!res || !payload.dol_file) { - // If we reach here, all attempts to load a DOL failed - kprintf("No DOL loaded! Halting."); - while (true) { - VIDEO_WaitVSync(); - } + if (!res) { + // If we reach here, we did not find a device with any shortcut files. + kprintf("\nNo shortcuts found\n"); + kprintf("Press A to reboot into onboard IPL...\n\n"); + wait_for_confirmation(); + return 0; + } + + if (!payload.dol_file) { + // If we reach here, we found a device with shortcut files but failed to load any + // shortcut. + kprintf("\nUnable to load shortcut\n"); + kprintf("Press A to reboot into onboard IPL...\n\n"); + wait_for_confirmation(); + return 0; } // Print DOL args. From 5a9a9aa8cc412fd3de60f0d535619273c042a3e9 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Aug 2022 20:37:59 -0500 Subject: [PATCH 17/22] add argv flag support - `--debug` flag enables debug mode. - `--ask` flag waits for initial input. --- source/main.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/source/main.c b/source/main.c index b4679c9..2d67968 100644 --- a/source/main.c +++ b/source/main.c @@ -272,7 +272,7 @@ load_usb(BOOT_PAYLOAD *payload, char slot) { } int -main() { +main(int _argc, char **_argv) { // GCVideo takes a while to boot up. // If VIDEO_GetPreferredMode is called before it's done, // it will not see the "component cable", and default to interlaced mode, @@ -315,7 +315,26 @@ main() { EXI_Unlock(EXI_CHANNEL_0); // Since we've disabled the Qoob, we wil reboot to the Nintendo IPL - scan_all_buttons_held(); + // Check argv flags. + int should_ask = false; + for (int i = 0; i < _argc; i++) { + if (strcmp(_argv[i], "--debug") == 0) { + kprintf("DEBUG: Debug enabled by flag.\n"); + debug_enabled = true; + } else if (strcmp(_argv[i], "--ask") == 0) { + should_ask = true; + } + } + + if (should_ask) { + kprintf("DEBUG: Press button...\n"); + do { + VIDEO_WaitVSync(); + scan_all_buttons_held(); + } while (all_buttons_held == 0 || all_buttons_held == PAD_BUTTON_DOWN); + } else { + scan_all_buttons_held(); + } // Check if d-pad down direction or reset button is held. if (all_buttons_held & PAD_BUTTON_DOWN || SYS_ResetButtonDown()) { From 6e8a424af1bfc7fa7c833c3da0ab85b3573e2e59 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 02:34:07 -0500 Subject: [PATCH 18/22] allow parse_cli_args to parse multiple strings - Update `parse_cli_args` to accept array of strings. - Prepares for future in which CLI args may come from multiple sources. --- source/cli_args.c | 75 +++++++++++++++++++++++++---------------------- source/cli_args.h | 2 +- source/main.c | 2 +- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/source/cli_args.c b/source/cli_args.c index 916c791..d254c08 100644 --- a/source/cli_args.c +++ b/source/cli_args.c @@ -8,7 +8,7 @@ // 0 - Failure // 1 - OK/Empty int -parse_cli_args(struct __argv *argv, const char *cli_option_str) { +parse_cli_args(struct __argv *argv, const char **cli_options_strs, int num_cli_options_strs) { kprintf("Parsing CLI args...\n"); argv->argc = 0; @@ -20,46 +20,51 @@ parse_cli_args(struct __argv *argv, const char *cli_option_str) { int argc = 0; size_t argv_size = 1; - int found_arg_start = false; - size_t arg_start_index = 0; - size_t arg_end_index = 0; - - int eof = false; - for (size_t i = 0; !eof; i++) { - eof = cli_option_str[i] == '\0'; - - // Check if we are at the end of a line. - if (cli_option_str[i] == '\n' || eof) { - // Check if we ever found a start to the arg. - if (found_arg_start) { - // Record the arg. - size_t line_len = (arg_end_index - arg_start_index) + 1; - args[argc] = cli_option_str + arg_start_index; - arg_lengths[argc] = line_len; - argc++; - argv_size += line_len + 1; - - if (argc == MAX_NUM_ARGV) { - kprintf("Reached max of %i args.\n", MAX_NUM_ARGV); - break; - } + for (int oi = 0; oi < num_cli_options_strs; ++oi) { + const char *cli_option_str = cli_options_strs[oi]; + int found_arg_start = false; + size_t arg_start_index = 0; + size_t arg_end_index = 0; + + int eof = false; + for (size_t i = 0; !eof; i++) { + eof = cli_option_str[i] == '\0'; + + // Check if we are at the end of a line. + if (cli_option_str[i] == '\n' || eof) { + // Check if we ever found a start to the arg. + if (found_arg_start) { + // Record the arg. + size_t line_len = (arg_end_index - arg_start_index) + 1; + args[argc] = cli_option_str + arg_start_index; + arg_lengths[argc] = line_len; + argc++; + argv_size += line_len + 1; + + if (argc == MAX_NUM_ARGV) { + kprintf("Reached max of %i args.\n", MAX_NUM_ARGV); + goto loop_break; + } - // Reset. - found_arg_start = false; + // Reset. + found_arg_start = false; + } } - } - // Check if we have a non-whitespace character. - else if (cli_option_str[i] != ' ' && cli_option_str[i] != '\t' - && cli_option_str[i] != '\r') { - // Record the start and end of the arg. - if (!found_arg_start) { - found_arg_start = true; - arg_start_index = i; + // Check if we have a non-whitespace character. + else if (cli_option_str[i] != ' ' && cli_option_str[i] != '\t' + && cli_option_str[i] != '\r') { + // Record the start and end of the arg. + if (!found_arg_start) { + found_arg_start = true; + arg_start_index = i; + } + arg_end_index = i; } - arg_end_index = i; } } +loop_break: + if (argc == 0) { kprintf("No args found\n"); return 1; diff --git a/source/cli_args.h b/source/cli_args.h index a1f8d23..b2360b4 100644 --- a/source/cli_args.h +++ b/source/cli_args.h @@ -3,6 +3,6 @@ #include int -parse_cli_args(struct __argv *argv, const char *cli_option_str); +parse_cli_args(struct __argv *argv, const char **cli_options_strs, int num_cli_options_strs); #endif diff --git a/source/main.c b/source/main.c index 2d67968..d02a985 100644 --- a/source/main.c +++ b/source/main.c @@ -132,7 +132,7 @@ load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { // Parse CLI file. if (cli_file) { - int res = parse_cli_args(&payload->argv, cli_file); + int res = parse_cli_args(&payload->argv, &cli_file, 1); free((void *) cli_file); if (!res) { return 1; From 697aaf90387cfe2873b18b2bd662f328a24f7061 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 02:37:36 -0500 Subject: [PATCH 19/22] added config file support - Allows for an `gekkoboot.ini` file at the root of devices to configure behaviors. - Rather than shortcuts requiring DOLs to be renamed to hardcoded filenames (`a.dol`), the config file can specify the mapping of button to DOL filepaths. - Besides DOLs, config can also specify shortcuts for explicitly booting into onboard IPL or USB Gecko (in the future). - Config can specify CLI args for DOLs. - Config can enable debug mode. --- README.md | 134 +++++++++++++----- meson.build | 3 + source/config.c | 97 +++++++++++++ source/config.h | 17 +++ source/inih/LICENSE.txt | 27 ++++ source/inih/README.md | 1 + source/inih/ini.c | 304 ++++++++++++++++++++++++++++++++++++++++ source/inih/ini.h | 182 ++++++++++++++++++++++++ source/main.c | 158 +++++++++++++++++++-- source/shortcut.c | 14 +- source/shortcut.h | 4 +- source/types.c | 16 +++ source/types.h | 28 ++++ 13 files changed, 934 insertions(+), 51 deletions(-) create mode 100644 source/config.c create mode 100644 source/config.h create mode 100644 source/inih/LICENSE.txt create mode 100644 source/inih/README.md create mode 100644 source/inih/ini.c create mode 100644 source/inih/ini.h create mode 100644 source/types.c create mode 100644 source/types.h diff --git a/README.md b/README.md index 6a222ca..6937dd1 100644 --- a/README.md +++ b/README.md @@ -11,36 +11,96 @@ Supported targets: ## Usage -gekkoboot will attempt to load DOLs from the following locations in order: -- USB Gecko in Card Slot B -- SD Gecko in Card Slot B -- USB Gecko in Card Slot A -- SD Gecko in Card Slot A -- SD2SP2 +gekkoboot will attempt to load DOLs from the following devices in order: +1. USB Gecko in Card Slot B +2. SD Gecko in Card Slot B +3. USB Gecko in Card Slot A +4. SD Gecko in Card Slot A +5. SD2SP2 -You can use button shortcuts to keep alternate software on quick access. -When loading from an SD card, gekkoboot will look for and load different filenames -depending on what buttons are being held: +For each device, gekkoboot checks for the presence of the following files in order: +1. `gekkoboot.ini` +2. File matching held button (`a.dol`, `x.dol`, etc.) +3. `ipl.dol` - Button Held | File Loaded --------------|-------------- - *None* | `/ipl.dol` - A | `/a.dol` - B | `/b.dol` - X | `/x.dol` - Y | `/y.dol` - Z | `/z.dol` - Start | `/start.dol` +If no file is found, the next device is attempted. After all devices are attempted, the system will reboot into the onboard IPL (original GameCube intro and menu). -CLI files are also supported. +Creating an `gekkoboot.ini` configuration file is the recommended route. -If the selected shortcut file cannot be loaded, gekkoboot will fall back to -`/ipl.dol`. If that cannot be loaded either, the next device will be searched. -If all fails, gekkoboot will reboot to the onboard IPL (original GameCube intro -and menu). +> [!IMPORTANT] +> Be careful not to touch any of the analog controls (sticks and triggers) when powering on as this is when they are calibrated. -Holding D-Pad Left or the reset button will skip gekkoboot functionality and -reboot straight into the onboard IPL. +**Something not working?** See the [troubleshooting section](#troubleshooting). + +### Configuration + +Create a file named `gekkoboot.ini` at the root of your SD card. This file should be formatted using INI syntax, which is basic `NAME=some value`. The following parameters are supported: + +> [!IMPORTANT] +> All values are case sensitive. + + Parameter | Description +------------------|------------- + `{SHORTCUT}` | Shortcut action. + `{SHORTCUT}_ARG` | CLI argument passed to shortcut DOL. + `DEBUG` | Set to `1` to enable debug mode. + +Replace `{SHORTCUT}` with one of the following: `A`, `B`, `X`, `Y`, `Z`, `START`, `DEFAULT`. + +Shortcut action my be one of: + + Value | Description +------------|------------- + A filepath | Path to a DOL to load. All paths are relative to the device root. + `ONBOARD` | Reboot into the onboard IPL (original GameCube intro and menu). + +Holding a button during boot will activate the shortcut with the matching name. If no button is held, `DEFAULT` is used (if unspecified, the `DEFAULT` action is `/ipl.dol`). + +Specify one `{SHORTCUT}_ARG` per CLI argument. You may specify as many as you like. Also consider using a CLI file. + +For example, this configuration would boot straight into Swiss by default, +or GBI if you held B, or the original GameCube intro if you held Z: + +```ini +DEFAULT=swiss.dol +B=gbi.dol +Z=ONBOARD +``` + +This configuration would boot into the original GameCube intro by default, +or Swiss if you held Z, or GBI if you held X or Y: + +```ini +DEFAULT=ONBOARD +Z=swiss.dol +X=gbi.dol +X_ARG=--zoom=2 +Y=gbi.dol +Y_ARG=--zoom=3 +Y_ARG=--vfilter=.5:.5:.0:.5:.0:.5 +``` + +Comments may be included by starting the line with the `#` character. These lines will be ignored. + +See the [Special Features](#special-features) section for additional functionality. + +### Button Files + +> [!IMPORTANT] +> If a config file is found, this behavior does not apply. + +The following buttons can be used as shortcuts to load the associated filenames when a configuration file is not present: + + Button Held | File Loaded +-------------|-------------- + A | `a.dol` + B | `b.dol` + X | `x.dol` + Y | `y.dol` + Z | `z.dol` + Start | `start.dol` + +Holding a button during boot will activate the shortcut. If no button is held, `ipl.dol` is used. For example, this configuration would boot straight into Swiss by default, or GBI if you held B, or the original GameCube intro if you held D-Pad Left: @@ -52,25 +112,27 @@ or Swiss if you held Z, or GBI if you held B: - `/z.dol` - Swiss - `/b.dol` - GBI -**Pro-tip:** You can prevent files from showing in Swiss by marking them as -hidden files on the SD card. +> [!TIP] +> You can prevent files from showing in Swiss by marking them as hidden files on the SD card. -If you hold multiple buttons, the highest in the table takes priority. -Be careful not to touch any of the analog controls (sticks and triggers) when -powering on as this is when they are calibrated. +### Special Features -gekkoboot also acts as a server for @emukidid's [usb-load](https://github.com/emukidid/gc-usb-load), -should you want to use it for development purposes. +CLI files are supported. They will be append after any CLI args defined in the config file. -**Something not working?** See the [troubleshooting section](#troubleshooting). +Holding D-Pad Left or the reset button will skip gekkoboot functionality and +reboot straight into the onboard IPL. +Holding D-Pad Down enables debug mode. You can hold this along with a shortcut button. + +gekkoboot also acts as a server for @emukidid's [usb-load](https://github.com/emukidid/gc-usb-load), +should you want to use it for development purposes. ## Installation Download and extract the [latest release]. -Prepare your SD card by copying DOLs onto the SD card and renaming them -according the table above. +Prepare your SD card by creating a configuration file and/or copying DOLs onto the SD card and renaming them +according to either the [Configuration](#configuration) or [Button Files](#button-files) instructions above. ### PicoBoot @@ -155,6 +217,8 @@ It will be saved as `boot.dol` and can be used in conjunction with the various Enable debug mode by holding d-pad in the down direction. This will allow you to read the diagnostic messages as well as enable more verbose output. Look for warning messages about unrecognized configuration parameters, file read failures, etc. +If multiple shortcut buttons are held, the highest in the table takes priority. + When choosing a shortcut button, beware that some software checks for buttons held at boot to alter certain behaviors. If your software behaves differently when booted through gekkoboot, ensure the diff --git a/meson.build b/meson.build index e9944b4..3b154b8 100644 --- a/meson.build +++ b/meson.build @@ -55,10 +55,13 @@ gekkoboot = executable( 'source/filesystem.c', 'source/shortcut.c', 'source/cli_args.c', + 'source/config.c', + 'source/types.c', 'source/fatfs/ff.c', 'source/fatfs/ffsystem.c', 'source/fatfs/ffunicode.c', 'source/ffshim.c', + 'source/inih/ini.c', version_file, font, dependencies: [ diff --git a/source/config.c b/source/config.c new file mode 100644 index 0000000..252c30f --- /dev/null +++ b/source/config.c @@ -0,0 +1,97 @@ +#include "config.h" +#include "filesystem.h" +#include "inih/ini.h" +#include +#include +#include + +const char *default_config_path = "/gekkoboot.ini"; + +void +default_config(CONFIG *config) { + // TODO: Should free strings if this function will ever be used more than once. + config->debug_enabled = false; + + for (int i = 0; i < NUM_SHORTCUTS; ++i) { + config->shortcut_actions[i].type = BOOT_TYPE_NONE; + config->shortcut_actions[i].dol_path = NULL; + config->shortcut_actions[i].dol_cli_options_strs = NULL; + config->shortcut_actions[i].num_dol_cli_options_strs = 0; + } + config->shortcut_actions[0].type = BOOT_TYPE_DOL; + config->shortcut_actions[0].dol_path = shortcuts[0].path; +} + +void +handle_boot_action(const char *value, BOOT_ACTION *action) { + if (strcmp(value, "ONBOARD") == 0) { + action->type = BOOT_TYPE_ONBOARD; + } else { + action->type = BOOT_TYPE_DOL; + action->dol_path = strdup(value); + } +} + +void +handle_cli_options(const char *value, BOOT_ACTION *action) { + if (action->num_dol_cli_options_strs % 100 == 0) { + action->dol_cli_options_strs = (const char **) realloc( + action->dol_cli_options_strs, + (action->num_dol_cli_options_strs + 100) * sizeof(const char *) + ); + } + action->dol_cli_options_strs[action->num_dol_cli_options_strs++] = strdup(value); +} + +// 0 - Failure +// 1 - OK +int +ini_parse_handler(void *_config, const char *section, const char *name, const char *value) { + CONFIG *config = (CONFIG *) _config; + + if (strcmp(name, "DEBUG") == 0) { + config->debug_enabled = strcmp(value, "1") == 0; + return 1; + } + + for (int i = 0; i < NUM_SHORTCUTS; ++i) { + if (strcmp(name, shortcuts[i].config_name) == 0) { + handle_boot_action(value, &config->shortcut_actions[i]); + return 1; + } + if (strcmp(name, shortcuts[i].config_cli_name) == 0) { + handle_cli_options(value, &config->shortcut_actions[i]); + return 1; + } + } + + kprintf("->> !! Unknown config entry: %s\n", name); + // return 0; + return 1; +} + +// 0 - Failure +// 1 - OK +int +parse_config(CONFIG *config, const char *config_str) { + default_config(config); + return ini_parse_string(config_str, ini_parse_handler, config) == 0; +} + +void +print_config(CONFIG *config) { + kprintf("debug_enabled: %i\n", config->debug_enabled); + for (int i = 0; i < NUM_SHORTCUTS; ++i) { + kprintf("shortcuts[%s].action: %s\n", + shortcuts[i].name, + get_boot_type_name(config->shortcut_actions[i].type)); + if (config->shortcut_actions[i].type == BOOT_TYPE_DOL) { + kprintf("shortcuts[%s].dol_path: %s\n", + shortcuts[i].name, + config->shortcut_actions[i].dol_path); + // kprintf("shortcuts[%s].num_dol_cli_options_strs: %i\n", + // shortcuts[i].name, + // config->shortcut_actions[i].num_dol_cli_options_strs); + } + } +} diff --git a/source/config.h b/source/config.h new file mode 100644 index 0000000..b50122c --- /dev/null +++ b/source/config.h @@ -0,0 +1,17 @@ +#ifndef INC_CONFIG_H +#define INC_CONFIG_H +#include "shortcut.h" +#include "types.h" + +typedef struct { + int debug_enabled; + BOOT_ACTION shortcut_actions[NUM_SHORTCUTS]; +} CONFIG; + +extern const char *default_config_path; +int +parse_config(CONFIG *config, const char *config_str); +void +print_config(CONFIG *config); + +#endif diff --git a/source/inih/LICENSE.txt b/source/inih/LICENSE.txt new file mode 100644 index 0000000..cb7ee2d --- /dev/null +++ b/source/inih/LICENSE.txt @@ -0,0 +1,27 @@ + +The "inih" library is distributed under the New BSD license: + +Copyright (c) 2009, Ben Hoyt +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Ben Hoyt nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/source/inih/README.md b/source/inih/README.md new file mode 100644 index 0000000..b472277 --- /dev/null +++ b/source/inih/README.md @@ -0,0 +1 @@ +https://github.com/benhoyt/inih diff --git a/source/inih/ini.c b/source/inih/ini.c new file mode 100644 index 0000000..509952d --- /dev/null +++ b/source/inih/ini.c @@ -0,0 +1,304 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#if INI_CUSTOM_ALLOCATOR +#include +void* ini_malloc(size_t size); +void ini_free(void* ptr); +void* ini_realloc(void* ptr, size_t size); +#else +#include +#define ini_malloc malloc +#define ini_free free +#define ini_realloc realloc +#endif +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to NUL at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Similar to strncpy, but ensures dest (size bytes) is + NUL-terminated, and doesn't pad with NULs. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) + dest[i] = src[i]; + dest[i] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + size_t max_line = INI_MAX_LINE; +#else + char* line; + size_t max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC && !INI_USE_STACK + char* new_line; + size_t offset; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)ini_malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, (int)max_line, stream) != NULL) { +#if INI_ALLOW_REALLOC && !INI_USE_STACK + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = ini_realloc(line, max_line); + if (!new_line) { + ini_free(line); + return -2; + } + line = new_line; + if (reader(line + offset, (int)(max_line - offset), stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(start, NULL); + if (*end) + *end = '\0'; + rstrip(start); +#endif + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; +#if INI_CALL_HANDLER_ON_NEW_SECTION + if (!HANDLER(user, section, NULL, NULL) && !error) + error = lineno; +#endif + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ +#if INI_ALLOW_NO_VALUE + *end = '\0'; + name = rstrip(start); + if (!HANDLER(user, section, name, NULL) && !error) + error = lineno; +#else + error = lineno; +#endif + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + ini_free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} diff --git a/source/inih/ini.h b/source/inih/ini.h new file mode 100644 index 0000000..01efa6b --- /dev/null +++ b/source/inih/ini.h @@ -0,0 +1,182 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#ifndef INI_H +#define INI_H + +/* TODO: Move these to preprocessor defines */ +/* https://github.com/benhoyt/inih/blob/master/README.md#compile-time-options */ +#define INI_ALLOW_MULTILINE 0 + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Visibility symbols, required for Windows DLLs */ +#ifndef INI_API +#if defined _WIN32 || defined __CYGWIN__ +# ifdef INI_SHARED_LIB +# ifdef INI_SHARED_LIB_BUILDING +# define INI_API __declspec(dllexport) +# else +# define INI_API __declspec(dllimport) +# endif +# else +# define INI_API +# endif +#else +# if defined(__GNUC__) && __GNUC__ >= 4 +# define INI_API __attribute__ ((visibility ("default"))) +# else +# define INI_API +# endif +#endif +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +INI_API int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +INI_API int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See https://github.com/benhoyt/inih/issues/21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +#define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Nonzero to call the handler at the start of each new section (with + name and value NULL). Default is to only call the handler on + each name=value pair. */ +#ifndef INI_CALL_HANDLER_ON_NEW_SECTION +#define INI_CALL_HANDLER_ON_NEW_SECTION 0 +#endif + +/* Nonzero to allow a name without a value (no '=' or ':' on the line) and + call the handler with value NULL in this case. Default is to treat + no-value lines as an error. */ +#ifndef INI_ALLOW_NO_VALUE +#define INI_ALLOW_NO_VALUE 0 +#endif + +/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory + allocation functions (INI_USE_STACK must also be 0). These functions must + have the same signatures as malloc/free/realloc and behave in a similar + way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ +#ifndef INI_CUSTOM_ALLOCATOR +#define INI_CUSTOM_ALLOCATOR 0 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* INI_H */ diff --git a/source/main.c b/source/main.c index d02a985..ab55d66 100644 --- a/source/main.c +++ b/source/main.c @@ -1,6 +1,8 @@ #include "cli_args.h" +#include "config.h" #include "filesystem.h" #include "shortcut.h" +#include "types.h" #include "version.h" #include #include @@ -22,11 +24,6 @@ u16 all_buttons_held; extern u8 __xfb[]; // -------------------- -typedef struct { - u8 *dol_file; - struct __argv argv; -} BOOT_PAYLOAD; - void scan_all_buttons_held() { PAD_ScanPads(); @@ -71,6 +68,20 @@ read_dol_file(u8 **dol_file, const char *path) { return (result == FS_OK || result == FS_NO_FILE || result == FS_FILE_EMPTY); } +// 0 - Failure +// 1 - OK/Does not exist +int +read_config_file(const char **config_file, const char *path) { + *config_file = NULL; + + kprintf("Trying config file: %s\n", path); + FS_RESULT result = fs_read_file_string(config_file, path); + if (result == FS_OK) { + kprintf("->> Config loaded\n"); + } + return (result == FS_OK || result == FS_NO_FILE || result == FS_FILE_EMPTY); +} + // 0 - Failure // 1 - OK/Does not exist/Skipped int @@ -98,6 +109,119 @@ read_cli_file(const char **cli_file, const char *dol_path) { return (result == FS_OK || result == FS_NO_FILE || result == FS_FILE_EMPTY); } +// 0 - Device should not be used. +// 1 - Device should be used. +int +load_config(BOOT_PAYLOAD *payload, int shortcut_index) { + // Attempt to read config file from mounted FAT device. + const char *config_file; + if (!read_config_file(&config_file, default_config_path)) { + return 1; + } + if (!config_file) { + return 0; + } + + // Config file was found. + // Default to no action in case of failure. + int res = 1; + payload->type = BOOT_TYPE_NONE; + + // Parse config file. + CONFIG config; + if (!parse_config(&config, config_file)) { + kprintf("->> !! Failed to parse config file\n"); + return res; + } + + // Set global state. + if (config.debug_enabled && !debug_enabled) { + kprintf("DEBUG: Debug enabled by config.\n"); + debug_enabled = true; + } + + // Print config. + if (debug_enabled) { + kprintf("\nDEBUG: About to print config. Press A to continue...\n"); + wait_for_confirmation(); + kprintf("----------\n"); + print_config(&config); + kprintf("----------\n\n"); + kprintf("DEBUG: Done printing. Press A to continue...\n"); + wait_for_confirmation(); + } + + // Choose boot action. + if (config.shortcut_actions[shortcut_index].type == BOOT_TYPE_NONE) { + kprintf("\"%s\" shortcut not configured\n", shortcuts[shortcut_index].name); + shortcut_index = 0; + } + kprintf("->> Using \"%s\" shortcut\n", shortcuts[shortcut_index].name); + BOOT_ACTION *action = &config.shortcut_actions[shortcut_index]; + + // Process boot action. + if (action->type == BOOT_TYPE_ONBOARD) { + kprintf("->> Shortcut action: Reboot to onboard IPL\n"); + payload->type = BOOT_TYPE_ONBOARD; + return res; + } + + if (action->type != BOOT_TYPE_DOL) { + // Should never happen. + kprintf("->> !! Internal Error: Unexpeted boot type: %i\n", action->type); + return res; + } + + kprintf("->> Shortcut action: Boot DOL\n"); + + // Read DOL file. + u8 *dol_file; + if (!read_dol_file(&dol_file, action->dol_path) || !dol_file) { + kprintf("->> !! Unable to read DOL\n"); + return res; + } + + // Attempt to read CLI file. + const char *cli_file; + if (!read_cli_file(&cli_file, action->dol_path)) { + return res; + } + if (!cli_file) { + kprintf("->> No CLI file\n"); + } + + // Combine CLI options from config and CLI file. + const char **cli_options_strs = NULL; + int num_cli_options_strs = 0; + if (action->num_dol_cli_options_strs > 0) { + cli_options_strs = action->dol_cli_options_strs; + num_cli_options_strs = action->num_dol_cli_options_strs; + } + if (cli_file) { + cli_options_strs = + realloc(cli_options_strs, + (num_cli_options_strs + 1) * sizeof(const char *)); + cli_options_strs[num_cli_options_strs++] = cli_file; + } + + // Parse CLI options. + if (num_cli_options_strs > 0) { + int parse_res = + parse_cli_args(&payload->argv, cli_options_strs, num_cli_options_strs); + if (cli_file) { + free((void *) cli_file); + } + if (!parse_res) { + return res; + } + } + + // Return DOL boot payload. + payload->type = BOOT_TYPE_DOL; + payload->dol_file = dol_file; + return res; +} + // 0 - Device should not be used. // 1 - Device should be used. int @@ -139,6 +263,7 @@ load_shortcut_files(BOOT_PAYLOAD *payload, int shortcut_index) { } } + payload->type = BOOT_TYPE_DOL; payload->dol_file = dol_file; return 1; } @@ -167,8 +292,9 @@ load_fat( fs_get_volume_label(device_name, volume_label); kprintf("Mounted \"%s\" volume from %s\n", volume_label, device_name); - // Attempt to load shortcut files. - res = load_shortcut_files(payload, shortcut_index); + // Attempt to load config or shortcut files. + res = (load_config(payload, shortcut_index) || load_shortcut_files(payload, shortcut_index) + ); kprintf("Unmounting %s\n", device_name); fs_unmount(); @@ -267,6 +393,7 @@ load_usb(BOOT_PAYLOAD *payload, char slot) { usb_recvbuffer_safe(channel, (void *) pointer, size); } + payload->type = BOOT_TYPE_DOL; payload->dol_file = dol_file; return res; } @@ -366,6 +493,7 @@ main(int _argc, char **_argv) { // Init payload. BOOT_PAYLOAD payload; + payload.type = BOOT_TYPE_NONE; payload.dol_file = NULL; payload.argv.argc = 0; payload.argv.length = 0; @@ -388,7 +516,7 @@ main(int _argc, char **_argv) { return 0; } - if (!payload.dol_file) { + if (payload.type == BOOT_TYPE_NONE) { // If we reach here, we found a device with shortcut files but failed to load any // shortcut. kprintf("\nUnable to load shortcut\n"); @@ -397,6 +525,20 @@ main(int _argc, char **_argv) { return 0; } + if (payload.type == BOOT_TYPE_ONBOARD) { + kprintf("Rebooting into onboard IPL...\n\n"); + delay_exit(); + return 0; + } + + if (payload.type != BOOT_TYPE_DOL) { + // Should never happen. + kprintf("\n->> !! Internal Error: Unexpected boot type: %i\n", payload.type); + kprintf("Press A to reboot into onboard IPL...\n\n"); + wait_for_confirmation(); + return 0; + } + // Print DOL args. if (debug_enabled) { if (payload.argv.length > 0) { diff --git a/source/shortcut.c b/source/shortcut.c index 78b4eb7..9d1b35c 100644 --- a/source/shortcut.c +++ b/source/shortcut.c @@ -2,13 +2,13 @@ #include SHORTCUT shortcuts[NUM_SHORTCUTS] = { - {"default", 0, "/ipl.dol"}, - {"A", PAD_BUTTON_A, "/a.dol"}, - {"B", PAD_BUTTON_B, "/b.dol"}, - {"X", PAD_BUTTON_X, "/x.dol"}, - {"Y", PAD_BUTTON_Y, "/y.dol"}, - {"Z", PAD_TRIGGER_Z, "/z.dol"}, - {"START", PAD_BUTTON_START, "/start.dol"}, + {"default", 0, "DEFAULT", "DEFAULT_ARG", "/ipl.dol"}, + {"A", PAD_BUTTON_A, "A", "A_ARG", "/a.dol"}, + {"B", PAD_BUTTON_B, "B", "B_ARG", "/b.dol"}, + {"X", PAD_BUTTON_X, "X", "X_ARG", "/x.dol"}, + {"Y", PAD_BUTTON_Y, "Y", "Y_ARG", "/y.dol"}, + {"Z", PAD_TRIGGER_Z, "Z", "Z_ARG", "/z.dol"}, + {"START", PAD_BUTTON_START, "START", "START_ARG", "/start.dol"}, // NOTE: Shouldn't use L, R or Joysticks as analog inputs are calibrated on boot. // Should also avoid D-Pad as it is used for special functionality. }; diff --git a/source/shortcut.h b/source/shortcut.h index ef652f4..f6f03b0 100644 --- a/source/shortcut.h +++ b/source/shortcut.h @@ -7,7 +7,9 @@ typedef struct { const char *const name; const u16 pad_buttons; - const char *path; + const char *const config_name; + const char *const config_cli_name; + const char *const path; } SHORTCUT; extern SHORTCUT shortcuts[NUM_SHORTCUTS]; diff --git a/source/types.c b/source/types.c new file mode 100644 index 0000000..ed9c233 --- /dev/null +++ b/source/types.c @@ -0,0 +1,16 @@ +#include "types.h" + +#define NUM_BOOT_TYPE_NAMES 3 +const char *boot_type_names[NUM_BOOT_TYPE_NAMES] = { + /*BOOT_TYPE_NONE (0)*/ "NONE", + /*BOOT_TYPE_DOL (1)*/ "DOL", + /*BOOT_TYPE_ONBOARD (2)*/ "ONBOARD", +}; + +const char * +get_boot_type_name(BOOT_TYPE type) { + if (type < 0 || type >= NUM_BOOT_TYPE_NAMES) { + return "Unknown"; + } + return boot_type_names[type]; +} diff --git a/source/types.h b/source/types.h new file mode 100644 index 0000000..136b7f0 --- /dev/null +++ b/source/types.h @@ -0,0 +1,28 @@ +#ifndef INC_TYPES_H +#define INC_TYPES_H +#include + +typedef enum { + BOOT_TYPE_NONE = 0, + BOOT_TYPE_DOL, + BOOT_TYPE_ONBOARD + // Changes to this enum should also be made to boot_type_names in types.c +} BOOT_TYPE; + +const char * +get_boot_type_name(BOOT_TYPE type); + +typedef struct { + BOOT_TYPE type; + const char *dol_path; + const char **dol_cli_options_strs; + int num_dol_cli_options_strs; +} BOOT_ACTION; + +typedef struct { + BOOT_TYPE type; + u8 *dol_file; + struct __argv argv; +} BOOT_PAYLOAD; + +#endif From 370ec18dddbe336128f21838d90b63e8acee2ba4 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 02:53:26 -0500 Subject: [PATCH 20/22] added boot type for USB Gecko - Allows config to specify `USBGECKO` shortcut. --- README.md | 1 + source/config.c | 2 ++ source/main.c | 15 +++++++++++---- source/types.c | 9 +++++---- source/types.h | 3 ++- 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6937dd1..5af6c18 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ Shortcut action my be one of: ------------|------------- A filepath | Path to a DOL to load. All paths are relative to the device root. `ONBOARD` | Reboot into the onboard IPL (original GameCube intro and menu). + `USBGECKO` | Attempt to load from USB Gecko. Holding a button during boot will activate the shortcut with the matching name. If no button is held, `DEFAULT` is used (if unspecified, the `DEFAULT` action is `/ipl.dol`). diff --git a/source/config.c b/source/config.c index 252c30f..436a4a6 100644 --- a/source/config.c +++ b/source/config.c @@ -26,6 +26,8 @@ void handle_boot_action(const char *value, BOOT_ACTION *action) { if (strcmp(value, "ONBOARD") == 0) { action->type = BOOT_TYPE_ONBOARD; + } else if (strcmp(value, "USBGECKO") == 0) { + action->type = BOOT_TYPE_USBGECKO; } else { action->type = BOOT_TYPE_DOL; action->dol_path = strdup(value); diff --git a/source/main.c b/source/main.c index ab55d66..4d44487 100644 --- a/source/main.c +++ b/source/main.c @@ -165,7 +165,11 @@ load_config(BOOT_PAYLOAD *payload, int shortcut_index) { payload->type = BOOT_TYPE_ONBOARD; return res; } - + if (action->type == BOOT_TYPE_USBGECKO) { + kprintf("->> Shortcut action: Use USB Gecko\n"); + payload->type = BOOT_TYPE_USBGECKO; + return res; + } if (action->type != BOOT_TYPE_DOL) { // Should never happen. kprintf("->> !! Internal Error: Unexpeted boot type: %i\n", action->type); @@ -502,12 +506,15 @@ main(int _argc, char **_argv) { // Attempt to load from each device. int res = - (load_usb(&payload, 'B') - || load_fat(&payload, "SD Gecko in slot B", &__io_gcsdb, shortcut_index) - || load_usb(&payload, 'A') + (load_fat(&payload, "SD Gecko in slot B", &__io_gcsdb, shortcut_index) || load_fat(&payload, "SD Gecko in slot A", &__io_gcsda, shortcut_index) || load_fat(&payload, "SD2SP2", &__io_gcsd2, shortcut_index)); + if (!res || payload.type == BOOT_TYPE_USBGECKO) { + payload.type = BOOT_TYPE_NONE; + res = (load_usb(&payload, 'B') || load_usb(&payload, 'A') || res); + } + if (!res) { // If we reach here, we did not find a device with any shortcut files. kprintf("\nNo shortcuts found\n"); diff --git a/source/types.c b/source/types.c index ed9c233..8e7ca93 100644 --- a/source/types.c +++ b/source/types.c @@ -1,10 +1,11 @@ #include "types.h" -#define NUM_BOOT_TYPE_NAMES 3 +#define NUM_BOOT_TYPE_NAMES 4 const char *boot_type_names[NUM_BOOT_TYPE_NAMES] = { - /*BOOT_TYPE_NONE (0)*/ "NONE", - /*BOOT_TYPE_DOL (1)*/ "DOL", - /*BOOT_TYPE_ONBOARD (2)*/ "ONBOARD", + /*BOOT_TYPE_NONE (0)*/ "NONE", + /*BOOT_TYPE_DOL (1)*/ "DOL", + /*BOOT_TYPE_ONBOARD (2)*/ "ONBOARD", + /*BOOT_TYPE_USBGECKO (3)*/ "USBGECKO", }; const char * diff --git a/source/types.h b/source/types.h index 136b7f0..89a6e6f 100644 --- a/source/types.h +++ b/source/types.h @@ -5,7 +5,8 @@ typedef enum { BOOT_TYPE_NONE = 0, BOOT_TYPE_DOL, - BOOT_TYPE_ONBOARD + BOOT_TYPE_ONBOARD, + BOOT_TYPE_USBGECKO, // Changes to this enum should also be made to boot_type_names in types.c } BOOT_TYPE; From a163d1748c451d55c15c052f555a1914ad22e364 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 12 Aug 2022 20:20:02 -0500 Subject: [PATCH 21/22] warn when shorcut dol path does not look right - Prints warning when DOL path does not have `.dol` or `.dol+cli` extension. --- source/config.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/config.c b/source/config.c index 436a4a6..6110956 100644 --- a/source/config.c +++ b/source/config.c @@ -29,6 +29,11 @@ handle_boot_action(const char *value, BOOT_ACTION *action) { } else if (strcmp(value, "USBGECKO") == 0) { action->type = BOOT_TYPE_USBGECKO; } else { + size_t len = strlen(value); + if ((len < 5 || strcmp(value + (len - 4), ".dol") != 0) + && (len < 9 || strcmp(value + (len - 8), ".dol+cli") != 0)) { + kprintf("->> !! Warning: Configured filename does not look like a DOL\n"); + } action->type = BOOT_TYPE_DOL; action->dol_path = strdup(value); } From 2a2afa562f39344fcaeba291721320dfa1a098b8 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 8 Sep 2022 02:45:20 -0500 Subject: [PATCH 22/22] added support for emulator build - Allows build compatible with Dolphin emulator for easy testing. --- .gitignore | 1 + buildtools/qoob_injector.py | 10 +- devkitPPC.ini | 3 +- devkitPPCEmu.ini | 2 + meson.build | 256 +++++++++++--------- meson.options | 1 + packer/meson.build | 2 +- source/filesystem.c | 5 + source/filesystem_emu.c | 130 ++++++++++ source/main.c | 46 +++- stub/meson.build | 2 +- subprojects/libfat.wrap | 7 + subprojects/packagefiles/libfat/configure | 48 ++++ subprojects/packagefiles/libfat/meson.build | 19 ++ 14 files changed, 414 insertions(+), 118 deletions(-) create mode 100644 devkitPPCEmu.ini create mode 100644 source/filesystem_emu.c create mode 100644 subprojects/libfat.wrap create mode 100755 subprojects/packagefiles/libfat/configure create mode 100644 subprojects/packagefiles/libfat/meson.build diff --git a/.gitignore b/.gitignore index dd518ae..cb68772 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ ipl.rom *.zip /subprojects/libogc2 +/subprojects/libfat !res/qoob_pro_none_upgrade.elf !res/qoob_sx_13c_upgrade.elf diff --git a/buildtools/qoob_injector.py b/buildtools/qoob_injector.py index 394be55..5d699b8 100755 --- a/buildtools/qoob_injector.py +++ b/buildtools/qoob_injector.py @@ -15,16 +15,18 @@ out = bytearray(f.read()) if bios.endswith(".gcb"): - if len(img) > 128 * 1024: - raise "Qoob Pro BIOS image too big to fit in flasher" + oversize_bytes = len(img) - (128 * 1024) + if oversize_bytes > 0: + raise Exception("Qoob Pro BIOS image too big to fit in flasher. %i bytes too large" % (oversize_bytes)) msg = b"gekkoboot for QOOB Pro\0" msg_offset = 0x1A68 img_offset = 0x1AE0 if bios.endswith(".qbsx"): - if len(img) > 62800: - raise "Qoob SX BIOS image too big to fit in flasher" + oversize_bytes = len(img) - 62800 + if oversize_bytes > 0: + raise Exception("Qoob SX BIOS image too big to fit in flasher. %i bytes too large" % (oversize_bytes)) msg = b"gekkoboot for QOOB SX\0" msg_offset = 7240 diff --git a/devkitPPC.ini b/devkitPPC.ini index aae6b61..19e5d76 100644 --- a/devkitPPC.ini +++ b/devkitPPC.ini @@ -1,6 +1,7 @@ [constants] prefix = 'powerpc-eabi-' -common_args = ['-DGEKKO','-mogc','-mcpu=750','-meabi','-mhard-float'] +platform_args = ['-mogc'] +common_args = ['-DGEKKO','-mcpu=750','-meabi','-mhard-float'] + platform_args link_args = [] [binaries] diff --git a/devkitPPCEmu.ini b/devkitPPCEmu.ini new file mode 100644 index 0000000..d3ebf0c --- /dev/null +++ b/devkitPPCEmu.ini @@ -0,0 +1,2 @@ +[constants] +platform_args = ['-mrvl','-DEMU_BUILD'] diff --git a/meson.build b/meson.build index 3b154b8..4be3aac 100644 --- a/meson.build +++ b/meson.build @@ -8,13 +8,33 @@ project( }, ) +emu_build = get_option('emu_build') + +platform = 'cube' +libogc_libs = ['ogc'] + +if emu_build + platform = 'wii' + libogc_libs += ['bte', 'wiiuse'] +endif + libogc_proj = subproject( 'libogc2', default_options: { - 'libraries': ['ogc'], + 'platform': platform, + 'libraries': libogc_libs, }, ) -libogc_deps = libogc_proj.get_variable('deps') +libogc_deps_dic = libogc_proj.get_variable('deps') +libogc_deps = [] +foreach lib : libogc_libs + libogc_deps += [libogc_deps_dic[lib]] +endforeach + +if emu_build + libfat_proj = subproject('libfat') + libfat_dep = libfat_proj.get_variable('dep') +endif subdir('buildtools') subdir('res') @@ -33,8 +53,6 @@ compressed_exes = {} subdir('stub') -linker_script = meson.current_source_dir() / 'ogc.ld' - git = find_program('git') version_file = vcs_tag( command: [git, 'describe', '--always', '--tags', '--dirty'], @@ -49,6 +67,18 @@ font = custom_target( command: [psf2c, '@INPUT@', '@OUTPUT@', 'console_font_8x16'], ) +gekkoboot_link_args = [] +gekkoboot_link_depends = [] +if emu_build + # Always link the math library because it is used by wiiuse. + # It is never explicitly included so the --as-needed flag would omit it otherwise. + gekkoboot_link_args += ['-lm'] +else + linker_script = meson.current_source_dir() / 'ogc.ld' + gekkoboot_link_args += ['-T' + linker_script] + gekkoboot_link_depends += [linker_script] +endif + gekkoboot = executable( 'gekkoboot', 'source/main.c', @@ -64,41 +94,47 @@ gekkoboot = executable( 'source/inih/ini.c', version_file, font, - dependencies: [ - libogc_deps['ogc'], - stub_dep, - ], - link_args: ['-T' + linker_script], - link_depends: [linker_script], + dependencies: ( + libogc_deps + + [stub_dep] + + (emu_build? [libfat_dep] : []) + ), + link_args: gekkoboot_link_args, + link_depends: gekkoboot_link_depends, name_suffix: 'elf', ) -compressed_exes += { - 'gekkoboot': { - 'exe': gekkoboot, - 'link_args': [ - '-Wl,--section-start,.init=0x01300000' - ], - 'dol': true, - }, - 'gekkoboot_sx': { - 'exe': gekkoboot, - 'link_args': [ - # This is the same entry point as the original BIOS, - # but the recovery slot hangs on "starting flashed app..." - # when loading it. A lowmem image works just fine. - #'-Wl,--section-start,.init=0x01500000', - - # Makes the ELF smaller so it actually fits - '-Wl,--nmagic', - ], - }, - 'gekkoboot_lowmem': { - 'exe': gekkoboot, - 'dol': true, - }, -} -subdir('packer') +if emu_build + dols += {'gekkoboot_emu': gekkoboot} +else + compressed_exes += { + 'gekkoboot': { + 'exe': gekkoboot, + 'link_args': [ + '-Wl,--section-start,.init=0x01300000' + ], + 'dol': true, + }, + 'gekkoboot_sx': { + 'exe': gekkoboot, + 'link_args': [ + # This is the same entry point as the original BIOS, + # but the recovery slot hangs on "starting flashed app..." + # when loading it. A lowmem image works just fine. + #'-Wl,--section-start,.init=0x01500000', + + # Makes the ELF smaller so it actually fits + '-Wl,--nmagic', + ], + }, + 'gekkoboot_lowmem': { + 'exe': gekkoboot, + 'dol': true, + }, + } + + subdir('packer') +endif foreach name, exe: dols dol = custom_target( @@ -111,87 +147,89 @@ foreach name, exe: dols set_variable(name + '_dol', dol) endforeach -if full_rom_opt.allowed() - qoob_pro = custom_target( - 'qoob_pro', - input: [gekkoboot_dol, ipl_rom], - output: 'gekkoboot_qoob_pro.gcb', +if not emu_build + if full_rom_opt.allowed() + qoob_pro = custom_target( + 'qoob_pro', + input: [gekkoboot_dol, ipl_rom], + output: 'gekkoboot_qoob_pro.gcb', + command: [dol2ipl, '@OUTPUT@', '@INPUT@'], + build_by_default: true, + install: true, + install_dir: '/qoob_pro', + ) + qoob_pro_updater_tgt = custom_target( + 'qoob_pro_updater', + input: [qoob_pro, qoob_pro_updater], + output: 'qoob_pro_gekkoboot_upgrade.elf', + command: [qoob_injector, '@INPUT@', '@OUTPUT@'], + build_by_default: true, + install: true, + install_dir: '/qoob_pro', + ) + endif + + gekkoboot_sx_stripped = custom_target( + gekkoboot_sx_compressed.name() + '_stripped', + input: gekkoboot_sx_compressed, + output: gekkoboot_sx_compressed.name() + '_stripped.elf', + command: [objcopy, '-S', '@INPUT@', '@OUTPUT@'], + ) + qoob_sx = custom_target( + 'qoob_sx', + input: gekkoboot_sx_stripped, + output: 'gekkoboot_qoob_sx.qbsx', command: [dol2ipl, '@OUTPUT@', '@INPUT@'], - build_by_default: true, - install: true, - install_dir: '/qoob_pro', ) - qoob_pro_updater_tgt = custom_target( - 'qoob_pro_updater', - input: [qoob_pro, qoob_pro_updater], - output: 'qoob_pro_gekkoboot_upgrade.elf', + qoob_sx_updater_tgt = custom_target( + 'qoob_sx_updater', + input: [qoob_sx, qoob_sx_updater], + output: 'qoob_sx_gekkoboot_upgrade.elf', command: [qoob_injector, '@INPUT@', '@OUTPUT@'], build_by_default: true, install: true, - install_dir: '/qoob_pro', + install_dir: '/', ) -endif - -gekkoboot_sx_stripped = custom_target( - gekkoboot_sx_compressed.name() + '_stripped', - input: gekkoboot_sx_compressed, - output: gekkoboot_sx_compressed.name() + '_stripped.elf', - command: [objcopy, '-S', '@INPUT@', '@OUTPUT@'], -) -qoob_sx = custom_target( - 'qoob_sx', - input: gekkoboot_sx_stripped, - output: 'gekkoboot_qoob_sx.qbsx', - command: [dol2ipl, '@OUTPUT@', '@INPUT@'], -) -qoob_sx_updater_tgt = custom_target( - 'qoob_sx_updater', - input: [qoob_sx, qoob_sx_updater], - output: 'qoob_sx_gekkoboot_upgrade.elf', - command: [qoob_injector, '@INPUT@', '@OUTPUT@'], - build_by_default: true, - install: true, - install_dir: '/', -) -viper = custom_target( - 'viper', - input: gekkoboot_dol, - output: 'gekkoboot_viper.vgc', - command: [dol2ipl, '@OUTPUT@', '@INPUT@'], - build_by_default: true, - install: true, - install_dir: '/', -) + viper = custom_target( + 'viper', + input: gekkoboot_dol, + output: 'gekkoboot_viper.vgc', + command: [dol2ipl, '@OUTPUT@', '@INPUT@'], + build_by_default: true, + install: true, + install_dir: '/', + ) -pico = custom_target( - 'pico', - input: gekkoboot_dol, - output: 'gekkoboot_pico.uf2', - command: [dol2ipl, '@OUTPUT@', '@INPUT@'], - build_by_default: true, - install: true, - install_dir: '/', -) + pico = custom_target( + 'pico', + input: gekkoboot_dol, + output: 'gekkoboot_pico.uf2', + command: [dol2ipl, '@OUTPUT@', '@INPUT@'], + build_by_default: true, + install: true, + install_dir: '/', + ) -gci = custom_target( - 'gci', - input: gekkoboot_lowmem_dol, - output: 'gekkoboot_memcard.gci', - command: [dol2gci, '@INPUT@', '@OUTPUT@', 'boot.dol'], - build_by_default: true, - install: true, - install_dir: '/', -) + gci = custom_target( + 'gci', + input: gekkoboot_lowmem_dol, + output: 'gekkoboot_memcard.gci', + command: [dol2gci, '@INPUT@', '@OUTPUT@', 'boot.dol'], + build_by_default: true, + install: true, + install_dir: '/', + ) -apploader = custom_target( - 'apploader', - input: gekkoboot_dol, - output: 'apploader.img', - command: [dol2ipl, '@OUTPUT@', '@INPUT@'], - build_by_default: true, - install: true, - install_dir: '/swiss_igr/swiss/patches', -) + apploader = custom_target( + 'apploader', + input: gekkoboot_dol, + output: 'apploader.img', + command: [dol2ipl, '@OUTPUT@', '@INPUT@'], + build_by_default: true, + install: true, + install_dir: '/swiss_igr/swiss/patches', + ) -install_data('README.md', install_dir: '/') + install_data('README.md', install_dir: '/') +endif diff --git a/meson.options b/meson.options index 03c9208..44417de 100644 --- a/meson.options +++ b/meson.options @@ -1 +1,2 @@ option('full_rom', type: 'feature', description: 'Whether to enable full ROM builds (Qoob Pro support)') +option('emu_build', type: 'boolean', value: false) diff --git a/packer/meson.build b/packer/meson.build index dabdf61..d14db95 100644 --- a/packer/meson.build +++ b/packer/meson.build @@ -19,7 +19,7 @@ packer_stub = static_library( link_with: packer_crt, dependencies: [ # For the headers - libogc_deps['ogc'].partial_dependency( + libogc_deps_dic['ogc'].partial_dependency( compile_args: true, sources: true, ), diff --git a/source/filesystem.c b/source/filesystem.c index 973878e..bbf93ed 100644 --- a/source/filesystem.c +++ b/source/filesystem.c @@ -1,4 +1,8 @@ #include "filesystem.h" + +#ifdef EMU_BUILD +#include "filesystem_emu.c" +#else #include "fatfs/ff.h" #include "ffshim.h" #include @@ -72,6 +76,7 @@ _fs_read_file(void **contents_, const char *path, int is_string) { *contents_ = contents; return FS_OK; } +#endif FS_RESULT fs_read_file(void **contents, const char *path) { diff --git a/source/filesystem_emu.c b/source/filesystem_emu.c new file mode 100644 index 0000000..890242d --- /dev/null +++ b/source/filesystem_emu.c @@ -0,0 +1,130 @@ +#include "filesystem.h" +#include +#include +#include +#include +#include +#include + +const DISC_INTERFACE *wiface = NULL; +const char * +get_errno_message(int _errno); + +FS_RESULT +fs_mount(const DISC_INTERFACE *iface_) { + wiface = iface_; + if (!fatMountSimple("sd", wiface)) { + return FS_NOT_READY; + } + return FS_OK; +} + +void +fs_unmount() { + fatUnmount("sd"); + wiface->shutdown(); + wiface = NULL; +} + +void +fs_get_volume_label(const char *path, char *label) { + strcpy(label, ""); +} + +FS_RESULT +_fs_read_file(void **contents_, const char *path, int is_string) { + char full_path[strlen(path) + 5]; + sprintf(full_path, "sd:/%s", path); + + errno = 0; + FILE *file = fopen(full_path, "rb"); + if (!file) { + if (errno == ENOENT) { + kprintf("File not found\n"); + return FS_NO_FILE; + } + kprintf("->> !! Failed to open file: %s\n", get_errno_message(errno)); + return FS_INT_ERR; + } + + fseek(file, 0, SEEK_END); + size_t size = ftell(file); + rewind(file); + + if (size <= 0) { + kprintf("->> !! File is empty\n"); + return FS_FILE_EMPTY; + } + kprintf("File size: %iB\n", size); + + // Malloc an extra byte if we are reading as a string incase we need to add NUL character. + void *contents = malloc(size + (is_string ? 1 : 0)); + if (!contents) { + kprintf("->> !! Couldn't allocate memory for file\n"); + return FS_NOT_ENOUGH_MEMORY; + } + + kprintf("Reading file...\n"); + errno = 0; + if (!fread(contents, size, 1, file)) { + kprintf("->> !! Failed to read file: %s\n", get_errno_message(errno)); + return FS_INT_ERR; + } + + fclose(file); + + // Ensure files read as strings end with NUL character. + if (is_string) { + // This is safe because we malloc an extra byte above if reading as string. + ((char *) contents)[size] = '\0'; + } + + *contents_ = contents; + return FS_OK; +} + +#define NUM_ERRNO_MSGS 35 +const char *errno_msgs[NUM_ERRNO_MSGS] = { + /*OK ( 0)*/ "OK", + /*EPERM ( 1)*/ "Operation not permitted", + /*ENOENT ( 2)*/ "No such file or directory", + /*ESRCH ( 3)*/ "No such process", + /*EINTR ( 4)*/ "Interrupted system call", + /*EIO ( 5)*/ "I/O error", + /*ENXIO ( 6)*/ "No such device or address", + /*E2BIG ( 7)*/ "Argument list too long", + /*ENOEXEC ( 8)*/ "Exec format error", + /*EBADF ( 9)*/ "Bad file number", + /*ECHILD (10)*/ "No child processes", + /*EAGAIN (11)*/ "Try again", + /*ENOMEM (12)*/ "Out of memory", + /*EACCES (13)*/ "Permission denied", + /*EFAULT (14)*/ "Bad address", + /*ENOTBLK (15)*/ "Block device required", + /*EBUSY (16)*/ "Device or resource busy", + /*EEXIST (17)*/ "File exists", + /*EXDEV (18)*/ "Cross-device link", + /*ENODEV (19)*/ "No such device", + /*ENOTDIR (20)*/ "Not a directory", + /*EISDIR (21)*/ "Is a directory", + /*EINVAL (22)*/ "Invalid argument", + /*ENFILE (23)*/ "File table overflow", + /*EMFILE (24)*/ "Too many open files", + /*ENOTTY (25)*/ "Not a typewriter", + /*ETXTBSY (26)*/ "Text file busy", + /*EFBIG (27)*/ "File too large", + /*ENOSPC (28)*/ "No space left on device", + /*ESPIPE (29)*/ "Illegal seek", + /*EROFS (30)*/ "Read-only file system", + /*EMLINK (31)*/ "Too many links", + /*EPIPE (32)*/ "Broken pipe", + /*EDOM (33)*/ "Math argument out of domain of func", + /*ERANGE (34)*/ "Math result not representable", +}; +const char * +get_errno_message(int _errno) { + if (_errno < 0 || _errno >= NUM_ERRNO_MSGS) { + return "Unknown"; + } + return errno_msgs[_errno]; +} \ No newline at end of file diff --git a/source/main.c b/source/main.c index 4d44487..15396c9 100644 --- a/source/main.c +++ b/source/main.c @@ -9,19 +9,24 @@ #include #include #include -#include #include #include #include #include +#ifndef EMU_BUILD #include "stub.h" +#include +extern u8 __xfb[]; +#else +#include +static void *__xfb = NULL; +#endif // Global State // -------------------- int debug_enabled = false; u16 all_buttons_held; -extern u8 __xfb[]; // -------------------- void @@ -402,8 +407,14 @@ load_usb(BOOT_PAYLOAD *payload, char slot) { return res; } +#ifdef EMU_BUILD +int +main_capture(int _argc, char **_argv) { +#else int main(int _argc, char **_argv) { +#endif +#ifndef EMU_BUILD // GCVideo takes a while to boot up. // If VIDEO_GetPreferredMode is called before it's done, // it will not see the "component cable", and default to interlaced mode, @@ -417,11 +428,15 @@ main(int _argc, char **_argv) { } } } +#endif VIDEO_Init(); PAD_Init(); GXRModeObj *rmode = VIDEO_GetPreferredMode(NULL); VIDEO_Configure(rmode); +#ifdef EMU_BUILD + __xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode)); +#endif VIDEO_SetNextFramebuffer(__xfb); VIDEO_SetBlack(FALSE); VIDEO_Flush(); @@ -432,6 +447,9 @@ main(int _argc, char **_argv) { ); kprintf("\n\ngekkoboot %s\n", version); +#ifdef EMU_BUILD + kprintf("EMU_BUILD\n"); +#endif // Disable Qoob u32 val = 6 << 24; @@ -457,6 +475,11 @@ main(int _argc, char **_argv) { } } +#ifdef EMU_BUILD + kprintf("DEBUG: Debug enabled by EMU_BUILD.\n"); + debug_enabled = true; + should_ask = true; +#endif if (should_ask) { kprintf("DEBUG: Press button...\n"); do { @@ -504,6 +527,7 @@ main(int _argc, char **_argv) { payload.argv.commandLine = NULL; payload.argv.argvMagic = ARGV_MAGIC; +#ifndef EMU_BUILD // Attempt to load from each device. int res = (load_fat(&payload, "SD Gecko in slot B", &__io_gcsdb, shortcut_index) @@ -514,6 +538,9 @@ main(int _argc, char **_argv) { payload.type = BOOT_TYPE_NONE; res = (load_usb(&payload, 'B') || load_usb(&payload, 'A') || res); } +#else + int res = load_fat(&payload, "Wii SD", &__io_wiisd, shortcut_index); +#endif if (!res) { // If we reach here, we did not find a device with any shortcut files. @@ -570,6 +597,7 @@ main(int _argc, char **_argv) { kprintf("Booting DOL...\n"); +#ifndef EMU_BUILD // Load stub. memcpy((void *) STUB_ADDR, stub, (size_t) stub_size); DCStoreRange((void *) STUB_ADDR, (u32) stub_size); @@ -593,4 +621,18 @@ main(int _argc, char **_argv) { // Will never reach here. return 0; +#else + kprintf("EMU_BUILD: Success!\n"); + return 0; +#endif +} + +#ifdef EMU_BUILD +int +main(int _argc, char **_argv) { + int res = main_capture(_argc, _argv); + kprintf("EMU_BUILD: Exited with %i. Press A to exit...\n", res); + wait_for_confirmation(); + return res; } +#endif diff --git a/stub/meson.build b/stub/meson.build index a6b6abd..e0de71f 100644 --- a/stub/meson.build +++ b/stub/meson.build @@ -16,7 +16,7 @@ stub_exe = executable( link_depends: stub_linker_script, dependencies: [ # For the headers - libogc_deps['ogc'].partial_dependency( + libogc_deps_dic['ogc'].partial_dependency( compile_args: true, sources: true, ), diff --git a/subprojects/libfat.wrap b/subprojects/libfat.wrap new file mode 100644 index 0000000..bcfe006 --- /dev/null +++ b/subprojects/libfat.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url = https://github.com/extremscorner/libfat.git +# TODO: redolution/libogc2 is behind extremscorner/libogc2 which introduced a breaking change +# which libfat relies on. Namely, the introduction of SYS_IsDMAAddress: +# https://github.com/devkitPro/libfat/commit/6e782e688f89e05b0f5750e0c5eeef1dda492a37 +revision = 5700bbf2945cbb7ceda0bac9516a32cb7cc6e474 +patch_directory=libfat \ No newline at end of file diff --git a/subprojects/packagefiles/libfat/configure b/subprojects/packagefiles/libfat/configure new file mode 100755 index 0000000..dea870f --- /dev/null +++ b/subprojects/packagefiles/libfat/configure @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +src=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")") + +if [[ "$(readlink -f "$PWD")" == "$src" ]]; then + printf '%s\n' 'This script must be run out of tree!' + exit 1 +fi + +tmp=$(mktemp -t Makefile.XXXXXXXXXX) +trap 'rm -f "$tmp"' EXIT + +function write() { + printf '%s\n' "$*" >> "$tmp" +} + +write '# Automatically generated file' + +for arg in "$@"; do + val="${arg#*=}" + case $arg in + --prefix=*) + write INSTALL_PREFIX := "$val" + ;; + --incdir=*) + write INCDEST := "$val" + ;; + --libdir=*) + write LIBDEST := "$val" + ;; + *) + printf '%s\n' "$arg: Invalid argument" + exit 1 + esac +done + +write SRCDIR := "$src" +write 'default:' +write ' $(MAKE) -C $(SRCDIR) wii-release' +write 'install:' +write ' @mkdir -p $(DESTDIR)$(INSTALL_PREFIX)' +write ' @mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/$(INCDEST)' +write ' @mkdir -p $(DESTDIR)$(INSTALL_PREFIX)/$(LIBDEST)' +write ' @cp -frv $(SRCDIR)/include/* -t $(DESTDIR)$(INSTALL_PREFIX)/$(INCDEST)' +write ' @cp -frv $(SRCDIR)/libogc2/lib/* -t $(DESTDIR)$(INSTALL_PREFIX)/$(LIBDEST)' +write ' @cp -frv $(SRCDIR)/libfat_license.txt $(DESTDIR)$(INSTALL_PREFIX)' + +cp "$tmp" Makefile diff --git a/subprojects/packagefiles/libfat/meson.build b/subprojects/packagefiles/libfat/meson.build new file mode 100644 index 0000000..ace2e70 --- /dev/null +++ b/subprojects/packagefiles/libfat/meson.build @@ -0,0 +1,19 @@ +project( + 'libfat', + ['c'], + meson_version: '>=1.1', +) + +external_project = import('unstable-external_project') + +p = external_project.add_project( + 'configure', + configure_options: [ + '--prefix=@PREFIX@', + '--libdir=@LIBDIR@', + '--incdir=@INCLUDEDIR@', + ], + cross_configure_options: [], +) + +dep = p.dependency(':wii/libfat.a')