// SPDX-License-Identifier: BSD-3-Clause // sdl_cap.c - LD_PRELOAD capture shim: records every frame GZDoom presents // through SDL into an append-only file, so the host can harvest the guest's // real rendered frames without any X/driver readback path. // // Hooks: // SDL_RenderPresent(SDL_Renderer*) -> SDL_RenderReadPixels(ARGB8888) // SDL_UpdateWindowSurface(SDL_Window*) -> SDL_GetWindowSurface pixels // Both forward to the real SDL after capturing. // // File layout (little endian): // [0] 8B magic "GZCAPV01" (+ 4088 B reserved zero) // [4096] frame records: // 4B "GZF2" | u32 w | u32 h | u32 stride | u32 bpp // u64 ts_ns (CLOCK_MONOTONIC) | u64 seq | stride*h bytes (top-down) // A trailing record with seq = 0xFFFFFFFFFFFFFFFF marks clean stop. // // Env: GZCAP (path, default /dev/shm/gzframes.bin), GZCAP_MAXF (0=unlimited) #define _GNU_SOURCE #include #include #include #include #include #include #include #define ARGB8888 0x16362004u typedef struct SDL_Renderer SDL_Renderer; typedef struct SDL_Window SDL_Window; typedef struct SDL_Surface { uint32_t flags; void *format; int w, h; int pitch; void *pixels; void *userdata; int locked; void *list_blitmap; struct { int x, y, w, h; } clip_rect; void *map; int refcount; } SDL_Surface; static int (*real_RenderReadPixels)(SDL_Renderer *, const void *, uint32_t, void *, int); static int (*real_RenderPresent)(SDL_Renderer *); static SDL_Surface *(*real_GetWindowSurface)(SDL_Window *); static int (*real_UpdateWindowSurface)(SDL_Window *); static int (*real_GetRendererOutputSize)(SDL_Renderer *, int *, int *); static void (*real_GL_SwapWindow)(SDL_Window *); static int (*real_GL_GetDrawableSize)(SDL_Window *, int *, int *); static void (*real_glReadPixels)(int, int, int, int, unsigned, unsigned, void *); static FILE *cap_fp; static unsigned long long cap_count, cap_max = 0; static int cap_stop; __attribute__((constructor)) static void cap_init(void) { const char *p = getenv("GZCAP"); const char *m = getenv("GZCAP_MAXF"); char hdr[4096]; if (m) cap_max = strtoull(m, NULL, 0); if (!p) p = "/dev/shm/gzframes.bin"; real_RenderReadPixels = dlsym(RTLD_NEXT, "SDL_RenderReadPixels"); real_RenderPresent = dlsym(RTLD_NEXT, "SDL_RenderPresent"); real_GetWindowSurface = dlsym(RTLD_NEXT, "SDL_GetWindowSurface"); real_UpdateWindowSurface = dlsym(RTLD_NEXT, "SDL_UpdateWindowSurface"); real_GetRendererOutputSize = dlsym(RTLD_NEXT, "SDL_GetRendererOutputSize"); real_GL_SwapWindow = dlsym(RTLD_NEXT, "SDL_GL_SwapWindow"); real_GL_GetDrawableSize = dlsym(RTLD_NEXT, "SDL_GL_GetDrawableSize"); real_glReadPixels = dlsym(RTLD_NEXT, "glReadPixels"); if (!real_glReadPixels) real_glReadPixels = dlsym(RTLD_DEFAULT, "glReadPixels"); if (!real_glReadPixels) { void *h = dlopen("libGL.so.1", RTLD_NOW | RTLD_GLOBAL); if (!h) h = dlopen("libGLESv2.so.2", RTLD_NOW | RTLD_GLOBAL); if (h) real_glReadPixels = dlsym(h, "glReadPixels"); } fprintf(stderr, "[gzcap] init path=%s present=%p updwin=%p swap=%p read=%p gros=%p\n", p, (void *)real_RenderPresent, (void *)real_UpdateWindowSurface, (void *)real_GL_SwapWindow, (void *)real_RenderReadPixels, (void *)real_GetRendererOutputSize, (void *)real_glReadPixels); cap_fp = fopen(p, "wb"); if (!cap_fp) return; memset(hdr, 0, sizeof(hdr)); memcpy(hdr, "GZCAPV01", 8); fwrite(hdr, 1, sizeof(hdr), cap_fp); fflush(cap_fp); } static void cap_record(const void *px, int w, int h, int stride, int bpp) { struct timespec ts; uint64_t tns, seq; uint32_t rh[4]; char m[4] = { 'G', 'Z', 'F', '2' }; if (!cap_fp || cap_stop) return; if (cap_max && cap_count >= cap_max) { uint64_t endseq = 0xFFFFFFFFFFFFFFFFull; fwrite(m, 1, 4, cap_fp); fwrite(&endseq, 8, 1, cap_fp); fflush(cap_fp); cap_stop = 1; return; } clock_gettime(CLOCK_MONOTONIC, &ts); tns = (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec; seq = cap_count; rh[0] = (uint32_t)w; rh[1] = (uint32_t)h; rh[2] = (uint32_t)stride; rh[3] = (uint32_t)bpp; fwrite(m, 1, 4, cap_fp); fwrite(rh, 4, 4, cap_fp); fwrite(&tns, 8, 1, cap_fp); fwrite(&seq, 8, 1, cap_fp); fwrite(px, 1, (size_t)stride * h, cap_fp); fflush(cap_fp); cap_count++; if ((cap_count & 31) == 0) fprintf(stderr, "[gzcap] frames=%llu\n", cap_count); } int SDL_RenderPresent(SDL_Renderer *r) { static int hit; if (!hit++) fprintf(stderr, "[gzcap] hook RenderPresent\n"); if (cap_fp && !cap_stop && real_RenderReadPixels && real_GetRendererOutputSize) { int w = 0, h = 0; if (real_GetRendererOutputSize(r, &w, &h) == 0 && w > 0 && h > 0 && w <= 4096 && h <= 4096) { static unsigned char *buf; static size_t bufsz; size_t need = (size_t)w * 4 * h; if (bufsz < need) { free(buf); buf = malloc(need); bufsz = buf ? need : 0; } if (buf && real_RenderReadPixels(r, NULL, ARGB8888, buf, w * 4) == 0) cap_record(buf, w, h, w * 4, 32); } } if (real_RenderPresent) return real_RenderPresent(r); return 0; } int SDL_UpdateWindowSurface(SDL_Window *win) { static int hit; if (!hit++) fprintf(stderr, "[gzcap] hook UpdateWindowSurface\n"); if (cap_fp && !cap_stop && real_GetWindowSurface) { SDL_Surface *s = real_GetWindowSurface(win); if (s && s->pixels && s->w > 0 && s->h > 0 && s->pitch > 0 && s->w <= 4096 && s->h <= 4096) cap_record(s->pixels, s->w, s->h, s->pitch, 32); } if (real_UpdateWindowSurface) return real_UpdateWindowSurface(win); return 0; } void SDL_GL_SwapWindow(SDL_Window *win) { static int hit; static unsigned char *gbuf; static size_t gbufsz; if (!hit++) fprintf(stderr, "[gzcap] hook GLSwapWindow\n"); if (cap_fp && !cap_stop && real_glReadPixels && real_GL_GetDrawableSize) { int w = 0, h = 0; if (real_GL_GetDrawableSize(win, &w, &h) == 0 && w > 0 && h > 0 && w <= 4096 && h <= 4096) { size_t need = (size_t)w * 4 * h; if (gbufsz < need) { free(gbuf); gbuf = malloc(need); gbufsz = gbuf ? need : 0; } if (gbuf) { real_glReadPixels(0, 0, w, h, 0x1908, 0x1401, gbuf); /* GL rows are bottom-up: flip into a scratch record */ { unsigned char *tmp = malloc(need); int y; if (tmp) { for (y = 0; y < h; y++) memcpy(tmp + (size_t)y * w * 4, gbuf + (size_t)(h - 1 - y) * w * 4, (size_t)w * 4); cap_record(tmp, w, h, w * 4, 32); free(tmp); } } } } } if (real_GL_SwapWindow) real_GL_SwapWindow(win); }