diff --git a/Dockerfile b/Dockerfile
index 312a02e..47768ed 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
-FROM wiiuenv/devkitppc:20211229
+FROM ghcr.io/wiiu-env/devkitppc:20240704
COPY --from=wiiuenv/libgui:20220109 /artifacts $DEVKITPRO
-WORKDIR project
\ No newline at end of file
+WORKDIR project
diff --git a/README.md b/README.md
index a172b22..7c271e3 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ A simple Wii U Menu replacement, still in early development and not ready for a
## Known Issues
- Random crashes
- The Keyboard input is implemented, but result is ignored.
-- nn::spm is not initalized and no quick start menu support. For the it's relying on the [AutobootModule](https://github.com/wiiu-env/AutobootModule) doing this.
+- `nn::spm` is not initalized and no quick start menu support. For the it's relying on the [AutobootModule](https://github.com/wiiu-env/AutobootModule) doing this.
- No sound on splash screen.
- Probably a lot more
@@ -23,7 +23,7 @@ A simple Wii U Menu replacement, still in early development and not ready for a
- Display applets like the original Wii U Menu
- Implement Account selection when no default account is set.
- Implement update check/no way to update games
-- Properly implement nn::spm and nn:sl (external storage and quick start menu)
+- Properly implement `nn::spm` and `nn::sl` (external storage and quick start menu)
- Fix search
- Implement all the other stuff the Wii U Menu offers (Account creationg, switching between Accounts, set default account etc.)
- Implement ways to launch the original Wii U Menu.
diff --git a/src/Application.cpp b/src/Application.cpp
index 907df52..bc7b1fc 100644
--- a/src/Application.cpp
+++ b/src/Application.cpp
@@ -1,42 +1,165 @@
-/****************************************************************************
- * Copyright (C) 2015 Dimok
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- ****************************************************************************/
#include "Application.h"
#include "common/common.h"
#include "resources/Resources.h"
#include "utils/AsyncExecutor.h"
#include "utils/logger.h"
+#include "utils/SplashSoundPlayer.h"
#include
#include
#include
-#include
-#include
-#include
-#include
-#include
+#include
+#include "gui/FreeTypeGX.h"
+#include "gui/VPadController.h"
+#include "gui/WPadController.h"
+#include "gui/memory.h"
+#include "gui/sounds/SoundHandler.hpp"
+#include
+#include
#include
+#include
#include
+#include
+#include
#include
+#include
+#include
+#include
-Application *Application::applicationInstance = nullptr;
-bool Application::exitApplication = false;
-bool Application::quitRequest = false;
+static void InitEmptyExternalStorage() {
+ DEBUG_FUNCTION_LINE("Fallback to empty ExtendedStorage");
+ nn::spm::VolumeId empty{};
+ nn::spm::SetDefaultExtendedStorageVolumeId(empty);
-Application::Application()
- : CThread(CThread::eAttributeAffCore1 | CThread::eAttributePinnedAff, 0, 0x800000), bgMusic(nullptr), video(nullptr), mainWindow(nullptr), fontSystem(nullptr), exitCode(0) {
+ nn::spm::StorageIndex storageIndex = 0;
+ nn::spm::SetExtendedStorage(&storageIndex);
+}
+
+static int numberUSBStorageDevicesConnected() {
+ DEBUG_FUNCTION_LINE("Check if USB Storage is connected");
+ auto *handle = (UhsHandle *) memalign(0x40, sizeof(UhsHandle));
+ if (!handle) {
+ return -1;
+ }
+ memset(handle, 0, sizeof(UhsHandle));
+ auto *config = (UhsConfig *) memalign(0x40, sizeof(UhsConfig));
+ if (!config) {
+ free(handle);
+ return -2;
+ }
+ memset(config, 0, sizeof(UhsConfig));
+
+ config->controller_num = 0;
+ uint32_t size = 5120;
+ void *buffer = memalign(0x40, size);
+ if (!buffer) {
+ free(handle);
+ free(config);
+ return -3;
+ }
+ memset(buffer, 0, size);
+
+ config->buffer = buffer;
+ config->buffer_size = size;
+
+ if (UhsClientOpen(handle, config) != UHS_STATUS_OK) {
+ DEBUG_FUNCTION_LINE("UhsClient failed");
+ free(handle);
+ free(config);
+ free(buffer);
+ return -4;
+ }
+
+ UhsInterfaceProfile profiles[10];
+ UhsInterfaceFilter filter = {
+ .match_params = MATCH_ANY};
+
+ UHSStatus result;
+ if ((result = UhsQueryInterfaces(handle, &filter, profiles, 10)) <= UHS_STATUS_OK) {
+ DEBUG_FUNCTION_LINE("UhsQueryInterfaces failed");
+ UhsClientClose(handle);
+ free(handle);
+ free(config);
+ free(buffer);
+ return -5;
+ }
+
+ auto found = 0;
+ for (int i = 0; i < (int) result; i++) {
+ if (profiles[i].if_desc.bInterfaceClass == USBCLASS_STORAGE) {
+ DEBUG_FUNCTION_LINE("Found USBCLASS_STORAGE");
+ found++;
+ }
+ }
+
+ UhsClientClose(handle);
+ free(handle);
+ free(config);
+ free(buffer);
+ return found;
+}
+
+void initExternalStorage() {
+ if (OSGetTitleID() == _SYSGetSystemApplicationTitleId(SYSTEM_APP_ID_MII_MAKER)) {
+ // nn::spm functions always call OSFatal when they fail, so we make sure have permission to use
+ // the lib before actually using it.
+ return;
+ }
+ int numConnectedStorage;
+ int maxTries = 1200; // Wait up to 20 seconds, like the Wii U Menu
+ if ((numConnectedStorage = numberUSBStorageDevicesConnected()) <= 0) {
+ maxTries = 1; // Only try once if no USBStorageDrive is connected
+ } else {
+ DEBUG_FUNCTION_LINE("Connected StorageDevices = %d", numConnectedStorage);
+ }
+
+ nn::spm::Initialize();
+
+ nn::spm::StorageListItem items[0x20];
+ int tries = 0;
+ bool found = false;
+
+ while (tries < maxTries) {
+ int32_t numItems = nn::spm::GetStorageList(items, 0x20);
+
+ DEBUG_FUNCTION_LINE("Number of items: %d", numItems);
+
+ for (int32_t 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 || (numConnectedStorage == numItems)) {
+ DEBUG_FUNCTION_LINE("Found all expected items, breaking.");
+ break;
+ }
+ OSSleepTicks(OSMillisecondsToTicks(16));
+ tries++;
+ }
+ if (!found) {
+ if (numConnectedStorage > 0) {
+ DEBUG_FUNCTION_LINE("USB Storage is connected but either it doesn't have a WFS partition or we ran into a timeout.");
+ }
+ InitEmptyExternalStorage();
+ }
+
+ nn::spm::Finalize();
+}
+Application * Application::applicationInstance = nullptr;
+bool Application::exitApplication = false;
+bool Application::quitRequest = false;
+
+Application::Application(): CThread(CThread::eAttributeAffCore1 | CThread::eAttributePinnedAff, 0, 0x800000), bgMusic(nullptr), video(nullptr), mainWindow(nullptr), fontSystem(nullptr), exitCode(0) {
controller[0] = new VPadController(GuiTrigger::CHANNEL_1);
controller[1] = new WPadController(GuiTrigger::CHANNEL_2);
controller[2] = new WPadController(GuiTrigger::CHANNEL_3);
@@ -45,11 +168,13 @@ Application::Application()
//! create bgMusic
bgMusic = new GuiSound(Resources::GetFile("bgMusic.ogg"), Resources::GetFileSize("bgMusic.ogg"));
- bgMusic->SetLoop(true);
- bgMusic->Play();
- bgMusic->SetVolume(50);
+ bgMusic -> SetLoop(true);
+ bgMusic -> Play();
+ bgMusic -> SetVolume(50);
- AsyncExecutor::execute([] { DEBUG_FUNCTION_LINE("Hello"); });
+ AsyncExecutor::execute([] {
+ DEBUG_FUNCTION_LINE("Hello");
+ });
exitApplication = false;
@@ -62,7 +187,7 @@ Application::~Application() {
DEBUG_FUNCTION_LINE("Destroy controller");
- for (auto &i : controller) {
+ for (auto & i: controller) {
delete i;
}
@@ -88,13 +213,20 @@ int32_t Application::exec() {
}
void Application::quit(int32_t code) {
- exitCode = code;
+ exitCode = code;
exitApplication = true;
- quitRequest = true;
+ quitRequest = true;
}
void Application::fadeOut() {
- GuiImage fadeOut(video->getTvWidth(), video->getTvHeight(), (GX2Color){0, 0, 0, 255});
+ GuiImage fadeOut(video->getTvWidth(), video->getTvHeight(), (GX2Color) {
+ 0,
+ 0,
+ 0,
+ 255
+ });
+
+glm::mat4 identityMatrix(1.0f);
for (int32_t i = 0; i < 255; i += 10) {
if (i > 255)
@@ -103,30 +235,30 @@ void Application::fadeOut() {
fadeOut.setAlpha(i / 255.0f);
//! start rendering DRC
- video->prepareDrcRendering();
- mainWindow->drawDrc(video);
+ video -> prepareDrcRendering();
+ mainWindow -> drawDrc(video);
GX2SetDepthOnlyControl(GX2_DISABLE, GX2_DISABLE, GX2_COMPARE_FUNC_ALWAYS);
- fadeOut.draw(video);
+ fadeOut.draw(video, identityMatrix);
GX2SetDepthOnlyControl(GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_FUNC_LEQUAL);
- video->drcDrawDone();
+ video -> drcDrawDone();
//! start rendering TV
- video->prepareTvRendering();
+ video -> prepareTvRendering();
- mainWindow->drawTv(video);
+ mainWindow -> drawTv(video);
GX2SetDepthOnlyControl(GX2_DISABLE, GX2_DISABLE, GX2_COMPARE_FUNC_ALWAYS);
- fadeOut.draw(video);
+ fadeOut.draw(video, identityMatrix);
GX2SetDepthOnlyControl(GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_FUNC_LEQUAL);
- video->tvDrawDone();
+ video -> tvDrawDone();
//! as last point update the effects as it can drop elements
- mainWindow->updateEffects();
+ mainWindow -> updateEffects();
- video->waitForVSync();
+ video -> waitForVSync();
}
}
@@ -134,63 +266,63 @@ bool Application::procUI() {
bool executeProcess = false;
switch (ProcUIProcessMessages(true)) {
- case PROCUI_STATUS_EXITING: {
- DEBUG_FUNCTION_LINE("PROCUI_STATUS_EXITING");
- exitCode = EXIT_SUCCESS;
- exitApplication = true;
- break;
- }
- case PROCUI_STATUS_RELEASE_FOREGROUND: {
- DEBUG_FUNCTION_LINE("PROCUI_STATUS_RELEASE_FOREGROUND");
- if (video != nullptr) {
- // we can turn ofF the screen but we don't need to and it will display the last image
- video->tvEnable(true);
- video->drcEnable(true);
-
- DEBUG_FUNCTION_LINE("delete fontSystem");
- delete fontSystem;
- fontSystem = nullptr;
-
- DEBUG_FUNCTION_LINE("delete video");
- delete video;
- video = nullptr;
-
- DEBUG_FUNCTION_LINE("deinitialze memory");
- libgui_memoryRelease();
- ProcUIDrawDoneRelease();
- } else {
- ProcUIDrawDoneRelease();
- }
- break;
+ case PROCUI_STATUS_EXITING: {
+ DEBUG_FUNCTION_LINE("PROCUI_STATUS_EXITING");
+ exitCode = EXIT_SUCCESS;
+ exitApplication = true;
+ break;
+ }
+ case PROCUI_STATUS_RELEASE_FOREGROUND: {
+ DEBUG_FUNCTION_LINE("PROCUI_STATUS_RELEASE_FOREGROUND");
+ if (video != nullptr) {
+ // we can turn ofF the screen but we don't need to and it will display the last image
+ video -> tvEnable(true);
+ video -> drcEnable(true);
+
+ DEBUG_FUNCTION_LINE("delete fontSystem");
+ delete fontSystem;
+ fontSystem = nullptr;
+
+ DEBUG_FUNCTION_LINE("delete video");
+ delete video;
+ video = nullptr;
+
+ DEBUG_FUNCTION_LINE("deinitialze memory");
+ libgui_memoryRelease();
+ ProcUIDrawDoneRelease();
+ } else {
+ ProcUIDrawDoneRelease();
}
- case PROCUI_STATUS_IN_FOREGROUND: {
- if (!quitRequest) {
- if (video == nullptr) {
- DEBUG_FUNCTION_LINE("PROCUI_STATUS_IN_FOREGROUND");
- DEBUG_FUNCTION_LINE("initialze memory");
- libgui_memoryInitialize();
-
- DEBUG_FUNCTION_LINE("Initialize video");
- video = new CVideo(GX2_TV_SCAN_MODE_720P, GX2_DRC_RENDER_MODE_SINGLE);
- DEBUG_FUNCTION_LINE("Video size %i x %i", video->getTvWidth(), video->getTvHeight());
-
- //! setup default Font
- DEBUG_FUNCTION_LINE("Initialize main font system");
- auto *fontSystem = new FreeTypeGX(Resources::GetFile("font.ttf"), Resources::GetFileSize("font.ttf"), true);
- GuiText::setPresetFont(fontSystem);
-
- if (mainWindow == nullptr) {
- DEBUG_FUNCTION_LINE("Initialize main window");
- mainWindow = new MainWindow(video->getTvWidth(), video->getTvHeight());
- }
+ break;
+ }
+ case PROCUI_STATUS_IN_FOREGROUND: {
+ if (!quitRequest) {
+ if (video == nullptr) {
+ DEBUG_FUNCTION_LINE("PROCUI_STATUS_IN_FOREGROUND");
+ DEBUG_FUNCTION_LINE("initialze memory");
+ libgui_memoryInitialize();
+
+ DEBUG_FUNCTION_LINE("Initialize video");
+ video = new CVideo(GX2_TV_SCAN_MODE_720P, GX2_DRC_RENDER_MODE_SINGLE);
+ DEBUG_FUNCTION_LINE("Video size %i x %i", video -> getTvWidth(), video -> getTvHeight());
+
+ //! setup default Font
+ DEBUG_FUNCTION_LINE("Initialize main font system");
+ auto * fontSystem = new FreeTypeGX(Resources::GetFile("font.ttf"), Resources::GetFileSize("font.ttf"), true);
+ GuiText::setPresetFont(fontSystem);
+
+ if (mainWindow == nullptr) {
+ DEBUG_FUNCTION_LINE("Initialize main window");
+ mainWindow = new MainWindow(video -> getTvWidth(), video -> getTvHeight());
}
- executeProcess = true;
}
- break;
+ executeProcess = true;
}
- case PROCUI_STATUS_IN_BACKGROUND:
- default:
- break;
+ break;
+ }
+ case PROCUI_STATUS_IN_BACKGROUND:
+ default:
+ break;
}
return executeProcess;
@@ -205,43 +337,43 @@ void Application::executeThread() {
continue;
}
- mainWindow->lockGUI();
- mainWindow->process();
+ mainWindow -> lockGUI();
+ mainWindow -> process();
//! Read out inputs
- for (auto &i : controller) {
- if (!i->update(video->getTvWidth(), video->getTvHeight()))
+ for (auto & i: controller) {
+ if (!i -> update(video -> getTvWidth(), video -> getTvHeight()))
continue;
//! update controller states
- mainWindow->update(i);
+ mainWindow -> update(i);
}
//! start rendering DRC
- video->prepareDrcRendering();
- mainWindow->drawDrc(video);
- video->drcDrawDone();
+ video -> prepareDrcRendering();
+ mainWindow -> drawDrc(video);
+ video -> drcDrawDone();
//! start rendering TV
- video->prepareTvRendering();
- mainWindow->drawTv(video);
- video->tvDrawDone();
+ video -> prepareTvRendering();
+ mainWindow -> drawTv(video);
+ video -> tvDrawDone();
//! enable screen after first frame render
- if (video->getFrameCount() == 0) {
- video->tvEnable(true);
- video->drcEnable(true);
+ if (video -> getFrameCount() == 0) {
+ video -> tvEnable(true);
+ video -> drcEnable(true);
}
//! as last point update the effects as it can drop elements
- mainWindow->updateEffects();
- mainWindow->unlockGUI();
+ mainWindow -> updateEffects();
+ mainWindow -> unlockGUI();
- video->waitForVSync();
+ video -> waitForVSync();
}
if (bgMusic) {
- bgMusic->SetVolume(0);
+ bgMusic -> SetVolume(0);
}
DEBUG_FUNCTION_LINE("delete mainWindow");
@@ -258,4 +390,5 @@ void Application::executeThread() {
DEBUG_FUNCTION_LINE("deinitialize memory");
libgui_memoryRelease();
+
}