diff --git a/Makefile b/Makefile index 551cef4..2af9690 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ endif ASFLAGS := -g $(ARCH) LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -LIBS := -lwut -lz +LIBS := -lfreetype -lpng -lbz2 -lwut -lz #------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level @@ -95,7 +95,7 @@ export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ - -I$(CURDIR)/$(BUILD) + -I$(CURDIR)/$(BUILD) -I$(DEVKITPRO)/portlibs/ppc/include/freetype2 export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) diff --git a/source/main.cpp b/source/main.cpp index 0841adc..510c749 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "utils/StringTools.h" #include "fs/DirList.h" @@ -25,11 +26,14 @@ #include "ElfUtils.h" #include "kernel.h" #include "common/module_defines.h" +#include "utils/DrawUtils.h" #define MEMORY_REGION_START 0x00900000 #define MEMORY_REGION_SIZE 0x00700000 #define MEMORY_REGION_END (MEMORY_REGION_START + MEMORY_REGION_SIZE) +#define AUTOBOOT_CONFIG_PATH "fs:/vol/external01/wiiu/environments/autoboot.cfg" + bool CheckRunning() { switch (ProcUIProcessMessages(true)) { case PROCUI_STATUS_EXITING: { @@ -52,7 +56,32 @@ bool CheckRunning() { extern "C" uint32_t textStart(); -std::string EnvironmentSelectionScreen(const std::map &payloads); +std::string EnvironmentSelectionScreen(const std::map &payloads, int32_t autobootIndex); + +std::optional getFileContent(const std::string &path) { + DEBUG_FUNCTION_LINE("Read %s", path.c_str()); + FILE *f = fopen(path.c_str(), "r"); + if (f) { + char buf[128]{}; + fgets(buf, sizeof(buf), f); + fclose(f); + + return std::string(buf); + } + DEBUG_FUNCTION_LINE("Failed"); + return {}; +} + +bool writeFileContent(const std::string &path, const std::string &content) { + DEBUG_FUNCTION_LINE("Write to file %s: %s", path.c_str(), content.c_str()); + FILE *f = fopen(path.c_str(), "w"); + if (f) { + fputs(content.c_str(), f); + fclose(f); + return true; + } + return false; +} int main(int argc, char **argv) { #ifdef DEBUG @@ -86,6 +115,26 @@ int main(int argc, char **argv) { if (strncmp(environmentPath, "fs:/vol/external01/wiiu/environments/", strlen("fs:/vol/external01/wiiu/environments/")) != 0) { DirList environmentDirs("fs:/vol/external01/wiiu/environments/", nullptr, DirList::Dirs, 1); + bool foundFromConfig = false; + bool forceMenu = true; + auto res = getFileContent(AUTOBOOT_CONFIG_PATH); + auto autobootIndex = -1; + if (res) { + DEBUG_FUNCTION_LINE("Got result %s", res->c_str()); + for (int i = 0; i < environmentDirs.GetFilecount(); i++) { + if (environmentDirs.GetFilename(i) == res.value()) { + DEBUG_FUNCTION_LINE("Found environment %s from config at index %d", res.value().c_str(), i); + autobootIndex = i; + environment_path = environmentDirs.GetFilepath(i); + foundFromConfig = true; + forceMenu = false; + break; + } + } + } else { + DEBUG_FUNCTION_LINE("No config found"); + } + std::map environmentPaths; for (int i = 0; i < environmentDirs.GetFilecount(); i++) { environmentPaths[environmentDirs.GetFilename(i)] = environmentDirs.GetFilepath(i); @@ -100,10 +149,9 @@ int main(int argc, char **argv) { btn = vpad_data.hold | vpad_data.trigger; } - environment_path = "fs:/vol/external01/wiiu/environments/default"; - - if ((btn & VPAD_BUTTON_X) == VPAD_BUTTON_X) { - environment_path = EnvironmentSelectionScreen(environmentPaths); + if (forceMenu || (btn & VPAD_BUTTON_X) == VPAD_BUTTON_X) { + DEBUG_FUNCTION_LINE("Open menu!"); + environment_path = EnvironmentSelectionScreen(environmentPaths, autobootIndex); DEBUG_FUNCTION_LINE("Selected %s", environment_path.c_str()); } } @@ -152,80 +200,131 @@ int main(int argc, char **argv) { return 0; } -std::string EnvironmentSelectionScreen(const std::map &payloads) { - // Init screen and screen buffers +#define COLOR_WHITE Color(0xffffffff) +#define COLOR_BLACK Color(0, 0, 0, 255) +#define COLOR_BACKGROUND Color(0, 40, 100, 255) +#define COLOR_TEXT COLOR_WHITE +#define COLOR_TEXT2 Color(0xB3ffffff) +#define COLOR_AUTOBOOT Color(0xaeea00ff) +#define COLOR_BORDER Color(204, 204, 204, 255) +#define COLOR_BORDER_HIGHLIGHTED Color(0x3478e4ff) + + +std::string EnvironmentSelectionScreen(const std::map &payloads, int32_t autobootIndex) { OSScreenInit(); - uint32_t screen_buf0_size = OSScreenGetBufferSizeEx(SCREEN_TV); - uint32_t screen_buf1_size = OSScreenGetBufferSizeEx(SCREEN_DRC); - auto *screenBuffer = (uint8_t *) memalign(0x100, screen_buf0_size + screen_buf1_size); - OSScreenSetBufferEx(SCREEN_TV, (void *) screenBuffer); - OSScreenSetBufferEx(SCREEN_DRC, (void *) (screenBuffer + screen_buf0_size)); - OSScreenEnableEx(SCREEN_TV, 1); - OSScreenEnableEx(SCREEN_DRC, 1); + uint32_t tvBufferSize = OSScreenGetBufferSizeEx(SCREEN_TV); + uint32_t drcBufferSize = OSScreenGetBufferSizeEx(SCREEN_DRC); - // Clear screens - OSScreenClearBufferEx(SCREEN_TV, 0); - OSScreenClearBufferEx(SCREEN_DRC, 0); + uint8_t *screenBuffer = (uint8_t *) memalign(0x100, tvBufferSize + drcBufferSize); - OSScreenFlipBuffersEx(SCREEN_TV); - OSScreenFlipBuffersEx(SCREEN_DRC); + OSScreenSetBufferEx(SCREEN_TV, screenBuffer); + OSScreenSetBufferEx(SCREEN_DRC, screenBuffer + tvBufferSize); - VPADStatus vpad_data; - VPADReadError error; - int selected = 0; - std::string header = "Please choose your environment:"; + OSScreenEnableEx(SCREEN_TV, TRUE); + OSScreenEnableEx(SCREEN_DRC, TRUE); + + DrawUtils::initBuffers(screenBuffer, tvBufferSize, screenBuffer + tvBufferSize, drcBufferSize); + DrawUtils::initFont(); + + uint32_t selected = 0; + int autoBoot = autobootIndex; + + DEBUG_FUNCTION_LINE("Time to draw"); + + bool redraw = true; while (true) { - // Clear screens - OSScreenClearBufferEx(SCREEN_TV, 0); - OSScreenClearBufferEx(SCREEN_DRC, 0); + VPADStatus vpad{}; + VPADRead(VPAD_CHAN_0, &vpad, 1, NULL); - int pos = 0; + if (vpad.trigger & VPAD_BUTTON_UP) { + if (selected > 0) { + selected--; + redraw = true; + } + } else if (vpad.trigger & VPAD_BUTTON_DOWN) { + if (selected < payloads.size() - 1) { + selected++; + redraw = true; + } + } else if (vpad.trigger & VPAD_BUTTON_A) { + break; + } else if (vpad.trigger & VPAD_BUTTON_X) { + autoBoot = -1; + redraw = true; + } else if (vpad.trigger & VPAD_BUTTON_Y) { + autoBoot = selected; + redraw = true; + } - OSScreenPutFontEx(SCREEN_TV, 0, pos, header.c_str()); - OSScreenPutFontEx(SCREEN_DRC, 0, pos, header.c_str()); + if (redraw) { + DrawUtils::beginDraw(); + DrawUtils::clear(COLOR_BACKGROUND); - pos += 2; + // draw buttons + uint32_t index = 8 + 24 + 8 + 4; + uint32_t i = 0; + for (auto const&[key, val]: payloads) { + if (i == selected) { + DrawUtils::drawRect(16, index, SCREEN_WIDTH - 16 * 2, 44, 4, COLOR_BORDER_HIGHLIGHTED); + } else { + DrawUtils::drawRect(16, index, SCREEN_WIDTH - 16 * 2, 44, 2, (i == autoBoot) ? COLOR_AUTOBOOT : COLOR_BORDER); + } + DrawUtils::setFontSize(24); + DrawUtils::setFontColor((i == autoBoot) ? COLOR_AUTOBOOT : COLOR_TEXT); + DrawUtils::print(16 * 2, index + 8 + 24, key.c_str()); + index += 42 + 8; + i++; + } + + DrawUtils::setFontColor(COLOR_TEXT); + + // draw top bar + DrawUtils::setFontSize(24); + DrawUtils::print(16, 6 + 24, "Environment Loader"); + DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE); + + // draw bottom bar + DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE); + DrawUtils::setFontSize(18); + DrawUtils::print(16, SCREEN_HEIGHT - 8, "\ue07d Navigate "); + DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 8, "\ue000 Choose", true); + const char *autobootHints = "\ue002 Clear Default / \ue003 Select Default"; + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(autobootHints) / 2, SCREEN_HEIGHT - 8, autobootHints, true); + + DrawUtils::endDraw(); + + redraw = false; + } + } + + DrawUtils::clear(COLOR_BLACK); + DrawUtils::endDraw(); + + DrawUtils::deinitFont(); + + free(screenBuffer); + + if (autoBoot != autobootIndex) { int i = 0; for (auto const&[key, val]: payloads) { - std::string text = StringTools::strfmt("%s %s", i == selected ? "> " : " ", key.c_str()); - OSScreenPutFontEx(SCREEN_TV, 0, pos, text.c_str()); - OSScreenPutFontEx(SCREEN_DRC, 0, pos, text.c_str()); + if (i == autoBoot) { + DEBUG_FUNCTION_LINE("Save config"); + writeFileContent(AUTOBOOT_CONFIG_PATH, key); + break; + } i++; - pos++; } - - VPADRead(VPAD_CHAN_0, &vpad_data, 1, &error); - if (vpad_data.trigger == VPAD_BUTTON_A) { - break; - } - - if (vpad_data.trigger == VPAD_BUTTON_UP) { - selected--; - if (selected < 0) { - selected = 0; - } - } else if (vpad_data.trigger == VPAD_BUTTON_DOWN) { - selected++; - if ((uint32_t) selected >= payloads.size()) { - selected = payloads.size() - 1; - } - } - - OSScreenFlipBuffersEx(SCREEN_TV); - OSScreenFlipBuffersEx(SCREEN_DRC); - - OSSleepTicks(OSMillisecondsToTicks(16)); } + int i = 0; for (auto const&[key, val]: payloads) { if (i == selected) { - free(screenBuffer); return val; } i++; } - free(screenBuffer); + return ""; } \ No newline at end of file diff --git a/source/utils/DrawUtils.cpp b/source/utils/DrawUtils.cpp new file mode 100644 index 0000000..eda1401 --- /dev/null +++ b/source/utils/DrawUtils.cpp @@ -0,0 +1,332 @@ +#include "DrawUtils.h" + +#include +#include +#include +#include +#include +#include +#include FT_FREETYPE_H + +// buffer width +#define TV_WIDTH 0x500 +#define DRC_WIDTH 0x380 + +bool DrawUtils::isBackBuffer; + +uint8_t* DrawUtils::tvBuffer = nullptr; +uint32_t DrawUtils::tvSize = 0; +uint8_t* DrawUtils::drcBuffer = nullptr; +uint32_t DrawUtils::drcSize = 0; + +// Don't put those into the class or we have to include ft everywhere +static FT_Library ft_lib = nullptr; +static FT_Face ft_face = nullptr; +static Color font_col = {0xFFFFFFFF}; + +void DrawUtils::initBuffers(void* tvBuffer, uint32_t tvSize, void* drcBuffer, uint32_t drcSize) +{ + DrawUtils::tvBuffer = (uint8_t*) tvBuffer; + DrawUtils::tvSize = tvSize; + DrawUtils::drcBuffer = (uint8_t*) drcBuffer; + DrawUtils::drcSize = drcSize; +} + +void DrawUtils::beginDraw() +{ + uint32_t pixel = *(uint32_t*) tvBuffer; + + // check which buffer is currently used + OSScreenPutPixelEx(SCREEN_TV, 0, 0, 0xABCDEF90); + if (*(uint32_t*) tvBuffer == 0xABCDEF90) { + isBackBuffer = false; + } else { + isBackBuffer = true; + } + + // restore the pixel we used for checking + *(uint32_t*) tvBuffer = pixel; +} + +void DrawUtils::endDraw() +{ + // OSScreenFlipBuffersEx already flushes the cache? + // DCFlushRange(tvBuffer, tvSize); + // DCFlushRange(drcBuffer, drcSize); + + OSScreenFlipBuffersEx(SCREEN_DRC); + OSScreenFlipBuffersEx(SCREEN_TV); +} + +void DrawUtils::clear(Color col) +{ + OSScreenClearBufferEx(SCREEN_TV, col.color); + OSScreenClearBufferEx(SCREEN_DRC, col.color); +} + +void DrawUtils::drawPixel(uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + float opacity = a / 255.0f; + + // put pixel in the drc buffer + uint32_t i = (x + y * DRC_WIDTH) * 4; + if (i + 3 < drcSize / 2) { + if (isBackBuffer) { + i += drcSize / 2; + } + if (a == 0xFF) { + drcBuffer[i ] = r; + drcBuffer[i + 1] = g; + drcBuffer[i + 2] = b; + } + else { + drcBuffer[i ] = r * opacity + drcBuffer[i ] * (1 - opacity); + drcBuffer[i + 1] = g * opacity + drcBuffer[i + 1] * (1 - opacity); + drcBuffer[i + 2] = b * opacity + drcBuffer[i + 2] * (1 - opacity); + } + } + + // scale and put pixel in the tv buffer + for (uint32_t yy = (y * 1.5); yy < ((y * 1.5) + 1); yy++) { + for (uint32_t xx = (x * 1.5); xx < ((x * 1.5) + 1); xx++) { + uint32_t i = (xx + yy * TV_WIDTH) * 4; + if (i + 3 < tvSize / 2) { + if (isBackBuffer) { + i += tvSize / 2; + } + if (a == 0xFF) { + tvBuffer[i ] = r; + tvBuffer[i + 1] = g; + tvBuffer[i + 2] = b; + } + else { + tvBuffer[i ] = r * opacity + tvBuffer[i ] * (1 - opacity); + tvBuffer[i + 1] = g * opacity + tvBuffer[i + 1] * (1 - opacity); + tvBuffer[i + 2] = b * opacity + tvBuffer[i + 2] * (1 - opacity); + } + } + } + } +} + +void DrawUtils::drawRectFilled(uint32_t x, uint32_t y, uint32_t w, uint32_t h, Color col) +{ + for (uint32_t yy = y; yy < y + h; yy++) { + for (uint32_t xx = x; xx < x + w; xx++) { + drawPixel(xx, yy, col); + } + } +} + +void DrawUtils::drawRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t borderSize, Color col) +{ + drawRectFilled(x, y, w, borderSize, col); + drawRectFilled(x, y + h - borderSize, w, borderSize, col); + drawRectFilled(x, y, borderSize, h, col); + drawRectFilled(x + w - borderSize, y, borderSize, h, col); +} + +void DrawUtils::drawBitmap(uint32_t x, uint32_t y, uint32_t target_width, uint32_t target_height, const uint8_t* data) +{ + if ( data[0] != 'B' || data[1] != 'M' ) { + // invalid header + return; + } + + uint32_t dataPos = __builtin_bswap32(*(uint32_t*)&(data[0x0A])); + uint32_t width = __builtin_bswap32(*(uint32_t*)&(data[0x12])); + uint32_t height = __builtin_bswap32(*(uint32_t*)&(data[0x16])); + + if (dataPos == 0) { + dataPos = 54; + } + + data += dataPos; + + // TODO flip image since bitmaps are stored upside down + + for (uint32_t yy = y; yy < y + target_height; yy++) { + for (uint32_t xx = x; xx < x + target_width; xx++) { + uint32_t i = (((xx - x) * width / target_width) + ((yy - y) * height / target_height) * width) * 3; + drawPixel(xx, yy, data[i + 2], data[i + 1], data[i], 0xFF); + } + } +} + +static void png_read_data(png_structp png_ptr, png_bytep outBytes, png_size_t byteCountToRead) +{ + void** data = (void**) png_get_io_ptr(png_ptr); + + memcpy(outBytes, *data, byteCountToRead); + *((uint8_t**) data) += byteCountToRead; +} + +void DrawUtils::drawPNG(uint32_t x, uint32_t y, const uint8_t* data) +{ + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(png_ptr == NULL) { + return; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if(info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + return; + } + + png_set_read_fn(png_ptr, (void*) &data, png_read_data); + + png_read_info(png_ptr, info_ptr); + + uint32_t width = 0; + uint32_t height = 0; + int bitDepth = 0; + int colorType = -1; + uint32_t retval = png_get_IHDR(png_ptr, info_ptr, &width, &height, &bitDepth, &colorType, NULL, NULL, NULL); + if(retval != 1) { + return; + } + + uint32_t bytesPerRow = png_get_rowbytes(png_ptr, info_ptr); + uint8_t* rowData = new uint8_t[bytesPerRow]; + + for(uint32_t yy = y; yy < y + height; yy++) { + png_read_row(png_ptr, (png_bytep)rowData, NULL); + + for (uint32_t xx = x; xx < x + width; xx++) { + if (colorType == PNG_COLOR_TYPE_RGB_ALPHA) { + uint32_t i = (xx - x) * 4; + drawPixel(xx, yy, rowData[i], rowData[i + 1], rowData[i + 2], rowData[i + 3]); + } + else if (colorType == PNG_COLOR_TYPE_RGB) { + uint32_t i = (xx - x) * 3; + drawPixel(xx, yy, rowData[i], rowData[i + 1], rowData[i + 2], 0xFF); + } + } + } + + delete[] rowData; + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); +} + +void DrawUtils::initFont() +{ + void* font = NULL; + uint32_t size = 0; + OSGetSharedData(OS_SHAREDDATATYPE_FONT_STANDARD, 0, &font, &size); + + if (font && size) { + FT_Init_FreeType(&ft_lib); + FT_New_Memory_Face(ft_lib, (FT_Byte*) font, size, 0, &ft_face); + } +} + +void DrawUtils::deinitFont() +{ + FT_Done_Face(ft_face); + FT_Done_FreeType(ft_lib); +} + +void DrawUtils::setFontSize(uint32_t size) +{ + FT_Set_Pixel_Sizes(ft_face, 0, size); +} + +void DrawUtils::setFontColor(Color col) +{ + font_col = col; +} + +static void draw_freetype_bitmap(FT_Bitmap* bitmap, FT_Int x, FT_Int y) +{ + FT_Int i, j, p, q; + FT_Int x_max = x + bitmap->width; + FT_Int y_max = y + bitmap->rows; + + for (i = x, p = 0; i < x_max; i++, p++) { + for (j = y, q = 0; j < y_max; j++, q++) { + if (i < 0 || j < 0 || i >= SCREEN_WIDTH || j >= SCREEN_HEIGHT) { + continue; + } + + float opacity = bitmap->buffer[q * bitmap->pitch + p] / 255.0f; + DrawUtils::drawPixel(i, j, font_col.r, font_col.g, font_col.b, font_col.a * opacity); + } + } +} + +void DrawUtils::print(uint32_t x, uint32_t y, const char* string, bool alignRight) +{ + wchar_t* buffer = new wchar_t[strlen(string) + 1]; + + size_t num = mbstowcs(buffer, string, strlen(string)); + if (num > 0) { + buffer[num] = 0; + } + else { + wchar_t* tmp = buffer; + while ((*tmp++ = *string++)); + } + + print(x, y, buffer, alignRight); + delete[] buffer; +} + +void DrawUtils::print(uint32_t x, uint32_t y, const wchar_t* string, bool alignRight) +{ + FT_GlyphSlot slot = ft_face->glyph; + FT_Vector pen = {(int) x, (int) y}; + + if (alignRight) { + pen.x -= getTextWidth(string); + } + + for (; *string; string++) { + uint32_t charcode = *string; + + if (charcode == '\n') { + pen.y += ft_face->size->metrics.height >> 6; + pen.x = x; + continue; + } + + FT_Load_Glyph(ft_face, FT_Get_Char_Index(ft_face, charcode), FT_LOAD_DEFAULT); + FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL); + + draw_freetype_bitmap(&slot->bitmap, pen.x + slot->bitmap_left, pen.y - slot->bitmap_top); + pen.x += slot->advance.x >> 6; + } +} + +uint32_t DrawUtils::getTextWidth(const char* string) +{ + wchar_t* buffer = new wchar_t[strlen(string) + 1]; + + size_t num = mbstowcs(buffer, string, strlen(string)); + if (num > 0) { + buffer[num] = 0; + } + else { + wchar_t* tmp = buffer; + while ((*tmp++ = *string++)); + } + + uint32_t width = getTextWidth(buffer); + delete[] buffer; + + return width; +} + +uint32_t DrawUtils::getTextWidth(const wchar_t* string) +{ + FT_GlyphSlot slot = ft_face->glyph; + uint32_t width = 0; + + for (; *string; string++) { + FT_Load_Glyph(ft_face, FT_Get_Char_Index(ft_face, *string), FT_LOAD_BITMAP_METRICS_ONLY); + + width += slot->advance.x >> 6; + } + + return width; +} diff --git a/source/utils/DrawUtils.h b/source/utils/DrawUtils.h new file mode 100644 index 0000000..dee4a87 --- /dev/null +++ b/source/utils/DrawUtils.h @@ -0,0 +1,57 @@ +#pragma once + +#include + +// visible screen sizes +#define SCREEN_WIDTH 854 +#define SCREEN_HEIGHT 480 + +union Color { + Color(uint32_t color) { + this->color = color; + } + Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a) { + this->r = r; this->g = g; this->b = b; this->a = a; + } + + uint32_t color; + struct { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; + }; +}; + +class DrawUtils { +public: + static void initBuffers(void* tvBuffer, uint32_t tvSize, void* drcBuffer, uint32_t drcSize); + static void beginDraw(); + static void endDraw(); + static void clear(Color col); + static void drawPixel(uint32_t x, uint32_t y, Color col) { drawPixel(x, y, col.r, col.g, col.b, col.a); } + static void drawPixel(uint32_t x, uint32_t y, uint8_t r, uint8_t g, uint8_t b, uint8_t a); + + static void drawRectFilled(uint32_t x, uint32_t y, uint32_t w, uint32_t h, Color col); + static void drawRect(uint32_t x, uint32_t y, uint32_t w, uint32_t h, uint32_t borderSize, Color col); + + static void drawBitmap(uint32_t x, uint32_t y, uint32_t target_width, uint32_t target_height, const uint8_t* data); + static void drawPNG(uint32_t x, uint32_t y, const uint8_t* data); + + static void initFont(); + static void deinitFont(); + static void setFontSize(uint32_t size); + static void setFontColor(Color col); + static void print(uint32_t x, uint32_t y, const char* string, bool alignRight = false); + static void print(uint32_t x, uint32_t y, const wchar_t* string, bool alignRight = false); + static uint32_t getTextWidth(const char* string); + static uint32_t getTextWidth(const wchar_t* string); + +private: + static bool isBackBuffer; + + static uint8_t* tvBuffer; + static uint32_t tvSize; + static uint8_t* drcBuffer; + static uint32_t drcSize; +};