Get rid of selectScreen, allow clearing the screens separately, implement a PIN message function on the bottom screen (you can place a textfile named pinmessage.txt in /luma, with 800 characters max)

This commit is contained in:
Aurora 2016-09-13 16:22:26 +02:00
parent ffaa5790c2
commit 7952271d61
8 changed files with 113 additions and 97 deletions

View File

@ -119,8 +119,8 @@ void configMenu(bool oldPinStatus)
initScreens();
drawString(CONFIG_TITLE, 10, 10, COLOR_TITLE);
drawString("Press A to select, START to save", 10, 30, COLOR_WHITE);
drawString(CONFIG_TITLE, true, 10, 10, COLOR_TITLE);
drawString("Press A to select, START to save", true, 10, 30, COLOR_WHITE);
//Character to display a selected option
char selected = 'x';
@ -133,8 +133,8 @@ void configMenu(bool oldPinStatus)
if(!(i == NEWCPU && !isN3DS))
{
multiOptions[i].posY = endPos + SPACING_Y;
endPos = drawString(multiOptionsText[i], 10, multiOptions[i].posY, COLOR_WHITE);
drawCharacter(selected, 10 + multiOptions[i].posXs[multiOptions[i].enabled] * SPACING_X, multiOptions[i].posY, COLOR_WHITE);
endPos = drawString(multiOptionsText[i], true, 10, multiOptions[i].posY, COLOR_WHITE);
drawCharacter(selected, true, 10 + multiOptions[i].posXs[multiOptions[i].enabled] * SPACING_X, multiOptions[i].posY, COLOR_WHITE);
}
}
@ -145,8 +145,8 @@ void configMenu(bool oldPinStatus)
for(u32 i = 0; i < singleOptionsAmount; i++)
{
singleOptions[i].posY = endPos + SPACING_Y;
endPos = drawString(singleOptionsText[i], 10, singleOptions[i].posY, color);
if(singleOptions[i].enabled) drawCharacter(selected, 10 + SPACING_X, singleOptions[i].posY, color);
endPos = drawString(singleOptionsText[i], true, 10, singleOptions[i].posY, color);
if(singleOptions[i].enabled) drawCharacter(selected, true, 10 + SPACING_X, singleOptions[i].posY, color);
color = COLOR_WHITE;
}
@ -191,22 +191,22 @@ void configMenu(bool oldPinStatus)
//The user moved to a different option, print the old option in white and the new one in red. Only print 'x's if necessary
if(oldSelectedOption < multiOptionsAmount)
{
drawString(multiOptionsText[oldSelectedOption], 10, multiOptions[oldSelectedOption].posY, COLOR_WHITE);
drawCharacter(selected, 10 + multiOptions[oldSelectedOption].posXs[multiOptions[oldSelectedOption].enabled] * SPACING_X, multiOptions[oldSelectedOption].posY, COLOR_WHITE);
drawString(multiOptionsText[oldSelectedOption], true, 10, multiOptions[oldSelectedOption].posY, COLOR_WHITE);
drawCharacter(selected, true, 10 + multiOptions[oldSelectedOption].posXs[multiOptions[oldSelectedOption].enabled] * SPACING_X, multiOptions[oldSelectedOption].posY, COLOR_WHITE);
}
else
{
u32 singleOldSelected = oldSelectedOption - multiOptionsAmount;
drawString(singleOptionsText[singleOldSelected], 10, singleOptions[singleOldSelected].posY, COLOR_WHITE);
if(singleOptions[singleOldSelected].enabled) drawCharacter(selected, 10 + SPACING_X, singleOptions[singleOldSelected].posY, COLOR_WHITE);
drawString(singleOptionsText[singleOldSelected], true, 10, singleOptions[singleOldSelected].posY, COLOR_WHITE);
if(singleOptions[singleOldSelected].enabled) drawCharacter(selected, true, 10 + SPACING_X, singleOptions[singleOldSelected].posY, COLOR_WHITE);
}
if(selectedOption < multiOptionsAmount)
drawString(multiOptionsText[selectedOption], 10, multiOptions[selectedOption].posY, COLOR_RED);
drawString(multiOptionsText[selectedOption], true, 10, multiOptions[selectedOption].posY, COLOR_RED);
else
{
u32 singleSelected = selectedOption - multiOptionsAmount;
drawString(singleOptionsText[singleSelected], 10, singleOptions[singleSelected].posY, COLOR_RED);
drawString(singleOptionsText[singleSelected], true, 10, singleOptions[singleSelected].posY, COLOR_RED);
}
}
else
@ -215,7 +215,7 @@ void configMenu(bool oldPinStatus)
if(selectedOption < multiOptionsAmount)
{
u32 oldEnabled = multiOptions[selectedOption].enabled;
drawCharacter(selected, 10 + multiOptions[selectedOption].posXs[oldEnabled] * SPACING_X, multiOptions[selectedOption].posY, COLOR_BLACK);
drawCharacter(selected, true, 10 + multiOptions[selectedOption].posXs[oldEnabled] * SPACING_X, multiOptions[selectedOption].posY, COLOR_BLACK);
multiOptions[selectedOption].enabled = (oldEnabled == 3 || !multiOptions[selectedOption].posXs[oldEnabled + 1]) ? 0 : oldEnabled + 1;
if(selectedOption == BRIGHTNESS) updateBrightness(multiOptions[BRIGHTNESS].enabled);
@ -224,17 +224,17 @@ void configMenu(bool oldPinStatus)
{
bool oldEnabled = singleOptions[selectedOption - multiOptionsAmount].enabled;
singleOptions[selectedOption - multiOptionsAmount].enabled = !oldEnabled;
if(oldEnabled) drawCharacter(selected, 10 + SPACING_X, singleOptions[selectedOption - multiOptionsAmount].posY, COLOR_BLACK);
if(oldEnabled) drawCharacter(selected, true, 10 + SPACING_X, singleOptions[selectedOption - multiOptionsAmount].posY, COLOR_BLACK);
}
}
//In any case, if the current option is enabled (or a multiple choice option is selected) we must display a red 'x'
if(selectedOption < multiOptionsAmount)
drawCharacter(selected, 10 + multiOptions[selectedOption].posXs[multiOptions[selectedOption].enabled] * SPACING_X, multiOptions[selectedOption].posY, COLOR_RED);
drawCharacter(selected, true, 10 + multiOptions[selectedOption].posXs[multiOptions[selectedOption].enabled] * SPACING_X, multiOptions[selectedOption].posY, COLOR_RED);
else
{
u32 singleSelected = selectedOption - multiOptionsAmount;
if(singleOptions[singleSelected].enabled) drawCharacter(selected, 10 + SPACING_X, singleOptions[singleSelected].posY, COLOR_RED);
if(singleOptions[singleSelected].enabled) drawCharacter(selected, true, 10 + SPACING_X, singleOptions[singleSelected].posY, COLOR_RED);
}
}

