diff --git a/Dockerfile b/Dockerfile index 349e967..e77c402 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM wiiuenv/devkitppc:20220213 +FROM wiiuenv/devkitppc:20220303 COPY --from=wiiuenv/wiiupluginsystem:20220123 /artifacts $DEVKITPRO COPY --from=wiiuenv/librpxloader:20220212 /artifacts $DEVKITPRO diff --git a/README.md b/README.md index 1ef7748..7b33f27 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,10 @@ 2. Requires the [WiiUPluginLoaderBackend](https://github.com/wiiu-env/WiiUPluginLoaderBackend) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. 3. Requires the [RPXLoadingModule](https://github.com/wiiu-env/RPXLoadingModule) in `sd:/wiiu/environments/[ENVIRONMENT]/modules`. +## Save data redirection +In order to preserve the order of homebrew apps even when you run the Wii U Menu without this plugin, this plugin will redirect the Wii U Menu save data to `sd:/wiiu/homebrew_on_menu_plugin`. +When no save data is found on the sd card, the current save data is copied from the console, but after that it's never updated. + ## Building using the Dockerfile It's possible to use a docker image for building. This way you don't need anything installed on your host system. @@ -24,4 +28,4 @@ docker run -it --rm -v ${PWD}:/project homebrew_on_menu_plugin-builder make clea ## Format the code via docker -`docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./include ./libraries ./plugins/example_plugin/src -i` \ No newline at end of file +`docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./src -i` \ No newline at end of file diff --git a/src/SaveRedirection.cpp b/src/SaveRedirection.cpp new file mode 100644 index 0000000..01c6d41 --- /dev/null +++ b/src/SaveRedirection.cpp @@ -0,0 +1,156 @@ +#include "SaveRedirection.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool gInWiiUMenu __attribute__((section(".data"))); + +static FSStatus CallWithNewPath(const char *oldPath, const std::function &callFunctionWithPath) { + if (!gInWiiUMenu || strncmp(oldPath, "/vol/save/", 10) != 0) { + return callFunctionWithPath(oldPath); + } + + char *newPath = (char *) malloc(0x280); + if (!newPath) { + return FS_STATUS_FATAL_ERROR; + } + + snprintf(newPath, 0x27F, SAVE_REPLACEMENT_PATH "/save/%s", &oldPath[10]); + + auto res = callFunctionWithPath(newPath); + free(newPath); + return res; +} + +void initSaveData() { + nn::act::Initialize(); + nn::act::PersistentId persistentId = nn::act::GetPersistentId(); + nn::act::Finalize(); + + std::string common = "fs:" SAVE_REPLACEMENT_PATH "/save/common"; + std::string commonOriginal = "fs:/vol/save/common"; + std::string user = StringTools::strfmt("fs:" SAVE_REPLACEMENT_PATH "/save/%08X", 0x80000000 | persistentId); + std::string userOriginal = StringTools::strfmt("fs:/vol/save/%08X", 0x80000000 | persistentId); + + FSUtils::CreateSubfolder(common.c_str()); + FSUtils::CreateSubfolder(user.c_str()); + + auto BaristaAccountSaveFilePathNew = user + "/BaristaAccountSaveFile.dat"; + auto BaristaAccountSaveFilePathOriginal = userOriginal + "/BaristaAccountSaveFile.dat"; + if (!FSUtils::CheckFile(BaristaAccountSaveFilePathNew.c_str())) { + DEBUG_FUNCTION_LINE("Copy %s to %s", BaristaAccountSaveFilePathOriginal.c_str(), BaristaAccountSaveFilePathNew.c_str()); + FSUtils::copyFile(BaristaAccountSaveFilePathOriginal, BaristaAccountSaveFilePathNew); + } + + auto BaristaCommonSaveFile = common + "/BaristaCommonSaveFile.dat"; + auto BaristaCommonSaveFileOriginal = commonOriginal + "/BaristaCommonSaveFile.dat"; + if (!FSUtils::CheckFile(BaristaCommonSaveFile.c_str())) { + DEBUG_FUNCTION_LINE("Copy %s to %s", BaristaCommonSaveFileOriginal.c_str(), BaristaCommonSaveFile.c_str()); + FSUtils::copyFile(BaristaCommonSaveFileOriginal, BaristaCommonSaveFile); + } + + auto BaristaIconDataBase = common + "/BaristaIconDataBase.dat"; + auto BaristaIconDataBaseOriginal = commonOriginal + "/BaristaIconDataBase.dat"; + if (!FSUtils::CheckFile(BaristaIconDataBase.c_str())) { + DEBUG_FUNCTION_LINE("Copy %s to %s", BaristaIconDataBaseOriginal.c_str(), BaristaIconDataBase.c_str()); + FSUtils::copyFile(BaristaIconDataBaseOriginal, BaristaIconDataBase); + } +} + +DECL_FUNCTION(FSStatus, FSOpenFileAsync, FSClient *client, FSCmdBlock *block, const char *path, const char *mode, FSFileHandle *outHandle, FSErrorFlag errorMask, FSAsyncData *asyncData) { + return CallWithNewPath(path, [client, block, mode, outHandle, errorMask, asyncData](const char *_path) -> FSStatus { + DEBUG_FUNCTION_LINE("Call FSOpenFileAsync %s", _path); + return real_FSOpenFileAsync(client, block, _path, mode, outHandle, errorMask, asyncData); + }); +} + +DECL_FUNCTION(FSStatus, FSGetStatAsync, FSClient *client, FSCmdBlock *block, const char *path, FSStat *stat, FSErrorFlag errorMask, FSAsyncData *asyncData) { + return CallWithNewPath(path, [client, block, stat, errorMask, asyncData](const char *_path) -> FSStatus { + DEBUG_FUNCTION_LINE("Call FSGetStatAsync %s", _path); + return real_FSGetStatAsync(client, block, _path, stat, errorMask, asyncData); + }); +} + +DECL_FUNCTION(FSStatus, FSRemoveAsync, FSClient *client, FSCmdBlock *block, const char *path, FSErrorFlag errorMask, FSAsyncData *asyncData) { + return CallWithNewPath(path, [client, block, errorMask, asyncData](const char *_path) -> FSStatus { + DEBUG_FUNCTION_LINE("Call FSRemoveAsync %s", _path); + return real_FSRemoveAsync(client, block, _path, errorMask, asyncData); + }); +} + +DECL_FUNCTION(FSStatus, FSOpenDirAsync, FSClient *client, FSCmdBlock *block, const char *path, FSDirectoryHandle *handle, FSErrorFlag errorMask, FSAsyncData *asyncData) { + return CallWithNewPath(path, [client, block, handle, errorMask, asyncData](const char *_path) -> FSStatus { + DEBUG_FUNCTION_LINE("Call FSOpenDirAsync %s", _path); + return real_FSOpenDirAsync(client, block, _path, handle, errorMask, asyncData); + }); +} + +DECL_FUNCTION(FSStatus, FSMakeDirAsync, FSClient *client, FSCmdBlock *block, const char *path, FSErrorFlag errorMask, FSAsyncData *asyncData) { + return CallWithNewPath(path, [client, block, errorMask, asyncData](const char *_path) -> FSStatus { + DEBUG_FUNCTION_LINE("Call FSMakeDirAsync %s", _path); + return real_FSMakeDirAsync(client, block, _path, errorMask, asyncData); + }); +} + +DECL_FUNCTION(FSStatus, FSChangeModeAsync, FSClient *client, FSCmdBlock *block, const char *path, FSMode mode, FSMode modeMask, FSErrorFlag errorMask, FSAsyncData *asyncData) { + return CallWithNewPath(path, [client, block, mode, modeMask, errorMask, asyncData](const char *_path) -> FSStatus { + DEBUG_FUNCTION_LINE("Call FSChangeModeAsync %s", _path); + return real_FSChangeModeAsync(client, block, _path, mode, modeMask, errorMask, asyncData); + }); +} + +DECL_FUNCTION(FSStatus, FSChangeDirAsync, FSClient *client, FSCmdBlock *block, const char *path, FSErrorFlag errorMask, FSAsyncData *asyncData) { + return CallWithNewPath(path, [client, block, errorMask, asyncData](const char *_path) -> FSStatus { + DEBUG_FUNCTION_LINE("Call FSChangeDirAsync %s", _path); + return real_FSChangeDirAsync(client, block, _path, errorMask, asyncData); + }); +} + +DECL_FUNCTION(int32_t, LoadConsoleAccount__Q2_2nn3actFUc13ACTLoadOptionPCcb, nn::act::SlotNo slot, nn::act::ACTLoadOption unk1, char const *unk2, bool unk3) { + int32_t result = real_LoadConsoleAccount__Q2_2nn3actFUc13ACTLoadOptionPCcb(slot, unk1, unk2, unk3); + if (result >= 0 && gInWiiUMenu) { + DEBUG_FUNCTION_LINE("changed account, init save data"); + // If the account has changed, we need to init save data for this account + // Calls our function replacement. + SAVEInit(); + } + + return result; +} + +DECL_FUNCTION(int32_t, SAVEInit) { + auto res = real_SAVEInit(); + if (res >= 0) { + if (OSGetTitleID() == 0x0005001010040000L || // Wii U Menu JPN + OSGetTitleID() == 0x0005001010040100L || // Wii U Menu USA + OSGetTitleID() == 0x0005001010040200L) { // Wii U Menu EUR + DEBUG_FUNCTION_LINE("Init Save redirection"); + + initSaveData(); + gInWiiUMenu = true; + } else { + gInWiiUMenu = false; + } + } + + return res; +} + + +WUPS_MUST_REPLACE(SAVEInit, WUPS_LOADER_LIBRARY_NN_SAVE, SAVEInit); +WUPS_MUST_REPLACE(FSOpenFileAsync, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFileAsync); +WUPS_MUST_REPLACE(FSOpenDirAsync, WUPS_LOADER_LIBRARY_COREINIT, FSOpenDirAsync); +WUPS_MUST_REPLACE(FSRemoveAsync, WUPS_LOADER_LIBRARY_COREINIT, FSRemoveAsync); +WUPS_MUST_REPLACE(FSMakeDirAsync, WUPS_LOADER_LIBRARY_COREINIT, FSMakeDirAsync); +// WUPS_MUST_REPLACE(FSRenameAsync, WUPS_LOADER_LIBRARY_COREINIT, FSRenameAsync); +WUPS_MUST_REPLACE(FSChangeDirAsync, WUPS_LOADER_LIBRARY_COREINIT, FSChangeDirAsync); +WUPS_MUST_REPLACE(FSChangeModeAsync, WUPS_LOADER_LIBRARY_COREINIT, FSChangeModeAsync); +WUPS_MUST_REPLACE(FSGetStatAsync, WUPS_LOADER_LIBRARY_COREINIT, FSGetStatAsync); +WUPS_MUST_REPLACE(LoadConsoleAccount__Q2_2nn3actFUc13ACTLoadOptionPCcb, WUPS_LOADER_LIBRARY_NN_ACT, LoadConsoleAccount__Q2_2nn3actFUc13ACTLoadOptionPCcb); \ No newline at end of file diff --git a/src/SaveRedirection.h b/src/SaveRedirection.h new file mode 100644 index 0000000..eaac9f1 --- /dev/null +++ b/src/SaveRedirection.h @@ -0,0 +1,8 @@ +#pragma once +#include + +extern bool gInWiiUMenu; + +#define SAVE_REPLACEMENT_PATH "/vol/external01/wiiu/homebrew_on_menu_plugin" + +void InitSaveData(); \ No newline at end of file diff --git a/src/fs/FSUtils.cpp b/src/fs/FSUtils.cpp index 740e369..c9e3ff4 100644 --- a/src/fs/FSUtils.cpp +++ b/src/fs/FSUtils.cpp @@ -139,3 +139,39 @@ int32_t FSUtils::saveBufferToFile(const char *path, void *buffer, uint32_t size) file.close(); return written; } + +bool FSUtils::copyFile(const std::string &in, const std::string &out) { + // Using C++ buffers is **really** slow. Copying in 1023 byte chunks. + // Let's do it the old way. + size_t size; + + int source = open(in.c_str(), O_RDONLY, 0); + if (source < 0) { + DEBUG_FUNCTION_LINE("Failed to open source %s", in.c_str()); + + return false; + } + int dest = open(out.c_str(), 0x602, 0644); + if (dest < 0) { + DEBUG_FUNCTION_LINE("Failed to open dest %s", out.c_str()); + close(source); + return false; + } + + auto bufferSize = 128 * 1024; + char *buf = (char *) malloc(bufferSize); + if (buf == nullptr) { + DEBUG_FUNCTION_LINE("Failed to alloc buffer"); + return false; + } + + while ((size = read(source, buf, bufferSize)) > 0) { + write(dest, buf, size); + } + + free(buf); + + close(source); + close(dest); + return true; +} diff --git a/src/fs/FSUtils.h b/src/fs/FSUtils.h index 7899406..333ba68 100644 --- a/src/fs/FSUtils.h +++ b/src/fs/FSUtils.h @@ -1,6 +1,6 @@ -#ifndef __FS_UTILS_H_ -#define __FS_UTILS_H_ +#pragma once +#include #include class FSUtils { @@ -13,6 +13,6 @@ public: static int32_t CheckFile(const char *filepath); static int32_t saveBufferToFile(const char *path, void *buffer, uint32_t size); -}; -#endif // __FS_UTILS_H_ + static bool copyFile(const std::string &in, const std::string &out); +};