Initial commit

This commit is contained in:
GaryOderNichts 2021-12-28 20:17:21 +01:00
commit be672f5a86
16 changed files with 1179 additions and 0 deletions

65
.github/workflows/ci.yml vendored Normal file
View File

@ -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

24
.github/workflows/pr.yml vendored Normal file
View File

@ -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"

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.elf
*.rpx
build/

3
Dockerfile Normal file
View File

@ -0,0 +1,3 @@
FROM wiiuenv/devkitppc:20211106
WORKDIR project

126
Makefile Normal file
View File

@ -0,0 +1,126 @@
#-------------------------------------------------------------------------------
.SUFFIXES:
#-------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/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
#-------------------------------------------------------------------------------

1
README.md Normal file
View File

@ -0,0 +1 @@
# AutobootModule

28
include/nn/cmpt/cmpt.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <wut.h>
#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

View File

@ -0,0 +1,28 @@
#pragma once
#include <wut.h>
#include <nn/result.h>
#include <coreinit/filesystem.h>
#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;
};
}

View File

@ -0,0 +1,33 @@
#pragma once
#include <wut.h>
#include <nn/result.h>
#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;
};
}

54
include/nn/sl/common.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include <wut.h>
#include <nn/result.h>
#include <coreinit/filesystem.h>
#include <coreinit/memdefaultheap.h>
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);
}

332
source/DrawUtils.cpp Normal file
View File

@ -0,0 +1,332 @@
#include "DrawUtils.h"
#include <cmath>
#include <coreinit/memory.h>
#include <coreinit/screen.h>
#include <coreinit/cache.h>
#include <png.h>
#include <ft2build.h>
#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;
}

57
source/DrawUtils.h Normal file
View File

@ -0,0 +1,57 @@
#pragma once
#include <cstdint>
// 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;
};

36
source/crt.c Normal file
View File

@ -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();
}

29
source/crt0.s Normal file
View File

@ -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

23
source/logger.h Normal file
View File

@ -0,0 +1,23 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <cstring>
#include <whb/log.h>
#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

337
source/main.cpp Normal file
View File

@ -0,0 +1,337 @@
#include <malloc.h>
#include <coreinit/screen.h>
#include <coreinit/filesystem.h>
#include <coreinit/memdefaultheap.h>
#include <vpad/input.h>
#include <sysapp/launch.h>
#include <sysapp/title.h>
#include <padscore/kpad.h>
#include <nn/acp.h>
#include <nn/act.h>
#include <nn/cmpt/cmpt.h>
#include <nn/ccr/sys_caffeine.h>
#include <nn/sl/common.h>
#include <nn/sl/FileStream.h>
#include <nn/sl/LaunchInfoDatabase.h>
#include <nn/spm.h>
#include <whb/log_module.h>
#include <whb/log_udp.h>
#include <whb/log_cafe.h>
#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;
}