From 92d91eb27423a104c750f52c1fdc3442f1b5c20b Mon Sep 17 00:00:00 2001 From: Pokes303 Date: Wed, 18 Sep 2019 22:53:34 +0200 Subject: [PATCH] Initial release --- Makefile | 139 ++++++++++++ src/file.cpp | 99 +++++++++ src/file.hpp | 16 ++ src/input.cpp | 221 +++++++++++++++++++ src/input.hpp | 20 ++ src/main.cpp | 588 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.hpp | 29 +++ src/menu.cpp | 48 ++++ src/menu.hpp | 2 + src/ticket.cpp | 182 +++++++++++++++ src/ticket.hpp | 4 + src/utils.cpp | 210 ++++++++++++++++++ src/utils.hpp | 34 +++ 13 files changed, 1592 insertions(+) create mode 100644 Makefile create mode 100644 src/file.cpp create mode 100644 src/file.hpp create mode 100644 src/input.cpp create mode 100644 src/input.hpp create mode 100644 src/main.cpp create mode 100644 src/main.hpp create mode 100644 src/menu.cpp create mode 100644 src/menu.hpp create mode 100644 src/ticket.cpp create mode 100644 src/ticket.hpp create mode 100644 src/utils.cpp create mode 100644 src/utils.hpp diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..a1fb8f84 --- /dev/null +++ b/Makefile @@ -0,0 +1,139 @@ +DEVKITPRO=/opt/devkitpro +DEVKITPPC=/opt/devkitpro/devkitPPC +WUT_ROOT=/opt/devkitpro/wut + +#------------------------------------------------------------------------------- +.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 := $(notdir $(CURDIR)) +BUILD := build +SOURCES := src +DATA := data +INCLUDES := include + +#------------------------------------------------------------------------------- +# options for code generation +#------------------------------------------------------------------------------- +CFLAGS := -g -Wall -O2 -ffunction-sections \ + $(MACHDEP) + +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ + +CXXFLAGS := $(CFLAGS) + +ASFLAGS := -g $(ARCH) +LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) + +LIBS := -lwut + +#------------------------------------------------------------------------------- +# 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) + +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_SRC) : $(HFILES_BIN) + +#------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#------------------------------------------------------------------------------- +endif +#------------------------------------------------------------------------------- diff --git a/src/file.cpp b/src/file.cpp new file mode 100644 index 00000000..660ab281 --- /dev/null +++ b/src/file.cpp @@ -0,0 +1,99 @@ +#include "file.hpp" +#include "utils.hpp" + +#include + +uint8_t readUInt8(std::string file, uint32_t pos) { + FILE* fp = fopen((installDir + file).c_str(), "rb"); + fseek(fp, pos, SEEK_SET); + uint8_t result = 0xFF; + fread(&result, 1, 1, fp); + fclose(fp); + return result; +} +uint16_t readUInt16(std::string file, uint32_t pos) { + FILE* fp = fopen((installDir + file).c_str(), "rb"); + fseek(fp, pos, SEEK_SET); + uint16_t result = 0xFFFF; + fread(&result, 2, 1, fp); + fclose(fp); + return result; +} +uint32_t readUInt32(std::string file, uint32_t pos) { + FILE* fp = fopen((installDir + file).c_str(), "rb"); + fseek(fp, pos, SEEK_SET); + uint32_t result = 0xFFFFFFFF; + fread(&result, 4, 1, fp); + fclose(fp); + return result; +} +uint64_t readUInt64(std::string file, uint32_t pos) { + FILE* fp = fopen((installDir + file).c_str(), "rb"); + fseek(fp, pos, SEEK_SET); + uint64_t result = 0xFFFFFFFFFFFFFFFF; + fread(&result, 8, 1, fp); + fclose(fp); + return result; +} + +void initRandom() { + srand(OSGetTime()); +} +void writeVoidBytes(FILE* fp, uint32_t len) { + uint8_t bytes[len] = {0}; + fwrite(bytes, len, 1, fp); +} +uint8_t charToByte(char c) { + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'A' && c <= 'F') + return c - 'A' + 0xA; + else if (c >= 'a' && c <= 'f') + return c - 'A' + 0xA; + return 0x77; +} +void writeCustomBytes(FILE* fp, std::string str) { + uint32_t len = str.size(); + uint8_t bytes[len / 2] = {0}; + for (uint32_t i = 0; i < len; i++) { + uint8_t b = charToByte(str[i]); + bytes[i / 2] += + ((i % 2 == 0) ? b * 0x10 : b); + } + fwrite(bytes, len / 2, 1, fp); +} +void writeRandomBytes(FILE* fp, uint32_t len) { + uint8_t bytes[len]; + for (uint32_t i = 0; i < len; i++) { + bytes[i] = rand() % 0xFF; + } + fwrite(bytes, len, 1, fp); +} +//Ported from various sites +int makeDir(const char *path) { + if (!path) + return -1; + + int res = mkdir(path, 777); + WHBLogPrintf("mkdir res: %d", res); + return res; +} +bool fileExists(const char *path) { + FILE *temp = fopen(path, "r"); + if (temp == NULL) + return false; + + fclose(temp); + return true; +} +bool dirExists(const char *path) { + WHBLogPrintf("f"); + FSDirectoryHandle dh; + + FSStatus fss = FSOpenDir(fsCli, fsCmd, path, &dh, 0); + WHBLogPrintf("fss: %u", fss); + if (fss == FS_STATUS_OK) { + FSCloseDir(fsCli, fsCmd, dh, 0); + return true; + } + return false; +} \ No newline at end of file diff --git a/src/file.hpp b/src/file.hpp new file mode 100644 index 00000000..8f278621 --- /dev/null +++ b/src/file.hpp @@ -0,0 +1,16 @@ +#include "main.hpp" + +uint8_t readUInt8(std::string file, uint32_t pos); +uint16_t readUInt16(std::string file, uint32_t pos); +uint32_t readUInt32(std::string file, uint32_t pos); +uint64_t readUInt64(std::string file, uint32_t pos); + +void initRandom(); +void writeVoidBytes(FILE* fp, uint32_t length); +uint8_t charToByte(char c); +void writeCustomBytes(FILE* fp, std::string str); +void writeRandomBytes(FILE* fp, uint32_t length); + +int makeDir(const char *path); +bool fileExists(const char *path); +bool dirExists(const char *path); \ No newline at end of file diff --git a/src/input.cpp b/src/input.cpp new file mode 100644 index 00000000..a3bd439b --- /dev/null +++ b/src/input.cpp @@ -0,0 +1,221 @@ +#include "input.hpp" +#include "utils.hpp" + +#include + +//WIP. This need a better implementation + +FSClient* swkbdCli; +nn::swkbd::CreateArg createArg; + +int globalMaxlength; +bool globalLimit; + +std::string outputStr; + +bool showed; + +bool SWKBD_Init() { + swkbdCli = (FSClient*)MEMAllocFromDefaultHeap(sizeof(FSClient)); + FSAddClient(swkbdCli, 0); + + WHBGfxInit(); + //createArg = nn::swkbd::CreateArg(); + createArg.regionType = nn::swkbd::RegionType::Europe; + createArg.workMemory = MEMAllocFromDefaultHeap(nn::swkbd::GetWorkMemorySize(0)); + createArg.fsClient = swkbdCli; + if (!nn::swkbd::Create(createArg)) { + WHBLogPrintf("nn::swkbd::Create failed"); + WHBGfxShutdown(); + return false; + } + WHBLogPrintf("nn::swkbd::Create success"); + WHBGfxShutdown(); + return true; +} +bool SWKBD_Show(int maxlength, bool limit) { + outputStr = "none"; + showed = true; + + WHBLogPrintf("WHBGfxInit(): %d", WHBGfxInit()); + // Show the keyboard + nn::swkbd::AppearArg appearArg; + appearArg.keyboardArg.configArg.languageType = nn::swkbd::LanguageType::English; + appearArg.inputFormArg.maxTextLength = maxlength; //-1 For unlimited size + globalMaxlength = maxlength; + if (!nn::swkbd::AppearInputForm(appearArg)) { + WHBLogPrintf("nn::swkbd::AppearInputForm failed"); + WHBGfxShutdown(); + return false; + } + globalLimit = limit; + if (!limit && maxlength != -1) + nn::swkbd::SetEnableOkButton(false); + WHBLogPrintf("nn::swkbd::AppearInputForm success"); + return true; +} +void SWKBD_Render(VPADStatus* vpad) { + if (globalMaxlength != -1) { + uint32_t len = std::u16string(nn::swkbd::GetInputFormString()).size(); + nn::swkbd::SetEnableOkButton((globalLimit) ? (len == (uint32_t)globalMaxlength) : (len <= (uint32_t)globalMaxlength)); + } + + nn::swkbd::ControllerInfo controllerInfo; + controllerInfo.vpad = vpad; + controllerInfo.kpad[0] = nullptr; + controllerInfo.kpad[1] = nullptr; + controllerInfo.kpad[2] = nullptr; + controllerInfo.kpad[3] = nullptr; + nn::swkbd::Calc(controllerInfo); + + if (nn::swkbd::IsNeedCalcSubThreadFont()) { + nn::swkbd::CalcSubThreadFont(); + WHBLogPrintf("SWKBD nn::swkbd::IsNeedCalcSubThreadFont()"); + } + + if (nn::swkbd::IsNeedCalcSubThreadPredict()) { + nn::swkbd::CalcSubThreadPredict(); + WHBLogPrintf("SWKBD nn::swkbd::CalcSubThreadPredict()"); + } + + WHBGfxBeginRender(); + + WHBGfxBeginRenderTV(); + WHBGfxClearColor(1.0f, 1.0f, 1.0f, 1.0f); + nn::swkbd::DrawTV(); + WHBGfxFinishRenderTV(); + + WHBGfxBeginRenderDRC(); + WHBGfxClearColor(0.0f, 0.0f, 0.0f, 0.0f); + nn::swkbd::DrawDRC(); + WHBGfxFinishRenderDRC(); + + WHBGfxFinishRender(); +} +void SWKBD_Hide(VPADStatus* vpad) { + for (uint8_t i = 0; i < 14; i++) { + SWKBD_Render(vpad); + } + WHBGfxShutdown(); +} +bool SWKBD_IsOkButton() { + if (nn::swkbd::IsDecideOkButton(nullptr)) { + nn::swkbd::DisappearInputForm(); + return true; + } + return false; +} +bool SWKBD_IsCancelButton() { + if (nn::swkbd::IsDecideCancelButton(nullptr)) { + nn::swkbd::DisappearInputForm(); + return true; + } + return false; +} +const char* SWKBD_GetError(KeyboardChecks check) { + if (!nn::swkbd::GetInputFormString()) + return "nn::swkbd::GetInputFormString returned NULL"; + + std::u16string output(nn::swkbd::GetInputFormString()); + + if (globalMaxlength != -1 && globalLimit && output.size() != (uint32_t)globalMaxlength) + return (std::string("Invalid input size (") + std::to_string(output.size()) + std::string("/") + std::to_string(globalMaxlength) + std::string(")")).c_str(); + + // Quick hack to get from a char16_t str to char for our log function + outputStr = ""; + + for (uint32_t i = 0; i < output.size(); ++i) { + if (output[i] > 0x7F) + outputStr += '?'; + else + outputStr += output[i]; + + switch (check) { + case CHECK_NUMERICAL: + if (!(output[i] >= '0' && output[i] <= '9')) + return "The wrote string must be only numerical [0->9]"; + break; + case CHECK_HEXADECIMAL: + if (!((output[i] >= '0' && output[i] <= '9') || (output[i] >= 'A' && output[i] <= 'F') || (output[i] >= 'a' && output[i] <= 'f'))) + return "The wrote string must be only hexadecimal [0->F]"; + break; + case CHECK_NOSPECIAL: + if (!((output[i] >= '0' && output[i] <= '9') || (output[i] >= 'A' && output[i] <= 'Z') || (output[i] >= 'a' && output[i] <= 'z') || output[i] != ' ')) + return "The wrote string must not have special characters [A->Z->9->Space]"; + break; + } + } + WHBLogPrintf("Input string: %s", outputStr.c_str()); + return NULL; +} +std::string SWKBD_GetText() { + return outputStr; +} +void SWKBD_Shutdown() { + nn::swkbd::Destroy(); + MEMFreeToDefaultHeap(createArg.workMemory); + + if (showed) { //Shutdown libraries properly + WHBGfxInit(); + WHBGfxShutdown(); + } +} + +bool showKeyboard(std::string* output, KeyboardChecks check, int maxlength, bool limit) { + WHBLogPrintf("Initialising SWKBD"); + bool kError = SWKBD_Show(maxlength, limit); + if (!kError) { + while(true) { + readInput(); + + startRefresh(); + colorStartRefresh(0xFF800000); + write(0, 0, "Error showing SWKBD:"); + write(0, 1, "nn::swkbd::AppearInputForm failed"); + errorScreen(2, B_RETURN); + endRefresh(); + + if (vpad.trigger == VPAD_BUTTON_B) + return false; + } + } + WHBLogPrintf("SWKBD initialised successfully"); + + while (true) { + readInput(); + SWKBD_Render(&vpad); + + if (SWKBD_IsOkButton()) { + WHBLogPrintf("SWKBD Ok button pressed"); + SWKBD_Hide(&vpad); + const char* kError = SWKBD_GetError(check); + if (kError) { + WHBLogPrintf("SWKBD Result string error: %s", kError); + while(true) { + readInput(); + + colorStartRefresh(0xFF800000); + write(0, 0, "Error on the resulted string:"); + write(0, 1, kError); + errorScreen(2, B_RETURN__Y_RETRY); + endRefresh(); + + if (vpad.trigger == VPAD_BUTTON_B) + return false; + else if (vpad.trigger == VPAD_BUTTON_Y) + return showKeyboard(output, check, maxlength, limit); + } + } + *output = SWKBD_GetText(); + return true; + } + + if (SWKBD_IsCancelButton()) { + WHBLogPrintf("SWKBD Cancel button pressed"); + SWKBD_Hide(&vpad); + startRefresh(); + endRefresh(); + return false; + } + } +} \ No newline at end of file diff --git a/src/input.hpp b/src/input.hpp new file mode 100644 index 00000000..bec75442 --- /dev/null +++ b/src/input.hpp @@ -0,0 +1,20 @@ +#include "main.hpp" + +enum KeyboardChecks { + CHECK_NONE = 0, //No check + CHECK_NUMERICAL = 1, //Only numbers + CHECK_HEXADECIMAL = 2, //Only hex + CHECK_NOSPECIAL = 3 //Only letters or numbers +}; + +bool SWKBD_Init(); +bool SWKBD_Show(int maxlength, bool limit); +void SWKBD_Render(VPADStatus* vpad); +void SWKBD_Hide(VPADStatus* vpad); +bool SWKBD_IsOkButton(); +bool SWKBD_IsCancelButton(); +const char* SWKBD_GetError(KeyboardChecks check); +std::string SWKBD_GetText(); +void SWKBD_Shutdown(); + +bool showKeyboard(std::string* output, KeyboardChecks check, int maxlength, bool limit); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 00000000..d5ef6571 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,588 @@ +#include "main.hpp" + +#include "utils.hpp" +#include "file.hpp" +#include "menu.hpp" +#include "input.hpp" +#include "ticket.hpp" + +#include +#include + +#include +#include + +#include + +FSClient *fsCli; +FSCmdBlock *fsCmd; + +VPADStatus vpad; +VPADReadError vError; + +std::string downloadUrl = "http://ccs.cdn.wup.shop.nintendo.net/ccs/download/"; +std::string installDir = "/vol/external01/install/"; + +uint16_t contents = 0xFFFF; //Contents count +uint16_t dcontent = 0xFFFF; //Actual content downloading + +typedef struct { + uint32_t app; + bool h3; + uint64_t size; +} File_to_download; + +const char* downloading = "UNKNOWN"; + +bool vibrateWhenFinished = true; +uint8_t vibrationPattern[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + +void readInput() { + VPADRead(VPAD_CHAN_0, &vpad, 1, &vError); + while (vError != VPAD_READ_SUCCESS) { + VPADRead(VPAD_CHAN_0, &vpad, 1, &vError); + colorStartRefresh(0xFF00FF00); + write(0, 0, "Error reading the WiiU Gamepad:"); + switch(vError) { + case VPAD_READ_SUCCESS: //Prevent 'default' detection + write(0, 1, "Success for an strange reason"); + WHBLogPrintf("VPAD Error: Success for an strange reason"); + return; + case VPAD_READ_NO_SAMPLES: + write(0, 1, "Waiting for samples..."); + WHBLogPrintf("VPAD Error: Waiting for samples..."); + break; + case VPAD_READ_INVALID_CONTROLLER: + write(0, 1, "Invalid controller"); + WHBLogPrintf("VPAD Error: Invalid controller"); + break; + default: + swrite(0, 1, std::string("Unknown error: 0x") + hex0(vError, 8)); + WHBLogPrintf("VPAD Error: Unknown error: 0x%s", hex0(vError, 8).c_str()); + break; + } + endRefresh(); + } + VPADGetTPCalibratedPoint(VPAD_CHAN_0, &vpad.tpNormal, &vpad.tpNormal); +} + +size_t write_callback(void *ptr, size_t size, size_t nmemb, FILE *stream) { + WHBLogPrintf("Writing file..."); + size_t written = fwrite(ptr, size, nmemb, stream); + return written; +} + +static int progressCallback(void *clientp, double dltotal, double dlnow) { + WHBLogPrintf("Downloading: %s (%u/%u) [%u%%] %u / %u bytes", downloading, dcontent, contents, (uint32_t)(dlnow / ((dltotal > 0) ? dltotal : 1) * 100), (uint32_t)dlnow, (uint32_t)dltotal); + startRefresh(); + if (dltotal == 0) + write(0, 0, "Preparing"); + else { + write(0, 0, "Downloading"); + if ((uint32_t)dltotal > 0) + swrite(0, 1, "[" + std::to_string((uint32_t)(dlnow / dltotal * 100)) + "%] " + std::to_string((uint32_t)dlnow / 1024 / 1024) + " / " + std::to_string((uint32_t)dltotal / 1024 / 1024) + " Mb"); + } + write(12, 0, downloading); + if (contents < 0xFFFF) + swrite(25, 0, "(" + std::to_string(dcontent) + "/" + std::to_string(contents) + ")"); + + writeDownloadLog(); + endRefresh(); + return 0; +} + +uint8_t downloadFile(std::string url, std::string file, uint8_t type) { + //Results: 0 = OK | 1 = Error | 2 = No ticket aviable + //Types: 0 = .app | 1 = .h3 | 2 = title.tmd | 3 = tilte.tik + downloading = file.c_str(); + + WHBLogPrintf("Download URL: %s", url.c_str()); + WHBLogPrintf("Download NAME: %s", file.c_str()); + CURL* curl = NULL; + FILE* fp; + int ret = 0; + + curl = curl_easy_init(); + if (!curl) { + WHBLogPrintf("curl_easy_init failed"); + colorStartRefresh(0xFF000000); + write(0, 0, "ERROR: curl_easy_init failed"); + write(0, 2, "File: "); + write(6, 2, downloading); + errorScreen(3, B_RETURN); + + curl_easy_cleanup(curl); + return 1; + } + WHBLogPrintf("curl_easy_init executed successfully"); + + std::string tUrl = downloadUrl + url; + std::string tFile = installDir + file; + + fp = fopen(tFile.c_str(), "wb"); + WHBLogPrintf("File opened successfully"); + curl_easy_setopt(curl, CURLOPT_URL, tUrl.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, progressCallback); + curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, NULL); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + + ret = curl_easy_perform(curl); + if (ret) { + WHBLogPrintf("curl_easy_perform returned an error: %d", ret); + + std::vector err; + switch(ret) { + default: + err.push_back("---> Unknown error"); + err.push_back("See: https://curl.haxx.se/libcurl/c/libcurl-errors.html"); + break; + case 6: + err.push_back("---> Network error"); + err.push_back("Your WiiU is not connected to the internet, check the"); + err.push_back("network settings and try again"); + break; + case 23: + err.push_back("---> Error from SD Card"); + err.push_back("The SD Card was extracted or invalid to write data,"); + err.push_back("re-insert it or use another one and restart the app"); + break; + case 56: + err.push_back("---> Network error"); + err.push_back("Failed while trying to download data, probably your"); + err.push_back("router was turned off, check the internet connecition"); + err.push_back("and try again"); + break; + } + + while (true) { + readInput(); + + colorStartRefresh(0xFF000000); + swrite(0, 0, "curl_easy_perform returned a non-valid value: " + std::to_string(ret)); + for (uint32_t i = 0; i < err.size(); i++) + swrite(0, i + 2, err[i]); + swrite(0, err.size() + 3, "File: " + std::string(downloading)); + errorScreen(err.size() + 4, B_RETURN__Y_RETRY); //CHANGE TO RETURN + endRefresh(); + + switch (vpad.trigger) { + case VPAD_BUTTON_B: + curl_easy_cleanup(curl); + fclose(fp); + return 1; + case VPAD_BUTTON_Y: + writeRetry(); + curl_easy_cleanup(curl); + fclose(fp); + return downloadFile(url, file, type); + } + } + } + WHBLogPrintf("curl_easy_perform executed successfully"); + + int resp = 404; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &resp); + WHBLogPrintf("The download returned: %u", resp); + if (resp != 200) { + if (type == 2 && resp == 404) { //Title.tmd not found + while(true) { + readInput(); + + colorStartRefresh(0xFF000000); + write(0, 0, "The download of title.tmd failed with error: "); + write(45, 0, std::to_string(resp).c_str()); + write(0, 2, "The title cannot be found on the NUS, maybe the provided"); + write(0, 3, "title ID doesn't exists or the TMD was deleted"); + errorScreen(4, B_RETURN__Y_RETRY); + endRefresh(); + + switch (vpad.trigger) { + case VPAD_BUTTON_B: + curl_easy_cleanup(curl); + fclose(fp); + return 1; + case VPAD_BUTTON_Y: + writeRetry(); + curl_easy_cleanup(curl); + fclose(fp); + return downloadFile(url, file, type); + } + } + } + else if (type == 3 && resp == 404) { //Fake ticket needed + fclose(fp); + return 2; + } + else { + uint8_t errLn = 2; + while(true) { + readInput(); + + colorStartRefresh(0xFF000000); + write(0, 0, "The download returned a result different to 200 (OK): "); + write(54, 0, std::to_string(resp).c_str()); + if (resp == 400) { + write(0, 2, "Request failed. Try again"); + errLn = 4; + } + write(0, errLn, "File: "); + write(6, errLn, downloading); + errorScreen(++errLn, B_RETURN__Y_RETRY); + endRefresh(); + + switch (vpad.trigger) { + case VPAD_BUTTON_B: + curl_easy_cleanup(curl); + fclose(fp); + return 1; + case VPAD_BUTTON_Y: + curl_easy_cleanup(curl); + fclose(fp); + return downloadFile(url, file, type); + } + } + } + } + else { + WHBLogPrintf("The file was downloaded successfully"); + addToDownloadLog("Download " + std::string(downloading)); + } + + curl_easy_cleanup(curl); + fclose(fp); + return 0; +} + +bool downloadTitle(std::string titleID, std::string titleVer, std::string folderName) { + WHBLogPrintf("Downloading title... tID: %s, tVer: %s, fName: %s", titleID.c_str(), titleVer.c_str(), folderName.c_str()); + + downloadUrl = "http://ccs.cdn.wup.shop.nintendo.net/ccs/download/" + titleID + "/"; + + std::string verLog = ""; + if (titleVer != "") { + verLog = " [v" + titleVer + "]"; + } + folderName = (folderName == "") ? titleID : (folderName + " [" + titleID + "]" + verLog); + + installDir = "/vol/external01/install/" + folderName + "/"; + + addToDownloadLog("Started the download of: " + titleID + verLog); + addToDownloadLog("The content will be saved on \"sd:/install/" + folderName + "\""); + + if (makeDir(installDir.c_str()) == FS_STATUS_EXISTS) + addToDownloadLog("WARNING: The download directory already exists"); + else + addToDownloadLog("Download directory successfully created"); + + startRefresh(); + write(0, 0, "Preparing the download..."); + writeDownloadLog(); + endRefresh(); + + WHBLogPrintf("Downloading TMD..."); + if (downloadFile((titleVer == "") ? "tmd" : ("tmd." + titleVer), "title.tmd", 2)) + return true; + addToDownloadLog("TMD Downloaded"); + + std::string titleType; + switch (readUInt32("title.tmd", 0x18C)) { //Title type + case 0x00050000: + titleType = "eShop or Packed"; + break; + case 0x00050002: + titleType = "eShop/Kiosk demo"; + break; + case 0x0005000C: + titleType = "eShop DLC"; + break; + case 0x0005000E: + titleType = "eShop Update"; + break; + case 0x00050010: + titleType = "System Application"; + break; + case 0x0005001B: + titleType = "System Data Archive"; + break; + case 0x00050030: + titleType = "Applet"; + break; + // vWii // + case 0x7: + titleType = "vWii IOS"; + break; + case 0x00070002: + titleType = "vWii Default Channel"; + break; + case 0x00070008: + titleType = "vWii System Channel"; + break; + default: + titleType = "Unknown (" + hex0(readUInt32("title.tmd", 0x18C), 8) + ")"; + break; + } + addToDownloadLog("=>Title type: " + titleType); + + uint8_t tikRes = downloadFile("cetk", "title.tik", 3); + if (tikRes == 1) { + return true; + } + else if (tikRes == 2) { + addToDownloadLog("Title.tik not found on the NUS. A fake ticket can be created"); + std::string encKey = ""; + while (true) { + readInput(); + + startRefresh(); + swrite(0, 0, "Input the Encrypted title key of " + titleID + " to create a"); + write(0, 1, " fake ticket (Without it, the download could not be installed)"); + write(0, 3, "You can get it from any 'WiiU title key site'"); + write(0, 5, "Press (A) to show the keyboard [32 hexadecimal numbers]"); + write(0, 6, "Press (B) to continue the download without the fake ticket"); + endRefresh(); + + if (vpad.trigger == VPAD_BUTTON_A) { + if (showKeyboard(&encKey, CHECK_HEXADECIMAL, 32, true)) { + startRefresh(); + write(0, 0, "Creating fake title.tik"); + writeDownloadLog(); + endRefresh(); + FILE* tik; + tik = fopen((installDir + "title.tik").c_str(), "wb"); + generateTik(tik, titleID, encKey); + fclose(tik); + addToDownloadLog("Fake ticket created successfully"); + break; + } + } + else if (vpad.trigger == VPAD_BUTTON_B) { + addToDownloadLog("Encrypted key wasn't wrote. Continuing without fake ticket"); + addToDownloadLog("(The download needs a fake ticket to be installed)"); + break; + } + } + } + + contents = readUInt16("title.tmd", 0x1DE); + File_to_download ftd[contents]; //Files to download + + //Get .app and .h3 files + for (uint16_t i = 0; i < contents; i++) { + ftd[i].app = readUInt32("title.tmd", 0xB04 + i * 0x30); //.app file + ftd[i].h3 = readUInt16("title.tmd", 0xB0A + i * 0x30) == 0x2003 ? true : false; //.h3? + ftd[i].size = readUInt64("title.tmd", 0xB0C + i * 0x30); //size + } + + for (dcontent = 0; dcontent < contents; dcontent++) { + if (downloadFile(hex0(ftd[dcontent].app, 8), hex0(ftd[dcontent].app, 8) + std::string(".app"), 0) == 1) + return true; + if (ftd[dcontent].h3) { //.h3 download + if (downloadFile(hex0(ftd[dcontent].app, 8) + std::string(".h3"), hex0(ftd[dcontent].app, 8) + std::string(".h3"), 1) == 1) + return true; + } + } + + WHBLogPrintf("Creating CERT..."); + startRefresh(); + write(0, 0, "Creating CERT"); + writeDownloadLog(); + endRefresh(); + + FILE* cert; + cert = fopen((installDir + "title.cert").c_str(), "wb"); + + writeCustomBytes(cert, "000100042EA66C66CFF335797D0497B77A197F9FE51AB5A41375DC73FD9E0B10669B1B9A5B7E8AB28F01B67B6254C14AA1331418F25BA549004C378DD72F0CE63B1F7091AAFE3809B7AC6C2876A61D60516C43A63729162D280BE21BE8E2FE057D8EB6E204242245731AB6FEE30E5335373EEBA970D531BBA2CB222D9684387D5F2A1BF75200CE0656E390CE19135B59E14F0FA5C1281A7386CCD1C8EC3FAD70FBCE74DEEE1FD05F46330B51F9B79E1DDBF4E33F14889D05282924C5F5DC2766EF0627D7EEDC736E67C2E5B93834668072216D1C78B823A072D34FF3ECF9BD11A29AF16C33BD09AFB2D74D534E027C19240D595A68EBB305ACC44AB38AB820C6D426560C"); + writeVoidBytes(cert, 0x3C); + writeCustomBytes(cert, "526F6F742D43413030303030303033"); + writeVoidBytes(cert, 0x34); + writeCustomBytes(cert, "0143503030303030303062"); + writeVoidBytes(cert, 0x36); + writeCustomBytes(cert, "137A080BA689C590FD0B2F0D4F56B632FB934ED0739517B33A79DE040EE92DC31D37C7F73BF04BD3E44E20AB5A6FEAF5984CC1F6062E9A9FE56C3285DC6F25DDD5D0BF9FE2EFE835DF2634ED937FAB0214D104809CF74B860E6B0483F4CD2DAB2A9602BC56F0D6BD946AED6E0BE4F08F26686BD09EF7DB325F82B18F6AF2ED525BFD828B653FEE6ECE400D5A48FFE22D538BB5335B4153342D4335ACF590D0D30AE2043C7F5AD214FC9C0FE6FA40A5C86506CA6369BCEE44A32D9E695CF00B4FD79ADB568D149C2028A14C9D71B850CA365B37F70B657791FC5D728C4E18FD22557C4062D74771533C70179D3DAE8F92B117E45CB332F3B3C2A22E705CFEC66F6DA3772B00010001"); + writeVoidBytes(cert, 0x35); + writeCustomBytes(cert, "010003704138EFBBBDA16A987DD901326D1C9459484C88A2861B91A312587AE70EF6237EC50E1032DC39DDE89A96A8E859D76A98A6E7E36A0CFE352CA893058234FF833FCB3B03811E9F0DC0D9A52F8045B4B2F9411B67A51C44B5EF8CE77BD6D56BA75734A1856DE6D4BED6D3A242C7C8791B3422375E5C779ABF072F7695EFA0F75BCB83789FC30E3FE4CC8392207840638949C7F688565F649B74D63D8D58FFADDA571E9554426B1318FC468983D4C8A5628B06B6FC5D507C13E7A18AC1511EB6D62EA5448F83501447A9AFB3ECC2903C9DD52F922AC9ACDBEF58C6021848D96E208732D3D1D9D9EA440D91621C7A99DB8843C59C1F2E2C7D9B577D512C166D6F"); + writeCustomBytes(cert, "7E1AAD4A774A37447E78FE2021E14A95D112A068ADA019F463C7A55685AABB6888B9246483D18B9C806F474918331782344A4B8531334B26303263D9D2EB4F4BB99602B352F6AE4046C69A5E7E8E4A18EF9BC0A2DED61310417012FD824CC116CFB7C4C1F7EC7177A17446CBDE96F3EDD88FCD052F0B888A45FDAF2B631354F40D16E5FA9C2C4EDA98E798D15E6046DC5363F3096B2C607A9D8DD55B1502A6AC7D3CC8D8C575998E7D796910C804C495235057E91ECD2637C9C1845151AC6B9A0490AE3EC6F47740A0DB0BA36D075956CEE7354EA3E9A4F2720B26550C7D394324BC0CB7E9317D8A8661F42191FF10B08256CE3FD25B745E5194906B4D61CB4C2E"); + writeVoidBytes(cert, 0x3C); + writeCustomBytes(cert, "526F6F74"); + writeVoidBytes(cert, 0x3F); + writeCustomBytes(cert, "0143413030303030303033"); + writeVoidBytes(cert, 0x36); + writeCustomBytes(cert, "7BE8EF6CB279C9E2EEE121C6EAF44FF639F88F078B4B77ED9F9560B0358281B50E55AB721115A177703C7A30FE3AE9EF1C60BC1D974676B23A68CC04B198525BC968F11DE2DB50E4D9E7F071E562DAE2092233E9D363F61DD7C19FF3A4A91E8F6553D471DD7B84B9F1B8CE7335F0F5540563A1EAB83963E09BE901011F99546361287020E9CC0DAB487F140D6626A1836D27111F2068DE4772149151CF69C61BA60EF9D949A0F71F5499F2D39AD28C7005348293C431FFBD33F6BCA60DC7195EA2BCC56D200BAF6D06D09C41DB8DE9C720154CA4832B69C08C69CD3B073A0063602F462D338061A5EA6C915CD5623579C3EB64CE44EF586D14BAAA8834019B3EEBEED37900010001"); + writeVoidBytes(cert, 0x35); + writeCustomBytes(cert, "010004919EBE464AD0F552CD1B72E7884910CF55A9F02E50789641D896683DC005BD0AEA87079D8AC284C675065F74C8BF37C88044409502A022980BB8AD48383F6D28A79DE39626CCB2B22A0F19E41032F094B39FF0133146DEC8F6C1A9D55CD28D9E1C47B3D11F4F5426C2C780135A2775D3CA679BC7E834F0E0FB58E68860A71330FC95791793C8FBA935A7A6908F229DEE2A0CA6B9B23B12D495A6FE19D0D72648216878605A66538DBF376899905D3445FC5C727A0E13E0E2C8971C9CFA6C60678875732A4E75523D2F562F12AABD1573BF06C94054AEFA81A71417AF9A4A066D0FFC5AD64BAB28B1FF60661F4437D49E1E0D9412EB4BCACF4CFD6A3408847982"); + writeVoidBytes(cert, 0x3C); + writeCustomBytes(cert, "526F6F742D43413030303030303033"); + writeVoidBytes(cert, 0x34); + writeCustomBytes(cert, "0158533030303030303063"); + writeVoidBytes(cert, 0x36); + writeCustomBytes(cert, "137A0894AD505BB6C67E2E5BDD6A3BEC43D910C772E9CC290DA58588B77DCC11680BB3E29F4EABBB26E98C2601985C041BB14378E689181AAD770568E928A2B98167EE3E10D072BEEF1FA22FA2AA3E13F11E1836A92A4281EF70AAF4E462998221C6FBB9BDD017E6AC590494E9CEA9859CEB2D2A4C1766F2C33912C58F14A803E36FCCDCCCDC13FD7AE77C7A78D997E6ACC35557E0D3E9EB64B43C92F4C50D67A602DEB391B06661CD32880BD64912AF1CBCB7162A06F02565D3B0ECE4FCECDDAE8A4934DB8EE67F3017986221155D131C6C3F09AB1945C206AC70C942B36F49A1183BCD78B6E4B47C6C5CAC0F8D62F897C6953DD12F28B70C5B7DF751819A983465262500010001"); + writeVoidBytes(cert, 0x34); + + fclose(cert); + + addToDownloadLog("Created CERT"); + addToDownloadLog("Download finished successfully"); + + for (int i = 0; i < 0x10; i++) + VPADControlMotor(VPAD_CHAN_0, vibrationPattern, 0xF); + + enableShutdown(); + + while (WHBProcIsRunning()) { + readInput(); + + colorStartRefresh(0x00800000); + swrite(0, 0, "Title " + titleID + " downloaded successfully"); + write(0, 1, "Press (A) to return"); + writeDownloadLog(); + endRefresh(); + + if (vpad.trigger == VPAD_BUTTON_A) { + clearDownloadLog(); + return true; + } + } + return false; +} + +int main() { + WHBProcInit(); + WHBLogUdpInit(); + WHBLogPrintf("\nInitialising libraries..."); + + FSInit(); + fsCli = (FSClient*)MEMAllocFromDefaultHeap(sizeof(FSClient)); + FSAddClient(fsCli, 0); + fsCmd = (FSCmdBlock*)MEMAllocFromDefaultHeap(sizeof(FSCmdBlock)); + FSInitCmdBlock(fsCmd); + WHBLogPrintf("FS started successfully"); + + VPADInit(); + WHBLogPrintf("VPAD started successfully"); + + SWKBD_Init(); + WHBLogPrintf("SWKBD started successfully"); + + initScreen(); + WHBLogPrintf("OSScreen started successfully"); + + initRandom(); + WHBLogPrintf("Random started successfully"); + + makeDir(installDir.c_str()); + + while(WHBProcIsRunning()) { +mainLoop: + readInput(); + + writeMainMenu(); + + switch (vpad.trigger) { + case VPAD_BUTTON_A: { //Download title + WHBLogPrintf("Download title screen"); + std::string titleID; + std::string titleVer; + std::string folderName; + + while (true) { + readInput(); + + startRefresh(); + write(0, 0, "Input a title ID to download their content [Ex: 000500001234ABCD]"); + write(0, 2, "If creating a fake ticket.tik is needed, you will need to write"); + write(0, 3, " his encrypted tile key (Check it on any 'WiiU title key site')"); + write(0, 5, "[USE THE VALID ENCRYPTED TITLE KEY FOR EACH TITLE, OTHERWISE, "); + write(0, 6, " IT WILL THROW A TITLE.TIK ERROR WHILE INSTALLING IT]"); + write(0, 8, "Downloaded titles can be installed with WUP Installer GX2"); + write(0, 10, "Press (A) to show the keyboard [Only input hexadecimal numbers]"); + write(0, 11, "Press (B) to return"); + endRefresh(); + + if (vpad.trigger == VPAD_BUTTON_A) { + if (showKeyboard(&titleID, CHECK_HEXADECIMAL, 16, true)) + break; + } + else if (vpad.trigger == VPAD_BUTTON_B) + goto mainLoop; + } + + while(true) { + readInput(); + + startRefresh(); + write(0, 0, "Provided title ID [Only 16 digit hexadecimal]:"); + swrite(3, 1, titleID); + write(0, 2, "Provided title version [Only numbers]:"); + swrite(3, 3, (titleVer != "") ? titleVer : ""); + write(0, 4, "Custom folder name [Only text and numbers]:"); + swrite(3, 5, "sd:/install/"); + swrite(15, 5, (folderName != "") ? folderName : ""); + + write(0, 7, "Press (A) to download"); + write(0, 8, "Press (B) to return"); + write(0, 9, "-------------------------------------------------------"); + write(0, 10, "Press (UP) to set the title ID"); + write(0, 11, "Press (RIGHT) to set the title version"); + write(0, 12, "Press (DOWN) to set a custom name to the download folder"); + write(0, 13, "-------------------------------------------------------"); + swrite(0, 14, "Press (Y) to " + std::string((vibrateWhenFinished) ? "deactivate" : "activate") + " the vibration when download finish"); //Thinking to change this to activate HOME Button led + endRefresh(); + + if (vpad.trigger == VPAD_BUTTON_A) + break; + else if (vpad.trigger == VPAD_BUTTON_B) + goto mainLoop; + else if (vpad.trigger == VPAD_BUTTON_UP) { + std::string tmpTitleID = titleID; + if (!showKeyboard(&titleID, CHECK_HEXADECIMAL, 16, true)) + titleID = tmpTitleID; + } + else if (vpad.trigger == VPAD_BUTTON_RIGHT) { + if (!showKeyboard(&titleVer, CHECK_NUMERICAL, 5, false)) + titleVer == ""; + } + else if (vpad.trigger == VPAD_BUTTON_DOWN) { + if (!showKeyboard(&folderName, CHECK_NOSPECIAL, FILENAME_MAX, false)) + folderName == ""; + } + else if (vpad.trigger == VPAD_BUTTON_Y) { + vibrateWhenFinished = !vibrateWhenFinished; + } + } + disableShutdown(); + + if (!downloadTitle(titleID, titleVer, folderName)) + goto exit; + + downloadUrl = "http://ccs.cdn.wup.shop.nintendo.net/ccs/download/"; + installDir = "/vol/external01/install/"; + break; + } + case VPAD_BUTTON_Y: { + WHBLogPrintf("Generate fake ticket screen"); + if (!generateFakeTicket()) + goto exit; + break; + } + } + } +exit: + WHBLogPrintf("Exiting..."); + SWKBD_Shutdown(); + shutdownScreen(); + + FSDelClient(fsCli, 0); + MEMFreeToDefaultHeap(fsCli); + MEMFreeToDefaultHeap(fsCmd); + + FSShutdown(); + VPADShutdown(); + socket_lib_finish(); + WHBLogUdpDeinit(); + WHBProcShutdown(); + + return 1; +} \ No newline at end of file diff --git a/src/main.hpp b/src/main.hpp new file mode 100644 index 00000000..3404fe31 --- /dev/null +++ b/src/main.hpp @@ -0,0 +1,29 @@ +#include <stdio.h> +#include <stdlib.h> //rand() +#include <malloc.h> +#include <string> +#include <cstring> +#include <vector> +#include <sys/stat.h> //mkdir + +#include <coreinit/filesystem.h> +#include <coreinit/memdefaultheap.h> + +#include <vpad/input.h> + +#include <whb/log.h> +#include <whb/log_udp.h> +#include <whb/gfx.h> + +#define DEBUG false + +extern FSClient *fsCli; +extern FSCmdBlock *fsCmd; + +extern VPADStatus vpad; +extern VPADReadError vError; + +extern std::string downloadUrl; +extern std::string installDir; + +void readInput(); \ No newline at end of file diff --git a/src/menu.cpp b/src/menu.cpp new file mode 100644 index 00000000..48af596c --- /dev/null +++ b/src/menu.cpp @@ -0,0 +1,48 @@ +#include "menu.hpp" +#include "utils.hpp" + +void writeMainMenu() { + startRefresh(); + write(0, 0, "=============================================================="); + write(0, 1, "= WUPDownloader by Pokes303 [1.0] ="); + write(0, 2, "=============================================================="); + + //write(0, 1, "= WiiU Title Downloader by Pokes303 [1.0] ="); + //write(0, 1, "= WiiU-FreeShop by Pokes303 [1.0] ="); + + write(0, 4, "Press (A) to download a content from the NUS with the title ID"); + write(0, 5, "Press (Y) to generate a fake <title.tik> file"); + + write(0, 7, "Press (HOME) to exit"); + + + write(0, 14, "DO NOT EJECT THE SD CARD OR THE APPLICATION WILL CRASH!"); + + write(0, 16, "Content = Game/Update/Dlc/Demo/Applet/etc..."); + endRefresh(); +} + +void writeFolderMenu() { + write(3, 0, "Select a folder to place the generated fake ticket.tik:"); + write(0, 1, "=============================================================="); + + + write(0, 15, "=============================================================="); + write(0, 16, "Press (A) to select || (B) to return || (Y) to refresh"); + write(16, 17, "Searching on => SD:/install/"); +} + +/* TODO: +*Support for 'that title key site' +*Support for modding games (like VC black screen)?? +*Multiple downloads +*Custom download servers +*SWKBD Support for search +*Download system updates +*Fix downloads/tickets +*Use DRC or WiiMote +*Download's error code buffer set +*Download titles to USB +*Create manual +*Decrypt downloads (Loadiine mode?) +*/ \ No newline at end of file diff --git a/src/menu.hpp b/src/menu.hpp new file mode 100644 index 00000000..cfe41253 --- /dev/null +++ b/src/menu.hpp @@ -0,0 +1,2 @@ +void writeMainMenu(); +void writeFolderMenu(); \ No newline at end of file diff --git a/src/ticket.cpp b/src/ticket.cpp new file mode 100644 index 00000000..975b7e10 --- /dev/null +++ b/src/ticket.cpp @@ -0,0 +1,182 @@ +#include "ticket.hpp" + +#include "utils.hpp" +#include "file.hpp" +#include "menu.hpp" +#include "input.hpp" + +#include <whb/proc.h> + +void generateTik(FILE* tik, std::string titleID, std::string encKey) { //Based on NUSPacker tik creation function + WHBLogPrintf("Generate tik function"); + + writeCustomBytes(tik, "00010004"); + writeRandomBytes(tik, 0x100); + writeVoidBytes(tik, 0x3C); + writeCustomBytes(tik, "526F6F742D434130303030303030332D58533030303030303063000000000000"); + writeVoidBytes(tik, 0x5C); + writeCustomBytes(tik, "010000"); + writeCustomBytes(tik, encKey); + writeCustomBytes(tik, "000005"); + writeRandomBytes(tik, 0x06); + writeVoidBytes(tik, 0x04); + writeCustomBytes(tik, titleID); + writeCustomBytes(tik, "00000011000000000000000000000005"); + writeVoidBytes(tik, 0xB0); + writeCustomBytes(tik, "00010014000000AC000000140001001400000000000000280000000100000084000000840003000000000000FFFFFF01"); + writeVoidBytes(tik, 0x7C); +} + +bool generateFakeTicket() { + FSDirectoryHandle dh; + FSStatus fsr = FSOpenDir(fsCli, fsCmd, installDir.c_str(), &dh, 0); + WHBLogPrintf("fsr: %u", fsr); + + std::vector<std::string> tikFolders; + + if (fsr == FS_STATUS_OK) { + + tikFolders.push_back("<CURRENT DIRECTORY>"); + FSDirectoryEntry de; + while (FSReadDir(fsCli, fsCmd, dh, &de, 0) == FS_STATUS_OK) { + if (de.info.flags == 0x8C000000) //Check if it's a directory + tikFolders.push_back(de.name); + } + FSCloseDir(fsCli, fsCmd, dh, 0); + } + + uint32_t tikCursor = 0; + uint32_t tikPos = 0; + bool mov = tikFolders.size() >= 12; + + while(WHBProcIsRunning()) { + readInput(); + + startRefresh(); + writeFolderMenu(); + + if (fsr == FS_STATUS_OK) { + for (uint32_t i = 0; i < 13 && i < tikFolders.size(); i++) { + swrite(1, i + 2, tikFolders[i + tikPos] + ((i == 0) ? "" : "/")); + if (tikCursor == i) + write(0, tikCursor + 2, ">"); + } + } + else { + write(0, 2, "ERROR OPENING THE FOLDER:"); + swrite(32, 2, std::to_string(fsr)); + write(0, 2, "Try to relaunch the application"); + } + + switch(vpad.trigger) { + case VPAD_BUTTON_A: + if (fsr == FS_STATUS_OK) + goto inputTikValues; + else + break; + case VPAD_BUTTON_B: + return true; + case VPAD_BUTTON_Y: + return generateFakeTicket(); + case VPAD_BUTTON_UP: + if (tikCursor <= 0) { + if (mov) { + if (tikPos > 0) { + tikPos--; + } + else { + tikCursor = 12; + tikPos = tikFolders.size() % 12 - 1; + } + } + else { + tikCursor = tikFolders.size() - 1; + } + } + else { + tikCursor--; + } + break; + case VPAD_BUTTON_DOWN: + if (tikCursor >= 12 || tikCursor >= tikFolders.size() - 1) { + if (mov && tikPos < tikFolders.size() % 12 - 1) + tikPos++; + else { + tikCursor = 0; + tikPos = 0; + } + } + else { + tikCursor++; + } + break; + } + endRefresh(); + } + return false; + +inputTikValues: + std::string titleID; + std::string encKey; + + while (WHBProcIsRunning()) { + readInput(); + + startRefresh(); + write(0, 0, "Title ID:"); + swrite(1, 1, (titleID == "") ? "NOT SET" : titleID); + write(0, 2, "Encrypted title key"); + swrite(1, 3, (encKey == "") ? "NOT SET" : encKey); + + write(0, 5, "You need to set the title ID and the Encrypted title key"); + write(0, 6, " to generate a fake ticket"); + + write(0, 8, "Press (A) to continue"); + if (titleID == "" || encKey == "") + write(0, 8, "====================="); + write(0, 9, "Press (B) to return"); + write(0, 10, "-------------------------------------------------------"); + write(0, 11, "Press (UP) to set the title ID"); + write(0, 12, "Press (DOWN) to set the Encrypted title key"); + endRefresh(); + + switch (vpad.trigger) { + case VPAD_BUTTON_A: + if (titleID != "" && encKey != "") { + startRefresh(); + write(0, 0, "Generating fake ticket..."); + endRefresh(); + + FILE* fakeTik; + std::string tikPath = ((tikCursor == 0) ? ("ticket_" + titleID + "_" + encKey + ".tik") : tikFolders[tikCursor + tikPos + 1]); + fakeTik = fopen((installDir + tikPath).c_str(), "wb"); + generateTik(fakeTik, titleID, encKey); + fclose(fakeTik); + + while(WHBProcIsRunning()) { + readInput(); + + colorStartRefresh(0x00800000); + write(0, 0, "Fake ticket generated on:"); + swrite(0, 1, " SD:/install/" + tikPath); + write(0, 3, "Press (A) to return"); + endRefresh(); + + if (vpad.trigger == VPAD_BUTTON_A) + return true; + } + return false; + } + break; + case VPAD_BUTTON_B: + return true; + case VPAD_BUTTON_UP: + showKeyboard(&titleID, CHECK_HEXADECIMAL, 16, true); + break; + case VPAD_BUTTON_DOWN: + showKeyboard(&encKey, CHECK_HEXADECIMAL, 32, true); + break; + } + } + return false; +} \ No newline at end of file diff --git a/src/ticket.hpp b/src/ticket.hpp new file mode 100644 index 00000000..1d906701 --- /dev/null +++ b/src/ticket.hpp @@ -0,0 +1,4 @@ +#include "main.hpp" + +void generateTik(FILE* tik, std::string titleID, std::string encKey); +bool generateFakeTicket(); \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 00000000..7f306ed4 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,210 @@ +#include "utils.hpp" + +#include <coreinit/screen.h> +#include <coreinit/cache.h> +#include <coreinit/systeminfo.h> +#include <coreinit/energysaver.h> + +#define MAX_DOWNLOADLOG_DRC 14 + +size_t tvBufferSize; +size_t drcBufferSize; + +void* tvBuffer; +void* drcBuffer; + +bool initScreen() { + OSScreenInit(); + + tvBufferSize = OSScreenGetBufferSizeEx(SCREEN_TV); + drcBufferSize = OSScreenGetBufferSizeEx(SCREEN_DRC); + + tvBuffer = memalign(0x100, tvBufferSize); + drcBuffer = memalign(0x100, drcBufferSize); + + if (!tvBuffer || !drcBuffer) { + WHBLogPrintf("Error initialising screen library"); + shutdownScreen(); + return false; + } + + OSScreenSetBufferEx(SCREEN_TV, tvBuffer); + OSScreenSetBufferEx(SCREEN_DRC, drcBuffer); + + OSScreenEnableEx(SCREEN_TV, true); + OSScreenEnableEx(SCREEN_DRC, true); + WHBLogPrintf("Screen library initialised successfully"); + return true; +} +void shutdownScreen() { + if (tvBuffer) free(tvBuffer); + if (drcBuffer) free(drcBuffer); + + OSScreenShutdown(); +} + +void startRefresh() { + OSScreenClearBufferEx(SCREEN_TV, 0x00000000); + OSScreenClearBufferEx(SCREEN_DRC, 0x00000000); +} +void endRefresh() { + DCFlushRange(tvBuffer, tvBufferSize); + DCFlushRange(drcBuffer, drcBufferSize); + + OSScreenFlipBuffersEx(SCREEN_TV); + OSScreenFlipBuffersEx(SCREEN_DRC); +} + +void write(uint32_t row, uint32_t column, const char* str) { //Write to the two screens + OSScreenPutFontEx(SCREEN_TV, row, column, str); + OSScreenPutFontEx(SCREEN_DRC, row, column, str); +} +void swrite(uint32_t row, uint32_t column, std::string str) { //string write() + OSScreenPutFontEx(SCREEN_TV, row, column, str.c_str()); + OSScreenPutFontEx(SCREEN_DRC, row, column, str.c_str()); +} + +/*void dwrite(uint32_t column, const char* str, ...) { //const char* write + OSScreenPutFontEx(SCREEN_TV, 0, column, str); + OSScreenPutFontEx(SCREEN_DRC, 0, column, str); +}*/ + +std::vector<std::string> downloadLog; +void addToDownloadLog(std::string str) { + if (downloadLog.size() >= MAX_DOWNLOADLOG_DRC) + downloadLog.erase(downloadLog.begin()); + downloadLog.push_back(str); + WHBLogPrintf(str.c_str()); +} +void clearDownloadLog() { + downloadLog.clear(); +} +void writeDownloadLog() { + write(0, 2, "------------------------------------------------------------"); + for (uint8_t i = 0; i < MAX_DOWNLOADLOG_DRC; i++) { + if (i >= downloadLog.size()) + break; + write(0, i + 3, downloadLog[i].c_str()); + } +} + +void colorStartRefresh(uint32_t color) { + OSScreenClearBufferEx(SCREEN_TV, color); + OSScreenClearBufferEx(SCREEN_DRC, color); +} + +void errorScreen(uint32_t line, ErrorOptions option) { + write(0, line++, "------------------------------------------------------------"); + switch (option) { + case B_RETURN: + write(0, line, "Press (B) to return"); + return; + case B_RETURN__A_CONTINUE: + write(0, line++, "Press (A) to continue"); + write(0, line, "Press (B) to return"); + return; + case B_RETURN__Y_RETRY: + write(0, line++, "Press (Y) to retry"); + write(0, line, "Press (B) to return"); + return; + } +} + +void writeRetry() { + startRefresh(); + write(0, 0, "Preparing the retry..."); + writeDownloadLog(); + endRefresh(); +} + +void enableShutdown() { + OSEnableHomeButtonMenu(true); + IMEnableAPD(); +} +void disableShutdown() { + OSEnableHomeButtonMenu(false); + uint32_t enabled = 0; + IMIsAPDEnabled(&enabled); + WHBLogPrintf("Enabled: %u", enabled); + IMDisableAPD(); +} + +std::string hex(uint64_t i) { + std::string result; + while (true) { + uint64_t div = i / 16; + uint64_t remainder = i % 16; + std::string wRemainder = std::to_string(remainder); + if (remainder > 9) { + switch (remainder) { + case 10: + wRemainder = "A"; + break; + case 11: + wRemainder = "B"; + break; + case 12: + wRemainder = "C"; + break; + case 13: + wRemainder = "D"; + break; + case 14: + wRemainder = "E"; + break; + case 15: + wRemainder = "F"; + break; + } + } + result = wRemainder + result; + + if (div != 0) + i = div; + else + break; + } + result = "0x" + result; + return result; +} +std::string hex0(uint64_t i, uint8_t digits) { + std::string result; + while (true) { + uint64_t div = i / 16; + uint64_t remainder = i % 16; + std::string wRemainder = std::to_string(remainder); + if (remainder > 9) { + switch (remainder) { + case 10: + wRemainder = "A"; + break; + case 11: + wRemainder = "B"; + break; + case 12: + wRemainder = "C"; + break; + case 13: + wRemainder = "D"; + break; + case 14: + wRemainder = "E"; + break; + case 15: + wRemainder = "F"; + break; + } + } + result = wRemainder + result; + + if (div != 0) + i = div; + else + break; + } + if (result.size() > digits) + return std::string("too few digits error"); + for (int i = digits - result.size(); i > 0; i--) + result = "0" + result; + return result; +} \ No newline at end of file diff --git a/src/utils.hpp b/src/utils.hpp new file mode 100644 index 00000000..cedbce29 --- /dev/null +++ b/src/utils.hpp @@ -0,0 +1,34 @@ +#include "main.hpp" + +bool initScreen(); +void shutdownScreen(); + +void startRefresh(); +void endRefresh(); + +void write(uint32_t row, uint32_t column, const char* str); +void swrite(uint32_t row, uint32_t column, std::string str); +void writeParsed(uint32_t row, uint32_t column, const char* str); + +void addToDownloadLog(std::string str); +void clearDownloadLog(); +void writeDownloadLog(); + +void colorStartRefresh(uint32_t color); + +enum ErrorOptions { + B_RETURN = 0, + B_RETURN__A_CONTINUE = 1, + B_RETURN__Y_RETRY = 2 +}; + +void errorScreen(uint32_t line, ErrorOptions option); +void writeRetry(); + +void enableShutdown(); +void disableShutdown(); + +std::string b_tostring(bool b); + +std::string hex(uint64_t i); //ex: 50D1 +std::string hex0(uint64_t i, uint8_t digits); //ex: 000050D1 \ No newline at end of file