diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index 1014849..8b6f07b 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -53,6 +53,7 @@ #define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD))) #define FTYPE_CIAINSTALL(tp) ((tp&(GAME_NCSD|GAME_NCCH|GAME_CIA)) || ((tp&GAME_NDS)&&(tp&(FLAG_DSIW))) || (tp&(GAME_TMD)&&(tp&(FLAG_NUSCDN)))) #define FTYPE_CXIDUMP(tp) (tp&(GAME_TMD)) +#define FTYPE_UNINSTALL(tp) (tp&(GAME_TIE)) #define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB)) #define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY)) #define FTYPE_TITLEINFO(tp) (tp&(GAME_TIE|GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS|GAME_GBA|GAME_TAD|GAME_3DSX)) diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 6a3388b..acd74a4 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -1094,6 +1094,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype)); bool cia_installable = (FTYPE_CIAINSTALL(filetype)) && !(drvtype & DRV_CTRNAND) && !(drvtype & DRV_TWLNAND) && !(drvtype & DRV_ALIAS); + bool uninstallable = (FTYPE_UNINSTALL(filetype)); bool cxi_dumpable = (FTYPE_CXIDUMP(filetype)); bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path; bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path && @@ -1165,7 +1166,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan (filetype & GAME_EXEFS) ? "Mount as EXEFS image" : (filetype & GAME_ROMFS) ? "Mount as ROMFS image" : (filetype & GAME_TMD ) ? "TMD file options..." : - (filetype & GAME_TIE ) ? "Show title info" : + (filetype & GAME_TIE ) ? "Manage Title..." : (filetype & GAME_BOSS ) ? "BOSS file options..." : (filetype & GAME_NUSCDN)? "Decrypt NUS/CDN file" : (filetype & GAME_SMDH) ? "Show SMDH title info" : @@ -1315,6 +1316,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan int cia_build_legit = (cia_buildable_legit) ? ++n_opt : -1; int cxi_dump = (cxi_dumpable) ? ++n_opt : -1; int cia_install = (cia_installable) ? ++n_opt : -1; + int uninstall = (uninstallable) ? ++n_opt : -1; int tik_build_enc = (tik_buildable) ? ++n_opt : -1; int tik_build_dec = (tik_buildable) ? ++n_opt : -1; int key_build = (key_buildable) ? ++n_opt : -1; @@ -1347,7 +1349,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan if (cia_build > 0) optionstr[cia_build-1] = (cia_build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)"; if (cia_build_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)"; if (cxi_dump > 0) optionstr[cxi_dump-1] = "Dump CXI/NDS file"; - if (cia_install > 0) optionstr[cia_install-1] = "Install game file"; + if (cia_install > 0) optionstr[cia_install-1] = "Install game image"; + if (uninstall > 0) optionstr[uninstall-1] = "Uninstall title"; if (tik_build_enc > 0) optionstr[tik_build_enc-1] = "Build " TIKDB_NAME_ENC; if (tik_build_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC; if (key_build > 0) optionstr[key_build-1] = "Build " KEYDB_NAME; @@ -1601,6 +1604,43 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan } return 0; } + else if (user_select == uninstall) { // -> uninstall title + bool full_uninstall = false; + + // safety confirmation + optionstr[0] = "Keep ticket & savegame"; + optionstr[1] = "Uninstall everything"; + optionstr[2] = "Abort uninstall"; + user_select = (int) (n_marked > 1) ? + ShowSelectPrompt(3, optionstr, "Uninstall %lu selected titles?", n_marked) : + ShowSelectPrompt(3, optionstr, "%s\nUninstall selected title?", pathstr); + full_uninstall = (user_select == 2); + if (!user_select || (user_select == 3)) + return 0; + + // batch uninstall + if (n_marked > 1) { + u32 n_success = 0; + u32 num = 0; + ShowProgress(0, 0, "batch uninstall"); + for (u32 i = 0; i < current_dir->n_entries; i++) { + const char* path = current_dir->entry[i].path; + if (!current_dir->entry[i].marked) continue; + if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) continue; + if (!ShowProgress(++num, n_marked, path)) break; + if (UninstallGameDataTie(path, true, full_uninstall, full_uninstall) == 0) + n_success++; + } + ShowPrompt(false, "%lu/%lu titles uninstalled", n_success, n_marked); + } else { + ShowString("%s\nUninstalling, please wait...", pathstr); + if (UninstallGameDataTie(file_path, true, full_uninstall, full_uninstall) != 0) + ShowPrompt(false, "%s\nUninstall failed!", pathstr); + } + + GetDirContents(current_dir, current_path); + return 0; + } else if (user_select == verify) { // -> verify game / nand file if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) { u32 n_success = 0; diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index f6f0d15..646a40f 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -1353,6 +1353,83 @@ u32 GetInstallSavePath(char* path, const char* drv, u64 tid64) { } } +u32 UninstallGameData(u64 tid64, bool remove_tie, bool remove_ticket, bool remove_save, bool from_emunand) { + char drv[3]; + + // check permissions for SysNAND (this includes everything we need) + if (!CheckWritePermissions(from_emunand ? "4:" : "1:")) return 1; + + // determine the drive + if (GetInstallDataDrive(drv, tid64, from_emunand) != 0) return 1; + + // remove data path + char path_data[256]; + if (GetInstallPath(path_data, drv, tid64, NULL, remove_save ? NULL : "content") != 0) return 1; + fvx_runlink(path_data); + + // clear leftovers + if (GetInstallPath(path_data, drv, tid64, NULL, NULL) != 0) + fvx_unlink(path_data); + // system save is *not* cleared in any case (!!!) + + // remove titledb entry / ticket + u32 ret = 0; + if (remove_tie || remove_ticket) { + // ensure remounting the old mount path + char path_store[256] = { 0 }; + char* path_bak = NULL; + strncpy(path_store, GetMountPath(), 256); + if (*path_store) path_bak = path_store; + + // we need the big endian title ID + u8 title_id[8]; + for (u32 i = 0; i < 8; i++) + title_id[i] = (tid64 >> ((7-i)*8)) & 0xFF; + + // ticket database + if (remove_ticket) { + char path_ticketdb[256]; + if ((GetInstallDbsPath(path_ticketdb, drv, "ticket.db") != 0) || !InitImgFS(path_ticketdb) || + ((RemoveTicketFromDB("D:/partitionA.bin", title_id)) != 0)) ret = 1; + } + + // title database + if (remove_tie) { + char path_titledb[256]; + if ((GetInstallDbsPath(path_titledb, drv, "title.db") != 0) || !InitImgFS(path_titledb) || + ((RemoveTitleInfoEntryFromDB("D:/partitionA.bin", title_id)) != 0)) ret = 1; + } + + // restore old mount path + InitImgFS(path_bak); + } + + return ret; +} + +u32 UninstallGameDataTie(const char* path, bool remove_tie, bool remove_ticket, bool remove_save) { + // requirements for this to work: + // * title.db from standard path mounted to T:/ + // * entry filename starts with title id + // * these two conditions need to be fulfilled for all ties + bool from_emunand = false; + u64 tid64; + + const char* mntpath = GetMountPath(); + if (!mntpath) return 1; + + // title.db from emunand? + if ((strncasecmp(mntpath, "B:/dbs/title.db", 16) == 0) || + (strncasecmp(mntpath, "4:/dbs/title.db", 16) == 0)) + from_emunand = true; + + // get title ID + if (sscanf(path, "T:/%016llx", &tid64) != 1) + return 1; + + return UninstallGameData(tid64, remove_tie, remove_ticket, remove_save, from_emunand); +} + u32 InstallCiaContent(const char* drv, const char* path_content, u32 offset, u32 size, TmdContentChunk* chunk, const u8* title_id, const u8* titlekey, bool cxi_fix) { char dest[256]; diff --git a/arm9/source/utils/gameutil.h b/arm9/source/utils/gameutil.h index f2c9379..ad2064c 100644 --- a/arm9/source/utils/gameutil.h +++ b/arm9/source/utils/gameutil.h @@ -15,6 +15,7 @@ u32 TrimGameFile(const char* path); u32 ShowGameFileTitleInfoF(const char* path, u16* screen, bool clear); u32 ShowGameFileTitleInfo(const char* path); u32 ShowCiaCheckerInfo(const char* path); +u32 UninstallGameDataTie(const char* path, bool remove_tie, bool remove_ticket, bool remove_save); u32 GetTmdContentPath(char* path_content, const char* path_tmd); u32 BuildNcchInfoXorpads(const char* destdir, const char* path); u32 CheckHealthAndSafetyInject(const char* hsdrv);