diff --git a/libctru/data/default_font.bin b/libctru/data/default_font.bin new file mode 100644 index 0000000..73436fc Binary files /dev/null and b/libctru/data/default_font.bin differ diff --git a/libctru/include/3ds.h b/libctru/include/3ds.h index beec914..d128d25 100644 --- a/libctru/include/3ds.h +++ b/libctru/include/3ds.h @@ -11,6 +11,7 @@ extern "C" { #include <3ds/linear.h> #include <3ds/os.h> #include <3ds/gfx.h> +#include <3ds/console.h> #include <3ds/services/ac.h> #include <3ds/services/apt.h> diff --git a/libctru/include/3ds/console.h b/libctru/include/3ds/console.h new file mode 100644 index 0000000..f8bcde6 --- /dev/null +++ b/libctru/include/3ds/console.h @@ -0,0 +1,139 @@ + + +/*! \file console.h + \brief 3ds stdio support. + +
+Provides stdio integration for printing to the 3DS screen as well as debug print +functionality provided by stderr. + +General usage is to initialize the console by: +consoleDemoInit() +or to customize the console usage by: +consoleInit() + +*/ + +#ifndef CONSOLE_H +#define CONSOLE_H + +#include <3ds/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef bool(* ConsolePrint)(void* con, char c); + +//! a font struct for the console. +typedef struct ConsoleFont +{ + u8* gfx; //!< A pointer to the font graphics + u16 asciiOffset; //!< Offset to the first valid character in the font table + u16 numChars; //!< Number of characters in the font graphics + +}ConsoleFont; + +/** \brief console structure used to store the state of a console render context. + +Default values from consoleGetDefault(); +
+PrintConsole defaultConsole =
+{
+	//Font:
+	{
+		(u8*)default_font_bin, //font gfx
+		0, //first ascii character in the set
+		128, //number of characters in the font set
+	},
+	0,0, //cursorX cursorY
+	0,0, //prevcursorX prevcursorY
+	40, //console width
+	30, //console height
+	0,  //window x
+	0,  //window y
+	32, //window width
+	24, //window height
+	3, //tab size
+	0, //font character offset
+	0,  //print callback
+	false //console initialized
+};
+
+*/ +typedef struct PrintConsole +{ + ConsoleFont font; //!< font of the console. + + u16 *frameBuffer; //!< framebuffer address. + + int cursorX; /*!< Current X location of the cursor (as a tile offset by default) */ + int cursorY; /*!< Current Y location of the cursor (as a tile offset by default) */ + + int prevCursorX; /*!< Internal state */ + int prevCursorY; /*!< Internal state */ + + int consoleWidth; /*!< Width of the console hardware layer in characters */ + int consoleHeight; /*!< Height of the console hardware layer in characters */ + + int windowX; /*!< Window X location in characters (not implemented) */ + int windowY; /*!< Window Y location in characters (not implemented) */ + int windowWidth; /*!< Window width in characters (not implemented) */ + int windowHeight; /*!< Window height in characters (not implemented) */ + + int tabSize; /*!< Size of a tab*/ + int fg; /*!< foreground color*/ + int bg; /*!< background color*/ + int flags; /*!< reverse/bright flags*/ + + ConsolePrint PrintChar; /*!< callback for printing a character. Should return true if it has handled rendering the graphics + (else the print engine will attempt to render via tiles) */ + + bool consoleInitialised; /*!< True if the console is initialized */ +}PrintConsole; + +#define CONSOLE_COLOR_BRIGHT (1<<0) +#define CONSOLE_COLOR_REVERSE (1<<1) + +/*! \brief Loads the font into the console + \param console pointer to the console to update, if NULL it will update the current console + \param font the font to load +*/ +void consoleSetFont(PrintConsole* console, ConsoleFont* font); + +/*! \brief Sets the print window + \param console console to set, if NULL it will set the current console window + \param x x location of the window + \param y y location of the window + \param width width of the window + \param height height of the window +*/ +void consoleSetWindow(PrintConsole* console, int x, int y, int width, int height); + +/*! \brief Gets a pointer to the console with the default values + 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); + +/*! \brief Make the specified console the render target + \param console A pointer to the console struct (must have been initialized with consoleInit(PrintConsole* console) + \return a pointer to the previous console +*/ +PrintConsole *consoleSelect(PrintConsole* console); + +/*! \brief Initialise the console. + \param screen The screen to use for the console + \param console A pointer to the console data to initialze (if it's NULL, the default console will be used) + \return A pointer to the current console. +*/ +PrintConsole* consoleInit(gfxScreen_t screen, PrintConsole* console); + +//! Clears the screan by using iprintf("\x1b[2J"); +void consoleClear(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libctru/include/3ds/gfx.h b/libctru/include/3ds/gfx.h index 1d426d0..7074953 100644 --- a/libctru/include/3ds/gfx.h +++ b/libctru/include/3ds/gfx.h @@ -2,6 +2,8 @@ #include <3ds/types.h> #include <3ds/services/gsp.h> +#define RGB565(r,g,b) (((b)&0x1f)|(((g)&0x3f)<<5)|(((r)&0x1f)<<11)) + typedef enum { GFX_TOP = 0, diff --git a/libctru/include/3ds/svc.h b/libctru/include/3ds/svc.h index ab0230c..271408f 100644 --- a/libctru/include/3ds/svc.h +++ b/libctru/include/3ds/svc.h @@ -16,10 +16,11 @@ typedef enum { } MemOp; typedef enum { - MEMPERM_READ =1, - MEMPERM_WRITE =2, - MEMPERM_EXECUTE=4, - MEMPERM_MAX =0xFFFFFFFF //force 4-byte + MEMPERM_READ = 1, + MEMPERM_WRITE = 2, + MEMPERM_EXECUTE = 4, + MEMPERM_DONTCARE = 0x10000000, + MEMPERM_MAX = 0xFFFFFFFF //force 4-byte } MemPerm; typedef struct { diff --git a/libctru/source/console.c b/libctru/source/console.c new file mode 100644 index 0000000..087bce7 --- /dev/null +++ b/libctru/source/console.c @@ -0,0 +1,565 @@ +#include +#include +#include +#include <3ds/gfx.h> +#include <3ds/console.h> + +#include "default_font_bin.h" + +//set up the palette for color printing +static u16 colorTable[] = { + RGB565( 0, 0, 0), // normal black + RGB565(17, 0, 0), // normal red + RGB565( 0,34, 0), // normal green + RGB565(17,34, 0), // normal yellow + RGB565( 0, 0,17), // normal blue + RGB565(17, 0,17), // normal magenta + RGB565( 0,34,17), // normal cyan + RGB565(17,34,17), // normal white + RGB565( 0, 0, 0), // bright black + RGB565(25, 0, 0), // bright red + RGB565( 0,52, 0), // bright green + RGB565(25,52, 0), // bright yellow + RGB565( 4,18,31), // bright blue + RGB565(25, 0,25), // bright magenta + RGB565( 0,52,25), // bright cyan + RGB565(28,57,28) // bright white +}; + +PrintConsole defaultConsole = +{ + //Font: + { + (u8*)default_font_bin, //font gfx + 0, //first ascii character in the set + 128 //number of characters in the font set + }, + (u16*)NULL, + 0,0, //cursorX cursorY + 0,0, //prevcursorX prevcursorY + 40, //console width + 30, //console height + 0, //window x + 0, //window y + 40, //window width + 30, //window height + 3, //tab size + 7, // foreground color + 0, // background color + CONSOLE_COLOR_BRIGHT, // flags + 0, //print callback + false //console initialized +}; + +PrintConsole currentCopy; + +PrintConsole* currentConsole = ¤tCopy; + +PrintConsole* consoleGetDefault(void){return &defaultConsole;} + +void consolePrintChar(char c); +void consoleDrawChar(int c); + +//--------------------------------------------------------------------------------- +static void consoleCls(char mode) { +//--------------------------------------------------------------------------------- + + int i = 0; + int colTemp,rowTemp; + + switch (mode) + { + case '[': + case '0': + { + colTemp = currentConsole->cursorX ; + rowTemp = currentConsole->cursorY ; + + while(i++ < ((currentConsole->windowHeight * currentConsole->windowWidth) - (rowTemp * currentConsole->consoleWidth + colTemp))) + consolePrintChar(' '); + + currentConsole->cursorX = colTemp; + currentConsole->cursorY = rowTemp; + break; + } + case '1': + { + colTemp = currentConsole->cursorX ; + rowTemp = currentConsole->cursorY ; + + currentConsole->cursorY = 0; + currentConsole->cursorX = 0; + + while (i++ < (rowTemp * currentConsole->windowWidth + colTemp)) + consolePrintChar(' '); + + currentConsole->cursorX = colTemp; + currentConsole->cursorY = rowTemp; + break; + } + case '2': + { + currentConsole->cursorY = 0; + currentConsole->cursorX = 0; + + while(i++ < currentConsole->windowHeight * currentConsole->windowWidth) + consolePrintChar(' '); + + currentConsole->cursorY = 0; + currentConsole->cursorX = 0; + break; + } + } +} +//--------------------------------------------------------------------------------- +static 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; + } + } +} + + +//--------------------------------------------------------------------------------- +ssize_t con_write(struct _reent *r,int 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(icursorY = (currentConsole->cursorY - parameter) < 0 ? 0 : currentConsole->cursorY - parameter; + escaping = false; + break; + case 'B': + sscanf(escapeseq,"[%dB", ¶meter); + currentConsole->cursorY = (currentConsole->cursorY + parameter) > currentConsole->windowHeight - 1 ? currentConsole->windowHeight - 1 : currentConsole->cursorY + parameter; + escaping = false; + break; + case 'C': + sscanf(escapeseq,"[%dC", ¶meter); + currentConsole->cursorX = (currentConsole->cursorX + parameter) > currentConsole->windowWidth - 1 ? currentConsole->windowWidth - 1 : currentConsole->cursorX + parameter; + escaping = false; + break; + case 'D': + sscanf(escapeseq,"[%dD", ¶meter); + currentConsole->cursorX = (currentConsole->cursorX - parameter) < 0 ? 0 : currentConsole->cursorX - parameter; + escaping = false; + break; + //--------------------------------------- + // Cursor position movement + //--------------------------------------- + case 'H': + case 'f': + sscanf(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': + escapeseq++; + escapelen--; + + if (escapelen == 1) { + escaping = false; + break; + } + + do { + if (strchr(escapeseq,';')) { + sscanf(escapeseq,"%d;%n", ¶meter, &consumed); + } else { + sscanf(escapeseq,"%dm%n", ¶meter, &consumed); + } + + escapeseq += consumed; + escapelen -= consumed; + + if (parameter == 0 ) { + currentConsole->flags |= CONSOLE_COLOR_BRIGHT; + currentConsole->flags &= ~CONSOLE_COLOR_REVERSE; + currentConsole->bg = 0; + currentConsole->fg = 7; + } else if (parameter == 7) { // reverse video + currentConsole->flags |= CONSOLE_COLOR_REVERSE; + } else if (parameter == 2) { // half bright + currentConsole->flags &= ~CONSOLE_COLOR_BRIGHT; + } else if (parameter >= 30 && parameter <= 37) { // writing color + currentConsole->fg = parameter - 30; + } else if (parameter >= 40 && parameter <= 47) { // screen color + currentConsole->bg = parameter - 40; + } + } while (escapelen > 0); + + escaping = false; + break; + } + } while (escaping); + continue; + } + + consolePrintChar(chr); + } + + return count; +} + +static const devoptab_t dotab_stdout = { + "con", + 0, + NULL, + NULL, + con_write, + NULL, + NULL, + NULL +}; + +static const devoptab_t dotab_null = { + "null", + 0, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +//--------------------------------------------------------------------------------- +PrintConsole* consoleInit(gfxScreen_t screen, PrintConsole* console) { +//--------------------------------------------------------------------------------- + + static bool firstConsoleInit = true; + + if(firstConsoleInit) { + devoptab_list[STD_OUT] = &dotab_stdout; + devoptab_list[STD_ERR] = &dotab_stdout; + + setvbuf(stdout, NULL , _IONBF, 0); + setvbuf(stderr, NULL , _IONBF, 0); + + firstConsoleInit = false; + } + + if(console) { + currentConsole = console; + } else { + console = currentConsole; + } + + *currentConsole = defaultConsole; + + console->consoleInitialised = 1; + + gfxSetScreenFormat(screen,GSP_RGB565_OES); + gfxSetDoubleBuffering(screen,false); + console->frameBuffer = (u16*)gfxGetFramebuffer(screen, GFX_LEFT, NULL, NULL); + + if(screen==GFX_TOP) { + console->consoleWidth = 50; + console->windowWidth = 50; + } + + + consoleCls('2'); + + return currentConsole; + +} +//--------------------------------------------------------------------------------- +PrintConsole *consoleSelect(PrintConsole* console){ +//--------------------------------------------------------------------------------- + PrintConsole *tmp = currentConsole; + currentConsole = console; + return tmp; +} + +//--------------------------------------------------------------------------------- +void consoleSetFont(PrintConsole* console, ConsoleFont* font){ +//--------------------------------------------------------------------------------- + + if(!console) console = currentConsole; + + console->font = *font; + +} + +//--------------------------------------------------------------------------------- +static void newRow() { +//--------------------------------------------------------------------------------- + + + currentConsole->cursorY ++; + + + if(currentConsole->cursorY >= currentConsole->windowHeight) { + currentConsole->cursorY --; + u16 *dst = ¤tConsole->frameBuffer[(currentConsole->windowX * 8 * 240) + (239 - (currentConsole->windowY * 8))]; + u16 *src = dst - 8; + + int i,j; + + for (i=0; iwindowWidth*8; i++) { + u32 *from = (u32*)((int)src & ~3); + u32 *to = (u32*)((int)dst & ~3); + for (j=0;j<(((currentConsole->windowHeight-1)*8)/2);j++) *(to--) = *(from--); + dst += 240; + src += 240; + } + + consoleClearLine('2'); + } +} +//--------------------------------------------------------------------------------- +void consoleDrawChar(int c) { +//--------------------------------------------------------------------------------- + c -= currentConsole->font.asciiOffset; + if ( c < 0 || c > currentConsole->font.numChars ) return; + + u8 *fontdata = currentConsole->font.gfx + (8 * c); + + int writingColor = currentConsole->fg; + int screenColor = currentConsole->bg; + + if (currentConsole->flags & CONSOLE_COLOR_BRIGHT) { + writingColor |= 8; + screenColor |=8; + } + + if (currentConsole->flags & CONSOLE_COLOR_REVERSE) { + int tmp = writingColor; + writingColor = screenColor; + screenColor = tmp; + } + + u16 bg = colorTable[screenColor]; + u16 fg = colorTable[writingColor]; + + u8 b1 = *(fontdata++); + u8 b2 = *(fontdata++); + u8 b3 = *(fontdata++); + u8 b4 = *(fontdata++); + u8 b5 = *(fontdata++); + u8 b6 = *(fontdata++); + u8 b7 = *(fontdata++); + u8 b8 = *(fontdata++); + + u8 mask = 0x80; + + + int i; + + int x = (currentConsole->cursorX + currentConsole->windowX) * 8; + int y = ((currentConsole->cursorY + currentConsole->windowY) *8 ); + + u16 *screen = ¤tConsole->frameBuffer[(x * 240) + (239 - (y + 7))]; + + for (i=0;i<8;i++) { + if (b8 & mask) { *(screen++) = fg; }else{ *(screen++) = bg; } + if (b7 & mask) { *(screen++) = fg; }else{ *(screen++) = bg; } + if (b6 & mask) { *(screen++) = fg; }else{ *(screen++) = bg; } + if (b5 & mask) { *(screen++) = fg; }else{ *(screen++) = bg; } + if (b4 & mask) { *(screen++) = fg; }else{ *(screen++) = bg; } + if (b3 & mask) { *(screen++) = fg; }else{ *(screen++) = bg; } + if (b2 & mask) { *(screen++) = fg; }else{ *(screen++) = bg; } + if (b1 & mask) { *(screen++) = fg; }else{ *(screen++) = bg; } + mask >>= 1; + screen += 240 - 8; + } + +} + +//--------------------------------------------------------------------------------- +void consolePrintChar(char c) { +//--------------------------------------------------------------------------------- + if (c==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; + } + } + + consoleDrawChar(' '); + break; + + case 9: + currentConsole->cursorX += currentConsole->tabSize - ((currentConsole->cursorX)%(currentConsole->tabSize)); + break; + case 10: + newRow(); + case 13: + currentConsole->cursorX = 0; + break; + default: + consoleDrawChar(c); + ++currentConsole->cursorX ; + break; + } +} + +//--------------------------------------------------------------------------------- +void consoleClear(void) { +//--------------------------------------------------------------------------------- + iprintf("\x1b[2J"); +} + +//--------------------------------------------------------------------------------- +void consoleSetWindow(PrintConsole* console, int x, int y, int width, int height){ +//--------------------------------------------------------------------------------- + + if(!console) console = currentConsole; + + console->windowWidth = width; + console->windowHeight = height; + console->windowX = x; + console->windowY = y; + + console->cursorX = 0; + console->cursorY = 0; + +} + + diff --git a/libctru/source/gfx.c b/libctru/source/gfx.c index 129903e..578cc32 100644 --- a/libctru/source/gfx.c +++ b/libctru/source/gfx.c @@ -81,7 +81,7 @@ void gfxWriteFramebufferInfo(gfxScreen_t screen) u8* framebufferInfoHeader=gfxSharedMemory+0x200+gfxThreadID*0x80; if(screen==GFX_BOTTOM)framebufferInfoHeader+=0x40; GSP_FramebufferInfo* framebufferInfo=(GSP_FramebufferInfo*)&framebufferInfoHeader[0x4]; - framebufferInfoHeader[0x0]^=doubleBuf[0x0]; + framebufferInfoHeader[0x0]^=doubleBuf[screen]; framebufferInfo[framebufferInfoHeader[0x0]]=(screen==GFX_TOP)?(topFramebufferInfo):(bottomFramebufferInfo); framebufferInfoHeader[0x1]=1; }