From 1edf07f35104ca22262dbc63796b244688c11f72 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:29:12 -0400 Subject: [PATCH 01/12] Fix read out-of-bounds when accumulating escape sequence --- .gitignore | 1 + source/arm9/console.c | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d988f2f..4a76e53 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ docs warn.log *.bz2 *~ +*.json \ No newline at end of file diff --git a/source/arm9/console.c b/source/arm9/console.c index 77b0a91..fc24557 100644 --- a/source/arm9/console.c +++ b/source/arm9/console.c @@ -321,7 +321,7 @@ ssize_t con_write(struct _reent *r,void *fd,const char *ptr, size_t len) { escaping = false; break; } - } while (escaping); + } while (escaping && i < len); continue; } From c27173427846256149cdf56cb1b92a75584804dc Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Wed, 6 Aug 2025 22:45:35 -0400 Subject: [PATCH 02/12] Properly handle ANSI terminal escape codes for color/style mode-setting --- .clang-format | 57 ++++++++++++ arm9/Makefile | 1 + source/arm9/console-utils.c | 181 ++++++++++++++++++++++++++++++++++++ source/arm9/console.c | 124 ++++-------------------- 4 files changed, 257 insertions(+), 106 deletions(-) create mode 100644 .clang-format create mode 100644 source/arm9/console-utils.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..d5f21cc --- /dev/null +++ b/.clang-format @@ -0,0 +1,57 @@ +BasedOnStyle: LLVM +MaxEmptyLinesToKeep: 1 +ColumnLimit: 120 +ExperimentalAutoDetectBinPacking: true + +# Pack +PackConstructorInitializers: Never +BinPackParameters: false +BinPackArguments: false + +# Indentation +UseTab: Always +IndentWidth: 4 +ContinuationIndentWidth: 4 +TabWidth: 4 +AccessModifierOffset: -4 +IndentCaseBlocks: false +IndentCaseLabels: false + +# Namespace +CompactNamespaces: true +NamespaceIndentation: None +FixNamespaceComments: true + +# Allow +AllowShortLambdasOnASingleLine: Inline +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false + +# Align +AlignConsecutiveShortCaseStatements: + Enabled: true + AcrossEmptyLines: true + AcrossComments: true + AlignCaseColons: false +AlignAfterOpenBracket: AlwaysBreak +AlignEscapedNewlines: Left + +# Break +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +# BreakConstructorInitializers: AfterColon +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true + +# Insert +InsertBraces: false +InsertNewlineAtEOF: true + +# Remove +RemoveBracesLLVM: true +KeepEmptyLines: + AtStartOfFile: false + AtStartOfBlock: false + AtEndOfFile: false diff --git a/arm9/Makefile b/arm9/Makefile index 4599ffe..e642a55 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -74,6 +74,7 @@ export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CFILES += console-utils.c SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.bin))) PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) diff --git a/source/arm9/console-utils.c b/source/arm9/console-utils.c new file mode 100644 index 0000000..9e793b7 --- /dev/null +++ b/source/arm9/console-utils.c @@ -0,0 +1,181 @@ +/*--------------------------------------------------------------------------------- + +Copyright (C) 2025 +trustytrojan + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you +must not claim that you wrote the original software. If you use +this software in a product, an acknowledgment in the product +documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source +distribution. + +---------------------------------------------------------------------------------*/ +#include +#include +#include + +// from console.c +extern PrintConsole *currentConsole; +void consoleCls(char mode); +void consoleClearLine(char mode); + +static void updateColorBright(const int param, int *const color, int *const bright) { + if (param == 0) { // Reset + *color = 15; // bright white + *bright = 0; + } else if (param == 1) { // Bright/bold + *bright = 1; + } else if (param >= 30 && param <= 37) { // fg color + *color = param - 30; + } else if (param >= 40 && param <= 47) { // bg color + *color = param - 40; + } else if (param >= 90 && param <= 97) { // bright fg color + *color = param - 90 + 8; + } else if (param >= 100 && param <= 107) { // bright bg color + *color = param - 100 + 8; + } else if (param == 39 || param == 49) { // Default color + *color = 15; // bright white + } +} + +static void consoleParseColor(const char *escapeseq, int escapelen) { + // Special case: \x1b[m resets attributes + if (*escapeseq == 'm') { + currentConsole->fontCurPal = 15 << 12; // Default color (bright white) + return; + } + + // truecolor rgb sequences `ESC[38;2;{r};{g};{b}m` will simply be ignored + if (siscanf(escapeseq, "38;2;%*d;%*d;%*dm") > 0) + return; + + unsigned id; + // 256 color format sequence `ESC[38;5;{ID}m` + if (siscanf(escapeseq, "38;5;%um", &id) > 0) { + if (id <= 15) + currentConsole->fontCurPal = id << 12; + return; + } + + int color = -1, bright = 0; + const char *p = escapeseq; + int consumed; + int param; + + while ((consumed = siscanf(p, "%d;", ¶m)) > 0) { + p += consumed; + updateColorBright(param, &color, &bright); + } + + if (siscanf(p, "%dm", ¶m) > 0) + updateColorBright(param, &color, &bright); + + int final_param = -1; + if (color != -1) { + final_param = color; + if (bright && final_param < 8) + final_param += 8; + } else if (bright) { + // if only intensity is set, brighten the current color + int current_color = (currentConsole->fontCurPal >> 12); + if (current_color < 8) + final_param = current_color + 8; + else + final_param = current_color; + } + + if (final_param != -1) + currentConsole->fontCurPal = final_param << 12; +} + +int consoleParseEscapeSequence(const char *ptr, int len) { + char chr; + const char *escapeseq = ptr; + int escapelen = 0; + int parameter; + + do { + chr = *(ptr++); + escapelen++; + + switch (chr) { + // Cursor directional movement + case 'A': + if (sscanf(escapeseq, "%dA", ¶meter) < 1) + parameter = 1; + currentConsole->cursorY = + (currentConsole->cursorY - parameter) < 0 ? 0 : currentConsole->cursorY - parameter; + return escapelen; + + case 'B': + if (sscanf(escapeseq, "%dB", ¶meter) < 1) + parameter = 1; + currentConsole->cursorY = (currentConsole->cursorY + parameter) > currentConsole->windowHeight - 1 + ? currentConsole->windowHeight - 1 + : currentConsole->cursorY + parameter; + return escapelen; + + case 'C': + if (sscanf(escapeseq, "%dC", ¶meter) < 1) + parameter = 1; + currentConsole->cursorX = (currentConsole->cursorX + parameter) > currentConsole->windowWidth - 1 + ? currentConsole->windowWidth - 1 + : currentConsole->cursorX + parameter; + return escapelen; + + case 'D': + if (sscanf(escapeseq, "%dD", ¶meter) < 1) + parameter = 1; + currentConsole->cursorX = + (currentConsole->cursorX - parameter) < 0 ? 0 : currentConsole->cursorX - parameter; + return escapelen; + + // Cursor position movement + case 'H': + case 'f': + sscanf(escapeseq, "%d;%d", ¤tConsole->cursorY, ¤tConsole->cursorX); + return escapelen; + + // Screen clear + case 'J': + consoleCls(escapeseq[escapelen - 2]); + return escapelen; + + // Line clear + case 'K': + consoleClearLine(escapeseq[escapelen - 2]); + return escapelen; + + // Save cursor position + case 's': + currentConsole->prevCursorX = currentConsole->cursorX; + currentConsole->prevCursorY = currentConsole->cursorY; + return escapelen; + + // Load cursor position + case 'u': + currentConsole->cursorX = currentConsole->prevCursorX; + currentConsole->cursorY = currentConsole->prevCursorY; + return escapelen; + + // Color/style modes + case 'm': + consoleParseColor(escapeseq, escapelen); + return escapelen; + } + } while (escapelen < len); + + // reached end of buffer! tell con_write to NOT add to its counter. + return 0; +} diff --git a/source/arm9/console.c b/source/arm9/console.c index fc24557..f7fc4dd 100644 --- a/source/arm9/console.c +++ b/source/arm9/console.c @@ -79,9 +79,11 @@ PrintConsole* consoleGetDefault(void){return &defaultConsole;} void consolePrintChar(char c); +int consoleParseEscapeSequence(const char *ptr, int len); + //--------------------------------------------------------------------------------- -static void consoleCls(char mode) { +void consoleCls(char mode) { //--------------------------------------------------------------------------------- int i = 0; @@ -132,7 +134,7 @@ static void consoleCls(char mode) { } } //--------------------------------------------------------------------------------- -static void consoleClearLine(char mode) { +void consoleClearLine(char mode) { //--------------------------------------------------------------------------------- int i = 0; @@ -212,7 +214,6 @@ ssize_t con_write(struct _reent *r,void *fd,const char *ptr, size_t len) { int i, count = 0; char *tmp = (char*)ptr; - int intensity = 0; if(!tmp || len<=0) return -1; @@ -223,106 +224,17 @@ ssize_t con_write(struct _reent *r,void *fd,const char *ptr, size_t len) { chr = *(tmp++); i++; count++; - if ( chr == 0x1b && *tmp == '[' ) { - bool escaping = true; - char *escapeseq = tmp; - int escapelen = 0; - - do { - chr = *(tmp++); - i++; count++; escapelen++; - int parameter; - - switch (chr) { - ///////////////////////////////////////// - // Cursor directional movement - ///////////////////////////////////////// - case 'A': - siscanf(escapeseq,"[%dA", ¶meter); - currentConsole->cursorY = (currentConsole->cursorY - parameter) < 0 ? 0 : currentConsole->cursorY - parameter; - escaping = false; - break; - case 'B': - siscanf(escapeseq,"[%dB", ¶meter); - currentConsole->cursorY = (currentConsole->cursorY + parameter) > currentConsole->windowHeight - 1 ? currentConsole->windowHeight - 1 : currentConsole->cursorY + parameter; - escaping = false; - break; - case 'C': - siscanf(escapeseq,"[%dC", ¶meter); - currentConsole->cursorX = (currentConsole->cursorX + parameter) > currentConsole->windowWidth - 1 ? currentConsole->windowWidth - 1 : currentConsole->cursorX + parameter; - escaping = false; - break; - case 'D': - siscanf(escapeseq,"[%dD", ¶meter); - currentConsole->cursorX = (currentConsole->cursorX - parameter) < 0 ? 0 : currentConsole->cursorX - parameter; - escaping = false; - break; - ///////////////////////////////////////// - // Cursor position movement - ///////////////////////////////////////// - case 'H': - case 'f': - siscanf(escapeseq,"[%d;%df", ¤tConsole->cursorY , ¤tConsole->cursorX ); - escaping = false; - break; - ///////////////////////////////////////// - // Screen clear - ///////////////////////////////////////// - case 'J': - consoleCls(escapeseq[escapelen-2]); - escaping = false; - break; - ///////////////////////////////////////// - // Line clear - ///////////////////////////////////////// - case 'K': - consoleClearLine(escapeseq[escapelen-2]); - escaping = false; - break; - ///////////////////////////////////////// - // Save cursor position - ///////////////////////////////////////// - case 's': - currentConsole->prevCursorX = currentConsole->cursorX ; - currentConsole->prevCursorY = currentConsole->cursorY ; - escaping = false; - break; - ///////////////////////////////////////// - // Load cursor position - ///////////////////////////////////////// - case 'u': - currentConsole->cursorX = currentConsole->prevCursorX ; - currentConsole->cursorY = currentConsole->prevCursorY ; - escaping = false; - break; - ///////////////////////////////////////// - // Color scan codes - ///////////////////////////////////////// - case 'm': - siscanf(escapeseq,"[%d;%dm", ¶meter, &intensity); - - //only handle 30-37,39 and intensity for the color changes - parameter -= 30; - - //39 is the reset code - if(parameter == 9){ - parameter = 15; - } - else if(parameter > 8){ - parameter -= 2; - } - else if(intensity){ - parameter += 8; - } - if(parameter < 16 && parameter >= 0){ - currentConsole->fontCurPal = parameter << 12; - } - - escaping = false; - break; - } - } while (escaping && i < len); - continue; + if ( chr == 0x1b && i < len && *tmp == '[' ) { + // skip the '[' + tmp++; i++; count++; + // len - i: the REMAINING length in the buffer + int consumed = consoleParseEscapeSequence(tmp, len - i); + if (consumed > 0) { + tmp += consumed; + i += consumed; + count += consumed; + continue; + } } consolePrintChar(chr); @@ -381,12 +293,12 @@ void consoleLoadFont(PrintConsole* console) { const u8* in = (const u8*)console->font.gfx; u32* out = (u32*)console->fontBgGfx; - for (i = 0; i < console->font.numChars * 8; i ++) { + for ( i = 0; i < console->font.numChars * 8; i ++) { unsigned cur = *in++; int j; u32 temp = 0; - for (j = 0; j < 8; j ++) { + for ( j = 0; j < 8; j ++) { temp |= ((cur&1) * 0xf) << (j*4); cur >>= 1; } @@ -408,7 +320,7 @@ void consoleLoadFont(PrintConsole* console) { console->fontCurPal <<= 12; } else { - for (i = 0; i < console->font.numChars * 16; i++) { + for ( i = 0; i < console->font.numChars * 16; i++) { u16 temp = 0; if(console->font.gfx[i] & 0xF) From bf54ea198073c53c99c3760b61fda305c24ae573 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Thu, 7 Aug 2025 07:54:45 -0400 Subject: [PATCH 03/12] Ignore private modes (commonly ending in `h` or `l`) --- source/arm9/console-utils.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source/arm9/console-utils.c b/source/arm9/console-utils.c index 9e793b7..e218f2e 100644 --- a/source/arm9/console-utils.c +++ b/source/arm9/console-utils.c @@ -110,6 +110,11 @@ int consoleParseEscapeSequence(const char *ptr, int len) { escapelen++; switch (chr) { + // Private modes - commonly end in 'h' or 'l'. We won't implement any (yet), so just consume them. + case 'h': + case 'l': + return escapelen; + // Cursor directional movement case 'A': if (sscanf(escapeseq, "%dA", ¶meter) < 1) From 991106457e13babc73dcbe00d8fbbafe02e17f81 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:01:09 -0400 Subject: [PATCH 04/12] fix: properly use `siscanf`, add TODO about this impl's pitfall --- source/arm9/console-utils.c | 69 +++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/source/arm9/console-utils.c b/source/arm9/console-utils.c index e218f2e..68f5296 100644 --- a/source/arm9/console-utils.c +++ b/source/arm9/console-utils.c @@ -25,6 +25,35 @@ distribution. #include #include +/* +TODO, if it becomes important enough: + +don't use the scanf strategy after all, because incomplete sequences written +to consoles will just be shown raw. to do what every modern terminal (emulator) does, +we need to implement a state machine with a buffer. + +as an example: +run `cat`, then press the Esc and Enter keys on your keyboard. this does three things: +1. '\e' is added to cat's input buffer +2. your terminal reacts to Enter and moves the cursor to the next line +3. cat sends its input buffer (containing "\e\n" in C string format) to stdout, + causing your terminal to: + 1. recognize the start of an escape sequence (\e) + 2. move the cursor to the next line (again) (\n) +then type "[36m" without the quotes. on my terminal emulator (foot), the "[36m" +was never shown, and everything i typed afterwards was cyan in color. my terminal +emulator let the \n go through without printing the \e, meaning it did in fact +store the \e in a buffer, or set a flag indicating that "i should check for the rest +of the escape sequence", then processed the "[36m", setting the color to cyan. + +we should recreate this strategy here as well to handle cases where not all of an +escape sequence is passed to a single con_write() call. + +not to mention: "\e[" is just one type of sequence (known as a control sequence introducer, +or CSI), but there are also "\e " sequences as well which can do other things. a state +machine would help here. +*/ + // from console.c extern PrintConsole *currentConsole; void consoleCls(char mode); @@ -56,31 +85,46 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { return; } - // truecolor rgb sequences `ESC[38;2;{r};{g};{b}m` will simply be ignored + // ignore truecolor rgb sequences in the form `ESC[38;2;{r};{g};{b}m` if (siscanf(escapeseq, "38;2;%*d;%*d;%*dm") > 0) return; unsigned id; - // 256 color format sequence `ESC[38;5;{ID}m` + // 256 color format sequence: `ESC[38;5;{ID}m` + // support it, but just use 0-15, ignore everything else. if (siscanf(escapeseq, "38;5;%um", &id) > 0) { if (id <= 15) currentConsole->fontCurPal = id << 12; return; } - int color = -1, bright = 0; + // -1 color means unchanged + int color = -1, bright = 0, items_matched, chars_consumed, param; const char *p = escapeseq; - int consumed; - int param; - while ((consumed = siscanf(p, "%d;", ¶m)) > 0) { - p += consumed; - updateColorBright(param, &color, &bright); - } + // %n does not match anything, it stores the number of characters + // consumed thus far into the next pointer! use this to advance `p`. + + // start consuming arguments, delimited with ';' + do { + items_matched = siscanf(p, "%d;%n", ¶m, &chars_consumed); - if (siscanf(p, "%dm", ¶m) > 0) + // advance p even if no items were matched! + p += chars_consumed; + + if (items_matched) + // the %d got matched, param is valid! + updateColorBright(param, &color, &bright); + } while (items_matched); + + // end of arguments must end with 'm' + if (siscanf(p, "%dm%n", ¶m, &chars_consumed)) + // the %d got matched, param is valid! updateColorBright(param, &color, &bright); + // advance p even if no items were matched! + p += chars_consumed; + int final_param = -1; if (color != -1) { final_param = color; @@ -95,6 +139,11 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { final_param = current_color; } + if (final_param == 0) + // this is black, and the texture is literally just nothing. + // we need a usable console, so make it gray. + final_param += 8; + if (final_param != -1) currentConsole->fontCurPal = final_param << 12; } From 24374ca98914dd55c7b6784c8a7a72761f218c7b Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Fri, 8 Aug 2025 13:12:52 -0400 Subject: [PATCH 05/12] just saving my work, no idea if this is working --- arm9/Makefile | 1 - include/nds/arm9/console.h | 2 + source/arm9/console-cursor.c | 94 ++++++++++++++ .../arm9/{console-utils.c => console-esc.c} | 63 ++++----- source/arm9/console-print.c | 122 ++++++++++++++++++ source/arm9/console-priv.h | 55 ++++++++ source/arm9/console.c | 70 +--------- 7 files changed, 312 insertions(+), 95 deletions(-) create mode 100644 source/arm9/console-cursor.c rename source/arm9/{console-utils.c => console-esc.c} (82%) create mode 100644 source/arm9/console-print.c create mode 100644 source/arm9/console-priv.h diff --git a/arm9/Makefile b/arm9/Makefile index e642a55..4599ffe 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -74,7 +74,6 @@ export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) -CFILES += console-utils.c SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.bin))) PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) diff --git a/include/nds/arm9/console.h b/include/nds/arm9/console.h index 61849e4..78838b6 100644 --- a/include/nds/arm9/console.h +++ b/include/nds/arm9/console.h @@ -168,6 +168,8 @@ typedef struct PrintConsole bool consoleInitialised; /*!< True if the console is initialized */ bool loadGraphics; /*!< True if consoleInit should attempt to load font graphics into background memory */ + + bool echo; // Whether to echo characters written to this console, similar to the ECHO termios attribute. }PrintConsole; diff --git a/source/arm9/console-cursor.c b/source/arm9/console-cursor.c new file mode 100644 index 0000000..d121306 --- /dev/null +++ b/source/arm9/console-cursor.c @@ -0,0 +1,94 @@ +/*--------------------------------------------------------------------------------- + +Copyright (C) 2025 +trustytrojan + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you +must not claim that you wrote the original software. If you use +this software in a product, an acknowledgment in the product +documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source +distribution. + +---------------------------------------------------------------------------------*/ +#include "console-priv.h" + +static u16 fbmvUnderCursor, cursorFbmv; +static bool isCursorShown = true; + +void consoleSetCursorChar(const char c) { + cursorFbmv = consoleComputeFontBgMapValue(c); +} + +void consoleSaveFbmvUnderCursor(void) { + if (!isCursorShown) + fbmvUnderCursor = *consoleFontBgMapAtCursor(); +} + +void consoleRestoreFbmvUnderCursor(void) { + *consoleFontBgMapAtCursor() = fbmvUnderCursor; +} + +static void consoleFlashCursor(TickTask *const _) { + u16 *const fbmAtCursor = consoleFontBgMapAtCursor(); + + if (isCursorShown) { + // "on" state, save the character under the cursor and display the cursor + fbmvUnderCursor = *fbmAtCursor; + *fbmAtCursor = cursorFbmv; + } else { + // "off" state, restore the character under the cursor + *fbmAtCursor = fbmvUnderCursor; + } + + isCursorShown = !isCursorShown; +} + +static TickTask cursorFlashTickTask; + +void consoleStartFlashingCursor(const int frequency) { + tickTaskStart(&cursorFlashTickTask, consoleFlashCursor, ticksFromHz(frequency), ticksFromHz(frequency)); +} + +void consoleStopFlashingCursor(void) { + tickTaskStop(&cursorFlashTickTask); +} + +void consoleSetCursorPos(const int x, const int y) { + consoleRestoreFbmvUnderCursor(); + currentConsole->cursorX = x; + currentConsole->cursorY = y; + consoleSaveFbmvUnderCursor(); +} + +void consoleSetCursorY(const int y) { + consoleSetCursorPos(currentConsole->cursorX, y); +} + +void consoleSetCursorX(const int x) { + consoleSetCursorPos(x, currentConsole->cursorY); +} + +void consoleMoveCursorX(const int dx) { + const PrintConsole *const c = currentConsole; + const int newX = c->cursorX + dx; + const int maxX = c->windowWidth - 1; + consoleSetCursorX((newX > maxX) ? maxX : newX); +} + +void consoleMoveCursorY(const int dy) { + const PrintConsole *const c = currentConsole; + const int newY = c->cursorY + dy; + const int maxY = c->windowHeight - 1; + consoleSetCursorY((newY > maxY) ? maxY : newY); +} diff --git a/source/arm9/console-utils.c b/source/arm9/console-esc.c similarity index 82% rename from source/arm9/console-utils.c rename to source/arm9/console-esc.c index 68f5296..194434a 100644 --- a/source/arm9/console-utils.c +++ b/source/arm9/console-esc.c @@ -21,8 +21,7 @@ must not be misrepresented as being the original software. distribution. ---------------------------------------------------------------------------------*/ -#include -#include +#include "console-priv.h" #include /* @@ -54,11 +53,6 @@ or CSI), but there are also "\e " sequences as well which can do other things. a machine would help here. */ -// from console.c -extern PrintConsole *currentConsole; -void consoleCls(char mode); -void consoleClearLine(char mode); - static void updateColorBright(const int param, int *const color, int *const bright) { if (param == 0) { // Reset *color = 15; // bright white @@ -109,21 +103,20 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { do { items_matched = siscanf(p, "%d;%n", ¶m, &chars_consumed); - // advance p even if no items were matched! - p += chars_consumed; - - if (items_matched) + if (items_matched > 0) { // the %d got matched, param is valid! updateColorBright(param, &color, &bright); - } while (items_matched); + } + + // only advance p if items were matched + p += chars_consumed; + } while (items_matched > 0); // end of arguments must end with 'm' - if (siscanf(p, "%dm%n", ¶m, &chars_consumed)) + if (siscanf(p, "%dm%n", ¶m, &chars_consumed) > 0) { // the %d got matched, param is valid! updateColorBright(param, &color, &bright); - - // advance p even if no items were matched! - p += chars_consumed; + } int final_param = -1; if (color != -1) { @@ -164,42 +157,49 @@ int consoleParseEscapeSequence(const char *ptr, int len) { case 'l': return escapelen; - // Cursor directional movement + // Cursor directional movement (TODO: make these use consoleMoveCursorX/Y instead) case 'A': if (sscanf(escapeseq, "%dA", ¶meter) < 1) parameter = 1; - currentConsole->cursorY = - (currentConsole->cursorY - parameter) < 0 ? 0 : currentConsole->cursorY - parameter; + // currentConsole->cursorY = + // (currentConsole->cursorY - parameter) < 0 ? 0 : currentConsole->cursorY - parameter; + consoleMoveCursorY(-parameter); return escapelen; case 'B': if (sscanf(escapeseq, "%dB", ¶meter) < 1) parameter = 1; - currentConsole->cursorY = (currentConsole->cursorY + parameter) > currentConsole->windowHeight - 1 - ? currentConsole->windowHeight - 1 - : currentConsole->cursorY + parameter; + // currentConsole->cursorY = (currentConsole->cursorY + parameter) > currentConsole->windowHeight - 1 + // ? currentConsole->windowHeight - 1 + // : currentConsole->cursorY + parameter; + consoleMoveCursorY(parameter); return escapelen; case 'C': if (sscanf(escapeseq, "%dC", ¶meter) < 1) parameter = 1; - currentConsole->cursorX = (currentConsole->cursorX + parameter) > currentConsole->windowWidth - 1 - ? currentConsole->windowWidth - 1 - : currentConsole->cursorX + parameter; + // currentConsole->cursorX = (currentConsole->cursorX + parameter) > currentConsole->windowWidth - 1 + // ? currentConsole->windowWidth - 1 + // : currentConsole->cursorX + parameter; + consoleMoveCursorX(parameter); return escapelen; case 'D': if (sscanf(escapeseq, "%dD", ¶meter) < 1) parameter = 1; - currentConsole->cursorX = - (currentConsole->cursorX - parameter) < 0 ? 0 : currentConsole->cursorX - parameter; + // currentConsole->cursorX = + // (currentConsole->cursorX - parameter) < 0 ? 0 : currentConsole->cursorX - parameter; + consoleMoveCursorX(-parameter); return escapelen; // Cursor position movement case 'H': - case 'f': - sscanf(escapeseq, "%d;%d", ¤tConsole->cursorY, ¤tConsole->cursorX); + case 'f': { + int x, y; + sscanf(escapeseq, "%d;%d", &y, &x); + consoleSetCursorPos(x, y); return escapelen; + } // Screen clear case 'J': @@ -219,8 +219,9 @@ int consoleParseEscapeSequence(const char *ptr, int len) { // Load cursor position case 'u': - currentConsole->cursorX = currentConsole->prevCursorX; - currentConsole->cursorY = currentConsole->prevCursorY; + // currentConsole->cursorX = currentConsole->prevCursorX; + // currentConsole->cursorY = currentConsole->prevCursorY; + consoleSetCursorPos(currentConsole->prevCursorX, currentConsole->prevCursorY); return escapelen; // Color/style modes diff --git a/source/arm9/console-print.c b/source/arm9/console-print.c new file mode 100644 index 0000000..e004658 --- /dev/null +++ b/source/arm9/console-print.c @@ -0,0 +1,122 @@ +/*--------------------------------------------------------------------------------- + +Copyright (C) 2025 +trustytrojan + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you +must not claim that you wrote the original software. If you use +this software in a product, an acknowledgment in the product +documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source +distribution. + +---------------------------------------------------------------------------------*/ +#include "console-priv.h" + +u16 *consoleFontBgMapAt(const int x, const int y) { + const PrintConsole *const c = currentConsole; + const int xOffset = c->cursorX + c->windowX; + const int yOffset = (c->cursorY + c->windowY) * c->consoleWidth; + return c->fontBgMap + xOffset + yOffset; +} + +u16 *consoleFontBgMapAtCursor(void) { + const PrintConsole *const c = currentConsole; + return consoleFontBgMapAt(c->cursorX, c->cursorY); +} + +u16 consoleComputeFontBgMapValue(const char ch) { + const PrintConsole *const c = currentConsole; + return c->fontCurPal | (u16)(ch + c->fontCharOffset - c->font.asciiOffset); +} + +// could have a better name, since we aren't always printing a character +void consolePrintChar(const char ch) { + if (!ch) + return; + + PrintConsole *const c = currentConsole; + + if (!c->fontBgMap) + return; + + if (ch + c->fontCharOffset - c->font.asciiOffset > c->font.numChars) + // this prevents weird stuff showing up when printing! + return; + + if (c->PrintChar && c->PrintChar(c, ch)) + return; + + if (c->cursorX >= c->windowWidth && c->echo) { + c->cursorX = 0; + newRow(); + } + + switch (ch) { + case '\a': + // bell character: TODO: add a callback for applications to respond to this. e.g. playing a bell sound + break; + + case '\b': + /* + the old code here actually moved the cursor back one (and up one row + if needed) and then "erased" the character by writing a space to fontBgMap. + + in a linux terminal emulator with the termios attr ICANON off, + backspace does NOT move the cursor. if the ECHO termios attr is on + it prints the \b character (visualized as ^?). + + with ICANON and ECHO on, backspace does NOT wrap the cursor around to the + last row. + + what dkp was trying to do here is emulate "canonical mode" + (ICANON termios attr), which tells the terminal to line-buffer + input from the keyboard before sending it to stdin. but it ALSO + does what WE (the console) were doing here before: erasing the current character + and moving the cursor back. THIS SHOULD BE KEYBOARD.C's JOB! + + SOLUTION: + WE should have an "echo" option, and keyboard.c should have a "line buffer" option. + and when keyboard.c's "line buffering" is on, **it should interface with the + currently selected console** to erase the character at the cursor and then move + the cursor back. + + the responsibilities should NOT be mixed together. + */ + + if (c->echo) + *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue('\b'); + break; + + case '\t': + // c->cursorX += c->tabSize - ((c->cursorX) % (c->tabSize)); + consoleMoveCursorX(c->tabSize - ((c->cursorX) % (c->tabSize))); + break; + + case '\n': + newRow(); + // also return to first column: + + case '\r': + // c->cursorX = 0; + consoleSetCursorX(0); + break; + + default: + // c->fontBgMap[c->cursorX + c->windowX + (c->cursorY + c->windowY) * c->consoleWidth] = + // c->fontCurPal | (u16)(ch + c->fontCharOffset - c->font.asciiOffset); + *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue(ch); + ++c->cursorX; + // consoleMoveCursorX(1); + } +} diff --git a/source/arm9/console-priv.h b/source/arm9/console-priv.h new file mode 100644 index 0000000..7fbe157 --- /dev/null +++ b/source/arm9/console-priv.h @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------- + +Copyright (C) 2025 +trustytrojan + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you +must not claim that you wrote the original software. If you use +this software in a product, an acknowledgment in the product +documentation would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source +distribution. + +---------------------------------------------------------------------------------*/ +#pragma once + +#include +#include +#include + +// console.c +extern PrintConsole *currentConsole; +void consoleCls(char mode); +void consoleClearLine(char mode); +void newRow(); + +// console-print.c +u16 *consoleFontBgMapAt(const int x, const int y); +u16 *consoleFontBgMapAtCursor(void); +u16 consoleComputeFontBgMapValue(char); +void consolePrintChar(char); + +// console-cursor.c +void consoleSaveFbmvUnderCursor(void); +void consoleRestoreFbmvUnderCursor(void); +void consoleMoveCursorX(int dx); // clamps cursorX to windowWidth +void consoleMoveCursorY(int dy); // clamps cursorY to windowHeight +void consoleSetCursorX(int x); +void consoleSetCursorY(int y); +void consoleSetCursorPos(int x, int y); +void consoleSetCursorChar(char); +void consoleStartFlashingCursor(int frequency); +void consoleStopFlashingCursor(void); + +// console-esc.c +int consoleParseEscapeSequence(const char *ptr, int len); diff --git a/source/arm9/console.c b/source/arm9/console.c index f7fc4dd..3fb7670 100644 --- a/source/arm9/console.c +++ b/source/arm9/console.c @@ -36,6 +36,8 @@ distribution. #include #include +#include "console-priv.h" + PrintConsole defaultConsole = { @@ -77,10 +79,6 @@ PrintConsole* currentConsole = ¤tCopy; PrintConsole* consoleGetDefault(void){return &defaultConsole;} -void consolePrintChar(char c); - -int consoleParseEscapeSequence(const char *ptr, int len); - //--------------------------------------------------------------------------------- void consoleCls(char mode) { @@ -443,6 +441,8 @@ PrintConsole* consoleInit(PrintConsole* console, int layer, if(loadGraphics) consoleLoadFont(console); + consoleSetCursorChar(219); + return currentConsole; } @@ -501,9 +501,9 @@ PrintConsole* consoleDemoInit(void) { } //--------------------------------------------------------------------------------- -static void newRow() { +void newRow() { //--------------------------------------------------------------------------------- - + consoleRestoreFbmvUnderCursor(); currentConsole->cursorY ++; @@ -523,64 +523,8 @@ static void newRow() { (' ' + currentConsole->fontCharOffset - currentConsole->font.asciiOffset); } -} - - -//--------------------------------------------------------------------------------- -void consolePrintChar(char c) { -//--------------------------------------------------------------------------------- - if (c==0) return; - if(currentConsole->fontBgMap == 0) return; - - if(currentConsole->PrintChar) - if(currentConsole->PrintChar(currentConsole, c)) - return; - - if(currentConsole->cursorX >= currentConsole->windowWidth) { - currentConsole->cursorX = 0; - - newRow(); - } - - switch(c) { - /* - The only special characters we will handle are tab (\t), carriage return (\r), line feed (\n) - and backspace (\b). - Carriage return & line feed will function the same: go to next line and put cursor at the beginning. - For everything else, use VT sequences. - - Reason: VT sequences are more specific to the task of cursor placement. - The special escape sequences \b \f & \v are archaic and non-portable. - */ - case 8: - currentConsole->cursorX--; - - if(currentConsole->cursorX < 0) { - if(currentConsole->cursorY > 0) { - currentConsole->cursorX = currentConsole->windowX - 1; - currentConsole->cursorY--; - } else { - currentConsole->cursorX = 0; - } - } - - currentConsole->fontBgMap[currentConsole->cursorX + currentConsole->windowX + (currentConsole->cursorY + currentConsole->windowY) * currentConsole->consoleWidth] = currentConsole->fontCurPal | (u16)(' ' + currentConsole->fontCharOffset - currentConsole->font.asciiOffset); - - break; - case 9: - currentConsole->cursorX += currentConsole->tabSize - ((currentConsole->cursorX)%(currentConsole->tabSize)); - break; - case 10: - newRow(); - case 13: - currentConsole->cursorX = 0; - break; - default: - currentConsole->fontBgMap[currentConsole->cursorX + currentConsole->windowX + (currentConsole->cursorY + currentConsole->windowY) * currentConsole->consoleWidth] = currentConsole->fontCurPal | (u16)(c + currentConsole->fontCharOffset - currentConsole->font.asciiOffset); - ++currentConsole->cursorX ; - break; - } + consoleSaveFbmvUnderCursor(); } //--------------------------------------------------------------------------------- From e835c6fd906f96d4ae8c2cb6be5c2ecdffaad01e Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:22:29 -0400 Subject: [PATCH 06/12] all ansi color sequences working! --- include/nds/arm9/console.h | 4 ++ source/arm9/console-cursor.c | 60 +++++++++++--------------- source/arm9/console-esc.c | 46 +++++++++++--------- source/arm9/console-print.c | 82 +++++++++++++++++++++++------------- source/arm9/console-priv.h | 13 +++--- source/arm9/console.c | 56 +++++++++++++----------- 6 files changed, 145 insertions(+), 116 deletions(-) diff --git a/include/nds/arm9/console.h b/include/nds/arm9/console.h index 78838b6..b95c444 100644 --- a/include/nds/arm9/console.h +++ b/include/nds/arm9/console.h @@ -170,6 +170,10 @@ typedef struct PrintConsole bool loadGraphics; /*!< True if consoleInit should attempt to load font graphics into background memory */ bool echo; // Whether to echo characters written to this console, similar to the ECHO termios attribute. + int bg2Id; // ID of bg used for ANSI background colors! + + u16 *fontBg2Map, *fontBg2Gfx; // map & gfx for the 2nd bg + u16 fontCurPal2; // palette for background colors }PrintConsole; diff --git a/source/arm9/console-cursor.c b/source/arm9/console-cursor.c index d121306..56a3fe5 100644 --- a/source/arm9/console-cursor.c +++ b/source/arm9/console-cursor.c @@ -23,52 +23,42 @@ distribution. ---------------------------------------------------------------------------------*/ #include "console-priv.h" -static u16 fbmvUnderCursor, cursorFbmv; -static bool isCursorShown = true; - -void consoleSetCursorChar(const char c) { - cursorFbmv = consoleComputeFontBgMapValue(c); -} - -void consoleSaveFbmvUnderCursor(void) { - if (!isCursorShown) - fbmvUnderCursor = *consoleFontBgMapAtCursor(); +// This saves the foreground and background color of +// whatever tile we just moved on top of. +// fb2mv should always be the full block ASCII character (219). +static u16 fbmvUnderCursor, fb2mvUnderCursor; + +void consoleSaveTileUnderCursor(void) { + fbmvUnderCursor = *consoleFontBgMapAtCursor(); + fb2mvUnderCursor = *consoleFontBg2MapAtCursor(); } -void consoleRestoreFbmvUnderCursor(void) { +void consoleRestoreTileUnderCursor(void) { *consoleFontBgMapAtCursor() = fbmvUnderCursor; + *consoleFontBg2MapAtCursor() = fb2mvUnderCursor; } -static void consoleFlashCursor(TickTask *const _) { - u16 *const fbmAtCursor = consoleFontBgMapAtCursor(); - - if (isCursorShown) { - // "on" state, save the character under the cursor and display the cursor - fbmvUnderCursor = *fbmAtCursor; - *fbmAtCursor = cursorFbmv; - } else { - // "off" state, restore the character under the cursor - *fbmAtCursor = fbmvUnderCursor; - } - - isCursorShown = !isCursorShown; -} - -static TickTask cursorFlashTickTask; +void consoleDrawCursor(void) { + // foreground: this is the character itself. we need to turn it black, + // which would usually be a fontCurPal of (0 << 12). however fbmvUnderCursor + // 99% of the time does not have those bits zeroed inside. (who uses black on black?) + // so we need to zero-out those bits: + // static const u16 BLACK_BITMASK = (u16)(UINT16_MAX << 4) >> 4; + *consoleFontBgMapAtCursor() = 0x0fff & fbmvUnderCursor; -void consoleStartFlashingCursor(const int frequency) { - tickTaskStart(&cursorFlashTickTask, consoleFlashCursor, ticksFromHz(frequency), ticksFromHz(frequency)); -} - -void consoleStopFlashingCursor(void) { - tickTaskStop(&cursorFlashTickTask); + // background: just use bright write (15) + *consoleFontBg2MapAtCursor() = (15 << 12) | consoleComputeFontBg2MapValue(219); } void consoleSetCursorPos(const int x, const int y) { - consoleRestoreFbmvUnderCursor(); + consoleRestoreTileUnderCursor(); currentConsole->cursorX = x; currentConsole->cursorY = y; - consoleSaveFbmvUnderCursor(); + consoleSaveTileUnderCursor(); + + // original tile saved! now let's make the cursor visible with + // white background and black foreground. + consoleDrawCursor(); } void consoleSetCursorY(const int y) { diff --git a/source/arm9/console-esc.c b/source/arm9/console-esc.c index 194434a..1136c4b 100644 --- a/source/arm9/console-esc.c +++ b/source/arm9/console-esc.c @@ -53,22 +53,24 @@ or CSI), but there are also "\e " sequences as well which can do other things. a machine would help here. */ -static void updateColorBright(const int param, int *const color, int *const bright) { +static void updateColorBright(const int param, int *const color, int *const bgcolor, int *const bright) { if (param == 0) { // Reset - *color = 15; // bright white + *color = 15; // fg: bright white + *bgcolor = 0; // bg: black *bright = 0; } else if (param == 1) { // Bright/bold *bright = 1; } else if (param >= 30 && param <= 37) { // fg color *color = param - 30; } else if (param >= 40 && param <= 47) { // bg color - *color = param - 40; + *bgcolor = param - 40; } else if (param >= 90 && param <= 97) { // bright fg color *color = param - 90 + 8; } else if (param >= 100 && param <= 107) { // bright bg color - *color = param - 100 + 8; + *bgcolor = param - 100 + 8; } else if (param == 39 || param == 49) { // Default color - *color = 15; // bright white + *color = 15; // fg: bright white + *bgcolor = 0; // bg: black } } @@ -93,7 +95,7 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { } // -1 color means unchanged - int color = -1, bright = 0, items_matched, chars_consumed, param; + int color = -1, bgcolor = -1, bright = 0, items_matched, chars_consumed, param; const char *p = escapeseq; // %n does not match anything, it stores the number of characters @@ -105,7 +107,7 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { if (items_matched > 0) { // the %d got matched, param is valid! - updateColorBright(param, &color, &bright); + updateColorBright(param, &color, &bgcolor, &bright); } // only advance p if items were matched @@ -115,30 +117,34 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { // end of arguments must end with 'm' if (siscanf(p, "%dm%n", ¶m, &chars_consumed) > 0) { // the %d got matched, param is valid! - updateColorBright(param, &color, &bright); + updateColorBright(param, &color, &bgcolor, &bright); } - int final_param = -1; + // handle cases when only bold (1) modifier is used. this only affects foreground. + // this should simply turn the current color into its bright variant. + int final_color = -1; if (color != -1) { - final_param = color; - if (bright && final_param < 8) - final_param += 8; + final_color = color; + if (bright && final_color < 8) + final_color += 8; } else if (bright) { // if only intensity is set, brighten the current color int current_color = (currentConsole->fontCurPal >> 12); if (current_color < 8) - final_param = current_color + 8; + final_color = current_color + 8; else - final_param = current_color; + final_color = current_color; } - if (final_param == 0) - // this is black, and the texture is literally just nothing. - // we need a usable console, so make it gray. - final_param += 8; + // final_param is a 4-bit integer (0-15). this is placed into the + // last 4 bits of fontCurPal, which is |'d with the character offset. + // this ALSO means we can extract it out of an FBMV by >>'ing 12. + // see consoleComputeFontBgMapValue() for reference. - if (final_param != -1) - currentConsole->fontCurPal = final_param << 12; + if (final_color != -1) + currentConsole->fontCurPal = final_color << 12; + if (bgcolor != -1) + currentConsole->fontCurPal2 = bgcolor << 12; } int consoleParseEscapeSequence(const char *ptr, int len) { diff --git a/source/arm9/console-print.c b/source/arm9/console-print.c index e004658..ac35c62 100644 --- a/source/arm9/console-print.c +++ b/source/arm9/console-print.c @@ -30,16 +30,33 @@ u16 *consoleFontBgMapAt(const int x, const int y) { return c->fontBgMap + xOffset + yOffset; } +u16 *consoleFontBg2MapAt(const int x, const int y) { + const PrintConsole *const c = currentConsole; + const int xOffset = c->cursorX + c->windowX; + const int yOffset = (c->cursorY + c->windowY) * c->consoleWidth; + return c->fontBg2Map + xOffset + yOffset; +} + u16 *consoleFontBgMapAtCursor(void) { const PrintConsole *const c = currentConsole; return consoleFontBgMapAt(c->cursorX, c->cursorY); } +u16 *consoleFontBg2MapAtCursor(void) { + const PrintConsole *const c = currentConsole; + return consoleFontBg2MapAt(c->cursorX, c->cursorY); +} + u16 consoleComputeFontBgMapValue(const char ch) { const PrintConsole *const c = currentConsole; return c->fontCurPal | (u16)(ch + c->fontCharOffset - c->font.asciiOffset); } +u16 consoleComputeFontBg2MapValue(const char ch) { + const PrintConsole *const c = currentConsole; + return c->fontCurPal2 | (u16)(ch + c->fontCharOffset - c->font.asciiOffset); +} + // could have a better name, since we aren't always printing a character void consolePrintChar(const char ch) { if (!ch) @@ -57,8 +74,9 @@ void consolePrintChar(const char ch) { if (c->PrintChar && c->PrintChar(c, ch)) return; - if (c->cursorX >= c->windowWidth && c->echo) { - c->cursorX = 0; + if (c->cursorX >= c->windowWidth) { + // c->cursorX = 0; + consoleSetCursorX(0); newRow(); } @@ -68,31 +86,33 @@ void consolePrintChar(const char ch) { break; case '\b': - /* - the old code here actually moved the cursor back one (and up one row - if needed) and then "erased" the character by writing a space to fontBgMap. - - in a linux terminal emulator with the termios attr ICANON off, - backspace does NOT move the cursor. if the ECHO termios attr is on - it prints the \b character (visualized as ^?). - - with ICANON and ECHO on, backspace does NOT wrap the cursor around to the - last row. - - what dkp was trying to do here is emulate "canonical mode" - (ICANON termios attr), which tells the terminal to line-buffer - input from the keyboard before sending it to stdin. but it ALSO - does what WE (the console) were doing here before: erasing the current character - and moving the cursor back. THIS SHOULD BE KEYBOARD.C's JOB! - - SOLUTION: - WE should have an "echo" option, and keyboard.c should have a "line buffer" option. - and when keyboard.c's "line buffering" is on, **it should interface with the - currently selected console** to erase the character at the cursor and then move - the cursor back. - - the responsibilities should NOT be mixed together. - */ + /* + the old code here actually moved the cursor back one (and up one row + if needed) and then "erased" the character by writing a space to fontBgMap. + + in a linux terminal emulator with the termios attr ICANON off, + backspace does NOT move the cursor. if the ECHO termios attr is on + it prints the \b character (visualized as ^?). + + with ICANON and ECHO on, backspace does NOT wrap the cursor around to the + last row. + + what dkp was trying to do here is emulate "canonical mode" + (ICANON termios attr), which tells the terminal to line-buffer + input from the keyboard before sending it to stdin. but it ALSO + does what WE (the console) were doing here before: erasing the current character + and moving the cursor back. THIS SHOULD BE KEYBOARD.C's JOB! + + SOLUTION: + keyboard.c should have both an "echo" and "line buffer" option. + when keyboard.c's "line buffering" is on, **it should interface with the + currently selected console** to erase the character at the cursor and then move + the cursor back. when "echo" is on, keyboard.c should send what it gets to the + console to be ***immediately*** rendered, with non-visual characters visualized. + for example, left arrow becomes ^[[D. + + the responsibilities should NOT be mixed together. + */ if (c->echo) *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue('\b'); @@ -113,10 +133,12 @@ void consolePrintChar(const char ch) { break; default: - // c->fontBgMap[c->cursorX + c->windowX + (c->cursorY + c->windowY) * c->consoleWidth] = - // c->fontCurPal | (u16)(ch + c->fontCharOffset - c->font.asciiOffset); - *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue(ch); + *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue(ch); // fg + *consoleFontBg2MapAtCursor() = consoleComputeFontBg2MapValue(219); // bg + ++c->cursorX; + consoleSaveTileUnderCursor(); + consoleDrawCursor(); // consoleMoveCursorX(1); } } diff --git a/source/arm9/console-priv.h b/source/arm9/console-priv.h index 7fbe157..969dc19 100644 --- a/source/arm9/console-priv.h +++ b/source/arm9/console-priv.h @@ -34,22 +34,23 @@ void consoleClearLine(char mode); void newRow(); // console-print.c -u16 *consoleFontBgMapAt(const int x, const int y); +u16 *consoleFontBgMapAt(int x, int y); +u16 *consoleFontBg2MapAt(int x, int y); u16 *consoleFontBgMapAtCursor(void); +u16 *consoleFontBg2MapAtCursor(void); u16 consoleComputeFontBgMapValue(char); +u16 consoleComputeFontBg2MapValue(char); void consolePrintChar(char); // console-cursor.c -void consoleSaveFbmvUnderCursor(void); -void consoleRestoreFbmvUnderCursor(void); +void consoleSaveTileUnderCursor(void); +void consoleRestoreTileUnderCursor(void); void consoleMoveCursorX(int dx); // clamps cursorX to windowWidth void consoleMoveCursorY(int dy); // clamps cursorY to windowHeight void consoleSetCursorX(int x); void consoleSetCursorY(int y); void consoleSetCursorPos(int x, int y); -void consoleSetCursorChar(char); -void consoleStartFlashingCursor(int frequency); -void consoleStopFlashingCursor(void); +void consoleDrawCursor(void); // console-esc.c int consoleParseEscapeSequence(const char *ptr, int len); diff --git a/source/arm9/console.c b/source/arm9/console.c index 3fb7670..0d01ad2 100644 --- a/source/arm9/console.c +++ b/source/arm9/console.c @@ -291,6 +291,7 @@ void consoleLoadFont(PrintConsole* console) { const u8* in = (const u8*)console->font.gfx; u32* out = (u32*)console->fontBgGfx; + u32* out2 = (u32*)console->fontBg2Gfx; for ( i = 0; i < console->font.numChars * 8; i ++) { unsigned cur = *in++; @@ -302,6 +303,7 @@ void consoleLoadFont(PrintConsole* console) { } *out++ = temp; + *out2++ = temp; } goto _setUpPalette; @@ -335,27 +337,28 @@ void consoleLoadFont(PrintConsole* console) { _setUpPalette: //set up the palette for color printing - palette[1 * 16 - 1] = RGB15(0,0,0); //30 normal black - palette[2 * 16 - 1] = RGB15(15,0,0); //31 normal red - palette[3 * 16 - 1] = RGB15(0,15,0); //32 normal green - palette[4 * 16 - 1] = RGB15(15,15,0); //33 normal yellow - - palette[5 * 16 - 1] = RGB15(0,0,15); //34 normal blue - palette[6 * 16 - 1] = RGB15(15,0,15); //35 normal magenta - palette[7 * 16 - 1] = RGB15(0,15,15); //36 normal cyan - palette[8 * 16 - 1] = RGB15(24,24,24); //37 normal white - - palette[9 * 16 - 1 ] = RGB15(15,15,15); //40 bright black - palette[10 * 16 - 1] = RGB15(31,0,0); //41 bright red - palette[11 * 16 - 1] = RGB15(0,31,0); //42 bright green - palette[12 * 16 - 1] = RGB15(31,31,0); //43 bright yellow - - palette[13 * 16 - 1] = RGB15(0,0,31); //44 bright blue - palette[14 * 16 - 1] = RGB15(31,0,31); //45 bright magenta - palette[15 * 16 - 1] = RGB15(0,31,31); //46 bright cyan - palette[16 * 16 - 1] = RGB15(31,31,31); //47 & 39 bright white + palette[1 * 16 - 1] = RGB15(0, 0, 0); // normal black + palette[2 * 16 - 1] = RGB15(15, 0, 0); // normal red + palette[3 * 16 - 1] = RGB15(0, 15, 0); // normal green + palette[4 * 16 - 1] = RGB15(15, 15, 0); // normal yellow + + palette[5 * 16 - 1] = RGB15(0, 0, 15); // normal blue + palette[6 * 16 - 1] = RGB15(15, 0, 15); // normal magenta + palette[7 * 16 - 1] = RGB15(0, 15, 15); // normal cyan + palette[8 * 16 - 1] = RGB15(24, 24, 24); // normal white + + palette[9 * 16 - 1] = RGB15(15, 15, 15); // bright black + palette[10 * 16 - 1] = RGB15(31, 0, 0); // bright red + palette[11 * 16 - 1] = RGB15(0, 31, 0); // bright green + palette[12 * 16 - 1] = RGB15(31, 31, 0); // bright yellow + + palette[13 * 16 - 1] = RGB15(0, 0, 31); // bright blue + palette[14 * 16 - 1] = RGB15(31, 0, 31); // bright magenta + palette[15 * 16 - 1] = RGB15(0, 31, 31); // bright cyan + palette[16 * 16 - 1] = RGB15(31, 31, 31); // bright white console->fontCurPal = 15 << 12; + console->fontCurPal2 = 0 << 12; } } else if(console->font.bpp == 8) { @@ -427,12 +430,17 @@ PrintConsole* consoleInit(PrintConsole* console, int layer, if(mainDisplay) { console->bgId = bgInit(layer, type, size, mapBase, tileBase); + console->bg2Id = bgInit(layer + 1, type, size, mapBase + 1, tileBase); } else { console->bgId = bgInitSub(layer, type, size, mapBase, tileBase); + console->bg2Id = bgInitSub(layer + 1, type, size, mapBase + 1, tileBase); } - console->fontBgGfx = (u16*)bgGetGfxPtr(console->bgId); - console->fontBgMap = (u16*)bgGetMapPtr(console->bgId); + console->fontBgGfx = bgGetGfxPtr(console->bgId); + console->fontBgMap = bgGetMapPtr(console->bgId); + + console->fontBg2Gfx = bgGetGfxPtr(console->bg2Id); + console->fontBg2Map = bgGetMapPtr(console->bg2Id); console->consoleInitialised = 1; @@ -441,8 +449,6 @@ PrintConsole* consoleInit(PrintConsole* console, int layer, if(loadGraphics) consoleLoadFont(console); - consoleSetCursorChar(219); - return currentConsole; } @@ -503,7 +509,7 @@ PrintConsole* consoleDemoInit(void) { //--------------------------------------------------------------------------------- void newRow() { //--------------------------------------------------------------------------------- - consoleRestoreFbmvUnderCursor(); + consoleRestoreTileUnderCursor(); currentConsole->cursorY ++; @@ -524,7 +530,7 @@ void newRow() { } - consoleSaveFbmvUnderCursor(); + consoleSaveTileUnderCursor(); } //--------------------------------------------------------------------------------- From b705d810578fee2030feacebe769c7e83fbd9ac8 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:47:27 -0400 Subject: [PATCH 07/12] fix: make `newRow()` use the new FontBgMap APIs (move the 2nd layer as well) --- include/nds/arm9/console.h | 3 ++- source/arm9/console-cursor.c | 27 ++++++++++++++----- source/arm9/console-print.c | 52 +++++++++++++++++++++++------------- source/arm9/console.c | 41 +++++++++++++++++----------- 4 files changed, 81 insertions(+), 42 deletions(-) diff --git a/include/nds/arm9/console.h b/include/nds/arm9/console.h index b95c444..8d1d121 100644 --- a/include/nds/arm9/console.h +++ b/include/nds/arm9/console.h @@ -221,9 +221,10 @@ PrintConsole *consoleSelect(PrintConsole* console); \param tileBase the tile graphics base \param mainDisplay if true main engine is used, otherwise false \param loadGraphics if true the default font graphics will be loaded into the layer + \param ansiBgColors if true, uses \c layer + 1 for ANSI background color escape sequences \return A pointer to the current console. */ -PrintConsole* consoleInit(PrintConsole* console, int layer, BgType type, BgSize size, int mapBase, int tileBase, bool mainDisplay, bool loadGraphics); +PrintConsole* consoleInit(PrintConsole* console, int layer, BgType type, BgSize size, int mapBase, int tileBase, bool mainDisplay, bool loadGraphics, bool ansiBgColors); /*! \brief Initialize the console to a default state for prototyping. This function sets the console to use sub display, VRAM_C, and BG0 and enables MODE_0_2D on the diff --git a/source/arm9/console-cursor.c b/source/arm9/console-cursor.c index 56a3fe5..fdda05b 100644 --- a/source/arm9/console-cursor.c +++ b/source/arm9/console-cursor.c @@ -51,14 +51,19 @@ void consoleDrawCursor(void) { } void consoleSetCursorPos(const int x, const int y) { - consoleRestoreTileUnderCursor(); + if (currentConsole->bg2Id != -1) + consoleRestoreTileUnderCursor(); + currentConsole->cursorX = x; currentConsole->cursorY = y; - consoleSaveTileUnderCursor(); - // original tile saved! now let's make the cursor visible with - // white background and black foreground. - consoleDrawCursor(); + if (currentConsole->bg2Id != -1) { + consoleSaveTileUnderCursor(); + + // original tile saved! now let's make the cursor visible with + // white background and black foreground. + consoleDrawCursor(); + } } void consoleSetCursorY(const int y) { @@ -69,16 +74,24 @@ void consoleSetCursorX(const int x) { consoleSetCursorPos(x, currentConsole->cursorY); } +int clamp(const int n, const int min, const int max) { + if (n < min) + return min; + if (n > max) + return max; + return n; +} + void consoleMoveCursorX(const int dx) { const PrintConsole *const c = currentConsole; const int newX = c->cursorX + dx; const int maxX = c->windowWidth - 1; - consoleSetCursorX((newX > maxX) ? maxX : newX); + consoleSetCursorX(clamp(newX, 0, maxX)); } void consoleMoveCursorY(const int dy) { const PrintConsole *const c = currentConsole; const int newY = c->cursorY + dy; const int maxY = c->windowHeight - 1; - consoleSetCursorY((newY > maxY) ? maxY : newY); + consoleSetCursorY(clamp(newY, 0, maxY)); } diff --git a/source/arm9/console-print.c b/source/arm9/console-print.c index ac35c62..45f85c2 100644 --- a/source/arm9/console-print.c +++ b/source/arm9/console-print.c @@ -25,15 +25,15 @@ distribution. u16 *consoleFontBgMapAt(const int x, const int y) { const PrintConsole *const c = currentConsole; - const int xOffset = c->cursorX + c->windowX; - const int yOffset = (c->cursorY + c->windowY) * c->consoleWidth; + const int xOffset = x + c->windowX; + const int yOffset = (y + c->windowY) * c->consoleWidth; return c->fontBgMap + xOffset + yOffset; } u16 *consoleFontBg2MapAt(const int x, const int y) { const PrintConsole *const c = currentConsole; - const int xOffset = c->cursorX + c->windowX; - const int yOffset = (c->cursorY + c->windowY) * c->consoleWidth; + const int xOffset = x + c->windowX; + const int yOffset = (y + c->windowY) * c->consoleWidth; return c->fontBg2Map + xOffset + yOffset; } @@ -57,6 +57,21 @@ u16 consoleComputeFontBg2MapValue(const char ch) { return c->fontCurPal2 | (u16)(ch + c->fontCharOffset - c->font.asciiOffset); } +void consoleCommitChar(const char ch) { + PrintConsole *const c = currentConsole; + + *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue(ch); // fg + if (c->bg2Id != -1) + *consoleFontBg2MapAtCursor() = consoleComputeFontBg2MapValue(219); // bg + + ++c->cursorX; + + if (c->bg2Id != -1) { + consoleSaveTileUnderCursor(); + consoleDrawCursor(); + } +} + // could have a better name, since we aren't always printing a character void consolePrintChar(const char ch) { if (!ch) @@ -75,8 +90,10 @@ void consolePrintChar(const char ch) { return; if (c->cursorX >= c->windowWidth) { - // c->cursorX = 0; - consoleSetCursorX(0); + if (currentConsole->bg2Id == -1) + c->cursorX = 0; + else + consoleSetCursorX(0); newRow(); } @@ -115,12 +132,15 @@ void consolePrintChar(const char ch) { */ if (c->echo) - *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue('\b'); + // *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue('\b'); + consoleCommitChar('\b'); break; case '\t': - // c->cursorX += c->tabSize - ((c->cursorX) % (c->tabSize)); - consoleMoveCursorX(c->tabSize - ((c->cursorX) % (c->tabSize))); + if (currentConsole->bg2Id == -1) + c->cursorX += c->tabSize - ((c->cursorX) % (c->tabSize)); + else + consoleMoveCursorX(c->tabSize - ((c->cursorX) % (c->tabSize))); break; case '\n': @@ -128,17 +148,13 @@ void consolePrintChar(const char ch) { // also return to first column: case '\r': - // c->cursorX = 0; - consoleSetCursorX(0); + if (currentConsole->bg2Id == -1) + c->cursorX = 0; + else + consoleSetCursorX(0); break; default: - *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue(ch); // fg - *consoleFontBg2MapAtCursor() = consoleComputeFontBg2MapValue(219); // bg - - ++c->cursorX; - consoleSaveTileUnderCursor(); - consoleDrawCursor(); - // consoleMoveCursorX(1); + consoleCommitChar(ch); } } diff --git a/source/arm9/console.c b/source/arm9/console.c index 0d01ad2..0b5db71 100644 --- a/source/arm9/console.c +++ b/source/arm9/console.c @@ -303,7 +303,8 @@ void consoleLoadFont(PrintConsole* console) { } *out++ = temp; - *out2++ = temp; + if (console->bg2Id != -1) + *out2++ = temp; } goto _setUpPalette; @@ -405,7 +406,8 @@ void consoleLoadFont(PrintConsole* console) { PrintConsole* consoleInit(PrintConsole* console, int layer, BgType type, BgSize size, int mapBase, int tileBase, - bool mainDisplay, bool loadGraphics){ + bool mainDisplay, bool loadGraphics, + bool ansiBgColors){ //--------------------------------------------------------------------------------- static bool firstConsoleInit = true; @@ -430,17 +432,19 @@ PrintConsole* consoleInit(PrintConsole* console, int layer, if(mainDisplay) { console->bgId = bgInit(layer, type, size, mapBase, tileBase); - console->bg2Id = bgInit(layer + 1, type, size, mapBase + 1, tileBase); + console->bg2Id = ansiBgColors ? bgInit(layer + 1, type, size, mapBase + 1, tileBase) : -1; } else { console->bgId = bgInitSub(layer, type, size, mapBase, tileBase); - console->bg2Id = bgInitSub(layer + 1, type, size, mapBase + 1, tileBase); + console->bg2Id = ansiBgColors ? bgInitSub(layer + 1, type, size, mapBase + 1, tileBase) : -1; } console->fontBgGfx = bgGetGfxPtr(console->bgId); console->fontBgMap = bgGetMapPtr(console->bgId); - console->fontBg2Gfx = bgGetGfxPtr(console->bg2Id); - console->fontBg2Map = bgGetMapPtr(console->bg2Id); + if (console->bg2Id != -1) { + console->fontBg2Gfx = bgGetGfxPtr(console->bg2Id); + console->fontBg2Map = bgGetMapPtr(console->bg2Id); + } console->consoleInitialised = 1; @@ -503,13 +507,14 @@ PrintConsole* consoleDemoInit(void) { videoSetModeSub(MODE_0_2D); vramSetBankC(VRAM_C_SUB_BG); - return consoleInit(NULL, defaultConsole.bgLayer, BgType_Text4bpp, BgSize_T_256x256, defaultConsole.mapBase, defaultConsole.gfxBase, false, true); + return consoleInit(NULL, defaultConsole.bgLayer, BgType_Text4bpp, BgSize_T_256x256, defaultConsole.mapBase, defaultConsole.gfxBase, false, true, false); } //--------------------------------------------------------------------------------- void newRow() { //--------------------------------------------------------------------------------- - consoleRestoreTileUnderCursor(); + if (currentConsole->bg2Id != -1) + consoleRestoreTileUnderCursor(); currentConsole->cursorY ++; @@ -520,17 +525,21 @@ void newRow() { currentConsole->cursorY --; for(rowCount = 0; rowCount < currentConsole->windowHeight - 1; rowCount++) - for(colCount = 0; colCount < currentConsole->windowWidth; colCount++) - currentConsole->fontBgMap[(colCount + currentConsole->windowX) + (rowCount + currentConsole->windowY) * currentConsole->consoleWidth] = - currentConsole->fontBgMap[(colCount + currentConsole->windowX) + (rowCount + currentConsole->windowY + 1) * currentConsole->consoleWidth]; - - for(colCount = 0; colCount < currentConsole->windowWidth; colCount++) - currentConsole->fontBgMap[(colCount + currentConsole->windowX) + (rowCount + currentConsole->windowY) * currentConsole->consoleWidth] = - (' ' + currentConsole->fontCharOffset - currentConsole->font.asciiOffset); + for(colCount = 0; colCount < currentConsole->windowWidth; colCount++) { + *consoleFontBgMapAt(colCount, rowCount) = *consoleFontBgMapAt(colCount, rowCount + 1); + if (currentConsole->bg2Id != -1) + *consoleFontBg2MapAt(colCount, rowCount) = *consoleFontBg2MapAt(colCount, rowCount + 1); + } + for(colCount = 0; colCount < currentConsole->windowWidth; colCount++) { + *consoleFontBgMapAt(colCount, rowCount) = consoleComputeFontBgMapValue(' '); + if (currentConsole->bg2Id != -1) + *consoleFontBg2MapAt(colCount, rowCount) = consoleComputeFontBg2MapValue(' '); + } } - consoleSaveTileUnderCursor(); + if (currentConsole->bg2Id != -1) + consoleSaveTileUnderCursor(); } //--------------------------------------------------------------------------------- From 75f449f05c9879141efd74bfb45a24f122d3c129 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Fri, 8 Aug 2025 16:54:41 -0400 Subject: [PATCH 08/12] add parameter comment for `consoleInit()` remove commented out code for directional cursor movement --- include/nds/arm9/console.h | 3 ++- source/arm9/console-esc.c | 10 ---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/include/nds/arm9/console.h b/include/nds/arm9/console.h index 8d1d121..2dd3780 100644 --- a/include/nds/arm9/console.h +++ b/include/nds/arm9/console.h @@ -221,7 +221,8 @@ PrintConsole *consoleSelect(PrintConsole* console); \param tileBase the tile graphics base \param mainDisplay if true main engine is used, otherwise false \param loadGraphics if true the default font graphics will be loaded into the layer - \param ansiBgColors if true, uses \c layer + 1 for ANSI background color escape sequences + \param ansiBgColors if true, uses `layer + 1` and `tileBase + 1` for ANSI background color escape sequences. + make sure the next initialized console does not use `layer + 1` and `tileBase + 1` ! \return A pointer to the current console. */ PrintConsole* consoleInit(PrintConsole* console, int layer, BgType type, BgSize size, int mapBase, int tileBase, bool mainDisplay, bool loadGraphics, bool ansiBgColors); diff --git a/source/arm9/console-esc.c b/source/arm9/console-esc.c index 1136c4b..29f7ff7 100644 --- a/source/arm9/console-esc.c +++ b/source/arm9/console-esc.c @@ -167,34 +167,24 @@ int consoleParseEscapeSequence(const char *ptr, int len) { case 'A': if (sscanf(escapeseq, "%dA", ¶meter) < 1) parameter = 1; - // currentConsole->cursorY = - // (currentConsole->cursorY - parameter) < 0 ? 0 : currentConsole->cursorY - parameter; consoleMoveCursorY(-parameter); return escapelen; case 'B': if (sscanf(escapeseq, "%dB", ¶meter) < 1) parameter = 1; - // currentConsole->cursorY = (currentConsole->cursorY + parameter) > currentConsole->windowHeight - 1 - // ? currentConsole->windowHeight - 1 - // : currentConsole->cursorY + parameter; consoleMoveCursorY(parameter); return escapelen; case 'C': if (sscanf(escapeseq, "%dC", ¶meter) < 1) parameter = 1; - // currentConsole->cursorX = (currentConsole->cursorX + parameter) > currentConsole->windowWidth - 1 - // ? currentConsole->windowWidth - 1 - // : currentConsole->cursorX + parameter; consoleMoveCursorX(parameter); return escapelen; case 'D': if (sscanf(escapeseq, "%dD", ¶meter) < 1) parameter = 1; - // currentConsole->cursorX = - // (currentConsole->cursorX - parameter) < 0 ? 0 : currentConsole->cursorX - parameter; consoleMoveCursorX(-parameter); return escapelen; From 613e16721697fc001c4e9e9ab4a96ee005d32125 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:18:20 -0400 Subject: [PATCH 09/12] bugfixes --- source/arm9/console-cursor.c | 11 +++-- source/arm9/console-esc.c | 45 +++++++++++-------- source/arm9/console-print.c | 17 ++++--- source/arm9/console.c | 86 +++++++++++------------------------- 4 files changed, 68 insertions(+), 91 deletions(-) diff --git a/source/arm9/console-cursor.c b/source/arm9/console-cursor.c index fdda05b..e36573f 100644 --- a/source/arm9/console-cursor.c +++ b/source/arm9/console-cursor.c @@ -23,9 +23,8 @@ distribution. ---------------------------------------------------------------------------------*/ #include "console-priv.h" -// This saves the foreground and background color of -// whatever tile we just moved on top of. -// fb2mv should always be the full block ASCII character (219). +// These save the palette and character of whatever tile the cursor moves on top of. +// fb2mv should always contain the full block ASCII character (219). static u16 fbmvUnderCursor, fb2mvUnderCursor; void consoleSaveTileUnderCursor(void) { @@ -41,9 +40,9 @@ void consoleRestoreTileUnderCursor(void) { void consoleDrawCursor(void) { // foreground: this is the character itself. we need to turn it black, // which would usually be a fontCurPal of (0 << 12). however fbmvUnderCursor - // 99% of the time does not have those bits zeroed inside. (who uses black on black?) - // so we need to zero-out those bits: - // static const u16 BLACK_BITMASK = (u16)(UINT16_MAX << 4) >> 4; + // is not being computed on the fly (like below), so 99% of the time it + // does not have those bits zeroed inside. (who uses black on black?) + // so we need to zero-out those bits to make it black: *consoleFontBgMapAtCursor() = 0x0fff & fbmvUnderCursor; // background: just use bright write (15) diff --git a/source/arm9/console-esc.c b/source/arm9/console-esc.c index 29f7ff7..0291f68 100644 --- a/source/arm9/console-esc.c +++ b/source/arm9/console-esc.c @@ -78,6 +78,7 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { // Special case: \x1b[m resets attributes if (*escapeseq == 'm') { currentConsole->fontCurPal = 15 << 12; // Default color (bright white) + currentConsole->fontCurPal2 = 0; // black bg return; } @@ -102,23 +103,27 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { // consumed thus far into the next pointer! use this to advance `p`. // start consuming arguments, delimited with ';' - do { - items_matched = siscanf(p, "%d;%n", ¶m, &chars_consumed); - - if (items_matched > 0) { - // the %d got matched, param is valid! - updateColorBright(param, &color, &bgcolor, &bright); - } - - // only advance p if items were matched - p += chars_consumed; - } while (items_matched > 0); - - // end of arguments must end with 'm' - if (siscanf(p, "%dm%n", ¶m, &chars_consumed) > 0) { - // the %d got matched, param is valid! - updateColorBright(param, &color, &bgcolor, &bright); - } + while (true) { + // Try to parse a parameter followed by a semicolon + items_matched = siscanf(p, "%d;%n", ¶m, &chars_consumed); + if (items_matched > 0) { + updateColorBright(param, &color, &bgcolor, &bright); + p += chars_consumed; + continue; + } + + // If that failed, try to parse the final parameter ending in 'm' + items_matched = siscanf(p, "%dm", ¶m); + if (items_matched > 0) { + updateColorBright(param, &color, &bgcolor, &bright); + } + + // End of sequence + break; + } + + if (!currentConsole) + return; // handle cases when only bold (1) modifier is used. this only affects foreground. // this should simply turn the current color into its bright variant. @@ -192,8 +197,10 @@ int consoleParseEscapeSequence(const char *ptr, int len) { case 'H': case 'f': { int x, y; - sscanf(escapeseq, "%d;%d", &y, &x); - consoleSetCursorPos(x, y); + if (sscanf(escapeseq, "%d;%d", &y, &x) == 2) + consoleSetCursorPos(x, y); + else + consoleSetCursorPos(0, 0); return escapelen; } diff --git a/source/arm9/console-print.c b/source/arm9/console-print.c index 45f85c2..ac269c3 100644 --- a/source/arm9/console-print.c +++ b/source/arm9/console-print.c @@ -61,6 +61,7 @@ void consoleCommitChar(const char ch) { PrintConsole *const c = currentConsole; *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue(ch); // fg + if (c->bg2Id != -1) *consoleFontBg2MapAtCursor() = consoleComputeFontBg2MapValue(219); // bg @@ -130,10 +131,6 @@ void consolePrintChar(const char ch) { the responsibilities should NOT be mixed together. */ - - if (c->echo) - // *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue('\b'); - consoleCommitChar('\b'); break; case '\t': @@ -155,6 +152,16 @@ void consolePrintChar(const char ch) { break; default: - consoleCommitChar(ch); + *consoleFontBgMapAtCursor() = consoleComputeFontBgMapValue(ch); // fg + + if (c->bg2Id != -1) + *consoleFontBg2MapAtCursor() = consoleComputeFontBg2MapValue(219); // bg + + ++c->cursorX; + + if (c->bg2Id != -1) { + consoleSaveTileUnderCursor(); + consoleDrawCursor(); + } } } diff --git a/source/arm9/console.c b/source/arm9/console.c index 0b5db71..6bdc01f 100644 --- a/source/arm9/console.c +++ b/source/arm9/console.c @@ -134,66 +134,26 @@ void consoleCls(char mode) { //--------------------------------------------------------------------------------- void consoleClearLine(char mode) { //--------------------------------------------------------------------------------- - - int i = 0; - int colTemp; - - switch (mode) - { - case '[': - case '0': - { - colTemp = currentConsole->cursorX ; - - while(i++ < (currentConsole->windowWidth - colTemp)) { - consolePrintChar(' '); - } - - currentConsole->cursorX = colTemp; - - break; - } - case '1': - { - colTemp = currentConsole->cursorX ; - - currentConsole->cursorX = 0; - - while(i++ < ((currentConsole->windowWidth - colTemp)-2)) { - consolePrintChar(' '); - } - - currentConsole->cursorX = colTemp; - - break; - } - case '2': - { - colTemp = currentConsole->cursorX ; - - currentConsole->cursorX = 0; - - while(i++ < currentConsole->windowWidth) { - consolePrintChar(' '); - } - - currentConsole->cursorX = colTemp; - - break; - } - default: - { - colTemp = currentConsole->cursorX ; - - while(i++ < (currentConsole->windowWidth - colTemp)) { - consolePrintChar(' '); - } - - currentConsole->cursorX = colTemp; - - break; - } - } + int i = 0; + int line = currentConsole->cursorY; + int start = 0; + int end = currentConsole->windowWidth; + + if (mode == '1') { + end = currentConsole->cursorX; + } else if (mode == '0') { + start = currentConsole->cursorX; + } + + // The character offset for ' ' in the font. + u16 blank = currentConsole->fontCharOffset - currentConsole->font.asciiOffset; + + for (i = start; i < end; i++) { + // Clear with default colors (fg: white, bg: black) regardless of current palette. + *consoleFontBgMapAt(i, line) = (15 << 12) | blank; + if (currentConsole->bg2Id != -1) + *consoleFontBg2MapAt(i, line) = (0 << 12) | blank; + } } //--------------------------------------------------------------------------------- @@ -222,7 +182,7 @@ ssize_t con_write(struct _reent *r,void *fd,const char *ptr, size_t len) { chr = *(tmp++); i++; count++; - if ( chr == 0x1b && i < len && *tmp == '[' ) { + if ( chr == 0x1b && *tmp == '[' && i < len ) { // skip the '[' tmp++; i++; count++; // len - i: the REMAINING length in the buffer @@ -527,12 +487,16 @@ void newRow() { for(rowCount = 0; rowCount < currentConsole->windowHeight - 1; rowCount++) for(colCount = 0; colCount < currentConsole->windowWidth; colCount++) { *consoleFontBgMapAt(colCount, rowCount) = *consoleFontBgMapAt(colCount, rowCount + 1); + // currentConsole->fontBgMap[(colCount + currentConsole->windowX) + (rowCount + currentConsole->windowY) * currentConsole->consoleWidth] = + // currentConsole->fontBgMap[(colCount + currentConsole->windowX) + (rowCount + currentConsole->windowY + 1) * currentConsole->consoleWidth]; if (currentConsole->bg2Id != -1) *consoleFontBg2MapAt(colCount, rowCount) = *consoleFontBg2MapAt(colCount, rowCount + 1); } for(colCount = 0; colCount < currentConsole->windowWidth; colCount++) { *consoleFontBgMapAt(colCount, rowCount) = consoleComputeFontBgMapValue(' '); + // currentConsole->fontBgMap[(colCount + currentConsole->windowX) + (rowCount + currentConsole->windowY) * currentConsole->consoleWidth] = + // (' ' + currentConsole->fontCharOffset - currentConsole->font.asciiOffset); if (currentConsole->bg2Id != -1) *consoleFontBg2MapAt(colCount, rowCount) = consoleComputeFontBg2MapValue(' '); } From 4e846a2541fb35ba6a0bc3ab943dcd38672ce64e Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Fri, 8 Aug 2025 21:04:54 -0400 Subject: [PATCH 10/12] `siscanf` and `sscanf` might just be different --- source/arm9/console-esc.c | 55 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/source/arm9/console-esc.c b/source/arm9/console-esc.c index 0291f68..cbf6191 100644 --- a/source/arm9/console-esc.c +++ b/source/arm9/console-esc.c @@ -75,10 +75,13 @@ static void updateColorBright(const int param, int *const color, int *const bgco } static void consoleParseColor(const char *escapeseq, int escapelen) { + if (!currentConsole) + return; + // Special case: \x1b[m resets attributes if (*escapeseq == 'm') { currentConsole->fontCurPal = 15 << 12; // Default color (bright white) - currentConsole->fontCurPal2 = 0; // black bg + currentConsole->fontCurPal2 = 0; // black bg return; } @@ -103,27 +106,23 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { // consumed thus far into the next pointer! use this to advance `p`. // start consuming arguments, delimited with ';' - while (true) { - // Try to parse a parameter followed by a semicolon - items_matched = siscanf(p, "%d;%n", ¶m, &chars_consumed); - if (items_matched > 0) { - updateColorBright(param, &color, &bgcolor, &bright); - p += chars_consumed; - continue; - } - - // If that failed, try to parse the final parameter ending in 'm' - items_matched = siscanf(p, "%dm", ¶m); - if (items_matched > 0) { - updateColorBright(param, &color, &bgcolor, &bright); - } - - // End of sequence - break; - } + while (true) { + // Try to parse a parameter followed by a semicolon + items_matched = siscanf(p, "%d;%n", ¶m, &chars_consumed); + if (items_matched > 0) { + updateColorBright(param, &color, &bgcolor, &bright); + p += chars_consumed; + continue; + } - if (!currentConsole) - return; + // If that failed, try to parse the final parameter ending in 'm' + items_matched = siscanf(p, "%dm", ¶m); + if (items_matched > 0) + updateColorBright(param, &color, &bgcolor, &bright); + + // End of sequence + break; + } // handle cases when only bold (1) modifier is used. this only affects foreground. // this should simply turn the current color into its bright variant. @@ -168,27 +167,27 @@ int consoleParseEscapeSequence(const char *ptr, int len) { case 'l': return escapelen; - // Cursor directional movement (TODO: make these use consoleMoveCursorX/Y instead) + // Cursor directional movement case 'A': - if (sscanf(escapeseq, "%dA", ¶meter) < 1) + if (siscanf(escapeseq, "%dA", ¶meter) < 1) parameter = 1; consoleMoveCursorY(-parameter); return escapelen; case 'B': - if (sscanf(escapeseq, "%dB", ¶meter) < 1) + if (siscanf(escapeseq, "%dB", ¶meter) < 1) parameter = 1; consoleMoveCursorY(parameter); return escapelen; case 'C': - if (sscanf(escapeseq, "%dC", ¶meter) < 1) + if (siscanf(escapeseq, "%dC", ¶meter) < 1) parameter = 1; consoleMoveCursorX(parameter); return escapelen; case 'D': - if (sscanf(escapeseq, "%dD", ¶meter) < 1) + if (siscanf(escapeseq, "%dD", ¶meter) < 1) parameter = 1; consoleMoveCursorX(-parameter); return escapelen; @@ -197,10 +196,10 @@ int consoleParseEscapeSequence(const char *ptr, int len) { case 'H': case 'f': { int x, y; - if (sscanf(escapeseq, "%d;%d", &y, &x) == 2) + if (siscanf(escapeseq, "%d;%d", &y, &x) == 2) consoleSetCursorPos(x, y); else - consoleSetCursorPos(0, 0); + consoleSetCursorPos(0, 0); return escapelen; } From 6da857faf39a81e2d0b3130e780d7b1aa6b80a57 Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:39:12 -0400 Subject: [PATCH 11/12] Escape sequence character buffering implemented --- include/nds/arm9/console.h | 8 +- source/arm9/console-esc.c | 237 ++++++++++++++++++++----------------- source/arm9/console-priv.h | 1 + source/arm9/console.c | 114 ++++++++---------- 4 files changed, 181 insertions(+), 179 deletions(-) diff --git a/include/nds/arm9/console.h b/include/nds/arm9/console.h index 2dd3780..51a47e8 100644 --- a/include/nds/arm9/console.h +++ b/include/nds/arm9/console.h @@ -169,11 +169,13 @@ typedef struct PrintConsole bool consoleInitialised; /*!< True if the console is initialized */ bool loadGraphics; /*!< True if consoleInit should attempt to load font graphics into background memory */ - bool echo; // Whether to echo characters written to this console, similar to the ECHO termios attribute. int bg2Id; // ID of bg used for ANSI background colors! - u16 *fontBg2Map, *fontBg2Gfx; // map & gfx for the 2nd bg u16 fontCurPal2; // palette for background colors + + // Internal buffer for potential ANSI escape sequences, as all the characters may not be available in one con_write() call. + char escBuf[32]; + int escBufLen; }PrintConsole; @@ -204,7 +206,7 @@ void consoleSetWindow(PrintConsole* console, int x, int y, int width, int height this should only be used when using a single console or without changing the console that is returned, other wise use consoleInit() \return A pointer to the console with the default values */ -PrintConsole* consoleGetDefault(void); +const PrintConsole* consoleGetDefault(void); /*! \brief Make the specified console the render target \param console A pointer to the console struct (must have been initialized with consoleInit(PrintConsole* console) diff --git a/source/arm9/console-esc.c b/source/arm9/console-esc.c index cbf6191..f9389c3 100644 --- a/source/arm9/console-esc.c +++ b/source/arm9/console-esc.c @@ -22,36 +22,9 @@ distribution. ---------------------------------------------------------------------------------*/ #include "console-priv.h" +#include #include - -/* -TODO, if it becomes important enough: - -don't use the scanf strategy after all, because incomplete sequences written -to consoles will just be shown raw. to do what every modern terminal (emulator) does, -we need to implement a state machine with a buffer. - -as an example: -run `cat`, then press the Esc and Enter keys on your keyboard. this does three things: -1. '\e' is added to cat's input buffer -2. your terminal reacts to Enter and moves the cursor to the next line -3. cat sends its input buffer (containing "\e\n" in C string format) to stdout, - causing your terminal to: - 1. recognize the start of an escape sequence (\e) - 2. move the cursor to the next line (again) (\n) -then type "[36m" without the quotes. on my terminal emulator (foot), the "[36m" -was never shown, and everything i typed afterwards was cyan in color. my terminal -emulator let the \n go through without printing the \e, meaning it did in fact -store the \e in a buffer, or set a flag indicating that "i should check for the rest -of the escape sequence", then processed the "[36m", setting the color to cyan. - -we should recreate this strategy here as well to handle cases where not all of an -escape sequence is passed to a single con_write() call. - -not to mention: "\e[" is just one type of sequence (known as a control sequence introducer, -or CSI), but there are also "\e " sequences as well which can do other things. a state -machine would help here. -*/ +// #include static void updateColorBright(const int param, int *const color, int *const bgcolor, int *const bright) { if (param == 0) { // Reset @@ -74,7 +47,7 @@ static void updateColorBright(const int param, int *const color, int *const bgco } } -static void consoleParseColor(const char *escapeseq, int escapelen) { +static void consoleParseColor(const char *escapeseq) { if (!currentConsole) return; @@ -151,88 +124,130 @@ static void consoleParseColor(const char *escapeseq, int escapelen) { currentConsole->fontCurPal2 = bgcolor << 12; } -int consoleParseEscapeSequence(const char *ptr, int len) { - char chr; - const char *escapeseq = ptr; - int escapelen = 0; - int parameter; - - do { - chr = *(ptr++); - escapelen++; - - switch (chr) { - // Private modes - commonly end in 'h' or 'l'. We won't implement any (yet), so just consume them. - case 'h': - case 'l': - return escapelen; - - // Cursor directional movement - case 'A': - if (siscanf(escapeseq, "%dA", ¶meter) < 1) - parameter = 1; - consoleMoveCursorY(-parameter); - return escapelen; - - case 'B': - if (siscanf(escapeseq, "%dB", ¶meter) < 1) - parameter = 1; - consoleMoveCursorY(parameter); - return escapelen; - - case 'C': - if (siscanf(escapeseq, "%dC", ¶meter) < 1) - parameter = 1; - consoleMoveCursorX(parameter); - return escapelen; - - case 'D': - if (siscanf(escapeseq, "%dD", ¶meter) < 1) - parameter = 1; - consoleMoveCursorX(-parameter); - return escapelen; - - // Cursor position movement - case 'H': - case 'f': { - int x, y; - if (siscanf(escapeseq, "%d;%d", &y, &x) == 2) - consoleSetCursorPos(x, y); - else - consoleSetCursorPos(0, 0); - return escapelen; - } +static void consoleParseCsiSequence(void) { + PrintConsole *const c = currentConsole; + const char *const seq = c->escBuf + 2; // Skip "\e[" + const int len = c->escBufLen; + + // The last character decides the function of the sequence + const char command = c->escBuf[c->escBufLen - 1]; + + switch (command) { + // Private modes - commonly end in 'h' or 'l'. We don't implement any (yet), so just consume them. + case 'h': + case 'l': + break; + + // Move cursor up + case 'A': { + int dy = 1; + siscanf(seq, "%dA", &dy); + consoleMoveCursorY(-dy); + break; + } + + // Move cursor down + case 'B': { + int dy = 1; + siscanf(seq, "%dB", &dy); + consoleMoveCursorY(dy); + break; + } + + // Move cursor right + case 'C': { + int dx = 1; + siscanf(seq, "%dC", &dx); + consoleMoveCursorX(dx); + break; + } + + // Move cursor left + case 'D': { + int dx = 1; + siscanf(seq, "%dD", &dx); + consoleMoveCursorX(-dx); + break; + } + + // Set cursor position + case 'H': + case 'f': { + int x, y; + if (siscanf(seq, "%d;%d", &y, &x) == 2) + consoleSetCursorPos(x, y); + else + consoleSetCursorPos(0, 0); + break; + } + + // Screen clear + case 'J': + consoleCls(seq[len - 2]); + break; + + // Line clear + case 'K': + consoleClearLine(seq[len - 2]); + break; + + // Save cursor position + case 's': + c->prevCursorX = c->cursorX; + c->prevCursorY = c->cursorY; + break; + + // Load cursor position + case 'u': + consoleSetCursorPos(c->prevCursorX, c->prevCursorY); + break; + + // Color/style modes + case 'm': + consoleParseColor(seq); + break; + } +} - // Screen clear - case 'J': - consoleCls(escapeseq[escapelen - 2]); - return escapelen; - - // Line clear - case 'K': - consoleClearLine(escapeseq[escapelen - 2]); - return escapelen; - - // Save cursor position - case 's': - currentConsole->prevCursorX = currentConsole->cursorX; - currentConsole->prevCursorY = currentConsole->cursorY; - return escapelen; - - // Load cursor position - case 'u': - // currentConsole->cursorX = currentConsole->prevCursorX; - // currentConsole->cursorY = currentConsole->prevCursorY; - consoleSetCursorPos(currentConsole->prevCursorX, currentConsole->prevCursorY); - return escapelen; - - // Color/style modes - case 'm': - consoleParseColor(escapeseq, escapelen); - return escapelen; +static void dumpAndResetEscBuf(void) { + PrintConsole *const c = currentConsole; + for (int i = 0; i < c->escBufLen; i++) + consolePrintChar(c->escBuf[i]); + c->escBufLen = 0; +} + +void consoleUpdateEscapeSequence(const char ch) { + PrintConsole *const c = currentConsole; + + if (!c) + return; + + if (ch == '\e') { + // We're most likely supposed to start buffering a new sequence. + dumpAndResetEscBuf(); + } + + // Append to buffer + c->escBuf[c->escBufLen++] = ch; + c->escBuf[c->escBufLen] = '\0'; // just to be safe + + // Do we only have an escape in the buffer? + if (c->escBufLen == 1 && c->escBuf[0] == '\e') + // Don't continue, we don't want to dump the buffer now. + return; + + // Do we have a CSI at the beginning of the buffer? + if (c->escBuf[0] == '\e' && c->escBuf[1] == '[') { + // Did we just form a full sequence? The sequences we parse all end in alphabetical characters. + if (isalpha(ch)) { + consoleParseCsiSequence(); + c->escBufLen = 0; } - } while (escapelen < len); + } else { + dumpAndResetEscBuf(); + } - // reached end of buffer! tell con_write to NOT add to its counter. - return 0; + if (c->escBufLen >= sizeof(c->escBuf) - 1) + // Escape sequences shouldn't be this long. + dumpAndResetEscBuf(); } diff --git a/source/arm9/console-priv.h b/source/arm9/console-priv.h index 969dc19..daecfba 100644 --- a/source/arm9/console-priv.h +++ b/source/arm9/console-priv.h @@ -53,4 +53,5 @@ void consoleSetCursorPos(int x, int y); void consoleDrawCursor(void); // console-esc.c +void consoleUpdateEscapeSequence(char c); int consoleParseEscapeSequence(const char *ptr, int len); diff --git a/source/arm9/console.c b/source/arm9/console.c index 6bdc01f..0bdfa08 100644 --- a/source/arm9/console.c +++ b/source/arm9/console.c @@ -39,45 +39,51 @@ distribution. #include "console-priv.h" -PrintConsole defaultConsole = -{ - //Font: - { - (u16*)default_font_bin, //font gfx - 0, //font palette - 0, //font color count - 1, //bpp - 0, //first ascii character in the set - 256, //number of characters in the font set - true //convert single color +const PrintConsole defaultConsole = { + .font = { + .gfx = (u16*)default_font_bin, + .pal = 0, + .numColors = 0, + .bpp = 1, + .asciiOffset = 0, + .numChars = 256, + .convertSingleColor = true }, - 0, //font background map - 0, //font background gfx - 22, //map base - 3, //char base - 0, //bg layer in use - -1, //bg id - 0,0, //cursorX cursorY - 0,0, //prevcursorX prevcursorY - 32, //console width - 24, //console height - 0, //window x - 0, //window y - 32, //window width - 24, //window height - 3, //tab size - 0, //font character offset - 0, //selected palette - 0, //print callback - false, //console initialized - true, //load graphics + .fontBgMap = 0, + .fontBgGfx = 0, + .mapBase = 22, + .gfxBase = 3, + .bgLayer = 0, + .bgId = -1, + .cursorX = 0, + .cursorY = 0, + .prevCursorX = 0, + .prevCursorY = 0, + .consoleWidth = 32, + .consoleHeight = 24, + .windowX = 0, + .windowY = 0, + .windowWidth = 32, + .windowHeight = 24, + .tabSize = 3, + .fontCharOffset = 0, + .fontCurPal = 0, + .PrintChar = NULL, + .consoleInitialised = false, + .loadGraphics = true, + .bg2Id = -1, + .fontBg2Map = NULL, + .fontBg2Gfx = NULL, + .fontCurPal2 = 0, + .escBuf = {}, + .escBufLen = 0 }; PrintConsole currentCopy; PrintConsole* currentConsole = ¤tCopy; -PrintConsole* consoleGetDefault(void){return &defaultConsole;} +const PrintConsole* consoleGetDefault(void){return &defaultConsole;} //--------------------------------------------------------------------------------- @@ -163,42 +169,19 @@ ssize_t nocash_write(struct _reent *r, void *fd, const char *ptr, size_t len) { return len; } +ssize_t con_write(struct _reent *r, void *fd, const char *ptr, size_t len) { + if (!ptr || len <= 0) + return -1; -//--------------------------------------------------------------------------------- -ssize_t con_write(struct _reent *r,void *fd,const char *ptr, size_t len) { -//--------------------------------------------------------------------------------- - - char chr; - - int i, count = 0; - char *tmp = (char*)ptr; - - if(!tmp || len<=0) return -1; - - i = 0; - - while(i 0) { - tmp += consumed; - i += consumed; - count += consumed; - continue; - } - } - - consolePrintChar(chr); + for (size_t i = 0; i < len; ++i) { + const char chr = ptr[i]; + if (chr == '\e' || currentConsole->escBufLen > 0) + consoleUpdateEscapeSequence(chr); + else + consolePrintChar(chr); } - return count; + return len; } static const devoptab_t dotab_stdout = { @@ -406,6 +389,7 @@ PrintConsole* consoleInit(PrintConsole* console, int layer, console->fontBg2Map = bgGetMapPtr(console->bg2Id); } + console->escBufLen = 0; console->consoleInitialised = 1; consoleCls('2'); From 2a6bce85dfedfa0d32ff1b68d9c4655a20e2a5de Mon Sep 17 00:00:00 2001 From: trustytrojan <87675609+trustytrojan@users.noreply.github.com> Date: Mon, 11 Aug 2025 14:42:37 -0400 Subject: [PATCH 12/12] Fix `consoleClearLine` mode usage --- source/arm9/console.c | 57 +++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/source/arm9/console.c b/source/arm9/console.c index 0bdfa08..cb865e2 100644 --- a/source/arm9/console.c +++ b/source/arm9/console.c @@ -137,29 +137,34 @@ void consoleCls(char mode) { } } } -//--------------------------------------------------------------------------------- + void consoleClearLine(char mode) { -//--------------------------------------------------------------------------------- - int i = 0; - int line = currentConsole->cursorY; - int start = 0; - int end = currentConsole->windowWidth; - - if (mode == '1') { - end = currentConsole->cursorX; - } else if (mode == '0') { - start = currentConsole->cursorX; - } - - // The character offset for ' ' in the font. - u16 blank = currentConsole->fontCharOffset - currentConsole->font.asciiOffset; - - for (i = start; i < end; i++) { - // Clear with default colors (fg: white, bg: black) regardless of current palette. - *consoleFontBgMapAt(i, line) = (15 << 12) | blank; - if (currentConsole->bg2Id != -1) - *consoleFontBg2MapAt(i, line) = (0 << 12) | blank; - } + int line = currentConsole->cursorY; + + // \e[K is same as \e[0K: from cursor to end of line + // so use its parameters as the default. + int start = currentConsole->cursorX; + int end = currentConsole->windowWidth; + + if (mode == '1') { + // start of line to cursor + start = 0; + end = currentConsole->cursorX; + } else if (mode == '2') { + // whole line + start = 0; + end = currentConsole->windowWidth; + } + + // The character offset for ' ' in the font. + const u16 blank = ' ' + currentConsole->fontCharOffset - currentConsole->font.asciiOffset; + + for (int i = start; i < end; ++i) { + // Clear with default colors (fg: white, bg: black) regardless of current palette. + *consoleFontBgMapAt(i, line) = (15 << 12) | blank; + if (currentConsole->bg2Id != -1) + *consoleFontBg2MapAt(i, line) = (0 << 12) | blank; + } } //--------------------------------------------------------------------------------- @@ -446,17 +451,15 @@ void consoleDebugInit(DebugDevice device){ //--------------------------------------------------------------------------------- // Places the console in a default mode using bg0 of the sub display, and vram c for // font and map..this is provided for rapid prototyping and nothing more -PrintConsole* consoleDemoInit(void) { //--------------------------------------------------------------------------------- +PrintConsole* consoleDemoInit(void) { videoSetModeSub(MODE_0_2D); vramSetBankC(VRAM_C_SUB_BG); return consoleInit(NULL, defaultConsole.bgLayer, BgType_Text4bpp, BgSize_T_256x256, defaultConsole.mapBase, defaultConsole.gfxBase, false, true, false); } -//--------------------------------------------------------------------------------- void newRow() { -//--------------------------------------------------------------------------------- if (currentConsole->bg2Id != -1) consoleRestoreTileUnderCursor(); @@ -471,16 +474,12 @@ void newRow() { for(rowCount = 0; rowCount < currentConsole->windowHeight - 1; rowCount++) for(colCount = 0; colCount < currentConsole->windowWidth; colCount++) { *consoleFontBgMapAt(colCount, rowCount) = *consoleFontBgMapAt(colCount, rowCount + 1); - // currentConsole->fontBgMap[(colCount + currentConsole->windowX) + (rowCount + currentConsole->windowY) * currentConsole->consoleWidth] = - // currentConsole->fontBgMap[(colCount + currentConsole->windowX) + (rowCount + currentConsole->windowY + 1) * currentConsole->consoleWidth]; if (currentConsole->bg2Id != -1) *consoleFontBg2MapAt(colCount, rowCount) = *consoleFontBg2MapAt(colCount, rowCount + 1); } for(colCount = 0; colCount < currentConsole->windowWidth; colCount++) { *consoleFontBgMapAt(colCount, rowCount) = consoleComputeFontBgMapValue(' '); - // currentConsole->fontBgMap[(colCount + currentConsole->windowX) + (rowCount + currentConsole->windowY) * currentConsole->consoleWidth] = - // (' ' + currentConsole->fontCharOffset - currentConsole->font.asciiOffset); if (currentConsole->bg2Id != -1) *consoleFontBg2MapAt(colCount, rowCount) = consoleComputeFontBg2MapValue(' '); }