From 9a2307a8ee3f5818bc10419c63989cfae827b142 Mon Sep 17 00:00:00 2001 From: niklascfw Date: Sun, 1 Mar 2026 14:43:58 +0100 Subject: [PATCH] Add screenshot capture via Joy-Con Capture button - source/screenshot.c, screenshot.h: take_screenshot() saves framebuffer as BMP - Path: sd:/switch/screenshot/omninx_installer_YYYYMMDD_HHMMSS.bmp (RTC timestamp) - 3s cooldown, backlight flash on capture; TegraExplorer-style BGR flip - main.c: call take_screenshot() when jc->cap in all joycon poll loops Made-with: Cursor --- source/main.c | 61 +++++++++++++++++------- source/screenshot.c | 111 ++++++++++++++++++++++++++++++++++++++++++++ source/screenshot.h | 13 ++++++ 3 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 source/screenshot.c create mode 100644 source/screenshot.h diff --git a/source/main.c b/source/main.c index aa25ae3..eddc11f 100644 --- a/source/main.c +++ b/source/main.c @@ -35,6 +35,7 @@ #include "fs.h" #include "version.h" #include "install.h" +#include "screenshot.h" // Configuration #define PAYLOAD_PATH "sd:/bootloader/update.bin" @@ -264,11 +265,15 @@ void ipl_main(void) { break; } - // Check joycon A button + // Check joycon A button; Capture = screenshot jc_gamepad_rpt_t *jc = joycon_poll(); - if (jc && jc->a) { - button_pressed = true; - break; + if (jc) { + if (jc->cap) + take_screenshot(); + if (jc->a) { + button_pressed = true; + break; + } } msleep(50); // Small delay to avoid busy-waiting @@ -345,6 +350,9 @@ void ipl_main(void) { jc_gamepad_rpt_t *jc = joycon_poll(); u8 btn = btn_read(); + if (jc && jc->cap) + take_screenshot(); + // D-pad or Vol+ / Vol- for selection (Vol+ = up, Vol- = down) bool cur_up = false, cur_down = false; if (jc) { @@ -402,9 +410,13 @@ void ipl_main(void) { bool user_cancelled = false; while (batt_pct < BATT_LOW_THRESHOLD && !bq24193_charger_connected() && !user_cancelled) { jc_gamepad_rpt_t *jc = joycon_poll(); - if (jc && jc->plus && jc->minus) { - user_cancelled = true; - break; + if (jc) { + if (jc->cap) + take_screenshot(); + if (jc->plus && jc->minus) { + user_cancelled = true; + break; + } } if (max17050_get_property(MAX17050_RepSOC, &batt_raw) == 0) { batt_pct = batt_raw >> 8; @@ -468,7 +480,11 @@ void ipl_main(void) { u8 btn_state = btn_read(); if (btn_state & BTN_POWER) acknowledged = true; jc_gamepad_rpt_t *jc = joycon_poll(); - if (jc && jc->a) acknowledged = true; + if (jc) { + if (jc->cap) + take_screenshot(); + if (jc->a) acknowledged = true; + } msleep(50); } // Wait for A and Power to be released so the same press doesn't start the install @@ -530,9 +546,11 @@ void ipl_main(void) { break; } - // Check joycon buttons + // Check joycon buttons; Capture = screenshot jc_gamepad_rpt_t *jc = joycon_poll(); if (jc) { + if (jc->cap) + take_screenshot(); if (jc->a) { button_pressed = true; break; @@ -578,7 +596,8 @@ void ipl_main(void) { // Wait 3 seconds before clearing screen to allow errors to be visible msleep(3000); - + // take_screenshot(); // Take a screenshot of the installation summary + // Clear screen for final summary to ensure it's visible gfx_clear_grey(0x1B); gfx_con_setpos(0, 0); @@ -630,11 +649,15 @@ void ipl_main(void) { break; } - // Check joycon A button + // Check joycon A button; Capture = screenshot jc_gamepad_rpt_t *jc = joycon_poll(); - if (jc && jc->a) { - button_pressed = true; - break; + if (jc) { + if (jc->cap) + take_screenshot(); + if (jc->a) { + button_pressed = true; + break; + } } msleep(50); // Small delay to avoid busy-waiting @@ -672,9 +695,13 @@ void ipl_main(void) { } jc_gamepad_rpt_t *jc = joycon_poll(); - if (jc && jc->a) { - button_pressed = true; - break; + if (jc) { + if (jc->cap) + take_screenshot(); + if (jc->a) { + button_pressed = true; + break; + } } msleep(50); diff --git a/source/screenshot.c b/source/screenshot.c new file mode 100644 index 0000000..2f18ffe --- /dev/null +++ b/source/screenshot.c @@ -0,0 +1,111 @@ +/* + * Screenshot support for OmniNX Installer Payload + * Based on TegraExplorer (AllgemeinerProblemLoeser) TakeScreenshot implementation. + */ + +#include "screenshot.h" +#include "gfx.h" +#include "nx_sd.h" +#include +#include +#include +#include +#include +#include +#include + +/* BMP file header (54 bytes), matching TegraExplorer tools.h */ +typedef struct __attribute__((packed)) _bmp_hdr_t +{ + u16 magic; + u32 size; + u32 rsvd; + u32 data_off; + u32 hdr_size; + u32 width; + u32 height; + u16 planes; + u16 pxl_bits; + u32 comp; + u32 img_size; + u32 res_h; + u32 res_v; + u64 rsvd2; +} bmp_hdr_t; + +#define BMP_HEADER_SIZE 0x36 +#define FB_SIZE 0x384000 /* 1280 * 720 * 4 */ +#define SCREEN_W 1280 +#define SCREEN_H 720 + +void take_screenshot(void) +{ + static u32 last_timer = 0; + + if (!sd_get_card_mounted()) + return; + + /* 3-second cooldown (same as TegraExplorer) */ + u32 now = get_tmr_s(); + if (last_timer != 0 && now < last_timer + 3) + return; + last_timer = now; + + const char basepath[] = "sd:/switch/screenshot"; + char name[48]; + char path[80]; + rtc_time_t rtc; + + max77620_rtc_get_time(&rtc); + s_printf(name, "omninx_installer_%04d%02d%02d_%02d%02d%02d.bmp", + (u32)rtc.year, (u32)rtc.month, (u32)rtc.day, + (u32)rtc.hour, (u32)rtc.min, (u32)rtc.sec); + s_printf(path, "%s/%s", basepath, name); + + f_mkdir("sd:/switch"); + f_mkdir(basepath); + + const u32 file_size = BMP_HEADER_SIZE + FB_SIZE; + u8 *bitmap = (u8 *)malloc(file_size); + u32 *fb_copy = (u32 *)malloc(FB_SIZE); + if (!bitmap || !fb_copy) { + if (bitmap) free(bitmap); + if (fb_copy) free(fb_copy); + return; + } + + /* Copy framebuffer with same coordinate flip as TegraExplorer (BGR layout) */ + u32 *fb_ptr = gfx_ctxt.fb; + for (int x = SCREEN_W - 1; x >= 0; x--) { + for (int y = SCREEN_H - 1; y >= 0; y--) + fb_copy[y * SCREEN_W + x] = *fb_ptr++; + } + + memcpy(bitmap + BMP_HEADER_SIZE, fb_copy, FB_SIZE); + + bmp_hdr_t *bmp = (bmp_hdr_t *)bitmap; + bmp->magic = 0x4D42; + bmp->size = file_size; + bmp->rsvd = 0; + bmp->data_off = BMP_HEADER_SIZE; + bmp->hdr_size = 40; + bmp->width = SCREEN_W; + bmp->height = SCREEN_H; + bmp->planes = 1; + bmp->pxl_bits = 32; + bmp->comp = 0; + bmp->img_size = FB_SIZE; + bmp->res_h = 2834; + bmp->res_v = 2834; + bmp->rsvd2 = 0; + + sd_save_to_file(bitmap, file_size, path); + + free(bitmap); + free(fb_copy); + + /* Brief backlight flash (same as TegraExplorer) */ + display_backlight_brightness(255, 1000); + msleep(100); + display_backlight_brightness(100, 1000); +} diff --git a/source/screenshot.h b/source/screenshot.h new file mode 100644 index 0000000..c037df1 --- /dev/null +++ b/source/screenshot.h @@ -0,0 +1,13 @@ +/* + * Screenshot support for OmniNX Installer Payload + * Based on TegraExplorer (AllgemeinerProblemLoeser) TakeScreenshot implementation. + */ + +#pragma once + +#include + +/* Take a screenshot of the current framebuffer and save as BMP to SD. + * Triggered by Capture button (jc->cap). Uses 3-second cooldown. + * Saves to sd:/switch/screenshot/omninx_installer_YYYYMMDD_HHMMSS.bmp */ +void take_screenshot(void);