Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions ZAPAROO_FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ non-blocking spawn) are intentionally omitted.
| 34 | **No load-settle guard, and why** | An earlier design feared a save pulsed during a ROM transfer: cores guard the trigger with `if(!bk_state)` so a pulse during a save *load* is ignored, but during the ROM download `bk_state` is 0 while `bk_ena` is already set, which in theory dumps stale BSRAM into the newly mounted save. **It does not reproduce.** Writes to `/dev/MiSTer_cmd` block while Main re-execs, and Main does not service the FIFO during the transfer, so the request is always delivered after the mount. Tested three ways: `zaparoo_save` 5 s into a SNES load (delivered after `Mount saves`, save byte-identical), 40 rapid pulses across a whole SNES load (none reached the core, save byte-identical), and a large N64 load (delivered after the mount and onto the OSD-open path, since Main shows load progress during a transfer). No guard added and no `user_io_set_download()` hook. Not proven impossible, only unreachable through the command FIFO | tested on hardware; `support/zaparoo/save.cpp` unchanged |
| 35 | **`menu_key_set()` needs an edge, not a value** | `menu_key` is a **single slot**, and `menu_key_get()` only delivers on a change: `c1 = menu_key; if (c1 != c2) { c = c1; } c2 = c1;` (`menu.cpp:600-606`). Re-sending the same value is silently dropped. Real keys escape this because press and release alternate, but `zaparoo_kiosk_osd_open()` sends only `KEY_F12 \| UPSTROKE`, so it worked **exactly once per session** and every later `zaparoo_osd open` was a silent no-op. Fixed by clearing the slot, then delivering the release from `zaparoo_kiosk_poll()` once `GetTimer(60)` has elapsed, comfortably past the 20 ms debounce in `menu_key_get()` so the cleared value is latched first and the release is a genuine edge. Verified with three consecutive open/close cycles plus a screen capture. **Any future synthetic key must do the same**, a bare repeat of the previous value will vanish | `support/zaparoo/kiosk.cpp` (`zaparoo_kiosk_poll`), called from `zaparoo_poll()` |
| 36 | **Fork diagnostics and stdout buffering** | A tty is line-buffered by libc, but stdout redirected to a file is block-buffered, so diagnostics sat in the buffer and a captured log read as empty or truncated. Fixed at the source with a single `setvbuf(stdout, NULL, _IOLBF, 0)` in a `__attribute__((constructor))`, declared **before** the service-start constructor so it runs first within that file and lands before anything prints. This covers upstream's own `printf` output too, so a captured log is readable end to end. An earlier `zlog.h` wrapper that wrapped every call site in `fflush` was removed in favour of this: fork code uses plain `printf` like upstream | `support/zaparoo/service_boot.cpp` (zero upstream hooks) |
| 37 | **Framebuffer geometry is always computed** | `video_fb_config()` records `fb_width`/`fb_height`/`brd_*` **before** deferring to `alt_launcher_handle_video_fb_config()`; the hook only replaces the enable and the kernel-module write. When the hook sat at the top of the function (`1d9c1e6`, beta 2/3), the menu process never assigned the geometry at all: `video_menu_bg()` built 0x0 wallpaper images (`Warning: bg1 is 0`), and the launch-time `video_fb_enable(0)` took `video_fb_set()`'s menu-background fallback and programmed a 0x0 framebuffer into the FPGA on the way into `fpga_load_rbf` → HDMI output died on every game launch from the frontend. A matching `video_fb_enable(0)` in `alt_launcher_shutdown()`'s no-child branch was tried and reverted: `app_restart()` calls that function a **second** time, after `fpga_load_rbf` has already loaded the game core, and `is_menu()` is a per-process cache of `orig_name` (`user_io.cpp:222`) so it is still true there. `video_fb_set()`'s menu-background fallback then flips the disable into `FB_EN` at menu geometry and writes it into the **new** core, killing every launch. Teardown video belongs on the live-child branch only, which runs before the reconfiguration | `video.cpp` (`video_fb_config`) |
| 38 | **gcdb probes the gamepad node, never a touchpad** | gcdb resolves a mapping's `bN` entries by enumerating the probed evdev node's key bits (`get_ctrl_index_maps`); `hN.x` hat entries are arithmetic. A DualSense contributes a gamepad node and a Touchpad node with the same Phys/Uniq, merged by `mergedevs()`, and `input_cb` loads the shared map from whichever node reaches it first, caching the result by vid/pid. Probed through the touchpad, every face button maps to a touchpad code while the d-pad keeps working (a beta report: "d-pad moves, Open/Options/View do nothing", only with keyboard dongles present, which reorder `/dev/input`). `zaparoo_gcdb_probe_dev()` swaps a `QUIRK_DS4TOUCH` probe for the group's `QUIRK_DS4` sibling, and the pre-warm (row 22) only feeds merge-base, non-touchpad nodes. Two adjacent upstream hardenings: `get_ctrl_index_maps` bounds its loops to `btn_map`'s size, and `gcdb_controller_idx` skips zeroed cache slots (an all-zero id used to match slot 0 and return an all-zero map as a hit) | `support/zaparoo/launcher_input_detect.inc` (`zaparoo_gcdb_probe_dev`, `zaparoo_prewarm_device_maps`), `input.cpp` (`input_cb` map load, 2-line hook), `gamecontroller_db.cpp` |
| 39 | **`app_restart()` copies `exe` before the launcher teardown** | `user_io_init` honours a per-core `main=` (`[RA_*] main=MiSTer_RA`) with `app_restart(path, xml, getFullPath(cfg.main))`, and that pointer aliases file_io's single static `full_path`. `app_restart()` calls `alt_launcher_shutdown()` first, and `kill_stale_frontends()` (`getFullPath(s_launcher_path)`) plus a cold `alt_launcher_installed()` (`FileExists`) rewrite the buffer to `/media/fat/zaparoo/frontend`, so the `execl` re-launched Main **as the frontend binary**: no Main, black screen, `/tmp/CORENAME` stuck at `MENU`. `exe` is now copied to a private buffer before the teardown. Any pointer handed across `alt_launcher_shutdown()` must not alias `full_path` | `fpga_io.cpp` (`app_restart`, 3 lines beside the existing hook) |
| 40 | **Persistent Main log (`main.log`)** | Main's stdout goes to the console after a reboot, so the first-exit-after-boot slowness could not be captured with the `/tmp/mz.log` recipe (row 23). If `/media/fat/zaparoo/main.log` exists, a constructor dup2's it over stdout and stderr before `main()`, writes a start marker (pid, uptime, wall clock) per exec, rotates to `.old` above 8 MB, and starts a detached heartbeat thread that writes `[zt <uptime ms>]` every 250 ms so upstream's untimed lines can be placed against zlog's `t=`. Marker and heartbeat use `dprintf`, not `printf`: the `setvbuf` in `service_boot.cpp` may not have run yet and is only valid before the stream's first use. Touch the file to enable, delete it to disable; absent file, nothing runs | `support/zaparoo/main_log.cpp` (zero upstream hooks) |
| 41 | **Late-appearing display keeps the frontend on the fallback video mode** | `read_edid()` bails silently when the ADV7513 does not sense HPD + monitor sense, so a TV powering up alongside the DE10 leaves `video_init()` on the built-in default (1280x720) and the frontend probes its render size against that: full-res instead of half. The MENU core exposes no HDMI interrupt pin, so upstream's `video_poll()` hot-plug re-init never runs and the wrong mode sticks until the next core load (observed: 34 minutes). The single 500 ms pre-spawn retry now polls out a 3 s hold window, and if the spawn still happens on the fallback mode the launcher watches the transmitter's sense bits for 30 s behind the running child and restarts it once the display answers. `video_reinit()` is spent only on a link down->up edge and at most 3 times per init, so a display that genuinely has no EDID is not flickered. Sense bits are read through a private `i2c_open(0x39)` handle, as `hdmi_cec.cpp` already does for the same chip | `support/zaparoo/alt_launcher.cpp` (zero upstream hooks) |
| 28 | **Early service start** | A `__attribute__((constructor))` runs `/media/fat/Scripts/zaparoo.sh -service start` before `main()`, so ahead of the core-1 affinity pin (a child would inherit it) and ahead of `FindStorage()`'s 30s USB wait. Double fork + `setsid()` and **no** `PR_SET_PDEATHSIG`, the inverse of `exec_launcher_child`: the service must survive the `app_restart()` re-exec every core load performs. No marker file, the script is an ensure | `support/zaparoo/service_boot.cpp` (zero upstream hooks) |

