commit be672f5a86c1d45412fe2ca4afc7ec2d32113db1 Author: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Tue Dec 28 20:17:21 2021 +0100 Initial commit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5e6a39b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: CI-Release + +on: + push: + branches: + - main + +jobs: + + build-binary: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - name: Checkout submodules using a PAT + run: | + git config --file .gitmodules --get-regexp url | while read url; do + git config --file=.gitmodules $(echo "$url" | sed -E "s/git@github.com:|https:\/\/github.com\//https:\/\/${{ secrets.CI_PAT }}:${{ secrets.CI_PAT }}@github.com\//") + done + git submodule sync + git submodule update --init --recursive + - name: build binary + run: | + docker build . -t builder + docker run --rm -v ${PWD}:/project builder make + - uses: actions/upload-artifact@master + with: + name: binary + path: "*.rpx" + deploy-binary: + needs: build-binary + runs-on: ubuntu-18.04 + steps: + - name: Get environment variables + id: get_repository_name + run: | + echo REPOSITORY_NAME=$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}' | sed -e "s/:refs//") >> $GITHUB_ENV + echo DATETIME=$(echo $(date '+%Y%m%d-%H%M%S')) >> $GITHUB_ENV + - uses: actions/download-artifact@master + with: + name: binary + - name: zip artifact + run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip *.rpx + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }} + release_name: Nightly-${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }} + draft: false + prerelease: true + body: | + Not a stable release: + ${{ github.event.head_commit.message }} + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip + asset_name: ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip + asset_content_type: application/unknown \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..8e28a5f --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,24 @@ +name: CI-PR + +on: [pull_request] + +jobs: + build-binary: + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v2 + - name: Checkout submodules using a PAT + run: | + git config --file .gitmodules --get-regexp url | while read url; do + git config --file=.gitmodules $(echo "$url" | sed -E "s/git@github.com:|https:\/\/github.com\//https:\/\/${{ secrets.CI_PAT }}:${{ secrets.CI_PAT }}@github.com\//") + done + git submodule sync + git submodule update --init --recursive + - name: build binary + run: | + docker build . -t builder + docker run --rm -v ${PWD}:/project builder make + - uses: actions/upload-artifact@master + with: + name: binary + path: "*.rpx" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0c39823 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.elf +*.rpx +build/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1fcc684 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,3 @@ +FROM wiiuenv/devkitppc:20211106 + +WORKDIR project \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3b21398 --- /dev/null +++ b/Makefile @@ -0,0 +1,126 @@ +#------------------------------------------------------------------------------- +.SUFFIXES: +#------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) + +include $(DEVKITPRO)/wut/share/wut_rules + +#------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +#------------------------------------------------------------------------------- +TARGET := 99_autoboot +BUILD := build +SOURCES := source +DATA := data +INCLUDES := source include + +#------------------------------------------------------------------------------- +# options for code generation +#------------------------------------------------------------------------------- +CFLAGS := -g -Wall -O3 -ffunction-sections -fno-exceptions \ + $(MACHDEP) + +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ + +CXXFLAGS := $(CFLAGS) -std=c++20 -fno-rtti + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) $(RPXSPECS) --entry=_start -Wl,-Map,$(notdir $*.map) + +LIBS := -lfreetype -lpng -lbz2 -lwut -lz + +#------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level +# containing include and lib +#------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(WUT_ROOT) + +#------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#------------------------------------------------------------------------------- + export LD := $(CC) +#------------------------------------------------------------------------------- +else +#------------------------------------------------------------------------------- + export LD := $(CXX) +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +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$(DEVKITPRO)/portlibs/ppc/include/freetype2 + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +.PHONY: $(BUILD) clean all + +#------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).rpx $(TARGET).elf + +#------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#------------------------------------------------------------------------------- +# main targets +#------------------------------------------------------------------------------- + +all : $(OUTPUT).rpx + +$(OUTPUT).rpx : $(OUTPUT).elf +$(OUTPUT).elf : $(OFILES) +$(OFILES) : + +$(OFILES_SRC) : $(HFILES_BIN) + +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..25b67c9 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# AutobootModule diff --git a/include/nn/cmpt/cmpt.h b/include/nn/cmpt/cmpt.h new file mode 100644 index 0000000..6b4f028 --- /dev/null +++ b/include/nn/cmpt/cmpt.h @@ -0,0 +1,28 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum ScreenType { + SCREEN_TYPE_TV = 1, + SCREEN_TYPE_DRC, + SCREEN_TYPE_BOTH, +} ScreenType; + +int32_t +CMPTGetDataSize(uint32_t* outSize); + +int32_t +CMPTLaunchTitle(void* dataBuffer, uint32_t bufferSize, uint32_t titleId_low, uint32_t titleId_high); + +int32_t +CMPTAcctSetScreenType(ScreenType type); + +int32_t +CMPTCheckScreenState(void); + +#ifdef __cplusplus +} +#endif diff --git a/include/nn/sl/FileStream.h b/include/nn/sl/FileStream.h new file mode 100644 index 0000000..907ab98 --- /dev/null +++ b/include/nn/sl/FileStream.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include "common.h" + +namespace nn::sl { + class FileStream { + public: + FileStream() { + instance = __ct__Q3_2nn2sl10FileStreamFv(nullptr); + } + + ~FileStream() { + if (instance != nullptr) { + __dt__Q3_2nn2sl10FileStreamFv(instance); + } + } + + nn::Result Initialize(FSClient *client, FSCmdBlock *cmdBlock, char const *path, char const *mode) { + return Initialize__Q3_2nn2sl10FileStreamFP8FSClientP10FSCmdBlockPCcT3(this->instance, client, cmdBlock, path, mode); + } + + FileStreamInternal *instance = nullptr; + }; + +} \ No newline at end of file diff --git a/include/nn/sl/LaunchInfoDatabase.h b/include/nn/sl/LaunchInfoDatabase.h new file mode 100644 index 0000000..a7b1508 --- /dev/null +++ b/include/nn/sl/LaunchInfoDatabase.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include "common.h" +#include "FileStream.h" + +namespace nn::sl { + class LaunchInfoDatabase { + public: + LaunchInfoDatabase() { + instance = __ct__Q3_2nn2sl18LaunchInfoDatabaseFv(nullptr); + } + + ~LaunchInfoDatabase() { + Finalize__Q3_2nn2sl18LaunchInfoDatabaseFv(instance); + } + + // nn::sl::LaunchInfoDatabase::Load(nn::sl::IStream &, nn::sl::Region) + nn::Result Load(nn::sl::FileStream *fileStream, nn::sl::Region region) { + return Load__Q3_2nn2sl18LaunchInfoDatabaseFRQ3_2nn2sl7IStreamQ3_2nn2sl6Region(instance, fileStream->instance, region); + } + + // nn::sl::LaunchInfoDatabase::GetLaunchInfoById(nn::sl::LaunchInfo *, unsigned long long) const + nn::Result GetLaunchInfoById(nn::sl::LaunchInfo *launchInfo, uint64_t titleId) { + return GetLaunchInfoById__Q3_2nn2sl18LaunchInfoDatabaseCFPQ3_2nn2sl10LaunchInfoUL(instance, launchInfo, titleId); + } + + private: + LaunchInfoDatabaseInternal *instance; + }; + +} \ No newline at end of file diff --git a/include/nn/sl/common.h b/include/nn/sl/common.h new file mode 100644 index 0000000..2c55560 --- /dev/null +++ b/include/nn/sl/common.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +namespace nn::sl { + typedef struct WUT_PACKED FileStreamInternal { + WUT_UNKNOWN_BYTES(0x10); + } FileStreamInternal; + WUT_CHECK_SIZE(FileStreamInternal, 0x10); + + extern "C" nn::Result Initialize__Q3_2nn2sl10FileStreamFP8FSClientP10FSCmdBlockPCcT3(FileStreamInternal *, FSClient *, FSCmdBlock *, char const *, char const *); + extern "C" FileStreamInternal *__ct__Q3_2nn2sl10FileStreamFv(FileStreamInternal *); + extern "C" void __dt__Q3_2nn2sl10FileStreamFv(FileStreamInternal *); + + typedef struct WUT_PACKED LaunchInfo { + uint64_t titleId; + WUT_UNKNOWN_BYTES(0x810 - 0x08); + } LaunchInfo; + + WUT_CHECK_OFFSET(LaunchInfo, 0x00, titleId); + WUT_CHECK_SIZE(LaunchInfo, 0x810); + + void + GetDefaultDatabasePath(char *, int size, uint32_t tid_hi, uint32_t tid_low) + asm("GetDefaultDatabasePath__Q2_2nn2slFPcUiUL"); + + nn::Result + Initialize(MEMAllocFromDefaultHeapExFn, MEMFreeToDefaultHeapFn) + asm("Initialize__Q2_2nn2slFPFUiT1_PvPFPv_v"); + + nn::Result + Finalize() + asm("Finalize__Q2_2nn2slFv"); + + typedef enum Region { + REGION_JPN = 0, + REGION_USA = 1, + REGION_EUR = 2 + } Region; + + typedef struct WUT_PACKED LaunchInfoDatabaseInternal { + WUT_UNKNOWN_BYTES(0x1C); + } LaunchInfoDatabaseInternal; + WUT_CHECK_SIZE(LaunchInfoDatabaseInternal, 0x1C); + + extern "C" LaunchInfoDatabaseInternal *__ct__Q3_2nn2sl18LaunchInfoDatabaseFv(LaunchInfoDatabaseInternal *); + extern "C" nn::Result Load__Q3_2nn2sl18LaunchInfoDatabaseFRQ3_2nn2sl7IStreamQ3_2nn2sl6Region(LaunchInfoDatabaseInternal *, nn::sl::FileStreamInternal *, nn::sl::Region); + extern "C" void Finalize__Q3_2nn2sl18LaunchInfoDatabaseFv(LaunchInfoDatabaseInternal *); + extern "C" nn::Result GetLaunchInfoById__Q3_2nn2sl18LaunchInfoDatabaseCFPQ3_2nn2sl10LaunchInfoUL(LaunchInfoDatabaseInternal *, nn::sl::LaunchInfo *, uint64_t titleId); +} + diff --git a/source/DrawUtils.cpp b/source/DrawUtils.cpp new file mode 100644 index 0000000..511232b --- /dev/null +++ b/source/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/DrawUtils.h b/source/DrawUtils.h new file mode 100644 index 0000000..dee4a87 --- /dev/null +++ b/source/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; +}; diff --git a/source/crt.c b/source/crt.c new file mode 100644 index 0000000..2fcd925 --- /dev/null +++ b/source/crt.c @@ -0,0 +1,36 @@ +void __init_wut_malloc(); + +void __init_wut_newlib(); + +void __init_wut_stdcpp(); + +void __init_wut_devoptab(); + +void __attribute__((weak)) __init_wut_socket(); + +void __fini_wut_malloc(); + +void __fini_wut_newlib(); + +void __fini_wut_stdcpp(); + +void __fini_wut_devoptab(); + +void __attribute__((weak)) __fini_wut_socket(); + +void __attribute__((weak)) +__init_wut_() { + __init_wut_malloc(); + __init_wut_newlib(); + __init_wut_stdcpp(); + __init_wut_devoptab(); + if (&__init_wut_socket) __init_wut_socket(); +} + +void __attribute__((weak)) +__fini_wut_() { + __fini_wut_devoptab(); + __fini_wut_stdcpp(); + __fini_wut_newlib(); + __fini_wut_malloc(); +} diff --git a/source/crt0.s b/source/crt0.s new file mode 100644 index 0000000..318de30 --- /dev/null +++ b/source/crt0.s @@ -0,0 +1,29 @@ +.extern main +.extern exit +.extern __init_wut_ +.extern __fini_wut_ + +.global _start +_start: + stwu 1, -0x28(1) + mflr 0 + stw 0, 0x2C(1) + stw 31, 0x24(1) + or 31, 1, 1 + stw 3, 0x18(31) + stw 4, 0x1C(31) + bl __init_wut_ + lwz 4, 0x1C(31) + lwz 3, 0x18(31) + bl main + or 9, 3, 3 + stw 9, 0x8(31) + bl __fini_wut_ + lwz 9, 0x8(31) + or 3, 9, 9 + addi 11, 31, 0x28 + lwz 0, 0x4(11) + mtlr 0 + lwz 31, -0x4(11) + or 1, 11, 11 + blr \ No newline at end of file diff --git a/source/logger.h b/source/logger.h new file mode 100644 index 0000000..c6fc916 --- /dev/null +++ b/source/logger.h @@ -0,0 +1,23 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) + +#define DEBUG_FUNCTION_LINE(FMT, ARGS...)do { \ + WHBLogPrintf("[%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + } while (0); + +#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...)do { \ + WHBLogWritef("[%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + } while (0); + +#ifdef __cplusplus +} +#endif diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..4d805a6 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,337 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "DrawUtils.h" +#include "logger.h" + +#define COLOR_WHITE Color(0xffffffff) +#define COLOR_BLACK Color(0, 0, 0, 255) +#define COLOR_BACKGROUND COLOR_BLACK +#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) + +static const char* menu_options[] = { + "Wii U Menu", + "Homebrew Launcher", + "vWii System Menu", + "vWii Homebrew Channel", +}; + +bool getQuickBoot() { + auto bootCheck = CCRSysCaffeineBootCheck(); + if (bootCheck == 0) { + nn::sl::Initialize(MEMAllocFromDefaultHeapEx, MEMFreeToDefaultHeap); + char path[0x80]; + nn::sl::GetDefaultDatabasePath(path, 0x80, 0x00050010, 0x10066000); // ECO process + FSCmdBlock cmdBlock; + FSInitCmdBlock(&cmdBlock); + + auto fileStream = new nn::sl::FileStream; + auto *fsClient = (FSClient *) memalign(0x40, sizeof(FSClient)); + memset(fsClient, 0, sizeof(*fsClient)); + FSAddClient(fsClient, FS_ERROR_FLAG_NONE); + + fileStream->Initialize(fsClient, &cmdBlock, path, "r"); + + auto database = new nn::sl::LaunchInfoDatabase; + database->Load(fileStream, nn::sl::REGION_EUR); + + CCRAppLaunchParam data; // load sys caffeine data + // load app launch param + CCRSysCaffeineGetAppLaunchParam(&data); + + // get launch info for id + nn::sl::LaunchInfo info; + auto result = database->GetLaunchInfoById(&info, data.titleId); + + delete database; + delete fileStream; + + FSDelClient(fsClient, FS_ERROR_FLAG_NONE); + + nn::sl::Finalize(); + + if (!result.IsSuccess()) { + DEBUG_FUNCTION_LINE("GetLaunchInfoById failed."); + return false; + } + + if (info.titleId == 0x0005001010040000L || + info.titleId == 0x0005001010040100L || + info.titleId == 0x0005001010040200L) { + DEBUG_FUNCTION_LINE("Skip quick booting into the Wii U Menu"); + return false; + } + if (!SYSCheckTitleExists(info.titleId)) { + DEBUG_FUNCTION_LINE("Title %016llX doesn't exist", info.titleId); + return false; + } + + MCPTitleListType titleInfo; + int32_t handle = MCP_Open(); + auto err = MCP_GetTitleInfo(handle, info.titleId, &titleInfo); + MCP_Close(handle); + if (err == 0) { + nn::act::Initialize(); + for (int i = 0; i < 13; i++) { + char uuid[16]; + result = nn::act::GetUuidEx(uuid, i); + if (result.IsSuccess()) { + if (memcmp(uuid, data.uuid, 8) == 0) { + DEBUG_FUNCTION_LINE("Load Console account %d", i); + nn::act::LoadConsoleAccount(i, 0, 0, 0); + break; + } + } + } + nn::act::Finalize(); + + ACPAssignTitlePatch(&titleInfo); + _SYSLaunchTitleWithStdArgsInNoSplash(info.titleId, nullptr); + return true; + } + + return false; + } else { + DEBUG_FUNCTION_LINE("No quick boot"); + } + return false; +} + +static void initExternalStorage(void){ + nn::spm::Initialize(); + + nn::spm::StorageListItem items[0x20]; + int32_t numItems = nn::spm::GetStorageList(items, 0x20); + + bool found = false; + for (int i = 0; i < numItems; i++) { + if (items[i].type == nn::spm::STORAGE_TYPE_WFS) { + nn::spm::StorageInfo info{}; + if (nn::spm::GetStorageInfo(&info, &items[i].index) == 0) { + DEBUG_FUNCTION_LINE("Using %s for extended storage", info.path); + + nn::spm::SetExtendedStorage(&items[i].index); + ACPMountExternalStorage(); + + nn::spm::SetDefaultExtendedStorageVolumeId(info.volumeId); + + found = true; + break; + } + } + } + if (!found) { + DEBUG_FUNCTION_LINE("Fallback to empty ExtendedStorage"); + nn::spm::VolumeId empty{}; + nn::spm::SetDefaultExtendedStorageVolumeId(empty); + + nn::spm::StorageIndex storageIndex = 0; + nn::spm::SetExtendedStorage(&storageIndex); + } + + nn::spm::Finalize(); +} + +void bootSystemMenu(void){ + nn::act::Initialize(); + nn::act::SlotNo slot = nn::act::GetSlotNo(); + nn::act::SlotNo defaultSlot = nn::act::GetDefaultAccount(); + nn::act::Finalize(); + + if (defaultSlot) { //normal menu boot + SYSLaunchMenu(); + } else { //show mii select + _SYSLaunchMenuWithCheckingAccount(slot); + } +} + +void bootHomebrewLauncher(void){ + uint64_t titleId = _SYSGetSystemApplicationTitleId(SYSTEM_APP_ID_MII_MAKER); + _SYSLaunchTitleWithStdArgsInNoSplash(titleId, nullptr); +} + +static void launchvWiiTitle(uint32_t titleId_low, uint32_t titleId_high){ + // we need to init kpad for cmpt + KPADInit(); + + // Try to find a screen type that works + CMPTAcctSetScreenType(SCREEN_TYPE_BOTH); + if (CMPTCheckScreenState() < 0) { + CMPTAcctSetScreenType(SCREEN_TYPE_DRC); + if (CMPTCheckScreenState() < 0) { + CMPTAcctSetScreenType(SCREEN_TYPE_TV); + } + } + + uint32_t dataSize = 0; + CMPTGetDataSize(&dataSize); + + void* dataBuffer = memalign(0x40, dataSize); + CMPTLaunchTitle(dataBuffer, dataSize, titleId_low, titleId_high); + free(dataBuffer); +} + +void bootvWiiMenu(void){ + launchvWiiTitle(0x00000001, 0x00000002); +} + +void bootHomebrewChannel(void){ + launchvWiiTitle(0x00010001, 'OHBC'); +} + +void handleMenuScreen(void){ + OSScreenInit(); + + uint32_t tvBufferSize = OSScreenGetBufferSizeEx(SCREEN_TV); + uint32_t drcBufferSize = OSScreenGetBufferSizeEx(SCREEN_DRC); + + uint8_t* screenBuffer = (uint8_t*) memalign(0x100, tvBufferSize + drcBufferSize); + + OSScreenSetBufferEx(SCREEN_TV, screenBuffer); + OSScreenSetBufferEx(SCREEN_DRC, screenBuffer + tvBufferSize); + + OSScreenEnableEx(SCREEN_TV, TRUE); + OSScreenEnableEx(SCREEN_DRC, TRUE); + + DrawUtils::initBuffers(screenBuffer, tvBufferSize, screenBuffer + tvBufferSize, drcBufferSize); + DrawUtils::initFont(); + + uint32_t selected = 0; + int autoBoot = -1; + bool redraw = true; + while (true) { + VPADStatus vpad{}; + VPADRead(VPAD_CHAN_0, &vpad, 1, NULL); + + if (vpad.trigger & VPAD_BUTTON_UP) { + if (selected > 0) { + selected--; + redraw = true; + } + } + else if (vpad.trigger & VPAD_BUTTON_DOWN) { + if (selected < sizeof(menu_options) / sizeof(char*) - 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; + } + + if (redraw) { + DrawUtils::beginDraw(); + DrawUtils::clear(COLOR_BACKGROUND); + + // draw buttons + uint32_t index = 8 + 24 + 8 + 4; + for (uint32_t i = 0; i < sizeof(menu_options) / sizeof(char*); i++) { + 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, menu_options[i]); + index += 42 + 8; + } + + DrawUtils::setFontColor(COLOR_TEXT); + + // draw top bar + DrawUtils::setFontSize(24); + DrawUtils::print(16, 6 + 24, "Tiramisu Boot Selector"); + 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 Autoboot / \ue003 Select Autboot"; + DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(autobootHints) / 2, SCREEN_HEIGHT - 8, autobootHints, true); + + DrawUtils::endDraw(); + + redraw = false; + } + } + + DrawUtils::deinitFont(); + + switch (selected) { + case 0: + bootSystemMenu(); + break; + case 1: + bootHomebrewLauncher(); + break; + case 2: + bootvWiiMenu(); + break; + case 3: + bootHomebrewChannel(); + break; + } + + free(screenBuffer); +} + + +int main(int argc, char **argv){ + if (!WHBLogModuleInit()) { + WHBLogCafeInit(); + WHBLogUdpInit(); + } + DEBUG_FUNCTION_LINE("Hello from Autoboot"); + + initExternalStorage(); + + if (getQuickBoot()) { + return 0; + } + + VPADStatus vpad{}; + VPADRead(VPAD_CHAN_0, &vpad, 1, NULL); + + if (vpad.hold & VPAD_BUTTON_PLUS) { + handleMenuScreen(); + return 0; + } + + bootSystemMenu(); + + return 0; +}