From 42f6baa6ea777866686734b65b8080d087c4287c Mon Sep 17 00:00:00 2001 From: w3irDv <170813473+w3irDv@users.noreply.github.com> Date: Sun, 20 Oct 2024 10:50:03 +0200 Subject: [PATCH] enh - batch restore - mocked version --- include/ApplicationState.h | 1 + include/BackupSetList.h | 1 + include/menu/BRTitleSelectState.h | 61 +++++++ include/menu/BackupSetListState.h | 10 +- include/menu/BatchRestoreOptions.h | 39 +++++ include/menu/BatchRestoreState.h | 32 ++++ include/savemng.h | 23 ++- src/main.cpp | 3 +- src/menu/BRTitleSelectState.cpp | 259 +++++++++++++++++++++++++++++ src/menu/BackupSetListState.cpp | 18 +- src/menu/BatchRestoreOptions.cpp | 191 +++++++++++++++++++++ src/menu/BatchRestoreState.cpp | 69 ++++++++ src/menu/MainMenuState.cpp | 10 +- src/savemng.cpp | 255 ++++++++++++++++++++++++---- src/utils/DrawUtils.cpp | 11 +- 15 files changed, 944 insertions(+), 39 deletions(-) create mode 100644 include/menu/BRTitleSelectState.h create mode 100644 include/menu/BatchRestoreOptions.h create mode 100644 include/menu/BatchRestoreState.h create mode 100644 src/menu/BRTitleSelectState.cpp create mode 100644 src/menu/BatchRestoreOptions.cpp create mode 100644 src/menu/BatchRestoreState.cpp diff --git a/include/ApplicationState.h b/include/ApplicationState.h index 2c0e0c5..af93602 100644 --- a/include/ApplicationState.h +++ b/include/ApplicationState.h @@ -27,4 +27,5 @@ class ApplicationState { virtual void render() = 0; virtual eSubState update(Input *input) = 0; + }; \ No newline at end of file diff --git a/include/BackupSetList.h b/include/BackupSetList.h index b31bc2c..7ca2e81 100644 --- a/include/BackupSetList.h +++ b/include/BackupSetList.h @@ -93,6 +93,7 @@ class BackupSetList { static const std::string ROOT_BS; static std::string getBackupSetSubPath() { return backupSetSubPath; }; + static std::string getBackupSetPath() { return currentBackupSetList->backupSetListRoot+backupSetSubPath; }; static std::string getBackupSetEntry() { return backupSetEntry; }; static std::string getBackupSetSubPath(int i); static void setBackupSetEntry(int i); diff --git a/include/menu/BRTitleSelectState.h b/include/menu/BRTitleSelectState.h new file mode 100644 index 0000000..e125afb --- /dev/null +++ b/include/menu/BRTitleSelectState.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class BRTitleSelectState : public ApplicationState { +public: + BRTitleSelectState(int sduser, int wiiuuser, bool common, bool wipeBeforeRestore, bool fullBackup,Title *titles, int titlesCount); + + enum eState { + STATE_BATCH_RESTORE_TITLE_SELECT, + STATE_DO_SUBSTATE, + }; + + void render() override; + ApplicationState::eSubState update(Input *input) override; + +private: + std::unique_ptr subState{}; + eState state = STATE_BATCH_RESTORE_TITLE_SELECT; + + + int sduser; + int wiiuuser; + bool common; + bool wipeBeforeRestore; + bool fullBackup; + Title *titles; + int titlesCount; + + + std::vector sortNames = {LanguageUtils::gettext("None"), + LanguageUtils::gettext("Name"), + LanguageUtils::gettext("Storage"), + LanguageUtils::gettext("Storage+Name")}; + + int titleSort = 1; + int scroll = 0; + int cursorPos = 0; + bool sortAscending = true; + int targ = 0; + + bool noTitles = false; + + std::vector c2t; + int candidatesCount; + + void updateC2t(); + + std::vector titleStateAfterBR = { + " ", + " > Aborted", + " > OK", + " > WR", + " > KO" + }; +}; \ No newline at end of file diff --git a/include/menu/BackupSetListState.h b/include/menu/BackupSetListState.h index 158500f..ecfafb7 100644 --- a/include/menu/BackupSetListState.h +++ b/include/menu/BackupSetListState.h @@ -2,14 +2,14 @@ #include #include +#include #include - - - class BackupSetListState : public ApplicationState { public: BackupSetListState(); + BackupSetListState(Title *titles, int titlesCount); + static void resetCursorPosition(); static void resetCursorAndScroll(); enum eState { @@ -29,6 +29,7 @@ class BackupSetListState : public ApplicationState { std::unique_ptr subState{}; eState state = STATE_BACKUPSET_MENU; eSubstateCalled substateCalled = NONE; + bool finalScreen; bool sortAscending; @@ -39,4 +40,7 @@ class BackupSetListState : public ApplicationState { std::string tag; std::string newTag; + + Title *titles; + int titlesCount; }; \ No newline at end of file diff --git a/include/menu/BatchRestoreOptions.h b/include/menu/BatchRestoreOptions.h new file mode 100644 index 0000000..dbe00bc --- /dev/null +++ b/include/menu/BatchRestoreOptions.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class BatchRestoreOptions : public ApplicationState { +public: + BatchRestoreOptions(Title *titles, int titlesCount); + + enum eState { + STATE_BATCH_RESTORE_OPTIONS_MENU, + STATE_DO_SUBSTATE, + }; + + void render() override; + ApplicationState::eSubState update(Input *input) override; + std::string tag; + +private: + std::unique_ptr subState{}; + eState state = STATE_BATCH_RESTORE_OPTIONS_MENU; + + int8_t wiiuuser = -1; + int8_t sduser = -1; + bool common = false; + + bool wipeBeforeRestore = true; + bool fullBackup = true; + std::set> batchSDUsers; + + Title *titles; + int titlesCount = 0; + + int cursorPos = 0; +}; \ No newline at end of file diff --git a/include/menu/BatchRestoreState.h b/include/menu/BatchRestoreState.h new file mode 100644 index 0000000..b27bfd6 --- /dev/null +++ b/include/menu/BatchRestoreState.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +class BatchRestoreState : public ApplicationState { +public: + BatchRestoreState(Title *wiiutitles, Title *wiititles, int wiiuTitlesCount, int vWiiTitlesCount) : wiiutitles(wiiutitles), + wiititles(wiititles), + wiiuTitlesCount(wiiuTitlesCount), + vWiiTitlesCount(vWiiTitlesCount) {} + enum eState { + STATE_BATCH_RESTORE_MENU, + STATE_DO_SUBSTATE, + }; + + void render() override; + ApplicationState::eSubState update(Input *input) override; + std::string tag; + +private: + std::unique_ptr subState{}; + eState state = STATE_BATCH_RESTORE_MENU; + + Title *wiiutitles; + Title *wiititles; + + int wiiuTitlesCount; + int vWiiTitlesCount; +}; \ No newline at end of file diff --git a/include/savemng.h b/include/savemng.h index 9fafcdc..e00b6d8 100644 --- a/include/savemng.h +++ b/include/savemng.h @@ -23,6 +23,23 @@ #define M_OFF 1 #define Y_OFF 1 +enum eBatchRestoreState { + NOT_TRIED = 0, + ABORTED = 1, + OK = 2, + WR = 3, + KO = 4 +}; + +struct backupInfo { + bool hasBatchBackup; + bool candidateToBeRestored; + bool selected; + bool hasUserSavedata; + bool hasCommonSavedata; + eBatchRestoreState batchRestoreState; +}; + struct Title { uint32_t highID; uint32_t lowID; @@ -37,6 +54,7 @@ struct Title { uint8_t *iconBuf; uint64_t accountSaveSize; uint32_t groupID; + backupInfo currentBackup; }; struct Saves { @@ -119,6 +137,7 @@ bool getLoadiineGameSaveDir(char *out, const char *productCode, const char *long bool getLoadiineSaveVersionList(int *out, const char *gamePath); bool isSlotEmpty(uint32_t highID, uint32_t lowID, uint8_t slot); bool isSlotEmpty(uint32_t highID, uint32_t lowID, uint8_t slot, const std::string &batchDatetime); +bool folderEmpty(const char *fPath); bool hasCommonSave(Title *title, bool inSD, bool iine, uint8_t slot, int version); void copySavedata(Title *title, Title *titled, int8_t wiiuuser, int8_t wiiuuser_d, bool common) __attribute__((hot)); std::string getNowDateForFolder() __attribute__((hot)); @@ -128,8 +147,8 @@ void writeMetadata(uint32_t highID,uint32_t lowID,uint8_t slot,bool isUSB,const void writeBackupAllMetadata(const std::string & Date); void backupAllSave(Title *titles, int count, const std::string &batchDatetime) __attribute__((hot)); void backupSavedata(Title *title, uint8_t slot, int8_t wiiuuser, bool common) __attribute__((hot)); -void restoreSavedata(Title *title, uint8_t slot, int8_t sduser, int8_t wiiuuser, bool common) __attribute__((hot)); -void wipeSavedata(Title *title, int8_t wiiuuser, bool common) __attribute__((hot)); +int restoreSavedata(Title *title, uint8_t slot, int8_t sduser, int8_t wiiuuser, bool common, bool interactive = true) __attribute__((hot)); +int wipeSavedata(Title *title, int8_t wiiuuser, bool common, bool interactive = true) __attribute__((hot)); void importFromLoadiine(Title *title, bool common, int version); void exportToLoadiine(Title *title, bool common, int version); int checkEntry(const char *fPath); diff --git a/src/main.cpp b/src/main.cpp index 1ddcc39..b0331be 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,7 +19,7 @@ #include #include -//#define DEBUG +#define DEBUG #ifdef DEBUG #include @@ -415,6 +415,7 @@ int main() { #ifdef DEBUG WHBLogUdpInit(); + WHBLogPrintf("Hello from savemii!"); #endif AXInit(); diff --git a/src/menu/BRTitleSelectState.cpp b/src/menu/BRTitleSelectState.cpp new file mode 100644 index 0000000..80f800f --- /dev/null +++ b/src/menu/BRTitleSelectState.cpp @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define MAX_TITLE_SHOW 14 + +BRTitleSelectState::BRTitleSelectState(int sduser, int wiiuuser, bool common, bool wipeBeforeRestore, bool fullBackup, + Title *titles, int titlesCount) : + sduser(sduser), + wiiuuser(wiiuuser), + common(common), + wipeBeforeRestore(wipeBeforeRestore), + fullBackup(fullBackup), + titles(titles), + titlesCount(titlesCount) +{ + WHBLogPrintf("sduser %d",sduser); + WHBLogPrintf("wiiuuser %d",wiiuuser); + WHBLogPrintf("common %c",common ? '0':'1'); + WHBLogPrintf("wipe %c",wipeBeforeRestore ? '0':'1'); + WHBLogPrintf("tcount %u",titlesCount); + WHBLogPrintf("titles %u",titles); + + c2t.clear(); + // from the subset of titles with backup data, filter out the ones without the specified user info + for (int i = 0; i < this->titlesCount; i++) { + WHBLogPrintf("processing %u",i); + WHBLogPrintf("has backup %u",i); + this->titles[i].currentBackup.candidateToBeRestored = false; + this->titles[i].currentBackup.selected = false; + this->titles[i].currentBackup.hasUserSavedata = false; + this->titles[i].currentBackup.hasCommonSavedata = false; + this->titles[i].currentBackup.batchRestoreState = NOT_TRIED; + if ( this->titles[i].currentBackup.hasBatchBackup == false ) + continue; + WHBLogPrintf("get id %u",i); + uint32_t highID = this->titles[i].highID; + uint32_t lowID = this->titles[i].lowID; + + + std::string srcPath = getDynamicBackupPath(highID, lowID, 0); + std::string usersavePath = srcPath+"/"+getSDacc()[sduser].persistentID; + + WHBLogPrintf("check user - %u",i); + WHBLogPrintf("check empty path - %s",usersavePath.c_str()); + + + if (! folderEmpty(usersavePath.c_str())) + this->titles[i].currentBackup.hasUserSavedata = true; + + std::string commonSavePath = srcPath+"/common"; + if (! folderEmpty(commonSavePath.c_str())) + this->titles[i].currentBackup.hasCommonSavedata = true; + + if ( sduser != -1 && common == false && ! this->titles[i].currentBackup.hasUserSavedata) + continue; + + // shouldn't happen for wii u titles, but ... + if ( sduser != -1 && ! this->titles[i].currentBackup.hasCommonSavedata && ! this->titles[i].currentBackup.hasUserSavedata ) + continue; + + WHBLogPrintf("set to true - %u", i); + this->titles[i].currentBackup.candidateToBeRestored = true; // backup has enough data to try restore + this->titles[i].currentBackup.selected = true; // from candidates list, user can select/deselest at wish + // to recover title from "candidate title" index + this->c2t.push_back(i); + } + WHBLogPrintf("out of loop"); + candidatesCount = (int) this->c2t.size(); + WHBLogPrintf("init done. size: %u",candidatesCount); + +}; + +void BRTitleSelectState::updateC2t() +{ + int j = 0; + for (int i = 0; i < this->titlesCount; i++) { + if ( ! this->titles[i].currentBackup.candidateToBeRestored ) + continue; + c2t[j++]=i; + } +} + +void BRTitleSelectState::render() { + WHBLogPrintf("rendering"); + if (this->state == STATE_DO_SUBSTATE) { + if (this->subState == nullptr) { + OSFatal("SubState was null"); + } + this->subState->render(); + return; + } + if (this->state == STATE_BATCH_RESTORE_TITLE_SELECT) { + if ((this->titles == nullptr) || (this->titlesCount == 0 || (this->candidatesCount == 0))) { + promptError(LanguageUtils::gettext("No Wii U titles found.")); + this->noTitles = true; + } + consolePrintPos(39, 0, LanguageUtils::gettext("%s Sort: %s \ue084"), + (this->titleSort > 0) ? (this->sortAscending ? "\ue083 \u2193" : "\ue083 \u2191") : "", this->sortNames[this->titleSort]); + for (int i = 0; i < MAX_TITLE_SHOW; i++) { + if (i + this->scroll < 0 || i + this->scroll >= (int) this->candidatesCount) + break; + DrawUtils::setFontColor(static_cast(0x00FF00FF)); + if (!this->titles[c2t[i + this->scroll]].currentBackup.selected) + DrawUtils::setFontColor(static_cast(0xFFFF00FF)); + if (strcmp(this->titles[c2t[i + this->scroll]].shortName, "DONT TOUCH ME") == 0) + DrawUtils::setFontColor(static_cast(0xFF0000FF)); + if (this->titles[c2t[i + this->scroll]].currentBackup.batchRestoreState == KO) + DrawUtils::setFontColor(static_cast(0xFF0000FF)); + if (strlen(this->titles[c2t[i + this->scroll]].shortName) != 0u) + consolePrintPos(M_OFF, i + 2, " %s %s%s%s [%s]", this->titles[c2t[i + this->scroll]].shortName, + this->titles[c2t[i + this->scroll]].isTitleOnUSB ? "(USB)" : "(NAND)", + this->titles[c2t[i + this->scroll]].isTitleDupe ? " [D]" : "", + this->titles[c2t[i + this->scroll]].currentBackup.selected ? LanguageUtils::gettext(" [Restore]" : LanguageUtils::gettext(" [Skip]"), + titleStateAfterBR[this->titles[c2t[i + this->scroll]].currentBackup.batchRestoreState]); + + else + consolePrintPos(M_OFF, i + 2, " %08lx%08lx", this->titles[i + this->scroll].highID, + this->titles[c2t[i + this->scroll]].lowID); + DrawUtils::setFontColor(COLOR_TEXT); + if (this->titles[c2t[i + this->scroll]].iconBuf != nullptr) { + DrawUtils::drawTGA((M_OFF + 4) * 12 - 2, (i + 3) * 24, 0.18, this->titles[c2t[i + this->scroll]].iconBuf); + } + } + consolePrintPos(-1, 2 + cursorPos, "\u2192"); + consolePrintPosAligned(17, 4, 2, LanguageUtils::gettext("\ue003: Select/Deselect \ue000: Restore selected titles \ue001: Back")); + } +} + +ApplicationState::eSubState BRTitleSelectState::update(Input *input) { + if (this->state == STATE_BATCH_RESTORE_TITLE_SELECT) { + if (input->get(TRIGGER, PAD_BUTTON_B) || noTitles) + return SUBSTATE_RETURN; + if (input->get(TRIGGER, PAD_BUTTON_R)) { + this->titleSort = (this->titleSort + 1) % 4; + sortTitle(this->titles, this->titles + this->titlesCount, this->titleSort, this->sortAscending); + this->updateC2t(); + } + if (input->get(TRIGGER, PAD_BUTTON_L)) { + if (this->titleSort > 0) { + this->sortAscending = !this->sortAscending; + sortTitle(this->titles, this->titles + this->titlesCount, this->titleSort, this->sortAscending); + this->updateC2t(); + } + } + if (input->get(TRIGGER, PAD_BUTTON_A)) { + /* + this->targ = cursorPos + this->scroll; + std::string path = StringUtils::stringFormat("%s/usr/title/000%x/%x/code/fw.img", + (this->titles[this->targ].isTitleOnUSB) ? getUSB().c_str() : "storage_mlc01:", this->titles[this->targ].highID, + this->titles[this->targ].lowID); + if (checkEntry(path.c_str()) != 0) + if (!promptConfirm(ST_ERROR, LanguageUtils::gettext("vWii saves are in the vWii section. Continue?"))) + return SUBSTATE_RUNNING; + */ + WHBLogPrintf("initiating restore process"); + if ( fullBackup ) { + const std::string batchDatetime = getNowDateForFolder(); + //backupAllSave(this->titles, this->titlesCount, batchDatetime); + WHBLogPrintf("perform all backup"); + } + + int retCode; + + for (int i = 0; i < titlesCount ; i++) { + if (! this->titles[i].currentBackup.selected ) + continue; + WHBLogPrintf("processing title %d %s",i,this->titles[i].shortName); + this->titles[i].currentBackup.batchRestoreState = OK; + WHBLogPrintf("TITLE HAS COMMON: %s",this->titles[i].currentBackup.hasCommonSavedata?"si":"no"); + bool effectiveCommon = common && this->titles[i].currentBackup.hasCommonSavedata; + if ( wipeBeforeRestore ) { + WHBLogPrintf("wiping"); + if (sduser != -1) { + if ( ! this->titles[i].currentBackup.hasUserSavedata && effectiveCommon) { // if sduser does not exist, don't try to wipe wiiuser, but only common + // -2 is a flag just to only operate on common saves (because sd user does not exist for this title) + retCode = wipeSavedata(&this->titles[i], -2, effectiveCommon, false); + if (retCode > 0) + this->titles[i].currentBackup.batchRestoreState = WR; + else if (retCode < 0) + this->titles[i].currentBackup.batchRestoreState = ABORTED; + + WHBLogPrintf("%u - wipe user: %d common: %s",i,-2,effectiveCommon ? "si":"no"); + goto wipeDone; + } + } + + retCode = wipeSavedata(&this->titles[i], wiiuuser, effectiveCommon, false); + if (retCode > 0) + this->titles[i].currentBackup.batchRestoreState = WR; + WHBLogPrintf("%u - wipe user: %d common: %s",i,wiiuuser,effectiveCommon ? "si":"no"); + } + wipeDone: + WHBLogPrintf("restoring"); + if (sduser != -1) { + if ( ! this->titles[i].currentBackup.hasUserSavedata && effectiveCommon) { + // -2 is a flag just to only operate on common saves (because sd user does not exist for this title) + retCode = restoreSavedata(&this->titles[i], 0, -2 , wiiuuser, effectiveCommon, false); + if (retCode > 0) + this->titles[i].currentBackup.batchRestoreState = KO; + WHBLogPrintf("%u - restore user: %d common: %s",i,-2,effectiveCommon ? "si":"no"); + continue; + } + } + retCode = restoreSavedata(&this->titles[i], 0, sduser, wiiuuser, effectiveCommon, false); //always from slot 0 + if (retCode > 0) + this->titles[i].currentBackup.batchRestoreState = KO; + WHBLogPrintf("%u - restore user: %d common: %s",i,wiiuuser,effectiveCommon ? "si":"no"); + } + + WHBLogPrintf("restore done"); + + //DrawUtils::setRedraw(true); + //this->state = STATE_DO_SUBSTATE; + //this->subState = std::make_unique(this->titles[this->targ], this->titles, this->titlesCount); + } + if (input->get(TRIGGER, PAD_BUTTON_DOWN)) { + if (this->candidatesCount <= 14) + cursorPos = (cursorPos + 1) % this->candidatesCount; + else if (cursorPos < 6) + cursorPos++; + else if (((cursorPos + this->scroll + 1) % this->candidatesCount) != 0) + scroll++; + else + cursorPos = scroll = 0; + } else if (input->get(TRIGGER, PAD_BUTTON_UP)) { + if (scroll > 0) + cursorPos -= (cursorPos > 6) ? 1 : 0 * (scroll--); + else if (cursorPos > 0) + cursorPos--; + else if (this->candidatesCount > 14) + scroll = this->candidatesCount - (cursorPos = 6) - 1; + else + cursorPos = this->candidatesCount - 1; + } + if (input->get(TRIGGER, PAD_BUTTON_Y)) { + this->titles[c2t[cursorPos + this->scroll]].currentBackup.selected = this->titles[c2t[cursorPos + this->scroll]].currentBackup.selected ? false:true; + } + } else if (this->state == STATE_DO_SUBSTATE) { + auto retSubState = this->subState->update(input); + if (retSubState == SUBSTATE_RUNNING) { + // keep running. + return SUBSTATE_RUNNING; + } else if (retSubState == SUBSTATE_RETURN) { + this->subState.reset(); + this->state = STATE_BATCH_RESTORE_TITLE_SELECT; + } + } + return SUBSTATE_RUNNING; +} \ No newline at end of file diff --git a/src/menu/BackupSetListState.cpp b/src/menu/BackupSetListState.cpp index ffa51ca..6334284 100644 --- a/src/menu/BackupSetListState.cpp +++ b/src/menu/BackupSetListState.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -16,7 +17,14 @@ int BackupSetListState::scroll = 0; static std::string language; BackupSetListState::BackupSetListState() { - this->sortAscending = BackupSetList::sortAscending; + finalScreen = true; + this->sortAscending = BackupSetList::sortAscending; +} + +BackupSetListState::BackupSetListState(Title *titles, int titlesCount) : + titles(titles), + titlesCount(titlesCount) { + finalScreen = false; } void BackupSetListState::resetCursorPosition() { // if bslist is modified after a new batch Backup @@ -87,7 +95,13 @@ ApplicationState::eSubState BackupSetListState::update(Input *input) { BackupSetList::setBackupSetEntry(cursorPos + scroll); BackupSetList::setBackupSetSubPath(); DrawUtils::setRedraw(true); - return SUBSTATE_RETURN; + if (finalScreen) + return SUBSTATE_RETURN; + else // is a step in batchRestore + { + this->state = STATE_DO_SUBSTATE; + this->subState = std::make_unique(titles, titlesCount); + } } if (input->get(TRIGGER, PAD_BUTTON_Y)) { this->state = STATE_DO_SUBSTATE; diff --git a/src/menu/BatchRestoreOptions.cpp b/src/menu/BatchRestoreOptions.cpp new file mode 100644 index 0000000..3e4a3ce --- /dev/null +++ b/src/menu/BatchRestoreOptions.cpp @@ -0,0 +1,191 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ENTRYCOUNT 5 + +extern Account *sdacc; +extern uint8_t sdaccn; + +BatchRestoreOptions::BatchRestoreOptions(Title *titles, + int titlesCount) : titles(titles), + titlesCount(titlesCount) { + + for (int i = 0; ititlesCount; i++) { + this->titles[i].currentBackup= { + .hasBatchBackup = false, + .candidateToBeRestored = false, + .selected = false, + .hasUserSavedata = false, + .hasCommonSavedata = false, + .batchRestoreState = NOT_TRIED + }; + if (this->titles[i].highID == 0 || this->titles[i].lowID == 0) + continue; + if ((this->titles[i].saveInit == false) || (this->titles[i].isTitleDupe == true && this->titles[i].isTitleOnUSB == false)) + continue; + if (strcmp(this->titles[i].shortName, "DONT TOUCH ME") == 0) // skip CBHC savedata + continue; + uint32_t highID = this->titles[i].highID; + uint32_t lowID = this->titles[i].lowID; + std::string srcPath = getDynamicBackupPath(highID, lowID, 0); + DIR *dir = opendir(srcPath.c_str()); + if (dir != nullptr) { + struct dirent *data; + while ((data = readdir(dir)) != nullptr) { + if(strcmp(data->d_name,".") == 0 || strcmp(data->d_name,"..") == 0 || ! (data->d_type & DT_DIR)) + continue; + if (data->d_name[0] == '8') + batchSDUsers.insert(data->d_name); + this->titles[i].currentBackup.hasBatchBackup=true; + WHBLogPrintf("has backup %d",i); + } + } + closedir(dir); + } + + if (sdacc != nullptr) + free(sdacc); + sdaccn = batchSDUsers.size(); + + int i=0; + sdacc = (Account *) malloc(sdaccn * sizeof(Account)); + for ( auto user : batchSDUsers ) { + strcpy(sdacc[i].persistentID,user.substr(0,8).c_str()); + sdacc[i].pID = strtoul(user.c_str(), nullptr, 16); + sdacc[i].slot = i; + i++; + } +} + +void BatchRestoreOptions::render() { + if (this->state == STATE_DO_SUBSTATE) { + if (this->subState == nullptr) { + OSFatal("SubState was null"); + } + this->subState->render(); + return; + } + + if (this->state == STATE_BATCH_RESTORE_OPTIONS_MENU) { + + consolePrintPos(M_OFF, 3, LanguageUtils::gettext("Select SD user to copy from:")); + if (sduser == -1) + consolePrintPos(M_OFF, 4, " < %s >", LanguageUtils::gettext("all users")); + else + consolePrintPos(M_OFF, 4, " < %s >", getSDacc()[sduser].persistentID); + } + + consolePrintPos(M_OFF, 6 , LanguageUtils::gettext("Select Wii U user to copy to")); + if (this->wiiuuser == -1) + consolePrintPos(M_OFF, 7, " < %s >", LanguageUtils::gettext("same user than in source")); + else + consolePrintPos(M_OFF, 7, " < %s (%s) >", + getWiiUacc()[wiiuuser].miiName, getWiiUacc()[wiiuuser].persistentID); + + if (this->wiiuuser > -1) { + consolePrintPos(M_OFF, 9, LanguageUtils::gettext("Include 'common' save?")); + consolePrintPos(M_OFF, 10, " < %s >", common ? LanguageUtils::gettext("yes") : LanguageUtils::gettext("no ")); + } + + consolePrintPos(M_OFF, 12, LanguageUtils::gettext(" Wipe Target users before restoring: < %s >"), wipeBeforeRestore ? LanguageUtils::gettext("Yes") : LanguageUtils::gettext("No")); + consolePrintPos(M_OFF, 13, LanguageUtils::gettext(" Backup all data before restoring (strongly recommended): < %s >"), fullBackup ? LanguageUtils::gettext("Yes"):LanguageUtils::gettext("No")); + + consolePrintPos(M_OFF, 4 + (cursorPos < 3 ? cursorPos * 3 : cursorPos + 5 ), "\u2192"); + + consolePrintPosAligned(17, 4, 2, LanguageUtils::gettext("\ue000: Ok! Go to Title selection \ue001: Back")); + } + +ApplicationState::eSubState BatchRestoreOptions::update(Input *input) { + if (this->state == STATE_BATCH_RESTORE_OPTIONS_MENU) { + if (input->get(TRIGGER, PAD_BUTTON_A)) { + this->state = STATE_DO_SUBSTATE; + WHBLogPrintf("to title select"); + WHBLogPrintf("sduser %d",sduser); + WHBLogPrintf("wiiuuser %d",wiiuuser); + WHBLogPrintf("titles %u",titles); + this->subState = std::make_unique(sduser, wiiuuser, common, wipeBeforeRestore, fullBackup, this->titles, this->titlesCount); + } + if (input->get(TRIGGER, PAD_BUTTON_B)) + return SUBSTATE_RETURN; + if (input->get(TRIGGER, PAD_BUTTON_X)) { + this->state = STATE_DO_SUBSTATE; + //this->subState = std::make_unique(); + } + if (input->get(TRIGGER, PAD_BUTTON_UP)) { + if (--cursorPos == -1) + ++cursorPos; + if (cursorPos == 2 && sduser == -1 ) + --cursorPos; + } + if (input->get(TRIGGER, PAD_BUTTON_DOWN)) { + if (++cursorPos == ENTRYCOUNT) + --cursorPos; + if (cursorPos == 2 && sduser == -1 ) + ++cursorPos; + } + if (input->get(TRIGGER, PAD_BUTTON_LEFT)) { + switch (cursorPos) { + case 0: + sduser = ((sduser == -1) ? -1 : (sduser - 1)); + this->wiiuuser = ((sduser == -1) ? -1 : this->wiiuuser); + break; + case 1: + wiiuuser = (((wiiuuser == -1) || (sduser == -1)) ? -1 : (wiiuuser - 1)); + wiiuuser = ((sduser > -1) && (wiiuuser == -1)) ? 0 : wiiuuser; + break; + case 2: + common = common ? false : true; + break; + case 3: + wipeBeforeRestore = wipeBeforeRestore ? false : true; + break; + case 4: + fullBackup = fullBackup ? false : true; + break; + default: + break; + } + } + if (input->get(TRIGGER, PAD_BUTTON_RIGHT)) { + switch (cursorPos) { + case 0: + sduser = ((sduser == (getSDaccn() - 1)) ? (getSDaccn() - 1) : (sduser + 1)); + wiiuuser = ((sduser > -1) && (wiiuuser == -1)) ? 0 : wiiuuser; + break; + case 1: + wiiuuser = ((wiiuuser == (getWiiUaccn() - 1)) ? (getWiiUaccn() - 1) : (wiiuuser + 1)); + wiiuuser = (sduser == -1) ? -1 : wiiuuser; + break; + case 2: + common = common ? false : true; + break; + case 3: + wipeBeforeRestore = wipeBeforeRestore ? false : true; + break; + case 4: + fullBackup = fullBackup ? false : true; + break; + default: + break; + } + } + } else if (this->state == STATE_DO_SUBSTATE) { + auto retSubState = this->subState->update(input); + if (retSubState == SUBSTATE_RUNNING) { + // keep running. + return SUBSTATE_RUNNING; + } else if (retSubState == SUBSTATE_RETURN) { + this->subState.reset(); + this->state = STATE_BATCH_RESTORE_OPTIONS_MENU; + } + } + return SUBSTATE_RUNNING; +} \ No newline at end of file diff --git a/src/menu/BatchRestoreState.cpp b/src/menu/BatchRestoreState.cpp new file mode 100644 index 0000000..986f6bc --- /dev/null +++ b/src/menu/BatchRestoreState.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +#include +#include + +#define ENTRYCOUNT 2 + +static int cursorPos = 0; + +void BatchRestoreState::render() { + if (this->state == STATE_DO_SUBSTATE) { + if (this->subState == nullptr) { + OSFatal("SubState was null"); + } + this->subState->render(); + return; + } + if (this->state == STATE_BATCH_RESTORE_MENU) { + consolePrintPosAligned(0, 4, 1,LanguageUtils::gettext("Batch Restore")); + consolePrintPos(M_OFF, 4, LanguageUtils::gettext(" Batch Restore is .... \n bla bla bla")); + consolePrintPos(M_OFF, 8, LanguageUtils::gettext(" Restore Wii U (%u Title%s)"), this->wiiuTitlesCount, + (this->wiiuTitlesCount > 1) ? "s" : ""); + consolePrintPos(M_OFF, 9, LanguageUtils::gettext(" Restore vWii (%u Title%s)"), this->vWiiTitlesCount, + (this->vWiiTitlesCount > 1) ? "s" : ""); + consolePrintPos(M_OFF, 8 + cursorPos, "\u2192"); + consolePrintPosAligned(17, 4, 2, LanguageUtils::gettext("\ue000: Continue to batch restore \ue001: Back")); + } +} + +ApplicationState::eSubState BatchRestoreState::update(Input *input) { + if (this->state == STATE_BATCH_RESTORE_MENU) { + if (input->get(TRIGGER, PAD_BUTTON_A)) { + const std::string batchDatetime = getNowDateForFolder(); + switch (cursorPos) { + case 0: + this->state = STATE_DO_SUBSTATE; + this->subState = std::make_unique(this->wiiutitles, this->wiiuTitlesCount); + break; + case 1: + this->state = STATE_DO_SUBSTATE; + this->subState = std::make_unique(this->wiititles, this->vWiiTitlesCount); + break; + default: + return SUBSTATE_RUNNING; + } + } + if (input->get(TRIGGER, PAD_BUTTON_B)) + return SUBSTATE_RETURN; + if (input->get(TRIGGER, PAD_BUTTON_UP)) + if (--cursorPos == -1) + ++cursorPos; + if (input->get(TRIGGER, PAD_BUTTON_DOWN)) + if (++cursorPos == ENTRYCOUNT) + --cursorPos; + } else if (this->state == STATE_DO_SUBSTATE) { + auto retSubState = this->subState->update(input); + if (retSubState == SUBSTATE_RUNNING) { + // keep running. + return SUBSTATE_RUNNING; + } else if (retSubState == SUBSTATE_RETURN) { + this->subState.reset(); + this->state = STATE_BATCH_RESTORE_MENU; + } + } + return SUBSTATE_RUNNING; +} \ No newline at end of file diff --git a/src/menu/MainMenuState.cpp b/src/menu/MainMenuState.cpp index 47be850..ea402a7 100644 --- a/src/menu/MainMenuState.cpp +++ b/src/menu/MainMenuState.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -8,10 +9,10 @@ #include #include -#include +#include #include -#define ENTRYCOUNT 3 +#define ENTRYCOUNT 4 static int cursorPos = 0; @@ -29,6 +30,7 @@ void MainMenuState::render() { consolePrintPos(M_OFF, 3, LanguageUtils::gettext(" vWii Save Management (%u Title%s)"), this->vWiiTitlesCount, (this->vWiiTitlesCount > 1) ? "s" : ""); consolePrintPos(M_OFF, 4, LanguageUtils::gettext(" Batch Backup")); + consolePrintPos(M_OFF, 5, LanguageUtils::gettext(" Batch Restore")); consolePrintPos(M_OFF, 2 + cursorPos, "\u2192"); consolePrintPos(M_OFF, 10, "tag: %s",tag.c_str()); consolePrintPosAligned(17, 4, 2, LanguageUtils::gettext("\uE002: Options \ue000: Select Mode")); @@ -51,6 +53,10 @@ ApplicationState::eSubState MainMenuState::update(Input *input) { this->state = STATE_DO_SUBSTATE; this->subState = std::make_unique(this->wiiutitles, this->wiititles, this->wiiuTitlesCount, this->vWiiTitlesCount); break; + case 3: + this->state = STATE_DO_SUBSTATE; + this->subState = std::make_unique(this->wiiutitles, this->wiititles, this->wiiuTitlesCount, this->vWiiTitlesCount); + break; default: break; } diff --git a/src/savemng.cpp b/src/savemng.cpp index 5ece898..b9b628f 100644 --- a/src/savemng.cpp +++ b/src/savemng.cpp @@ -12,6 +12,9 @@ #include #include +#include +#include + #define __FSAShimSend ((FSError(*)(FSAShimBuffer *, uint32_t))(0x101C400 + 0x042d90)) #define IO_MAX_FILE_BUFFER (1024 * 1024) // 1 MB @@ -209,10 +212,10 @@ int32_t loadTitleIcon(Title *title) { return -23; } -static bool folderEmpty(const char *fPath) { +bool folderEmpty(const char *fPath) { DIR *dir = opendir(fPath); if (dir == nullptr) - return false; + return true; // if empty or non-existant, return true. bool empty = true; struct dirent *data; @@ -1063,20 +1066,76 @@ void backupSavedata(Title *title, uint8_t slot, int8_t wiiuuser, bool common) { } -void restoreSavedata(Title *title, uint8_t slot, int8_t sduser, int8_t wiiuuser, bool common) { - if (isSlotEmpty(title->highID, title->lowID, slot)) { - promptError(LanguageUtils::gettext("No backup found on selected slot.")); - return; +/* + bool copyDir_c = true; + bool checkEntry_b = true; + bool copyDir_b = true; + bool copyDir2_b = true; +*/ + bool saveinit = true; + + +static bool copyDir_c(const std::string &pPath, const std::string &tPath) { // Source: ft2sd + WHBLogPrintf("copy %s %s",pPath.c_str(),tPath.c_str()); + return true; +} + +int checkEntry_b(const char *fPath) { + WHBLogPrintf("checkEntry %s",fPath); + return 2; +} + +static bool copyDir_b(const std::string &pPath, const std::string &tPath) { // Source: ft2sd + WHBLogPrintf("copy %s %s",pPath.c_str(),tPath.c_str()); + return true; +} + +static bool copyDir2_b(const std::string &pPath, const std::string &tPath) { // Source: ft2sd + WHBLogPrintf("copy %s %s",pPath.c_str(),tPath.c_str()); + return true; +} + + +/* + bool removeDir_c = true; + bool removeDir_b = true; +*/ + bool unlink_c = true; + bool unlink_b=true; + +static bool removeDir_c(const std::string &pPath) { + WHBLogPrintf("removeDir %s",pPath.c_str()); + return true; +} + + +static bool removeDir_b(const std::string &pPath) { + WHBLogPrintf("removeDir %s",pPath.c_str()); + return true; +} + +int restoreSavedata(Title *title, uint8_t slot, int8_t sduser, int8_t wiiuuser, bool common, bool interactive /*= true*/) { + int errorCode = 0; + char shortName[256]; + if (strlen(title->shortName) != 0u) + strcpy(shortName,title->shortName); + else + sprintf(shortName,"%08x-%08x", title->highID,title->lowID); + if (isSlotEmpty(title->highID, title->lowID, slot)) { + promptError(LanguageUtils::gettext("%s\nNo backup found on selected slot."),shortName); + return -2; + } + if (interactive) { + if (!promptConfirm(ST_WARNING, LanguageUtils::gettext("Are you sure?"))) + return -1; + // individual backups always to ROOT backupSet + BackupSetList::saveBackupSetSubPath(); + BackupSetList::setBackupSetSubPathToRoot(); + int slotb = getEmptySlot(title->highID, title->lowID); + if ((slotb >= 0) && promptConfirm(ST_YES_NO, LanguageUtils::gettext("Backup current savedata first to next empty slot?"))) + backupSavedata(title, slotb, wiiuuser, common); + BackupSetList::restoreBackupSetSubPath(); } - if (!promptConfirm(ST_WARNING, LanguageUtils::gettext("Are you sure?"))) - return; - // backups to ROOT backupSet - BackupSetList::saveBackupSetSubPath(); - BackupSetList::setBackupSetSubPathToRoot(); - int slotb = getEmptySlot(title->highID, title->lowID); - if ((slotb >= 0) && promptConfirm(ST_YES_NO, LanguageUtils::gettext("Backup current savedata first to next empty slot?"))) - backupSavedata(title, slotb, wiiuuser, common); - BackupSetList::restoreBackupSetSubPath(); uint32_t highID = title->highID; uint32_t lowID = title->lowID; bool isUSB = title->isTitleOnUSB; @@ -1085,9 +1144,83 @@ void restoreSavedata(Title *title, uint8_t slot, int8_t sduser, int8_t wiiuuser, srcPath = getDynamicBackupPath(highID, lowID, slot); const std::string path = (isWii ? "storage_slccmpt01:/title" : (isUSB ? (getUSB() + "/usr/save").c_str() : "storage_mlc01:/usr/save")); std::string dstPath = StringUtils::stringFormat("%s/%08x/%08x/%s", path.c_str(), highID, lowID, isWii ? "data" : "user"); + createFolderUnlocked(dstPath); + std::string srcCommonPath = srcPath + "/common"; + std::string dstCommonPath = dstPath + "/common"; + bool commonSaved = false; + bool doBase; + bool doCommon; + bool singleUser = false; + + if ( isWii ) { + doBase = true; + doCommon = false; + } + else + { + switch (sduser) { + case -2: // no usser + doBase = false; + doCommon = common; + break; + case -1: // allusers + doBase = true; + doCommon = false; + break; + default: // wiiuuser = 0 .. n + doBase = true; + doCommon = common; + singleUser = true; + srcPath.append(StringUtils::stringFormat("/%s", sdacc[sduser].persistentID)); + dstPath.append(StringUtils::stringFormat("/%s", wiiuacc[wiiuuser].persistentID)); + break; + } + } + + if (doCommon) { + //FSAMakeQuota(handle, newlibtoFSA(dstCommonPath).c_str(), 0x666, title->accountSaveSize); + //if (copyDir(srcCommonPath, dstCommonPath)) + if (copyDir_c(srcCommonPath, dstCommonPath)) + commonSaved = true; + else { + promptError(LanguageUtils::gettext("Common save not restored.")); + errorCode = 1; + } + } + + if (doBase) { + if (singleUser) + { + //if (checkEntry(srcPath.c_str()) == 2) { + if (checkEntry_b(srcPath.c_str()) == 2) { + //FSAMakeQuota(handle, newlibtoFSA(dstPath).c_str(), 0x666, title->accountSaveSize); + //if (!copyDir(srcPath, dstPath)) { + if (!copyDir_b(srcPath, dstPath)) { + promptError(LanguageUtils::gettext("Restore failed.")); + errorCode += 2; + } + } + else + if (!commonSaved) + promptError(LanguageUtils::gettext("No save found for this user.")); + } + else + { + //FSAMakeQuotaFromDir(srcPath.c_str(), dstPath.c_str(), title->accountSaveSize); + //if (!copyDir(srcPath, dstPath)) { + if (!copyDir2_b(srcPath, dstPath)) { + promptError(LanguageUtils::gettext("Restore failed.")); + errorCode += 4; + } + } + + } + +// Original code +/* if ((sduser > -1) && !isWii) { if (common) { FSAMakeQuota(handle, newlibtoFSA(dstPath + "/common").c_str(), 0x666, title->accountSaveSize); @@ -1106,23 +1239,22 @@ void restoreSavedata(Title *title, uint8_t slot, int8_t sduser, int8_t wiiuuser, else if (!commonSaved) promptError(LanguageUtils::gettext("No save found for this user.")); - } else { FSAMakeQuotaFromDir(srcPath.c_str(), dstPath.c_str(), title->accountSaveSize); if (!copyDir(srcPath, dstPath)) promptError(LanguageUtils::gettext("Restore failed.")); } - +*/ - - - if (!title->saveInit && !isWii) { + //if (!title->saveInit && !isWii) { + if (!saveinit) { std::string userPath = StringUtils::stringFormat("%s/%08x/%08x/user", path.c_str(), highID, lowID); FSAShimBuffer *shim = (FSAShimBuffer *) memalign(0x40, sizeof(FSAShimBuffer)); if (!shim) { - return; + errorCode +=8; + return errorCode; } shim->clientHandle = handle; @@ -1154,23 +1286,88 @@ void restoreSavedata(Title *title, uint8_t slot, int8_t sduser, int8_t wiiuuser, } else if (dstPath.rfind("storage_usb02:", 0) == 0) { FSAFlushVolume(handle, "/vol/storage_usb02"); } + return errorCode; } -void wipeSavedata(Title *title, int8_t wiiuuser, bool common) { - if (!promptConfirm(ST_WARNING, LanguageUtils::gettext("Are you sure?")) || !promptConfirm(ST_WARNING, LanguageUtils::gettext("Hm, are you REALLY sure?"))) - return; - int slotb = getEmptySlot(title->highID, title->lowID); - if ((slotb >= 0) && promptConfirm(ST_YES_NO, LanguageUtils::gettext("Backup current savedata first?"))) - backupSavedata(title, slotb, wiiuuser, common); +int wipeSavedata(Title *title, int8_t wiiuuser, bool common, bool interactive /*= true*/) { + int errorCode = 0; + if (interactive) { + if (!promptConfirm(ST_WARNING, LanguageUtils::gettext("Are you sure?")) || !promptConfirm(ST_WARNING, LanguageUtils::gettext("Hm, are you REALLY sure?"))) + return -1; + int slotb = getEmptySlot(title->highID, title->lowID); + if ((slotb >= 0) && promptConfirm(ST_YES_NO, LanguageUtils::gettext("Backup current savedata first?"))) + backupSavedata(title, slotb, wiiuuser, common); + } uint32_t highID = title->highID; uint32_t lowID = title->lowID; bool isUSB = title->isTitleOnUSB; bool isWii = ((highID & 0xFFFFFFF0) == 0x00010000); std::string srcPath; - std::string origPath; + std::string commonPath; std::string path; path = (isWii ? "storage_slccmpt01:/title" : (isUSB ? (getUSB() + "/usr/save") : "storage_mlc01:/usr/save")); srcPath = StringUtils::stringFormat("%s/%08x/%08x/%s", path.c_str(), highID, lowID, isWii ? "data" : "user"); + commonPath = srcPath + "/common"; + + bool doBase; + bool doCommon; + + if ( isWii ) { + doBase = true; + doCommon = false; + } + else + { + switch (wiiuuser) { + case -2: // no usser + doBase = false; + doCommon = common; + break; + case -1: // allusers + doBase = true; + doCommon = false; + break; + default: // wiiuuser = 0 .. n + doBase = true; + doCommon = common; + srcPath += "/" + std::string(wiiuacc[wiiuuser].persistentID); + break; + } + } + + if (doCommon) { + //if (!removeDir(commonPath)) { + if (!removeDir_c(commonPath)) { + promptError(LanguageUtils::gettext("Common save not found.")); + errorCode = 1; + } + //if (unlink(commonPath.c_str()) == -1) { + if (!unlink_c) { + promptError(LanguageUtils::gettext("Failed to delete common folder: %s"), strerror(errno)); + errorCode += 2; + } + } + + if (doBase) { + //if (checkEntry(srcPath.c_str()) == 2) { + if (checkEntry_b(srcPath.c_str()) == 2) { + //if (!removeDir(srcPath)) { + if (!removeDir_b(srcPath)) { + promptError(LanguageUtils::gettext("Failed to delete savefile.")); + errorCode += 4; + } + if ((wiiuuser > -1) && !isWii) { + //if (unlink(srcPath.c_str()) == -1) { + if (!unlink_b) { + promptError(LanguageUtils::gettext("Failed to delete user folder: %s"), strerror(errno)); + errorCode += 8; + } + } + } + } + + /* + if ((wiiuuser > -1) && !isWii) { if (common) { origPath = srcPath + "/common"; @@ -1190,6 +1387,7 @@ void wipeSavedata(Title *title, int8_t wiiuuser, bool common) { promptError(LanguageUtils::gettext("Failed to delete user folder: %s"), strerror(errno)); } } + */ std::string volPath; if (srcPath.find("_usb01") != std::string::npos) { @@ -1202,6 +1400,7 @@ void wipeSavedata(Title *title, int8_t wiiuuser, bool common) { volPath = "/vol/storage_slccmpt01"; } FSAFlushVolume(handle, volPath.c_str()); + return errorCode; } void importFromLoadiine(Title *title, bool common, int version) { diff --git a/src/utils/DrawUtils.cpp b/src/utils/DrawUtils.cpp index 0149513..d386495 100644 --- a/src/utils/DrawUtils.cpp +++ b/src/utils/DrawUtils.cpp @@ -14,6 +14,9 @@ #include #include +#include +#include + // buffer width #define TV_WIDTH 0x500 #define DRC_WIDTH 0x380 @@ -306,12 +309,18 @@ void DrawUtils::print(uint32_t x, uint32_t y, const wchar_t *string, bool alignR return; } + + if (*string == '\n') { - penY += mtx.minHeight; + WHBLogPrintf("minHeight: %d",mtx.minHeight); + //penY += mtx.minHeight; + penY += 40; // temporal - penX = x; continue; } + + textureWidth = (mtx.minWidth + 3) & ~3; textureHeight = mtx.minHeight;