diff --git a/Dockerfile b/Dockerfile index 39dfe5b..52a14d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,5 +5,6 @@ COPY --from=ghcr.io/wiiu-env/libnotifications:20240426 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/librpxloader:20240425 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libcurlwrapper:20240505 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libsdutils:20230621 /artifacts $DEVKITPRO +COPY --from=ghcr.io/wiiu-env/libmocha:20231127 /artifacts $DEVKITPRO WORKDIR project diff --git a/Makefile b/Makefile index 4ad9008..112a3ca 100644 --- a/Makefile +++ b/Makefile @@ -48,13 +48,13 @@ CXXFLAGS += -DDEBUG -DVERBOSE_DEBUG -g CFLAGS += -DDEBUG -DVERBOSE_DEBUG -g endif -LIBS := -lcurlwrapper -lnotifications -lrpxloader -lsdutils -lwups -lwut +LIBS := -lmocha -lcurlwrapper -lnotifications -lrpxloader -lsdutils -lwups -lwut #------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level # containing include and lib #------------------------------------------------------------------------------- -LIBDIRS := $(PORTLIBS) $(WUPS_ROOT) $(WUMS_ROOT) $(WUT_ROOT) +LIBDIRS := $(PORTLIBS) $(WUPS_ROOT) $(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/README.md b/README.md index e650c70..ec1c5e8 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ For building you need: - [librpxloader](https://github.com/wiiu-env/librpxloader) - [libcurlwrapper](https://github.com/wiiu-env/libcurlwrapper) - [libsdutils](https://github.com/wiiu-env/libsdutils) +- [libmocha](https://github.com/wiiu-env/libmocha) ## Building using the Dockerfile diff --git a/src/common.h b/src/common.h index c260239..21003b4 100644 --- a/src/common.h +++ b/src/common.h @@ -6,4 +6,8 @@ #define AROMA_UPDATER_OLD_PATH_FULL SD_CARD_PATH AROMA_UPDATER_OLD_PATH #define AROMA_UPDATER_NEW_PATH_FULL SD_CARD_PATH AROMA_UPDATER_NEW_PATH #define AROMA_UPDATER_NEW_DIRECTORY_FULL SD_CARD_PATH AROMA_UPDATER_NEW_DIRECTORY -#define AROMA_UPDATER_LAST_UPDATE_URL "https://aroma.foryour.cafe/api/latest_version" \ No newline at end of file +#define AROMA_UPDATER_LAST_UPDATE_URL "https://aroma.foryour.cafe/api/latest_version" + + +#define BACKUPS_DIRECTORY "wiiu/backups" +#define BACKUPS_DIRECTORY_FULL SD_CARD_PATH BACKUPS_DIRECTORY \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 6093fff..622087a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,13 @@ #include "main.h" #include "Hints.h" #include "UpdaterCheck.h" -#include "common.h" #include "utils/DownloadUtils.h" #include "utils/LatestVersion.h" #include "utils/config.h" +#include "utils/utils.h" #include #include +#include #include #include #include @@ -72,44 +73,6 @@ bool InitConfigValuesFromStorage() { return result; } -/* - * Migrates wiiu/apps/AromaUpdater.wuhb to wiiu/apps/AromaUpdater/AromaUpdater.wuhb - */ -void MigrateAromaUpdater() { - struct stat st {}; - bool oldExists = false; - bool newExists = false; - if (stat(AROMA_UPDATER_NEW_PATH_FULL, &st) >= 0 && S_ISREG(st.st_mode)) { - DEBUG_FUNCTION_LINE_VERBOSE("\"%s\" exists", AROMA_UPDATER_NEW_PATH_FULL); - newExists = true; - } - st = {}; - if (stat(AROMA_UPDATER_OLD_PATH_FULL, &st) >= 0 && S_ISREG(st.st_mode)) { - DEBUG_FUNCTION_LINE_VERBOSE("\"%s\" exists", AROMA_UPDATER_OLD_PATH_FULL); - oldExists = true; - } - if (newExists) { - if (oldExists) { - if (remove(AROMA_UPDATER_OLD_PATH_FULL) < 0) { - DEBUG_FUNCTION_LINE_WARN("Failed to remove old Aroma Updater: %d", errno); - } - } else { - DEBUG_FUNCTION_LINE_VERBOSE("Only new AromaUpdater.wuhb exists"); - } - return; - } else if (oldExists) { - if (stat(AROMA_UPDATER_NEW_DIRECTORY_FULL, &st) < 0 || !S_ISDIR(st.st_mode)) { - if (mkdir(AROMA_UPDATER_NEW_DIRECTORY_FULL, 0777) < 0) { - DEBUG_FUNCTION_LINE_WARN("Failed to create: \"%s\"", AROMA_UPDATER_NEW_DIRECTORY_FULL); - } - } - if (rename(AROMA_UPDATER_OLD_PATH_FULL, AROMA_UPDATER_NEW_PATH_FULL) < 0) { - DEBUG_FUNCTION_LINE_WARN("Failed to move Aroma Updater to new path"); - } - } else { - DEBUG_FUNCTION_LINE_VERBOSE("No AromaUpdater.wuhb exists"); - } -} INITIALIZE_PLUGIN() { initLogging(); @@ -123,11 +86,17 @@ INITIALIZE_PLUGIN() { DEBUG_FUNCTION_LINE_ERR("SDUtils_InitLibrary failed"); } + if (Mocha_InitLibrary() != MOCHA_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Mocha_InitLibrary failed"); + } + InitConfigValuesFromStorage(); InitConfigMenu(); - MigrateAromaUpdater(); + Utils::MigrateAromaUpdater(); + + Utils::DumpOTPAndSeeprom(); } ON_APPLICATION_START() { diff --git a/src/main.h b/src/main.h index d7006b6..8adbaa4 100644 --- a/src/main.h +++ b/src/main.h @@ -1,5 +1,5 @@ #pragma once #include "version.h" -#define PLUGIN_VERSION "v0.1.4" +#define PLUGIN_VERSION "v0.1.5" #define PLUGIN_VERSION_FULL PLUGIN_VERSION PLUGIN_VERSION_EXTRA diff --git a/src/utils/CFile.cpp b/src/utils/CFile.cpp new file mode 100644 index 0000000..e366b0b --- /dev/null +++ b/src/utils/CFile.cpp @@ -0,0 +1,173 @@ + +#include "CFile.hpp" +#include +#include +#include +#include + +CFile::CFile() { + iFd = -1; + mem_file = nullptr; + filesize = 0; + pos = 0; +} + +CFile::CFile(const std::string &filepath, eOpenTypes mode) { + iFd = -1; + this->open(filepath, mode); +} + +CFile::CFile(const uint8_t *mem, int32_t size) { + iFd = -1; + this->open(mem, size); +} + +CFile::~CFile() { + this->close(); +} + +int32_t CFile::open(const std::string &filepath, eOpenTypes mode) { + this->close(); + int32_t openMode = 0; + + // This depend on the devoptab implementation. + // see https://github.com/devkitPro/wut/blob/master/libraries/wutdevoptab/devoptab_fs_open.c#L21 fpr reference + + switch (mode) { + default: + case ReadOnly: // file must exist + openMode = O_RDONLY; + break; + case WriteOnly: // file will be created / zerod + openMode = O_TRUNC | O_CREAT | O_WRONLY; + break; + case ReadWrite: // file must exist + openMode = O_RDWR; + break; + case Append: // append to file, file will be created if missing. write only + openMode = O_CREAT | O_APPEND | O_WRONLY; + break; + } + + //! Using fopen works only on the first launch as expected + //! on the second launch it causes issues because we don't overwrite + //! the .data sections which is needed for a normal application to re-init + //! this will be added with launching as RPX + iFd = ::open(filepath.c_str(), openMode); + if (iFd < 0) + return iFd; + + + filesize = ::lseek(iFd, 0, SEEK_END); + ::lseek(iFd, 0, SEEK_SET); + + return 0; +} + +int32_t CFile::open(const uint8_t *mem, int32_t size) { + this->close(); + + mem_file = mem; + filesize = size; + + return 0; +} + +void CFile::close() { + if (iFd >= 0) + ::close(iFd); + + iFd = -1; + mem_file = NULL; + filesize = 0; + pos = 0; +} + +int32_t CFile::read(uint8_t *ptr, size_t size) { + if (iFd >= 0) { + int32_t ret = ::read(iFd, ptr, size); + if (ret > 0) + pos += ret; + return ret; + } + + int32_t readsize = size; + + if (readsize > (int64_t) (filesize - pos)) + readsize = filesize - pos; + + if (readsize <= 0) + return readsize; + + if (mem_file != NULL) { + memcpy(ptr, mem_file + pos, readsize); + pos += readsize; + return readsize; + } + + return -1; +} + +int32_t CFile::write(const uint8_t *ptr, size_t size) { + if (iFd >= 0) { + size_t done = 0; + while (done < size) { + int32_t ret = ::write(iFd, ptr, size - done); + if (ret <= 0) + return ret; + + ptr += ret; + done += ret; + pos += ret; + } + return done; + } + + return -1; +} + +int32_t CFile::seek(long int offset, int32_t origin) { + int32_t ret = 0; + int64_t newPos = pos; + + if (origin == SEEK_SET) { + newPos = offset; + } else if (origin == SEEK_CUR) { + newPos += offset; + } else if (origin == SEEK_END) { + newPos = filesize + offset; + } + + if (newPos < 0) { + pos = 0; + } else { + pos = newPos; + } + + if (iFd >= 0) + ret = ::lseek(iFd, pos, SEEK_SET); + + if (mem_file != NULL) { + if (pos > filesize) { + pos = filesize; + } + } + + return ret; +} + +int32_t CFile::fwrite(const char *format, ...) { + char tmp[512]; + tmp[0] = 0; + int32_t result = -1; + + va_list va; + va_start(va, format); + if ((vsprintf(tmp, format, va) >= 0)) { + result = this->write((uint8_t *) tmp, strlen(tmp)); + } + va_end(va); + + + return result; +} diff --git a/src/utils/CFile.hpp b/src/utils/CFile.hpp new file mode 100644 index 0000000..27c0265 --- /dev/null +++ b/src/utils/CFile.hpp @@ -0,0 +1,71 @@ +#ifndef CFILE_HPP_ +#define CFILE_HPP_ + +#include +#include +#include +#include +#include +#include + +class CFile { +public: + enum eOpenTypes { + ReadOnly, + WriteOnly, + ReadWrite, + Append + }; + + CFile(); + + CFile(const std::string &filepath, eOpenTypes mode); + + CFile(const uint8_t *memory, int32_t memsize); + + virtual ~CFile(); + + int32_t open(const std::string &filepath, eOpenTypes mode); + + int32_t open(const uint8_t *memory, int32_t memsize); + + BOOL isOpen() const { + if (iFd >= 0) + return true; + + if (mem_file) + return true; + + return false; + } + + void close(); + + int32_t read(uint8_t *ptr, size_t size); + + int32_t write(const uint8_t *ptr, size_t size); + + int32_t fwrite(const char *format, ...); + + int32_t seek(long int offset, int32_t origin); + + uint64_t tell() { + return pos; + }; + + uint64_t size() { + return filesize; + }; + + void rewind() { + this->seek(0, SEEK_SET); + }; + +protected: + int32_t iFd; + const uint8_t *mem_file{}; + uint64_t filesize{}; + uint64_t pos{}; +}; + +#endif diff --git a/src/utils/FSUtils.cpp b/src/utils/FSUtils.cpp new file mode 100644 index 0000000..4e3c23e --- /dev/null +++ b/src/utils/FSUtils.cpp @@ -0,0 +1,127 @@ +#include "FSUtils.h" +#include "CFile.hpp" +#include "logger.h" +#include +#include +#include +#include +#include + + +int32_t FSUtils::CheckFile(const char *filepath) { + if (!filepath) + return 0; + + struct stat filestat {}; + + char dirnoslash[strlen(filepath) + 2]; + snprintf(dirnoslash, sizeof(dirnoslash), "%s", filepath); + + while (dirnoslash[strlen(dirnoslash) - 1] == '/') + dirnoslash[strlen(dirnoslash) - 1] = '\0'; + + char *notRoot = strrchr(dirnoslash, '/'); + if (!notRoot) { + strcat(dirnoslash, "/"); + } + + if (stat(dirnoslash, &filestat) == 0) + return 1; + + return 0; +} + +int32_t FSUtils::CreateSubfolder(const char *fullpath) { + if (!fullpath) + return 0; + + int32_t result = 0; + + char dirnoslash[strlen(fullpath) + 1]; + strcpy(dirnoslash, fullpath); + + int32_t pos = strlen(dirnoslash) - 1; + while (dirnoslash[pos] == '/') { + dirnoslash[pos] = '\0'; + pos--; + } + + if (CheckFile(dirnoslash)) { + return 1; + } else { + char parentpath[strlen(dirnoslash) + 2]; + strcpy(parentpath, dirnoslash); + char *ptr = strrchr(parentpath, '/'); + + if (!ptr) { + //!Device root directory (must be with '/') + strcat(parentpath, "/"); + struct stat filestat; + if (stat(parentpath, &filestat) == 0) + return 1; + + return 0; + } + + ptr++; + ptr[0] = '\0'; + + result = CreateSubfolder(parentpath); + } + + if (!result) + return 0; + + if (mkdir(dirnoslash, 0777) == -1) { + return 0; + } + + return 1; +} + +int32_t FSUtils::saveBufferToFile(const char *path, void *buffer, uint32_t size) { + CFile file(path, CFile::WriteOnly); + if (!file.isOpen()) { + DEBUG_FUNCTION_LINE_ERR("Failed to open %s\n", path); + return 0; + } + int32_t written = file.write((const uint8_t *) buffer, 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_ERR("Failed to open source %s", in.c_str()); + + return false; + } + int dest = open(out.c_str(), 0x602, 0644); + if (dest < 0) { + DEBUG_FUNCTION_LINE_ERR("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_ERR("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/utils/FSUtils.h b/src/utils/FSUtils.h new file mode 100644 index 0000000..83c7a25 --- /dev/null +++ b/src/utils/FSUtils.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +class FSUtils { +public: + //! todo: C++ class + static int32_t CreateSubfolder(const char *fullpath); + + static int32_t CheckFile(const char *filepath); + + static int32_t saveBufferToFile(const char *path, void *buffer, uint32_t size); + + static bool copyFile(const std::string &in, const std::string &out); +}; \ No newline at end of file diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp new file mode 100644 index 0000000..b3cd039 --- /dev/null +++ b/src/utils/utils.cpp @@ -0,0 +1,116 @@ +#include "utils.h" +#include "FSUtils.h" +#include "common.h" +#include "logger.h" +#include +#include +#include +#include + +bool Utils::GetSerialId(std::string &serialID) { + bool result = false; + alignas(0x40) MCPSysProdSettings settings{}; + auto handle = MCP_Open(); + if (handle >= 0) { + if (MCP_GetSysProdSettings(handle, &settings) == 0) { + serialID = std::string(settings.code_id) + settings.serial_id; + result = true; + } else { + DEBUG_FUNCTION_LINE_ERR("Failed to get SerialId"); + } + MCP_Close(handle); + } else { + DEBUG_FUNCTION_LINE_ERR("MCP_Open failed"); + } + return result; +} + +static inline bool existsAsFile(std::string_view path) { + struct stat st {}; + if (stat(path.data(), &st) >= 0 && S_ISREG(st.st_mode)) { + DEBUG_FUNCTION_LINE_VERBOSE("\"%s\" exists", path.data()); + return true; + } else { + DEBUG_FUNCTION_LINE_VERBOSE("\"%s\" doesn't exists", path.data()); + } + return false; +} + +/* + * Migrates wiiu/apps/AromaUpdater.wuhb to wiiu/apps/AromaUpdater/AromaUpdater.wuhb + */ +void Utils::MigrateAromaUpdater() { + bool newExists = existsAsFile(AROMA_UPDATER_NEW_PATH_FULL); + bool oldExists = existsAsFile(AROMA_UPDATER_OLD_PATH_FULL); + + if (newExists) { + if (oldExists) { + if (remove(AROMA_UPDATER_OLD_PATH_FULL) < 0) { + DEBUG_FUNCTION_LINE_WARN("Failed to remove old Aroma Updater: %d", errno); + } + } else { + DEBUG_FUNCTION_LINE_VERBOSE("Only new AromaUpdater.wuhb exists"); + } + return; + } else if (oldExists) { + if (!FSUtils::CreateSubfolder(AROMA_UPDATER_NEW_DIRECTORY_FULL)) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"%s\"", AROMA_UPDATER_NEW_DIRECTORY_FULL); + } + + if (rename(AROMA_UPDATER_OLD_PATH_FULL, AROMA_UPDATER_NEW_PATH_FULL) < 0) { + DEBUG_FUNCTION_LINE_WARN("Failed to move Aroma Updater to new path"); + } + } else { + DEBUG_FUNCTION_LINE_VERBOSE("No AromaUpdater.wuhb exists"); + } +} + +/* + * Dumps the OTP and SEEPROM when if it's no exists. + */ +void Utils::DumpOTPAndSeeprom() { + std::string serialId; + if (!Utils::GetSerialId(serialId)) { + DEBUG_FUNCTION_LINE_WARN("Failed to get SerialId of the console, skip OTP/SEEPROM dumping"); + return; + } + std::string backupPathConsole = string_format(BACKUPS_DIRECTORY_FULL "/%s", serialId.c_str()); + std::string backupPathConsoleOtpPath = backupPathConsole + "/opt.bin"; + std::string backupPathConsoleSeepromPath = backupPathConsole + "/seeprom.bin"; + + if (!FSUtils::CreateSubfolder(backupPathConsole.c_str())) { + DEBUG_FUNCTION_LINE_WARN("Failed to create \"%s\"", backupPathConsole.c_str()); + } + + bool seepromExists = FSUtils::CheckFile(backupPathConsoleSeepromPath.c_str()); + bool optExists = FSUtils::CheckFile(backupPathConsoleOtpPath.c_str()); + + if (!seepromExists) { + uint8_t data[0x200] = {}; + if (Mocha_SEEPROMRead(data, 0, sizeof(data)) != sizeof(data)) { + DEBUG_FUNCTION_LINE_WARN("Failed to read SEEPROM"); + } else { + if (FSUtils::saveBufferToFile(backupPathConsoleSeepromPath.c_str(), (void *) data, sizeof(data)) != sizeof(data)) { + DEBUG_FUNCTION_LINE_WARN("Failed to write SEEPROM backup (\"%s\")", backupPathConsoleSeepromPath.c_str()); + } else { + DEBUG_FUNCTION_LINE_INFO("Created SEEPROM backup: \"%s\"", backupPathConsoleSeepromPath.c_str()); + } + } + } else { + DEBUG_FUNCTION_LINE_VERBOSE("SEEPROM backup already exists"); + } + if (!optExists) { + WiiUConsoleOTP otp = {}; + if (Mocha_ReadOTP(&otp) != MOCHA_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE_WARN("Failed to read otp"); + } else { + if (FSUtils::saveBufferToFile(backupPathConsoleOtpPath.c_str(), (void *) &otp, sizeof(otp)) != sizeof(otp)) { + DEBUG_FUNCTION_LINE_WARN("Failed to write otp backup (\"%s\")", backupPathConsoleOtpPath.c_str()); + } else { + DEBUG_FUNCTION_LINE_INFO("Created OTP backup: \"%s\"", backupPathConsoleOtpPath.c_str()); + } + } + } else { + DEBUG_FUNCTION_LINE_VERBOSE("OTP backup already exists"); + } +} \ No newline at end of file diff --git a/src/utils/utils.h b/src/utils/utils.h new file mode 100644 index 0000000..1095a29 --- /dev/null +++ b/src/utils/utils.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include + +namespace Utils { + bool GetSerialId(std::string &serialID); + + void MigrateAromaUpdater(); + void DumpOTPAndSeeprom(); +} // namespace Utils + +template +std::string string_format(const std::string &format, Args... args) { + int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0' + auto size = static_cast(size_s); + auto buf = std::make_unique(size); + std::snprintf(buf.get(), size, format.c_str(), args...); + return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside +}