From 097d78156e8f309996286edcb5f58d56d9b67b41 Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Thu, 16 Jul 2026 13:34:06 +0530 Subject: [PATCH 1/8] games/NXDoom: RGB565 support, loadable-module support, and crash fixes. Add RGB565 framebuffer support to blit_screen() - it previously assumed a 32-bit ARGB framebuffer unconditionally, a real limitation for any board whose framebuffer is FB_FMT_RGB16_565. A single blit loop now branches on pinfo.bpp only at the pixel-write step (RGBTO16() for 16bpp, the existing ARGBTO32() path for 32bpp); anything else fails loudly via i_error() rather than reading/writing past the intended pixel bounds silently. Also centers the scaled viewport within the framebuffer instead of pinning it to the top-left corner, and adds CONFIG_GAMES_NXDOOM_PREFDIR to the IWAD search path (previously only used for the config/save file location; the Kconfig entry always has a default, so no #ifdef guard is needed around using it). Makes GAMES_NXDOOM tristate and lets MODULE follow it (MODULE = $(CONFIG_GAMES_NXDOOM)) instead of hardcoding MODULE = m, so it can still be built in as before or selected as a standalone loadable module installable via nxpkg/nxstore, same as every other tristate-capable app in apps/. Fix two real hardware crashes found while bringing this up as a loadable module: - A truncated/corrupted config line was silently overriding a variable's compiled-in default with an empty/unparsable value instead of being skipped - this let a corrupted "screenblocks" line through as screenblocks=0, and the renderer's view-size math divides by a value derived from screenblocks, reaching a divide-by-zero hardware exception. Also clamps screenblocks to its own valid range [3, 11] as an independent second layer of defense. - r_map_plane()'s bounds check was gated behind the debug-only CONFIG_GAMES_NXDOOM_RANGECHECK and, when tripped, called the fatal i_error() - both wrong: the check guards a real out-of-bounds array access (observed with values far past even viewheight), so it cannot be optional, and killing the whole process over one glitched plane span is worse than vanilla DOOM's own behavior of rendering the glitch. Now unconditional and clamps y into range instead of touching memory outside the buffers' bounds, so the span still renders (as one glitched row) rather than leaving a gap. Also adds CONFIG_GAMES_NXDOOM_HEAP_BUFFERS: the renderer's visplanes/openings/drawsegs/vissprites scratch buffers remain static arrays by default (matching vanilla DOOM), with heap allocation available as an opt-in for targets where their combined size threatens the internal DRAM budget once linked into a full application image. CONFIG_GAMES_NXDOOM_STATDUMP_MAX_CAPTURES makes statdump's diagnostic capture-buffer size (unrelated to gameplay) a Kconfig option instead of a hardcoded value, default unchanged from vanilla (32). Assisted-by: Claude:claude-sonnet-5 Signed-off-by: aviralgarg05 --- games/NXDoom/Kconfig | 22 +++++++- games/NXDoom/src/d_iwad.c | 10 ++++ games/NXDoom/src/doom/r_bsp.c | 4 ++ games/NXDoom/src/doom/r_bsp.h | 4 ++ games/NXDoom/src/doom/r_main.c | 18 +++++++ games/NXDoom/src/doom/r_plane.c | 87 +++++++++++++++++++++++++++++--- games/NXDoom/src/doom/r_plane.h | 1 + games/NXDoom/src/doom/r_things.c | 4 ++ games/NXDoom/src/doom/r_things.h | 4 ++ games/NXDoom/src/doom/statdump.c | 2 +- games/NXDoom/src/i_video.c | 58 +++++++++++++++++---- games/NXDoom/src/m_config.c | 17 ++++++- 12 files changed, 213 insertions(+), 18 deletions(-) diff --git a/games/NXDoom/Kconfig b/games/NXDoom/Kconfig index cbf47a97a51..0c4d8d5b5a9 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,26 @@ 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 + ---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/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_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..cc0d6ca8272 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,34 @@ 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. + */ + + 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 >= SCREENHEIGHT) + { + y = SCREENHEIGHT - 1; } -#endif if (planeheight != cachedheight[y]) { @@ -195,7 +222,55 @@ 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("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 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_video.c b/games/NXDoom/src/i_video.c index 0d3b41af30e..544c0fe6580 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, diff --git a/games/NXDoom/src/m_config.c b/games/NXDoom/src/m_config.c index d4441f5eaae..e7874eaac42 100644 --- a/games/NXDoom/src/m_config.c +++ b/games/NXDoom/src/m_config.c @@ -2098,7 +2098,9 @@ static void load_default_collection(default_collection_t *collection) while (!feof(f)) { - if (fscanf(f, "%79s %99[^\n]\n", defname, strparm) != 2) + strparm[0] = '\0'; + + if (fscanf(f, "%79s %99[^\n]\n", defname, strparm) < 1) { /* This line doesn't match */ @@ -2136,6 +2138,19 @@ static void load_default_collection(default_collection_t *collection) memmove(strparm, strparm + 1, sizeof(strparm) - 1); } + /* A line with a name but no (or an unparsable) value - e.g. a + * config file left truncated by an unclean shutdown mid-write - + * must not silently override this variable's compiled-in default + * with a bogus zero/empty value. This is what let a corrupted + * "screenblocks" line (empty value) through as screenblocks=0, + * which fed a divide-by-zero straight into the renderer. + */ + + if (strparm[0] == '\0') + { + continue; + } + set_variable(def, strparm); } From b6eb1f44a3be165f848df6e853d918580094fc6b Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Thu, 23 Jul 2026 15:57:41 +0530 Subject: [PATCH 2/8] games/NXDoom: fix out-of-bounds framebuffer write in plane rendering. r_map_plane()'s existing bounds check clamped an out-of-range `y` to SCREENHEIGHT - 1, believing that to be the array bound in need of protection. On real hardware that clamp itself caused a crash: `y` is stored into ds_y and later used by r_draw_span() (r_draw.c) to index ylookup[], 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 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 clamping to viewheight - 1 instead is safe for cachedheight[]/cacheddistance[]/cachedxstep[]/cachedystep[] too. r_draw.c's own RANGECHECK-gated debug assertions are updated to match (they previously compared against SCREENHEIGHT as well). Separately, r_make_spans() indexes spanstart[t1]/spanstart[b1] (read) and writes spanstart[t2]/spanstart[b2] using row indices taken directly from a visplane's top[]/bottom[] arrays, before r_map_plane() is ever called - so its clamp can't protect these. In valid play these rows are either a real screen row or vanilla DOOM's 0xff (255) "no span here" sentinel, and the surrounding while-loop guards are written so the sentinel can never reach spanstart[] except at a plane's own edge columns, where writing spanstart[255] is part of the normal algorithm - already out-of-bounds on this port, since spanstart[] is only sized SCREENHEIGHT (200). A corrupted BSP/segment can also hand these a genuinely arbitrary value (this is what the row-255 crash above traced back to). Guard every touch of spanstart[] directly instead of altering t1/b1/t2/b2 themselves, so the span-tracking state machine's comparisons - including the sentinel logic they rely on - are completely unaffected; a row outside the array just contributes 0 as its span start instead of corrupting or reading past memory. Also gives GAMES_NXDOOM_STATDUMP_MAX_CAPTURES an explicit range (1 1024) - this diagnostic capture-buffer size Kconfig option had no bound at all. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: aviralgarg05 --- games/NXDoom/Kconfig | 1 + games/NXDoom/src/doom/r_draw.c | 4 +- games/NXDoom/src/doom/r_plane.c | 74 ++++++++++++++++++++++++++++++--- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/games/NXDoom/Kconfig b/games/NXDoom/Kconfig index 0c4d8d5b5a9..4fa67011d4c 100644 --- a/games/NXDoom/Kconfig +++ b/games/NXDoom/Kconfig @@ -193,6 +193,7 @@ config GAMES_NXDOOM_HEAP_BUFFERS 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 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_plane.c b/games/NXDoom/src/doom/r_plane.c index cc0d6ca8272..3cd01a7246f 100644 --- a/games/NXDoom/src/doom/r_plane.c +++ b/games/NXDoom/src/doom/r_plane.c @@ -132,6 +132,18 @@ static void r_map_plane(int y, int x1, int x2) * 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) @@ -143,9 +155,9 @@ static void r_map_plane(int y, int x1, int x2) { y = 0; } - else if (y >= SCREENHEIGHT) + else if (y >= viewheight) { - y = SCREENHEIGHT - 1; + y = viewheight - 1; } if (planeheight != cachedheight[y]) @@ -187,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--; } } @@ -239,6 +286,21 @@ void r_init_planes(void) 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"); } From cddef4d8da54a7b7a9d33b64d427337274536535 Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Thu, 23 Jul 2026 15:58:19 +0530 Subject: [PATCH 3/8] games/NXDoom: fix infinite loop parsing corrupted config data. load_default_collection()'s `while (!feof(f))` loop decided whether to keep looping off feof() rather than fscanf()'s own return value - the classic version of this bug: on a config file whose bytes don't line up with "%s %[^\n]\n" at all (e.g. leftover binary/corrupted content from a previous write), fscanf() can fail a conversion without the stream ever reaching EOF, and feof() has no way to know that. On real hardware this hung NXDoom completely at startup with a corrupted default.cfg on disk - no crash, no output, and no way to close the game either, since the hang happened before the main loop (and its SIGTERM poll point) was ever reached. Terminate directly off EOF/error from fscanf() instead. A failed conversion is only guaranteed to consume nothing, not to advance the stream, so also track the file position directly and force one byte of progress (or bail out) if a scan attempt didn't move it - corrupt/ binary content can now only ever cost one pass over the file, never an infinite loop. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: aviralgarg05 --- games/NXDoom/src/m_config.c | 41 +++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/games/NXDoom/src/m_config.c b/games/NXDoom/src/m_config.c index e7874eaac42..4409ff2befe 100644 --- a/games/NXDoom/src/m_config.c +++ b/games/NXDoom/src/m_config.c @@ -2096,12 +2096,49 @@ static void load_default_collection(default_collection_t *collection) return; } - while (!feof(f)) + for (; ; ) { + int nmatched; + long before; + strparm[0] = '\0'; - if (fscanf(f, "%79s %99[^\n]\n", defname, strparm) < 1) + /* Checking feof() to decide whether to keep looping (rather than + * acting on fscanf()'s own return value) is the classic C bug: on + * a config file whose bytes don't line up with "%s %[^\n]\n" at + * all - e.g. leftover binary/corrupted content from a previous + * write - fscanf() can fail a conversion without the stream ever + * reaching EOF, and feof() has no way to know that. That left + * this loop spinning forever parsing nothing, which is exactly + * what made a corrupted default.cfg hang the whole game at + * startup instead of just skipping the bad file. + * + * Terminating off EOF/error from fscanf() itself closes the + * common case, but a failed conversion is only guaranteed to + * consume nothing - it is not guaranteed to have advanced the + * stream either, so a byte sequence that never matches "%s" but + * also never reports EOF/error could still spin without making + * progress. Track the file position directly and force one + * byte of forward progress (or bail out) if a scan attempt + * didn't move it, so corrupt/binary content can only ever cost + * one pass over the file, never an infinite loop. + */ + + before = ftell(f); + nmatched = fscanf(f, "%79s %99[^\n]\n", defname, strparm); + + if (nmatched == EOF) { + break; + } + + if (nmatched < 1) + { + if (ftell(f) == before && fgetc(f) == EOF) + { + break; + } + /* This line doesn't match */ continue; From 5a3d77b65ae1969d532aa1654bf7387105a19ec4 Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Thu, 23 Jul 2026 15:59:23 +0530 Subject: [PATCH 4/8] games/NXDoom: add cooperative SIGTERM quit path for external supervisors. A supervisor process (e.g. an app-store UI that owns the framebuffer while a launched game runs directly against /dev/fb0) has no reachable in-game quit path to drive - no keyboard/touch input is wired up for that - so it can only ask NXDoom to exit from the outside. The only existing option for that was a forced task_delete() from the supervisor's side, which was found on real hardware to hang the entire board (not just this one task) when it landed mid framebuffer/ heap access, on this flat-memory build where a bad access in one task isn't contained to that task. Add i_install_quit_signal()/i_poll_quit_signal()/a SIGTERM handler (i_system.c/i_system.h): the handler itself only sets a volatile sig_atomic_t flag - it must not call i_quit() (or anything it does: munmap, fclose, exit()'s atexit chain) directly, since a signal can land at literally any point in this process's own execution, including mid-malloc()/mid-blit, the same "unsafe mid-operation teardown" risk as being force-killed from outside, just moved into this process's own context. i_poll_quit_signal() defers the actual shutdown to a call in the main per-frame loop (d_doomloop(), d_main.c) - a point that's definitely safe (outside any framebuffer/heap access) and reached every frame regardless of what else NXDoom is doing, which is what's actually verified working end-to-end against a real supervisor's SIGTERM/close path on hardware. i_install_quit_signal() is called once from main() (i_main.c), and also: - Checks sigaction()'s return value and logs via syslog on failure instead of ignoring it silently - the game still runs either way, but silently leaving close non-functional with no trace of why would make a real close-path bug harder to diagnose than it needs to be. - Resets quit_requested and exit_funcs at the start of i_install_quit_signal(). This board's flat, single address-space build normally relaunches NXDoom as a fresh loadable ELF module with its own zeroed .bss, but GAMES_NXDOOM is a tristate Kconfig symbol and can also be built in as a true built-in sharing this process's address space across "launches" with no fresh .bss at all - a leaked quit_requested flag would call i_quit() again before the game even starts on a second invocation, and a leaked exit_funcs chain would re-run every previous invocation's exit handlers a second time. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: aviralgarg05 --- games/NXDoom/src/doom/d_main.c | 6 +++ games/NXDoom/src/i_main.c | 9 ++++ games/NXDoom/src/i_system.c | 77 ++++++++++++++++++++++++++++++++++ games/NXDoom/src/i_system.h | 17 ++++++++ 4 files changed, 109 insertions(+) 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/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); From b51959ea772d9f25bb3c8aeb6d0a15bcc1f1d9ef Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Thu, 23 Jul 2026 16:00:42 +0530 Subject: [PATCH 5/8] games/NXDoom: validate framebuffer pixel format, not just depth. blit_screen() branches purely on pinfo.bpp (16 vs 32) to decide between the RGB565 and RGB32 conversion paths, but bit depth alone doesn't determine pixel layout - multiple incompatible formats share the same depth. i_init_graphics() now checks vinfo.fmt against the one format blit_screen() actually emits for each depth (FB_FMT_RGB16_565 for 16bpp, FB_FMT_RGB32 for 32bpp) and fails loudly via i_error() on any mismatch or unsupported depth, instead of silently misinterpreting the framebuffer's actual pixel layout. Assisted-by: Claude:claude-sonnet-5 Signed-off-by: aviralgarg05 --- games/NXDoom/src/i_video.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/games/NXDoom/src/i_video.c b/games/NXDoom/src/i_video.c index 544c0fe6580..b2189339b9a 100644 --- a/games/NXDoom/src/i_video.c +++ b/games/NXDoom/src/i_video.c @@ -772,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 = From c4bf6360570bba75baea1f3a72d9de5dfd80ea2e Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Thu, 23 Jul 2026 21:04:20 +0530 Subject: [PATCH 6/8] games/NXDoom: Parse configuration one line at a time. Read each physical configuration line independently so malformed or truncated input cannot consume a following setting as its value. Discard overlong lines and retain defaults for incomplete entries. Assisted-by: Codex:gpt-5 Signed-off-by: aviralgarg05 --- games/NXDoom/src/m_config.c | 54 +++++++++---------------------------- 1 file changed, 12 insertions(+), 42 deletions(-) diff --git a/games/NXDoom/src/m_config.c b/games/NXDoom/src/m_config.c index 4409ff2befe..af29eeab089 100644 --- a/games/NXDoom/src/m_config.c +++ b/games/NXDoom/src/m_config.c @@ -2082,6 +2082,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,51 +2097,28 @@ static void load_default_collection(default_collection_t *collection) return; } - for (; ; ) + while (fgets(line, sizeof(line), f) != NULL) { - int nmatched; - long before; - strparm[0] = '\0'; - /* Checking feof() to decide whether to keep looping (rather than - * acting on fscanf()'s own return value) is the classic C bug: on - * a config file whose bytes don't line up with "%s %[^\n]\n" at - * all - e.g. leftover binary/corrupted content from a previous - * write - fscanf() can fail a conversion without the stream ever - * reaching EOF, and feof() has no way to know that. That left - * this loop spinning forever parsing nothing, which is exactly - * what made a corrupted default.cfg hang the whole game at - * startup instead of just skipping the bad file. - * - * Terminating off EOF/error from fscanf() itself closes the - * common case, but a failed conversion is only guaranteed to - * consume nothing - it is not guaranteed to have advanced the - * stream either, so a byte sequence that never matches "%s" but - * also never reports EOF/error could still spin without making - * progress. Track the file position directly and force one - * byte of forward progress (or bail out) if a scan attempt - * didn't move it, so corrupt/binary content can only ever cost - * one pass over the file, never an infinite loop. + /* Parse one physical line at a time. fscanf() with whitespace in + * its format can consume the next line as a missing value. */ - before = ftell(f); - nmatched = fscanf(f, "%79s %99[^\n]\n", defname, strparm); - - if (nmatched == EOF) + if (strchr(line, '\n') == NULL && + strlen(line) == sizeof(line) - 1) { - break; - } + int ch; - if (nmatched < 1) - { - if (ftell(f) == before && fgetc(f) == EOF) + while ((ch = fgetc(f)) != '\n' && ch != EOF) { - break; } - /* This line doesn't match */ + continue; + } + if (sscanf(line, "%79s %99[^\n]", defname, strparm) != 2) + { continue; } @@ -2175,14 +2153,6 @@ static void load_default_collection(default_collection_t *collection) memmove(strparm, strparm + 1, sizeof(strparm) - 1); } - /* A line with a name but no (or an unparsable) value - e.g. a - * config file left truncated by an unclean shutdown mid-write - - * must not silently override this variable's compiled-in default - * with a bogus zero/empty value. This is what let a corrupted - * "screenblocks" line (empty value) through as screenblocks=0, - * which fed a divide-by-zero straight into the renderer. - */ - if (strparm[0] == '\0') { continue; From 8bfd83c401a96680f63172eb8a14d4dc7e969406 Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Thu, 23 Jul 2026 21:12:45 +0530 Subject: [PATCH 7/8] games/NXDoom: Check visplane capacity before writing. Move the maximum-visplane guard ahead of the new plane initialization. At full capacity, the previous ordering wrote three fields one element past the visplanes array before reporting the overflow. Assisted-by: Codex:gpt-5 Signed-off-by: aviralgarg05 --- games/NXDoom/src/doom/r_plane.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/games/NXDoom/src/doom/r_plane.c b/games/NXDoom/src/doom/r_plane.c index 3cd01a7246f..a2a84c18ad7 100644 --- a/games/NXDoom/src/doom/r_plane.c +++ b/games/NXDoom/src/doom/r_plane.c @@ -451,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; From 1fbe5cc67849703e9b10371d56cc05afc343381c Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Fri, 24 Jul 2026 00:33:43 +0530 Subject: [PATCH 8/8] games/NXDoom: Preserve defaults for invalid integer settings. Check integer conversions before updating bound configuration values. This keeps compiled defaults intact for malformed non-numeric values instead of propagating an uninitialized result from sscanf(). Assisted-by: Codex:gpt-5 Signed-off-by: aviralgarg05 --- games/NXDoom/src/m_config.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/games/NXDoom/src/m_config.c b/games/NXDoom/src/m_config.c index af29eeab089..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) {