---
Expand Down
5 changes: 4 additions & 1 deletion deploy-zaparoo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ echo "==> Copying new binary..."
scp_cmd "$BINARY" "root@$MISTER_IP:$REMOTE_PATH"

echo "==> Restarting MiSTer_Zaparoo..."
ssh_cmd "killall MiSTer_Zaparoo 2>/dev/null || true; sync; nohup $REMOTE_PATH </dev/null >/dev/ttyS0 2>/dev/ttyS0 &"
# setsid, not nohup: Main needs its own session, or it dies with the ssh one.
ssh_cmd "killall MiSTer_Zaparoo 2>/dev/null || true; sync; sleep 1; cd /media/fat && setsid $REMOTE_PATH </dev/null >/dev/ttyS0 2>/dev/ttyS0 &"
sleep 4
ssh_cmd "pgrep -f MiSTer_Zaparoo >/dev/null" || { echo "error: MiSTer_Zaparoo did not come up" >&2; exit 1; }

echo "==> Done."
4 changes: 4 additions & 0 deletions fpga_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,10 @@ char *getappname()

void app_restart(const char *path, const char *xml, const char *exe)
{
// Zaparoo: exe may alias file_io's shared full_path buffer (user_io passes
// getFullPath(cfg.main)), which alt_launcher_shutdown() rewrites.
static char exe_copy[PATH_MAX];
if (exe) { snprintf(exe_copy, sizeof(exe_copy), "%s", exe); exe = exe_copy; }
alt_launcher_shutdown();
sync();
fpga_core_reset(1);
Expand Down
12 changes: 10 additions & 2 deletions gamecontroller_db.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,12 @@ void get_ctrl_index_maps(int dev_fd, char *guid, uint16_t *btn_map, uint16_t *ab
printf("Gamecontrollerdb: mapping buttons for %s ", guid);
if (ioctl(dev_fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits) >= 0)
{
for (int i = BTN_JOYSTICK; i < KEY_MAX; i++)
// Zaparoo: btn_map has KEY_MAX - BTN_JOYSTICK slots at both call sites,
// but the two loops append every key bit the node advertises. A
// composite dongle (keyboard plus consumer control in one node) can
// exceed that and run into the abs_map that sits next to it.
const uint16_t btn_max = KEY_MAX - BTN_JOYSTICK;
for (int i = BTN_JOYSTICK; i < KEY_MAX && btn_cnt < btn_max; i++)
{
if (test_bit(i, keybits))
{
Expand All @@ -215,7 +220,7 @@ void get_ctrl_index_maps(int dev_fd, char *guid, uint16_t *btn_map, uint16_t *ab
btn_cnt++;
}
}
for (int i = 0; i < BTN_JOYSTICK; i++)
for (int i = 0; i < BTN_JOYSTICK && btn_cnt < btn_max; i++)
{
if (test_bit(i, keybits))
{
Expand Down Expand Up @@ -517,6 +522,9 @@ static int gcdb_controller_idx(uint16_t bustype, uint16_t vid, uint16_t pid, uin
{
for (int i=0; i < MAX_GCDB_ENTRIES; i++)
{
// Zaparoo: db_maps is zero-initialised, so an unused slot would match a
// device reporting no vid/pid and hand it an all-zero map as a hit.
if (!db_maps[i].id[1] && !db_maps[i].id[2]) continue;
if (db_maps[i].id[0] == bustype && db_maps[i].id[1] == vid && db_maps[i].id[2] == pid && db_maps[i].id[3] == version)
{
return i;
Expand Down
5 changes: 4 additions & 1 deletion input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3020,7 +3020,10 @@ static void input_cb(struct input_event *ev, struct input_absinfo *absinfo, int
{
if (!load_map(get_map_name(dev, 1), &input[dev].mmap, sizeof(input[dev].mmap)))
{
if (!gcdb_map_for_controller(input[sub_dev].bustype, input[sub_dev].vid, input[sub_dev].pid, input[sub_dev].gcdb_version, pool[sub_dev].fd, input[dev].mmap))
// Zaparoo: probe the gamepad node of a merged DualSense, never its
// touchpad (see zaparoo_gcdb_probe_dev in launcher_input_detect.inc).
int probe = zaparoo_gcdb_probe_dev(dev, sub_dev);
if (!gcdb_map_for_controller(input[probe].bustype, input[probe].vid, input[probe].pid, input[probe].gcdb_version, pool[probe].fd, input[dev].mmap))
{
memset(input[dev].mmap, 0, sizeof(input[dev].mmap));
memcpy(input[dev].mmap, def_mmap, sizeof(def_mmap));
Expand Down
Loading