View File

@ -32,15 +32,13 @@
#include "fs.h"
#include "font.h"
static bool bottomScreenSelected = false;
bool loadSplash(void)
{
u32 topSplashSize = getFileSize("/luma/splash.bin"),
bottomSplashSize = getFileSize("/luma/splashbottom.bin");
const char *topSplashPath = "/luma/splash.bin",
*bottomSplashPath = "/luma/splashbottom.bin";
bool isTopSplashValid = topSplashSize == SCREEN_TOP_FBSIZE,
isBottomSplashValid = bottomSplashSize == SCREEN_BOTTOM_FBSIZE;
bool isTopSplashValid = getFileSize(topSplashPath) == SCREEN_TOP_FBSIZE,
isBottomSplashValid = getFileSize(bottomSplashPath) == SCREEN_BOTTOM_FBSIZE;
//Don't delay boot nor init the screens if no splash images or invalid splash images are on the SD
if(!isTopSplashValid && !isBottomSplashValid)
@ -48,22 +46,17 @@ bool loadSplash(void)
initScreens();
if(isTopSplashValid) fileRead(fb->top_left, "/luma/splash.bin", topSplashSize);
if(isBottomSplashValid) fileRead(fb->bottom, "/luma/splashbottom.bin", bottomSplashSize);
if(isTopSplashValid) fileRead(fb->top_left, topSplashPath, 0);
if(isBottomSplashValid) fileRead(fb->bottom, bottomSplashPath, 0);
chrono(3);
return true;
}
void selectScreen(bool isBottomScreen)
void drawCharacter(char character, bool isTopScreen, u32 posX, u32 posY, u32 color)
{
bottomScreenSelected = isBottomScreen;
}
void drawCharacter(char character, u32 posX, u32 posY, u32 color)
{
u8 *const select = bottomScreenSelected ? fb->bottom : fb->top_left;
u8 *select = isTopScreen ? fb->top_left : fb->bottom;
for(u32 y = 0; y < 8; y++)
{
@ -81,7 +74,7 @@ void drawCharacter(char character, u32 posX, u32 posY, u32 color)
}
}
u32 drawString(const char *string, u32 posX, u32 posY, u32 color)
u32 drawString(const char *string, bool isTopScreen, u32 posX, u32 posY, u32 color)
{
for(u32 i = 0, line_i = 0; i < strlen(string); i++, line_i++)
{
@ -91,15 +84,15 @@ u32 drawString(const char *string, u32 posX, u32 posY, u32 color)
line_i = 0;
i++;
}
else if(line_i >= ((bottomScreenSelected ? SCREEN_BOTTOM_WIDTH : SCREEN_TOP_WIDTH) - posX) / SPACING_X)
else if(line_i >= ((isTopScreen ? SCREEN_TOP_WIDTH : SCREEN_BOTTOM_WIDTH) - posX) / SPACING_X)
{
//Make sure we never get out of the screen
posY += SPACING_Y;
line_i = 2; //Little offset so we know the same string continues
line_i = 1; //Little offset so we know the same string continues
if(string[i] == ' ') i++; //Spaces at the start look weird
}
drawCharacter(string[i], posX + line_i * SPACING_X, posY, color);
drawCharacter(string[i], isTopScreen, posX + line_i * SPACING_X, posY, color);
}
return posY;

