From 657f6eaad96624cb164bd8fea1513c6ef43fe8a5 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 11:04:04 +0200 Subject: [PATCH 01/15] Amiga: fix latent issues found in code review - fname[18]/sprintf risks overflow for >=10 in any version field; switch to snprintf into a wider static buffer. - (1L << i) for i==31 (or shifting into the depth-loop terminator) is undefined for signed long; use 1UL. - Drop unused cnt= from amii_display_nhwindow's DoMenuScroll call; the menu return value is consumed elsewhere, not here. --- sys/amiga/winchar.c | 2 +- sys/amiga/winfuncs.c | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/sys/amiga/winchar.c b/sys/amiga/winchar.c index 9b29152a7..8980bdf55 100644 --- a/sys/amiga/winchar.c +++ b/sys/amiga/winchar.c @@ -116,7 +116,7 @@ ReadImageFile(const char *filename, struct BitMap **bmp) prop = FindProp(iff, ID_BMAP, ID_CMAP); if (prop) { unsigned char *cmap = prop->sp_Data; - for (j = 0; j < (1L << np) * 3; j += 3) { + for (j = 0; j < (1UL << np) * 3; j += 3) { amii_initmap[j / 3] = amiv_init_map[j / 3] = ((cmap[j+0] >> 4) << 8) diff --git a/sys/amiga/winfuncs.c b/sys/amiga/winfuncs.c index b964d9ffc..3af760edb 100644 --- a/sys/amiga/winfuncs.c +++ b/sys/amiga/winfuncs.c @@ -1087,9 +1087,9 @@ amii_init_nhwindows(int *argcp, char **argv) NewHackScreen.Width = max(WIDTH, amiIDisplay->xpix); NewHackScreen.Height = max(SCREENHEIGHT, amiIDisplay->ypix); { - static char fname[18]; - sprintf(fname, "NetHack %d.%d.%d", VERSION_MAJOR, VERSION_MINOR, - PATCHLEVEL); + static char fname[32]; + snprintf(fname, sizeof fname, "NetHack %d.%d.%d", + VERSION_MAJOR, VERSION_MINOR, PATCHLEVEL); NewHackScreen.DefaultTitle = fname; } if (IntuitionBase->LibNode.lib_Version >= 37) { @@ -1216,7 +1216,7 @@ amii_init_nhwindows(int *argcp, char **argv) /* Find out how deep the screen needs to be, 32 planes is enough! */ for (i = 0; i < 32; ++i) { - if ((1L << i) >= amii_numcolors) + if ((1UL << i) >= (unsigned long) amii_numcolors) break; } @@ -1240,7 +1240,7 @@ amii_init_nhwindows(int *argcp, char **argv) if (--NewHackScreen.Depth < 3) Abort(AN_OpenScreen & ~AT_DeadEnd); } - amii_numcolors = 1L << NewHackScreen.Depth; + amii_numcolors = 1UL << NewHackScreen.Depth; if (HackScreen->Height > 300 && forcenobig == 0) bigscreen = 1; else @@ -1526,7 +1526,6 @@ void amii_display_nhwindow(winid win, boolean blocking) { menu_item *mip; - int cnt; static int lastwin = -1; struct amii_WinDesc *cw; @@ -1549,7 +1548,7 @@ amii_display_nhwindow(winid win, boolean blocking) } if (cw->type == NHW_MENU || cw->type == NHW_TEXT) { - cnt = DoMenuScroll(win, blocking, PICK_ONE, &mip); + (void) DoMenuScroll(win, blocking, PICK_ONE, &mip); } else if (cw->type == NHW_MAP) { amii_end_glyphout(win); /* Do more if it is time... */ From 7c8524ae37a62679e3a29bbdfc4e30855153c141 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 11:04:10 +0200 Subject: [PATCH 02/15] Amiga: make amigapkg depend on $(GAMEBIN) Without the dependency, 'make amigapkg' would copy whatever was already in targets/amiga/ without ever rebuilding when sources changed -- silently shipping a stale binary. --- sys/unix/hints/include/cross-post.500 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sys/unix/hints/include/cross-post.500 b/sys/unix/hints/include/cross-post.500 index 42e562690..e01623002 100644 --- a/sys/unix/hints/include/cross-post.500 +++ b/sys/unix/hints/include/cross-post.500 @@ -307,7 +307,7 @@ UUDECODE = ../util/uudecode ../util/uudecode: ../sys/share/uudecode.c $(CC) $(CFLAGS) -o $@ $< -amigapkg: $(AMITILES) ../util/uudecode +amigapkg: $(GAMEBIN) $(AMITILES) ../util/uudecode mkdir -p $(TARGETPFX)pkg/NetHack/tiles $(TARGETPFX)pkg/NetHack/hack cp $(GAMEBIN) $(TARGETPFX)pkg/NetHack/nethack cp ../dat/nhdat $(TARGETPFX)pkg/NetHack/nhdat From f5837878259bb43960c247b4fc5604e65705b7a8 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 11:12:40 +0200 Subject: [PATCH 03/15] Amiga: fix overview-window crashes - MyAllocBitMap left bm->mflags uninitialized, so MyFreeBitMap took the wrong path between FreeRaster and FreeMem and intermittently corrupted exec's free list (Software Failure 0x81000005, DEADEND in FreeMem). - The NHW_OVER window is BORDERLESS, so attaching WINDOWSIZING | WINDOWDRAG | WINDOWCLOSE created phantom gadgets that hit-test against unrelated input. Pressing ESC while the overview was selected fired CLOSEWINDOW and destroyed the window underneath the running code, leading to wild-PC crashes. Drop the gadget flags; SHIFT-HELP already toggles the overview cleanly via delayed_key_action. - amii_destroy_nhwindow only reset WIN_MAP / WIN_STATUS / WIN_MESSAGE / WIN_INVEN; WIN_OVER and WIN_BASE kept pointing at freed slots, so any later 'WIN_X != WIN_ERR && amii_wins[WIN_X]->win' check dereferenced NULL. Reset them too. --- sys/amiga/winchar.c | 1 + sys/amiga/winfuncs.c | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/sys/amiga/winchar.c b/sys/amiga/winchar.c index 8980bdf55..b84561311 100644 --- a/sys/amiga/winchar.c +++ b/sys/amiga/winchar.c @@ -189,6 +189,7 @@ MyAllocBitMap(int xsize, int ysize, int depth, long mflags) if (!bm) return (NULL); + bm->mflags = mflags; bm->xsize = xsize; bm->ysize = ysize; InitBitMap(&bm->bm, depth, xsize, ysize); diff --git a/sys/amiga/winfuncs.c b/sys/amiga/winfuncs.c index 3af760edb..9b172201d 100644 --- a/sys/amiga/winfuncs.c +++ b/sys/amiga/winfuncs.c @@ -227,6 +227,10 @@ amii_destroy_nhwindow(winid win) /* just hide */ WIN_MESSAGE = WIN_ERR; else if (win == WIN_INVEN) WIN_INVEN = WIN_ERR; + else if (win == WIN_OVER) + WIN_OVER = WIN_ERR; + else if (win == WIN_BASE) + WIN_BASE = WIN_ERR; } struct FillParams { @@ -416,8 +420,13 @@ amii_create_nhwindow(int type) if (nw->Width >= amiIDisplay->xpix - nw->LeftEdge) nw->Width = amiIDisplay->xpix - nw->LeftEdge; } else if (WINVERS_AMIV && type == NHW_OVER) { - nw->Flags |= WINDOWSIZING | WINDOWDRAG | WINDOWCLOSE; - nw->IDCMPFlags |= CLOSEWINDOW; + /* No window-system gadgets: BORDERLESS means there is no border + * to attach close/size/drag gadgets to. On some Kickstarts the + * phantom gadgets hit-test against unrelated input (selecting + * the overview and pressing ESC has been observed to destroy + * the window and crash the game). SHIFT-HELP toggles the + * overview cleanly via delayed_key_action; that is the + * supported close path. */ /* Bring up window as half the width of the message window, and make * the message window change to one half the width... */ From 74c87caac37e7cd1855a18fa646f3071915cff05 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 11:35:27 +0200 Subject: [PATCH 04/15] Amiga: fix bitmap/IFF resource handling in winchar.c - MyAllocBitMap left bm->bm.Planes[] uninitialized; InitBitMap only fills BytesPerRow/Rows/Flags/Depth, not Planes[]. If AllocRaster fails mid-loop, MyFreeBitMap was iterating up to Depth and would pass uninitialized stack-garbage pointers to FreeRaster. Zero Planes[] before the alloc loop. - ReadImageFile leaked iffparse.library, the IFFHandle, the DOS file handle, and any open-IFF state on every panic path. On AmigaOS those handles are not auto-reclaimed when the process dies, so each failure stranded resources until reboot. Restructure to a single cleanup label and free in reverse-acquisition order before panicking. - OpenIFF returns an error code that was being thrown away, so a failed open would feed corrupt state to ParseIFF. Check and bail. --- sys/amiga/winchar.c | 68 +++++++++++++++++++++++++++++++++------------ 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/sys/amiga/winchar.c b/sys/amiga/winchar.c index b84561311..9636ba3e7 100644 --- a/sys/amiga/winchar.c +++ b/sys/amiga/winchar.c @@ -79,36 +79,54 @@ struct BitMap *tileimg, *tile; BitMapHeader ReadImageFile(const char *filename, struct BitMap **bmp) { - BitMapHeader *bmhd, bmhds; + BitMapHeader *bmhd, bmhds = { 0 }; int j, np; - struct IFFHandle *iff; + long err; + struct IFFHandle *iff = NULL; struct StoredProperty *prop; + int iff_opened = 0; + const char *errfmt = NULL; + long errcode = 0; IFFParseBase = OpenLibrary("iffparse.library", 0L); if (!IFFParseBase) panic("No iffparse.library"); iff = AllocIFF(); - if (!iff) - panic("can't start IFF processing"); + if (!iff) { + errfmt = "can't start IFF processing"; + goto cleanup; + } iff->iff_Stream = Open(filename, MODE_OLDFILE); - if (iff->iff_Stream == 0) - panic("Can't open %s", filename); + if (iff->iff_Stream == 0) { + errfmt = "Can't open %s"; + goto cleanup; + } InitIFFasDOS(iff); - OpenIFF(iff, IFFF_READ); + if ((err = OpenIFF(iff, IFFF_READ)) != 0) { + errfmt = "OpenIFF failed on %s, code %ld"; + errcode = err; + goto cleanup; + } + iff_opened = 1; + PropChunk(iff, ID_BMAP, ID_BMHD); PropChunk(iff, ID_BMAP, ID_CMAP); PropChunk(iff, ID_BMAP, ID_PDAT); StopChunk(iff, ID_BMAP, ID_PLNE); - if ((j = ParseIFF(iff, IFFPARSE_SCAN)) != 0) - panic("ParseIFF failed on %s, code %d", - filename, j); + if ((err = ParseIFF(iff, IFFPARSE_SCAN)) != 0) { + errfmt = "ParseIFF failed on %s, code %ld"; + errcode = err; + goto cleanup; + } prop = FindProp(iff, ID_BMAP, ID_BMHD); - if (!prop) - panic("No BMHD chunk in %s", filename); + if (!prop) { + errfmt = "No BMHD chunk in %s"; + goto cleanup; + } bmhd = (BitMapHeader *) prop->sp_Data; np = bmhd->nPlanes; @@ -132,18 +150,29 @@ ReadImageFile(const char *filename, struct BitMap **bmp) *bmp = MyAllocBitMap(bmhd->w, bmhd->h, np, MEMF_CHIP | MEMF_CLEAR); - if (!*bmp) - panic("Can't allocate bitmap for %s", filename); + if (!*bmp) { + errfmt = "Can't allocate bitmap for %s"; + goto cleanup; + } for (j = 0; j < np; j++) ReadChunkBytes(iff, (*bmp)->Planes[j], RASSIZE(bmhd->w, bmhd->h)); bmhds = *bmhd; - CloseIFF(iff); - Close(iff->iff_Stream); - FreeIFF(iff); + +cleanup: + if (iff_opened) + CloseIFF(iff); + if (iff && iff->iff_Stream) + Close(iff->iff_Stream); + if (iff) + FreeIFF(iff); CloseLibrary(IFFParseBase); + IFFParseBase = NULL; + + if (errfmt) + panic(errfmt, filename, errcode); return bmhds; } @@ -193,6 +222,11 @@ MyAllocBitMap(int xsize, int ysize, int depth, long mflags) bm->xsize = xsize; bm->ysize = ysize; InitBitMap(&bm->bm, depth, xsize, ysize); + /* InitBitMap does not zero Planes[]; if a later AllocRaster fails + * and MyFreeBitMap unwinds, the uninitialized entries above the + * failure would be passed to FreeRaster as garbage pointers. */ + for (j = 0; j < (int) (sizeof bm->bm.Planes / sizeof bm->bm.Planes[0]); ++j) + bm->bm.Planes[j] = NULL; for (j = 0; j < depth; ++j) { if (mflags & MEMF_CHIP) bm->bm.Planes[j] = AllocRaster(xsize, ysize); From cb46d9effda8c01618c0dbfb05b20ca64d76a4f8 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 11:35:41 +0200 Subject: [PATCH 05/15] Amiga: tighten two minor issues from review - amii_set_text_font called CloseLibrary(DiskfontBase) outside the OpenLibrary guard; on Kickstart V36+ that is a no-op for a NULL handle, but on V33/V34 it is undefined. Move the close inside the if-block where DiskfontBase is known non-NULL. - amii_get_ext_cmd's bounds check used BUFSZ for an obufp[100] buffer; the tighter COLNO check actually bounded it but the expression was misleading. Use sizeof obufp. --- sys/amiga/winami.c | 3 ++- sys/amiga/winfuncs.c | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/sys/amiga/winami.c b/sys/amiga/winami.c index 19a660e42..edd1b5d15 100644 --- a/sys/amiga/winami.c +++ b/sys/amiga/winami.c @@ -499,7 +499,8 @@ amii_get_ext_cmd(void) sel = com_index; } else { colx = put_ext_cmd(obufp, colx, cw, bottom); - if (bufp - obufp < BUFSZ - 1 && bufp - obufp < COLNO) + if (bufp - obufp < (int) sizeof obufp - 1 + && bufp - obufp < COLNO) bufp++; } } else if (c == ('X' - 64) || c == '\177') { diff --git a/sys/amiga/winfuncs.c b/sys/amiga/winfuncs.c index 9b172201d..f2ffdb641 100644 --- a/sys/amiga/winfuncs.c +++ b/sys/amiga/winfuncs.c @@ -1732,7 +1732,7 @@ amii_set_text_font(char *name, int size) /* Look for windows to set, and change them */ - if (DiskfontBase = OpenLibrary("diskfont.library", amii_libvers)) { + if ((DiskfontBase = OpenLibrary("diskfont.library", amii_libvers))) { TextsFont = OpenDiskFont(&TextsFont13); for (i = 0; TextsFont && i < MAXWIN; ++i) { if ((cw = amii_wins[i]) && cw->win != NULL) { @@ -1751,9 +1751,9 @@ amii_set_text_font(char *name, int size) } } } + CloseLibrary(DiskfontBase); + DiskfontBase = NULL; } - CloseLibrary(DiskfontBase); - DiskfontBase = NULL; } void From 157c005f02deadd3251003f43513b84926f69f59 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:18:44 +0200 Subject: [PATCH 06/15] Amiga: harden host-side bmp/xpm to iff converters Replace #pragma-pack BMP header reads with little-endian byte readers so the tool works on any host endianness. Add dimension and color-count range checks, zero-init pixel remap table, check calloc, free bmpdata on early returns, send malloc errors to stderr. Add bp>xbuf guards to xpmgetline's strip loop. --- sys/amiga/bmp2iff_host.c | 76 ++++++++++++++++++++++++++++++++++++---- sys/amiga/xpm2iff_host.c | 18 +++++++--- 2 files changed, 83 insertions(+), 11 deletions(-) diff --git a/sys/amiga/bmp2iff_host.c b/sys/amiga/bmp2iff_host.c index 85bb42088..1182aa732 100644 --- a/sys/amiga/bmp2iff_host.c +++ b/sys/amiga/bmp2iff_host.c @@ -65,6 +65,41 @@ static const RGB amiv_pal[16] = { {0xFF,0xBB,0x99}, /* 15 peach */ }; +/* --------------------------------------------------------- */ +/* Little-endian readers (BMP is LE regardless of host) */ +/* --------------------------------------------------------- */ + +static int +read_u16le(FILE *fp, uint16_t *out) +{ + int lo = fgetc(fp), hi = fgetc(fp); + if (hi == EOF) return 0; + *out = (uint16_t)(((unsigned) hi << 8) | (unsigned) lo); + return 1; +} + +static int +read_u32le(FILE *fp, uint32_t *out) +{ + int b0 = fgetc(fp), b1 = fgetc(fp); + int b2 = fgetc(fp), b3 = fgetc(fp); + if (b3 == EOF) return 0; + *out = ((uint32_t)(unsigned) b3 << 24) + | ((uint32_t)(unsigned) b2 << 16) + | ((uint32_t)(unsigned) b1 << 8) + | (uint32_t)(unsigned) b0; + return 1; +} + +static int +read_i32le(FILE *fp, int32_t *out) +{ + uint32_t v; + if (!read_u32le(fp, &v)) return 0; + *out = (int32_t) v; + return 1; +} + /* --------------------------------------------------------- */ /* Colour helpers */ /* --------------------------------------------------------- */ @@ -345,7 +380,7 @@ main(int argc, char **argv) int nplanes, maxcol; int i, y; RGB outpal[256]; - int remap[256]; + int remap[256] = {0}; uint8_t *remapped; uint8_t *plane_data[8]; uint8_t cmap_rgb[256 * 3]; @@ -370,8 +405,22 @@ main(int argc, char **argv) bmpfp = fopen(argv[3], "rb"); if (!bmpfp) { perror(argv[3]); return 1; } - if (fread(&fhdr, sizeof(fhdr), 1, bmpfp) != 1 - || fread(&ihdr, sizeof(ihdr), 1, bmpfp) != 1) { + if (!read_u16le(bmpfp, &fhdr.bfType) + || !read_u32le(bmpfp, &fhdr.bfSize) + || !read_u16le(bmpfp, &fhdr.bfReserved1) + || !read_u16le(bmpfp, &fhdr.bfReserved2) + || !read_u32le(bmpfp, &fhdr.bfOffBits) + || !read_u32le(bmpfp, &ihdr.biSize) + || !read_i32le(bmpfp, &ihdr.biWidth) + || !read_i32le(bmpfp, &ihdr.biHeight) + || !read_u16le(bmpfp, &ihdr.biPlanes) + || !read_u16le(bmpfp, &ihdr.biBitCount) + || !read_u32le(bmpfp, &ihdr.biCompression) + || !read_u32le(bmpfp, &ihdr.biSizeImage) + || !read_i32le(bmpfp, &ihdr.biXPelsPerMeter) + || !read_i32le(bmpfp, &ihdr.biYPelsPerMeter) + || !read_u32le(bmpfp, &ihdr.biClrUsed) + || !read_u32le(bmpfp, &ihdr.biClrImportant)) { fprintf(stderr, "Failed to read BMP header\n"); return 1; } @@ -388,6 +437,11 @@ main(int argc, char **argv) img_w = ihdr.biWidth; img_h = abs(ihdr.biHeight); + if (img_w <= 0 || img_w > 16384 || img_h <= 0 || img_h > 16384) { + fprintf(stderr, "BMP dimensions out of range: %dx%d\n", + img_w, img_h); + return 1; + } ncolors = ihdr.biClrUsed ? ihdr.biClrUsed : 256; if (ncolors > 256) ncolors = 256; @@ -410,13 +464,15 @@ main(int argc, char **argv) rowstride = (img_w + 3) & ~3; bmpdata = malloc(rowstride * img_h); if (!bmpdata) { - printf("%s\n", "malloc failure on bmpdata"); + fprintf(stderr, "malloc failure on bmpdata\n"); return 1; } fseek(bmpfp, fhdr.bfOffBits, SEEK_SET); if (fread(bmpdata, 1, rowstride * img_h, bmpfp) != (size_t)(rowstride * img_h)) { fprintf(stderr, "Failed to read pixel data\n"); + free(bmpdata); + fclose(bmpfp); return 1; } fclose(bmpfp); @@ -424,7 +480,8 @@ main(int argc, char **argv) /* flip bottom-up to top-down */ pixels = malloc(img_w * img_h); if (!pixels) { - printf("%s\n", "malloc failure on pixels"); + fprintf(stderr, "malloc failure on pixels\n"); + free(bmpdata); return 1; } if (ihdr.biHeight > 0) { @@ -450,7 +507,7 @@ main(int argc, char **argv) remapped = malloc(img_w * img_h); if (!remapped) { - printf("%s\n", "malloc failure on remapped"); + fprintf(stderr, "malloc failure on remapped\n"); return 1; } for (i = 0; i < img_w * img_h; i++) @@ -458,8 +515,13 @@ main(int argc, char **argv) /* convert to bitplanes */ planesize = (img_w / 8) * img_h; - for (i = 0; i < nplanes; i++) + for (i = 0; i < nplanes; i++) { plane_data[i] = calloc(1, planesize); + if (!plane_data[i]) { + fprintf(stderr, "calloc failure for plane %d\n", i); + return 1; + } + } to_planes(remapped, img_w, img_h, nplanes, plane_data); diff --git a/sys/amiga/xpm2iff_host.c b/sys/amiga/xpm2iff_host.c index 589d4f56f..5a968979e 100644 --- a/sys/amiga/xpm2iff_host.c +++ b/sys/amiga/xpm2iff_host.c @@ -62,12 +62,13 @@ xpmgetline(void) /* strip trailing <",> and whitespace */ for (bp = xbuf; *bp; bp++) ; - bp--; - while (isspace((unsigned char)*bp)) + if (bp > xbuf) bp--; - if (*bp == ',') + while (bp > xbuf && isspace((unsigned char)*bp)) bp--; - if (*bp == '"') + if (bp > xbuf && *bp == ',') + bp--; + if (bp > xbuf && *bp == '"') bp--; bp++; *bp = '\0'; @@ -209,10 +210,19 @@ main(int argc, char **argv) return 1; } + if (XpmScreen.Colors < 1 || XpmScreen.Colors > 256) { + fprintf(stderr, + "xpm2iff_host: unsupported color count %d\n", + XpmScreen.Colors); + return 1; + } + /* nplanes = ceil(log2(Colors)) */ nplanes = 0; i = XpmScreen.Colors - 1; while (i > 0) { nplanes++; i >>= 1; } + if (nplanes == 0) + nplanes = 1; colors = 1 << nplanes; From ebcf31a4dae5575d87014f7d81b9274a49142ccf Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:19:28 +0200 Subject: [PATCH 07/15] Amiga: drop UNTESTED AROS path; tighten fopenp separator write The UNTESTED #ifdef in freediskspace was never gated by any hints file, so the unsigned-long-long path could only be enabled by a stray manual #define -- in which case the return type is still long and silently truncates. Remove the branches. In fopenp the separator '/' write was unchecked: when the path segment exactly filled the buffer to BUFSIZ-2 it would land at buf[BUFSIZ-1] and the follow-on NUL would write past the end. Guard the write. --- sys/amiga/amidos.c | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/sys/amiga/amidos.c b/sys/amiga/amidos.c index e7b22259a..097e57905 100644 --- a/sys/amiga/amidos.c +++ b/sys/amiga/amidos.c @@ -142,15 +142,7 @@ getlogin(void) long freediskspace(char *path) { -#ifdef UNTESTED - /* these changes from Patric Mueller for AROS to - * handle larger disks. Also needs limits.h and aros/oldprograms.h - * for AROS. (keni) - */ - unsigned long long freeBytes = 0; -#else long freeBytes = 0; -#endif struct InfoData *infoData; /* Remember... longword aligned */ char fileName[32]; @@ -192,11 +184,6 @@ freediskspace(char *path) infoData->id_NumBlocks - infoData->id_NumBlocksUsed; freeBytes -= (freeBytes + EXTENSION) / (EXTENSION + 1); freeBytes *= infoData->id_BytesPerBlock; -#ifdef UNTESTED - if (freeBytes > LONG_MAX) { - freeBytes = LONG_MAX; - } -#endif } if (freeBytes < 0) freeBytes = 0; @@ -368,8 +355,11 @@ fopenp(const char *name, const char *mode) return (NULL); lastch = *bp++ = *pp++; } - if (lastch != ':' && lastch != '/' && bp != buf) + if (lastch != ':' && lastch != '/' && bp != buf) { + if (bp >= buf + BUFSIZ - 2) + return (NULL); *bp++ = '/'; + } if (bp + strlen(name) > buf + BUFSIZ - 1) return (NULL); strcpy(bp, name); From d99eeb17c2c5acbcac1f519909348de116af27a1 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:21:23 +0200 Subject: [PATCH 08/15] Amiga: header hygiene and palette-size constants Add AMII_PALETTE_SIZE / AMIV_PALETTE_SIZE in amiconf.h to make the actual populated portion of the init-map arrays explicit. Drop the redundant extern void exit() declaration. Annotate Abort with NORETURN in both amiconf.h and winproto.h; drop the duplicate Abort declaration further down winproto.h. Convert the bare-token "CLIPPING must be defined" assertion in windefs.h into a real #error directive. Comment in winext.h to disambiguate the three similarly named amii*_init*map palette arrays. --- include/amiconf.h | 7 +++++-- sys/amiga/windefs.h | 2 +- sys/amiga/winext.h | 11 +++++++++++ sys/amiga/winproto.h | 4 +--- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/include/amiconf.h b/include/amiconf.h index 47306f9d2..593f3e470 100644 --- a/include/amiconf.h +++ b/include/amiconf.h @@ -48,9 +48,8 @@ extern void nethack_exit(int); extern void amii_setpens(int); extern void getlind(const char *, char *, const char *); -extern void exit(int); extern void CleanUp(void); -extern void Abort(long); +extern void Abort(long) NORETURN; extern int getpid(void); extern int kbhit(void); extern int WindowGetchar(void); @@ -84,6 +83,10 @@ extern void ami_wininit_data(int); #define CHANGE_COLOR 1 #define DEPTH 6 /* Maximum depth of the screen allowed */ #define AMII_MAXCOLORS (1L << DEPTH) +/* Number of palette entries actually populated in amii_init_map[] (AMII text + * mode) and amiv_init_map[] (AMIV tile mode). Indices beyond these read 0. */ +#define AMII_PALETTE_SIZE 8 +#define AMIV_PALETTE_SIZE 32 typedef unsigned short AMII_COLOR_TYPE; #define PORT_HELP "amii.hlp" diff --git a/sys/amiga/windefs.h b/sys/amiga/windefs.h index 234d213a8..84faf98ff 100644 --- a/sys/amiga/windefs.h +++ b/sys/amiga/windefs.h @@ -29,7 +29,7 @@ #include "func_tab.h" #ifndef CLIPPING -CLIPPING must be defined for the AMIGA version +#error "CLIPPING must be defined for the AMIGA version" #endif #undef LI diff --git a/sys/amiga/winext.h b/sys/amiga/winext.h index 02d1436a6..0ceff1d6b 100644 --- a/sys/amiga/winext.h +++ b/sys/amiga/winext.h @@ -23,6 +23,17 @@ extern struct amii_DisplayDesc *amiIDisplay; /* the Amiga Intuition descriptor */ extern struct window_procs amii_procs; extern struct window_procs amiv_procs; +/* Three similarly-named palette arrays. Note the position of the + * second underscore distinguishes them: + * amii_initmap = working/runtime palette (mutated by tile/tomb load + * and the in-game color editor). + * amii_init_map = AMII (text-mode) compile-time defaults, 8 entries. + * amiv_init_map = AMIV (tile-mode) compile-time defaults, 32 entries + * (mutated by ReadImageFile when a tile/tomb IFF + * carries its own CMAP). + * The naming is historical; sysflags.amii_curmap is yet another related + * array holding the user's saved color choices. + */ extern unsigned short amii_initmap[AMII_MAXCOLORS]; extern unsigned short amiv_init_map[AMII_MAXCOLORS]; extern unsigned short amii_init_map[AMII_MAXCOLORS]; diff --git a/sys/amiga/winproto.h b/sys/amiga/winproto.h index 831db69a3..60bb1adf9 100644 --- a/sys/amiga/winproto.h +++ b/sys/amiga/winproto.h @@ -59,7 +59,7 @@ int amikbhit(void); int WindowGetchar(void); WETYPE WindowGetevent(void); void amii_cleanup(void); -void Abort(long rc); +void Abort(long rc) NORETURN; void CleanUp(void); void flush_glyph_buffer(struct Window *w); void amiga_print_glyph(winid window, int color_index, int glyph); @@ -120,8 +120,6 @@ void amii_display_file(const char *fn, boolean complain); void SetBorder(struct Gadget *gd); /* malloc/free provided by stdlib.h */ -void Abort(long rc); - win_request_info *amii_ctrl_nhwindow(winid, int, win_request_info *); /* amirip.c */ From 15e3973ac2c5eb639b85277912eddfcc3245df59 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:24:15 +0200 Subject: [PATCH 09/15] Amiga: tighten input/dialog buffer bounds Right-size the Intuition string-gadget buffer to BUFSZ so a caller with a BUFSZ-sized buffer cannot be overflowed. Enlarge the amii_yn_function prompt buffer to fit the worst-case query + resp + def + trailing space and switch the appends to Snprintf with remaining-space tracking. Replace sprintf in amii_display_file's "Can't display X: Y" path with Snprintf. In EditColor's Save path drop the strcpy/strcat chain that could trail off the end of oname/nname when dirname returned a near-full path; use Snprintf instead. Rewrite dirname() to copy first and truncate the copy, so it no longer briefly NULs the caller's string. --- sys/amiga/winami.c | 15 +++++++++------ sys/amiga/winreq.c | 48 +++++++++++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/sys/amiga/winami.c b/sys/amiga/winami.c index edd1b5d15..6fdd95b55 100644 --- a/sys/amiga/winami.c +++ b/sys/amiga/winami.c @@ -339,7 +339,7 @@ struct NewScreen NewHackScreen = { 0, 0, WIDTH, SCREENHEIGHT, 3, 0, void amii_askname(void) { - char plnametmp[300]; /* From winreq.c: sizeof(StrStringSIBuff) */ + char plnametmp[BUFSZ]; /* matches StrStringSIBuff in winreq.c */ *plnametmp = 0; do { amii_getlin("Who are you?", plnametmp); @@ -577,7 +577,7 @@ amii_yn_function(const char *query, const char *resp, char def) char q; char rtmp[40]; boolean digit_ok, allow_num; - char prompt[BUFSZ]; + char prompt[BUFSZ + QBUFSZ + 16]; struct amii_WinDesc *cw; if (cw = amii_wins[WIN_MESSAGE]) @@ -592,10 +592,12 @@ amii_yn_function(const char *query, const char *resp, char def) *rb = '\0'; (void) strncpy(prompt, query, QBUFSZ - 1); prompt[QBUFSZ - 1] = '\0'; - Sprintf(eos(prompt), " [%s]", respbuf); + Snprintf(eos(prompt), sizeof prompt - strlen(prompt), + " [%s]", respbuf); if (def) - Sprintf(eos(prompt), " (%c)", def); - Strcat(prompt, " "); + Snprintf(eos(prompt), sizeof prompt - strlen(prompt), + " (%c)", def); + Snprintf(eos(prompt), sizeof prompt - strlen(prompt), " "); pline("%s", prompt); } else { amii_putstr(WIN_MESSAGE, 0, query); @@ -705,7 +707,8 @@ amii_display_file(const char *fn, boolean complain) if ((fp = dlb_fopen(fn, RDTMODE)) == (dlb *) NULL) { if (complain) { - sprintf(buf, "Can't display %s: %s", fn, strerror(errno)); + Snprintf(buf, sizeof buf, + "Can't display %s: %s", fn, strerror(errno)); amii_addtopl(buf); } return; diff --git a/sys/amiga/winreq.c b/sys/amiga/winreq.c index d4a1bc362..fb87db8bc 100644 --- a/sys/amiga/winreq.c +++ b/sys/amiga/winreq.c @@ -21,8 +21,8 @@ struct IntuiText IText1 = { 3, 0, JAM1, 4, 1, NULL, (UBYTE *) "Cancel", struct Gadget Gadget2 = { NULL, 9, 15, 56, 10, NULL, RELVERIFY, BOOLGADGET, (APTR) &Border1, NULL, &IText1, NULL, NULL, 1, NULL }; -UBYTE StrStringSIBuff[300]; -struct StringInfo StrStringSInfo = { StrStringSIBuff, UNDOBUFFER, 0, 300, 0, +UBYTE StrStringSIBuff[BUFSZ]; +struct StringInfo StrStringSInfo = { StrStringSIBuff, UNDOBUFFER, 0, BUFSZ, 0, 0, 0, 0, 0, 0, 0, 0, NULL }; SHORT BorderVectors2[] = { 0, 0, 439, 0, 439, 11, 0, 11, 0, 0 }; struct Border Border2 = { -1, -1, 3, 0, JAM1, 5, BorderVectors2, NULL }; @@ -187,14 +187,23 @@ EditColor(void) break; } - strcpy(oname, dirname((char *) configfile)); - if (oname[strlen(oname) - 1] != ':') { - sprintf(nname, "%s/New_NetHack.cnf", oname); - strcat(oname, "/"); - strcat(oname, "Old_NetHack.cnf"); - } else { - sprintf(nname, "%sNew_NetHack.cnf", oname); - strcat(oname, "Old_NetHack.cnf"); + { + size_t olen; + strncpy(oname, dirname((char *) configfile), + sizeof(oname) - 1); + oname[sizeof(oname) - 1] = '\0'; + olen = strlen(oname); + if (olen > 0 && oname[olen - 1] != ':') { + Snprintf(nname, sizeof nname, + "%s/New_NetHack.cnf", oname); + Snprintf(oname + olen, sizeof(oname) - olen, + "/Old_NetHack.cnf"); + } else { + Snprintf(nname, sizeof nname, + "%sNew_NetHack.cnf", oname); + Snprintf(oname + olen, sizeof(oname) - olen, + "Old_NetHack.cnf"); + } } nfp = fopen(nname, "w"); @@ -506,20 +515,19 @@ EditClipping(void) char * dirname(char *str) { - char *t, c; static char dir[300]; + char *t; - t = strrchr(str, '/'); + strncpy(dir, str, sizeof(dir) - 1); + dir[sizeof(dir) - 1] = '\0'; + + t = strrchr(dir, '/'); + if (!t) + t = strrchr(dir, ':'); if (!t) - t = strrchr(str, ':'); - if (!t) { dir[0] = '\0'; - } else { - c = *t; - *t = 0; - strcpy(dir, str); - *t = c; - } + else + *t = '\0'; return (dir); } From 252ca5bef7ad1cca25693d40e5e8b0df3594b608 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:27:13 +0200 Subject: [PATCH 10/15] Amiga: defensive NULL/bounds guards in menus and window creation Guard the gd lookup in DoMenuScroll's GADGETUP/MOUSEMOVE branches so a window with no GadgetID==1 does not deref NULL; match the existing guards in the keyboard-scroll branches. In the keyboard selector and MENU_UNSELECT_ALL paths, only mutate items with canselect set so a non-selectable header cannot have its str stomped. Clamp MENU_LAST_PAGE topidx to >= 0. Make find_menu_item return NULL on negative idx instead of the head item. Guard the PROMPTFIRST data[] shuffle behind cury > 0. In amii_destroy_nhwindow's NHW_OVER branch use cw->win with a NULL guard instead of dereferencing amii_wins[WIN_OVER]->win blindly. Range-check the type argument to amii_create_nhwindow. Fix the *argv_in[1] precedence bug so the -L/-l flag does not deref NULL when it is the last argument. Wrap AllocAslRequest result in a NULL check before AslRequestTags/FreeAslRequest. Defensively bounds-check the idx argument to DispCol. Replace the -25937 signed-int literal in clipwin's PropInfo with the equivalent UWORD value 39599. Simplify amii_start_menu's free loop; switch DoMenuScroll's inventory title and Count display to Snprintf, and stop passing countString to pline as a format. --- sys/amiga/clipwin.c | 2 +- sys/amiga/winamenu.c | 34 +++++++++++++++++++---------- sys/amiga/winfuncs.c | 51 +++++++++++++++++++++++++------------------- sys/amiga/winreq.c | 3 +++ 4 files changed, 56 insertions(+), 34 deletions(-) diff --git a/sys/amiga/clipwin.c b/sys/amiga/clipwin.c index f2d38ce64..6277b6b60 100644 --- a/sys/amiga/clipwin.c +++ b/sys/amiga/clipwin.c @@ -197,7 +197,7 @@ static struct Gadget ClipXSIZE = { static struct PropInfo ClipClipYSIZESInfo = { AUTOKNOB + FREEHORIZ, /* PropInfo flags */ - -25937, -1, /* horizontal and vertical pot values */ + 39599, -1, /* horizontal and vertical pot values */ 10922, -1, /* horizontal and vertical body values */ }; diff --git a/sys/amiga/winamenu.c b/sys/amiga/winamenu.c index 19d439563..7a9e404ed 100644 --- a/sys/amiga/winamenu.c +++ b/sys/amiga/winamenu.c @@ -32,8 +32,7 @@ amii_start_menu(winid window, unsigned long mbehavior UNUSED) cw->data = NULL; } - for (mip = cw->menu.items, i = 0; - (mip = cw->menu.items) && i < cw->menu.count; ++i) { + while ((mip = cw->menu.items) != NULL) { cw->menu.items = mip->next; free(mip); } @@ -149,11 +148,13 @@ amii_end_menu(winid window, const char *morestr) cw->menu.last->next = cw->menu.items; cw->menu.items = cw->menu.last; cw->menu.last = mip; - t = cw->data[cw->cury - 1]; - for (i = cw->cury - 1; i > 0; i--) { - cw->data[i] = cw->data[i - 1]; + if (cw->cury > 0) { + t = cw->data[cw->cury - 1]; + for (i = cw->cury - 1; i > 0; i--) { + cw->data[i] = cw->data[i - 1]; + } + cw->data[0] = t; } - cw->data[0] = t; #endif } @@ -190,6 +191,8 @@ amii_menu_item * find_menu_item(struct amii_WinDesc *cw, int idx) { amii_menu_item *mip; + if (idx < 0) + return NULL; for (mip = cw->menu.items; idx > 0 && mip; mip = mip->next) --idx; @@ -336,7 +339,8 @@ DoMenuScroll(int win, int blocking, int how, menu_item **retmip) nw->Screen = HackScreen; if (win == WIN_INVEN) { - sprintf(title, "%s the %s's Inventory", svp.plname, svp.pl_character); + Snprintf(title, sizeof title, "%s the %s's Inventory", + svp.plname, svp.pl_character); nw->Title = title; if (lastinvent.MaxX != 0) { nw->LeftEdge = lastinvent.MinX; @@ -606,7 +610,8 @@ DoMenuScroll(int win, int blocking, int how, menu_item **retmip) if (how == PICK_ANY) { amip = cw->menu.items; while (amip) { - if (amip->selected) { + if (amip->canselect && amip->selector + && amip->selected) { amip->selected = FALSE; amip->count = -1; amip->str[SOFF + 2] = '-'; @@ -738,8 +743,9 @@ DoMenuScroll(int win, int blocking, int how, menu_item **retmip) } else { reset_counting = TRUE; } - sprintf(countString, "Count: %d", count); - pline(countString); + Snprintf(countString, sizeof countString, + "Count: %ld", count); + pline("%s", countString); } } else if (code == CTRL('D') || code == CTRL('U') || code == MENU_NEXT_PAGE @@ -761,7 +767,7 @@ DoMenuScroll(int win, int blocking, int how, menu_item **retmip) if (code == MENU_FIRST_PAGE) { topidx = 0; } else if (code == MENU_LAST_PAGE) { - topidx = cw->maxrow - wheight; + topidx = max(0, cw->maxrow - wheight); } else for (i = 0; i < endcnt; ++i) { if (code == CTRL('D') || code == MENU_NEXT_PAGE) { @@ -851,6 +857,8 @@ DoMenuScroll(int win, int blocking, int how, menu_item **retmip) } else { int selected = FALSE; for (amip = cw->menu.items; amip; amip = amip->next) { + if (!amip->canselect) + continue; if (amip->selector == code) { if (how == PICK_ONE) aredone = 1; @@ -905,6 +913,8 @@ DoMenuScroll(int win, int blocking, int how, menu_item **retmip) aredone = 1; for (gd = w->FirstGadget; gd && gd->GadgetID != 1;) gd = gd->NextGadget; + if (!gd) + break; pip = (struct PropInfo *) gd->SpecialInfo; totalvis = CountLines(win); @@ -917,6 +927,8 @@ DoMenuScroll(int win, int blocking, int how, menu_item **retmip) case MOUSEMOVE: for (gd = w->FirstGadget; gd && gd->GadgetID != 1;) gd = gd->NextGadget; + if (!gd) + break; pip = (struct PropInfo *) gd->SpecialInfo; totalvis = CountLines(win); diff --git a/sys/amiga/winfuncs.c b/sys/amiga/winfuncs.c index f2ffdb641..e318f3dfa 100644 --- a/sys/amiga/winfuncs.c +++ b/sys/amiga/winfuncs.c @@ -164,23 +164,23 @@ amii_destroy_nhwindow(winid win) /* just hide */ WIN_OVER = WIN_ERR; } } else if (cw->type == NHW_OVER) { - struct Window *w = amii_wins[WIN_OVER]->win; - amii_oldover.MinX = w->LeftEdge; - amii_oldover.MinY = w->TopEdge; - amii_oldover.MaxX = w->Width; - amii_oldover.MaxY = w->Height; + struct Window *w = cw->win; + if (w) { + amii_oldover.MinX = w->LeftEdge; + amii_oldover.MinY = w->TopEdge; + amii_oldover.MaxX = w->Width; + amii_oldover.MaxY = w->Height; - if (WIN_MESSAGE != WIN_ERR && amii_wins[WIN_MESSAGE]) { - w = amii_wins[WIN_MESSAGE]->win; - amii_oldmsg.MinX = w->LeftEdge; - amii_oldmsg.MinY = w->TopEdge; - amii_oldmsg.MaxX = w->Width; - amii_oldmsg.MaxY = w->Height; - SizeWindow(amii_wins[WIN_MESSAGE]->win, - (amiIDisplay->xpix - - amii_wins[WIN_MESSAGE]->win->LeftEdge) - - amii_wins[WIN_MESSAGE]->win->Width, - 0); + if (WIN_MESSAGE != WIN_ERR && amii_wins[WIN_MESSAGE] + && (w = amii_wins[WIN_MESSAGE]->win) != NULL) { + amii_oldmsg.MinX = w->LeftEdge; + amii_oldmsg.MinY = w->TopEdge; + amii_oldmsg.MaxX = w->Width; + amii_oldmsg.MaxY = w->Height; + SizeWindow(w, + (amiIDisplay->xpix - w->LeftEdge) - w->Width, + 0); + } } } } @@ -359,6 +359,9 @@ amii_create_nhwindow(int type) panic("no memory for msg port"); } + if (type < 0 || type > NHW_OVER) + panic("bad type %d in create_nhwindow", type); + nw = &new_wins[type].newwin; nw->Width = amiIDisplay->xpix; nw->Screen = HackScreen; @@ -915,7 +918,7 @@ amii_init_nhwindows(int *argcp, char **argv) for (t = 1; t <= lclargc; t++) { if (!strcmp("-L", *argv_in) || !strcmp("-l", *argv_in)) { - bigscreen = (*argv_in[1] == 'l') ? -1 : 1; + bigscreen = ((*argv_in)[1] == 'l') ? -1 : 1; /* and eat the flag */ (*argcp)--; } else { @@ -1123,12 +1126,16 @@ amii_init_nhwindows(int *argcp, char **argv) SM_FilterHook.h_Data = 0; SM_FilterHook.h_SubEntry = 0; SMR = AllocAslRequest(ASL_ScreenModeRequest, NULL); - if (AslRequestTags(SMR, ASLSM_FilterFunc, (ULONG) &SM_FilterHook, - TAG_END)) - amii_scrnmode = SMR->sm_DisplayID; - else + if (SMR) { + if (AslRequestTags(SMR, ASLSM_FilterFunc, + (ULONG) &SM_FilterHook, TAG_END)) + amii_scrnmode = SMR->sm_DisplayID; + else + amii_scrnmode = 0; + FreeAslRequest(SMR); + } else { amii_scrnmode = 0; - FreeAslRequest(SMR); + } } if (forcenobig == 0) { diff --git a/sys/amiga/winreq.c b/sys/amiga/winreq.c index fb87db8bc..4b3056838 100644 --- a/sys/amiga/winreq.c +++ b/sys/amiga/winreq.c @@ -704,6 +704,9 @@ DispCol(struct Window *w, int idx, UWORD *colors) char buf[50]; char *colname, *defval; + if (idx < 0 || idx >= amii_numcolors) + return; + if (WINVERS_AMIV) { colname = amiv_colnames[idx].name; defval = amiv_colnames[idx].defval; From 4e63ba90ca70d4cbd881af1345acaf591426101b Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:28:43 +0200 Subject: [PATCH 11/15] Amiga: bound BufferQueueChar; move CloseWindow out of Forbid() Make the BufferQueueChar macro bounds-check KbdBuffered against KBDBUFFER internally so the RAWKEY and NEWSIZE 'R'-64 paths can no longer push past the 10-byte queue; widen KbdBuffered to int so the counter cannot wrap silently in the queue-scan loops. In amii_cleanup move kill_nhwindows()/DeleteMsgPort() outside the Forbid()/Permit() pair: CloseWindow can wait on layers.library semaphores on OS 3.x and that is unsafe under Forbid. Keep only the IDCMP-flush loop inside. --- sys/amiga/amiwind.c | 7 ++++--- sys/amiga/winext.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/sys/amiga/amiwind.c b/sys/amiga/amiwind.c index d259d5128..c3c1b3394 100644 --- a/sys/amiga/amiwind.c +++ b/sys/amiga/amiwind.c @@ -19,7 +19,8 @@ static struct Message *GetFMsg(struct MsgPort *); static int BufferGetchar(void); void ProcessMessage(struct IntuiMessage *message); -#define BufferQueueChar(ch) (KbdBuffer[KbdBuffered++] = (ch)) +#define BufferQueueChar(ch) \ + do { if (KbdBuffered < KBDBUFFER) KbdBuffer[KbdBuffered++] = (ch); } while (0) struct Device *ConsoleDevice = NULL; @@ -51,7 +52,7 @@ struct Library *DiskfontBase; #define KBDBUFFER 10 static unsigned char KbdBuffer[KBDBUFFER]; -unsigned char KbdBuffered; +int KbdBuffered; #ifdef HACKFONT @@ -609,10 +610,10 @@ amii_cleanup(void) Forbid(); while (msg = (struct IntuiMessage *) GetMsg(HackPort)) ReplyMsg((struct Message *) msg); + Permit(); kill_nhwindows(1); DeleteMsgPort(HackPort); HackPort = NULL; - Permit(); } /* Close the screen, under v37 or greater it is a pub screen and there may diff --git a/sys/amiga/winext.h b/sys/amiga/winext.h index 0ceff1d6b..adfd83cd6 100644 --- a/sys/amiga/winext.h +++ b/sys/amiga/winext.h @@ -63,7 +63,7 @@ extern struct Menu HackMenu[]; extern struct Menu *MenuStrip; extern struct NewMenu GTHackMenu[]; extern APTR *VisualInfo; -extern unsigned char KbdBuffered; +extern int KbdBuffered; extern struct TextFont *TextsFont; extern struct TextFont *HackFont; extern struct IOStdReq ConsoleIO; From 459113a48e729df3ad0f1c81698bfa37dea7343d Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:29:37 +0200 Subject: [PATCH 12/15] Amiga: fix --more-- infinite loop on overlong words When a single word exceeds the visible message-window width the wrap loop found no whitespace, called outmore(cw), and continued without advancing str -- and on the next iteration curx==0 took it straight back to the same spot. Force-break the word at the column boundary when we are already at the start of a line. Also reset the wrapping static flag to 0 after the NHW_BASE wrap cleanup runs, so the cleanup fires once after a wrap instead of on every subsequent putstr. --- sys/amiga/winstr.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/sys/amiga/winstr.c b/sys/amiga/winstr.c index 2f7329467..9d3e207f4 100644 --- a/sys/amiga/winstr.c +++ b/sys/amiga/winstr.c @@ -125,8 +125,25 @@ amii_putstr(winid window, int attr, const char *str) p = (char *) str; if (p == str) { - /* p = (char *)&str[ cw->cols ]; */ - outmore(cw); + /* No whitespace within visible width. */ + if (cw->curx > 0) { + /* Mid-line: clear it and retry at column 0. */ + outmore(cw); + continue; + } + /* Already at line start: word is longer than one + * line, so force-break it at the column boundary. */ + i = cw->cols - 1 - fudge; + if (i <= 0) + i = 1; + if ((size_t) i > strlen(str)) + i = strlen(str); + outsubstr(cw, (char *) str, i, fudge); + cw->curx += i; + str += i; + if (*str) + amii_scrollmsg(w, cw); + amii_cl_end(cw, cw->curx); continue; } @@ -205,6 +222,7 @@ amii_putstr(winid window, int attr, const char *str) TextSpaces(w->RPort, cw->cols); cw->cury--; } + wrapping = 0; } amii_curs(window, cw->curx + 1, cw->cury); Text(w->RPort, (char *) str, strlen((char *) str)); From 146fbf20d8aa4cda608365628694bd152e118126 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:30:48 +0200 Subject: [PATCH 13/15] Amiga: clamp amii_numcolors and guard tile CMAP loop Reject tile/tomb IFF files whose nPlanes field exceeds DEPTH: the CMAP loop writes 1<= 7 would corrupt adjacent BSS. After OpenScreen succeeds, clamp amii_numcolors to the actually populated portion of the init-map arrays (AMII_PALETTE_SIZE for text mode, AMIV_PALETTE_SIZE for tile mode). On a 64-color screen this stops LoadRGB4 from loading the zero-initialized tail entries as black. Replace the matching magic 32 in the tilefile selection with AMIV_PALETTE_SIZE. While there, add the (char) cast on amii_glyph_buffer's truncating assignment to make the contract explicit. --- sys/amiga/winchar.c | 8 +++++++- sys/amiga/winfuncs.c | 9 +++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/sys/amiga/winchar.c b/sys/amiga/winchar.c index 9636ba3e7..ed486c70e 100644 --- a/sys/amiga/winchar.c +++ b/sys/amiga/winchar.c @@ -130,6 +130,12 @@ ReadImageFile(const char *filename, struct BitMap **bmp) bmhd = (BitMapHeader *) prop->sp_Data; np = bmhd->nPlanes; + if (np > DEPTH) { + errfmt = "%s: too many bitplanes (code %ld)"; + errcode = np; + goto cleanup; + } + /* Load CMAP into palette arrays if present */ prop = FindProp(iff, ID_BMAP, ID_CMAP); if (prop) { @@ -819,7 +825,7 @@ amii_lprint_glyph(winid window, int color_index, int glyph) /* * Add it to the end of the buffer */ - amii_glyph_buffer[glyph_buffer_index++] = glyph; + amii_glyph_buffer[glyph_buffer_index++] = (char) glyph; amii_g_nodes[glyph_node_index - 1].len++; } else { /* See if we're out of glyph nodes */ diff --git a/sys/amiga/winfuncs.c b/sys/amiga/winfuncs.c index e318f3dfa..c5d997ef6 100644 --- a/sys/amiga/winfuncs.c +++ b/sys/amiga/winfuncs.c @@ -1219,9 +1219,9 @@ amii_init_nhwindows(int *argcp, char **argv) if (WINVERS_AMIV) { extern char *tilefile; - if (amii_numcolors >= 32) { + if (amii_numcolors >= AMIV_PALETTE_SIZE) { tilefile = (char *) fqname("tiles/tiles32.iff", DATAPREFIX, 0); - amii_numcolors = 32; + amii_numcolors = AMIV_PALETTE_SIZE; } else { tilefile = (char *) fqname("tiles/tiles16.iff", DATAPREFIX, 0); } @@ -1257,6 +1257,11 @@ amii_init_nhwindows(int *argcp, char **argv) Abort(AN_OpenScreen & ~AT_DeadEnd); } amii_numcolors = 1UL << NewHackScreen.Depth; + { + int palmax = WINVERS_AMIV ? AMIV_PALETTE_SIZE : AMII_PALETTE_SIZE; + if (amii_numcolors > palmax) + amii_numcolors = palmax; + } if (HackScreen->Height > 300 && forcenobig == 0) bigscreen = 1; else From 89971a5fc97b0ecf688e30b42ee794b80ac873a1 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:31:44 +0200 Subject: [PATCH 14/15] Amiga: fix extended-command menu mouse-pick The amii_get_ext_cmd menu used the first character of each command as the item identity (id.a_char) and then linearly searched extcmdlist for the first command starting with that character. Many commands share a first letter, so picking #airlevel returned #adjust, #wipe returned #wear, etc. Store the actual index in id.a_int and read it back directly. While in that function, size obufp at BUFSZ (was 100) and replace the unbounded strcpy from extcmdlist[i].ef_txt with strncpy + explicit NUL. --- sys/amiga/winami.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sys/amiga/winami.c b/sys/amiga/winami.c index 6fdd95b55..631af1771 100644 --- a/sys/amiga/winami.c +++ b/sys/amiga/winami.c @@ -375,7 +375,7 @@ amii_get_ext_cmd(void) int bottom = 0; struct Window *w; - char obufp[100]; + char obufp[BUFSZ]; char *bufp = obufp; int c; int com_index, oindex; @@ -417,7 +417,7 @@ amii_get_ext_cmd(void) amii_start_menu(win, MENU_BEHAVE_STANDARD); for (i = 0; extcmdlist[i].ef_txt != NULL; ++i) { - id.a_char = extcmdlist[i].ef_txt[0]; + id.a_int = i; sprintf(buf, "%-10s - %s ", extcmdlist[i].ef_txt, extcmdlist[i].ef_desc); amii_add_menu(win, (const glyph_info *) 0, &id, @@ -432,16 +432,14 @@ amii_get_ext_cmd(void) if (sel == 0) { return (-1); } else { - sel = mip->item.a_char; - for (i = 0; extcmdlist[i].ef_txt != NULL; ++i) { - if (sel == extcmdlist[i].ef_txt[0]) - break; - } + i = mip->item.a_int; /* copy in the text */ if (extcmdlist[i].ef_txt != NULL) { amii_clear_nhwindow(WIN_MESSAGE); - strcpy(bufp = obufp, extcmdlist[i].ef_txt); + strncpy(obufp, extcmdlist[i].ef_txt, sizeof(obufp) - 1); + obufp[sizeof(obufp) - 1] = '\0'; + bufp = obufp; (void) put_ext_cmd(obufp, colx, cw, bottom); return (i); } else From 6736f878aafee238a7c17250a345a3bccb504a67 Mon Sep 17 00:00:00 2001 From: Ingo Paschke Date: Tue, 12 May 2026 15:33:29 +0200 Subject: [PATCH 15/15] Amiga: graphical tombstone RTG-screen fallback amii_outrip relies on LoadRGB4/transpalette fade and raw BltBitMap to a SMART_REFRESH window -- chipset-era idioms that do not reach the visible display on Picasso96 or CyberGraphX screens. On RTG the screen stayed black and the user saw nothing between the death messages and the high-score list. Detect RTG by screen size > 800x600 and fall through to genl_outrip so RTG users get the ASCII tombstone instead. Switch the still-graphical path to BltBitMapRastPort so the blit goes through the layer system, and move CloseWindow(ripwin) outside the Forbid()/Permit() pair (same fix as amii_cleanup). Rename cmap_white/cmap_black to cmap_outline/cmap_fill -- those variables actually hold the indices of the darkest and lightest palette entries, used for the four offset outline strokes and the centered fill stroke respectively; the old names were backwards. --- sys/amiga/amirip.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/sys/amiga/amirip.c b/sys/amiga/amirip.c index 6c23a4f6c..5ccb9aac1 100644 --- a/sys/amiga/amirip.c +++ b/sys/amiga/amirip.c @@ -81,7 +81,7 @@ static struct NewWindow newwin = { 0, 0, 640, 200, 1, 0, int wh; /* was local in outrip, but needed for SCALE macro */ -int cmap_white, cmap_black; +int cmap_outline, cmap_fill; void amii_outrip(winid tmpwin, int how, time_t when) @@ -99,6 +99,16 @@ amii_outrip(winid tmpwin, int how, time_t when) if (!WINVERS_AMIV || HackScreen->RastPort.BitMap->Depth < 4) goto cleanup; + /* The graphical tombstone uses raw chipset-style palette twiddling + * (LoadRGB4, transpalette fade) and BltBitMap to a SMART_REFRESH + * window. That works on the native chipset and AGA, but on RTG + * setups (Picasso96, CyberGraphX) the visible display is decoupled + * from the chipset registers and the tombstone never appears. + * Detect RTG by screen size beyond what chipset modes can reach + * and fall through to the plain ASCII tombstone instead. */ + if (HackScreen->Width > 800 || HackScreen->Height > 600) + goto cleanup; + /* Use the users display size */ newwin.Height = amiIDisplay->ypix - newwin.TopEdge; newwin.Width = amiIDisplay->xpix; @@ -132,11 +142,11 @@ amii_outrip(winid tmpwin, int how, time_t when) for (i = 0; i < SIZE(cols); i++) cols[i] = cols_base[i] + xoff; - cmap_white = search_cmap(0, 0, 0); - cmap_black = search_cmap(15, 15, 15); + cmap_outline = search_cmap(0, 0, 0); + cmap_fill = search_cmap(15, 15, 15); - BltBitMap(tombimg, 0, 0, rp->BitMap, xoff, yoff, tomb_bmhd.w, tomb_bmhd.h, - 0xc0, 0xff, NULL); + BltBitMapRastPort(tombimg, 0, 0, rp, xoff, yoff, + tomb_bmhd.w, tomb_bmhd.h, 0xc0); /* Put together death description */ formatkiller(buf, sizeof buf, how, FALSE); @@ -254,8 +264,8 @@ cleanup: Forbid(); while (imsg = (struct IntuiMessage *) GetMsg(ripwin->UserPort)) ReplyMsg((struct Message *) imsg); - CloseWindow(ripwin); Permit(); + CloseWindow(ripwin); } LoadRGB4(&HackScreen->ViewPort, sysflags.amii_curmap, amii_numcolors); @@ -279,23 +289,23 @@ tomb_text(char *p) sprintf(buf, " %s ", p); l = TextLength(rp, buf, strlen(buf)); - SetAPen(rp, cmap_white); + SetAPen(rp, cmap_outline); Move(rp, cols[cno] - (l / 2) - 1, tomb_line); Text(rp, buf, strlen(buf)); - SetAPen(rp, cmap_white); + SetAPen(rp, cmap_outline); Move(rp, cols[cno] - (l / 2) + 1, tomb_line); Text(rp, buf, strlen(buf)); - SetAPen(rp, cmap_white); + SetAPen(rp, cmap_outline); Move(rp, cols[cno] - (l / 2), tomb_line - 1); Text(rp, buf, strlen(buf)); - SetAPen(rp, cmap_white); + SetAPen(rp, cmap_outline); Move(rp, cols[cno] - (l / 2), tomb_line + 1); Text(rp, buf, strlen(buf)); - SetAPen(rp, cmap_black); + SetAPen(rp, cmap_fill); Move(rp, cols[cno] - (l / 2), tomb_line); Text(rp, buf, strlen(buf)); }