From b8ef4b15b78cc60a2d1a6a8fc23ccc72eeee2e16 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Sat, 13 Dec 2025 19:20:48 -0800 Subject: [PATCH 1/6] Add user-friendly error display when games fail to load. - Show actionable error messages instead of silent failures - Catch core crashes during load with segfault recovery - Capture libretro error logs for display to user - Add fallback input polling for misbehaving cores - Fix GFX_wrapText to preserve existing newlines (for multi-line messages) - Fix memory leak (missing fclose on malloc failure) - Clean up temp directories after archive extraction --- tests/unit/all/common/test_gfx_text.c | 61 ++++ workspace/all/common/gfx_text.c | 123 ++++---- workspace/all/minarch/minarch.c | 263 ++++++++++++++++-- .../all/minarch/minarch_loop_audioclock.inc | 8 + workspace/all/minarch/minarch_loop_vsync.inc | 8 + workspace/all/paks/MinUI/launch.sh.template | 5 +- 6 files changed, 383 insertions(+), 85 deletions(-) diff --git a/tests/unit/all/common/test_gfx_text.c b/tests/unit/all/common/test_gfx_text.c index 26f4357f..26eb6705 100644 --- a/tests/unit/all/common/test_gfx_text.c +++ b/tests/unit/all/common/test_gfx_text.c @@ -327,6 +327,61 @@ void test_wrapText_only_spaces(void) { TEST_ASSERT_GREATER_OR_EQUAL(0, width); } +void test_wrapText_preserves_existing_newlines(void) { + char text[256] = "Line one\nLine two"; + + // Width is wide enough that no wrapping is needed + GFX_wrapText(&mock_font, text, 500, 0); + + // Original newline should be preserved + TEST_ASSERT_EQUAL_STRING("Line one\nLine two", text); +} + +void test_wrapText_wraps_after_existing_newline(void) { + // First line short, second line needs wrapping + // "Short" = 50px, "This is a longer second line" = 280px + char text[256] = "Short\nThis is a longer second line"; + + // 150px should fit "Short" but not the second line + GFX_wrapText(&mock_font, text, 150, 0); + + // First newline preserved, second line should have wrapped + TEST_ASSERT_TRUE(strchr(text, '\n') != NULL); + + // Count newlines - should have more than 1 now (original + wrap) + int newlines = 0; + for (char* p = text; *p; p++) { + if (*p == '\n') + newlines++; + } + TEST_ASSERT_GREATER_THAN(1, newlines); +} + +void test_wrapText_multiple_existing_newlines(void) { + char text[256] = "A\nB\nC"; + + // Wide enough that no wrapping needed + GFX_wrapText(&mock_font, text, 500, 0); + + // All newlines preserved + TEST_ASSERT_EQUAL_STRING("A\nB\nC", text); +} + +void test_wrapText_existing_newlines_count_toward_max_lines(void) { + char text[256] = "Line one\nLine two\nLine three is very long and should wrap"; + + // max_lines = 3, but we already have 3 lines from existing newlines + GFX_wrapText(&mock_font, text, 100, 3); + + // Count newlines - should not exceed 2 (for 3 lines) + int newlines = 0; + for (char* p = text; *p; p++) { + if (*p == '\n') + newlines++; + } + TEST_ASSERT_LESS_OR_EQUAL(2, newlines); +} + /////////////////////////////// // GFX_sizeText() Tests /////////////////////////////// @@ -467,5 +522,11 @@ int main(void) { RUN_TEST(test_wrapText_empty_string); RUN_TEST(test_wrapText_only_spaces); + // Existing newline handling + RUN_TEST(test_wrapText_preserves_existing_newlines); + RUN_TEST(test_wrapText_wraps_after_existing_newline); + RUN_TEST(test_wrapText_multiple_existing_newlines); + RUN_TEST(test_wrapText_existing_newlines_count_toward_max_lines); + return UNITY_END(); } diff --git a/workspace/all/common/gfx_text.c b/workspace/all/common/gfx_text.c index fb430b97..bd652451 100644 --- a/workspace/all/common/gfx_text.c +++ b/workspace/all/common/gfx_text.c @@ -75,9 +75,10 @@ int GFX_truncateText(TTF_Font* ttf_font, const char* in_name, char* out_name, in /** * Wraps text to fit within a maximum width by inserting newlines. * - * Breaks text at space characters to create wrapped lines. The last - * line is truncated with "..." if it still exceeds max_width. - * Modifies the input string in place by replacing spaces with newlines. + * Breaks text at space characters to create wrapped lines. Preserves + * existing newlines (intentional line breaks). Each line segment is + * wrapped independently. Lines that can't wrap (no spaces) are truncated + * with "...". * * @param font TTF font to measure text with * @param str String to wrap (modified in place) @@ -88,69 +89,79 @@ int GFX_truncateText(TTF_Font* ttf_font, const char* in_name, char* out_name, in * @note Input string is modified - spaces become newlines at wrap points */ int GFX_wrapText(TTF_Font* ttf_font, char* str, int max_width, int max_lines) { - if (!str) + if (!str || !str[0] || max_width <= 0) return 0; - int line_width; int max_line_width = 0; - char* line = str; - char buffer[MAX_PATH]; - - TTF_SizeUTF8(ttf_font, line, &line_width, NULL); - if (line_width <= max_width) { - line_width = GFX_truncateText(ttf_font, line, buffer, max_width, 0); - strcpy(line, buffer); - return line_width; - } - - char* prev = NULL; - char* tmp = line; int lines = 1; - while (!max_lines || lines < max_lines) { - tmp = strchr(tmp, ' '); - if (!tmp) { - if (prev) { - TTF_SizeUTF8(ttf_font, line, &line_width, NULL); - if (line_width >= max_width) { - if (line_width > max_line_width) - max_line_width = line_width; - prev[0] = '\n'; - line = prev + 1; - } - } - break; + char* line_start = str; // Start of current line being measured + char* last_space = NULL; // Last space we could wrap at + char* p = str; + + while (*p) { + // Hit existing newline - reset for next line + if (*p == '\n') { + // Measure this line segment + char saved = *p; + *p = '\0'; + int w; + TTF_SizeUTF8(ttf_font, line_start, &w, NULL); + *p = saved; + if (w > max_line_width) + max_line_width = w; + line_start = p + 1; + last_space = NULL; + lines++; + p++; + continue; } - tmp[0] = '\0'; - - TTF_SizeUTF8(ttf_font, line, &line_width, NULL); - - if (line_width >= max_width) { // wrap - if (line_width > max_line_width) - max_line_width = line_width; - tmp[0] = ' '; - tmp += 1; - if (prev) { - prev[0] = '\n'; - prev += 1; - line = prev; - } else { - // TODO: If first word is too long (no prev space), it gets skipped. - // Should truncate with "..." instead of dropping it entirely. - line = tmp; + + // Track spaces as potential wrap points + if (*p == ' ') + last_space = p; + + // Measure current line (up to and including current char) + char saved = *(p + 1); + *(p + 1) = '\0'; + int line_width; + TTF_SizeUTF8(ttf_font, line_start, &line_width, NULL); + *(p + 1) = saved; + + // Line too long - wrap at last space if possible + if (line_width > max_width) { + if (last_space) { + // Wrap at the last space + if (max_lines && lines >= max_lines) + break; + *last_space = '\n'; + line_start = last_space + 1; + last_space = NULL; + lines++; } - lines += 1; - } else { // continue - tmp[0] = ' '; - prev = tmp; - tmp += 1; + // If no space to wrap at, we'll truncate at the end } + + if (line_width > max_line_width) + max_line_width = line_width; + + p++; } - line_width = GFX_truncateText(ttf_font, line, buffer, max_width, 0); - strcpy(line, buffer); + // Truncate final line if it's too long + if (*line_start) { + int w; + TTF_SizeUTF8(ttf_font, line_start, &w, NULL); + if (w > max_width) { + // Use GFX_truncateText to truncate with "..." + char buffer[MAX_PATH]; + GFX_truncateText(ttf_font, line_start, buffer, max_width, 0); + strcpy(line_start, buffer); + TTF_SizeUTF8(ttf_font, line_start, &w, NULL); + } + if (w > max_line_width) + max_line_width = w; + } - if (line_width > max_line_width) - max_line_width = line_width; return max_line_width; } diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 9f73a63b..8084c759 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include #include #include @@ -89,6 +91,16 @@ static SDL_Surface* screen; // Main screen surface (managed by platform API) static int quit = 0; // Set to 1 to exit main loop static int show_menu = 0; // Set to 1 to display in-game menu static int simple_mode = 0; // Simplified interface mode (fewer options) +static int input_polled_this_frame = 0; // Tracks if core called input_poll_callback +static int toggled_ff_on = 0; // Fast-forward toggle state (for TOGGLE_FF vs HOLD_FF interaction) + +// Fatal error handling - detail shown when game fails to load +static char fatal_error_detail[512] = {0}; +static int capturing_core_errors = 0; // Flag to enable capture during load + +// Segfault recovery during core loading +static sigjmp_buf segfault_jmp; +static volatile sig_atomic_t in_core_load = 0; // Video geometry state for dynamic updates (type defined in minarch_env.h) static MinArchVideoState video_state; @@ -178,6 +190,17 @@ static struct Core core; // Game instance (struct defined in minarch_internal.h) static struct Game game; +/** + * Sets a fatal error message for display when game fails to load. + * Title is always "Game failed to load" - only the detail varies. + */ +static void setFatalError(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + vsnprintf(fatal_error_detail, sizeof(fatal_error_detail), fmt, args); + va_end(args); +} + /** * Opens and prepares a game for loading into the core. * @@ -207,12 +230,25 @@ static void Game_open(char* path) { strcpy(exts, core.extensions); MinArchGame_parseExtensions(exts, extensions, MINARCH_MAX_EXTENSIONS, &supports_archive); - // Extract if core doesn't support archives natively, or if it's a 7z file - // (7z is never natively supported by libretro cores) - if (!supports_archive || suffixMatch(".7z", game.path)) { + // Extract if core doesn't support the archive format natively + if (!supports_archive) { int result = MinArchArchive_extract(game.path, extensions, game.tmp_path, sizeof(game.tmp_path)); if (result != MINARCH_ARCHIVE_OK) { + if (result == MINARCH_ARCHIVE_ERR_NO_MATCH) { + // Build extension list for user - this is specific and actionable + char ext_list[256] = {0}; + size_t pos = 0; + for (int i = 0; extensions[i] && pos < sizeof(ext_list) - 10; i++) { + if (i > 0) + pos += snprintf(ext_list + pos, sizeof(ext_list) - pos, ", "); + pos += + snprintf(ext_list + pos, sizeof(ext_list) - pos, ".%s", extensions[i]); + } + setFatalError("No compatible files found in archive\nExpected: %s", ext_list); + } else { + setFatalError("Failed to extract archive"); + } LOG_error("Failed to extract archive: %s (error %d)", game.path, result); return; } @@ -226,6 +262,7 @@ static void Game_open(char* path) { FILE* file = fopen(path, "r"); if (file == NULL) { + setFatalError("Could not open ROM file\n%s", strerror(errno)); LOG_error("Error opening game: %s\n\t%s", path, strerror(errno)); return; } @@ -236,7 +273,9 @@ static void Game_open(char* path) { fseek(file, 0, SEEK_SET); game.data = malloc(game.size); if (game.data == NULL) { + setFatalError("Not enough memory to load ROM\nFile size: %ld bytes", (long)game.size); LOG_error("Couldn't allocate memory for file: %s", path); + fclose(file); return; } @@ -266,8 +305,18 @@ static void Game_open(char* path) { static void Game_close(void) { if (game.data) free(game.data); - if (game.tmp_path[0]) + if (game.tmp_path[0]) { + // Remove extracted file and temp directory remove(game.tmp_path); + // Extract directory path and remove it + char dir_path[MAX_PATH]; + strcpy(dir_path, game.tmp_path); + char* last_slash = strrchr(dir_path, '/'); + if (last_slash) { + *last_slash = '\0'; + rmdir(dir_path); + } + } game.is_open = 0; VIB_setStrength(0); // Ensure rumble is disabled } @@ -2144,9 +2193,15 @@ static int ignore_menu = 0; // Suppress menu button (used for shortcuts) * * Also translates platform button presses to libretro button flags. * - * @note This is a libretro callback, invoked by core on each frame + * @note This is a libretro callback, invoked by core on each frame. + * Also called by main loop as fallback for misbehaving cores. + * Guard ensures it only runs once per frame. */ static void input_poll_callback(void) { + if (input_polled_this_frame) + return; // Already ran this frame + + input_polled_this_frame = 1; PAD_poll(); int show_setting = 0; @@ -2160,8 +2215,7 @@ static void input_poll_callback(void) { ignore_menu = 1; } - static int toggled_ff_on = - 0; // this logic only works because TOGGLE_FF is before HOLD_FF in the menu... + // this logic only works because TOGGLE_FF is before HOLD_FF in the menu... for (int i = 0; i < SHORTCUT_COUNT; i++) { MinArchButtonMapping* mapping = &config.shortcuts[i]; int btn = 1 << mapping->local_id; @@ -2230,15 +2284,7 @@ static void input_poll_callback(void) { show_menu = 1; } - // TODO: figure out how to ignore button when MENU+button is handled first - // TODO: array size of LOCAL_ whatever that macro is - // TODO: then split it into two loops - // TODO: first check for MENU+button - // TODO: when found mark button the array - // TODO: then check for button - // TODO: only modify if absent from array - // TODO: the shortcuts loop above should also contribute to the array - + // Translate platform buttons to libretro button flags for core buttons = 0; for (int i = 0; config.controls[i].name; i++) { MinArchButtonMapping* mapping = &config.controls[i]; @@ -2395,6 +2441,10 @@ static void retro_log_callback(enum retro_log_level level, const char* fmt, ...) case RETRO_LOG_ERROR: default: LOG_error("%s", msg_buffer); + // Capture error for display if we're loading + if (capturing_core_errors && msg_buffer[0] != '\0') { + setFatalError("%s", msg_buffer); + } break; } } @@ -3530,8 +3580,12 @@ void Core_open(const char* core_path, const char* tag_name) { LOG_info("Core_open"); core.handle = dlopen(core_path, RTLD_LAZY); - if (!core.handle) - LOG_error("%s", dlerror()); + if (!core.handle) { + const char* error = dlerror(); + LOG_error("%s", error); + setFatalError("Failed to load core\n%s", error); + return; + } core.init = dlsym(core.handle, "retro_init"); core.deinit = dlsym(core.handle, "retro_deinit"); @@ -3598,13 +3652,68 @@ void Core_init(void) { core.init(); core.initialized = 1; } -void Core_load(void) { + +/** + * Signal handler for catching segfaults during core loading. + * Allows graceful error display instead of silent crash. + */ +static void core_load_segfault_handler(int sig) { + (void)sig; + if (in_core_load) { + siglongjmp(segfault_jmp, 1); + } + // Not in core_load - restore default handler and re-raise + signal(SIGSEGV, SIG_DFL); + raise(SIGSEGV); +} + +bool Core_load(void) { LOG_info("Core_load"); struct retro_game_info game_info; MinArchCore_buildGameInfo(&game, &game_info); LOG_info("game path: %s (%i)", game_info.path, game_info.size); - core.load_game(&game_info); + // Set up segfault handler to catch core crashes during load_game(). + // Some cores crash when given invalid ROM data or unsupported formats. + // Without this, the user would see a silent crash; with it, we can + // display a helpful error message before exiting gracefully. + LOG_debug("Setting up SIGSEGV handler for core load"); + struct sigaction sa, old_sa; + sa.sa_handler = core_load_segfault_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sigaction(SIGSEGV, &sa, &old_sa); + + capturing_core_errors = 1; + in_core_load = 1; + + LOG_debug("Calling core.load_game"); + bool success; + if (sigsetjmp(segfault_jmp, 1) == 0) { + // Normal path + success = core.load_game(&game_info); + LOG_debug("core.load_game returned %s", success ? "true" : "false"); + } else { + // Caught segfault - core crashed + LOG_error("Core crashed during load_game (SIGSEGV caught)"); + success = false; + if (fatal_error_detail[0] == '\0') { + setFatalError("Core crashed during initialization"); + } + } + + in_core_load = 0; + capturing_core_errors = 0; + sigaction(SIGSEGV, &old_sa, NULL); // Restore old handler + LOG_debug("Restored old SIGSEGV handler"); + + if (!success) { + LOG_debug("Core_load failed"); + if (fatal_error_detail[0] == '\0') { + setFatalError("Core could not be initialized"); + } + return false; + } SRAM_read(); RTC_read(); @@ -3624,6 +3733,7 @@ void Core_load(void) { LOG_info("aspect_ratio: %f (%ix%i) fps: %f", info.aspect_ratio, av_info.geometry.base_width, av_info.geometry.base_height, core.fps); + return true; } void Core_reset(void) { core.reset(); @@ -4863,6 +4973,85 @@ static void limitFF(void) { * @note Exits early if game fails to load */ +/** + * Display a fatal error message and wait for user acknowledgment. + * + * Shows "Game failed to load" title (large, white) and detail text (small, gray). + * Blocks until user presses A or B. + * + * Reads from global fatal_error_detail. + */ +static void showFatalError(void) { + static const char* title_text = "Game failed to start."; + + if (!screen || !font.large || !font.small) { + LOG_error("showFatalError: UI not initialized"); + return; + } + + // Copy and wrap detail text to fit screen + char detail[512]; + strncpy(detail, fatal_error_detail, sizeof(detail) - 1); + detail[sizeof(detail) - 1] = '\0'; + int text_width = ui.screen_width_px - ui.edge_padding_px * 2; + GFX_wrapText(font.small, detail, text_width, 0); + + char* pairs[] = {"B", "BACK", NULL}; + int dirty = 1; + + while (1) { + GFX_startFrame(); + PAD_poll(); + + if (PAD_justPressed(BTN_A) || PAD_justPressed(BTN_B)) + break; + + // Use NULL callbacks - no game state to save during error display + PWR_update(&dirty, NULL, NULL, NULL); + + if (dirty) { + GFX_clear(screen); + + // Calculate dimensions for vertical centering + int title_h = TTF_FontHeight(font.large); + int detail_line_h = TTF_FontLineSkip(font.small); + + // Count detail lines + int detail_lines = 1; + for (const char* p = detail; *p; p++) { + if (*p == '\n') + detail_lines++; + } + int detail_h = detail_lines * detail_line_h; + + int spacing = DP(4); + int total_h = title_h + spacing + detail_h; + int content_area_h = DP(ui.screen_height - ui.pill_height - ui.edge_padding * 2); + int y = DP(ui.edge_padding) + (content_area_h - total_h) / 2; + + // Title (large, white, centered) + SDL_Surface* title = TTF_RenderUTF8_Blended(font.large, title_text, COLOR_WHITE); + if (title) { + SDL_Rect title_rect = {(screen->w - title->w) / 2, y, title->w, title->h}; + SDL_BlitSurface(title, NULL, screen, &title_rect); + SDL_FreeSurface(title); + } + + y += title_h + spacing; + + // Detail text (small, gray, multi-line, left-aligned with standard padding) + GFX_blitText(font.small, detail, detail_line_h, COLOR_GRAY, screen, + &(SDL_Rect){ui.edge_padding_px, y, text_width, detail_h}); + + GFX_blitButtonGroup(pairs, 0, screen, 1); + GFX_flip(screen); + dirty = 0; + } else { + GFX_sync(); + } + } +} + // Main loop implementation selected at compile-time based on sync mode #ifdef SYNC_MODE_AUDIOCLOCK #include "minarch_loop_audioclock.inc" @@ -4971,13 +5160,32 @@ int main(int argc, char* argv[]) { if (!HAS_POWER_BUTTON) PWR_disableSleep(); + LOG_debug("InitSettings"); + InitSettings(); // Initialize early so GetMute() works in error display + LOG_debug("MSG_init"); MSG_init(); Core_open(core_path, tag_name); + if (!core.handle) { + LOG_debug("Core_open failed, core.handle=NULL"); + if (fatal_error_detail[0] != '\0') { + LOG_info("Showing fatal error: %s", fatal_error_detail); + showFatalError(); + } + goto finish; + } + Game_open(rom_path); // nes tries to load gamegenie setting before this returns ffs - if (!game.is_open) + if (!game.is_open) { + LOG_debug("Game_open failed, game.is_open=0"); + if (fatal_error_detail[0] != '\0') { + LOG_info("Showing fatal error: %s", fatal_error_detail); + GFX_clearBlit(); // Ensure UI rendering mode + showFatalError(); + } goto finish; + } simple_mode = exists(SIMPLE_MODE_PATH); @@ -5000,7 +5208,12 @@ int main(int argc, char* argv[]) { // ah, because it's defined before options_menu... options_menu.items[1].desc = (char*)core.version; - Core_load(); + if (!Core_load()) { + LOG_info("Showing fatal error: %s", fatal_error_detail); + GFX_clearBlit(); // Ensure UI rendering mode (core may have set game mode) + showFatalError(); + goto finish; + } LOG_debug("Input_init"); Input_init(NULL); @@ -5015,9 +5228,6 @@ int main(int argc, char* argv[]) { LOG_debug("SND_init (sample_rate=%.0f, fps=%.2f)", core.sample_rate, core.fps); SND_init(core.sample_rate, core.fps); - LOG_debug("InitSettings"); - InitSettings(); // after we initialize audio - LOG_debug("Menu_init"); Menu_init(); @@ -5031,10 +5241,11 @@ int main(int argc, char* argv[]) { run_main_loop(); Menu_quit(); - QuitSettings(); finish: + QuitSettings(); + Game_close(); Core_unload(); diff --git a/workspace/all/minarch/minarch_loop_audioclock.inc b/workspace/all/minarch/minarch_loop_audioclock.inc index eb17e203..ff71b1d1 100644 --- a/workspace/all/minarch/minarch_loop_audioclock.inc +++ b/workspace/all/minarch/minarch_loop_audioclock.inc @@ -26,6 +26,7 @@ static void run_main_loop(void) { sec_start = SDL_GetTicks(); while (!quit) { GFX_startFrame(); + input_polled_this_frame = 0; // Reset at start of frame // Always run core - audio blocking in SND_batchSamples() handles timing. // When audio buffer is full, the core will block (up to 10ms) waiting for @@ -68,9 +69,16 @@ static void run_main_loop(void) { trackFPS(); updateAutoCPU(); + // Fallback input poll - ensures MENU button and shortcuts work even when + // core doesn't call input_poll_callback (e.g., showing error screens). + // Guard inside callback prevents double execution. + input_poll_callback(); + if (show_menu) { + LOG_debug("Main loop: show_menu=1, entering Menu_loop"); GFX_clearBlit(); // Switch to UI mode for menu rendering Menu_loop(); + LOG_debug("Main loop: returned from Menu_loop"); } hdmimon(); diff --git a/workspace/all/minarch/minarch_loop_vsync.inc b/workspace/all/minarch/minarch_loop_vsync.inc index 5367a571..d57d6552 100644 --- a/workspace/all/minarch/minarch_loop_vsync.inc +++ b/workspace/all/minarch/minarch_loop_vsync.inc @@ -41,6 +41,7 @@ static void run_main_loop(void) { sec_start = SDL_GetTicks(); while (!quit) { GFX_startFrame(); + input_polled_this_frame = 0; // Reset at start of frame // Frame pacing: Bresenham accumulator decides whether to run core this vsync. // Vsync (from GFX_flip) is the timing source - each loop iteration = one display refresh. @@ -97,9 +98,16 @@ static void run_main_loop(void) { updateAutoCPU(); } + // Fallback input poll - ensures MENU button and shortcuts work even when + // core doesn't call input_poll_callback (e.g., showing error screens). + // Guard inside callback prevents double execution. + input_poll_callback(); + if (show_menu) { + LOG_debug("Main loop: show_menu=1, entering Menu_loop"); GFX_clearBlit(); // Switch to UI mode for menu rendering Menu_loop(); + LOG_debug("Main loop: returned from Menu_loop"); } hdmimon(); diff --git a/workspace/all/paks/MinUI/launch.sh.template b/workspace/all/paks/MinUI/launch.sh.template index 983d09b5..f08dcece 100644 --- a/workspace/all/paks/MinUI/launch.sh.template +++ b/workspace/all/paks/MinUI/launch.sh.template @@ -37,9 +37,8 @@ export LD_LIBRARY_PATH="$SYSTEM_PATH/lib:$LD_LIBRARY_PATH" # Native C binaries use LOG_FILE environment variable for file logging. # Each binary launch sets LOG_FILE inline for clarity and consistency. # Shell scripts use log.sh library for their own logging. -# Set LOG_SYNC=1 to enable crash-safe mode (fsync after each log write). - -# Uncomment the next line to enable crash-safe logging (slower but survives crashes): +# LOG_SYNC=1 enables crash-safe mode (fsync after each log write). +# Slower but ensures logs survive crashes - uncomment for debugging. # export LOG_SYNC=1 if [ -f "$SDCARD_PATH/.system/common/log.sh" ]; then From 01688bb3d57e61e2cf265d5d66d5d2792665a0ca Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Sat, 13 Dec 2025 21:41:12 -0800 Subject: [PATCH 2/6] Remove incorrectly mapped cores. --- skeleton/BASE/Bios/C128/.gitkeep | 0 skeleton/BASE/Bios/PET/.gitkeep | 0 skeleton/BASE/Bios/PLUS4/.gitkeep | 0 skeleton/BASE/Bios/VIC/.gitkeep | 0 workspace/all/paks/Emus/cores.json | 12 ------------ 5 files changed, 12 deletions(-) delete mode 100644 skeleton/BASE/Bios/C128/.gitkeep delete mode 100644 skeleton/BASE/Bios/PET/.gitkeep delete mode 100644 skeleton/BASE/Bios/PLUS4/.gitkeep delete mode 100644 skeleton/BASE/Bios/VIC/.gitkeep diff --git a/skeleton/BASE/Bios/C128/.gitkeep b/skeleton/BASE/Bios/C128/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/skeleton/BASE/Bios/PET/.gitkeep b/skeleton/BASE/Bios/PET/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/skeleton/BASE/Bios/PLUS4/.gitkeep b/skeleton/BASE/Bios/PLUS4/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/skeleton/BASE/Bios/VIC/.gitkeep b/skeleton/BASE/Bios/VIC/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/workspace/all/paks/Emus/cores.json b/workspace/all/paks/Emus/cores.json index 07c8b98e..0c1bcd8a 100644 --- a/workspace/all/paks/Emus/cores.json +++ b/workspace/all/paks/Emus/cores.json @@ -128,18 +128,6 @@ }, "COLECO": { "emu_exe": "bluemsx" - }, - "C128": { - "emu_exe": "vice_x64" - }, - "VIC": { - "emu_exe": "vice_x64" - }, - "PLUS4": { - "emu_exe": "vice_x64" - }, - "PET": { - "emu_exe": "vice_x64" } } } From 4c64bea533285c6e58a3e18cc1071c59ec51f384 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Sat, 13 Dec 2025 22:48:28 -0800 Subject: [PATCH 3/6] Emu build-time config merge, reduce platform configs by 95%. - Add merge_configs() to generate-paks.sh for base + platform merging - Platform configs now sparse (only unique settings, 1-2 lines each) - Delete 12 redundant configs, remove invalid gkdpixel platform - Update Emus README documenting merge behavior --- scripts/generate-paks.sh | 126 +++++++++++++++++- workspace/all/paks/Emus/README.md | 41 ++++-- .../paks/Emus/configs/gkdpixel/FC/default.cfg | 22 --- .../paks/Emus/configs/gkdpixel/GB/default.cfg | 18 --- .../Emus/configs/gkdpixel/GBC/default.cfg | 15 --- .../paks/Emus/configs/gkdpixel/MD/default.cfg | 20 --- .../paks/Emus/configs/gkdpixel/PS/default.cfg | 17 --- .../Emus/configs/gkdpixel/SFC/default.cfg | 14 -- .../all/paks/Emus/configs/m17/GB/default.cfg | 19 --- .../all/paks/Emus/configs/m17/GBA/default.cfg | 15 --- .../all/paks/Emus/configs/m17/GBC/default.cfg | 16 --- .../all/paks/Emus/configs/m17/MD/default.cfg | 19 --- .../all/paks/Emus/configs/m17/PS/default.cfg | 16 --- .../configs/rg35xxplus/FC/default-cube.cfg | 21 --- .../Emus/configs/rg35xxplus/GB/default.cfg | 20 --- .../configs/rg35xxplus/GBA/default-cube.cfg | 15 --- .../configs/rg35xxplus/GBA/default-wide.cfg | 15 --- .../Emus/configs/rg35xxplus/GBA/default.cfg | 15 --- .../Emus/configs/rg35xxplus/GBC/default.cfg | 17 --- .../Emus/configs/rg35xxplus/PS/default.cfg | 21 --- .../configs/rg35xxplus/SFC/default-cube.cfg | 13 -- .../paks/Emus/configs/rgb30/FC/default.cfg | 21 --- .../paks/Emus/configs/rgb30/GB/default.cfg | 19 --- .../paks/Emus/configs/rgb30/GBA/default.cfg | 15 --- .../paks/Emus/configs/rgb30/GBC/default.cfg | 16 --- .../paks/Emus/configs/rgb30/PS/default.cfg | 21 --- .../paks/Emus/configs/rgb30/SFC/default.cfg | 13 -- .../Emus/configs/tg5040/FC/default-brick.cfg | 21 --- .../paks/Emus/configs/tg5040/GB/default.cfg | 19 --- .../paks/Emus/configs/tg5040/GBA/default.cfg | 15 --- .../paks/Emus/configs/tg5040/GBC/default.cfg | 16 --- .../Emus/configs/tg5040/PS/default-brick.cfg | 21 --- .../Emus/configs/tg5040/SFC/default-brick.cfg | 13 -- .../Emus/configs/trimuismart/GB/default.cfg | 18 --- .../Emus/configs/trimuismart/GBC/default.cfg | 15 --- .../Emus/configs/trimuismart/MD/default.cfg | 19 --- .../Emus/configs/trimuismart/PS/default.cfg | 15 --- .../skeleton/SYSTEM/common/bin/arm/.gitkeep | 0 .../skeleton/SYSTEM/common/bin/arm64/.gitkeep | 0 39 files changed, 151 insertions(+), 621 deletions(-) delete mode 100755 workspace/all/paks/Emus/configs/gkdpixel/FC/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/gkdpixel/GB/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/gkdpixel/GBC/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/gkdpixel/MD/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/gkdpixel/PS/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/gkdpixel/SFC/default.cfg delete mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/GBA/default.cfg delete mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/PS/default.cfg delete mode 100644 workspace/all/paks/Emus/configs/rgb30/GB/default.cfg delete mode 100644 workspace/all/paks/Emus/configs/rgb30/GBC/default.cfg delete mode 100644 workspace/all/paks/Emus/configs/rgb30/PS/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/tg5040/GB/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/tg5040/GBA/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/tg5040/GBC/default.cfg delete mode 100644 workspace/all/paks/Emus/configs/trimuismart/GB/default.cfg delete mode 100644 workspace/all/paks/Emus/configs/trimuismart/GBC/default.cfg delete mode 100644 workspace/all/paks/Emus/skeleton/SYSTEM/common/bin/arm/.gitkeep delete mode 100644 workspace/all/paks/Emus/skeleton/SYSTEM/common/bin/arm64/.gitkeep diff --git a/scripts/generate-paks.sh b/scripts/generate-paks.sh index a20a32aa..c8f22dd9 100755 --- a/scripts/generate-paks.sh +++ b/scripts/generate-paks.sh @@ -63,6 +63,84 @@ PARALLEL_JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) # Export variables for parallel subprocesses export TEMPLATE_DIR BUILD_DIR PLATFORMS_JSON CORES_JSON +# Merge two config files with last-one-wins semantics +# Usage: merge_configs base_file override_file output_file +# If override_file doesn't exist or is empty, just copies base_file +merge_configs() { + local base_file="$1" + local override_file="$2" + local output_file="$3" + + # If no override, just copy base + if [ ! -f "$override_file" ]; then + cp "$base_file" "$output_file" + return + fi + + # Use awk to merge: base values first, then override values win + awk ' + BEGIN { key_count = 0 } + + # Function to extract key from a line (handles "bind X = Y" and "key = value" and "-key = value") + function get_key(line) { + # Remove leading/trailing whitespace + gsub(/^[[:space:]]+|[[:space:]]+$/, "", line) + + # Skip empty lines + if (line == "") return "" + + # Handle locked prefix + if (substr(line, 1, 1) == "-") { + line = substr(line, 2) + } + + # Find the = sign + eq_pos = index(line, "=") + if (eq_pos == 0) return "" + + key = substr(line, 1, eq_pos - 1) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", key) + return key + } + + # First pass: read base file, track order + FNR == NR { + key = get_key($0) + if (key != "") { + if (!(key in values)) { + order[key_count++] = key + } + values[key] = $0 + } else if ($0 ~ /^[[:space:]]*$/) { + # Preserve empty lines from base as placeholders + order[key_count++] = "__EMPTY__" key_count + values["__EMPTY__" key_count] = "" + } + next + } + + # Second pass: read override file, update values + { + key = get_key($0) + if (key != "") { + if (!(key in values)) { + # New key from override, add to end + order[key_count++] = key + } + values[key] = $0 + } + } + + END { + for (i = 0; i < key_count; i++) { + key = order[i] + print values[key] + } + } + ' "$base_file" "$override_file" > "$output_file" +} +export -f merge_configs + # Function to generate a single pak (called in parallel) # Arguments: platform core generate_pak() { @@ -92,17 +170,53 @@ generate_pak() { touch -r "$newest" "$output_dir/launch.sh" fi - # Copy config files - base first, then platform-specific overwrites + # Merge config files - base provides defaults, platform overrides specific values local cfg_base_dir="$TEMPLATE_DIR/configs/base/${core}" local cfg_platform_dir="$TEMPLATE_DIR/configs/${platform}/${core}" + local base_default="$cfg_base_dir/default.cfg" - if [ -d "$cfg_base_dir" ]; then - rsync -a "$cfg_base_dir"/*.cfg "$output_dir/" 2>/dev/null || true - fi - if [ -d "$cfg_platform_dir" ]; then - rsync -a "$cfg_platform_dir"/*.cfg "$output_dir/" 2>/dev/null || true + # Skip if no base config exists + if [ ! -f "$base_default" ]; then + return fi + # Generate default.cfg: merge base + platform override + merge_configs "$base_default" "$cfg_platform_dir/default.cfg" "$output_dir/default.cfg" + + # Generate device-variant configs (default-{device}.cfg) + # These inherit from base/default.cfg, then apply platform device-specific overrides + # Check both base and platform for device variants + local device_cfgs="" + [ -d "$cfg_base_dir" ] && device_cfgs="$device_cfgs $(ls "$cfg_base_dir"/default-*.cfg 2>/dev/null || true)" + [ -d "$cfg_platform_dir" ] && device_cfgs="$device_cfgs $(ls "$cfg_platform_dir"/default-*.cfg 2>/dev/null || true)" + + # Get unique device variant names + for cfg_path in $device_cfgs; do + [ -z "$cfg_path" ] && continue + local cfg_name=$(basename "$cfg_path") + local device_tag="${cfg_name#default-}" + device_tag="${device_tag%.cfg}" + + # Skip if already processed (dedup) + [ -f "$output_dir/$cfg_name" ] && continue + + # Device variants inherit from base/default.cfg, then apply device-specific overrides + # Priority: base/default.cfg -> base/default-{device}.cfg -> platform/default-{device}.cfg + local base_device="$cfg_base_dir/$cfg_name" + local platform_device="$cfg_platform_dir/$cfg_name" + + if [ -f "$base_device" ]; then + # Base has device variant - merge base/default.cfg + base/default-{device}.cfg first + merge_configs "$base_default" "$base_device" "$output_dir/$cfg_name.tmp" + # Then merge platform override if exists + merge_configs "$output_dir/$cfg_name.tmp" "$platform_device" "$output_dir/$cfg_name" + rm -f "$output_dir/$cfg_name.tmp" + else + # No base device variant - merge base/default.cfg + platform/default-{device}.cfg + merge_configs "$base_default" "$platform_device" "$output_dir/$cfg_name" + fi + done + } export -f generate_pak diff --git a/workspace/all/paks/Emus/README.md b/workspace/all/paks/Emus/README.md index f7d0e725..1a71ae40 100644 --- a/workspace/all/paks/Emus/README.md +++ b/workspace/all/paks/Emus/README.md @@ -32,29 +32,50 @@ minarch-paks/ ## Config Template Structure -Templates mirror the pak output structure: +Platform configs are **merged** onto base configs at build time, using last-one-wins semantics +(matching runtime behavior). This allows platform configs to be sparse - containing only the +settings that differ from base. **Template:** ``` configs/ - base/GBA/default.cfg → GBA.pak/default.cfg (fallback) - tg5040/GBA/default.cfg → GBA.pak/default.cfg (overwrites base) - tg5040/GBA/default-brick.cfg → GBA.pak/default-brick.cfg (adds brick variant) + base/GBA/default.cfg → Complete core defaults (bindings, locked options) + tg5040/GBA/default.cfg → Sparse overrides (just tg5040-specific settings) + tg5040/GBA/default-brick.cfg → Sparse brick-specific overrides ``` -**Generated Pak:** +**Generated Pak (after merge):** ``` GBA.pak/ launch.sh - default.cfg ← From tg5040/GBA/default.cfg (or base/GBA/ if missing) - default-brick.cfg ← From tg5040/GBA/default-brick.cfg (if exists) + default.cfg ← base/GBA/default.cfg merged with tg5040/GBA/default.cfg + default-brick.cfg ← base/GBA/default.cfg merged with tg5040/GBA/default-brick.cfg ``` +### Build-Time Merge Logic + +1. **default.cfg**: `base/{CORE}/default.cfg` + `{platform}/{CORE}/default.cfg` +2. **default-{device}.cfg**: `base/{CORE}/default.cfg` + `{platform}/{CORE}/default-{device}.cfg` + +When the same key appears in both files, the platform value wins (last-one-wins). + ### Key Principles -1. **Sparse Platform Overrides** - Only create `{platform}/{TAG}/` when you need platform-specific settings -2. **Additive Device Variants** - `default-{device}.cfg` files are ADDITIONAL configs, not replacements -3. **Fallback to Base** - If no platform override exists, uses `base/{TAG}/default.cfg` +1. **Sparse Platform Overrides** - Platform configs should contain ONLY settings that differ from base +2. **Cascading Merge** - Build-time behavior matches runtime (system.cfg → default.cfg → user.cfg) +3. **Device Variants Inherit** - `default-{device}.cfg` inherits from base `default.cfg`, then applies device-specific settings + +### Example: Sparse Platform Config + +Instead of duplicating all 15 lines from base, a platform config only needs the unique lines: + +```cfg +# configs/rg35xxplus/GBA/default-cube.cfg (just 2 lines!) +minarch_screen_scaling = Native +minarch_screen_sharpness = Sharp +``` + +The build script merges this with `base/GBA/default.cfg` to produce a complete 16-line config. ## Config Loading Hierarchy (Runtime) diff --git a/workspace/all/paks/Emus/configs/gkdpixel/FC/default.cfg b/workspace/all/paks/Emus/configs/gkdpixel/FC/default.cfg deleted file mode 100755 index 971d2571..00000000 --- a/workspace/all/paks/Emus/configs/gkdpixel/FC/default.cfg +++ /dev/null @@ -1,22 +0,0 @@ -minarch_screen_scaling = Native - -fceumm_sndquality = High -fceumm_sndvolume = 10 --fceumm_aspect = 8:7 PAR --fceumm_turbo_enable = Player 1 --fceumm_show_adv_system_options = disabled --fceumm_show_adv_sound_options = disabled - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Change Disk = NONE:L1 -bind Insert Disk = NONE:R1 -bind Insert Coin = NONE:R2 diff --git a/workspace/all/paks/Emus/configs/gkdpixel/GB/default.cfg b/workspace/all/paks/Emus/configs/gkdpixel/GB/default.cfg deleted file mode 100755 index d3e80e7b..00000000 --- a/workspace/all/paks/Emus/configs/gkdpixel/GB/default.cfg +++ /dev/null @@ -1,18 +0,0 @@ -gambatte_gb_colorization = internal -gambatte_gb_internal_palette = TWB64 - Pack 1 -gambatte_gb_palette_twb64_1 = TWB64 038 - Pokemon mini Ver. -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/gkdpixel/GBC/default.cfg b/workspace/all/paks/Emus/configs/gkdpixel/GBC/default.cfg deleted file mode 100755 index 606e7b74..00000000 --- a/workspace/all/paks/Emus/configs/gkdpixel/GBC/default.cfg +++ /dev/null @@ -1,15 +0,0 @@ -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/gkdpixel/MD/default.cfg b/workspace/all/paks/Emus/configs/gkdpixel/MD/default.cfg deleted file mode 100755 index 9c11684f..00000000 --- a/workspace/all/paks/Emus/configs/gkdpixel/MD/default.cfg +++ /dev/null @@ -1,20 +0,0 @@ -minarch_screen_scaling = Native - --picodrive_sound_rate = 44100 --picodrive_smstype = Auto --picodrive_smsfm = off --picodrive_smsmapper = Auto --picodrive_ggghost = off - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Mode = SELECT -bind Start = START -bind A Button = Y -bind B Button = X:B -bind C Button = A -bind X Button = B:L1 -bind Y Button = L1:X -bind Z Button = R1 diff --git a/workspace/all/paks/Emus/configs/gkdpixel/PS/default.cfg b/workspace/all/paks/Emus/configs/gkdpixel/PS/default.cfg deleted file mode 100755 index 09cc2c1c..00000000 --- a/workspace/all/paks/Emus/configs/gkdpixel/PS/default.cfg +++ /dev/null @@ -1,17 +0,0 @@ --pcsx_rearmed_display_internal_fps = disabled --pcsx_rearmed_show_input_settings = disabled - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Circle = A -bind Cross = B -bind Triangle = X -bind Square = Y -bind L1 Button = L1 -bind R1 Button = R1 -bind L2 Button = NONE:L2 -bind R2 Button = NONE:R2 diff --git a/workspace/all/paks/Emus/configs/gkdpixel/SFC/default.cfg b/workspace/all/paks/Emus/configs/gkdpixel/SFC/default.cfg deleted file mode 100755 index 34024e24..00000000 --- a/workspace/all/paks/Emus/configs/gkdpixel/SFC/default.cfg +++ /dev/null @@ -1,14 +0,0 @@ -minarch_screen_scaling = Native - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Y Button = Y -bind X Button = X -bind B Button = B -bind A Button = A -bind L Button = L1 -bind R Button = R1 \ No newline at end of file diff --git a/workspace/all/paks/Emus/configs/m17/GB/default.cfg b/workspace/all/paks/Emus/configs/m17/GB/default.cfg index 631f921e..588d12df 100755 --- a/workspace/all/paks/Emus/configs/m17/GB/default.cfg +++ b/workspace/all/paks/Emus/configs/m17/GB/default.cfg @@ -1,20 +1 @@ minarch_screen_sharpness = Crisp - -gambatte_gb_colorization = internal -gambatte_gb_internal_palette = TWB64 - Pack 1 -gambatte_gb_palette_twb64_1 = TWB64 038 - Pokemon mini Ver. -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/m17/GBA/default.cfg b/workspace/all/paks/Emus/configs/m17/GBA/default.cfg index 612de02a..588d12df 100755 --- a/workspace/all/paks/Emus/configs/m17/GBA/default.cfg +++ b/workspace/all/paks/Emus/configs/m17/GBA/default.cfg @@ -1,16 +1 @@ minarch_screen_sharpness = Crisp - --gpsp_save_method = libretro - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind L Button = L1 -bind R Button = R1 diff --git a/workspace/all/paks/Emus/configs/m17/GBC/default.cfg b/workspace/all/paks/Emus/configs/m17/GBC/default.cfg index feacafef..588d12df 100755 --- a/workspace/all/paks/Emus/configs/m17/GBC/default.cfg +++ b/workspace/all/paks/Emus/configs/m17/GBC/default.cfg @@ -1,17 +1 @@ minarch_screen_sharpness = Crisp - -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/m17/MD/default.cfg b/workspace/all/paks/Emus/configs/m17/MD/default.cfg index af4bc7e8..588d12df 100755 --- a/workspace/all/paks/Emus/configs/m17/MD/default.cfg +++ b/workspace/all/paks/Emus/configs/m17/MD/default.cfg @@ -1,20 +1 @@ minarch_screen_sharpness = Crisp - --picodrive_sound_rate = 44100 --picodrive_smstype = Auto --picodrive_smsfm = off --picodrive_smsmapper = Auto --picodrive_ggghost = off - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Mode = SELECT -bind Start = START -bind A Button = Y -bind B Button = X:B -bind C Button = A -bind X Button = B:L1 -bind Y Button = L1:X -bind Z Button = R1 diff --git a/workspace/all/paks/Emus/configs/m17/PS/default.cfg b/workspace/all/paks/Emus/configs/m17/PS/default.cfg index 032c3ea2..aa67d0ac 100755 --- a/workspace/all/paks/Emus/configs/m17/PS/default.cfg +++ b/workspace/all/paks/Emus/configs/m17/PS/default.cfg @@ -1,18 +1,2 @@ --pcsx_rearmed_display_internal_fps = disabled --pcsx_rearmed_show_input_settings = disabled - -minarch_gamepad_type = 1 -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Circle = A -bind Cross = B -bind Triangle = X -bind Square = Y -bind L1 Button = L1 -bind R1 Button = R1 bind L2 Button = NONE:L2 bind R2 Button = NONE:R2 diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/FC/default-cube.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/FC/default-cube.cfg index d70f896e..809ad66c 100644 --- a/workspace/all/paks/Emus/configs/rg35xxplus/FC/default-cube.cfg +++ b/workspace/all/paks/Emus/configs/rg35xxplus/FC/default-cube.cfg @@ -1,23 +1,2 @@ minarch_screen_scaling = Cropped minarch_screen_sharpness = Sharp - -fceumm_sndquality = High -fceumm_sndvolume = 10 --fceumm_aspect = 8:7 PAR --fceumm_turbo_enable = Player 1 --fceumm_show_adv_system_options = disabled --fceumm_show_adv_sound_options = disabled - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Change Disk = NONE:L1 -bind Insert Disk = NONE:R1 -bind Insert Coin = NONE:R2 diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GB/default.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GB/default.cfg index c86d692c..0cb4b441 100644 --- a/workspace/all/paks/Emus/configs/rg35xxplus/GB/default.cfg +++ b/workspace/all/paks/Emus/configs/rg35xxplus/GB/default.cfg @@ -1,21 +1 @@ -minarch_screen_scaling = Native minarch_screen_sharpness = Sharp - -gambatte_gb_colorization = internal -gambatte_gb_internal_palette = TWB64 - Pack 1 -gambatte_gb_palette_twb64_1 = TWB64 038 - Pokemon mini Ver. -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-cube.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-cube.cfg index e8e7a59e..eb64d37b 100644 --- a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-cube.cfg +++ b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-cube.cfg @@ -1,17 +1,2 @@ minarch_screen_scaling = Native minarch_screen_sharpness = Sharp - --gpsp_save_method = libretro - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind L Button = L1 -bind R Button = R1 diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg index e8e7a59e..eb64d37b 100644 --- a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg +++ b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg @@ -1,17 +1,2 @@ minarch_screen_scaling = Native minarch_screen_sharpness = Sharp - --gpsp_save_method = libretro - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind L Button = L1 -bind R Button = R1 diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default.cfg deleted file mode 100644 index a93522bc..00000000 --- a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default.cfg +++ /dev/null @@ -1,15 +0,0 @@ - --gpsp_save_method = libretro - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind L Button = L1 -bind R Button = R1 diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GBC/default.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GBC/default.cfg index 3fc42db9..0cb4b441 100644 --- a/workspace/all/paks/Emus/configs/rg35xxplus/GBC/default.cfg +++ b/workspace/all/paks/Emus/configs/rg35xxplus/GBC/default.cfg @@ -1,18 +1 @@ -minarch_screen_scaling = Native minarch_screen_sharpness = Sharp - -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/PS/default.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/PS/default.cfg deleted file mode 100644 index efe39427..00000000 --- a/workspace/all/paks/Emus/configs/rg35xxplus/PS/default.cfg +++ /dev/null @@ -1,21 +0,0 @@ - --pcsx_rearmed_display_internal_fps = disabled --pcsx_rearmed_show_input_settings = disabled - -pcsx_rearmed_dithering = enabled - -minarch_gamepad_type = 1 -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Circle = A -bind Cross = B -bind Triangle = X -bind Square = Y -bind L1 Button = L1 -bind R1 Button = R1 -bind L2 Button = L2 -bind R2 Button = R2 \ No newline at end of file diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/SFC/default-cube.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/SFC/default-cube.cfg index f888ca5d..809ad66c 100644 --- a/workspace/all/paks/Emus/configs/rg35xxplus/SFC/default-cube.cfg +++ b/workspace/all/paks/Emus/configs/rg35xxplus/SFC/default-cube.cfg @@ -1,15 +1,2 @@ minarch_screen_scaling = Cropped minarch_screen_sharpness = Sharp - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Y Button = Y -bind X Button = X -bind B Button = B -bind A Button = A -bind L Button = L1 -bind R Button = R1 \ No newline at end of file diff --git a/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg b/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg index 5071d35c..a339f49e 100644 --- a/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg +++ b/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg @@ -1,22 +1 @@ minarch_screen_scaling = Cropped - -fceumm_sndquality = High -fceumm_sndvolume = 10 --fceumm_aspect = 8:7 PAR --fceumm_turbo_enable = Player 1 --fceumm_show_adv_system_options = disabled --fceumm_show_adv_sound_options = disabled - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Change Disk = NONE:L1 -bind Insert Disk = NONE:R1 -bind Insert Coin = NONE:R2 diff --git a/workspace/all/paks/Emus/configs/rgb30/GB/default.cfg b/workspace/all/paks/Emus/configs/rgb30/GB/default.cfg deleted file mode 100644 index 4ef8034e..00000000 --- a/workspace/all/paks/Emus/configs/rgb30/GB/default.cfg +++ /dev/null @@ -1,19 +0,0 @@ - -gambatte_gb_colorization = internal -gambatte_gb_internal_palette = TWB64 - Pack 1 -gambatte_gb_palette_twb64_1 = TWB64 038 - Pokemon mini Ver. -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg b/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg index 859f07bd..e97b3703 100644 --- a/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg +++ b/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg @@ -1,16 +1 @@ minarch_screen_scaling = Native - --gpsp_save_method = libretro - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind L Button = L1 -bind R Button = R1 diff --git a/workspace/all/paks/Emus/configs/rgb30/GBC/default.cfg b/workspace/all/paks/Emus/configs/rgb30/GBC/default.cfg deleted file mode 100644 index e06978a3..00000000 --- a/workspace/all/paks/Emus/configs/rgb30/GBC/default.cfg +++ /dev/null @@ -1,16 +0,0 @@ - -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/rgb30/PS/default.cfg b/workspace/all/paks/Emus/configs/rgb30/PS/default.cfg deleted file mode 100644 index efe39427..00000000 --- a/workspace/all/paks/Emus/configs/rgb30/PS/default.cfg +++ /dev/null @@ -1,21 +0,0 @@ - --pcsx_rearmed_display_internal_fps = disabled --pcsx_rearmed_show_input_settings = disabled - -pcsx_rearmed_dithering = enabled - -minarch_gamepad_type = 1 -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Circle = A -bind Cross = B -bind Triangle = X -bind Square = Y -bind L1 Button = L1 -bind R1 Button = R1 -bind L2 Button = L2 -bind R2 Button = R2 \ No newline at end of file diff --git a/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg b/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg index 2d9b31aa..a339f49e 100644 --- a/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg +++ b/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg @@ -1,14 +1 @@ minarch_screen_scaling = Cropped - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Y Button = Y -bind X Button = X -bind B Button = B -bind A Button = A -bind L Button = L1 -bind R Button = R1 \ No newline at end of file diff --git a/workspace/all/paks/Emus/configs/tg5040/FC/default-brick.cfg b/workspace/all/paks/Emus/configs/tg5040/FC/default-brick.cfg index a3583cea..9e8fd1e5 100755 --- a/workspace/all/paks/Emus/configs/tg5040/FC/default-brick.cfg +++ b/workspace/all/paks/Emus/configs/tg5040/FC/default-brick.cfg @@ -1,22 +1 @@ minarch_screen_scaling = Aspect - -fceumm_sndquality = High -fceumm_sndvolume = 10 --fceumm_aspect = 8:7 PAR --fceumm_turbo_enable = Player 1 --fceumm_show_adv_system_options = disabled --fceumm_show_adv_sound_options = disabled - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Change Disk = NONE:L1 -bind Insert Disk = NONE:R1 -bind Insert Coin = NONE:R2 diff --git a/workspace/all/paks/Emus/configs/tg5040/GB/default.cfg b/workspace/all/paks/Emus/configs/tg5040/GB/default.cfg deleted file mode 100755 index 4ef8034e..00000000 --- a/workspace/all/paks/Emus/configs/tg5040/GB/default.cfg +++ /dev/null @@ -1,19 +0,0 @@ - -gambatte_gb_colorization = internal -gambatte_gb_internal_palette = TWB64 - Pack 1 -gambatte_gb_palette_twb64_1 = TWB64 038 - Pokemon mini Ver. -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/tg5040/GBA/default.cfg b/workspace/all/paks/Emus/configs/tg5040/GBA/default.cfg deleted file mode 100755 index a93522bc..00000000 --- a/workspace/all/paks/Emus/configs/tg5040/GBA/default.cfg +++ /dev/null @@ -1,15 +0,0 @@ - --gpsp_save_method = libretro - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind L Button = L1 -bind R Button = R1 diff --git a/workspace/all/paks/Emus/configs/tg5040/GBC/default.cfg b/workspace/all/paks/Emus/configs/tg5040/GBC/default.cfg deleted file mode 100755 index e06978a3..00000000 --- a/workspace/all/paks/Emus/configs/tg5040/GBC/default.cfg +++ /dev/null @@ -1,16 +0,0 @@ - -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/tg5040/PS/default-brick.cfg b/workspace/all/paks/Emus/configs/tg5040/PS/default-brick.cfg index 9f4fbec0..9e8fd1e5 100755 --- a/workspace/all/paks/Emus/configs/tg5040/PS/default-brick.cfg +++ b/workspace/all/paks/Emus/configs/tg5040/PS/default-brick.cfg @@ -1,22 +1 @@ minarch_screen_scaling = Aspect - --pcsx_rearmed_display_internal_fps = disabled --pcsx_rearmed_show_input_settings = disabled - -pcsx_rearmed_dithering = enabled - -minarch_gamepad_type = 1 -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Circle = A -bind Cross = B -bind Triangle = X -bind Square = Y -bind L1 Button = L1 -bind R1 Button = R1 -bind L2 Button = L2 -bind R2 Button = R2 \ No newline at end of file diff --git a/workspace/all/paks/Emus/configs/tg5040/SFC/default-brick.cfg b/workspace/all/paks/Emus/configs/tg5040/SFC/default-brick.cfg index f9177fde..9e8fd1e5 100755 --- a/workspace/all/paks/Emus/configs/tg5040/SFC/default-brick.cfg +++ b/workspace/all/paks/Emus/configs/tg5040/SFC/default-brick.cfg @@ -1,14 +1 @@ minarch_screen_scaling = Aspect - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Y Button = Y -bind X Button = X -bind B Button = B -bind A Button = A -bind L Button = L1 -bind R Button = R1 \ No newline at end of file diff --git a/workspace/all/paks/Emus/configs/trimuismart/GB/default.cfg b/workspace/all/paks/Emus/configs/trimuismart/GB/default.cfg deleted file mode 100644 index d3e80e7b..00000000 --- a/workspace/all/paks/Emus/configs/trimuismart/GB/default.cfg +++ /dev/null @@ -1,18 +0,0 @@ -gambatte_gb_colorization = internal -gambatte_gb_internal_palette = TWB64 - Pack 1 -gambatte_gb_palette_twb64_1 = TWB64 038 - Pokemon mini Ver. -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/trimuismart/GBC/default.cfg b/workspace/all/paks/Emus/configs/trimuismart/GBC/default.cfg deleted file mode 100644 index 606e7b74..00000000 --- a/workspace/all/paks/Emus/configs/trimuismart/GBC/default.cfg +++ /dev/null @@ -1,15 +0,0 @@ -gambatte_gb_bootloader = disabled --gambatte_audio_resampler = sinc - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind A Button = A -bind B Button = B -bind A Turbo = NONE:X -bind B Turbo = NONE:Y -bind Prev. Palette = NONE:L1 -bind Next Palette = NONE:R1 diff --git a/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg b/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg index 9c11684f..e97b3703 100644 --- a/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg +++ b/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg @@ -1,20 +1 @@ minarch_screen_scaling = Native - --picodrive_sound_rate = 44100 --picodrive_smstype = Auto --picodrive_smsfm = off --picodrive_smsmapper = Auto --picodrive_ggghost = off - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Mode = SELECT -bind Start = START -bind A Button = Y -bind B Button = X:B -bind C Button = A -bind X Button = B:L1 -bind Y Button = L1:X -bind Z Button = R1 diff --git a/workspace/all/paks/Emus/configs/trimuismart/PS/default.cfg b/workspace/all/paks/Emus/configs/trimuismart/PS/default.cfg index 09cc2c1c..aa67d0ac 100644 --- a/workspace/all/paks/Emus/configs/trimuismart/PS/default.cfg +++ b/workspace/all/paks/Emus/configs/trimuismart/PS/default.cfg @@ -1,17 +1,2 @@ --pcsx_rearmed_display_internal_fps = disabled --pcsx_rearmed_show_input_settings = disabled - -bind Up = UP -bind Down = DOWN -bind Left = LEFT -bind Right = RIGHT -bind Select = SELECT -bind Start = START -bind Circle = A -bind Cross = B -bind Triangle = X -bind Square = Y -bind L1 Button = L1 -bind R1 Button = R1 bind L2 Button = NONE:L2 bind R2 Button = NONE:R2 diff --git a/workspace/all/paks/Emus/skeleton/SYSTEM/common/bin/arm/.gitkeep b/workspace/all/paks/Emus/skeleton/SYSTEM/common/bin/arm/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/workspace/all/paks/Emus/skeleton/SYSTEM/common/bin/arm64/.gitkeep b/workspace/all/paks/Emus/skeleton/SYSTEM/common/bin/arm64/.gitkeep deleted file mode 100644 index e69de29b..00000000 From f66d1c6c481cd7d2c8e9c9df6739419ef00136cf Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Sat, 13 Dec 2025 22:57:59 -0800 Subject: [PATCH 4/6] Simplify emu configs to default to aspect + crisp scaling. --- workspace/all/paks/Emus/README.md | 50 +++++++++++-------- .../all/paks/Emus/configs/base/GB/default.cfg | 1 - .../paks/Emus/configs/base/GBC/default.cfg | 1 - .../all/paks/Emus/configs/m17/GB/default.cfg | 1 - .../all/paks/Emus/configs/m17/GBA/default.cfg | 1 - .../all/paks/Emus/configs/m17/GBC/default.cfg | 1 - .../all/paks/Emus/configs/m17/MD/default.cfg | 1 - .../Emus/configs/rg35xxplus/GB/default.cfg | 1 - .../configs/rg35xxplus/GBA/default-wide.cfg | 2 - .../Emus/configs/rg35xxplus/GBC/default.cfg | 1 - .../paks/Emus/configs/rgb30/FC/default.cfg | 1 + .../paks/Emus/configs/rgb30/GBA/default.cfg | 1 + .../paks/Emus/configs/rgb30/SFC/default.cfg | 1 + .../Emus/configs/tg5040/FC/default-brick.cfg | 1 - .../Emus/configs/tg5040/PS/default-brick.cfg | 1 - .../Emus/configs/tg5040/SFC/default-brick.cfg | 1 - .../Emus/configs/trimuismart/MD/default.cfg | 1 - 17 files changed, 33 insertions(+), 34 deletions(-) delete mode 100755 workspace/all/paks/Emus/configs/m17/GB/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/m17/GBA/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/m17/GBC/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/m17/MD/default.cfg delete mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/GB/default.cfg delete mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg delete mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/GBC/default.cfg delete mode 100755 workspace/all/paks/Emus/configs/tg5040/FC/default-brick.cfg delete mode 100755 workspace/all/paks/Emus/configs/tg5040/PS/default-brick.cfg delete mode 100755 workspace/all/paks/Emus/configs/tg5040/SFC/default-brick.cfg delete mode 100644 workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg diff --git a/workspace/all/paks/Emus/README.md b/workspace/all/paks/Emus/README.md index 1a71ae40..559005c0 100644 --- a/workspace/all/paks/Emus/README.md +++ b/workspace/all/paks/Emus/README.md @@ -65,6 +65,22 @@ When the same key appears in both files, the platform value wins (last-one-wins) 2. **Cascading Merge** - Build-time behavior matches runtime (system.cfg → default.cfg → user.cfg) 3. **Device Variants Inherit** - `default-{device}.cfg` inherits from base `default.cfg`, then applies device-specific settings +### Default Settings Philosophy + +Base configs use **Aspect** scaling and **Crisp** sharpness as defaults. This follows the principle that most users want games to fill their screens as much as possible while looking as good as possible. + +**Standard displays (4:3, 16:9):** +- `minarch_screen_scaling = Aspect` - Fill the screen while preserving aspect ratio +- `minarch_screen_sharpness = Crisp` - Sharp pixels with subtle smoothing + +**Square displays (cube, rgb30):** +- `minarch_screen_scaling = Native` or `Cropped` - Integer scaling for pixel-perfect display +- `minarch_screen_sharpness = Sharp` - Maximum crispness for integer-scaled pixels + +Platform overrides exist only when truly needed: +- **Square screens**: Different scaling for integer-pixel modes (cube, rgb30) +- **Button availability**: L2/R2 bindings for PS on devices that have those buttons (m17, trimuismart) + ### Example: Sparse Platform Config Instead of duplicating all 15 lines from base, a platform config only needs the unique lines: @@ -197,43 +213,37 @@ Create `default-{device}.cfg` when a specific core needs different settings on a 1. **Create template:** ```bash - # For GBA on Trimui Brick - skeleton/TEMPLATES/minarch-paks/configs/tg5040/GBA/default-brick.cfg + # For GBA on RG CubeXX (square screen) + workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-cube.cfg ``` -2. **Add device-specific settings:** +2. **Add device-specific settings (sparse - only what differs from base):** ```cfg - minarch_screen_scaling = Aspect - minarch_cpu_speed = Powersave - - -gpsp_save_method = libretro - - bind Up = UP - bind Down = DOWN - # ... (full button mapping) + minarch_screen_scaling = Native + minarch_screen_sharpness = Sharp ``` 3. **Regenerate paks:** ```bash - ./scripts/generate-paks.sh tg5040 GBA + ./scripts/generate-paks.sh rg35xxplus GBA ``` 4. **Result:** ``` GBA.pak/ - default.cfg ← Standard TG5040 - default-brick.cfg ← Brick variant + default.cfg ← Base config (Aspect + Crisp) + default-cube.cfg ← Merged: base + cube overrides (Native + Sharp) ``` ### Common Device Differences -**Cube (square screen):** -- `minarch_screen_scaling = Native` (integer scaling works better on square) -- `minarch_screen_sharpness = Sharp` (crisp pixels) +**Square screens (cube, rgb30):** +- `minarch_screen_scaling = Native` for systems that fit perfectly (GBA on 720×720) +- `minarch_screen_scaling = Cropped` for 4:3 systems (FC, SFC) to maximize size +- `minarch_screen_sharpness = Sharp` for pixel-perfect integer scaling -**Brick (smaller screen):** -- `minarch_screen_scaling = Aspect` (maximize visible area) -- May need different CPU speeds for performance +**Brick (smaller 4:3 screen):** +- Uses base defaults (Aspect + Crisp) - no overrides needed ## Complete Config Hierarchy diff --git a/workspace/all/paks/Emus/configs/base/GB/default.cfg b/workspace/all/paks/Emus/configs/base/GB/default.cfg index a5ec6286..4ef8034e 100644 --- a/workspace/all/paks/Emus/configs/base/GB/default.cfg +++ b/workspace/all/paks/Emus/configs/base/GB/default.cfg @@ -1,4 +1,3 @@ -minarch_screen_scaling = Native gambatte_gb_colorization = internal gambatte_gb_internal_palette = TWB64 - Pack 1 diff --git a/workspace/all/paks/Emus/configs/base/GBC/default.cfg b/workspace/all/paks/Emus/configs/base/GBC/default.cfg index c854f885..e06978a3 100644 --- a/workspace/all/paks/Emus/configs/base/GBC/default.cfg +++ b/workspace/all/paks/Emus/configs/base/GBC/default.cfg @@ -1,4 +1,3 @@ -minarch_screen_scaling = Native gambatte_gb_bootloader = disabled -gambatte_audio_resampler = sinc diff --git a/workspace/all/paks/Emus/configs/m17/GB/default.cfg b/workspace/all/paks/Emus/configs/m17/GB/default.cfg deleted file mode 100755 index 588d12df..00000000 --- a/workspace/all/paks/Emus/configs/m17/GB/default.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_sharpness = Crisp diff --git a/workspace/all/paks/Emus/configs/m17/GBA/default.cfg b/workspace/all/paks/Emus/configs/m17/GBA/default.cfg deleted file mode 100755 index 588d12df..00000000 --- a/workspace/all/paks/Emus/configs/m17/GBA/default.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_sharpness = Crisp diff --git a/workspace/all/paks/Emus/configs/m17/GBC/default.cfg b/workspace/all/paks/Emus/configs/m17/GBC/default.cfg deleted file mode 100755 index 588d12df..00000000 --- a/workspace/all/paks/Emus/configs/m17/GBC/default.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_sharpness = Crisp diff --git a/workspace/all/paks/Emus/configs/m17/MD/default.cfg b/workspace/all/paks/Emus/configs/m17/MD/default.cfg deleted file mode 100755 index 588d12df..00000000 --- a/workspace/all/paks/Emus/configs/m17/MD/default.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_sharpness = Crisp diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GB/default.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GB/default.cfg deleted file mode 100644 index 0cb4b441..00000000 --- a/workspace/all/paks/Emus/configs/rg35xxplus/GB/default.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg deleted file mode 100644 index eb64d37b..00000000 --- a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg +++ /dev/null @@ -1,2 +0,0 @@ -minarch_screen_scaling = Native -minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GBC/default.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GBC/default.cfg deleted file mode 100644 index 0cb4b441..00000000 --- a/workspace/all/paks/Emus/configs/rg35xxplus/GBC/default.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg b/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg index a339f49e..809ad66c 100644 --- a/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg +++ b/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg @@ -1 +1,2 @@ minarch_screen_scaling = Cropped +minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg b/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg index e97b3703..eb64d37b 100644 --- a/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg +++ b/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg @@ -1 +1,2 @@ minarch_screen_scaling = Native +minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg b/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg index a339f49e..809ad66c 100644 --- a/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg +++ b/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg @@ -1 +1,2 @@ minarch_screen_scaling = Cropped +minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/tg5040/FC/default-brick.cfg b/workspace/all/paks/Emus/configs/tg5040/FC/default-brick.cfg deleted file mode 100755 index 9e8fd1e5..00000000 --- a/workspace/all/paks/Emus/configs/tg5040/FC/default-brick.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_scaling = Aspect diff --git a/workspace/all/paks/Emus/configs/tg5040/PS/default-brick.cfg b/workspace/all/paks/Emus/configs/tg5040/PS/default-brick.cfg deleted file mode 100755 index 9e8fd1e5..00000000 --- a/workspace/all/paks/Emus/configs/tg5040/PS/default-brick.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_scaling = Aspect diff --git a/workspace/all/paks/Emus/configs/tg5040/SFC/default-brick.cfg b/workspace/all/paks/Emus/configs/tg5040/SFC/default-brick.cfg deleted file mode 100755 index 9e8fd1e5..00000000 --- a/workspace/all/paks/Emus/configs/tg5040/SFC/default-brick.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_scaling = Aspect diff --git a/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg b/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg deleted file mode 100644 index e97b3703..00000000 --- a/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg +++ /dev/null @@ -1 +0,0 @@ -minarch_screen_scaling = Native From e50bd88279aa06b21be2c24e0fe6a394c6100e45 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Sun, 14 Dec 2025 09:14:25 -0800 Subject: [PATCH 5/6] Add data-driven integer scaling config system. - New generate-scaling-configs.py script calculates optimal scaling per platform/core - Uses 95% fill threshold for large screens, 100% for small (<3") - minarch.c now forces Sharp pixels for Native/Cropped modes automatically - Generates 23 platform configs (only where integer scaling works well) - Cropped override for FC/SFC on square screens --- scripts/generate-scaling-configs.py | 378 ++++++++++++++++++ workspace/all/minarch/minarch.c | 6 +- .../all/paks/Emus/configs/m17/PS/default.cfg | 0 .../Emus/configs/miyoomini/MD/default.cfg | 2 + .../Emus/configs/miyoomini/PS/default.cfg | 2 + .../configs/rg35xxplus/FC/default-cube.cfg | 2 +- .../configs/rg35xxplus/GBA/default-cube.cfg | 2 +- .../configs/rg35xxplus/GBA/default-wide.cfg | 2 + .../Emus/configs/rg35xxplus/MD/default.cfg | 2 + .../configs/rg35xxplus/NGP/default-wide.cfg | 2 + .../Emus/configs/rg35xxplus/NGP/default.cfg | 2 + .../Emus/configs/rg35xxplus/PS/default.cfg | 2 + .../configs/rg35xxplus/SFC/default-cube.cfg | 2 +- .../paks/Emus/configs/rgb30/FC/default.cfg | 2 +- .../paks/Emus/configs/rgb30/GBA/default.cfg | 2 +- .../paks/Emus/configs/rgb30/SFC/default.cfg | 2 +- .../paks/Emus/configs/tg5040/GB/default.cfg | 2 + .../paks/Emus/configs/tg5040/GBC/default.cfg | 2 + .../paks/Emus/configs/tg5040/GG/default.cfg | 2 + .../Emus/configs/tg5040/MS/default-brick.cfg | 2 + .../Emus/configs/tg5040/NGP/default-brick.cfg | 2 + .../paks/Emus/configs/tg5040/PS/default.cfg | 2 + .../Emus/configs/trimuismart/MD/default.cfg | 2 + .../Emus/configs/trimuismart/NGP/default.cfg | 2 + .../Emus/configs/trimuismart/PS/default.cfg | 3 + 25 files changed, 421 insertions(+), 8 deletions(-) create mode 100755 scripts/generate-scaling-configs.py mode change 100755 => 100644 workspace/all/paks/Emus/configs/m17/PS/default.cfg create mode 100644 workspace/all/paks/Emus/configs/miyoomini/MD/default.cfg create mode 100644 workspace/all/paks/Emus/configs/miyoomini/PS/default.cfg create mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg create mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/MD/default.cfg create mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/NGP/default-wide.cfg create mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/NGP/default.cfg create mode 100644 workspace/all/paks/Emus/configs/rg35xxplus/PS/default.cfg create mode 100644 workspace/all/paks/Emus/configs/tg5040/GB/default.cfg create mode 100644 workspace/all/paks/Emus/configs/tg5040/GBC/default.cfg create mode 100644 workspace/all/paks/Emus/configs/tg5040/GG/default.cfg create mode 100644 workspace/all/paks/Emus/configs/tg5040/MS/default-brick.cfg create mode 100644 workspace/all/paks/Emus/configs/tg5040/NGP/default-brick.cfg create mode 100644 workspace/all/paks/Emus/configs/tg5040/PS/default.cfg create mode 100644 workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg create mode 100644 workspace/all/paks/Emus/configs/trimuismart/NGP/default.cfg diff --git a/scripts/generate-scaling-configs.py b/scripts/generate-scaling-configs.py new file mode 100755 index 00000000..3eb1a09f --- /dev/null +++ b/scripts/generate-scaling-configs.py @@ -0,0 +1,378 @@ +#!/usr/bin/env python3 +""" +Generate platform-specific scaling configs based on integer scaling analysis. + +This script is the SOURCE OF TRUTH for platform scaling overrides. +Change the rules here, regenerate, done. + +Usage: + ./scripts/generate-scaling-configs.py # Analyze only (dry run) + ./scripts/generate-scaling-configs.py --apply # Generate configs + ./scripts/generate-scaling-configs.py --clean # Remove all platform configs +""" + +import os +import sys +import shutil +from pathlib import Path + +# ============================================================================= +# CONFIGURATION - Edit these to change the rules +# ============================================================================= + +# Minimum fill percentage to recommend Native scaling +FILL_THRESHOLD_LARGE_SCREEN = 95 # Screens >= 3" +FILL_THRESHOLD_SMALL_SCREEN = 100 # Screens < 3" (must be perfect fit) + +# Screen size threshold (inches) +MIN_SCREEN_INCHES = 3.0 + +# ============================================================================= +# DEVICE DATA +# ============================================================================= + +# Format: (width, height, diagonal_inches) +PLATFORMS = { + "miyoomini": (640, 480, 2.8), # Under 3" - strict threshold + "rg35xxplus": (640, 480, 3.5), + "rgb30": (720, 720, 4.0), # Square + "m17": (480, 272, 4.3), # Miyoo A30 / similar + "trimuismart": (640, 480, 4.95), + "tg5040": (1280, 720, 4.96), # Trimui Smart Pro +} + +# Device variants (platform -> variant -> screen info) +VARIANTS = { + "rg35xxplus": { + "cube": (720, 720, 3.95), # Square + "wide": (720, 480, 3.4), + }, + "tg5040": { + "brick": (1024, 768, 3.2), + }, +} + +# Square screens get Sharp sharpness, others get Sharp too when Native +SQUARE_SCREENS = {"cube", "rgb30"} + +# ============================================================================= +# CORE DATA +# ============================================================================= + +CORES = { + "GB": (160, 144), + "GBC": (160, 144), + "GBA": (240, 160), + "FC": (256, 224), + "SFC": (256, 224), + "MD": (320, 224), + "PS": (320, 240), + "PCE": (256, 224), + "GG": (160, 144), + "MS": (256, 192), + "NGP": (160, 152), + "WSC": (224, 144), +} + +# ============================================================================= +# SPECIAL OVERRIDES - configs that need extra settings beyond scaling +# ============================================================================= + +# Extra lines to add to specific platform/core configs +EXTRA_CONFIG_LINES = { + ("m17", "PS", None): ["bind L2 Button = NONE:L2", "bind R2 Button = NONE:R2"], + ("trimuismart", "PS", None): ["bind L2 Button = NONE:L2", "bind R2 Button = NONE:R2"], +} + +# Scaling overrides (platform, core, variant) -> scaling value +# Use this for cases where math says one thing but experience says another +SCALING_OVERRIDES = { + # Square screens: FC/SFC only get 71% fill with Native, use Cropped instead + ("rg35xxplus", "FC", "cube"): "Cropped", + ("rg35xxplus", "SFC", "cube"): "Cropped", + ("rgb30", "FC", None): "Cropped", + ("rgb30", "SFC", None): "Cropped", +} + +# ============================================================================= +# CALCULATION LOGIC +# ============================================================================= + +def calc_fill(screen_w, screen_h, core_w, core_h): + """Calculate integer scaling fill percentage in limiting dimension.""" + scale_w = screen_w // core_w + scale_h = screen_h // core_h + scale = min(scale_w, scale_h) + + if scale == 0: + return 0, 0 + + if scale_w <= scale_h: + fill = (core_w * scale) / screen_w * 100 + else: + fill = (core_h * scale) / screen_h * 100 + + return fill, scale + + +def should_use_native(fill, screen_inches): + """Determine if Native scaling should be used based on fill % and screen size.""" + if screen_inches < MIN_SCREEN_INCHES: + return fill >= FILL_THRESHOLD_SMALL_SCREEN + return fill >= FILL_THRESHOLD_LARGE_SCREEN + + +def is_square_screen(platform, variant): + """Check if this is a square screen.""" + if variant in SQUARE_SCREENS: + return True + if platform in SQUARE_SCREENS: + return True + return False + + +# ============================================================================= +# CONFIG GENERATION +# ============================================================================= + +def generate_config_content(platform, core, variant, scaling, fill): + """Generate the content for a config file.""" + if scaling == "Cropped": + lines = ["# Auto-generated: Cropped fills screen by scaling up and trimming edges"] + else: + lines = [f"# Auto-generated: {fill:.0f}% fill with integer scaling"] + lines.append(f"minarch_screen_scaling = {scaling}") + # Note: Sharpness is automatic - minarch forces Sharp for Native/Cropped modes + + # Add any extra lines for this combo + key = (platform, core, variant) + if key in EXTRA_CONFIG_LINES: + lines.append("") + lines.extend(EXTRA_CONFIG_LINES[key]) + + return "\n".join(lines) + "\n" + + +def get_recommendations(): + """Calculate all recommendations based on current rules.""" + recommendations = [] + + # Base platforms + for platform, (screen_w, screen_h, inches) in PLATFORMS.items(): + for core, (core_w, core_h) in CORES.items(): + fill, scale = calc_fill(screen_w, screen_h, core_w, core_h) + + # Check for scaling override + override_key = (platform, core, None) + if override_key in SCALING_OVERRIDES: + scaling = SCALING_OVERRIDES[override_key] + recommendations.append({ + "platform": platform, + "core": core, + "variant": None, + "scaling": scaling, + "fill": fill, + "scale": scale, + "screen": f"{screen_w}x{screen_h}", + "inches": inches, + "reason": "override", + }) + elif should_use_native(fill, inches): + recommendations.append({ + "platform": platform, + "core": core, + "variant": None, + "scaling": "Native", + "fill": fill, + "scale": scale, + "screen": f"{screen_w}x{screen_h}", + "inches": inches, + "reason": "calculated", + }) + + # Variants + for platform, variants in VARIANTS.items(): + for variant, (screen_w, screen_h, inches) in variants.items(): + for core, (core_w, core_h) in CORES.items(): + fill, scale = calc_fill(screen_w, screen_h, core_w, core_h) + + # Check for scaling override + override_key = (platform, core, variant) + if override_key in SCALING_OVERRIDES: + scaling = SCALING_OVERRIDES[override_key] + recommendations.append({ + "platform": platform, + "core": core, + "variant": variant, + "scaling": scaling, + "fill": fill, + "scale": scale, + "screen": f"{screen_w}x{screen_h}", + "inches": inches, + "reason": "override", + }) + elif should_use_native(fill, inches): + recommendations.append({ + "platform": platform, + "core": core, + "variant": variant, + "scaling": "Native", + "fill": fill, + "scale": scale, + "screen": f"{screen_w}x{screen_h}", + "inches": inches, + "reason": "calculated", + }) + + # Also add configs that only have extra lines (no scaling change needed) + for (platform, core, variant), extra_lines in EXTRA_CONFIG_LINES.items(): + # Check if we already have a recommendation for this combo + existing = any( + r["platform"] == platform and r["core"] == core and r["variant"] == variant + for r in recommendations + ) + if not existing: + # Get screen info + if variant and platform in VARIANTS and variant in VARIANTS[platform]: + screen_w, screen_h, inches = VARIANTS[platform][variant] + else: + screen_w, screen_h, inches = PLATFORMS.get(platform, (0, 0, 0)) + + core_w, core_h = CORES.get(core, (0, 0)) + fill, scale = calc_fill(screen_w, screen_h, core_w, core_h) if core_w else (0, 0) + + recommendations.append({ + "platform": platform, + "core": core, + "variant": variant, + "scaling": None, # No scaling override, just extra lines + "fill": fill, + "scale": scale, + "screen": f"{screen_w}x{screen_h}", + "inches": inches, + "reason": "extra_lines_only", + }) + + return recommendations + + +# ============================================================================= +# FILE OPERATIONS +# ============================================================================= + +def get_config_path(base_dir, platform, core, variant): + """Get the path for a config file.""" + filename = f"default-{variant}.cfg" if variant else "default.cfg" + return base_dir / platform / core / filename + + +def clean_platform_configs(base_dir): + """Remove all platform config directories (not base/).""" + removed = [] + for item in base_dir.iterdir(): + if item.is_dir() and item.name != "base": + shutil.rmtree(item) + removed.append(item.name) + return removed + + +def write_configs(base_dir, recommendations): + """Write config files for all recommendations.""" + written = [] + for rec in recommendations: + path = get_config_path(base_dir, rec["platform"], rec["core"], rec["variant"]) + path.parent.mkdir(parents=True, exist_ok=True) + + # Generate content + if rec["scaling"]: + content = generate_config_content( + rec["platform"], rec["core"], rec["variant"], + rec["scaling"], rec["fill"] + ) + else: + # Extra lines only, no scaling + lines = EXTRA_CONFIG_LINES.get( + (rec["platform"], rec["core"], rec["variant"]), [] + ) + content = "\n".join(lines) + "\n" + + path.write_text(content) + written.append(path) + + return written + + +# ============================================================================= +# MAIN +# ============================================================================= + +def print_analysis(recommendations): + """Print analysis of recommendations.""" + print(f"\n{'=' * 70}") + print(f"INTEGER SCALING ANALYSIS") + print(f" Large screens (>= {MIN_SCREEN_INCHES}\"): {FILL_THRESHOLD_LARGE_SCREEN}%+ fill -> Native") + print(f" Small screens (< {MIN_SCREEN_INCHES}\"): {FILL_THRESHOLD_SMALL_SCREEN}%+ fill -> Native") + print(f"{'=' * 70}\n") + + # Group by platform + by_platform = {} + for rec in recommendations: + key = rec["platform"] + (f"-{rec['variant']}" if rec["variant"] else "") + if key not in by_platform: + by_platform[key] = [] + by_platform[key].append(rec) + + for platform in sorted(by_platform.keys()): + recs = by_platform[platform] + print(f"{platform} ({recs[0]['screen']}, {recs[0]['inches']}\"):") + for rec in sorted(recs, key=lambda r: -r["fill"]): + scaling = rec["scaling"] or "(extra lines only)" + reason = f" [{rec['reason']}]" if rec["reason"] != "calculated" else "" + print(f" {rec['core']:<5} {rec['fill']:>5.0f}% -> {scaling}{reason}") + print() + + print(f"Total configs to generate: {len(recommendations)}") + + +def main(): + # Find config directory + script_dir = Path(__file__).parent + config_dir = script_dir.parent / "workspace" / "all" / "paks" / "Emus" / "configs" + + if not config_dir.exists(): + print(f"Error: Config directory not found: {config_dir}") + sys.exit(1) + + # Parse arguments + apply_changes = "--apply" in sys.argv + clean_only = "--clean" in sys.argv + + if clean_only: + print(f"Cleaning platform configs in {config_dir}...") + removed = clean_platform_configs(config_dir) + print(f"Removed: {', '.join(removed) if removed else '(none)'}") + return + + # Calculate recommendations + recommendations = get_recommendations() + + # Print analysis + print_analysis(recommendations) + + if apply_changes: + print(f"\nApplying changes to {config_dir}...") + + # Clean old configs + removed = clean_platform_configs(config_dir) + if removed: + print(f"Removed old platform dirs: {', '.join(removed)}") + + # Write new configs + written = write_configs(config_dir, recommendations) + print(f"Created {len(written)} config files") + else: + print("\nDry run - use --apply to generate configs") + + +if __name__ == "__main__": + main() diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index 8084c759..e9f781ea 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -1425,7 +1425,8 @@ static void Config_syncFrontend(char* key, int value) { if (exactMatch(key, config.frontend.options[FE_OPT_SCALING].key)) { screen_scaling = value; - if (screen_scaling == MINARCH_SCALE_NATIVE) + // Integer scaling modes (Native/Cropped) always use sharp pixels + if (screen_scaling == MINARCH_SCALE_NATIVE || screen_scaling == MINARCH_SCALE_CROPPED) GFX_setSharpness(SHARPNESS_SHARP); else GFX_setSharpness(screen_sharpness); @@ -1440,7 +1441,8 @@ static void Config_syncFrontend(char* key, int value) { } else if (exactMatch(key, config.frontend.options[FE_OPT_SHARPNESS].key)) { screen_sharpness = value; - if (screen_scaling == MINARCH_SCALE_NATIVE) + // Integer scaling modes (Native/Cropped) always use sharp pixels + if (screen_scaling == MINARCH_SCALE_NATIVE || screen_scaling == MINARCH_SCALE_CROPPED) GFX_setSharpness(SHARPNESS_SHARP); else GFX_setSharpness(screen_sharpness); diff --git a/workspace/all/paks/Emus/configs/m17/PS/default.cfg b/workspace/all/paks/Emus/configs/m17/PS/default.cfg old mode 100755 new mode 100644 diff --git a/workspace/all/paks/Emus/configs/miyoomini/MD/default.cfg b/workspace/all/paks/Emus/configs/miyoomini/MD/default.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/miyoomini/MD/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/miyoomini/PS/default.cfg b/workspace/all/paks/Emus/configs/miyoomini/PS/default.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/miyoomini/PS/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/FC/default-cube.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/FC/default-cube.cfg index 809ad66c..dce3f14c 100644 --- a/workspace/all/paks/Emus/configs/rg35xxplus/FC/default-cube.cfg +++ b/workspace/all/paks/Emus/configs/rg35xxplus/FC/default-cube.cfg @@ -1,2 +1,2 @@ +# Auto-generated: Cropped fills screen by scaling up and trimming edges minarch_screen_scaling = Cropped -minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-cube.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-cube.cfg index eb64d37b..d2d5f668 100644 --- a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-cube.cfg +++ b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-cube.cfg @@ -1,2 +1,2 @@ +# Auto-generated: 100% fill with integer scaling minarch_screen_scaling = Native -minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/rg35xxplus/GBA/default-wide.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/MD/default.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/MD/default.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/rg35xxplus/MD/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/NGP/default-wide.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/NGP/default-wide.cfg new file mode 100644 index 00000000..9c54cd2d --- /dev/null +++ b/workspace/all/paks/Emus/configs/rg35xxplus/NGP/default-wide.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 95% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/NGP/default.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/NGP/default.cfg new file mode 100644 index 00000000..9c54cd2d --- /dev/null +++ b/workspace/all/paks/Emus/configs/rg35xxplus/NGP/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 95% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/PS/default.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/PS/default.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/rg35xxplus/PS/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/rg35xxplus/SFC/default-cube.cfg b/workspace/all/paks/Emus/configs/rg35xxplus/SFC/default-cube.cfg index 809ad66c..dce3f14c 100644 --- a/workspace/all/paks/Emus/configs/rg35xxplus/SFC/default-cube.cfg +++ b/workspace/all/paks/Emus/configs/rg35xxplus/SFC/default-cube.cfg @@ -1,2 +1,2 @@ +# Auto-generated: Cropped fills screen by scaling up and trimming edges minarch_screen_scaling = Cropped -minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg b/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg index 809ad66c..dce3f14c 100644 --- a/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg +++ b/workspace/all/paks/Emus/configs/rgb30/FC/default.cfg @@ -1,2 +1,2 @@ +# Auto-generated: Cropped fills screen by scaling up and trimming edges minarch_screen_scaling = Cropped -minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg b/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg index eb64d37b..d2d5f668 100644 --- a/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg +++ b/workspace/all/paks/Emus/configs/rgb30/GBA/default.cfg @@ -1,2 +1,2 @@ +# Auto-generated: 100% fill with integer scaling minarch_screen_scaling = Native -minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg b/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg index 809ad66c..dce3f14c 100644 --- a/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg +++ b/workspace/all/paks/Emus/configs/rgb30/SFC/default.cfg @@ -1,2 +1,2 @@ +# Auto-generated: Cropped fills screen by scaling up and trimming edges minarch_screen_scaling = Cropped -minarch_screen_sharpness = Sharp diff --git a/workspace/all/paks/Emus/configs/tg5040/GB/default.cfg b/workspace/all/paks/Emus/configs/tg5040/GB/default.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/tg5040/GB/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/tg5040/GBC/default.cfg b/workspace/all/paks/Emus/configs/tg5040/GBC/default.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/tg5040/GBC/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/tg5040/GG/default.cfg b/workspace/all/paks/Emus/configs/tg5040/GG/default.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/tg5040/GG/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/tg5040/MS/default-brick.cfg b/workspace/all/paks/Emus/configs/tg5040/MS/default-brick.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/tg5040/MS/default-brick.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/tg5040/NGP/default-brick.cfg b/workspace/all/paks/Emus/configs/tg5040/NGP/default-brick.cfg new file mode 100644 index 00000000..feb7f688 --- /dev/null +++ b/workspace/all/paks/Emus/configs/tg5040/NGP/default-brick.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 99% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/tg5040/PS/default.cfg b/workspace/all/paks/Emus/configs/tg5040/PS/default.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/tg5040/PS/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg b/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg new file mode 100644 index 00000000..d2d5f668 --- /dev/null +++ b/workspace/all/paks/Emus/configs/trimuismart/MD/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/trimuismart/NGP/default.cfg b/workspace/all/paks/Emus/configs/trimuismart/NGP/default.cfg new file mode 100644 index 00000000..9c54cd2d --- /dev/null +++ b/workspace/all/paks/Emus/configs/trimuismart/NGP/default.cfg @@ -0,0 +1,2 @@ +# Auto-generated: 95% fill with integer scaling +minarch_screen_scaling = Native diff --git a/workspace/all/paks/Emus/configs/trimuismart/PS/default.cfg b/workspace/all/paks/Emus/configs/trimuismart/PS/default.cfg index aa67d0ac..fb1ebc46 100644 --- a/workspace/all/paks/Emus/configs/trimuismart/PS/default.cfg +++ b/workspace/all/paks/Emus/configs/trimuismart/PS/default.cfg @@ -1,2 +1,5 @@ +# Auto-generated: 100% fill with integer scaling +minarch_screen_scaling = Native + bind L2 Button = NONE:L2 bind R2 Button = NONE:R2 From ef61378317fadaae5f052e7806d05832a6f92593 Mon Sep 17 00:00:00 2001 From: Nick Chapman Date: Sun, 14 Dec 2025 10:24:49 -0800 Subject: [PATCH 6/6] Review fixes. --- tests/unit/all/common/test_gfx_text.c | 20 ++++++ tests/unit/all/common/test_minarch_game.c | 75 +++++------------------ tests/unit/all/common/test_utils.c | 29 +++++++++ workspace/all/common/gfx_text.c | 3 + workspace/all/common/utils.c | 10 +++ workspace/all/common/utils.h | 14 +++++ workspace/all/minarch/minarch.c | 22 ++++--- workspace/all/minarch/minarch_game.c | 23 ++----- workspace/all/minarch/minarch_game.h | 11 ++-- 9 files changed, 113 insertions(+), 94 deletions(-) diff --git a/tests/unit/all/common/test_gfx_text.c b/tests/unit/all/common/test_gfx_text.c index 26eb6705..946c1beb 100644 --- a/tests/unit/all/common/test_gfx_text.c +++ b/tests/unit/all/common/test_gfx_text.c @@ -382,6 +382,23 @@ void test_wrapText_existing_newlines_count_toward_max_lines(void) { TEST_ASSERT_LESS_OR_EQUAL(2, newlines); } +void test_wrapText_returned_width_excludes_overflow(void) { + // Regression test: after wrapping, the returned max_line_width should NOT + // include the pre-wrap overflow width. Bug was: after wrapping we'd fall + // through and update max_line_width with the too-long measurement. + char text[256] = "aaa bb cc"; + + // max_width = 50 (5 chars at 10px each) + // "aaa bb" = 60px triggers wrap at space after "aaa" + // Result: "aaa\nbb cc" + // Line widths: "aaa" = 30px, "bb cc" = 50px + // Expected max_line_width: 50 (NOT 60 from the overflow) + int width = GFX_wrapText(&mock_font, text, 50, 0); + + TEST_ASSERT_EQUAL_STRING("aaa\nbb cc", text); + TEST_ASSERT_EQUAL_INT(50, width); // Bug would return 60 +} + /////////////////////////////// // GFX_sizeText() Tests /////////////////////////////// @@ -528,5 +545,8 @@ int main(void) { RUN_TEST(test_wrapText_multiple_existing_newlines); RUN_TEST(test_wrapText_existing_newlines_count_toward_max_lines); + // Regression tests + RUN_TEST(test_wrapText_returned_width_excludes_overflow); + return UNITY_END(); } diff --git a/tests/unit/all/common/test_minarch_game.c b/tests/unit/all/common/test_minarch_game.c index 31f52267..67ba58cc 100644 --- a/tests/unit/all/common/test_minarch_game.c +++ b/tests/unit/all/common/test_minarch_game.c @@ -32,90 +32,59 @@ void tearDown(void) { void test_parseExtensions_single_extension(void) { char exts[] = "gb"; char* out[32]; - bool supports_archive = false; - int count = MinArchGame_parseExtensions(exts, out, 32, &supports_archive); + int count = MinArchGame_parseExtensions(exts, out, 32); TEST_ASSERT_EQUAL_INT(1, count); TEST_ASSERT_EQUAL_STRING("gb", out[0]); TEST_ASSERT_NULL(out[1]); - TEST_ASSERT_FALSE(supports_archive); } void test_parseExtensions_multiple_extensions(void) { char exts[] = "gb|gbc|dmg"; char* out[32]; - bool supports_archive = false; - int count = MinArchGame_parseExtensions(exts, out, 32, &supports_archive); + int count = MinArchGame_parseExtensions(exts, out, 32); TEST_ASSERT_EQUAL_INT(3, count); TEST_ASSERT_EQUAL_STRING("gb", out[0]); TEST_ASSERT_EQUAL_STRING("gbc", out[1]); TEST_ASSERT_EQUAL_STRING("dmg", out[2]); TEST_ASSERT_NULL(out[3]); - TEST_ASSERT_FALSE(supports_archive); } -void test_parseExtensions_with_zip_support(void) { - char exts[] = "nes|fds|zip"; +void test_parseExtensions_with_archive_extensions(void) { + char exts[] = "nes|fds|zip|7z"; char* out[32]; - bool supports_archive = false; - int count = MinArchGame_parseExtensions(exts, out, 32, &supports_archive); + int count = MinArchGame_parseExtensions(exts, out, 32); - TEST_ASSERT_EQUAL_INT(3, count); - TEST_ASSERT_TRUE(supports_archive); -} - -void test_parseExtensions_zip_in_middle(void) { - char exts[] = "nes|zip|fds"; - char* out[32]; - bool supports_archive = false; - - int count = MinArchGame_parseExtensions(exts, out, 32, &supports_archive); - - TEST_ASSERT_EQUAL_INT(3, count); - TEST_ASSERT_TRUE(supports_archive); -} - -void test_parseExtensions_zip_only(void) { - char exts[] = "zip"; - char* out[32]; - bool supports_archive = false; - - int count = MinArchGame_parseExtensions(exts, out, 32, &supports_archive); - - TEST_ASSERT_EQUAL_INT(1, count); - TEST_ASSERT_TRUE(supports_archive); + TEST_ASSERT_EQUAL_INT(4, count); + TEST_ASSERT_EQUAL_STRING("zip", out[2]); + TEST_ASSERT_EQUAL_STRING("7z", out[3]); } void test_parseExtensions_empty_string(void) { char exts[] = ""; char* out[32]; - bool supports_archive = true; // Start true to verify it gets set false - int count = MinArchGame_parseExtensions(exts, out, 32, &supports_archive); + int count = MinArchGame_parseExtensions(exts, out, 32); TEST_ASSERT_EQUAL_INT(0, count); - TEST_ASSERT_FALSE(supports_archive); } void test_parseExtensions_null_string(void) { char* out[32]; - bool supports_archive = true; - int count = MinArchGame_parseExtensions(NULL, out, 32, &supports_archive); + int count = MinArchGame_parseExtensions(NULL, out, 32); TEST_ASSERT_EQUAL_INT(0, count); - TEST_ASSERT_FALSE(supports_archive); } void test_parseExtensions_null_output(void) { char exts[] = "gb|gbc"; - bool supports_archive = false; - int count = MinArchGame_parseExtensions(exts, NULL, 32, &supports_archive); + int count = MinArchGame_parseExtensions(exts, NULL, 32); TEST_ASSERT_EQUAL_INT(0, count); } @@ -123,9 +92,8 @@ void test_parseExtensions_null_output(void) { void test_parseExtensions_respects_max(void) { char exts[] = "a|b|c|d|e|f|g|h|i|j"; char* out[5]; - bool supports_archive = false; - int count = MinArchGame_parseExtensions(exts, out, 5, &supports_archive); + int count = MinArchGame_parseExtensions(exts, out, 5); TEST_ASSERT_EQUAL_INT(5, count); TEST_ASSERT_EQUAL_STRING("a", out[0]); @@ -136,24 +104,12 @@ void test_parseExtensions_typical_core(void) { // Typical SNES core extensions char exts[] = "smc|sfc|swc|fig|bs|st|bin"; char* out[32]; - bool supports_archive = false; - int count = MinArchGame_parseExtensions(exts, out, 32, &supports_archive); + int count = MinArchGame_parseExtensions(exts, out, 32); TEST_ASSERT_EQUAL_INT(7, count); TEST_ASSERT_EQUAL_STRING("smc", out[0]); TEST_ASSERT_EQUAL_STRING("bin", out[6]); - TEST_ASSERT_FALSE(supports_archive); -} - -void test_parseExtensions_null_supports_archive_pointer(void) { - char exts[] = "gb|zip"; - char* out[32]; - - // Should not crash when supports_archive is NULL - int count = MinArchGame_parseExtensions(exts, out, 32, NULL); - - TEST_ASSERT_EQUAL_INT(2, count); } /////////////////////////////// @@ -316,15 +272,12 @@ int main(void) { // parseExtensions tests RUN_TEST(test_parseExtensions_single_extension); RUN_TEST(test_parseExtensions_multiple_extensions); - RUN_TEST(test_parseExtensions_with_zip_support); - RUN_TEST(test_parseExtensions_zip_in_middle); - RUN_TEST(test_parseExtensions_zip_only); + RUN_TEST(test_parseExtensions_with_archive_extensions); RUN_TEST(test_parseExtensions_empty_string); RUN_TEST(test_parseExtensions_null_string); RUN_TEST(test_parseExtensions_null_output); RUN_TEST(test_parseExtensions_respects_max); RUN_TEST(test_parseExtensions_typical_core); - RUN_TEST(test_parseExtensions_null_supports_archive_pointer); // matchesExtension tests RUN_TEST(test_matchesExtension_exact_match); diff --git a/tests/unit/all/common/test_utils.c b/tests/unit/all/common/test_utils.c index f0a4fc20..df6604e2 100644 --- a/tests/unit/all/common/test_utils.c +++ b/tests/unit/all/common/test_utils.c @@ -113,6 +113,31 @@ void test_containsString_case_insensitive(void) { TEST_ASSERT_TRUE(containsString("Hello World", "world")); } +// strArrayContains tests +void test_strArrayContains_found(void) { + char* arr[] = {"zip", "7z", "nes", NULL}; + TEST_ASSERT_TRUE(strArrayContains(arr, "zip")); + TEST_ASSERT_TRUE(strArrayContains(arr, "7z")); + TEST_ASSERT_TRUE(strArrayContains(arr, "nes")); +} + +void test_strArrayContains_not_found(void) { + char* arr[] = {"zip", "7z", "nes", NULL}; + TEST_ASSERT_FALSE(strArrayContains(arr, "gz")); + TEST_ASSERT_FALSE(strArrayContains(arr, "ZIP")); // case-sensitive +} + +void test_strArrayContains_empty_array(void) { + char* arr[] = {NULL}; + TEST_ASSERT_FALSE(strArrayContains(arr, "zip")); +} + +void test_strArrayContains_null_inputs(void) { + char* arr[] = {"zip", NULL}; + TEST_ASSERT_FALSE(strArrayContains(NULL, "zip")); + TEST_ASSERT_FALSE(strArrayContains(arr, NULL)); +} + void test_hide_hidden_file(void) { TEST_ASSERT_TRUE(hide(".hidden")); TEST_ASSERT_TRUE(hide(".gitignore")); @@ -906,6 +931,10 @@ int main(void) { RUN_TEST(test_containsString_found); RUN_TEST(test_containsString_not_found); RUN_TEST(test_containsString_case_insensitive); + RUN_TEST(test_strArrayContains_found); + RUN_TEST(test_strArrayContains_not_found); + RUN_TEST(test_strArrayContains_empty_array); + RUN_TEST(test_strArrayContains_null_inputs); RUN_TEST(test_hide_hidden_file); RUN_TEST(test_hide_disabled_file); RUN_TEST(test_hide_map_txt); diff --git a/workspace/all/common/gfx_text.c b/workspace/all/common/gfx_text.c index bd652451..e94a1dd5 100644 --- a/workspace/all/common/gfx_text.c +++ b/workspace/all/common/gfx_text.c @@ -137,6 +137,9 @@ int GFX_wrapText(TTF_Font* ttf_font, char* str, int max_width, int max_lines) { line_start = last_space + 1; last_space = NULL; lines++; + // Reset p to scan the new line from the start, so we don't miss spaces + p = line_start; + continue; } // If no space to wrap at, we'll truncate at the end } diff --git a/workspace/all/common/utils.c b/workspace/all/common/utils.c index 25238398..49aacd5f 100644 --- a/workspace/all/common/utils.c +++ b/workspace/all/common/utils.c @@ -100,6 +100,16 @@ int containsString(char* haystack, char* needle) { return strcasestr(haystack, needle) != NULL; } +int strArrayContains(char** arr, const char* str) { + if (!arr || !str) + return 0; + for (int i = 0; arr[i]; i++) { + if (strcmp(arr[i], str) == 0) + return 1; + } + return 0; +} + /** * Determines if a file should be hidden in the UI. * diff --git a/workspace/all/common/utils.h b/workspace/all/common/utils.h index 6e95fc19..29fa1ef2 100644 --- a/workspace/all/common/utils.h +++ b/workspace/all/common/utils.h @@ -70,6 +70,20 @@ int exactMatch(const char* str1, const char* str2); */ int containsString(char* haystack, char* needle); +/** + * Checks if a NULL-terminated string array contains a specific string. + * + * @param arr NULL-terminated array of string pointers + * @param str String to search for (exact match, case-sensitive) + * @return 1 if string is found, 0 otherwise + * + * @example + * char* exts[] = {"zip", "7z", "nes", NULL}; + * strArrayContains(exts, "zip"); // returns 1 + * strArrayContains(exts, "gz"); // returns 0 + */ +int strArrayContains(char** arr, const char* str); + /** * Determines if a file should be hidden in the UI. * diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.c index e9f781ea..3bff4bbf 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.c @@ -224,14 +224,23 @@ static void Game_open(char* path) { // Handle archive files (.zip, .7z) if (MinArchArchive_isArchive(game.path)) { LOG_info("is archive file"); - bool supports_archive = false; char exts[128]; char* extensions[MINARCH_MAX_EXTENSIONS]; strcpy(exts, core.extensions); - MinArchGame_parseExtensions(exts, extensions, MINARCH_MAX_EXTENSIONS, &supports_archive); + MinArchGame_parseExtensions(exts, extensions, MINARCH_MAX_EXTENSIONS); - // Extract if core doesn't support the archive format natively - if (!supports_archive) { + // Check if core supports this specific archive format natively. + // Cores like FBNeo/MAME can handle archives directly (block_extract=true), + // but only for formats they explicitly list in their extensions. + const char* archive_ext = NULL; + if (suffixMatch(".zip", game.path)) + archive_ext = "zip"; + else if (suffixMatch(".7z", game.path)) + archive_ext = "7z"; + + bool core_handles_this_archive = archive_ext && strArrayContains(extensions, archive_ext); + + if (!core_handles_this_archive) { int result = MinArchArchive_extract(game.path, extensions, game.tmp_path, sizeof(game.tmp_path)); if (result != MINARCH_ARCHIVE_OK) { @@ -3664,9 +3673,8 @@ static void core_load_segfault_handler(int sig) { if (in_core_load) { siglongjmp(segfault_jmp, 1); } - // Not in core_load - restore default handler and re-raise - signal(SIGSEGV, SIG_DFL); - raise(SIGSEGV); + // Not in core_load - terminate immediately using async-signal-safe function + _exit(128 + SIGSEGV); } bool Core_load(void) { diff --git a/workspace/all/minarch/minarch_game.c b/workspace/all/minarch/minarch_game.c index efa76c8f..bd0d35ca 100644 --- a/workspace/all/minarch/minarch_game.c +++ b/workspace/all/minarch/minarch_game.c @@ -18,35 +18,20 @@ #include "utils.h" #endif -int MinArchGame_parseExtensions(char* extensions_str, char** out_extensions, int max_extensions, - bool* out_supports_archive) { - if (!extensions_str || !out_extensions || max_extensions <= 0) { - if (out_supports_archive) - *out_supports_archive = false; +int MinArchGame_parseExtensions(char* extensions_str, char** out_extensions, int max_extensions) { + if (!extensions_str || !out_extensions || max_extensions <= 0) return 0; - } int count = 0; - bool supports_archive = false; - char* ext; - - // Parse pipe-delimited extensions - ext = strtok(extensions_str, "|"); + char* ext = strtok(extensions_str, "|"); while (ext && count < max_extensions) { out_extensions[count++] = ext; - if (strcmp("zip", ext) == 0 || strcmp("7z", ext) == 0) { - supports_archive = true; - } ext = strtok(NULL, "|"); } // NULL-terminate the array - if (count < max_extensions) { + if (count < max_extensions) out_extensions[count] = NULL; - } - - if (out_supports_archive) - *out_supports_archive = supports_archive; return count; } diff --git a/workspace/all/minarch/minarch_game.h b/workspace/all/minarch/minarch_game.h index 1021a924..03c0ea91 100644 --- a/workspace/all/minarch/minarch_game.h +++ b/workspace/all/minarch/minarch_game.h @@ -31,20 +31,17 @@ * * @param extensions_str Pipe-delimited extension string (e.g., "gb|gbc|dmg") * Modified in place (strtok) - * @param out_extensions Array to receive extension pointers + * @param out_extensions Array to receive extension pointers (NULL-terminated) * @param max_extensions Maximum number of extensions to store - * @param out_supports_archive Set to true if "zip" or "7z" extension found * @return Number of extensions parsed * * @example * char exts[] = "gb|gbc|zip"; * char* ext_array[32]; - * bool supports_archive; - * int count = MinArchGame_parseExtensions(exts, ext_array, 32, &supports_archive); - * // count=3, ext_array={"gb","gbc","zip"}, supports_archive=true + * int count = MinArchGame_parseExtensions(exts, ext_array, 32); + * // count=3, ext_array={"gb","gbc","zip",NULL} */ -int MinArchGame_parseExtensions(char* extensions_str, char** out_extensions, int max_extensions, - bool* out_supports_archive); +int MinArchGame_parseExtensions(char* extensions_str, char** out_extensions, int max_extensions); /** * Checks if a filename matches any of the given extensions.