View File

@ -45,6 +45,5 @@
#define COLOR_YELLOW 0x00FFFF
bool loadSplash(void);
void selectScreen(bool isBottomScreen);
void drawCharacter(char character, u32 posX, u32 posY, u32 color);
u32 drawString(const char *string, u32 posX, u32 posY, u32 color);
void drawCharacter(char character, bool isTopScreen, u32 posX, u32 posY, u32 color);
u32 drawString(const char *string, bool isTopScreen, u32 posX, u32 posY, u32 color);

View File

@ -107,25 +107,25 @@ void detectAndProcessExceptionDumps(void)
initScreens();
drawString("An exception occurred", 10, 10, COLOR_RED);
u32 posY = drawString(dumpHeader->processor == 11 ? "Processor: ARM11 (core )" : "Processor: ARM9", 10, 30, COLOR_WHITE);
if(dumpHeader->processor == 11) drawCharacter('0' + dumpHeader->core, 10 + 29 * SPACING_X, 30, COLOR_WHITE);
drawString("An exception occurred", true, 10, 10, COLOR_RED);
u32 posY = drawString(dumpHeader->processor == 11 ? "Processor: ARM11 (core )" : "Processor: ARM9", true, 10, 30, COLOR_WHITE);
if(dumpHeader->processor == 11) drawCharacter('0' + dumpHeader->core, true, 10 + 29 * SPACING_X, 30, COLOR_WHITE);
posY = drawString("Exception type: ", 10, posY + SPACING_Y, COLOR_WHITE);
drawString(handledExceptionNames[dumpHeader->type], 10 + 17 * SPACING_X, posY, COLOR_WHITE);
posY = drawString("Exception type: ", true, 10, posY + SPACING_Y, COLOR_WHITE);
drawString(handledExceptionNames[dumpHeader->type], true, 10 + 17 * SPACING_X, posY, COLOR_WHITE);
if(dumpHeader->type == 2)
{
if((regs[16] & 0x20) == 0 && dumpHeader->codeDumpSize >= 4)
{
u32 instr = *(vu32 *)(stackDump - 4);
if(instr == 0xE12FFF7E) drawString(specialExceptions[0], 10 + 32 * SPACING_X, posY, COLOR_WHITE);
else if(instr == 0xEF00003C) drawString(specialExceptions[1], 10 + 32 * SPACING_X, posY, COLOR_WHITE);
if(instr == 0xE12FFF7E) drawString(specialExceptions[0], true, 10 + 32 * SPACING_X, posY, COLOR_WHITE);
else if(instr == 0xEF00003C) drawString(specialExceptions[1], true, 10 + 32 * SPACING_X, posY, COLOR_WHITE);
}
else if((regs[16] & 0x20) == 0 && dumpHeader->codeDumpSize >= 2)
{
u16 instr = *(vu16 *)(stackDump - 2);
if(instr == 0xDF3C) drawString(specialExceptions[1], 10 + 32 * SPACING_X, posY, COLOR_WHITE);
if(instr == 0xDF3C) drawString(specialExceptions[1], true, 10 + 32 * SPACING_X, posY, COLOR_WHITE);
}
}
@ -133,22 +133,22 @@ void detectAndProcessExceptionDumps(void)
{
char processName[] = "Current process: ";
memcpy(processName + sizeof(processName) - 9, (void *)additionalData, 8);
posY = drawString(processName, 10, posY + SPACING_Y, COLOR_WHITE);
posY = drawString(processName, true, 10, posY + SPACING_Y, COLOR_WHITE);
}
posY += SPACING_Y;
for(u32 i = 0; i < 17; i += 2)
{
posY = drawString(registerNames[i], 10, posY + SPACING_Y, COLOR_WHITE);
posY = drawString(registerNames[i], true, 10, posY + SPACING_Y, COLOR_WHITE);
hexItoa(regs[i], hexString, 8);
drawString(hexString, 10 + 7 * SPACING_X, posY, COLOR_WHITE);
drawString(hexString, true, 10 + 7 * SPACING_X, posY, COLOR_WHITE);
if(i != 16 || dumpHeader->processor != 9)
{
drawString(registerNames[i + 1], 10 + 22 * SPACING_X, posY, COLOR_WHITE);
drawString(registerNames[i + 1], true, 10 + 22 * SPACING_X, posY, COLOR_WHITE);
hexItoa(i == 16 ? regs[20] : regs[i + 1], hexString, 8);
drawString(hexString, 10 + 29 * SPACING_X, posY, COLOR_WHITE);
drawString(hexString, true, 10 + 29 * SPACING_X, posY, COLOR_WHITE);
}
}
@ -156,28 +156,24 @@ void detectAndProcessExceptionDumps(void)
u32 mode = regs[16] & 0xF;
if(dumpHeader->type == 3 && (mode == 7 || mode == 11))
posY = drawString("Incorrect dump: failed to dump code and/or stack", 10, posY + SPACING_Y, COLOR_YELLOW) + SPACING_Y;
posY = drawString("Incorrect dump: failed to dump code and/or stack", true, 10, posY + SPACING_Y, COLOR_YELLOW) + SPACING_Y;
selectScreen(true);
u32 posYBottom = drawString("Stack dump:", 10, 10, COLOR_WHITE) + SPACING_Y;
u32 posYBottom = drawString("Stack dump:", false, 10, 10, COLOR_WHITE) + SPACING_Y;
for(u32 line = 0; line < 19 && stackDump < additionalData; line++)
{
hexItoa(regs[13] + 8 * line, hexString, 8);
posYBottom = drawString(hexString, 10, posYBottom + SPACING_Y, COLOR_WHITE);
drawCharacter(':', 10 + 8 * SPACING_X, posYBottom, COLOR_WHITE);
posYBottom = drawString(hexString, false, 10, posYBottom + SPACING_Y, COLOR_WHITE);
drawCharacter(':', false, 10 + 8 * SPACING_X, posYBottom, COLOR_WHITE);
for(u32 i = 0; i < 8 && stackDump < additionalData; i++, stackDump++)
{
char byteString[] = "00";
hexItoa(*stackDump, byteString, 2);
drawString(byteString, 10 + 10 * SPACING_X + 3 * i * SPACING_X, posYBottom, COLOR_WHITE);
drawString(byteString, false, 10 + 10 * SPACING_X + 3 * i * SPACING_X, posYBottom, COLOR_WHITE);
}
}
selectScreen(false);
char path[42];
char fileName[] = "crash_dump_00000000.dmp";
const char *pathFolder = dumpHeader->processor == 9 ? "/luma/dumps/arm9" : "/luma/dumps/arm11";
@ -189,12 +185,12 @@ void detectAndProcessExceptionDumps(void)
if(fileWrite((void *)dumpHeader, path, dumpHeader->totalSize))
{
posY = drawString("You can find a dump in the following file:", 10, posY + SPACING_Y, COLOR_WHITE);
posY = drawString(path, 10, posY + SPACING_Y, COLOR_WHITE) + SPACING_Y;
posY = drawString("You can find a dump in the following file:", true, 10, posY + SPACING_Y, COLOR_WHITE);
posY = drawString(path, true, 10, posY + SPACING_Y, COLOR_WHITE) + SPACING_Y;
}
else posY = drawString("Error writing the dump file", 10, posY + SPACING_Y, COLOR_RED);
else posY = drawString("Error writing the dump file", true, 10, posY + SPACING_Y, COLOR_RED);
drawString("Press any button to shutdown", 10, posY + SPACING_Y, COLOR_WHITE);
drawString("Press any button to shutdown", true, 10, posY + SPACING_Y, COLOR_WHITE);
memset32((void *)dumpHeader, 0, dumpHeader->totalSize);

