diff --git a/source/ApplicationState.cpp b/source/ApplicationState.cpp index c1bf789..664205a 100644 --- a/source/ApplicationState.cpp +++ b/source/ApplicationState.cpp @@ -34,7 +34,9 @@ void ApplicationState::render() { WiiUScreen::drawLine("Getting app information"); } else if (this->state == STATE_CHECK_PATCH_POSSIBLE) { WiiUScreen::drawLine("Check if console can be patched."); - } else if (this->state == STATE_CHECK_PATCH_POSSIBLE_DONE) { + } else if (this->state == STATE_CHECK_REMOVAL_POSSIBLE) { + WiiUScreen::drawLine("Check if Aroma can be removed."); + } else if (this->state == STATE_INSTALL_MENU) { WiiUScreen::drawLinef("Compatible title:"); WiiUScreen::drawLinef("%s", appInfo->appName); WiiUScreen::drawLine(); @@ -74,12 +76,24 @@ void ApplicationState::render() { WiiUScreen::drawLine(); WiiUScreen::drawLine("Press A to return to the Wii U Menu"); } else { + std::string options = ""; WiiUScreen::drawLine("Do you want to install Aroma?"); WiiUScreen::drawLine(""); - if (this->selectedOption == 0) { - WiiUScreen::drawLine("> Install Exit"); - } else if (this->selectedOption == 1) { - WiiUScreen::drawLine(" Install > Exit"); + + if (this->removalPossible) { + if (this->selectedOption == 0) { + WiiUScreen::drawLine("> Exit Install Remove"); + } else if (this->selectedOption == 1) { + WiiUScreen::drawLine(" Exit > Install Remove"); + } else if (this->selectedOption == 2) { + WiiUScreen::drawLine(" Exit Install > Remove"); + } + } else { + if (this->selectedOption == 0) { + WiiUScreen::drawLine("> Exit Install"); + } else if (this->selectedOption == 1) { + WiiUScreen::drawLine(" Exit > Install"); + } } } } else if (this->state == STATE_INSTALL_CHOOSE_COLDBOOT) { @@ -104,7 +118,7 @@ void ApplicationState::render() { if (this->selectedOption == 0) { WiiUScreen::drawLine("> Back Install without Coldboot"); } else if (this->selectedOption == 1) { - WiiUScreen::drawLine("> Back > Install without Coldboot"); + WiiUScreen::drawLine(" Back > Install without Coldboot"); } } else if (this->state == STATE_INSTALL_CONFIRM_DIALOG) { WiiUScreen::drawLine("Are you REALLY sure you want to install Aroma?"); @@ -143,6 +157,28 @@ void ApplicationState::render() { WiiUScreen::drawLine("Aroma was successfully installed"); WiiUScreen::drawLine(); WiiUScreen::drawLine("Press A to shutdown the console"); + } else if (this->state == STATE_REMOVE_CONFIRM_DIALOG) { + WiiUScreen::drawLine("Are you REALLY sure you want to remove Aroma?"); + WiiUScreen::drawLine("If you have installed unsigned system"); + WiiUScreen::drawLine("applications or modified system files"); + WiiUScreen::drawLine("this could make your console unusable"); + WiiUScreen::drawLine(); + WiiUScreen::drawLine(); + if (this->selectedOption == 0) { + WiiUScreen::drawLine("> Back Remove"); + } else if (this->selectedOption == 1) { + WiiUScreen::drawLine(" Back > Remove"); + } + } else if (this->state == STATE_REMOVE_STARTED) { + WiiUScreen::drawLine("Removing..."); + } else if (this->state == STATE_REMOVE_COLDBOOT) { + WiiUScreen::drawLine("... remove system.xml coldboot patches"); + } else if (this->state == STATE_REMOVE_AROMA) { + WiiUScreen::drawLine("... remove Aroma application patches"); + } else if (this->state == STATE_REMOVE_SUCCESS) { + WiiUScreen::drawLine("Aroma was successfully removed"); + WiiUScreen::drawLine(); + WiiUScreen::drawLine("Press A to shutdown the console"); } printFooter(); WiiUScreen::flipBuffers(); @@ -169,17 +205,21 @@ void ApplicationState::update(Input *input) { getAppInformation(); } else if (this->state == STATE_CHECK_PATCH_POSSIBLE) { checkPatchPossible(); - } else if (this->state == STATE_CHECK_PATCH_POSSIBLE_DONE) { + } else if (this->state == STATE_CHECK_REMOVAL_POSSIBLE) { + checkRemovalPossible(); + } else if (this->state == STATE_INSTALL_MENU) { if (this->fstPatchPossible && this->cosPatchPossible) { - proccessMenuNavigation(input, 2); + proccessMenuNavigation(input, 2 + (this->removalPossible ? 1 : 0)); if (entrySelected(input)) { - if (this->selectedOption == 0) { + if (this->selectedOption == 1) { if (systemXMLPatchPossible) { this->state = STATE_INSTALL_CHOOSE_COLDBOOT; this->installColdboot = false; } else { this->state = STATE_INSTALL_CONFIRM_DIALOG; } + } else if ((this->selectedOption == 2) && this->removalPossible) { + this->state = STATE_REMOVE_CONFIRM_DIALOG; } else { SYSLaunchMenu(); } @@ -202,7 +242,7 @@ void ApplicationState::update(Input *input) { proccessMenuNavigation(input, 3); if (entrySelected(input)) { if (this->selectedOption == 0) { // Back - this->state = STATE_CHECK_PATCH_POSSIBLE_DONE; + this->state = STATE_INSTALL_MENU; } else { if (selectedOption == 1) { // Install with coldboot this->installColdboot = true; @@ -216,7 +256,7 @@ void ApplicationState::update(Input *input) { proccessMenuNavigation(input, 2); if (entrySelected(input)) { if (this->selectedOption == 0) { - this->state = STATE_CHECK_PATCH_POSSIBLE_DONE; + this->state = STATE_INSTALL_MENU; } else { this->state = STATE_INSTALL_CONFIRM_DIALOG; } @@ -226,7 +266,7 @@ void ApplicationState::update(Input *input) { proccessMenuNavigation(input, 2); if (entrySelected(input)) { if (this->selectedOption == 0) { - this->state = STATE_CHECK_PATCH_POSSIBLE_DONE; + this->state = STATE_INSTALL_MENU; } else { this->state = STATE_INSTALL_STARTED; OSEnableHomeButtonMenu(false); @@ -296,6 +336,52 @@ void ApplicationState::update(Input *input) { if (entrySelected(input)) { OSShutdown(); } + } else if (this->state == STATE_REMOVE_CONFIRM_DIALOG) { + proccessMenuNavigation(input, 2); + if (entrySelected(input)) { + if (this->selectedOption == 0) { + this->state = STATE_INSTALL_MENU; + } else { + this->state = STATE_REMOVE_STARTED; + OSEnableHomeButtonMenu(false); + } + this->selectedOption = 0; + return; + } + } else if (this->state == STATE_REMOVE_STARTED) { + this->state = STATE_REMOVE_COLDBOOT; + } else if (this->state == STATE_REMOVE_COLDBOOT) { + auto result = InstallerService::patchSystemXML("storage_slc_installer:/config", *this->systemMenuTitleId); + if (result != InstallerService::SUCCESS) { + setError(ERROR_INSTALLER_ERROR); + this->installerError = result; + } else { + auto fsaFd = IOSUHAX_FSA_Open(); + if (fsaFd >= 0) { + if (IOSUHAX_FSA_FlushVolume(fsaFd, "/vol/storage_mlc01") == 0) { + DEBUG_FUNCTION_LINE("Flushed mlc"); + } + if (IOSUHAX_FSA_FlushVolume(fsaFd, "/vol/system") == 0) { + DEBUG_FUNCTION_LINE("Flushed slc"); + } + IOSUHAX_FSA_Close(fsaFd); + } else { + DEBUG_FUNCTION_LINE("Failed to open fsa"); + } + this->state = STATE_REMOVE_AROMA; + } + } else if (this->state == STATE_REMOVE_AROMA) { + auto result = InstallerService::restoreAppFiles(this->appInfo->path); + if (result != InstallerService::SUCCESS) { + setError(ERROR_INSTALLER_ERROR); + this->installerError = result; + } else { + this->state = STATE_REMOVE_SUCCESS; + } + } else if (this->state == STATE_REMOVE_SUCCESS) { + if (entrySelected(input)) { + OSShutdown(); + } } } @@ -345,8 +431,24 @@ void ApplicationState::checkPatchPossible() { if (result != InstallerService::SUCCESS) { DEBUG_FUNCTION_LINE("ERROR: %s", InstallerService::ErrorMessage(result).c_str()); } - this->state = STATE_CHECK_PATCH_POSSIBLE_DONE; + if (this->rpxAlreadyPatched) { + this->state = STATE_CHECK_REMOVAL_POSSIBLE; + } else { + this->state = STATE_INSTALL_MENU; + } +} + +void ApplicationState::checkRemovalPossible() { + this->removalPossible = false; + + this->systemMenuTitleId = InstallerService::getSystemMenuTitleId(); + if (this->systemMenuTitleId) { + this->removalPossible = InstallerService::checkSystemXML("storage_slc_installer:/config", *this->systemMenuTitleId) == InstallerService::SUCCESS; + this->removalPossible &= InstallerService::isBackupAvailable(this->appInfo->path); + } + + this->state = STATE_INSTALL_MENU; } void ApplicationState::getAppInformation() { diff --git a/source/ApplicationState.h b/source/ApplicationState.h index 4342f33..dfe26eb 100644 --- a/source/ApplicationState.h +++ b/source/ApplicationState.h @@ -21,7 +21,8 @@ public: STATE_WELCOME_SCREEN, STATE_GET_APP_INFORMATION, STATE_CHECK_PATCH_POSSIBLE, - STATE_CHECK_PATCH_POSSIBLE_DONE, + STATE_CHECK_REMOVAL_POSSIBLE, + STATE_INSTALL_MENU, STATE_INSTALL_CHOOSE_COLDBOOT, STATE_INSTALL_NO_COLDBOOT_ALLOWED, STATE_INSTALL_CONFIRM_DIALOG, @@ -31,7 +32,12 @@ public: STATE_INSTALL_COS, STATE_INSTALL_RPX, STATE_INSTALL_SYSTEM_XML, - STATE_INSTALL_SUCCESS + STATE_INSTALL_SUCCESS, + STATE_REMOVE_CONFIRM_DIALOG, + STATE_REMOVE_STARTED, + STATE_REMOVE_COLDBOOT, + STATE_REMOVE_AROMA, + STATE_REMOVE_SUCCESS }; ApplicationState(); @@ -44,6 +50,8 @@ public: void checkPatchPossible(); + void checkRemovalPossible(); + void getAppInformation(); std::optional appInfo; @@ -69,11 +77,13 @@ private: bool fstPatchPossible = false; bool cosPatchPossible = false; bool systemXMLPatchPossible = false; + bool removalPossible = false; eGameState state; eErrorState error = ERROR_NONE; uint64_t coldbootTitleId; _gList_t *coldbootTitle; + std::optional systemMenuTitleId; bool fstAlreadyPatched; bool rpxAlreadyPatched; bool cosAlreadyPatched; diff --git a/source/InstallerService.cpp b/source/InstallerService.cpp index 08ec8ef..425f3ab 100644 --- a/source/InstallerService.cpp +++ b/source/InstallerService.cpp @@ -186,6 +186,35 @@ std::optional InstallerService::getInstalledAppInformation() { return {}; } +std::optional InstallerService::getSystemMenuTitleId() { + auto mcpHandle = (int32_t) MCP_Open(); + auto titleCount = (uint32_t) 1; + auto *titleList = (MCPTitleListType *) memalign(32, sizeof(MCPTitleListType) * titleCount); + + MCP_TitleListByAppType(mcpHandle, MCP_APP_TYPE_SYSTEM_MENU, &titleCount, titleList, sizeof(MCPTitleListType) * titleCount); + + MCP_Close(mcpHandle); + + if (titleCount != 1) { + DEBUG_FUNCTION_LINE("More than 1 System Menu title!? Found %d", titleCount); + return {}; + } + + if ((titleList->titleId != 0x0005001010040000L) && + (titleList->titleId != 0x0005001010040100L) && + (titleList->titleId != 0x0005001010040200L)) + { + DEBUG_FUNCTION_LINE("Unrecognized System Menu title"); + return {}; + } + + uint64_t menuTid = titleList->titleId; + + free(titleList); + + return menuTid; +} + InstallerService::eResults InstallerService::patchFSTData(uint8_t *fstData, uint32_t size) { auto *fstHeader = (FSTHeader *) fstData; if (strncmp(FSTHEADER_MAGIC, fstHeader->magic, 3) != 0) { @@ -317,6 +346,77 @@ std::string InstallerService::ErrorMessage(InstallerService::eResults error) { } +bool InstallerService::isBackupAvailable(const std::string &path) { + std::string backupList[] = { + {"/content/title.fst.bak"}, + {"/content/cos.xml.bak" }, + {"/content/safe.rpx.bak" }, + }; + + for (auto &backupEntry : backupList) { + std::string backupFile = path + backupEntry; + std::string backupSha1 = backupFile + ".sha1"; + + if (!FSUtils::CheckFile(backupFile.c_str())) { + return false; + } + + if (!FSUtils::CheckFile(backupSha1.c_str())) { + continue; + } + + uint8_t *sha1FileCont; + uint32_t sha1FileSize; + FSUtils::LoadFileToMem(backupSha1.c_str(), &sha1FileCont, &sha1FileSize); + if (!sha1FileCont) { + return false; + } + + std::string savedHash = std::string(sha1FileCont, sha1FileCont + sha1FileSize); + std::string fileHash = Utils::hashFile(backupFile); + if (fileHash != savedHash) { + return false; + } + } + + return true; +} + +InstallerService::eResults InstallerService::restoreAppFiles(const std::string &path) { + std::string backupList[][2] = { + {"/code/title.fst", "/content/title.fst.bak"}, + {"/code/cos.xml", "/content/cos.xml.bak" }, + {"/code/safe.rpx", "/content/safe.rpx.bak" }, + }; + + for (auto &backupOp : backupList) { + std::string destPath = path + backupOp[0]; + std::string backupPath = path + backupOp[1]; + + if (!FSUtils::copyFile(backupPath, destPath)) { + DEBUG_FUNCTION_LINE("Failed to copy files"); + return FAILED_TO_COPY_FILES; + } + + std::string srcHash = Utils::hashFile(backupPath); + std::string dstHash = Utils::hashFile(destPath); + if (srcHash != dstHash) { + DEBUG_FUNCTION_LINE("Hashes do not match. %s %s", srcHash.c_str(), dstHash.c_str()); + return FAILED_TO_CHECK_HASH_COPIED_FILES; + } + } + + for (auto &backupOp : backupList) { + std::string backupPath = path + backupOp[1]; + std::string backupSha1Path = backupPath + ".sha1"; + ::remove(backupPath.c_str()); + ::remove(backupSha1Path.c_str()); + } + + DEBUG_FUNCTION_LINE("Successfully restored app files"); + return SUCCESS; +} + InstallerService::eResults InstallerService::backupAppFiles(const std::string &path) { std::string backupList[][2] = { {"/code/title.fst", "/content/title.fst.bak"}, diff --git a/source/InstallerService.h b/source/InstallerService.h index 4dec188..7a9f0dc 100644 --- a/source/InstallerService.h +++ b/source/InstallerService.h @@ -43,6 +43,10 @@ public: return false; } + static bool isBackupAvailable(const std::string &path); + + static eResults restoreAppFiles(const std::string &path); + static eResults backupAppFiles(const std::string &path); static eResults patchCOS(const std::string &path, char *hash); @@ -55,6 +59,8 @@ public: static std::optional getInstalledAppInformation(); + static std::optional getSystemMenuTitleId(); + static std::string ErrorMessage(eResults error); static std::string ErrorDescription(eResults error);