diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 983eec9..a88e0d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: - name: zip artifact run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip *.rpx - name: Create Release - uses: "softprops/action-gh-release@v1" + uses: "softprops/action-gh-release@v2" with: tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }} draft: false diff --git a/.gitignore b/.gitignore index aadaac0..1f083d7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build/ .vscode/ cmake-build-debug/ CMakeLists.txt +*.zip diff --git a/Dockerfile b/Dockerfile index 7397c5d..2470778 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ -FROM ghcr.io/wiiu-env/devkitppc:20230621 +FROM ghcr.io/wiiu-env/devkitppc:20240423 COPY --from=ghcr.io/wiiu-env/libmocha:20230621 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/librpxloader:20240425 /artifacts $DEVKITPRO WORKDIR project diff --git a/Makefile b/Makefile index 7abe15a..17f9555 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,8 @@ TOPDIR ?= $(CURDIR) include $(DEVKITPRO)/wut/share/wut_rules +WUMS_ROOT := $(DEVKITPRO)/wums + #------------------------------------------------------------------------------- # TARGET is the name of the output # BUILD is the directory where object files & intermediate files will be placed @@ -36,7 +38,7 @@ CXXFLAGS := $(CFLAGS) -std=c++20 -fno-rtti ASFLAGS := -g $(ARCH) LDFLAGS = -g $(ARCH) $(RPXSPECS) --entry=_start -Wl,-Map,$(notdir $*.map) -LIBS := -lpng -lmocha -lwut -lz +LIBS := -lrpxloader -lpng -lmocha -lwut -lz ifeq ($(DEBUG),1) CXXFLAGS += -DDEBUG -g @@ -52,7 +54,7 @@ endif # list of directories containing libraries, this must be the top level # containing include and lib #------------------------------------------------------------------------------- -LIBDIRS := $(PORTLIBS) $(WUT_ROOT) $(WUT_ROOT)/usr +LIBDIRS := $(PORTLIBS) $(WUMS_ROOT) $(WUT_ROOT) $(WUT_ROOT)/usr #------------------------------------------------------------------------------- # no real need to edit anything past this point unless you need to add additional diff --git a/source/MenuUtils.cpp b/source/MenuUtils.cpp index addbf31..69878dd 100644 --- a/source/MenuUtils.cpp +++ b/source/MenuUtils.cpp @@ -18,7 +18,7 @@ #include #include -#define AUTOBOOT_MODULE_VERSION "v0.2.0" +#define AUTOBOOT_MODULE_VERSION "v0.2.1" const char *autoboot_config_strings[] = { "wiiu_menu", diff --git a/source/QuickStartUtils.cpp b/source/QuickStartUtils.cpp index 77f5b4b..4ec20dd 100644 --- a/source/QuickStartUtils.cpp +++ b/source/QuickStartUtils.cpp @@ -13,12 +13,17 @@ #include #include #include +#include #include +#include #include #include extern "C" void __fini_wut(); +#define UPPER_TITLE_ID_HOMEBREW 0x0005000F +#define TITLE_ID_HOMEBREW_MASK (((uint64_t) UPPER_TITLE_ID_HOMEBREW) << 32) + static void StartAppletAndExit() { DEBUG_FUNCTION_LINE("Wait for applet"); ProcUIInit(OSSavesDone_ReadyToRelease); @@ -79,28 +84,65 @@ void loadConsoleAccount(const char *data_uuid) { nn::act::Finalize(); } +class FileStreamWrapper { +public: + static std::unique_ptr CreateFromPath(std::string_view path, std::string_view mode = "r") { + return std::unique_ptr(new FileStreamWrapper(path, mode)); + } + + ~FileStreamWrapper() { + mFileStream.reset(); + FSDelClient(&mFsClient, FS_ERROR_FLAG_NONE); + } + + nn::sl::details::IStreamBase &GetStream() { + return *mFileStream; + } + +private: + explicit FileStreamWrapper(std::string_view path, std::string_view mode) { + FSAddClient(&mFsClient, FS_ERROR_FLAG_NONE); + FSInitCmdBlock(&mCmdBlock); + mFileStream = std::make_unique(); + mFileStream->Initialize(&mFsClient, &mCmdBlock, path.data(), mode.data()); + } + + std::unique_ptr mFileStream{}; + FSClient mFsClient{}; + FSCmdBlock mCmdBlock{}; +}; + bool getQuickBoot() { + // Waits until the quick start menu has been closed. TODO: abort this checks after a given time? auto bootCheck = CCRSysCaffeineBootCheck(); if (bootCheck == 0) { nn::sl::Initialize(MEMAllocFromDefaultHeapEx, MEMFreeToDefaultHeap); char path[0x80]; nn::sl::GetDefaultDatabasePath(path, 0x80, 0x0005001010066000); // ECO process - FSCmdBlock cmdBlock; - FSInitCmdBlock(&cmdBlock); - - auto fileStream = new nn::sl::FileStream; - auto *fsClient = (FSClient *) memalign(0x40, sizeof(FSClient)); - if (!fsClient) { - DEBUG_FUNCTION_LINE("Couldn't alloc memory for fsClient."); - return false; + nn::sl::LaunchInfoDatabase launchInfoDatabase; + nn::sl::LaunchInfo info; + { + // In theory the region doesn't even matter. + // The region is to load a "system table" into the LaunchInfoDatabase which provides the LaunchInfos for + // the Wii U Menu and System Settings. In the code below we check for all possible System Settings title id and + // have a fallback to the Wii U Menu... This means we could get away a wrong region, but let's use the correct one + // anyway + const auto region = []() { + if (SYSCheckTitleExists(0x0005001010047000L)) { // JPN System Settings + return nn::sl::REGION_JPN; + } else if (SYSCheckTitleExists(0x0005001010047100L)) { // USA System Settings + return nn::sl::REGION_USA; + } else if (SYSCheckTitleExists(0x0005001010047200L)) { // EUR System Settings + return nn::sl::REGION_EUR; + } + return nn::sl::REGION_EUR; + }(); + auto fileStream = FileStreamWrapper::CreateFromPath(path); + if (launchInfoDatabase.Load(fileStream->GetStream(), region).IsFailure()) { + DEBUG_FUNCTION_LINE_ERR("Failed to load LaunchInfoDatabase"); + return false; + } } - 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 @@ -108,15 +150,7 @@ bool getQuickBoot() { loadConsoleAccount(data.uuid); - // 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); - free(fsClient); + auto result = launchInfoDatabase.GetLaunchInfoById(&info, data.titleId); nn::sl::Finalize(); @@ -125,6 +159,17 @@ bool getQuickBoot() { return false; } + if ((info.titleId & TITLE_ID_HOMEBREW_MASK) == TITLE_ID_HOMEBREW_MASK) { + std::string homebrewPath = info.parameter; + DEBUG_FUNCTION_LINE("Trying to launch homebrew title: \"%s\"", homebrewPath.c_str()); + + if (auto err = RPXLoader_LaunchHomebrew(homebrewPath.c_str()); err != RPX_LOADER_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to launch homebrew title: %s (%d)", RPXLoader_GetStatusStr(err), err); + return false; + } + return true; + } + if (info.titleId == 0x0005001010040000L || info.titleId == 0x0005001010040100L || info.titleId == 0x0005001010040200L) { @@ -132,6 +177,14 @@ bool getQuickBoot() { return false; } + if (info.titleId == 0x0005001010047000L || + info.titleId == 0x0005001010047100L || + info.titleId == 0x0005001010047200L) { + DEBUG_FUNCTION_LINE("Launch Settings"); + _SYSLaunchSettings(nullptr); + return true; + } + if (info.titleId == 0x000500301001220AL || info.titleId == 0x000500301001210AL || info.titleId == 0x000500301001200AL) { diff --git a/source/main.cpp b/source/main.cpp index c9d03de..91ae2fc 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,12 @@ int32_t main(int32_t argc, char **argv) { OSFatal("AutobootModule: Mocha_InitLibrary failed"); } + // Use librpxloader. + RPXLoaderStatus error3; + if ((error3 = RPXLoader_InitLibrary()) != RPX_LOADER_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE("AutobootModule: Failed to init RPXLoader. This can be ignored when not running Aroma. Error %s [%d]", RPXLoader_GetStatusStr(error3), error3); + } + InputUtils::InputData buttons = InputUtils::getControllerInput(); FSAInit();