diff --git a/meta/meta.xml b/meta/meta.xml index 2943ec7..80b0b96 100644 --- a/meta/meta.xml +++ b/meta/meta.xml @@ -2,7 +2,7 @@ SaveMii Ryuzaki_MrL - 1.0.0 + 1.1.0 20170404000000 WiiU/vWii Save Manager WiiU/vWii Save Manager diff --git a/src/lib_easy.c b/src/lib_easy.c index 37ed017..c60be7c 100644 --- a/src/lib_easy.c +++ b/src/lib_easy.c @@ -69,53 +69,6 @@ void uprintf(int x, int y, const char* format, ...) { curr_line=curr_line_tmp; } -void pause(int showmsg) { - while(1) { - if (showmsg) { - OSScreenClearBufferEx(0, 0); - OSScreenClearBufferEx(1, 0); - console_print_pos(0, curr_line, "Press any key to continue...\n"); - OSScreenFlipBuffersEx(0); - OSScreenFlipBuffersEx(1); - } - updatePressedButtons(); - if (isPressed(0xFFFF)) break; - } -} - -int promptConfirm(const char* question) { - ucls(); - const char* msg = "(A) Confirm - (B) Cancel"; - int ret = 0; - while(1) { - OSScreenClearBufferEx(0, 0); - OSScreenClearBufferEx(1, 0); - console_print_pos(25 - (strlen(question)>>1), 8, question); - console_print_pos(25 - (strlen(msg)>>1), 10, msg); - OSScreenFlipBuffersEx(0); - OSScreenFlipBuffersEx(1); - updatePressedButtons(); - if (isPressed(VPAD_BUTTON_A | VPAD_BUTTON_B | VPAD_BUTTON_HOME)) { - ret = isPressed(VPAD_BUTTON_A); - break; - } - } - return ret; -} - -void promptError(const char* message) { - ucls(); - while(1) { - OSScreenClearBufferEx(0, 0); - OSScreenClearBufferEx(1, 0); - console_print_pos(25 - (strlen(message)>>1), 9, message); - OSScreenFlipBuffersEx(0); - OSScreenFlipBuffersEx(1); - updatePressedButtons(); - if (isPressed(0xFFFF)) break; - } -} - int64_t uGetTime() { return OSGetTime()/SECS_TO_TICKS(1); } diff --git a/src/lib_easy.h b/src/lib_easy.h index 085ec47..0df8ebc 100644 --- a/src/lib_easy.h +++ b/src/lib_easy.h @@ -50,9 +50,6 @@ void ucls(); void ScreenInit(); void flipBuffers(); void uprintf(int x, int y, const char* format, ...); -void pause(int showmsg); -int promptConfirm(const char* question); -void promptError(const char* message); int64_t uGetTime(); void updatePressedButtons(); void updateHeldButtons(); diff --git a/src/main.c b/src/main.c index d5d3c8e..b298f7b 100644 --- a/src/main.c +++ b/src/main.c @@ -10,24 +10,17 @@ #include "savemng.h" #define VERSION_MAJOR 1 -#define VERSION_MINOR 0 +#define VERSION_MINOR 1 #define VERSION_MICRO 0 u8 slot = 0; +bool allusers = 0, common = 1; int menu = 0, mode = 0, task = 0, targ = 0; int cursor = 0, scroll = 0; int titleswiiu = 0, titlesvwii = 0; -typedef struct { - u32 highID; - u32 lowID; - char shortname[256]; - bool isTitleOnUSB; -} Title; - //just to be able to call async -void someFunc(void *arg) -{ +void someFunc(void *arg) { (void)arg; } @@ -59,6 +52,7 @@ void MCPHookClose() { Title* loadWiiUTitles() { + // Source: haxchi installer int mcp_handle = MCP_Open(); int count = MCP_TitleCount(mcp_handle); int listSize = count*0x61; @@ -79,7 +73,7 @@ Title* loadWiiUTitles() { char* element = tList+(i*0x61); u32 highID = *(u32*)(element), lowID = *(u32*)(element+4); if (highID!=0x00050000) continue; - bool isTitleOnUSB = memcmp(element+0x56,"mlc",4)!=0; + bool isTitleOnUSB = (memcmp(element+0x56,"usb",4)==0); char path[255]; memset(path, 0, 255); sprintf(path, "storage_%s:/usr/title/%08x/%08x/meta/meta.xml", isTitleOnUSB ? "usb" : "mlc", highID, lowID); @@ -101,11 +95,14 @@ Title* loadWiiUTitles() { for (cur_node = root_element->children; cur_node; cur_node = cur_node->next) { if ( (cur_node->type != XML_ELEMENT_NODE) || - (memcmp(cur_node->name, "shortname_en", 13) != 0) || (xmlNodeGetContent(cur_node) == NULL) || (!strlen((char*)xmlNodeGetContent(cur_node))) ) continue; - strcpy(titles[titleswiiu].shortname, (char*)xmlNodeGetContent(cur_node)); + + if (!memcmp(cur_node->name, "shortname_en", 13)) + strcpy(titles[titleswiiu].shortName, (char*)xmlNodeGetContent(cur_node)); + if (!memcmp(cur_node->name, "product_code", 13)) + strcpy(titles[titleswiiu].productCode, (char*)(xmlNodeGetContent(cur_node)+6)); } xmlFreeDoc(tmp); @@ -133,8 +130,8 @@ Title* loadWiiUTitles() { Title* loadWiiTitles() { - struct dirent *dirent = NULL; - DIR *dir = NULL; + struct dirent* dirent = NULL; + DIR* dir = NULL; dir = opendir("slccmpt01:/title/00010000"); if (dir == NULL) { @@ -168,7 +165,7 @@ Title* loadWiiTitles() { fread(bnrBuf, 0x02, 0x20, bnrFile); fclose(bnrFile); for (int j = 0, k = 0; j < 0x20; j++) { - titles[i].shortname[k++] = (char)bnrBuf[j]; + titles[i].shortName[k++] = (char)bnrBuf[j]; } free(bnrBuf); } @@ -192,7 +189,7 @@ Title* loadWiiTitles() { } void unloadTitles(Title* titles) { - free(titles); + if (titles) free(titles); } /* Entry point */ @@ -206,6 +203,7 @@ int Menu_Main(void) { } if (res < 0) { promptError("IOSUHAX_Open failed."); + unmount_sd_fat("sd"); return EXIT_SUCCESS; } @@ -214,6 +212,7 @@ int Menu_Main(void) { int fsaFd = IOSUHAX_FSA_Open(); if (fsaFd < 0) { promptError("IOSUHAX_FSA_Open failed."); + unmount_sd_fat("sd"); if (mcp_hook_fd >= 0) MCPHookClose(); else IOSUHAX_Close(); return EXIT_SUCCESS; @@ -226,8 +225,9 @@ int Menu_Main(void) { ucls(); Title* wiiutitles = loadWiiUTitles(); Title* wiititles = loadWiiTitles(); + int* versionList = (int*)malloc(0x100*sizeof(int)); - while(1) { + while(wiiutitles!=NULL && wiititles!=NULL) { OSScreenClearBufferEx(0, 0); OSScreenClearBufferEx(1, 0); @@ -237,29 +237,46 @@ int Menu_Main(void) { Title* titles = mode ? wiititles : wiiutitles; int count = mode ? titlesvwii : titleswiiu; + int entrycount = 0; switch(menu) { case 0: { // Main Menu + entrycount = 2; console_print_pos(0, 2, " Wii U Save Management"); console_print_pos(0, 3, " vWii Save Management"); console_print_pos(0, 2 + cursor, "->"); } break; - case 1: { // WiiU/vWii Save Management - console_print_pos(0, 2, " Backup savedata"); - console_print_pos(0, 3, " Restore savedata"); - // console_print_pos(0, 4, " Wipe savedata"); - console_print_pos(0, 2 + cursor, "->"); - } break; - case 2: { // Wii/Wii U Title List + case 1: { // Select Title + entrycount = count; for (int i = 0; i < 14; i++) { if (i+scroll<0 || i+scroll>=count) break; - if (strlen(titles[i+scroll].shortname)) console_print_pos(0, i+2, " %s", titles[i+scroll].shortname); + if (strlen(titles[i+scroll].shortName)) console_print_pos(0, i+2, " %s", titles[i+scroll].shortName); else console_print_pos(0, i+2, " %08lx%08lx", titles[i+scroll].highID, titles[i+scroll].lowID); } console_print_pos(0, 2 + cursor, "->"); } break; - case 3: { // Slot select - console_print_pos(0, 2, "Press LEFT or RIGHT to select slot."); - console_print_pos(0, 3, " < %03u >", slot); + case 2: { // Select Task + entrycount = 3 + 2*(mode==0); + console_print_pos(0, 2, " Backup savedata"); + console_print_pos(0, 3, " Restore savedata"); + console_print_pos(0, 4, " Wipe savedata"); + if (mode==0) { + console_print_pos(0, 5, " Import from loadiine"); + console_print_pos(0, 6, " Export to loadiine"); + } + console_print_pos(0, 2 + cursor, "->"); + } break; + case 3: { // Select Options + entrycount = 1 + 2*(mode==0); + console_print_pos(0, 2, "Select %s:", task>2 ? "version" : "slot"); + if (task > 2) console_print_pos(0, 3, " < v%u >", versionList ? versionList[slot] : 0); + else console_print_pos(0, 3, " < %03u >", slot); + if (mode==0) { + console_print_pos(0, 5, "Select user:"); + console_print_pos(0, 6, " < %s >", (allusers&&(task<3)) ? "all users" : "this user"); + console_print_pos(0, 8, "Include 'common' save?"); + console_print_pos(0, 9, " < %s >", common ? "yes" : "no "); + console_print_pos(0, 3 + cursor*3, "->"); + } } break; } @@ -272,8 +289,6 @@ int Menu_Main(void) { updatePressedButtons(); updateHeldButtons(); - int entrycount = ((menu==0) ? 2 : ((menu==1) ? 2 : count)); - if (isPressed(VPAD_BUTTON_DOWN) || isHeld(VPAD_BUTTON_DOWN)) { if (entrycount<=14) cursor = (cursor + 1) % entrycount; else if (cursor < 6) cursor++; @@ -289,10 +304,22 @@ int Menu_Main(void) { } if (isPressed(VPAD_BUTTON_LEFT) || isHeld(VPAD_BUTTON_LEFT)) { - if (menu==3) slot--; + if (menu==3) { + switch(cursor) { + case 0: slot--; break; + case 1: allusers^=1; break; + case 2: common^=1; break; + } + } usleep(100000); } else if (isPressed(VPAD_BUTTON_RIGHT) || isHeld(VPAD_BUTTON_RIGHT)) { - if (menu==3) slot++; + if (menu==3) { + switch(cursor) { + case 0: slot++; break; + case 1: allusers^=1; break; + case 2: common^=1; break; + } + } usleep(100000); } @@ -300,20 +327,30 @@ int Menu_Main(void) { ucls(); if (menu<3) { if (menu==0) mode = cursor; - if (menu==1) task = cursor; - if (menu==2) targ = cursor+scroll; + if (menu==1) targ = cursor+scroll; + if (menu==2) { + task = cursor; + if (task > 2) { + char gamePath[PATH_SIZE]; + memset(versionList, 0, 0x100*sizeof(int)); + if (getLoadiineGameSaveDir(gamePath, titles[targ].productCode)==0) + getLoadiineSaveVersionList(versionList, gamePath); + } + } menu++; cursor = scroll = 0; } else { switch(task) { - case 0: backupSavedata(titles[targ].highID, titles[targ].lowID, titles[targ].isTitleOnUSB, slot); break; - case 1: restoreSavedata(titles[targ].highID, titles[targ].lowID, titles[targ].isTitleOnUSB, slot); break; - case 2: wipeSavedata(titles[targ].highID, titles[targ].lowID, titles[targ].isTitleOnUSB); break; + case 0: backupSavedata(&titles[targ], slot, allusers, common); break; + case 1: restoreSavedata(&titles[targ], slot, allusers, common); break; + case 2: wipeSavedata(&titles[targ], allusers, common); break; + case 3: importFromLoadiine(&titles[targ], common, versionList ? versionList[slot] : 0); break; + case 4: exportToLoadiine(&titles[targ], common, versionList ? versionList[slot] : 0); break; } } - } else if (isPressed(VPAD_BUTTON_B)) { + } else if (isPressed(VPAD_BUTTON_B) && menu>0) { ucls(); - if (menu>0) menu--; + menu--; cursor = scroll = 0; } @@ -323,6 +360,7 @@ int Menu_Main(void) { unloadTitles(wiiutitles); unloadTitles(wiititles); + free(versionList); fatUnmount("sd"); fatUnmount("usb"); diff --git a/src/savemng.c b/src/savemng.c index 2c5accf..0613623 100644 --- a/src/savemng.c +++ b/src/savemng.c @@ -2,15 +2,15 @@ #define BUFFER_SIZE 0x80000 -void console_print_pos(int x, int y, const char *format, ...) { +void console_print_pos(int x, int y, const char* format, ...) { - char * tmp = NULL; + char* tmp = NULL; va_list va; va_start(va, format); if ((vasprintf(&tmp, format, va) >= 0) && tmp) { - if(strlen(tmp) > 79) tmp[79] = 0; + if (strlen(tmp) > 79) tmp[79] = 0; OSScreenPutFontEx(0, x, y, tmp); OSScreenPutFontEx(1, x, y, tmp); @@ -18,30 +18,64 @@ void console_print_pos(int x, int y, const char *format, ...) { } va_end(va); - if(tmp) free(tmp); + if (tmp) free(tmp); } -int DumpFile(char *pPath, const char * output_path) { +bool promptConfirm(const char* question) { + ucls(); + const char* msg = "(A) Confirm - (B) Cancel"; + int ret = 0; + while(1) { + OSScreenClearBufferEx(0, 0); + OSScreenClearBufferEx(1, 0); + console_print_pos(25 - (strlen(question)>>1), 8, question); + console_print_pos(25 - (strlen(msg)>>1), 10, msg); + OSScreenFlipBuffersEx(0); + OSScreenFlipBuffersEx(1); + updatePressedButtons(); + if (isPressed(VPAD_BUTTON_A | VPAD_BUTTON_B | VPAD_BUTTON_HOME)) { + ret = isPressed(VPAD_BUTTON_A); + break; + } + } + return ret; +} - char *pFilename = strrchr(pPath, '/'); - if(!pFilename) pFilename = pPath; +void promptError(const char* message) { + ucls(); + while(1) { + OSScreenClearBufferEx(0, 0); + OSScreenClearBufferEx(1, 0); + console_print_pos(25 - (strlen(message)>>1), 9, message); + OSScreenFlipBuffersEx(0); + OSScreenFlipBuffersEx(1); + updatePressedButtons(); + if (isPressed(0xFFFF)) break; + } +} + +// Source: ft2sd +int DumpFile(char* pPath, const char* output_path) { + + char* pFilename = strrchr(pPath, '/'); + if (!pFilename) pFilename = pPath; else pFilename++; unsigned char* dataBuf = (unsigned char*)memalign(0x40, BUFFER_SIZE); - if(!dataBuf) { + if (!dataBuf) { promptError("Out of memory."); return -1; } - FILE *pReadFile = fopen(pPath, "rb"); - if(!pReadFile) { + FILE* pReadFile = fopen(pPath, "rb"); + if (!pReadFile) { promptError("Failed to open file."); return -2; } - FILE *pWriteFile = fopen(output_path, "wb"); - if(!pWriteFile) { + FILE* pWriteFile = fopen(output_path, "wb"); + if (!pWriteFile) { promptError("Failed to create file."); fclose(pReadFile); return -3; @@ -50,7 +84,6 @@ int DumpFile(char *pPath, const char * output_path) { unsigned int size = 0; unsigned int ret; - // Copy rpl in memory while ((ret = fread(dataBuf, 0x1, BUFFER_SIZE, pReadFile)) > 0) { fwrite(dataBuf, 0x01, ret, pWriteFile); size += ret; @@ -64,10 +97,11 @@ int DumpFile(char *pPath, const char * output_path) { } -int DumpDir(char *pPath, const char * target_path) { +// Source: ft2sd +int DumpDir(char* pPath, const char* target_path) { - struct dirent *dirent = NULL; - DIR *dir = NULL; + struct dirent* dirent = NULL; + DIR* dir = NULL; dir = opendir(pPath); if (dir == NULL) { @@ -82,14 +116,14 @@ int DumpDir(char *pPath, const char * target_path) { OSScreenClearBufferEx(0, 0); OSScreenClearBufferEx(1, 0); - if(strcmp(dirent->d_name, "..") == 0 || strcmp(dirent->d_name, ".") == 0) continue; + if (strcmp(dirent->d_name, "..") == 0 || strcmp(dirent->d_name, ".") == 0) continue; int len = strlen(pPath); snprintf(pPath + len, FS_MAX_FULLPATH_SIZE - len, "/%s", dirent->d_name); - if(dirent->d_type & DT_DIR) { + if (dirent->d_type & DT_DIR) { - char *targetPath = (char*)malloc(FS_MAX_FULLPATH_SIZE); + char* targetPath = (char*)malloc(FS_MAX_FULLPATH_SIZE); snprintf(targetPath, FS_MAX_FULLPATH_SIZE, "%s/%s", target_path, dirent->d_name); CreateSubfolder(targetPath); @@ -98,7 +132,7 @@ int DumpDir(char *pPath, const char * target_path) { } else { - char *targetPath = (char*)malloc(FS_MAX_FULLPATH_SIZE); + char* targetPath = (char*)malloc(FS_MAX_FULLPATH_SIZE); snprintf(targetPath, FS_MAX_FULLPATH_SIZE, "%s/%s", target_path, dirent->d_name); console_print_pos(0, 0, "Copying file %s", dirent->d_name); @@ -123,28 +157,265 @@ int DumpDir(char *pPath, const char * target_path) { } -void backupSavedata(u32 highID, u32 lowID, bool isUSB, int slot) { +int DeleteDir(char* pPath) { - char srcPath[256]; - char dstPath[256]; - const char* path = ((highID==0x00010000) ? "slccmpt01:/title" : (isUSB ? "storage_usb:/usr/save" : "storage_mlc:/usr/save")); - sprintf(srcPath, "%s/%08x/%08x/%s", path, highID, lowID, (highID==0x00010000) ? "data" : "user"); - sprintf(dstPath, "sd:/wiiu/apps/savemii/backups/%08x%08x/%i", highID, lowID, slot); + struct dirent* dirent = NULL; + DIR* dir = NULL; + + dir = opendir(pPath); + if (dir == NULL) { + promptError("Failed to open directory."); + return -1; + } + + while ((dirent = readdir(dir)) != 0) { + + OSScreenClearBufferEx(0, 0); + OSScreenClearBufferEx(1, 0); + + if (strcmp(dirent->d_name, "..") == 0 || strcmp(dirent->d_name, ".") == 0) continue; + + int len = strlen(pPath); + snprintf(pPath + len, FS_MAX_FULLPATH_SIZE - len, "/%s", dirent->d_name); + + if (dirent->d_type & DT_DIR) { + DeleteDir(pPath); + } else { + console_print_pos(0, 0, "Deleting file %s", dirent->d_name); + console_print_pos(0, 1, "From: %s", pPath); + if (remove(pPath)!=0) promptError("Failed to delete file."); + } + + OSScreenFlipBuffersEx(0); + OSScreenFlipBuffersEx(1); + + pPath[len] = 0; + + } + + closedir(dir); + + return 0; + +} + +// Source: loadiine_gx2 +void getUserID(char* out) { + + /* get persistent ID - thanks to Maschell */ + unsigned int nn_act_handle; + unsigned long (*GetPersistentIdEx)(unsigned char); + int (*GetSlotNo)(void); + void (*nn_Initialize)(void); + void (*nn_Finalize)(void); + OSDynLoad_Acquire("nn_act.rpl", &nn_act_handle); + OSDynLoad_FindExport(nn_act_handle, 0, "GetPersistentIdEx__Q2_2nn3actFUc", &GetPersistentIdEx); + OSDynLoad_FindExport(nn_act_handle, 0, "GetSlotNo__Q2_2nn3actFv", &GetSlotNo); + OSDynLoad_FindExport(nn_act_handle, 0, "Initialize__Q2_2nn3actFv", &nn_Initialize); + OSDynLoad_FindExport(nn_act_handle, 0, "Finalize__Q2_2nn3actFv", &nn_Finalize); + + nn_Initialize(); // To be sure that it is really Initialized + + unsigned char slotno = GetSlotNo(); + unsigned int persistentID = GetPersistentIdEx(slotno); + nn_Finalize(); //must be called an equal number of times to nn_Initialize + + sprintf(out, "%08X", persistentID); + +} + +int getLoadiineGameSaveDir(char* out, const char* productCode) { + + struct dirent* dirent = NULL; + DIR* dir = NULL; + + dir = opendir("sd:/wiiu/saves"); + if (dir == NULL) { + promptError("Failed to open directory."); + return -1; + } + + while ((dirent = readdir(dir)) != 0) { + + if ((dirent->d_type & DT_DIR) && (strstr(dirent->d_name, productCode)!=NULL)) { + sprintf(out, "sd:/wiiu/saves/%s", dirent->d_name); + closedir(dir); + return 0; + } + + } + + promptError("Loadiine game folder not found."); + closedir(dir); + return -2; + +} + +int getLoadiineSaveVersionList(int* out, const char* gamePath) { + + struct dirent* dirent = NULL; + DIR* dir = NULL; + + dir = opendir(gamePath); + if (dir == NULL) { + promptError("Loadiine game folder not found."); + return -1; + } + + int i = 0; + while ((i < 255) && ((dirent = readdir(dir)) != 0)) { + + if ((dirent->d_type & DT_DIR) && (strchr(dirent->d_name, 'v')!=NULL)) { + out[++i] = strtol((dirent->d_name)+1, NULL, 10); + } + + } + + closedir(dir); + return 0; + +} + +int getLoadiineUserDir(char* out, const char* fullSavePath, const char* userID) { + + struct dirent* dirent = NULL; + DIR* dir = NULL; + + dir = opendir(fullSavePath); + if (dir == NULL) { + promptError("Failed to open directory."); + return -1; + } + + while ((dirent = readdir(dir)) != 0) { + + if ((dirent->d_type & DT_DIR) && (strstr(dirent->d_name, userID))) { + sprintf(out, "%s/%s", fullSavePath, dirent->d_name); + closedir(dir); + return 0; + } + + } + + sprintf(out, "%s/u", fullSavePath); + closedir(dir); + return 0; + +} + +void backupSavedata(Title* title, u8 slot, bool allusers, bool common) { + + u32 highID = title->highID, lowID = title->lowID; + bool isUSB = title->isTitleOnUSB, isWii = (highID==0x00010000); + char srcPath[PATH_SIZE]; + char dstPath[PATH_SIZE]; + const char* path = (isWii ? "slccmpt01:/title" : (isUSB ? "storage_usb:/usr/save" : "storage_mlc:/usr/save")); + sprintf(srcPath, "%s/%08x/%08x/%s", path, highID, lowID, isWii ? "data" : "user"); + sprintf(dstPath, "sd:/wiiu/backups/%08x%08x/%u", highID, lowID, slot); + if (!allusers && !isWii) { + char usrPath[16]; + getUserID(usrPath); + sprintf(dstPath + strlen(dstPath), "/%s", usrPath); + u32 offset = strlen(srcPath); + if (common) { + strcpy(srcPath + offset, "/common"); + if (DumpDir(srcPath, dstPath)!=0) promptError("Common save not found."); + } + sprintf(srcPath + offset, "/%s", usrPath); + } DumpDir(srcPath, dstPath); } -void restoreSavedata(u32 highID, u32 lowID, bool isUSB, int slot) { +void restoreSavedata(Title* title, u8 slot, bool allusers, bool common) { - char srcPath[256]; - char dstPath[256]; - const char* path = ((highID==0x00010000) ? "slccmpt01:/title" : (isUSB ? "storage_usb:/usr/save" : "storage_mlc:/usr/save")); - sprintf(srcPath, "sd:/wiiu/apps/savemii/backups/%08x%08x/%i", highID, lowID, slot); - sprintf(dstPath, "%s/%08x/%08x/%s", path, highID, lowID, (highID==0x00010000) ? "data" : "user"); + if (!promptConfirm("Are you sure?")) return; + u32 highID = title->highID, lowID = title->lowID; + bool isUSB = title->isTitleOnUSB, isWii = (highID==0x00010000); + char srcPath[PATH_SIZE]; + char dstPath[PATH_SIZE]; + const char* path = (isWii ? "slccmpt01:/title" : (isUSB ? "storage_usb:/usr/save" : "storage_mlc:/usr/save")); + sprintf(srcPath, "sd:/wiiu/backups/%08x%08x/%u", highID, lowID, slot); + sprintf(dstPath, "%s/%08x/%08x/%s", path, highID, lowID, isWii ? "data" : "user"); + if (!allusers && !isWii) { + char usrPath[16]; + getUserID(usrPath); + sprintf(dstPath + strlen(dstPath), "/%s", usrPath); + u32 offset = strlen(srcPath); + if (common) { + strcpy(srcPath + offset, "/common"); + if (DumpDir(srcPath, dstPath)!=0) promptError("Common save not found."); + } + sprintf(srcPath + offset, "/%s", usrPath); + } DumpDir(srcPath, dstPath); } -void wipeSavedata(u32 highID, u32 lowID, bool isUSB) { - -} \ No newline at end of file +void wipeSavedata(Title* title, bool allusers, bool common) { + + if (!promptConfirm("Are you sure?") || !promptConfirm("Hm, are you REALLY sure?")) return; + u32 highID = title->highID, lowID = title->lowID; + bool isUSB = title->isTitleOnUSB, isWii = (highID==0x00010000); + char srcPath[PATH_SIZE]; + const char* path = (isWii ? "slccmpt01:/title" : (isUSB ? "storage_usb:/usr/save" : "storage_mlc:/usr/save")); + sprintf(srcPath, "%s/%08x/%08x/%s", path, highID, lowID, isWii ? "data" : "user"); + if (!allusers && !isWii) { + u32 offset = strlen(srcPath); + if (common) { + strcpy(srcPath + offset, "/common"); + if (DeleteDir(srcPath)!=0) promptError("Common save not found."); + } + char usrPath[16]; + getUserID(usrPath); + sprintf(srcPath + offset, "/%s", usrPath); + } + DeleteDir(srcPath); + +} + +void importFromLoadiine(Title* title, bool common, int version) { + + if (!promptConfirm("Are you sure?")) return; + u32 highID = title->highID, lowID = title->lowID; + bool isUSB = title->isTitleOnUSB; + char srcPath[PATH_SIZE]; + char dstPath[PATH_SIZE]; + if (getLoadiineGameSaveDir(srcPath, title->productCode)!=0) return; + if (version) sprintf(srcPath + strlen(srcPath), "/v%i", version); + char usrPath[16]; + getUserID(usrPath); + u32 srcOffset = strlen(srcPath); + getLoadiineUserDir(srcPath, srcPath, usrPath); + sprintf(dstPath, "storage_%s:/usr/save/%08x/%08x/user", isUSB ? "usb" : "mlc", highID, lowID); + u32 dstOffset = strlen(dstPath); + sprintf(dstPath + dstOffset, "/%s", usrPath); + DumpDir(srcPath, dstPath); + strcpy(srcPath + srcOffset, "/c"); + strcpy(dstPath + dstOffset, "/common"); + if (DumpDir(srcPath, dstPath)!=0) promptError("Common save not found."); + +} + +void exportToLoadiine(Title* title, bool common, int version) { + + if (!promptConfirm("Are you sure?")) return; + u32 highID = title->highID, lowID = title->lowID; + bool isUSB = title->isTitleOnUSB; + char srcPath[PATH_SIZE]; + char dstPath[PATH_SIZE]; + if (getLoadiineGameSaveDir(dstPath, title->productCode)!=0) return; + if (version) sprintf(dstPath + strlen(dstPath), "/v%u", version); + char usrPath[16]; + getUserID(usrPath); + u32 dstOffset = strlen(dstPath); + getLoadiineUserDir(dstPath, dstPath, usrPath); + sprintf(srcPath, "storage_%s:/usr/save/%08x/%08x/user", isUSB ? "usb" : "mlc", highID, lowID); + u32 srcOffset = strlen(srcPath); + sprintf(srcPath + srcOffset, "/%s", usrPath); + DumpDir(srcPath, dstPath); + strcpy(dstPath + dstOffset, "/c"); + strcpy(srcPath + srcOffset, "/common"); + if (DumpDir(srcPath, dstPath)!=0) promptError("Common save not found."); + +} diff --git a/src/savemng.h b/src/savemng.h index 1e2be18..47a8962 100644 --- a/src/savemng.h +++ b/src/savemng.h @@ -10,15 +10,33 @@ #include "lib_easy.h" +#define PATH_SIZE 0x200 + #ifdef __cplusplus extern "C" { #endif -void console_print_pos(int x, int y, const char *format, ...); +typedef struct { + u32 highID; + u32 lowID; + char shortName[256]; + char productCode[32]; + bool isTitleOnUSB; +} Title; -void backupSavedata(u32 highID, u32 lowID, bool isUSB, int slot); -void restoreSavedata(u32 highID, u32 lowID, bool isUSB, int slot); -void wipeSavedata(u32 highID, u32 lowID, bool isUSB); +void console_print_pos(int x, int y, const char* format, ...); +bool promptConfirm(const char* question); +void promptError(const char* message); + +int getLoadiineGameSaveDir(char* out, const char* productCode); +int getLoadiineSaveVersionList(int* out, const char* gamePath); +int getLoadiineUserDir(char* out, const char* fullSavePath, const char* userID); + +void backupSavedata(Title* title, u8 slot, bool allusers, bool common); +void restoreSavedata(Title* title, u8 slot, bool allusers, bool common); +void wipeSavedata(Title* title, bool allusers, bool common); +void importFromLoadiine(Title* title, bool common, int version); +void exportToLoadiine(Title* title, bool common, int version); #ifdef __cplusplus }