/**************************************************************************** * Copyright (C) 2021 Maschell * * 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 "UpdaterState.h" #include "utils/UpdateUtils.h" UpdaterState::UpdaterState() { this->mState = STATE_DOWNLOAD_VERSIONS; this->mErrorState = ERROR_NONE; this->mLoadAnimation = {"\ue020", "\ue021", "\ue022", "\ue023", "\ue024", "\ue025", "\ue026", "\ue027"}; } UpdaterState::~UpdaterState() = default; void UpdaterState::setError(UpdaterState::eErrorState err) { this->mState = STATE_ERROR; this->mErrorState = err; } extern "C" void OSLaunchTitlel(uint32_t, uint32_t, int, void *); ApplicationState::eSubState UpdaterState::update(Input *input) { switch (this->mState) { case STATE_ERROR: { if (entrySelected(input)) { return ApplicationState::SUBSTATE_RETURN; } return ApplicationState::SUBSTATE_RUNNING; } case STATE_DOWNLOAD_VERSIONS: { return UpdaterState::UpdateDownloadVersions(input); } case STATE_PARSE_VERSIONS: { return UpdaterState::UpdateParseVersions(input); } case STATE_GET_ENVIRONMENT_DIRECTORY: { return UpdaterState::UpdateGetEnvironmentDirectory(input); } case STATE_CHECK_ENVIRONMENT_DIRECTORY: { return UpdaterState::UpdateCheckEnvironmentDirectory(input); } case STATE_CHECK_VERSIONS: { return UpdaterState::UpdateCheckVersions(input); } case STATE_SHOW_VERSIONS: { return UpdateShowVersionsMenu(input); } case STATE_SELECTED_PACKAGES_EMPTY: { if (buttonPressed(input, Input::BUTTON_A)) { mState = STATE_SHOW_VERSIONS; } else if (buttonPressed(input, Input::BUTTON_B)) { mState = STATE_SHOW_VERSIONS; } break; } case STATE_CONFIRM_PACKAGES: { if (buttonPressed(input, Input::BUTTON_A)) { mState = STATE_UPDATE_CHECK_FILES; } else if (buttonPressed(input, Input::BUTTON_B)) { mState = STATE_SHOW_VERSIONS; } break; } case STATE_UPDATE_CHECK_FILES: { mFilesToUpdateAll = UpdateUtils::GetNonLatestFilesFromVersionInfo(mVersionInfo, mOnlyRequired); mFilesToUpdateAllSize = mFilesToUpdateAll.size(); mState = STATE_UPDATE_GET_INDIVIDUAL_FILE; break; } case STATE_UPDATE_GET_INDIVIDUAL_FILE: { bool fullBreak = false; while (!mFilesToUpdateAll.empty()) { mCurFile = mFilesToUpdateAll.front(); mFilesToUpdateAll.erase(mFilesToUpdateAll.begin() + 0); if (mCurFile->getStatus() == VersionCheck::FileStatus::Outdated || mCurFile->getStatus() == VersionCheck::FileStatus::Missing) { mFilesToUpdateConfirmed.push_back(mCurFile.value()); mCurFile = {}; } else { mState = STATE_UPDATE_CHECK_INDIVIDUAL_FILE; fullBreak = true; break; } } if (fullBreak) { break; } mCurFile = {}; if (!mFilesToUpdateConfirmed.empty() && (mFilesToUpdateConfirmed.size() == mFilesToUpdateAllSize)) { mFilesDownloadList = mFilesToUpdateConfirmed; mState = STATE_UPDATE_PROCESS_DOWNLOAD_FILES; } else { mState = STATE_UPDATE_PROCESS_FILES; } break; } case STATE_UPDATE_CHECK_INDIVIDUAL_FILE: { if (!mCurFile.has_value()) { setError(ERROR_UNKNOWN); break; } else { if (buttonPressed(input, Input::BUTTON_A)) { mFilesToUpdateConfirmed.push_back(mCurFile.value()); mState = STATE_UPDATE_GET_INDIVIDUAL_FILE; } else if (buttonPressed(input, Input::BUTTON_B)) { mState = STATE_UPDATE_GET_INDIVIDUAL_FILE; } else { break; } mCurFile = {}; } break; } case STATE_UPDATE_PROCESS_FILES: { if (!mFilesToUpdateConfirmed.empty()) { if (buttonPressed(input, Input::BUTTON_A)) { mFilesDownloadList = mFilesToUpdateConfirmed; mState = STATE_UPDATE_PROCESS_DOWNLOAD_FILES; break; } else if (buttonPressed(input, Input::BUTTON_B)) { return SUBSTATE_RETURN; } } else { mState = STATE_UPDATE_PROCESS_FILES_EMPTY; } break; } case STATE_UPDATE_PROCESS_FILES_EMPTY: { if (buttonPressed(input, Input::BUTTON_A)) { return SUBSTATE_RETURN; } break; } case STATE_UPDATE_PROCESS_DOWNLOAD_FILES: { return UpdaterState::UpdateProcessDownloadFiles(input); } case STATE_UPDATE_PROCESS_DOWNLOAD_FILES_FINISHED: { return UpdaterState::UpdateDownloadFilesFinished(input); } case STATE_UPDATE_SUCCESS: { if (buttonPressed(input, Input::BUTTON_A)) { OSLaunchTitlel(0xffffffff, 0xfffffffe, 0, nullptr); return SUBSTATE_RESTART; } break; } } return ApplicationState::SUBSTATE_RUNNING; } void UpdaterState::render() { DrawUtils::beginDraw(); DrawUtils::clear(BACKGROUND_COLOR); ApplicationState::printHeader(); switch (mState) { case STATE_ERROR: { UpdaterState::RenderError(); break; } case STATE_DOWNLOAD_VERSIONS: { UpdaterState::RenderDownloadVersions(); break; } case STATE_PARSE_VERSIONS: { DrawUtils::setFontColor(COLOR_WHITE); DrawUtils::setFontSize(20); DrawUtils::print(16, 80, "Parsing the latest versions..."); break; } case STATE_GET_ENVIRONMENT_DIRECTORY: { break; }; case STATE_CHECK_ENVIRONMENT_DIRECTORY: { UpdaterState::RenderCheckEnvironmentDirectory(); break; }; case STATE_CHECK_VERSIONS: { DrawUtils::setFontColor(COLOR_WHITE); DrawUtils::setFontSize(20); if (this->mProgress > 0.0f) { DrawUtils::printf(16, 80, false, "Checking the version of each file, this might take a bit... %.0f%%. %s", this->mProgress * 100.0f, this->mLoadAnimation[mAnimationTick++ % this->mLoadAnimation.size()].c_str()); } else { DrawUtils::printf(16, 80, false, "Checking the version of each file, this might take a bit... %s", this->mLoadAnimation[mAnimationTick++ % this->mLoadAnimation.size()].c_str()); } break; } case STATE_SHOW_VERSIONS: { UpdaterState::RenderShowVersions(); break; } case STATE_SELECTED_PACKAGES_EMPTY: { DrawUtils::setFontColor(COLOR_WHITE); DrawUtils::setFontSize(20); DrawUtils::printf(16, 80, false, "The selected packages are already up to date"); DrawUtils::setFontSize(18); DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 14, "\ue000 Return", true); break; } case STATE_CONFIRM_PACKAGES: { UpdaterState::RenderConfirmPackages(); break; } case STATE_UPDATE_CHECK_FILES: { DrawUtils::setFontColor(COLOR_WHITE); DrawUtils::setFontSize(20); DrawUtils::print(16, 80, "Checking files.."); break; } case STATE_UPDATE_GET_INDIVIDUAL_FILE: { DrawUtils::setFontColor(COLOR_WHITE); DrawUtils::setFontSize(20); DrawUtils::print(16, 80, "Checking files..."); break; } case STATE_UPDATE_CHECK_INDIVIDUAL_FILE: { UpdaterState::RenderCheckIndividualFile(); break; } case STATE_UPDATE_PROCESS_FILES: { DrawUtils::setFontColor(COLOR_WHITE); DrawUtils::setFontSize(20); DrawUtils::printf(16, 80, false, "You have skipped %d files.", mFilesToUpdateAllSize - mFilesToUpdateConfirmed.size()); DrawUtils::printf(16, 100, false, "Do you want to continue?"); DrawUtils::setFontSize(18); DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 14, "\ue000 Confirm", true); DrawUtils::print(16, SCREEN_HEIGHT - 14, "\ue001 Abort"); break; } case STATE_UPDATE_PROCESS_FILES_EMPTY: DrawUtils::setFontColor(COLOR_WHITE); DrawUtils::setFontSize(20); DrawUtils::print(16, 80, "All files that could be updated have been skipped."); DrawUtils::setFontSize(18); DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 14, "\ue000 Return", true); break; case STATE_UPDATE_PROCESS_DOWNLOAD_FILES: { UpdaterState::RenderProcessDownloadFiles(); break; } case STATE_UPDATE_PROCESS_DOWNLOAD_FILES_FINISHED: { DrawUtils::setFontColor(COLOR_WHITE); DrawUtils::setFontSize(20); DrawUtils::print(16, 80, "Copy files..."); break; } case STATE_UPDATE_SUCCESS: { DrawUtils::setFontSize(20); DrawUtils::print(16, 80, "All files have been updated successfully."); DrawUtils::print(16, 100, "The console will now restart."); DrawUtils::setFontSize(18); DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 14, "\ue000 Restart", true); break; } } ApplicationState::printFooter(); DrawUtils::endDraw(); } const char *UpdaterState::ErrorMessage() const { switch (this->mErrorState) { case ERROR_NONE: return "ERROR_NONE"; case ERROR_FAILED_TO_DOWNLOAD_VERSIONS: return "Failed to download the latest version information."; case ERROR_FAILED_TO_PARSE_VERSIONS: return "Failed to parse the latest version information."; case ERROR_FAILED_TO_SAVE_FILE: return "Failed to write a file to the sd card."; case ERROR_FAILED_TO_FIND_FILE_IN_ZIP: return "Failed to find the file in the zip."; case ERROR_UNKNOWN: return "Something unexpected has happened."; case ERROR_DOWNLOAD_FAILED: return "The download has failed"; case ERROR_EXTRACT_FAILED: return "Extracting a file to the sd card has failed."; case ERROR_FAILED_TO_CREATE_THREAD: return "Creating a thread has failed."; case ERROR_DOWNLOADED_FILES_INVALID: return "The downloaded files were corrupt."; case ERROR_FAILED_COPY_FILES: return "Failed to finalize the update."; case ERROR_FAILED_TO_CREATE_DIR: return "Failed to create a directory."; } return "Something unexpected has happened."; } const char *UpdaterState::ErrorDescription() const { switch (this->mErrorState) { case ERROR_NONE: return "ERROR_NONE"; case ERROR_FAILED_TO_DOWNLOAD_VERSIONS: return "ERROR_FAILED_TO_DOWNLOAD_VERSIONS"; case ERROR_FAILED_TO_PARSE_VERSIONS: return "ERROR_FAILED_TO_PARSE_VERSIONS"; case ERROR_FAILED_TO_SAVE_FILE: return "ERROR_FAILED_TO_SAVE_FILE"; case ERROR_FAILED_TO_FIND_FILE_IN_ZIP: return "ERROR_FAILED_TO_FIND_FILE_IN_ZIP"; case ERROR_UNKNOWN: return "ERROR_UNKNOWN"; case ERROR_DOWNLOAD_FAILED: return "ERROR_DOWNLOAD_FAILED"; case ERROR_EXTRACT_FAILED: return "ERROR_EXTRACT_FAILED"; case ERROR_FAILED_TO_CREATE_THREAD: return "ERROR_FAILED_TO_CREATE_THREAD"; case ERROR_DOWNLOADED_FILES_INVALID: return "ERROR_DOWNLOADED_FILES_INVALID"; case ERROR_FAILED_COPY_FILES: return "ERROR_FAILED_COPY_FILES"; case ERROR_FAILED_TO_CREATE_DIR: return "ERROR_FAILED_TO_CREATE_DIR"; } return "UNKNOWN_ERROR"; }