From 5b55c5447b82997a0e10cb91630039b4b022e8b5 Mon Sep 17 00:00:00 2001 From: SeveNIC8 Date: Sat, 24 Oct 2026 00:18:37 +0200 Subject: [PATCH 1/2] feat: add disc management functions (eject/Insert) and update menu for disc operations --- workspace/all/minarch/ma_game.c | 108 ++++++++++++++++++++++++++- workspace/all/minarch/ma_game.h | 3 + workspace/all/minarch/ma_menu.c | 125 +++++++++++++++++++++++--------- 3 files changed, 201 insertions(+), 35 deletions(-) diff --git a/workspace/all/minarch/ma_game.c b/workspace/all/minarch/ma_game.c index d971c388f..3a09a9369 100644 --- a/workspace/all/minarch/ma_game.c +++ b/workspace/all/minarch/ma_game.c @@ -11,9 +11,12 @@ struct Game game; struct retro_disk_control_ext_callback disk_control_ext; +static unsigned disc_index_to_restore; +static int has_disc_index_to_restore; void Game_open(char* path) { LOG_info("Game_open\n"); + has_disc_index_to_restore = 0; int skipzip = 0; memset(&game, 0, sizeof(game)); @@ -144,19 +147,120 @@ void Game_close(void) { VIB_setStrength(0); // just in case } +/// @brief Return -1 if the core doesn't support ejecting and inserting discs, 0 if the disc is ejected, 1 if a disc is inserted +/// @param +/// @return +int Game_getDiscEjectState(void) { + if (!disk_control_ext.get_eject_state || !disk_control_ext.set_eject_state) return -1; + return disk_control_ext.get_eject_state() ? 1 : 0; +} + +/// @brief Return -1 if the core doesn't support ejecting and inserting discs, 0 if the disc is ejected, 1 if a disc is inserted +/// @param +/// @return +int Game_getDiscInsertedState(void) { + if (!disk_control_ext.get_eject_state || !disk_control_ext.set_eject_state || + !disk_control_ext.get_image_index || !disk_control_ext.set_image_index || + !disk_control_ext.get_num_images) return -1; + if (disk_control_ext.get_eject_state()) return 0; + unsigned image_count = disk_control_ext.get_num_images(); + if (image_count == 0) return -1; + return disk_control_ext.get_image_index() < image_count; +} + +/// @brief Eject or insert the disc tray. If ejecting, the current disc index will be saved and restored when inserting. +int Game_setDiscEjectState(int ejected) { + if (!disk_control_ext.get_eject_state || !disk_control_ext.set_eject_state || + !disk_control_ext.get_image_index || !disk_control_ext.set_image_index || + !disk_control_ext.get_num_images) { + LOG_error("Core does not support ejecting and inserting discs\n"); + return 0; + } + + unsigned image_count = disk_control_ext.get_num_images(); + if (image_count == 0) { + LOG_error("Core has no disc images to eject or insert\n"); + return 0; + } + + if (ejected) { // Eject the disc tray + unsigned image_index = disk_control_ext.get_image_index(); + if (image_index < image_count) { + disc_index_to_restore = image_index; + has_disc_index_to_restore = 1; + } + + if (!disk_control_ext.set_eject_state(true)) { + LOG_error("Failed to eject disc tray\n"); + return 0; + } + if (!disk_control_ext.set_image_index(image_count)) { // + LOG_warn("Core rejected empty disc index; leaving drive ejected\n"); + } + return 1; + } + + if (!disk_control_ext.get_eject_state()) return 1; // Already inserted + + unsigned current_index = disk_control_ext.get_image_index(); + unsigned restore_index = current_index < image_count ? current_index : + (has_disc_index_to_restore ? disc_index_to_restore : 0); + if (restore_index >= image_count) restore_index = 0; + if (!disk_control_ext.set_image_index(restore_index)) { + LOG_error("Failed to insert disc\n"); + return 0; + } + if (!disk_control_ext.set_eject_state(false)) { + LOG_error("Failed to close disc tray\n"); + return 0; + } + has_disc_index_to_restore = 0; + return 1; +} + void Game_changeDisc(char* path) { - if (exactMatch(game.path, path) || !exists(path)) return; + if (!exists(path)) return; + if (exactMatch(game.path, path) && Game_getDiscInsertedState() != 0) return; + + if (!disk_control_ext.set_eject_state || !disk_control_ext.replace_image_index) { + LOG_error("Core does not support changing discs\n"); + return; + } + + if (!disk_control_ext.set_eject_state(true)) { + LOG_error("Failed to eject disc tray\n"); + return; + } Game_close(); Game_open(path); + if (!game.is_open) { + LOG_error("Failed to open disc: %s\n", path); + if (!disk_control_ext.set_eject_state(false)) { + LOG_error("Failed to close disc tray\n"); + } + return; + } + struct retro_game_info game_info = {}; game_info.path = game.path; game_info.data = game.data; game_info.size = game.size; - disk_control_ext.replace_image_index(0, &game_info); + if (!disk_control_ext.replace_image_index(0, &game_info)) { + LOG_error("Failed to replace disc with: %s\n", path); + if (!disk_control_ext.set_eject_state(false)) { + LOG_error("Failed to close disc tray\n"); + } + return; + } + + if (!disk_control_ext.set_eject_state(false)) { + LOG_error("Failed to close disc tray\n"); + } + putFile(CHANGE_DISC_PATH, path); // NextUI still needs to know this to update recents.txt } diff --git a/workspace/all/minarch/ma_game.h b/workspace/all/minarch/ma_game.h index 358ead21b..7bd5333a3 100644 --- a/workspace/all/minarch/ma_game.h +++ b/workspace/all/minarch/ma_game.h @@ -3,4 +3,7 @@ void Game_open(char* path); void Game_close(void); void Game_changeDisc(char* path); +int Game_getDiscEjectState(void); +int Game_getDiscInsertedState(void); +int Game_setDiscEjectState(int ejected); int extract_zip(char** extensions); diff --git a/workspace/all/minarch/ma_menu.c b/workspace/all/minarch/ma_menu.c index 85ca4341a..7dd0eea8e 100644 --- a/workspace/all/minarch/ma_menu.c +++ b/workspace/all/minarch/ma_menu.c @@ -113,11 +113,12 @@ void MSG_quit(void) { /////////////////////////////////////// -#define MENU_ITEM_COUNT 5 +#define MENU_ITEM_COUNT 6 #define MENU_SLOT_COUNT 8 enum { ITEM_CONT, + ITEM_EJECT, ITEM_SAVE, ITEM_LOAD, ITEM_OPTS, @@ -139,6 +140,8 @@ static struct { SDL_Surface* bitmap; SDL_Surface* overlay; char* items[MENU_ITEM_COUNT]; + int item_ids[MENU_ITEM_COUNT]; + int item_count; char* disc_paths[9]; // up to 9 paths, Arc the Lad Collection is 7 discs char minui_dir[256]; char slot_path[256]; @@ -159,6 +162,7 @@ static struct { .items = { [ITEM_CONT] = "Continue", + [ITEM_EJECT] = "Eject Disc", [ITEM_SAVE] = "Save", [ITEM_LOAD] = "Load", [ITEM_OPTS] = "Options", @@ -1760,6 +1764,23 @@ void Menu_loadState(void) { } } +/// @brief Sets the disc items menu entries based on the drive and disc insertion status +/// @param has_drive +static void Menu_setDiscItems(int has_drive) { + menu.item_count = 0; + menu.item_ids[menu.item_count++] = ITEM_CONT; + if (has_drive) { + menu.item_ids[menu.item_count++] = ITEM_EJECT; + } + menu.item_ids[menu.item_count++] = ITEM_SAVE; + menu.item_ids[menu.item_count++] = ITEM_LOAD; + menu.item_ids[menu.item_count++] = ITEM_OPTS; + menu.item_ids[menu.item_count++] = ITEM_QUIT; +} + + +/// @brief +/// @param void Menu_loop(void) { int cw, ch; @@ -1813,11 +1834,17 @@ void Menu_loop(void) { int rom_disc = -1; char disc_name[16]; if (menu.total_discs) { - rom_disc = menu.disc; + rom_disc = menu.disc; // store the current disc index for comparison later sprintf(disc_name, "Disc %i", menu.disc+1); } int selected = 0; // resets every launch + int tray_state = Game_getDiscEjectState(); + int disc_inserted = Game_getDiscInsertedState(); + int has_drive = tray_state >= 0 && disc_inserted >= 0; // TODO: check for drive presence on systems with no disc drive (eg. smart) + int disc_selection_changed = 0; + + Menu_setDiscItems(has_drive); Menu_initState(); int status = STATUS_CONT; // TODO: no longer used? @@ -1837,43 +1864,50 @@ void Menu_loop(void) { PAD_poll(); if (PAD_justPressed(BTN_UP)) { - selected -= 1; - if (selected<0) selected += MENU_ITEM_COUNT; + do { + selected = (selected + menu.item_count - 1) % menu.item_count; // wrap around menu entries + } while (menu.item_ids[selected]==ITEM_EJECT && disc_inserted<=0); // skip eject if no disc inserted dirty = 1; } else if (PAD_justPressed(BTN_DOWN)) { - selected += 1; - if (selected>=MENU_ITEM_COUNT) selected -= MENU_ITEM_COUNT; + do { + selected = (selected + 1) % menu.item_count; // wrap around menu entries + } while (menu.item_ids[selected]==ITEM_EJECT && disc_inserted<=0); // skip eject if no disc inserted dirty = 1; } else if (PAD_justPressed(BTN_LEFT)) { - if (menu.total_discs>1 && selected==ITEM_CONT) { - menu.disc -= 1; + if (menu.total_discs>1 && menu.item_ids[selected]==ITEM_CONT) { + menu.disc = Game_getDiscInsertedState() ? menu.disc - 1 : rom_disc; // reset to current disc if not changed yet + //menu.disc = 1; if (menu.disc<0) menu.disc += menu.total_discs; + disc_selection_changed = 1; dirty = 1; sprintf(disc_name, "Disc %i", menu.disc+1); } - else if (selected==ITEM_SAVE || selected==ITEM_LOAD) { + else if (menu.item_ids[selected]==ITEM_SAVE || menu.item_ids[selected]==ITEM_LOAD) { menu.slot -= 1; if (menu.slot<0) menu.slot += MENU_SLOT_COUNT; dirty = 1; } } else if (PAD_justPressed(BTN_RIGHT)) { - if (menu.total_discs>1 && selected==ITEM_CONT) { - menu.disc += 1; + if (menu.total_discs>1 && menu.item_ids[selected]==ITEM_CONT) { + menu.disc = Game_getDiscInsertedState() ? menu.disc + 1 : rom_disc; // reset to current disc if not changed yet + //menu.disc += 1; if (menu.disc==menu.total_discs) menu.disc -= menu.total_discs; + disc_selection_changed = 1; dirty = 1; sprintf(disc_name, "Disc %i", menu.disc+1); } - else if (selected==ITEM_SAVE || selected==ITEM_LOAD) { + else if (menu.item_ids[selected]==ITEM_SAVE || menu.item_ids[selected]==ITEM_LOAD) { menu.slot += 1; if (menu.slot>=MENU_SLOT_COUNT) menu.slot -= MENU_SLOT_COUNT; dirty = 1; } } - if (dirty && (selected==ITEM_SAVE || selected==ITEM_LOAD)) { + int selected_item = menu.item_ids[selected]; + if (dirty && (selected_item==ITEM_SAVE || selected_item==ITEM_LOAD)) { Menu_updateState(); } @@ -1882,18 +1916,31 @@ void Menu_loop(void) { show_menu = 0; } else if (PAD_justPressed(BTN_A)) { - switch(selected) { - case ITEM_CONT: - if (menu.total_discs && rom_disc!=menu.disc) { + switch(selected_item) { + case ITEM_CONT: //&& rom_disc!=menu.disc && disc_inserted != 0) { + if (menu.total_discs && disc_selection_changed) { // TODO: check for disc change on systems with no disc drive (eg. smart) status = STATUS_DISC; - char* disc_path = menu.disc_paths[menu.disc]; - Game_changeDisc(disc_path); + char* disc_path = menu.total_discs ? menu.disc_paths[menu.disc] : game.path; + Game_changeDisc(disc_path); // TODO: check for disc change on systems with no disc drive (eg. smart) + disc_inserted = Game_getDiscInsertedState(); // update disc state after change } else { status = STATUS_CONT; } show_menu = 0; break; + case ITEM_EJECT: + if (disc_inserted > 0 && Game_setDiscEjectState(1)) { + tray_state = Game_getDiscEjectState(); + disc_inserted = Game_getDiscInsertedState(); + if (tray_state >= 0 && disc_inserted >= 0) { + Menu_setDiscItems(has_drive); + if (disc_inserted == 0) selected = 0; + disc_selection_changed = 0; + dirty = 1; + } + } + break; case ITEM_SAVE: { Menu_saveState(); @@ -1979,30 +2026,24 @@ void Menu_loop(void) { GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OKAY", NULL }, 1, screen, 1); // list - oy = (((DEVICE_HEIGHT / FIXED_SCALE) - PADDING * 2) - (MENU_ITEM_COUNT * PILL_SIZE)) / 2; - for (int i=0; i1 && i==ITEM_CONT) { + if ((menu.total_discs>1 && item_id==ITEM_CONT) || + (has_drive && item_id==ITEM_CONT) || item_id==ITEM_EJECT) { GFX_blitPillDark(ASSET_WHITE_PILL, screen, &(SDL_Rect){ SCALE1(PADDING), - SCALE1(oy + PADDING), + SCALE1(oy + PADDING + (i * PILL_SIZE)), screen->w - SCALE1(PADDING * 2), SCALE1(PILL_SIZE) }); - text = TTF_RenderUTF8_Blended(font.large, disc_name, text_color); - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ - screen->w - SCALE1(PADDING + BUTTON_PADDING) - text->w, - SCALE1(oy + PADDING + 4) - }); - SDL_FreeSurface(text); } - TTF_SizeUTF8(font.large, item, &ow, NULL); ow += SCALE1(BUTTON_PADDING*2); @@ -2014,7 +2055,9 @@ void Menu_loop(void) { SCALE1(PILL_SIZE) }); } - + if (item_id==ITEM_EJECT && disc_inserted <= 0) { + text_color = uintToColour(THEME_COLOR3_255); + } // text text = TTF_RenderUTF8_Blended(font.large, item, text_color); @@ -2023,10 +2066,26 @@ void Menu_loop(void) { SCALE1(oy + PADDING + (i * PILL_SIZE) + 4) }); SDL_FreeSurface(text); + + if ((menu.total_discs>1 && item_id==ITEM_CONT) || (has_drive && item_id==ITEM_CONT)) { + SDL_Color state_color = i==selected ? uintToColour(THEME_COLOR5_255) : COLOR_WHITE; + const char* state_text = disc_inserted > 0 + ? (menu.total_discs ? disc_name : "Inserted") + : (disc_selection_changed ? disc_name : "No Disc"); + if (disc_inserted == 0 && !disc_selection_changed) { + state_color = uintToColour(THEME_COLOR3_255); + } + text = TTF_RenderUTF8_Blended(font.large, state_text, state_color); + SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + screen->w - SCALE1(PADDING + BUTTON_PADDING) - text->w, + SCALE1(oy + PADDING + (i * PILL_SIZE) + 4) + }); + SDL_FreeSurface(text); + } } // slot preview - if (selected==ITEM_SAVE || selected==ITEM_LOAD) { + if (selected_item==ITEM_SAVE || selected_item==ITEM_LOAD) { #define WINDOW_RADIUS 4 // TODO: this logic belongs in blitRect? #define PAGINATION_HEIGHT 6 // unscaled From f178079b2c93960c755c24fb4eca528eceae78b6 Mon Sep 17 00:00:00 2001 From: SeveNIC8 Date: Sun, 25 Oct 2026 00:40:21 +0200 Subject: [PATCH 2/2] fix: enhance eject functionality to check for available discs before allowing eject --- workspace/all/minarch/ma_menu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workspace/all/minarch/ma_menu.c b/workspace/all/minarch/ma_menu.c index 7dd0eea8e..68f0415df 100644 --- a/workspace/all/minarch/ma_menu.c +++ b/workspace/all/minarch/ma_menu.c @@ -1930,7 +1930,7 @@ void Menu_loop(void) { show_menu = 0; break; case ITEM_EJECT: - if (disc_inserted > 0 && Game_setDiscEjectState(1)) { + if (menu.total_discs > 0 && disc_inserted > 0 && Game_setDiscEjectState(1)) { // disable eject if no disc inserted or no disc drive tray_state = Game_getDiscEjectState(); disc_inserted = Game_getDiscInsertedState(); if (tray_state >= 0 && disc_inserted >= 0) { @@ -2055,7 +2055,7 @@ void Menu_loop(void) { SCALE1(PILL_SIZE) }); } - if (item_id==ITEM_EJECT && disc_inserted <= 0) { + if (item_id==ITEM_EJECT && (disc_inserted <= 0 || menu.total_discs <= 0)) { // disable eject if no disc inserted or no disc drive text_color = uintToColour(THEME_COLOR3_255); }