Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0cea191
feat(zaparoo): F2 toggle to switch the launcher between HDMI and CRT …
asturur May 9, 2026
6d2690b
fix(zaparoo): handle F2 toggle in user_io_kbd, not HandleUI
asturur May 9, 2026
390c141
Revert "fix(zaparoo): handle F2 toggle in user_io_kbd, not HandleUI"
asturur May 9, 2026
4220a82
Revert "feat(zaparoo): F2 toggle to switch the launcher between HDMI …
asturur May 9, 2026
7d0f6a4
feat(zaparoo): OSD menu entry to toggle CRT path
asturur May 9, 2026
4afaa67
feat(zaparoo): also surface CRT mode toggle in System Settings (MENU_…
asturur May 9, 2026
99f9829
feat(zaparoo): let F12 open the OSD on top of a running launcher
asturur May 9, 2026
155254e
feat(zaparoo): F12 on menu core opens System Settings directly, F12 c…
asturur May 9, 2026
9c831d0
fix(zaparoo): suppress fb_terminal nag and let System Settings render…
asturur May 9, 2026
54001f1
fix(zaparoo): F12/MENU open the OSD overlay over a running launcher
asturur May 9, 2026
00b7251
fix(zaparoo): allow OSD to close in CRT mode
asturur May 9, 2026
6dc8bb8
fix(zaparoo): scope the alt-launcher F-key exception to F12 only
asturur May 10, 2026
b9e3bea
feat(zaparoo): support custom MENU_RBF and consolidate alt-launcher F…
wizzomafizzo May 10, 2026
27019d8
fix(zaparoo): route joypad input to launcher in native CRT mode
wizzomafizzo May 10, 2026
08ccd5d
feat(zaparoo): persist alt-launcher CRT/HDMI mode across reboots
wizzomafizzo May 10, 2026
c7804d2
fix(zaparoo): clear FPGA CRT scan-out region before launcher spawn
wizzomafizzo May 10, 2026
892777c
refactor(zaparoo): trim alt-launcher OSD menu via dedicated helper
wizzomafizzo May 10, 2026
72037bc
refactor(zaparoo): drop ALT_LAUNCHER and MENU_RBF ini knobs
wizzomafizzo May 10, 2026
09a7467
fix(build): apply cumulative fork diff in stable build
wizzomafizzo May 10, 2026
54fbded
feat(zaparoo): restore exit-launcher escape-to-stock behavior
wizzomafizzo May 10, 2026
af81c62
feat(zaparoo): right-side Display Centering page with H/V offset
asturur May 10, 2026
02552bc
docs(zaparoo): add ZAPAROO_FORK.md change map and cleanup backlog
asturur May 10, 2026
7d6b40a
good
asturur May 10, 2026
3eeb116
removed dsstore
asturur May 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ static const ini_var_t ini_vars[] =
{ "XBE2_SHIFT", (void*)(&(cfg.xbe2_shift)), UINT16, 0, 0x22F },
{ "SPD_QUIRK", (void*)(&(cfg.spd_quirk)), UINT8, 0, 3 },
{ "ALT_LAUNCHER", (void*)(&(cfg.alt_launcher)), STRING, 0, sizeof(cfg.alt_launcher) - 1 },
{ "MENU_RBF", (void*)(&(cfg.menu_rbf)), STRING, 0, sizeof(cfg.menu_rbf) - 1 },
};

static const int nvars = (int)(sizeof(ini_vars) / sizeof(ini_var_t));
Expand Down
1 change: 1 addition & 0 deletions cfg.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ typedef struct {
uint16_t xbe2_shift;
uint8_t spd_quirk;
char alt_launcher[1024];
char menu_rbf[1024];
} cfg_t;

extern cfg_t cfg;
Expand Down
5 changes: 3 additions & 2 deletions file_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "scheduler.h"
#include "video.h"
#include "support.h"
#include "support/zaparoo/menu_rbf.h"

#define MIN(a,b) (((a)<(b)) ? (a) : (b))

Expand Down Expand Up @@ -1127,7 +1128,7 @@ void setStorage(int dev)
{
device = 0;
FileSave(CONFIG_DIR"/device.bin", &dev, sizeof(int));
fpga_load_rbf("menu.rbf");
fpga_load_rbf(menu_rbf_name());
}

