From 15d773fbc95625754326d429b2b2ae5a8f05925c Mon Sep 17 00:00:00 2001 From: Callan Barrett Date: Thu, 11 Jun 2026 13:57:42 +0800 Subject: [PATCH 1/3] feat(zaparoo): adopt native CRT video v2 DDR contract The Menu_MiSTer core dropped its status[9] CRT-enable bit and H/V offset status bits; publishing DDR frames is now the mode switch and centering moves into DDR word1 (frontend-owned). Update the Main side to match: - fb mode 320x240 stride 1280 -> 352x240 stride 1408; blank the full 3 MB v2 DDR region. - Remove all status[9] writes and the 500 ms re-assert timer; remove H/V offset handling (status pushes, persistence, getters/setters). - Zero DDR word0/word1 on CRT-path teardown so the core reverts to its noise pattern even after a frontend crash. - Add exit-code-42 respawn: the frontend rewrites zaparoo_launcher_crt.bin itself and exits 42 to be respawned under the new setting. - Remove the OSD CRT/Video pages entirely (launcher owns CRT mode now); this also shrinks the fork's menu.cpp diff against upstream. --- menu.cpp | 100 ----------------- support/zaparoo/alt_launcher.cpp | 155 +++++++++----------------- support/zaparoo/alt_launcher.h | 9 -- support/zaparoo/alt_launcher_menu.cpp | 8 +- support/zaparoo/launcher_pages.cpp | 101 ----------------- support/zaparoo/launcher_pages.h | 40 ------- 6 files changed, 54 insertions(+), 359 deletions(-) delete mode 100644 support/zaparoo/launcher_pages.cpp delete mode 100644 support/zaparoo/launcher_pages.h diff --git a/menu.cpp b/menu.cpp index ef17940f7..42f4147c5 100644 --- a/menu.cpp +++ b/menu.cpp @@ -51,7 +51,6 @@ along with this program. If not, see . #include "menu.h" #include "support/zaparoo/alt_launcher.h" #include "support/zaparoo/alt_launcher_menu.h" -#include "support/zaparoo/launcher_pages.h" #include "support/zaparoo/menu_rbf.h" #include "user_io.h" #include "debug.h" @@ -86,15 +85,6 @@ enum MENU MENU_MISC1, MENU_MISC2, - // Right-side companion to System Settings on the alt-launcher menu core. - // Top "Zaparoo Frontend" page lists sub-pages (Video) and an exit row; - // the Video sub-page hosts CRT mode + H/V centering offsets and binds - // left/right arrows to value adjustment. - MENU_ZAPAROO_LAUNCHER1, - MENU_ZAPAROO_LAUNCHER2, - MENU_ZAPAROO_VIDEO1, - MENU_ZAPAROO_VIDEO2, - MENU_SELECT_INI1, MENU_SELECT_INI2, @@ -6913,100 +6903,10 @@ void HandleUI(void) { menustate = MENU_MISC1; } - else if (right && alt_launcher_configured()) - { - menustate = MENU_ZAPAROO_LAUNCHER1; - menusub = 0; - } if (!hold_cnt && reboot_req) fpga_load_rbf(menu_rbf_name()); break; - /******************************************************************/ - /* zaparoo frontend pages (right-side sibling of System) */ - /******************************************************************/ - case MENU_ZAPAROO_LAUNCHER1: - if (!alt_launcher_configured()) - { - menustate = MENU_NONE1; - break; - } - helptext_idx = 0; - parentstate = menustate; - launcher_page_render(menusub, &menumask); - menustate = MENU_ZAPAROO_LAUNCHER2; - break; - - case MENU_ZAPAROO_LAUNCHER2: - if (menu) - { - menustate = MENU_NONE1; - break; - } - if (left) - { - menustate = MENU_SYSTEM1; - menusub = 0; - break; - } - if (right && menusub == 0) - { - menustate = MENU_ZAPAROO_VIDEO1; - menusub = 0; - break; - } - if (select) - { - int act = launcher_page_handle_select(menusub); - if (act == 1) - { - menustate = MENU_ZAPAROO_VIDEO1; - menusub = 0; - } - else if (act == 0) - { - menustate = MENU_NONE1; - } - } - break; - - case MENU_ZAPAROO_VIDEO1: - if (!alt_launcher_configured()) - { - menustate = MENU_NONE1; - break; - } - helptext_idx = 0; - parentstate = menustate; - video_page_render(menusub, &menumask); - menustate = MENU_ZAPAROO_VIDEO2; - break; - - case MENU_ZAPAROO_VIDEO2: - if (menu) - { - menustate = MENU_ZAPAROO_LAUNCHER1; - menusub = 0; - break; - } - if (left || right || plus || minus) - { - video_page_adjust(menusub, (right || plus) ? +1 : -1); - menustate = MENU_ZAPAROO_VIDEO1; - break; - } - if (select) - { - if (!video_page_handle_select(menusub)) - { - menustate = MENU_ZAPAROO_LAUNCHER1; - menusub = 0; - break; - } - menustate = MENU_ZAPAROO_VIDEO1; - } - break; - case MENU_JOYSYSMAP: strcpy(joy_bnames[SYS_BTN_A - DPAD_NAMES], "A"); strcpy(joy_bnames[SYS_BTN_B - DPAD_NAMES], "B"); diff --git a/support/zaparoo/alt_launcher.cpp b/support/zaparoo/alt_launcher.cpp index 6408ff2c0..b8be17a95 100644 --- a/support/zaparoo/alt_launcher.cpp +++ b/support/zaparoo/alt_launcher.cpp @@ -68,7 +68,6 @@ uint16_t alt_launcher_fb_terminal_key(uint32_t mask, bool osd_button) static pid_t s_pid = 0; static int s_crash_count = 0; static unsigned long s_respawn_timer = 0; -static unsigned long s_native_status_timer = 0; static unsigned long s_native_fb_mode_timer = 0; static bool s_gave_up = false; static bool s_init_pending = false; @@ -80,45 +79,20 @@ 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[] = "zaparoo_launcher_crt.bin"; -static const char s_offsets_file[] = "zaparoo_video_offsets.bin"; -static int8_t s_h_offset = 0; -static int8_t s_v_offset = 0; - -static int8_t clamp_offset(int8_t v) -{ - if (v < -8) return -8; - if (v > 7) return 7; - return v; -} +// Frontend exit code requesting a respawn after it rewrote +// zaparoo_launcher_crt.bin itself (launcher-owned CRT toggle). +#define ALT_LAUNCHER_EXIT_RELOAD 42 static bool load_persisted_native_crt(void) { + // Read-only on this side: the frontend writes the file (its in-app CRT + // toggle), then exits with ALT_LAUNCHER_EXIT_RELOAD to get respawned. 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 load_persisted_offsets(void) -{ - int8_t buf[2] = { 0, 0 }; - FileLoadConfig(s_offsets_file, buf, sizeof(buf)); - s_h_offset = clamp_offset(buf[0]); - s_v_offset = clamp_offset(buf[1]); -} - -static void save_persisted_offsets(void) -{ - int8_t buf[2] = { s_h_offset, s_v_offset }; - FileSaveConfig(s_offsets_file, buf, sizeof(buf)); -} - static void set_launcher_fb_mode(int fmt, int rb, int width, int height, int stride, bool log = true) { FILE *fp = fopen(s_fb_mode_path, "wt"); @@ -136,19 +110,20 @@ static void set_launcher_fb_mode(int fmt, int rb, int width, int height, int str static void set_native_crt_fb_mode(bool log = true) { - set_launcher_fb_mode(8888, 1, 320, 240, 1280, log); + set_launcher_fb_mode(8888, 1, 352, 240, 1408, log); } static void blank_native_crt_fb(void) { // CRT path doesn't read /dev/fb0 (kernel FB at 0x22000000). The frontend - // runs a worker that copies the top-left 320x240 from /dev/fb0 into a - // separate FPGA-mapped region at 0x3A000000 (control word + two 320x240 - // RGBA buffers). The FPGA scans out from that region. Nothing zeros it - // across frontend restarts or software reboots, so the previous session's - // last frame ghosts in until the frontend's writer thread starts. + // runs a worker that copies the top-left 352x240 from /dev/fb0 into a + // separate FPGA-mapped region at 0x3A000000 (two control words + two + // framebuffers, 3 MB under the v2 contract). The core scans out from + // that region. A zeroed word0 means "writer stopped", so this blank + // deterministically parks the core on its noise pattern — and clears any + // previous session's frame — until the frontend's writer publishes. const uint32_t native_addr = 0x3A000000u; - const uint32_t native_size = 0x000A0000u; + const uint32_t native_size = 0x00300000u; void *p = shmem_map(native_addr, native_size); if (!p) { @@ -160,6 +135,24 @@ static void blank_native_crt_fb(void) printf("alt_launcher: blanked %u bytes of CRT native video DDR at 0x%x\n", native_size, native_addr); } +static void zero_native_crt_words(void) +{ + // Only a zeroed word0 releases the core from the last published frame + // (there is no core-side disable bit), so teardown must clear the + // control words even when the frontend crashed before its own stop + // handler could. + const uint32_t native_addr = 0x3A000000u; + const uint32_t map_size = 0x1000u; + void *p = shmem_map(native_addr, map_size); + if (!p) + { + printf("alt_launcher: zero words shmem_map(0x%x, %u) failed\n", native_addr, map_size); + return; + } + memset(p, 0, 8); + shmem_unmap(p, map_size); +} + static void clear_launcher_tty(void) { int tty_fd = open(s_tty_path, O_WRONLY | O_CLOEXEC); @@ -224,11 +217,10 @@ static bool launcher_tty_ready(pid_t pid) static void disable_native_crt_path(void) { - user_io_status_set("[9]", 0); + zero_native_crt_words(); video_fb_enable(0); set_vga_fb(0); set_launcher_fb_mode(8888, 1, 960, 720, 3840); - s_native_status_timer = 0; s_native_fb_mode_timer = 0; } @@ -237,19 +229,16 @@ static void enable_native_crt_path(void) set_vga_fb(0); video_fb_enable(0); - // Double-write with a settle window so the kernel module's 320x240 layout - // is live before status[9] flips. Without this, the frontend renders for - // up to a second under stale dims (the post-fork retry timer used to be - // what eventually fixed the picture). + // Double-write with a settle window so the kernel module's 352x240 layout + // is live before the frontend's fb-geometry validation runs. Without + // this, the frontend renders for up to a second under stale dims (the + // post-fork retry timer used to be what eventually fixed the picture). set_native_crt_fb_mode(false); usleep(100000); set_native_crt_fb_mode(); blank_native_crt_fb(); - user_io_status_set("[9]", 1); - s_native_status_timer = GetTimer(500); - if (!s_native_status_timer) s_native_status_timer = 1; s_native_fb_mode_timer = GetTimer(1000); if (!s_native_fb_mode_timer) s_native_fb_mode_timer = 1; } @@ -401,10 +390,7 @@ static void spawn(void) if (!s_native_crt) video_fb_enable(1); else - { input_switch(0); - user_io_status_set("[9]", 1); - } // The frontend grabs input as soon as it starts. If the OSD is still // up (e.g. user toggled CRT mode or hit Reboot from System Settings), @@ -422,47 +408,6 @@ bool alt_launcher_native_crt(void) return s_native_crt && s_pid != 0; } -int8_t alt_launcher_h_offset(void) -{ - return s_h_offset; -} - -int8_t alt_launcher_v_offset(void) -{ - return s_v_offset; -} - -void alt_launcher_set_h_offset(int8_t v) -{ - s_h_offset = clamp_offset(v); - save_persisted_offsets(); - // 4-bit two's-complement bit pattern; FPGA reinterprets as signed -8..+7. - user_io_status_set("[13:10]", (uint32_t)((uint8_t)s_h_offset & 0xF)); -} - -void alt_launcher_set_v_offset(int8_t v) -{ - s_v_offset = clamp_offset(v); - save_persisted_offsets(); - user_io_status_set("[17:14]", (uint32_t)((uint8_t)s_v_offset & 0xF)); -} - -void alt_launcher_toggle_crt(void) -{ - bool current_crt = alt_launcher_native_crt(); - bool target_crt = !current_crt; - - 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 frontend was running. After it returns we - // always have a clean slate to spawn the next frontend invocation. - alt_launcher_shutdown(); - alt_launcher_init(target_crt); -} - void alt_launcher_init(bool native_crt) { if (!alt_launcher_configured() || s_pid || s_gave_up) @@ -514,13 +459,6 @@ void alt_launcher_poll(void) { if (s_pid) { - if (s_native_crt && s_native_status_timer && CheckTimer(s_native_status_timer)) - { - user_io_status_set("[9]", 1); - s_native_status_timer = GetTimer(500); - if (!s_native_status_timer) s_native_status_timer = 1; - } - if (s_native_crt && s_native_fb_mode_timer && CheckTimer(s_native_fb_mode_timer)) { set_native_crt_fb_mode(); @@ -537,6 +475,20 @@ void alt_launcher_poll(void) int sig = WIFSIGNALED(status) ? WTERMSIG(status) : 0; bool escaped = (exited && exit_status == 0) || sig == SIGTERM || sig == SIGINT; bool crashed = !escaped && (sig != 0 || (exited && exit_status != 0)); + // The frontend rewrote zaparoo_launcher_crt.bin itself and wants + // to be respawned under the new setting — release the current + // video path and re-enter through the normal init machinery. + if (exited && exit_status == ALT_LAUNCHER_EXIT_RELOAD) + { + bool crt = load_persisted_native_crt(); + printf("alt_launcher: reload requested, respawning (crt=%d)\n", crt); + release_launcher_video(); + reset_launcher_tty(); + s_respawn_timer = 0; + s_crash_count = 0; + alt_launcher_init(crt); + return; + } // Any exit while in CRT mode drops back to HDMI / no frontend // for the rest of this session — respawning into CRT after the // user already left it is a UX trap. The persisted CRT @@ -657,11 +609,6 @@ void zaparoo_alt_launcher_init_for_core(void) void zaparoo_alt_launcher_init_for_menu(void) { bool crt = load_persisted_native_crt(); - load_persisted_offsets(); - printf("alt_launcher: initializing menu frontend (persisted crt=%d, h=%d, v=%d)\n", - crt, s_h_offset, s_v_offset); - // Push the persisted offsets to the FPGA now that the menu RBF is loaded. - user_io_status_set("[13:10]", (uint32_t)((uint8_t)s_h_offset & 0xF)); - user_io_status_set("[17:14]", (uint32_t)((uint8_t)s_v_offset & 0xF)); + printf("alt_launcher: initializing menu frontend (persisted crt=%d)\n", crt); alt_launcher_init(crt); } diff --git a/support/zaparoo/alt_launcher.h b/support/zaparoo/alt_launcher.h index 3f8aad78a..4d24c4606 100644 --- a/support/zaparoo/alt_launcher.h +++ b/support/zaparoo/alt_launcher.h @@ -7,21 +7,12 @@ void alt_launcher_init(bool native_crt); void alt_launcher_poll(void); void alt_launcher_shutdown(void); -void alt_launcher_toggle_crt(void); void alt_launcher_prepare_for_script(void); void alt_launcher_resume_after_script(void); bool alt_launcher_native_crt(void); bool alt_launcher_active(void); bool alt_launcher_configured(void); -// Display centering: signed offsets clamped to -8..+7. Setters update the -// in-memory cache, persist to the config dir, and push to the FPGA via -// user_io_status_set so the change takes effect immediately. -int8_t alt_launcher_h_offset(void); -int8_t alt_launcher_v_offset(void); -void alt_launcher_set_h_offset(int8_t v); -void alt_launcher_set_v_offset(int8_t v); - void alt_launcher_cfg_apply(void); uint16_t alt_launcher_fb_terminal_key(uint32_t mask, bool osd_button); diff --git a/support/zaparoo/alt_launcher_menu.cpp b/support/zaparoo/alt_launcher_menu.cpp index a4e0d89d0..5067be869 100644 --- a/support/zaparoo/alt_launcher_menu.cpp +++ b/support/zaparoo/alt_launcher_menu.cpp @@ -23,9 +23,7 @@ int alt_launcher_render_system_menu(int menusub, uint64_t *menumask, char s[256]; int m = 0; - // Right arrow indicates a sibling page (Zaparoo Frontend) accessible - // via the right-arrow key — see MENU_ZAPAROO_LAUNCHER1 in menu.cpp. - OsdSetTitle("System Settings", OSD_ARROW_LEFT | OSD_ARROW_RIGHT); + OsdSetTitle("System Settings", OSD_ARROW_LEFT); *menumask = 0x1F; OsdWrite(m++); @@ -81,8 +79,8 @@ int alt_launcher_translate_system_select(int menusub) // Maps trimmed-menu menusub to upstream MENU_SYSTEM2 dispatch index: // 0 Remap -> 1, 1 Define joy -> 2, 2 Scripts -> 3, 3 Reboot -> 5, - // 4 Exit -> 6. CRT mode lives on the Zaparoo Frontend's Video - // sub-page now, not here. + // 4 Exit -> 6. CRT mode has no OSD entry — the launcher owns it + // (persisted bool + exit-code respawn, see alt_launcher.cpp). static const int map[] = { 1, 2, 3, 5, 6 }; if (menusub < 0 || menusub >= (int)(sizeof(map) / sizeof(map[0]))) return -1; return map[menusub]; diff --git a/support/zaparoo/launcher_pages.cpp b/support/zaparoo/launcher_pages.cpp deleted file mode 100644 index af16dd544..000000000 --- a/support/zaparoo/launcher_pages.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "launcher_pages.h" -#include "alt_launcher.h" -#include "osd.h" - -#include - -// Mirrors menu.cpp's STD_EXIT (a local #define there, kept in sync -// here rather than re-exporting it via a header touch). -#define LAUNCHER_STD_EXIT " exit" - -void launcher_page_render(int menusub, uint64_t *menumask) -{ - OsdSetSize(16); - OsdSetTitle("Zaparoo Frontend", OSD_ARROW_LEFT); - *menumask = 0x3; // Video, Exit - - int m = 0; - OsdWrite(m++); - OsdWrite(m++, ""); - OsdWrite(m++, ""); - OsdWrite(m++, ""); - OsdWrite(m++, ""); - - OsdWrite(m++, " Video \x16", menusub == 0); - - while (m < OsdGetSize() - 1) OsdWrite(m++, ""); - OsdWrite(15, LAUNCHER_STD_EXIT, menusub == 1); -} - -int launcher_page_handle_select(int menusub) -{ - switch (menusub) - { - case 0: return 1; - case 1: return 0; - default: return -1; - } -} - -void video_page_render(int menusub, uint64_t *menumask) -{ - OsdSetSize(16); - // No arrow flags: left/right are bound to value adjustment on this - // page, not sibling navigation. - OsdSetTitle("Video", 0); - *menumask = 0xF; // CRT, H Offset, V Offset, Exit - - char s[64]; - int m = 0; - - OsdWrite(m++); - OsdWrite(m++, ""); - OsdWrite(m++, ""); - - sprintf(s, " CRT mode: %-15s", alt_launcher_native_crt() ? "On" : "Off"); - OsdWrite(m++, s, menusub == 0); - - OsdWrite(m++, ""); - sprintf(s, " H Offset: %+3d", alt_launcher_h_offset()); - OsdWrite(m++, s, menusub == 1); - - sprintf(s, " V Offset: %+3d", alt_launcher_v_offset()); - OsdWrite(m++, s, menusub == 2); - - while (m < OsdGetSize() - 1) OsdWrite(m++, ""); - OsdWrite(15, LAUNCHER_STD_EXIT, menusub == 3); -} - -bool video_page_handle_select(int menusub) -{ - switch (menusub) - { - case 0: - alt_launcher_toggle_crt(); - return true; - case 3: - return false; - default: - return true; - } -} - -void video_page_adjust(int menusub, int dir) -{ - if (dir == 0) return; - int8_t step = (int8_t)(dir > 0 ? 1 : -1); - switch (menusub) - { - case 0: - alt_launcher_toggle_crt(); - break; - case 1: - alt_launcher_set_h_offset((int8_t)(alt_launcher_h_offset() + step)); - break; - case 2: - alt_launcher_set_v_offset((int8_t)(alt_launcher_v_offset() + step)); - break; - default: - break; - } -} diff --git a/support/zaparoo/launcher_pages.h b/support/zaparoo/launcher_pages.h deleted file mode 100644 index f6bd9df40..000000000 --- a/support/zaparoo/launcher_pages.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include - -// Right-side companion to the trimmed System Settings menu, reachable -// via the right arrow when alt_launcher_configured() is true. This file -// hosts both pages of that companion: the top-level "Zaparoo Frontend" -// page and the nested "Video" sub-page that owns the CRT mode toggle -// plus the H/V centering offsets. -// -// The Video page binds left/right arrows to value adjustment instead of -// sibling navigation, which is why it lives behind a sub-page rather -// than directly under System Settings. - -// Top "Zaparoo Frontend" page. menusub layout: 0 = Video, 1 = Exit. -void launcher_page_render(int menusub, uint64_t *menumask); - -// Translates a select press on the Frontend page. -// 1 -> entered Video sub-page -// 0 -> Exit pressed (close OSD) -// -1 -> no-op -int launcher_page_handle_select(int menusub); - -// Video sub-page. menusub layout: 0 = CRT mode, 1 = H Offset, -// 2 = V Offset, 3 = Exit. -// -// The OSD is closed automatically by the frontend spawn path (see -// MenuHide() in spawn() in alt_launcher.cpp) when CRT toggling -// triggers a respawn — these helpers don't need to signal that. -void video_page_render(int menusub, uint64_t *menumask); - -// Returns true if the press was consumed (re-render only); false when -// Exit was selected (caller pops back to the Frontend page). -bool video_page_handle_select(int menusub); - -// Adjust the highlighted row by `dir` (-1 / +1). Toggles CRT mode on -// the CRT row regardless of sign; ±1 on the H/V offset rows; no-op -// elsewhere. -void video_page_adjust(int menusub, int dir); From a0c1bae577eb0166bd8d3b611a781e96b65e6f3e Mon Sep 17 00:00:00 2001 From: Callan Barrett Date: Thu, 11 Jun 2026 17:23:58 +0800 Subject: [PATCH 2/3] feat(zaparoo): per-standard CRT fb geometry and OSD CRT mode toggle Extend zaparoo_launcher_crt.bin to two bytes [enabled, mode], where mode is the DDR word1 id (0 NTSC 352x240, 1 480i 720x480, 2 PAL 352x288). set_native_crt_fb_mode() programs the matching geometry and enable_native_crt_path() refreshes the mode from the file on every CRT spawn, so a video-standard change in the frontend (rewrite file, exit 42) gets the right framebuffer on respawn - including the T+1s re-assert, which previously stomped any non-NTSC shape back to 352x240. Legacy 1-byte files partial-read as NTSC. Restore a CRT mode toggle to the OSD System Settings menu, under Scripts. It flips byte 0 of the same state file (preserving the standard byte) and respawns the frontend through the same re-entry steps as the exit-42 reload, re-arming after an earlier give-up or escape. Contained in the zaparoo support files; menu.cpp untouched. --- support/zaparoo/alt_launcher.cpp | 96 +++++++++++++++++++++++++-- support/zaparoo/alt_launcher.h | 2 + support/zaparoo/alt_launcher_menu.cpp | 29 +++++--- 3 files changed, 112 insertions(+), 15 deletions(-) diff --git a/support/zaparoo/alt_launcher.cpp b/support/zaparoo/alt_launcher.cpp index b8be17a95..9fd3537ff 100644 --- a/support/zaparoo/alt_launcher.cpp +++ b/support/zaparoo/alt_launcher.cpp @@ -72,6 +72,7 @@ static unsigned long s_native_fb_mode_timer = 0; static bool s_gave_up = false; static bool s_init_pending = false; static bool s_native_crt = false; +static uint8_t s_native_crt_mode = 0; static bool s_resume_after_script = false; static bool s_script_resume_crt = false; static const int s_vt = 2; @@ -84,13 +85,33 @@ static const char s_crt_state_file[] = "zaparoo_launcher_crt.bin"; // zaparoo_launcher_crt.bin itself (launcher-owned CRT toggle). #define ALT_LAUNCHER_EXIT_RELOAD 42 +// zaparoo_launcher_crt.bin layout (written by the frontend, and by the +// OSD toggle below): +// byte 0: CRT enabled (0/1) +// byte 1: video standard as the DDR word1 mode id - 0 NTSC 352x240, +// 1 480i 720x480, 2 PAL 352x288. Absent in legacy 1-byte +// files; FileLoad partial-reads into the zeroed buffer, so +// legacy reads as NTSC. +// The mode byte lives here (not only in the frontend's toml) because +// this side programs the framebuffer geometry before the spawn and +// re-asserts it ~1 s after - both writes must match the standard the +// frontend will render, or the re-assert stomps a PAL framebuffer back +// to 352x240 under a running Qt. +static void load_persisted_native_crt_state(bool *enabled, uint8_t *mode) +{ + uint8_t v[2] = {0, 0}; + FileLoadConfig(s_crt_state_file, v, sizeof(v)); + if (v[1] > 2) v[1] = 0; + *enabled = v[0] != 0; + *mode = v[1]; +} + static bool load_persisted_native_crt(void) { - // Read-only on this side: the frontend writes the file (its in-app CRT - // toggle), then exits with ALT_LAUNCHER_EXIT_RELOAD to get respawned. - uint8_t v = 0; - FileLoadConfig(s_crt_state_file, &v, sizeof(v)); - return v != 0; + bool enabled; + uint8_t mode; + load_persisted_native_crt_state(&enabled, &mode); + return enabled; } static void set_launcher_fb_mode(int fmt, int rb, int width, int height, int stride, bool log = true) @@ -110,7 +131,18 @@ static void set_launcher_fb_mode(int fmt, int rb, int width, int height, int str static void set_native_crt_fb_mode(bool log = true) { - set_launcher_fb_mode(8888, 1, 352, 240, 1408, log); + switch (s_native_crt_mode) + { + case 1: // 480i60, rendered progressive by the frontend + set_launcher_fb_mode(8888, 1, 720, 480, 2880, log); + break; + case 2: // PAL 50p + set_launcher_fb_mode(8888, 1, 352, 288, 1408, log); + break; + default: // NTSC 60p + set_launcher_fb_mode(8888, 1, 352, 240, 1408, log); + break; + } } static void blank_native_crt_fb(void) @@ -226,10 +258,20 @@ static void disable_native_crt_path(void) static void enable_native_crt_path(void) { + // Refresh the standard from the state file on every CRT spawn: the + // frontend rewrites the mode byte (video standard change) before + // exiting with ALT_LAUNCHER_EXIT_RELOAD, and the T+1s re-assert in + // alt_launcher_poll must use the same geometry. + { + bool enabled; + load_persisted_native_crt_state(&enabled, &s_native_crt_mode); + (void)enabled; + } + set_vga_fb(0); video_fb_enable(0); - // Double-write with a settle window so the kernel module's 352x240 layout + // Double-write with a settle window so the kernel module's mode layout // is live before the frontend's fb-geometry validation runs. Without // this, the frontend renders for up to a second under stale dims (the // post-fork retry timer used to be what eventually fixed the picture). @@ -589,6 +631,46 @@ void alt_launcher_shutdown(void) } } +bool alt_launcher_native_crt_persisted(void) +{ + return load_persisted_native_crt(); +} + +void alt_launcher_toggle_native_crt(void) +{ + // The OSD's System Settings toggle. Same contract as the frontend's + // in-app toggle: flip byte 0 of the state file (byte 1 - the video + // standard - is preserved), then respawn the launcher under the new + // setting via the same re-entry steps as the ALT_LAUNCHER_EXIT_RELOAD + // path in alt_launcher_poll. + uint8_t v[2] = {0, 0}; + FileLoadConfig(s_crt_state_file, v, sizeof(v)); + if (v[1] > 2) v[1] = 0; + v[0] = v[0] ? 0 : 1; + if (!FileSaveConfig(s_crt_state_file, v, sizeof(v))) + { + printf("alt_launcher: OSD CRT toggle: could not write %s\n", s_crt_state_file); + return; + } + printf("alt_launcher: OSD CRT toggle -> enabled=%d mode=%d\n", v[0], v[1]); + + if (s_pid) + { + pid_t pid = s_pid; + wait_launcher_stopped(pid); + } + user_io_osd_key_enable(1); + release_launcher_video(); + reset_launcher_tty(); + s_respawn_timer = 0; + s_crash_count = 0; + // Re-arm after an earlier give-up or escape: an explicit OSD toggle + // is the user asking for the frontend back. + s_gave_up = false; + s_escaped = false; + alt_launcher_init(v[0] != 0); +} + bool zaparoo_is_native_core(void) { static const char *name = "Zaparoo Launcher"; diff --git a/support/zaparoo/alt_launcher.h b/support/zaparoo/alt_launcher.h index 4d24c4606..77d3183b6 100644 --- a/support/zaparoo/alt_launcher.h +++ b/support/zaparoo/alt_launcher.h @@ -12,6 +12,8 @@ void alt_launcher_resume_after_script(void); bool alt_launcher_native_crt(void); bool alt_launcher_active(void); bool alt_launcher_configured(void); +bool alt_launcher_native_crt_persisted(void); +void alt_launcher_toggle_native_crt(void); void alt_launcher_cfg_apply(void); uint16_t alt_launcher_fb_terminal_key(uint32_t mask, bool osd_button); diff --git a/support/zaparoo/alt_launcher_menu.cpp b/support/zaparoo/alt_launcher_menu.cpp index 5067be869..d20e9ce71 100644 --- a/support/zaparoo/alt_launcher_menu.cpp +++ b/support/zaparoo/alt_launcher_menu.cpp @@ -24,7 +24,7 @@ int alt_launcher_render_system_menu(int menusub, uint64_t *menumask, int m = 0; OsdSetTitle("System Settings", OSD_ARROW_LEFT); - *menumask = 0x1F; + *menumask = 0x3F; OsdWrite(m++); sprintf(s, " MiSTer v%s", version + 5); @@ -60,15 +60,20 @@ int alt_launcher_render_system_menu(int menusub, uint64_t *menumask, OsdWrite(m++, " Remap keyboard \x16", menusub == 0); OsdWrite(m++, " Define joystick buttons \x16", menusub == 1); OsdWrite(m++, " Scripts \x16", menusub == 2); + // Same persisted bit the frontend's Settings toggle writes + // (zaparoo_launcher_crt.bin byte 0); selecting it respawns the + // frontend under the new mode immediately. + sprintf(s, " CRT mode: %s", alt_launcher_native_crt_persisted() ? " On" : "Off"); + OsdWrite(m++, s, menusub == 3); OsdWrite(m++, ""); int cr = m; - OsdWrite(m++, " Reboot (hold \x16 cold reboot)", menusub == 3); + OsdWrite(m++, " Reboot (hold \x16 cold reboot)", menusub == 4); *sysinfo_timer = 0; *reboot_req = 0; while (m < OsdGetSize() - 1) OsdWrite(m++, ""); - OsdWrite(15, ALT_STD_EXIT, menusub == 4); + OsdWrite(15, ALT_STD_EXIT, menusub == 5); return cr; } @@ -77,16 +82,24 @@ int alt_launcher_translate_system_select(int menusub) { if (!alt_launcher_configured()) return menusub; + // 3 is the CRT mode toggle, which has no upstream dispatch case: + // perform the toggle here and return -1 so MENU_SYSTEM2 redraws the + // menu (the respawn then drops the OSD via spawn()'s MenuHide). + if (menusub == 3) + { + alt_launcher_toggle_native_crt(); + return -1; + } + // Maps trimmed-menu menusub to upstream MENU_SYSTEM2 dispatch index: - // 0 Remap -> 1, 1 Define joy -> 2, 2 Scripts -> 3, 3 Reboot -> 5, - // 4 Exit -> 6. CRT mode has no OSD entry — the launcher owns it - // (persisted bool + exit-code respawn, see alt_launcher.cpp). - static const int map[] = { 1, 2, 3, 5, 6 }; + // 0 Remap -> 1, 1 Define joy -> 2, 2 Scripts -> 3, 4 Reboot -> 5, + // 5 Exit -> 6 (3, CRT mode, is handled above). + static const int map[] = { 1, 2, 3, -1, 5, 6 }; if (menusub < 0 || menusub >= (int)(sizeof(map) / sizeof(map[0]))) return -1; return map[menusub]; } bool alt_launcher_system_holding_reboot(int menusub) { - return alt_launcher_configured() && menusub == 3; + return alt_launcher_configured() && menusub == 4; } From c3a98267b5fc11748bffe6f0dc168ae71212c1d6 Mon Sep 17 00:00:00 2001 From: Callan Barrett Date: Thu, 18 Jun 2026 17:36:46 +0800 Subject: [PATCH 3/3] docs(zaparoo): clarify native CRT launcher toggles --- support/zaparoo/alt_launcher.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/support/zaparoo/alt_launcher.h b/support/zaparoo/alt_launcher.h index 7c8d04c4d..5769c07cf 100644 --- a/support/zaparoo/alt_launcher.h +++ b/support/zaparoo/alt_launcher.h @@ -12,7 +12,9 @@ void alt_launcher_resume_after_script(void); bool alt_launcher_native_crt(void); bool alt_launcher_active(void); bool alt_launcher_configured(void); +// Returns the persisted native CRT enable state used by launcher restarts. bool alt_launcher_native_crt_persisted(void); +// Flips the persisted native CRT state and respawns the launcher to apply it. void alt_launcher_toggle_native_crt(void); bool alt_launcher_scheduler_sleep_enabled(void);