diff --git a/games/NXDoom/Kconfig b/games/NXDoom/Kconfig index cbf47a97a51..4fa67011d4c 100644 --- a/games/NXDoom/Kconfig +++ b/games/NXDoom/Kconfig @@ -4,7 +4,7 @@ # config GAMES_NXDOOM - bool "NXDoom" + tristate "NXDoom" default n depends on ALLOW_GPL_COMPONENTS depends on VIDEO_FB @@ -178,6 +178,27 @@ config GAMES_NXDOOM_MAXDRAWSEGS memory, so you may reduce the number. However, too few will cause rendering issues (overflow is checked to avoid crashes). +config GAMES_NXDOOM_HEAP_BUFFERS + bool "Allocate renderer scratch buffers on the heap" + default n + ---help--- + The visplanes/openings/drawsegs/vissprites renderer scratch buffers + (sized by the options above) are static arrays by default, matching + vanilla DOOM. On a target where their combined size threatens the + internal DRAM budget once linked into a full application image, + enable this to allocate them from the heap instead (this target's + heap may be backed by external RAM/PSRAM). Static allocation is + preferred where DRAM budget is not a concern. + +config GAMES_NXDOOM_STATDUMP_MAX_CAPTURES + int "Maximum statdump capture buffer entries" + default 32 + range 1 1024 + ---help--- + Number of playtime-statistics capture slots statdump.c reserves. + This is diagnostic/debug capture storage, not required for normal + gameplay - reduce it on a DRAM-constrained target. + config GAMES_NXDOOM_RANGECHECK bool "Perform range checks" default y diff --git a/games/NXDoom/src/d_iwad.c b/games/NXDoom/src/d_iwad.c index a6f1cd9453d..5c072c90bf0 100644 --- a/games/NXDoom/src/d_iwad.c +++ b/games/NXDoom/src/d_iwad.c @@ -271,6 +271,16 @@ static void buld_iwad_dir_list(void) add_iwad_dir(m_dir_name(myargv[0])); + /* Add the board's configured DOOM data directory. Kconfig documents + * CONFIG_GAMES_NXDOOM_PREFDIR as "Directory where DOOM WAD files are + * stored", but until now it was only used for the config/save file + * location -- nothing actually searched it for IWADs, forcing every + * launch to rely on the current directory or DOOMWADDIR/DOOMWADPATH + * being set by hand first. + */ + + add_iwad_dir(CONFIG_GAMES_NXDOOM_PREFDIR); + /* Add DOOMWADDIR if it is in the environment */ env = getenv("DOOMWADDIR"); diff --git a/games/NXDoom/src/doom/d_main.c b/games/NXDoom/src/doom/d_main.c index 52df6eda110..b715a62837d 100644 --- a/games/NXDoom/src/doom/d_main.c +++ b/games/NXDoom/src/doom/d_main.c @@ -1294,6 +1294,12 @@ void d_doomloop(void) while (1) { + /* Safe point (outside any framebuffer/heap access) for nxstore's + * SIGTERM-driven close request to actually take effect - see + * i_install_quit_signal() in i_system.h. + */ + + i_poll_quit_signal(); d_run_frame(); } } diff --git a/games/NXDoom/src/doom/r_bsp.c b/games/NXDoom/src/doom/r_bsp.c index 605b21beaa8..7dbd64b708e 100644 --- a/games/NXDoom/src/doom/r_bsp.c +++ b/games/NXDoom/src/doom/r_bsp.c @@ -78,7 +78,11 @@ line_t *linedef; sector_t *frontsector; sector_t *backsector; +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +drawseg_t *drawsegs; +#else drawseg_t drawsegs[CONFIG_GAMES_NXDOOM_MAXDRAWSEGS]; +#endif drawseg_t *ds_p; /* newend is one past the last valid seg */ diff --git a/games/NXDoom/src/doom/r_bsp.h b/games/NXDoom/src/doom/r_bsp.h index 66c6d611fff..c964b234c4a 100644 --- a/games/NXDoom/src/doom/r_bsp.h +++ b/games/NXDoom/src/doom/r_bsp.h @@ -52,7 +52,11 @@ extern boolean markceiling; extern boolean skymap; +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +extern drawseg_t *drawsegs; +#else extern drawseg_t drawsegs[CONFIG_GAMES_NXDOOM_MAXDRAWSEGS]; +#endif extern drawseg_t *ds_p; extern lighttable_t **hscalelight; diff --git a/games/NXDoom/src/doom/r_draw.c b/games/NXDoom/src/doom/r_draw.c index ff243912aed..00feeb9f7f3 100644 --- a/games/NXDoom/src/doom/r_draw.c +++ b/games/NXDoom/src/doom/r_draw.c @@ -597,7 +597,7 @@ void r_draw_span(void) #ifdef CONFIG_GAMES_NXDOOM_RANGECHECK if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH || - (unsigned)ds_y > SCREENHEIGHT) + ds_y < 0 || ds_y >= viewheight) { i_error("r_draw_span: %i to %i at %i", ds_x1, ds_x2, ds_y); } @@ -724,7 +724,7 @@ void r_draw_span_low(void) #ifdef CONFIG_GAMES_NXDOOM_RANGECHECK if (ds_x2 < ds_x1 || ds_x1 < 0 || ds_x2 >= SCREENWIDTH || - (unsigned)ds_y > SCREENHEIGHT) + ds_y < 0 || ds_y >= viewheight) { i_error("r_draw_span: %i to %i at %i", ds_x1, ds_x2, ds_y); } diff --git a/games/NXDoom/src/doom/r_main.c b/games/NXDoom/src/doom/r_main.c index b50cdc33773..513185d4165 100644 --- a/games/NXDoom/src/doom/r_main.c +++ b/games/NXDoom/src/doom/r_main.c @@ -685,6 +685,24 @@ fixed_t r_scale_from_global_angle(angle_t visangle) void r_set_view_size(int blocks, int detail) { + /* screenblocks is only ever meant to hold 3..11 (set that way by the + * options menu and by the config default of 9). The renderer's view + * geometry math divides by values derived from it - notably + * pspriteiscale = FRACUNIT * SCREENWIDTH / viewwidth in + * r_execute_set_view_size() - so a 0 or otherwise out-of-range value + * turns into a divide-by-zero hardware exception (EXCCAUSE=6), which on + * this flat-memory build takes the whole board down rather than just + * this task. Clamp defensively so a bad/missing config value degrades + * to the default screen size instead of a system crash. + */ + + if (blocks < 3 || blocks > 11) + { + printf("r_set_view_size: screenblocks=%d out of range, using 10\n", + blocks); + blocks = 10; + } + setsizeneeded = true; setblocks = blocks; setdetail = detail; diff --git a/games/NXDoom/src/doom/r_plane.c b/games/NXDoom/src/doom/r_plane.c index 65a2c2fa78b..a2a84c18ad7 100644 --- a/games/NXDoom/src/doom/r_plane.c +++ b/games/NXDoom/src/doom/r_plane.c @@ -57,12 +57,17 @@ planefunction_t ceilingfunc; /* Here comes the obnoxious "visplane". */ +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +visplane_t *visplanes; +short *openings; +#else visplane_t visplanes[CONFIG_GAMES_NXDOOM_MAXVISPLANES]; +short openings[MAXOPENINGS]; +#endif visplane_t *lastvisplane; visplane_t *floorplane; visplane_t *ceilingplane; -short openings[MAXOPENINGS]; short *lastopening; /* Clip values are the solid pixel bounding the range. floorclip starts out @@ -114,12 +119,46 @@ static void r_map_plane(int y, int x1, int x2) fixed_t length; unsigned index; -#ifdef CONFIG_GAMES_NXDOOM_RANGECHECK - if (x2 < x1 || x1 < 0 || x2 >= viewwidth || y > viewheight) + /* y indexes cachedheight[]/cacheddistance[]/cachedxstep[]/cachedystep[] + * below, all sized SCREENHEIGHT - a y outside that range (observed on + * this port: y=255 against a 200-entry array, well past even + * viewheight) is an out-of-bounds array write, not just a "debug + * assertion". This used to be gated behind CONFIG_GAMES_NXDOOM_ + * RANGECHECK and fatal (i_error(), which tears down the whole process + * on what vanilla Doom would just render as one glitched span) - both + * wrong: the memory-safety check must not be optional, and killing the + * entire game over one bad plane span is worse than just not drawing + * it. Clamp y into range instead of touching memory outside the + * buffers' real bounds - this still renders the span (as one glitched + * row, the same "wrong but visible" failure mode vanilla DOOM has) so + * a bad plane doesn't leave a blank gap on screen either. + * + * The clamp bound must be viewheight, not SCREENHEIGHT: this y is + * stored into ds_y and later used by r_draw_span() to index + * ylookup[] (r_draw.c), which r_init_buffer() only populates for + * [0, viewheight) - viewheight can be smaller than SCREENHEIGHT (a + * sub-window within the physical screen), so entries from viewheight + * up to SCREENHEIGHT are zero-initialized (NULL) pointers. Clamping + * to SCREENHEIGHT - 1 instead of viewheight - 1 traded the original + * out-of-bounds write for a NULL-pointer-plus-offset framebuffer + * write - confirmed on real hardware as a load/store exception at a + * small virtual address. viewheight is always <= SCREENHEIGHT, so + * this bound is safe for cachedheight[]/etc. too. + */ + + if (x2 < x1 || x1 < 0 || x2 >= viewwidth) { - i_error("R_MapPlane: %i, %i at %i", x1, x2, y); + return; + } + + if (y < 0) + { + y = 0; + } + else if (y >= viewheight) + { + y = viewheight - 1; } -#endif if (planeheight != cachedheight[y]) { @@ -160,27 +199,62 @@ static void r_map_plane(int y, int x1, int x2) spanfunc(); } +/* Row indices into spanstart[] (sized SCREENHEIGHT) that are only ever + * safe to use as an array index within that range - t1/b1/t2/b2 in + * r_make_spans() below are also compared directly against each other to + * drive the span-tracking state machine (including vanilla DOOM's 0xff + * sentinel for "no span"/edge-of-plane), and that comparison logic must + * see the real, un-clamped values or the sentinel handling breaks. Only + * the array touches themselves need guarding. + */ + +static inline boolean r_row_in_range(int row) +{ + return row >= 0 && row < SCREENHEIGHT; +} + static void r_make_spans(int x, int t1, int b1, int t2, int b2) { + /* t1/b1/t2/b2 come from a visplane's top[]/bottom[] arrays. In valid + * play these are either a real screen row or vanilla DOOM's 0xff + * (255) "no span here" sentinel; the loop conditions normally keep + * that sentinel away from spanstart[]. A malformed renderer state + * can violate that invariant, however: row 255 was observed reaching + * r_map_plane() on real hardware, after spanstart[t1]/[b1] had already + * been evaluated as the call argument. Guard every spanstart[] touch + * directly instead of altering t1/b1/t2/b2, so the state-machine + * comparisons and normal sentinel handling remain unchanged. An + * invalid closing row uses column zero as its bounded fallback; an + * invalid opening row is ignored. + */ + while (t1 < t2 && t1 <= b1) { - r_map_plane(t1, spanstart[t1], x - 1); + r_map_plane(t1, r_row_in_range(t1) ? spanstart[t1] : 0, x - 1); t1++; } while (b1 > b2 && b1 >= t1) { - r_map_plane(b1, spanstart[b1], x - 1); + r_map_plane(b1, r_row_in_range(b1) ? spanstart[b1] : 0, x - 1); b1--; } while (t2 < t1 && t2 <= b2) { - spanstart[t2] = x; + if (r_row_in_range(t2)) + { + spanstart[t2] = x; + } + t2++; } while (b2 > b1 && b2 >= t2) { - spanstart[b2] = x; + if (r_row_in_range(b2)) + { + spanstart[b2] = x; + } + b2--; } } @@ -195,7 +269,70 @@ static void r_make_spans(int x, int t1, int b1, int t2, int b2) void r_init_planes(void) { - /* Doh! */ +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS + /* These renderer scratch buffers are sized for a comfortable margin + * above vanilla DOOM's original limits and, on a DRAM-constrained + * target, blow the internal DRAM budget as static arrays - opt-in + * heap allocation instead (comes out of the PSRAM-backed user heap + * on this target) via CONFIG_GAMES_NXDOOM_HEAP_BUFFERS. + */ + + visplanes = malloc(sizeof(visplane_t) * CONFIG_GAMES_NXDOOM_MAXVISPLANES); + openings = malloc(sizeof(short) * MAXOPENINGS); + drawsegs = malloc(sizeof(drawseg_t) * CONFIG_GAMES_NXDOOM_MAXDRAWSEGS); + vissprites = malloc(sizeof(vissprite_t) * + CONFIG_GAMES_NXDOOM_MAXVISSPRITES); + + if (visplanes == NULL || openings == NULL || drawsegs == NULL || + vissprites == NULL) + { + /* i_error() doesn't necessarily terminate the whole board on this + * flat, single address-space build (see the comment below on + * relaunch) - free whatever partially succeeded so a failed + * allocation attempt doesn't leak across a subsequent relaunch. + */ + + free(visplanes); + free(openings); + free(drawsegs); + free(vissprites); + visplanes = NULL; + openings = NULL; + drawsegs = NULL; + vissprites = NULL; + + i_error("r_init_planes: failed to allocate renderer buffers"); + } + + /* i_quit() can be followed by another r_init_planes() call within the + * same boot (relaunching the game via nxpkg on this flat, single + * address-space build), so these heap buffers must be freed on exit + * or every relaunch leaks the previous allocation permanently. + */ + + i_at_exit(r_shutdown_planes, true); +#endif +} + +/* r_shutdown_planes + * Frees the renderer scratch buffers allocated by r_init_planes. Only + * registered as an exit handler when CONFIG_GAMES_NXDOOM_HEAP_BUFFERS is + * set - the static-array buffers have nothing to free. + */ + +void r_shutdown_planes(void) +{ +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS + free(visplanes); + free(openings); + free(drawsegs); + free(vissprites); + + visplanes = NULL; + openings = NULL; + drawsegs = NULL; + vissprites = NULL; +#endif } /* r_clear_planes @@ -314,13 +451,15 @@ visplane_t *r_check_plane(visplane_t *pl, int start, int stop) /* make a new visplane */ + if (lastvisplane - visplanes == CONFIG_GAMES_NXDOOM_MAXVISPLANES) + { + i_error("r_check_plane: no more visplanes"); + } + lastvisplane->height = pl->height; lastvisplane->picnum = pl->picnum; lastvisplane->lightlevel = pl->lightlevel; - if (lastvisplane - visplanes == CONFIG_GAMES_NXDOOM_MAXVISPLANES) - i_error("r_check_plane: no more visplanes"); - pl = lastvisplane++; pl->minx = start; pl->maxx = stop; diff --git a/games/NXDoom/src/doom/r_plane.h b/games/NXDoom/src/doom/r_plane.h index 1ae3b79254b..f51ccfa37fa 100644 --- a/games/NXDoom/src/doom/r_plane.h +++ b/games/NXDoom/src/doom/r_plane.h @@ -58,6 +58,7 @@ extern fixed_t distscale[SCREENWIDTH]; ****************************************************************************/ void r_init_planes(void); +void r_shutdown_planes(void); void r_clear_planes(void); void r_draw_planes(void); diff --git a/games/NXDoom/src/doom/r_things.c b/games/NXDoom/src/doom/r_things.c index 019d83a7048..0084da909df 100644 --- a/games/NXDoom/src/doom/r_things.c +++ b/games/NXDoom/src/doom/r_things.c @@ -93,7 +93,11 @@ spriteframe_t sprtemp[29]; int maxframe; const char *spritename; +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +vissprite_t *vissprites; +#else vissprite_t vissprites[CONFIG_GAMES_NXDOOM_MAXVISSPRITES]; +#endif vissprite_t *vissprite_p; int newvissprite; diff --git a/games/NXDoom/src/doom/r_things.h b/games/NXDoom/src/doom/r_things.h index cdd29948368..eb730c28d7e 100644 --- a/games/NXDoom/src/doom/r_things.h +++ b/games/NXDoom/src/doom/r_things.h @@ -28,7 +28,11 @@ * Public Data ****************************************************************************/ +#ifdef CONFIG_GAMES_NXDOOM_HEAP_BUFFERS +extern vissprite_t *vissprites; +#else extern vissprite_t vissprites[CONFIG_GAMES_NXDOOM_MAXVISSPRITES]; +#endif extern vissprite_t *vissprite_p; extern vissprite_t vsprsortedhead; diff --git a/games/NXDoom/src/doom/statdump.c b/games/NXDoom/src/doom/statdump.c index 1db5657ab6c..977a4b9b160 100644 --- a/games/NXDoom/src/doom/statdump.c +++ b/games/NXDoom/src/doom/statdump.c @@ -39,7 +39,7 @@ * Pre-processor Definitions ****************************************************************************/ -#define MAX_CAPTURES 32 +#define MAX_CAPTURES CONFIG_GAMES_NXDOOM_STATDUMP_MAX_CAPTURES /**************************************************************************** * Private Data diff --git a/games/NXDoom/src/i_main.c b/games/NXDoom/src/i_main.c index bd9dab60910..17029afc58a 100644 --- a/games/NXDoom/src/i_main.c +++ b/games/NXDoom/src/i_main.c @@ -57,6 +57,15 @@ void d_doom_main(void); int main(int argc, char **argv) { + /* Lets nxstore (or any other supervisor) ask this process to exit + * cleanly via SIGTERM instead of the only other option being a forced + * task_delete() from outside - see i_system.h/i_system.c for why that + * matters on this board (a forced kill mid framebuffer/heap access was + * observed to hang the whole system, not just this task). + */ + + i_install_quit_signal(); + /* save arguments */ myargc = argc; diff --git a/games/NXDoom/src/i_system.c b/games/NXDoom/src/i_system.c index b3867e0add9..a98c16a17a0 100644 --- a/games/NXDoom/src/i_system.c +++ b/games/NXDoom/src/i_system.c @@ -22,10 +22,13 @@ * Included Files ****************************************************************************/ +#include +#include #include #include #include #include +#include #include #include "config.h" @@ -75,6 +78,15 @@ static atexit_listentry_t *exit_funcs = NULL; static boolean already_quitting = false; +/* Set only by i_quit_signal_handler() (async-signal-safe: a single + * sig_atomic_t store, nothing else) and read only by + * i_poll_quit_signal(), called from a safe point in the main loop - see + * the comment on i_install_quit_signal() in i_system.h for why the + * actual i_quit() cleanup is deferred out of the signal handler itself. + */ + +static volatile sig_atomic_t quit_requested = 0; + /* Read Access Violation emulation. * * From PrBoom+, by entryway. @@ -320,6 +332,71 @@ void i_quit(void) exit(0); } +/* i_quit_signal_handler + * + * A supervisor process (nxstore) has no reachable in-game quit path to + * drive (no keyboard/touch input is wired up here) - it can only ask + * from the outside, via SIGTERM. This handler does the one thing a + * signal handler is safe to do: set a flag. It must NOT call i_quit() + * (or anything it does - munmap, fclose, exit()'s atexit chain) directly, + * because a signal can land at literally any point in this process's own + * execution, including mid-malloc()/mid-blit - exactly the same "unsafe + * mid-operation teardown" risk as being force-killed from outside, just + * moved from another task's context into this one. i_poll_quit_signal() + * defers the real work to a known-safe boundary instead. + */ + +static void i_quit_signal_handler(int signo) +{ + (void)signo; + quit_requested = 1; +} + +void i_install_quit_signal(void) +{ + struct sigaction sa; + + /* This board's flat, single address-space build can relaunch NXDoom + * (via nxpkg) as a fresh loadable ELF module - a proper posix_spawn of + * a new module load, which gets its own zeroed .bss/re-initialized + * .data - but GAMES_NXDOOM is a tristate Kconfig symbol and can also + * be built in as a true built-in (MODULE=n) sharing this process's + * address space across "launches" with no fresh .bss at all. Reset + * both pieces of state a stale second invocation could see: a leaked + * quit_requested flag would call i_quit() again before the game even + * starts, and a leaked exit_funcs chain would run every previous + * invocation's exit handlers a second time (double free()s, etc.) in + * addition to this invocation's own. + */ + + quit_requested = 0; + exit_funcs = NULL; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = i_quit_signal_handler; + + if (sigaction(SIGTERM, &sa, NULL) < 0) + { + /* Not fatal - the game still runs, it just can't be asked to + * close cleanly from the outside (nxstore's close button will + * have nothing to signal into). Surface it rather than silently + * leaving close non-functional with no trace of why. + */ + + syslog(LOG_WARNING, + "nxdoom: failed to install SIGTERM handler: %d\n", errno); + } +} + +void i_poll_quit_signal(void) +{ + if (quit_requested) + { + syslog(LOG_WARNING, "nxdoom: quit signal seen, calling i_quit\n"); + i_quit(); + } +} + void i_error(const char *error, ...) { char msgbuf[512]; diff --git a/games/NXDoom/src/i_system.h b/games/NXDoom/src/i_system.h index ae1c568068b..ac0c934ae8b 100644 --- a/games/NXDoom/src/i_system.h +++ b/games/NXDoom/src/i_system.h @@ -73,6 +73,23 @@ ticcmd_t *i_base_ticcmd(void); void i_quit(void) NORETURN; +/* Installs a SIGTERM handler that only sets a flag (async-signal-safe) - + * the actual i_quit() cleanup (unmapping the framebuffer, closing fds) + * runs later from i_poll_quit_signal(), called once per tic from a known + * safe point in the main loop rather than from the signal handler itself, + * so a supervisor process (nxstore) requesting an exit can never land in + * the middle of a frame's worth of direct framebuffer/heap access. + */ + +void i_install_quit_signal(void); + +/* Checks the flag set by the SIGTERM handler and calls i_quit() if it's + * set. Must only be called from a safe point in the main loop - see + * i_install_quit_signal(). + */ + +void i_poll_quit_signal(void); + void i_error(const char *error, ...) NORETURN PRINTF_ATTR(1, 2); void i_tactile(int on, int off, int total); diff --git a/games/NXDoom/src/i_video.c b/games/NXDoom/src/i_video.c index 0d3b41af30e..b2189339b9a 100644 --- a/games/NXDoom/src/i_video.c +++ b/games/NXDoom/src/i_video.c @@ -92,6 +92,13 @@ struct graphics_state_s uint8_t scale; + /* Pixel offset to center the scaled game viewport within the frame + * buffer when the buffer is larger than SCREENWIDTH/HEIGHT * scale. + */ + + int xoffset; + int yoffset; + bool inited; /* Track initialization */ }; @@ -262,13 +269,25 @@ static void blit_screen(void) uint8_t p_idx; void *fbptr; - /* TODO: It would be best to do this more efficiently/with less memory. - * It also would be good if we could handle the palette translation here - * such that DOOM can be played on frame buffers with differing bit depths - * and pixel formats. - */ + /* TODO: It would be best to do this more efficiently/with less memory. */ + + if (g_graphics_state.pinfo.bpp != 16 && g_graphics_state.pinfo.bpp != 32) + { + /* The xoffset/stride math below assumes one of those two pixel + * sizes, so continuing would read/write past the intended pixel + * bounds on the very first frame. Fail loudly instead of + * silently corrupting framebuffer memory. + */ + + i_error("Unsupported framebuffer depth: %u bpp", + g_graphics_state.pinfo.bpp); + } + + fbptr = g_graphics_state.fbmem + + g_graphics_state.yoffset * g_graphics_state.pinfo.stride + + g_graphics_state.xoffset * + (g_graphics_state.pinfo.bpp == 16 ? 2 : 4); - fbptr = g_graphics_state.fbmem; for (unsigned y = 0; y < SCREENHEIGHT * g_graphics_state.scale; y++) { for (unsigned x = 0; x < SCREENWIDTH * g_graphics_state.scale; x++) @@ -277,9 +296,18 @@ static void blit_screen(void) .scrnbuf[(y / g_graphics_state.scale) * SCREENWIDTH + (x / g_graphics_state.scale)]; - ((uint32_t *)(fbptr))[x] = - ARGBTO32(g_palette[p_idx].a, g_palette[p_idx].r, - g_palette[p_idx].g, g_palette[p_idx].b); + if (g_graphics_state.pinfo.bpp == 16) + { + ((uint16_t *)(fbptr))[x] = + RGBTO16(g_palette[p_idx].r, g_palette[p_idx].g, + g_palette[p_idx].b); + } + else + { + ((uint32_t *)(fbptr))[x] = + ARGBTO32(g_palette[p_idx].a, g_palette[p_idx].r, + g_palette[p_idx].g, g_palette[p_idx].b); + } } fbptr += g_graphics_state.pinfo.stride; @@ -724,6 +752,18 @@ void i_init_graphics(void) yscale = g_graphics_state.vinfo.yres / SCREENHEIGHT; g_graphics_state.scale = xscale > yscale ? yscale : xscale; + /* Center the scaled viewport within the frame buffer rather than + * pinning it to the top-left corner, since the buffer is typically + * larger than SCREENWIDTH/HEIGHT * scale. + */ + + g_graphics_state.xoffset = + (g_graphics_state.vinfo.xres - + SCREENWIDTH * g_graphics_state.scale) / 2; + g_graphics_state.yoffset = + (g_graphics_state.vinfo.yres - + SCREENHEIGHT * g_graphics_state.scale) / 2; + /* Get frame buffer plane info */ if (ioctl(g_graphics_state.fd, FBIOGET_PLANEINFO, @@ -732,6 +772,22 @@ void i_init_graphics(void) i_error("ioctl(FBIOGET_PLANEINFO) failed: %d\n", errno); } + /* Depth alone is not enough: several incompatible pixel layouts use 16 + * or 32 bits. The conversion in blit_screen() emits RGB565 or RGB32. + */ + + if ((g_graphics_state.pinfo.bpp == 16 && + g_graphics_state.vinfo.fmt != FB_FMT_RGB16_565) || + (g_graphics_state.pinfo.bpp == 32 && + g_graphics_state.vinfo.fmt != FB_FMT_RGB32) || + (g_graphics_state.pinfo.bpp != 16 && + g_graphics_state.pinfo.bpp != 32)) + { + i_error("Unsupported framebuffer format: fmt=%u, bpp=%u", + g_graphics_state.vinfo.fmt, + g_graphics_state.pinfo.bpp); + } + /* Initialize frame buffer memory for actual rendering */ g_graphics_state.fbmem = diff --git a/games/NXDoom/src/m_config.c b/games/NXDoom/src/m_config.c index d4441f5eaae..9e3fb879e33 100644 --- a/games/NXDoom/src/m_config.c +++ b/games/NXDoom/src/m_config.c @@ -1982,16 +1982,14 @@ static void save_default_collection(default_collection_t *collection) * ****************************************************************************/ -static int parse_int_parameter(const char *strparm) +static int parse_int_parameter(const char *strparm, int *param) { - int param; - if (strparm[0] == '0' && strparm[1] == 'x') - sscanf(strparm + 2, "%x", (unsigned int *)¶m); - else - sscanf(strparm, "%i", ¶m); + { + return sscanf(strparm + 2, "%x", (unsigned int *)param) == 1; + } - return param; + return sscanf(strparm, "%i", param) == 1; } static void set_variable(default_t *def, const char *value) @@ -2008,7 +2006,11 @@ static void set_variable(default_t *def, const char *value) case DEFAULT_INT: case DEFAULT_INT_HEX: - *def->location.i = parse_int_parameter(value); + if (parse_int_parameter(value, &intparm)) + { + *def->location.i = intparm; + } + break; case DEFAULT_KEY: @@ -2017,7 +2019,11 @@ static void set_variable(default_t *def, const char *value) * file (save the old value in untranslated) */ - intparm = parse_int_parameter(value); + if (!parse_int_parameter(value, &intparm)) + { + break; + } + def->untranslated = intparm; if (intparm >= 0 && intparm < 128) { @@ -2082,6 +2088,7 @@ static void load_default_collection(default_collection_t *collection) default_t *def; char defname[80]; char strparm[100]; + char line[256]; /* read the file in, overriding any set defaults */ @@ -2096,12 +2103,28 @@ static void load_default_collection(default_collection_t *collection) return; } - while (!feof(f)) + while (fgets(line, sizeof(line), f) != NULL) { - if (fscanf(f, "%79s %99[^\n]\n", defname, strparm) != 2) + strparm[0] = '\0'; + + /* Parse one physical line at a time. fscanf() with whitespace in + * its format can consume the next line as a missing value. + */ + + if (strchr(line, '\n') == NULL && + strlen(line) == sizeof(line) - 1) { - /* This line doesn't match */ + int ch; + + while ((ch = fgetc(f)) != '\n' && ch != EOF) + { + } + + continue; + } + if (sscanf(line, "%79s %99[^\n]", defname, strparm) != 2) + { continue; } @@ -2136,6 +2159,11 @@ static void load_default_collection(default_collection_t *collection) memmove(strparm, strparm + 1, sizeof(strparm) - 1); } + if (strparm[0] == '\0') + { + continue; + } + set_variable(def, strparm); }