View File

@ -46,14 +46,14 @@ static char pinKeyToLetter(u32 pressed)
void newPin(bool allowSkipping)
{
clearScreens();
clearScreens(true, true);
u8 length = 4 + 2 * (MULTICONFIG(PIN) - 1);
char *title = allowSkipping ? "Press START to skip or enter a new PIN" : "Enter a new PIN to proceed";
drawString(title, 10, 10, COLOR_TITLE);
drawString("PIN ( digits): ", 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
drawCharacter('0' + length, 10 + 5 * SPACING_X, 10 + 2 * SPACING_Y, COLOR_WHITE);
drawString(title, true, 10, 10, COLOR_TITLE);
drawString("PIN ( digits): ", true, 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
drawCharacter('0' + length, true, 10 + 5 * SPACING_X, 10 + 2 * SPACING_Y, COLOR_WHITE);
//Pad to AES block length with zeroes
u8 __attribute__((aligned(4))) enteredPassword[0x10] = {0};
@ -80,7 +80,7 @@ void newPin(bool allowSkipping)
enteredPassword[cnt++] = (u8)key; //Add character to password
//Visualize character on screen
drawCharacter(key, 10 + charDrawPos, 10 + 2 * SPACING_Y, COLOR_WHITE);
drawCharacter(key, true, 10 + charDrawPos, 10 + 2 * SPACING_Y, COLOR_WHITE);
charDrawPos += 2 * SPACING_X;
}
@ -132,11 +132,24 @@ bool verifyPin(void)
u8 cnt = 0;
int charDrawPos = 16 * SPACING_X;
const char *messagePath = "/luma/pinmessage.txt";
u32 messageSize = getFileSize(messagePath);
if(messageSize > 0 && messageSize < 800)
{
char message[messageSize + 1];
fileRead(message, messagePath, 0);
message[messageSize] = 0;
drawString(message, false, 10, 10, COLOR_WHITE);
}
while(!unlock)
{
drawString("Press START to shutdown or enter PIN to proceed", 10, 10, COLOR_TITLE);
drawString("PIN ( digits): ", 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
drawCharacter('0' + pin.length, 10 + 5 * SPACING_X, 10 + 2 * SPACING_Y, COLOR_WHITE);
drawString("Press START to shutdown or enter PIN to proceed", true, 10, 10, COLOR_TITLE);
drawString("PIN ( digits): ", true, 10, 10 + 2 * SPACING_Y, COLOR_WHITE);
drawCharacter('0' + pin.length, true, 10 + 5 * SPACING_X, 10 + 2 * SPACING_Y, COLOR_WHITE);
u32 pressed;
do
@ -155,7 +168,7 @@ bool verifyPin(void)
enteredPassword[cnt++] = (u8)key; //Add character to password
//Visualize character on screen
drawCharacter(key, 10 + charDrawPos, 10 + 2 * SPACING_Y, COLOR_WHITE);
drawCharacter(key, true, 10 + charDrawPos, 10 + 2 * SPACING_Y, COLOR_WHITE);
charDrawPos += 2 * SPACING_X;
if(cnt >= pin.length)
@ -168,9 +181,9 @@ bool verifyPin(void)
charDrawPos = 16 * SPACING_X;
cnt = 0;
clearScreens();
clearScreens(true, false);
drawString("Wrong PIN, try again", 10, 10 + 4 * SPACING_Y, COLOR_RED);
drawString("Wrong PIN, try again", true, 10, 10 + 4 * SPACING_Y, COLOR_RED);
}
}
}

View File

@ -101,29 +101,42 @@ void updateBrightness(u32 brightnessIndex)
invokeArm11Function(ARM11);
}
void clearScreens(void)
void clearScreens(bool clearTop, bool clearBottom)
{
static bool clearTopTmp,
clearBottomTmp;
clearTopTmp = clearTop;
clearBottomTmp = clearBottom;
void __attribute__((naked)) ARM11(void)
{
//Disable interrupts
__asm(".word 0xF10C01C0");
//Setting up two simultaneous memory fills using the GPU
vu32 *REGs_PSC0 = (vu32 *)0x10400010;
vu32 *REGs_PSC0 = (vu32 *)0x10400010,
*REGs_PSC1 = (vu32 *)0x10400020;
if(clearTopTmp)
{
REGs_PSC0[0] = (u32)fb->top_left >> 3; //Start address
REGs_PSC0[1] = (u32)(fb->top_left + SCREEN_TOP_FBSIZE) >> 3; //End address
REGs_PSC0[2] = 0; //Fill value
REGs_PSC0[3] = (2 << 8) | 1; //32-bit pattern; start
}
vu32 *REGs_PSC1 = (vu32 *)0x10400020;
if(clearBottomTmp)
{
REGs_PSC1[0] = (u32)fb->bottom >> 3; //Start address
REGs_PSC1[1] = (u32)(fb->bottom + SCREEN_BOTTOM_FBSIZE) >> 3; //End address
REGs_PSC1[2] = 0; //Fill value
REGs_PSC1[3] = (2 << 8) | 1; //32-bit pattern; start
}
while(!((REGs_PSC0[3] & 2) && (REGs_PSC1[3] & 2)));
while(!((!clearTopTmp || (REGs_PSC0[3] & 2)) && (!clearBottomTmp || (REGs_PSC1[3] & 2))));
if(fb->top_right != fb->top_left)
if(fb->top_right != fb->top_left && clearTopTmp)
{
REGs_PSC0[0] = (u32)fb->top_right >> 3; //Start address
REGs_PSC0[1] = (u32)(fb->top_right + SCREEN_TOP_FBSIZE) >> 3; //End address
@ -136,6 +149,8 @@ void clearScreens(void)
WAIT_FOR_ARM9();
}
flushDCacheRange(&clearTopTmp, 1);
flushDCacheRange(&clearBottomTmp, 1);
flushDCacheRange((void *)fb, sizeof(struct fb));
invokeArm11Function(ARM11);
}
@ -246,14 +261,14 @@ void initScreens(void)
flushDCacheRange((void *)fb, sizeof(struct fb));
invokeArm11Function(ARM11);
clearScreens();
clearScreens(true, true);
//Turn on backlight
i2cWriteRegister(I2C_DEV_MCU, 0x22, 0x2A);
}
else
{
clearScreens();
clearScreens(true, true);
updateBrightness(MULTICONFIG(BRIGHTNESS));
}
}