static int orig_device = 0;
Expand Down Expand Up @@ -1693,7 +1694,7 @@ int ScanDirectory(char* path, int mode, const char *extension, int options, cons
// skip hidden files
if (!strncasecmp(de->d_name, ".", 1)) continue;
//skip non-selectable files
if (!strcasecmp(de->d_name, "menu.rbf")) continue;
if (is_menu_rbf(de->d_name)) continue;
if (!strncasecmp(de->d_name, "menu_20", 7)) continue;
if (!strncasecmp(de->d_name, "boot", 4))
{
Expand Down
5 changes: 3 additions & 2 deletions fpga_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "shmem.h"
#include "offload.h"
#include "support/zaparoo/alt_launcher.h"
#include "support/zaparoo/menu_rbf.h"

#include "fpga_base_addr_ac5.h"
#include "fpga_manager.h"
Expand Down Expand Up @@ -441,7 +442,7 @@ int fpga_load_rbf(const char *name, const char *cfg, const char *xml)
printf("Loading RBF: %s\n", name);

if(name[0] == '/') strcpy(path, name);
else sprintf(path, "%s/%s", !strcasecmp(name, "menu.rbf") ? getStorageDir(0) : getRootDir(), name);
else sprintf(path, "%s/%s", is_menu_rbf(name) ? getStorageDir(0) : getRootDir(), name);

Comment on lines 444 to 446

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bound path writes in fpga_load_rbf.

Line 445 writes into a fixed 1024-byte buffer with sprintf; a long name can overflow path and corrupt stack state. Please switch to bounded writes and reject truncation.

💡 Suggested fix
-	if(name[0] == '/') strcpy(path, name);
-	else sprintf(path, "%s/%s", is_menu_rbf(name) ? getStorageDir(0) : getRootDir(), name);
+	if (name[0] == '/')
+	{
+		if (snprintf(path, sizeof(path), "%s", name) >= (int)sizeof(path)) return -1;
+	}
+	else
+	{
+		if (snprintf(path, sizeof(path), "%s/%s",
+			is_menu_rbf(name) ? getStorageDir(0) : getRootDir(), name) >= (int)sizeof(path)) return -1;
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fpga_io.cpp` around lines 444 - 446, The write into the fixed char path[1024]
in fpga_load_rbf uses sprintf and can overflow; replace both
sprintf/sprintf-like usage with snprintf(path, sizeof(path), ...) and check the
return value for truncation (ret < 0 or ret >= sizeof(path)) and treat that as
an error path (reject the name and return/fail), and for the absolute case
(name[0]=='/') similarly use snprintf and check truncation; reference
is_menu_rbf, getStorageDir, getRootDir and the local buffer path when
implementing the bounded write and rejection on truncation.

int rbf = open(path, O_RDONLY);
if (rbf < 0)
Expand Down Expand Up @@ -504,7 +505,7 @@ int fpga_load_rbf(const char *name, const char *cfg, const char *xml)
}
close(rbf);

app_restart(!strcasecmp(name, "menu.rbf") ? "menu.rbf" : path, xml);
app_restart(is_menu_rbf(name) ? menu_rbf_name() : path, xml);
return ret;
}

Expand Down
2 changes: 1 addition & 1 deletion input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3564,7 +3564,7 @@ static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int
if (osd_event == 2) joy_digital(input[dev].num, 0, 0, 0, BTN_OSD);
}

if (user_io_osd_is_visible() || video_fb_state())
if (user_io_osd_is_visible() || video_fb_state() || alt_launcher_active())
{
if (ev->value <= 1)
{
Expand Down
84 changes: 71 additions & 13 deletions menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "hardware.h"
#include "menu.h"
#include "support/zaparoo/alt_launcher.h"
#include "support/zaparoo/menu_rbf.h"
#include "user_io.h"
#include "debug.h"
#include "fpga_io.h"
Expand Down Expand Up @@ -829,7 +830,11 @@ const char* get_rbf_name_bootcore(char *str)

static void vga_nag()
{
if (video_fb_state())
// With an alt launcher configured the user has fb_terminal on by design
// and the CRT is fed directly by the menu core (snow pattern when status[9]=0,
// or the launcher's framebuffer when status[9]=1). The "fix MiSTer.ini"
// nag is not appropriate — just leave the OSD off without the warning.
if (video_fb_state() && !alt_launcher_configured())
{
EnableOsd_on(OSD_VGA);
OsdSetSize(16);
Expand Down Expand Up @@ -1270,7 +1275,7 @@ void HandleUI(void)
}

//prevent OSD control while script is executing on framebuffer
if ((!video_fb_state() || video_chvt(0) != 2) && !select_ini)
if (((!video_fb_state() || video_chvt(0) != 2) || alt_launcher_active()) && !select_ini)
{
switch (c)
{
Expand All @@ -1285,11 +1290,14 @@ void HandleUI(void)
if (!ignore_osd_release)
menu = true;
ignore_osd_release = false;
if(video_fb_state()) video_menu_bg(user_io_status_get("[3:1]"));
video_fb_enable(0);
if (!alt_launcher_active())
{
if(video_fb_state()) video_menu_bg(user_io_status_get("[3:1]"));
video_fb_enable(0);
}
break;
case KEY_F1:
if (is_menu() && cfg.fb_terminal)
if (!alt_launcher_active() && is_menu() && cfg.fb_terminal)
{
user_io_status_set("[3:1]", user_io_status_get("[3:1]") + 1);
user_io_status_save(user_io_create_config_name());
Expand All @@ -1312,7 +1320,7 @@ void HandleUI(void)
break;

case KEY_F9:
if ((is_menu() || ((get_key_mod() & (LALT | RALT)) && (get_key_mod() & (LCTRL | RCTRL))) || has_fb_terminal) && cfg.fb_terminal)
if (!alt_launcher_active() && (is_menu() || ((get_key_mod() & (LALT | RALT)) && (get_key_mod() & (LCTRL | RCTRL))) || has_fb_terminal) && cfg.fb_terminal)
{
video_chvt(1);
video_fb_enable(!video_fb_state());
Expand Down Expand Up @@ -1376,7 +1384,6 @@ void HandleUI(void)
break;
}
}

if (select_ini)
{
DISKLED_ON;
Expand Down Expand Up @@ -1556,7 +1563,13 @@ void HandleUI(void)
menustate = MENU_UNLOCK1;
osd_code_entry[0] = 0;
}
else if (menu || (is_menu() && !video_fb_state()) || (menustate == MENU_NONE2 && !mgl->done && mgl->state == 1))
// On the menu core without an alt launcher, the menu *is* the screen —
// keep auto-opening the OSD whenever fb_terminal is off. With an alt
// launcher running, that rule would re-open System Settings the moment
// the user closes it (in CRT mode video_fb_state is false), so the OSD
// could never actually close. Suppress the auto-open in that case;
// explicit F12/MENU presses still open and close it normally.
else if (menu || (is_menu() && !video_fb_state() && !alt_launcher_active()) || (menustate == MENU_NONE2 && !mgl->done && mgl->state == 1))
{
OsdSetSize(16);
menusub = 0;
Expand All @@ -1577,8 +1590,19 @@ void HandleUI(void)
}
else if (is_menu())
{
menusub = 6;
SelectFile("", 0, SCANO_CORES, MENU_CORE_FILE_SELECTED1, MENU_SYSTEM1);
if (alt_launcher_configured())
{
// With an alt launcher, the file picker is not the user's
// natural entry point — they want the OSD overlay (System
// Settings) directly so they can flip CRT mode etc.
menustate = MENU_SYSTEM1;
menusub = 0;
}
else
{
menusub = 6;
SelectFile("", 0, SCANO_CORES, MENU_CORE_FILE_SELECTED1, MENU_SYSTEM1);
}
}
else if (is_minimig())
{
Expand Down Expand Up @@ -2811,6 +2835,10 @@ void HandleUI(void)
{
menumask |= (1ULL << ALT_LAUNCHER_MENUSUB);
MenuWrite(n++, " Launcher", menusub == ALT_LAUNCHER_MENUSUB, 0);

menumask |= (1ULL << ALT_LAUNCHER_CRT_MENUSUB);
sprintf(s, " CRT mode: %s", alt_launcher_native_crt() ? "On" : "Off");
MenuWrite(n++, s, menusub == ALT_LAUNCHER_CRT_MENUSUB, 0);
}

MenuWrite(n++, " Core \x16", menusub == 0, 0);
Expand Down Expand Up @@ -3059,6 +3087,11 @@ void HandleUI(void)
reboot_req = 1;
break;

case ALT_LAUNCHER_CRT_MENUSUB:
alt_launcher_toggle_crt();
menustate = MENU_COMMON1;
break;

default:
menustate = MENU_NONE1;
break;
Expand Down Expand Up @@ -3107,7 +3140,7 @@ void HandleUI(void)
}
}

if(!hold_cnt && reboot_req) fpga_load_rbf("menu.rbf");
if(!hold_cnt && reboot_req) fpga_load_rbf(menu_rbf_name());
break;

case MENU_VIDEOPROC1:
Expand Down Expand Up @@ -6685,7 +6718,11 @@ void HandleUI(void)
/* system menu */
/******************************************************************/
case MENU_SYSTEM1:
if (video_fb_state())
// Without an alt launcher, the wallpaper / fb_terminal flow can't coexist
// with this menu — bail out so vga_nag can show the MiSTer.ini warning.
// With an alt launcher the OSD overlay is exactly what we want on top of
// the running launcher, so let it render through.
if (video_fb_state() && !alt_launcher_configured())
{
menustate = MENU_NONE1;
break;
Expand All @@ -6698,6 +6735,7 @@ void HandleUI(void)
m = 0;
OsdSetTitle("System Settings", OSD_ARROW_LEFT);
menumask = 0x7F;
if (alt_launcher_configured()) menumask |= (1ULL << 7);

OsdWrite(m++);
sprintf(s, " MiSTer v%s", version + 5);
Expand Down Expand Up @@ -6750,6 +6788,11 @@ void HandleUI(void)
OsdWrite(m++, " Define joystick buttons \x16", menusub == 2);
OsdWrite(m++, " Scripts \x16", menusub == 3);
OsdWrite(m++, " Help \x16", menusub == 4);
if (alt_launcher_configured())
{
sprintf(s, " CRT mode: %-15s", alt_launcher_native_crt() ? "On" : "Off");
OsdWrite(m++, s, menusub == 7);
}
OsdWrite(m++, "");
cr = m;
OsdWrite(m++, " Reboot (hold \x16 cold reboot)", menusub == 5);
Expand All @@ -6765,6 +6808,13 @@ void HandleUI(void)
case MENU_SYSTEM2:
if (menu)
{
if (alt_launcher_configured())
{
// F12 toggles: the OSD opened directly into System Settings,
// pressing F12 again closes it instead of opening the core picker.
menustate = MENU_NONE1;
break;
}
SelectFile("", 0, SCANO_CORES, MENU_CORE_FILE_SELECTED1, MENU_SYSTEM1);
break;
}
Expand Down Expand Up @@ -6835,14 +6885,22 @@ void HandleUI(void)
case 6:
menustate = MENU_NONE1;
break;

case 7:
if (alt_launcher_configured())
{
alt_launcher_toggle_crt();
menustate = MENU_SYSTEM1;
}
break;
}
}
else if (left)
{
menustate = MENU_MISC1;
}

if (!hold_cnt && reboot_req) fpga_load_rbf("menu.rbf");
if (!hold_cnt && reboot_req) fpga_load_rbf(menu_rbf_name());
break;

case MENU_JOYSYSMAP:
Expand Down
42 changes: 42 additions & 0 deletions support/zaparoo/alt_launcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ static const int s_vt = 2;
static const char s_tty[] = "tty2";
static const char s_tty_path[] = "/dev/tty2";
static const char s_fb_mode_path[] = "/sys/module/MiSTer_fb/parameters/mode";
static const char s_crt_state_file[] = "alt_launcher_crt.bin";

static bool load_persisted_native_crt(void)
{
uint8_t v = 0;
FileLoadConfig(s_crt_state_file, &v, sizeof(v));
return v != 0;
}

static void save_persisted_native_crt(bool crt)
{
uint8_t v = crt ? 1 : 0;
FileSaveConfig(s_crt_state_file, &v, sizeof(v));
}

static void set_launcher_fb_mode(int fmt, int rb, int width, int height, int stride, bool log = true)
{
Expand Down Expand Up @@ -269,6 +283,27 @@ bool alt_launcher_active(void)
return s_pid != 0;
}

bool alt_launcher_native_crt(void)
{
return s_native_crt && s_pid != 0;
}

void alt_launcher_toggle_crt(void)
{
bool current_crt = alt_launcher_native_crt();
bool target_crt = !current_crt;

Comment thread
coderabbitai[bot] marked this conversation as resolved.
save_persisted_native_crt(target_crt);

printf("alt_launcher: toggle CRT path %d -> %d\n", current_crt, target_crt);

// Shutdown drops status[9], releases the FB mode and restores HPS framebuffer
// state regardless of whether the launcher was running. After it returns we
// always have a clean slate to spawn the next launcher invocation.
alt_launcher_shutdown();
alt_launcher_init(target_crt);
}
Comment on lines +404 to +409

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid blocking cooperative scheduling during CRT toggle.

This path synchronously calls alt_launcher_shutdown(), which can wait in bounded polling loops. Add scheduler_yield() in those wait loops (or make toggle async) so UI/poll coroutines stay responsive.

As per coding guidelines, "Long-running support code in cooperative scheduler context must call scheduler_yield() to stay cooperative and allow the coroutine to switch between poll and ui threads".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@support/zaparoo/alt_launcher.cpp` around lines 300 - 305, The shutdown path
calls alt_launcher_shutdown() synchronously and its bounded polling loops can
block the cooperative scheduler; modify alt_launcher_shutdown() to insert
scheduler_yield() inside any bounded/wait loops (e.g., the polling loops that
wait for FB/HPS release or status[9] drop) or convert the CRT toggle to an
asynchronous workflow so the coroutine can relinquish control; ensure
scheduler_yield() is called on each loop iteration or on long waits and keep
alt_launcher_init(target_crt) invocation unchanged so the caller remains
cooperative.


void alt_launcher_init(bool native_crt)
{
if (!cfg.alt_launcher[0] || !cfg.fb_terminal || s_pid || s_gave_up)
Expand Down Expand Up @@ -410,3 +445,10 @@ void zaparoo_alt_launcher_init_for_core(void)
alt_launcher_init(true);
}
}

void zaparoo_alt_launcher_init_for_menu(void)
{
bool crt = load_persisted_native_crt();
printf("alt_launcher: initializing menu launcher (persisted crt=%d)\n", crt);
alt_launcher_init(crt);
}
8 changes: 6 additions & 2 deletions support/zaparoo/alt_launcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

#include <stdint.h>

#define ALT_LAUNCHER_MENUSUB 31
#define ALT_LAUNCHER_MENUSUB 31
#define ALT_LAUNCHER_CRT_MENUSUB 30

void alt_launcher_init(bool native_crt = false);
void alt_launcher_init(bool native_crt);
void alt_launcher_poll(void);
void alt_launcher_shutdown(void);
void alt_launcher_toggle_crt(void);
bool alt_launcher_native_crt(void);
bool alt_launcher_active(void);
bool alt_launcher_configured(void);

Expand All @@ -15,3 +18,4 @@ uint16_t alt_launcher_fb_terminal_key(uint32_t mask, bool osd_button);

bool zaparoo_is_native_core(void);
void zaparoo_alt_launcher_init_for_core(void);
void zaparoo_alt_launcher_init_for_menu(void);
23 changes: 23 additions & 0 deletions support/zaparoo/menu_rbf.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "menu_rbf.h"
#include <string.h>
#include <strings.h>
#include "../../cfg.h"

const char *menu_rbf_name(void)
{
return cfg.menu_rbf[0] ? cfg.menu_rbf : "menu.rbf";
}

bool is_menu_rbf(const char *name)
{
if (!name || !name[0]) return false;
if (!strcasecmp(name, "menu.rbf")) return true;
if (cfg.menu_rbf[0])
{
if (!strcasecmp(name, cfg.menu_rbf)) return true;
const char *slash = strrchr(cfg.menu_rbf, '/');
const char *base = slash ? slash + 1 : cfg.menu_rbf;
if (base[0] && !strcasecmp(name, base)) return true;
}
return false;
}
4 changes: 4 additions & 0 deletions support/zaparoo/menu_rbf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma once

const char *menu_rbf_name(void);
bool is_menu_rbf(const char *name);
Loading