From 7bdbe3290e6d2ad868562cbde8dd86d64b8578ed Mon Sep 17 00:00:00 2001 From: Alistair-Afton Date: Tue, 15 Sep 2026 19:08:06 +0200 Subject: [PATCH 1/2] Normalize out-of-range pen colors from Lua COLOR_RESET (-1) in a Lua pen table was stored raw into Pen.fg/bg/tile_fg/ tile_bg, then reinterpreted as uint8_t (255) in doSetTile_char and used to index the 16-entry uccolor array. The launcher hits this via TextAreaContent pens that use bg=COLOR_RESET. Decode now substitutes the Pen defaults (grey fg, black bg) for any out-of-range color, and doSetTile_char does the same for pens from other sources before indexing uccolor. Fixes #5865 --- docs/changelog.txt | 1 + library/LuaApi.cpp | 10 ++++++++++ library/modules/Screen.cpp | 10 ++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index ff3943a28c..371bd9ba4e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -62,6 +62,7 @@ Template for new versions: ## Fixes - Fix broken weather lookup in ``World::ReadCurrentWeather`` - `timestream`: deal properly with units who have breathing difficulties +- Fix out-of-bounds color table access when Lua pens use ``COLOR_RESET`` ## Misc Improvements - Added ``Coord2d`` and ``Coord3d`` C++ templates, providing a standard set operations for 2-tuples and 3-tuples of any numeric type diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 92f68d7202..2a1ff44a0c 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -238,8 +238,14 @@ static void decode_pen(lua_State *L, Pen &pen, int idx) get_char_field(L, &pen.ch, idx, "ch", 0); + // COLOR_RESET only has meaning on text consoles; substitute the Pen + // defaults so out-of-range colors can't reach the screen buffers get_int_field(L, &pen.fg, idx, "fg", 7); + if (pen.fg < 0 || pen.fg > COLOR_MAX) + pen.fg = COLOR_GREY; get_int_field(L, &pen.bg, idx, "bg", 0); + if (pen.bg < 0 || pen.bg > COLOR_MAX) + pen.bg = COLOR_BLACK; lua_getfield(L, idx, "bold"); if (lua_isnil(L, -1)) @@ -253,7 +259,11 @@ static void decode_pen(lua_State *L, Pen &pen, int idx) get_int_or_closure_field(L, &pen.tile, idx, "tile", 0); bool tcolor = get_int_field(L, &pen.tile_fg, idx, "tile_fg", 7); + if (pen.tile_fg < 0 || pen.tile_fg > COLOR_MAX) + pen.tile_fg = COLOR_GREY; tcolor = get_int_field(L, &pen.tile_bg, idx, "tile_bg", 0) || tcolor; + if (pen.tile_bg < 0 || pen.tile_bg > COLOR_MAX) + pen.tile_bg = COLOR_BLACK; if (tcolor) pen.tile_mode = Pen::TileColor; diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index c5451ce806..e99390142b 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -179,17 +179,19 @@ static bool doSetTile_char(const Pen &pen, int x, int y, bool use_graphics) *flag &= flag_mask; } - uint8_t fg = pen.fg | (pen.bold << 3); - uint8_t bg = pen.bg; + // COLOR_RESET only has meaning on text consoles; use the Pen defaults + // (grey text on black) so out-of-range colors can't reach the buffers + uint8_t fg = (pen.fg < 0 ? COLOR_GREY : pen.fg) | (pen.bold << 3); + uint8_t bg = pen.bg < 0 ? COLOR_BLACK : pen.bg; if (pen.tile_mode == Screen::Pen::CharColor) *flag |= 2; // SCREENTEXPOS_FLAG_ADDCOLOR else if (pen.tile_mode == Screen::Pen::TileColor) { *flag |= 1; // SCREENTEXPOS_FLAG_GRAYSCALE if (pen.tile_fg) - fg = pen.tile_fg; + fg = pen.tile_fg < 0 ? COLOR_GREY : pen.tile_fg; if (pen.tile_bg) - bg = pen.tile_bg; + bg = pen.tile_bg < 0 ? COLOR_BLACK : pen.tile_bg; } if (pen.tile && use_graphics) { From 31d2cbfe0b4450ef348749d8694f1b0cf4825921 Mon Sep 17 00:00:00 2001 From: Alistair-Afton Date: Tue, 15 Sep 2026 23:19:13 +0200 Subject: [PATCH 2/2] Screen: clamp pen colors above COLOR_MAX as well Pen colors that exceed COLOR_MAX now fall back to the same defaults as negative ones, so no out-of-range value can reach the screen buffers. --- library/modules/Screen.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index e99390142b..133caa82b2 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -179,19 +179,21 @@ static bool doSetTile_char(const Pen &pen, int x, int y, bool use_graphics) *flag &= flag_mask; } - // COLOR_RESET only has meaning on text consoles; use the Pen defaults - // (grey text on black) so out-of-range colors can't reach the buffers - uint8_t fg = (pen.fg < 0 ? COLOR_GREY : pen.fg) | (pen.bold << 3); - uint8_t bg = pen.bg < 0 ? COLOR_BLACK : pen.bg; + // out-of-range colors (including COLOR_RESET, which only has meaning + // on text consoles) get the Pen defaults, grey text on black, so + // they can't reach the buffers + uint8_t fg = (pen.fg < 0 || pen.fg > COLOR_MAX ? COLOR_GREY : pen.fg) + | (pen.bold << 3); + uint8_t bg = pen.bg < 0 || pen.bg > COLOR_MAX ? COLOR_BLACK : pen.bg; if (pen.tile_mode == Screen::Pen::CharColor) *flag |= 2; // SCREENTEXPOS_FLAG_ADDCOLOR else if (pen.tile_mode == Screen::Pen::TileColor) { *flag |= 1; // SCREENTEXPOS_FLAG_GRAYSCALE if (pen.tile_fg) - fg = pen.tile_fg < 0 ? COLOR_GREY : pen.tile_fg; + fg = pen.tile_fg < 0 || pen.tile_fg > COLOR_MAX ? COLOR_GREY : pen.tile_fg; if (pen.tile_bg) - bg = pen.tile_bg < 0 ? COLOR_BLACK : pen.tile_bg; + bg = pen.tile_bg < 0 || pen.tile_bg > COLOR_MAX ? COLOR_BLACK : pen.tile_bg; } if (pen.tile && use_graphics) {