View File

@ -42,5 +42,5 @@ static volatile struct fb {
void deinitScreens(void);
void updateBrightness(u32 brightnessIndex);
void clearScreens(void);
void clearScreens(bool clearTop, bool clearBottom);
void initScreens(void);

View File

@ -56,7 +56,7 @@ u32 waitInput(void)
void mcuReboot(void)
{
if(!isFirmlaunch && PDN_GPU_CNT != 1) clearScreens();
if(!isFirmlaunch && PDN_GPU_CNT != 1) clearScreens(true, true);
flushEntireDCache(); //Ensure that all memory transfers have completed and that the data cache has been flushed
@ -66,7 +66,7 @@ void mcuReboot(void)
void mcuPowerOff(void)
{
if(!isFirmlaunch && PDN_GPU_CNT != 1) clearScreens();
if(!isFirmlaunch && PDN_GPU_CNT != 1) clearScreens(true, true);
flushEntireDCache(); //Ensure that all memory transfers have completed and that the data cache has been flushed
@ -115,9 +115,9 @@ void error(const char *message)
{
initScreens();
drawString("An error has occurred:", 10, 10, COLOR_RED);
u32 posY = drawString(message, 10, 30, COLOR_WHITE);
drawString("Press any button to shutdown", 10, posY + 2 * SPACING_Y, COLOR_WHITE);
drawString("An error has occurred:", true, 10, 10, COLOR_RED);
u32 posY = drawString(message, true, 10, 30, COLOR_WHITE);
drawString("Press any button to shutdown", true, 10, posY + 2 * SPACING_Y, COLOR_WHITE);
waitInput();
mcuPowerOff();