From 31d2402d593d68ccd8b05e95b26088b6e5aed26e Mon Sep 17 00:00:00 2001 From: 0verjoY <59394546+0verjoY@users.noreply.github.com> Date: Sun, 19 Feb 2023 20:47:31 +0100 Subject: [PATCH] - FAT stuff: - Refactored almost everything - Will mount available devices at start and unmount at exit app - Device select screen will now only show avialable devices - Added option to remount devices in the select screen - Will now warn if region checks are disabled in select screen - Added option to reenable region checks in select screen - Probably even more changes - SM wad installation: - Changed the region check functions (WIP) - While you install a SM you'll now be able to retain Priiloader - Bug fixes - Refactoring - Even more bug fixes --- Test/menu.c | 1416 ------------------------------------------ data/appboot.bin | Bin 182208 -> 182432 bytes source/fat.c | 128 ++-- source/fat.h | 31 +- source/fileops.c | 80 +++ source/fileops.h | 17 + source/globals.h | 16 +- source/gui.c | 37 +- source/iospatch.c | 4 +- source/menu.c | 663 ++++++++++---------- source/nand.c | 383 +++++++++++- source/nand.h | 21 +- source/sha1.c | 14 + source/sha1.h | 1 + source/wad-manager.c | 40 +- source/wad.c | 620 ++++++++++++++---- source/wad.h | 6 +- 17 files changed, 1476 insertions(+), 2001 deletions(-) delete mode 100644 Test/menu.c create mode 100644 source/fileops.c create mode 100644 source/fileops.h diff --git a/Test/menu.c b/Test/menu.c deleted file mode 100644 index aa50a82..0000000 --- a/Test/menu.c +++ /dev/null @@ -1,1416 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sys.h" -#include "fat.h" -#include "nand.h" -#include "restart.h" -#include "title.h" -#include "usbstorage.h" -#include "utils.h" -#include "video.h" -#include "wad.h" -#include "wpad.h" -#include -#include "globals.h" -#include "iospatch.h" -#include "appboot.h" - -/* FAT device list */ -//static fatDevice fdevList[] = { -fatDevice fdevList[] = { - { "sd", "Wii SD Slot", &__io_wiisd }, - { "usb", "USB Mass Storage Device", &__io_usbstorage }, - { "usb2", "USB 2.0 Mass Storage Device", &__io_wiiums }, - { "gcsda", "SD Gecko (Slot A)", &__io_gcsda }, - { "gcsdb", "SD Gecko (Slot B)", &__io_gcsdb }, - //{ "smb", "SMB share", NULL }, -}; - -/* NAND device list */ -//static nandDevice ndevList[] = { -nandDevice ndevList[] = { - { "Disable", 0, 0x00, 0x00 }, - { "SD/SDHC Card", 1, 0xF0, 0xF1 }, - { "USB 2.0 Mass Storage Device", 2, 0xF2, 0xF3 }, -}; - -/* FAT device */ -static fatDevice *fdev = NULL; -static nandDevice *ndev = NULL; - -// wiiNinja: Define a buffer holding the previous path names as user -// traverses the directory tree. Max of 10 levels is define at this point -static u8 gDirLevel = 0; -static char gDirList [MAX_DIR_LEVELS][MAX_FILE_PATH_LEN]; -static s32 gSeleted[MAX_DIR_LEVELS]; -static s32 gStart[MAX_DIR_LEVELS]; - -/* Macros */ -#define NB_FAT_DEVICES (sizeof(fdevList) / sizeof(fatDevice)) -#define NB_NAND_DEVICES (sizeof(ndevList) / sizeof(nandDevice)) - -// Local prototypes: wiiNinja -void WaitPrompt (char *prompt); -int PushCurrentDir(char *dirStr, int Selected, int Start); -char *PopCurrentDir(int *Selected, int *Start); -bool IsListFull (void); -char *PeekCurrentDir (void); -u32 WaitButtons(void); -u32 Pad_GetButtons(void); -void WiiLightControl (int state); - -int __Menu_IsGreater(const void *p1, const void *p2) -{ - u32 n1 = *(u32 *)p1; - u32 n2 = *(u32 *)p2; - - /* Equal */ - if (n1 == n2) - return 0; - - return (n1 > n2) ? 1 : -1; -} - - -int __Menu_EntryCmp(const void *p1, const void *p2) -{ - fatFile *f1 = (fatFile *)p1; - fatFile *f2 = (fatFile *)p2; - - /* Compare entries */ // wiiNinja: Include directory - if ((f1->isdir) && !(f2->isdir)) - return (-1); - else if (!(f1->isdir) && (f2->isdir)) - return (1); - else - return strcasecmp(f1->filename, f2->filename); -} - -static bool __FolderExists(const char *path) -{ - DIR *dir; - dir = opendir(path); - if(dir) - { - closedir(dir); - return true; - } - return false; -} - -static size_t __GetFileSizeBytes(const char *path) -{ - FILE *f; - size_t size = 0; - - f = fopen(path, "rb"); - if(!f) return 0; - - //Get file size - fseek(f, 0, SEEK_END); - size = ftell(f); - fclose(f); - - return size; -} - -char gFileName[MAX_FILE_PATH_LEN]; -s32 __Menu_RetrieveList(char *inPath, fatFile **outbuf, u32 *outlen) -{ - fatFile *buffer = NULL; - DIR *dir = NULL; - struct dirent *ent = NULL; - - //char dirpath[256], filename[768]; - u32 cnt; - - /* Generate dirpath */ - //sprintf(dirpath, "%s:" WAD_DIRECTORY, fdev->mount); - - /* Open directory */ - dir = opendir(inPath); - if (!dir) - return -1; - - /* Count entries */ - for (cnt = 0; ((ent = readdir(dir)) != NULL);) { - cnt++; - } - - if (cnt > 0) { - /* Allocate memory */ - buffer = malloc(sizeof(fatFile) * cnt); - if (!buffer) { - closedir(dir); - return -2; - } - - /* Reset directory */ - rewinddir(dir); - - /* Get entries */ - for (cnt = 0; ((ent = readdir(dir)) != NULL);) - { - bool addFlag = false; - bool isdir = false; - size_t fsize = 0; - - snprintf(gFileName, MAX_FILE_PATH_LEN, "%s/%s", inPath, ent->d_name); - if (__FolderExists(gFileName)) // wiiNinja - { - isdir = true; - // Add only the item ".." which is the previous directory - // AND if we're not at the root directory - if ((strcmp (ent->d_name, "..") == 0) && (gDirLevel > 1)) - addFlag = true; - else if (strcmp (ent->d_name, ".") != 0) - addFlag = true; - } - else - { - if(strlen(ent->d_name)>4) - { - if(!strcasecmp(ent->d_name+strlen(ent->d_name)-4, ".wad")) - { - fsize = __GetFileSizeBytes(gFileName); - addFlag = true; - } - } - } - - if (addFlag == true) - { - fatFile *file = &buffer[cnt++]; - - /* File name */ - strcpy(file->filename, ent->d_name); - - /* File stats */ - file->isdir = isdir; - file->fsize = fsize; - - } - } - - /* Sort list */ - qsort(buffer, cnt, sizeof(fatFile), __Menu_EntryCmp); - } - - /* Close directory */ - closedir(dir); - - /* Set values */ - *outbuf = buffer; - *outlen = cnt; - - return 0; -} - - -void Menu_SelectIOS(void) -{ - u8 *iosVersion = NULL; - u32 iosCnt; - u8 tmpVersion; - - u32 cnt; - s32 ret, selected = 0; - bool found = false; - - /* Get IOS versions */ - ret = Title_GetIOSVersions(&iosVersion, &iosCnt); - if (ret < 0) - return; - - /* Sort list */ - qsort(iosVersion, iosCnt, sizeof(u8), __Menu_IsGreater); - - if (gConfig.cIOSVersion < 0) - tmpVersion = CIOS_VERSION; - else - { - tmpVersion = (u8)gConfig.cIOSVersion; - // For debugging only - //printf ("User pre-selected cIOS: %i\n", tmpVersion); - //WaitButtons(); - } - - /* Set default version */ - for (cnt = 0; cnt < iosCnt; cnt++) { - u8 version = iosVersion[cnt]; - - /* Custom IOS available */ - //if (version == CIOS_VERSION) - if (version == tmpVersion) - { - selected = cnt; - found = true; - break; - } - - /* Current IOS */ - if (version == IOS_GetVersion()) - selected = cnt; - } - - /* Ask user for IOS version */ - if ((gConfig.cIOSVersion < 0) || (found == false)) - { - for (;;) - { - /* Clear console */ - Con_Clear(); - - printf("\t>> Select IOS version to use: < IOS%d >\n\n", iosVersion[selected]); - - printf("\t Press LEFT/RIGHT to change IOS version.\n\n"); - - printf("\t Press A button to continue.\n"); - printf("\t Press HOME button to restart.\n\n"); - - u32 buttons = WaitButtons(); - - /* LEFT/RIGHT buttons */ - if (buttons & WPAD_BUTTON_LEFT) { - if ((--selected) <= -1) - selected = (iosCnt - 1); - } - if (buttons & WPAD_BUTTON_RIGHT) { - if ((++selected) >= iosCnt) - selected = 0; - } - - /* HOME button */ - if (buttons & WPAD_BUTTON_HOME) - Restart(); - - /* A button */ - if (buttons & WPAD_BUTTON_A) - break; - } - } - - - u8 version = iosVersion[selected]; - - if (IOS_GetVersion() != version) { - /* Shutdown subsystems */ - Wpad_Disconnect(); - //mload_close(); - - /* Load IOS */ - - if(!loadIOS(version)) Wpad_Init(), Menu_SelectIOS(); - - /* Initialize subsystems */ - Wpad_Init(); - } -} - -void Menu_FatDevice(void) -{ - int ret, selected = 0; - - /* Unmount FAT device */ - //if (fdev) - //Fat_Unmount(fdev); - //if (((fdevList[selected].mount[0] == 's') && (ndev->name[0] == 'S'))) - //selected++; - static const u16 konamiCode[] = { - WPAD_BUTTON_UP, WPAD_BUTTON_UP, WPAD_BUTTON_DOWN, WPAD_BUTTON_DOWN, WPAD_BUTTON_LEFT, - WPAD_BUTTON_RIGHT, WPAD_BUTTON_LEFT, WPAD_BUTTON_RIGHT, WPAD_BUTTON_B, WPAD_BUTTON_A - }; - - int codePosition = 0; - - /* Select source device */ - if (gConfig.fatDeviceIndex < 0) - { - for (;;) { - /* Clear console */ - Con_Clear(); - - /* Selected device */ - fdev = &fdevList[selected]; - - printf("\t>> Select source device: < %s >\n\n", fdev->name); - - printf("\t Press LEFT/RIGHT to change the selected device.\n\n"); - - printf("\t Press A button to continue.\n"); - printf("\t Press HOME button to restart.\n\n"); - - u32 buttons = WaitButtons(); - - if (buttons & (WPAD_BUTTON_UP | WPAD_BUTTON_DOWN | WPAD_BUTTON_RIGHT | WPAD_BUTTON_LEFT | WPAD_BUTTON_A | WPAD_BUTTON_B)) { - if (buttons & konamiCode[codePosition]) - ++codePosition; - else - codePosition = 0; - } - - /* LEFT/RIGHT buttons */ - if (buttons & WPAD_BUTTON_LEFT) { - if ((--selected) <= -1) - selected = (NB_FAT_DEVICES - 1); - if ((fdevList[selected].mount[0] == 's') && (ndev->name[0] == 'S')) - selected--; - if ((fdevList[selected].mount[0] == 'u' && fdevList[selected].mount[3] == '2') && (ndev->name[0] == 'U')) - selected--; - if ((selected) <= -1) - selected = (NB_FAT_DEVICES - 1); - } - if (buttons & WPAD_BUTTON_RIGHT) { - if ((++selected) >= NB_FAT_DEVICES) - selected = 0; - if ((fdevList[selected].mount[0] == 's') && (ndev->name[0] == 'S')) - selected++; - if ((fdevList[selected].mount[0] == 'u' && fdevList[selected].mount[3] == '2') && (ndev->name[0] == 'U')) - selected++; - } - - /* HOME button */ - if (buttons & WPAD_BUTTON_HOME) - Restart(); - - /* A button */ - if (buttons & WPAD_BUTTON_A) { - if (codePosition == sizeof(konamiCode) / sizeof(konamiCode[0])) { - extern bool skipRegionSafetyCheck; - skipRegionSafetyCheck = true; - printf("[+] Disabled SM region checks\n"); - sleep(2); - } - break; - } - } - } - else - { - sleep(5); - fdev = &fdevList[gConfig.fatDeviceIndex]; - } - - printf("[+] Mounting %s, please wait...", fdev->name ); - fflush(stdout); - - /* Mount FAT device */ - - ret = Fat_Mount(fdev); - if (ret < 0) { - printf(" ERROR! (ret = %d)\n", ret); - goto err; - } else - printf(" OK!\n"); - - return; - -err: - - if(gConfig.fatDeviceIndex >= 0) gConfig.fatDeviceIndex = -1; - WiiLightControl (WII_LIGHT_OFF); - printf("\n"); - printf(" Press any button to continue...\n"); - - WaitButtons(); - - /* Prompt menu again */ - Menu_FatDevice(); -} - -void Menu_NandDevice(void) -{ - int ret, selected = 0; - - /* Disable NAND emulator */ - if (ndev) { - Nand_Unmount(ndev); - Nand_Disable(); - } - - /* Select source device */ - if (gConfig.nandDeviceIndex < 0) - { - for (;;) { - /* Clear console */ - Con_Clear(); - - /* Selected device */ - ndev = &ndevList[selected]; - - printf("\t>> Select NAND emulator device: < %s >\n\n", ndev->name); - - printf("\t Press LEFT/RIGHT to change the selected device.\n\n"); - - printf("\t Press A button to continue.\n"); - printf("\t Press HOME button to restart.\n\n"); - - u32 buttons = WaitButtons(); - - /* LEFT/RIGHT buttons */ - if (buttons & WPAD_BUTTON_LEFT) { - if ((--selected) <= -1) - selected = (NB_NAND_DEVICES - 1); - } - if (buttons & WPAD_BUTTON_RIGHT) { - if ((++selected) >= NB_NAND_DEVICES) - selected = 0; - } - - /* HOME button */ - if (buttons & WPAD_BUTTON_HOME) - Restart(); - - /* A button */ - if (buttons & WPAD_BUTTON_A) - break; - } - } - else - { - ndev = &ndevList[gConfig.nandDeviceIndex]; - } - - /* No NAND device */ - if (!ndev->mode) - return; - - printf("[+] Enabling NAND emulator..."); - fflush(stdout); - - /* Mount NAND device */ - ret = Nand_Mount(ndev); - if (ret < 0) { - printf(" ERROR! (ret = %d)\n", ret); - goto err; - } - - /* Enable NAND emulator */ - ret = Nand_Enable(ndev); - if (ret < 0) { - printf(" ERROR! (ret = %d)\n", ret); - goto err; - } else - printf(" OK!\n"); - - return; - -err: - printf("\n"); - printf(" Press any button to continue...\n"); - - WaitButtons(); - - /* Prompt menu again */ - Menu_NandDevice(); -} - -char gTmpFilePath[MAX_FILE_PATH_LEN]; -/* Install and/or Uninstall multiple WADs - Leathl */ -int Menu_BatchProcessWads(fatFile *files, int fileCount, char *inFilePath, int installCnt, int uninstallCnt) -{ - int count; - - for (;;) - { - Con_Clear(); - - if ((installCnt > 0) & (uninstallCnt == 0)) { - printf("[+] %d file%s marked for installation.\n", installCnt, (installCnt == 1) ? "" : "s"); - printf(" Do you want to proceed?\n"); - } - else if ((installCnt == 0) & (uninstallCnt > 0)) { - printf("[+] %d file%s marked for uninstallation.\n", uninstallCnt, (uninstallCnt == 1) ? "" : "s"); - printf(" Do you want to proceed?\n"); - } - else { - printf("[+] %d file%s marked for installation and %d file%s for uninstallation.\n", installCnt, (installCnt == 1) ? "" : "s", uninstallCnt, (uninstallCnt == 1) ? "" : "s"); - printf(" Do you want to proceed?\n"); - } - - printf("\n\n Press A to continue.\n"); - printf(" Press B to go back to the menu.\n\n"); - - u32 buttons = WaitButtons(); - - if (buttons & WPAD_BUTTON_A) - break; - - if (buttons & WPAD_BUTTON_B) - return 0; - } - - WiiLightControl (WII_LIGHT_ON); - int errors = 0; - int success = 0; - s32 ret; - - for (count = 0; count < fileCount; count++) - { - fatFile *thisFile = &files[count]; - - if ((thisFile->install == 1) | (thisFile->install == 2)) { - int mode = thisFile->install; - Con_Clear(); - printf("[+] Opening \"%s\", please wait...\n\n", thisFile->filename); - - sprintf(gTmpFilePath, "%s/%s", inFilePath, thisFile->filename); - - FILE *fp = fopen(gTmpFilePath, "rb"); - if (!fp) { - printf(" ERROR!\n"); - errors += 1; - continue; - } - - printf("[+] %s WAD, please wait...\n", (mode == 2) ? "Uninstalling" : "Installing"); - if (mode == 2) { - ret = Wad_Uninstall(fp); - } - else { - ret = Wad_Install(fp); - } - - if (ret < 0) errors += 1; - else success += 1; - - thisFile->installstate = ret; - - if (fp) - fclose(fp); - } - } - - WiiLightControl (WII_LIGHT_OFF); - - printf("\n"); - printf(" %d titles succeeded and %d failed...\n", success, errors); - - if (errors > 0) - { - printf("\n Some operations failed"); - printf("\n Press A to list.\n"); - printf(" Press B skip.\n"); - - u32 buttons = WaitButtons(); - - if ((buttons & WPAD_BUTTON_A)) - { - Con_Clear(); - - int i=0; - for (count = 0; count < fileCount; count++) - { - fatFile *thisFile = &files[count]; - - if (thisFile->installstate <0) - { - char str[41]; - strncpy(str, thisFile->filename, 40); //Only 40 chars to fit the screen - str[40]=0; - i++; - if(thisFile->installstate == -999) printf(" %s BRICK BLOCKED\n", str); - else if(thisFile->installstate == -998) printf(" %s Skipped\n", str); - else if(thisFile->installstate == -106) printf(" %s Not installed?\n", str); - else if(thisFile->installstate == -1036 ) printf(" %s Needed IOS missing\n", str); - else if(thisFile->installstate == -4100 ) printf(" %s No trucha bug?\n", str); - else printf(" %s error %d\n", str, thisFile->installstate); - if( i == 17 ) - { - printf("\n Press any button to continue\n"); - WaitButtons(); - i = 0; - } - } - } - } - } - printf("\n Press any button to continue...\n"); - WaitButtons(); - - return 1; -} - -/* File Operations - Leathl */ -int Menu_FileOperations(fatFile *file, char *inFilePath) -{ - f32 filesize = (file->fsize / MB_SIZE); - - for (;;) - { - Con_Clear(); - - printf("[+] WAD Filename : %s\n", file->filename); - printf(" WAD Filesize : %.2f MB\n\n\n", filesize); - - - printf("[+] Select action: < %s WAD >\n\n", "Delete"); //There's yet nothing else than delete - - printf(" Press LEFT/RIGHT to change selected action.\n\n"); - - printf(" Press A to continue.\n"); - printf(" Press B to go back to the menu.\n\n"); - - u32 buttons = WaitButtons(); - - /* A button */ - if (buttons & WPAD_BUTTON_A) - break; - - /* B button */ - if (buttons & WPAD_BUTTON_B) - return 0; - } - - Con_Clear(); - - printf("[+] Deleting \"%s\", please wait...\n", file->filename); - - sprintf(gTmpFilePath, "%s/%s", inFilePath, file->filename); - int error = remove(gTmpFilePath); - if (error != 0) - printf(" ERROR!"); - else - printf(" Successfully deleted!"); - - printf("\n"); - printf(" Press any button to continue...\n"); - - WaitButtons(); - - return !error; -} - -void Menu_WadManage(fatFile *file, char *inFilePath) -{ - FILE *fp = NULL; - - //char filepath[128]; - f32 filesize; - - u32 mode = 0; - - /* File size in megabytes */ - filesize = (file->fsize / MB_SIZE); - - for (;;) { - /* Clear console */ - Con_Clear(); - - printf("[+] WAD Filename : %s\n", file->filename); - printf(" WAD Filesize : %.2f MB\n\n\n", filesize); - - - printf("[+] Select action: < %s WAD >\n\n", (!mode) ? "Install" : "Uninstall"); - - printf(" Press LEFT/RIGHT to change selected action.\n\n"); - - printf(" Press A to continue.\n"); - printf(" Press B to go back to the menu.\n\n"); - - u32 buttons = WaitButtons(); - - /* LEFT/RIGHT buttons */ - if (buttons & (WPAD_BUTTON_LEFT | WPAD_BUTTON_RIGHT)) - mode ^= 1; - - /* A button */ - if (buttons & WPAD_BUTTON_A) - break; - - /* B button */ - if (buttons & WPAD_BUTTON_B) - return; - } - - /* Clear console */ - Con_Clear(); - - printf("[+] Opening \"%s\", please wait...", file->filename); - fflush(stdout); - - /* Generate filepath */ - // sprintf(filepath, "%s:" WAD_DIRECTORY "/%s", fdev->mount, file->filename); - sprintf(gTmpFilePath, "%s/%s", inFilePath, file->filename); // wiiNinja - - /* Open WAD */ - fp = fopen(gTmpFilePath, "rb"); - if (!fp) { - printf(" ERROR!\n"); - goto out; - } else - printf(" OK!\n\n"); - - printf("[+] %s WAD, please wait...\n", (!mode) ? "Installing" : "Uninstalling"); - - /* Do install/uninstall */ - WiiLightControl (WII_LIGHT_ON); - if (!mode) - Wad_Install(fp); - else - Wad_Uninstall(fp); - WiiLightControl (WII_LIGHT_OFF); - -out: - /* Close file */ - if (fp) - fclose(fp); - - printf("\n"); - printf(" Press any button to continue...\n"); - - /* Wait for button */ - WaitButtons(); -} - -void Menu_WadList(void) -{ - char str [100]; - fatFile *fileList = NULL; - u32 fileCnt; - int ret, selected = 0, start = 0; - char *tmpPath = malloc (MAX_FILE_PATH_LEN); - int installCnt = 0; - int uninstallCnt = 0; - - //fatFile *installFiles = malloc(sizeof(fatFile) * 50); - //int installCount = 0; - - // wiiNinja: check for malloc error - if (tmpPath == NULL) - { - ret = -997; // What am I gonna use here? - printf(" ERROR! Out of memory (ret = %d)\n", ret); - return; - } - - printf("[+] Retrieving file list..."); - fflush(stdout); - - gDirLevel = 0; - - // push root dir as base folder - sprintf(tmpPath, "%s:%s", fdev->mount, WAD_DIRECTORY); - PushCurrentDir(tmpPath,0,0); - // if user provides startup directory, try it out first - if (strcmp (WAD_DIRECTORY, gConfig.startupPath) != 0) - { - // replace root dir with provided startup directory - sprintf(tmpPath, "%s:%s", fdev->mount, gConfig.startupPath); - // If the directory can be successfully opened, it must exists - DIR *tmpDirPtr = opendir(tmpPath); - if (tmpDirPtr) - { - closedir (tmpDirPtr); - PushCurrentDir(tmpPath,0,0); - } - else // unable to open provided dir, stick with root dir - sprintf(tmpPath, "%s:%s", fdev->mount, WAD_DIRECTORY); - } - - /* Retrieve filelist */ -getList: - free (fileList); - fileList = NULL; - - ret = __Menu_RetrieveList(tmpPath, &fileList, &fileCnt); - if (ret < 0) { - printf(" ERROR! (ret = %d)\n", ret); - goto err; - } - - /* No files */ - if (!fileCnt) { - printf(" No files found!\n"); - goto err; - } - - /* Set install-values to 0 - Leathl */ - int counter; - for (counter = 0; counter < fileCnt; counter++) { - fatFile *file = &fileList[counter]; - file->install = 0; - } - - for (;;) - { - u32 cnt; - s32 index; - - /* Clear console */ - Con_Clear(); - - /** Print entries **/ - cnt = strlen(tmpPath); - if(cnt>30) - index = cnt-30; - else - index = 0; - - printf("[+] WAD files on [%s]:\n\n", tmpPath+index); - - /* Print entries */ - for (cnt = start; cnt < fileCnt; cnt++) - { - fatFile *file = &fileList[cnt]; - f32 filesize = file->fsize / MB_SIZE; - - /* Entries per page limit */ - if ((cnt - start) >= ENTRIES_PER_PAGE) - break; - - strncpy(str, file->filename, 40); //Only 40 chars to fit the screen - str[40]=0; - - /* Print filename */ - //printf("\t%2s %s (%.2f MB)\n", (cnt == selected) ? ">>" : " ", file->filename, filesize); - if (file->isdir) // wiiNinja - printf("\t%2s [%s]\n", (cnt == selected) ? ">>" : " ", str); - else - printf("\t%2s%s%s (%.2f MB)\n", (cnt == selected) ? ">>" : " ", (file->install == 1) ? "+" : ((file->install == 2) ? "-" : " "), str, filesize); - - } - - printf("\n"); - - printf("[+] Press A to (un)install."); - if(gDirLevel>1) - printf(" Press B to go up-level DIR.\n"); - else - printf(" Press B to select a device.\n"); - printf(" Use +/X and -/Y to (un)mark. Press 1/Z/ZR for delete menu.\n"); - - printf(" Press 2 to launch app.\n"); - - /** Controls **/ - u32 buttons = WaitButtons(); - - if (buttons & WPAD_BUTTON_2) - { - if (!LoadApp(tmpPath)) - { - printf(" Failed to load app.\n"); - goto err; - } - - Fat_Unmount(fdev); - //SetIos(36); - - LaunchApp(); - } - - /* DPAD buttons */ - if (buttons & WPAD_BUTTON_UP) { - selected--; - - if (selected <= -1) - selected = (fileCnt - 1); - } - if (buttons & WPAD_BUTTON_LEFT) { - selected = selected + ENTRIES_PER_PAGE; - - if (selected >= fileCnt) - selected = 0; - } - if (buttons & WPAD_BUTTON_DOWN) { - selected ++; - - if (selected >= fileCnt) - selected = 0; - } - if (buttons & WPAD_BUTTON_RIGHT) { - selected = selected - ENTRIES_PER_PAGE; - - if (selected <= -1) - selected = (fileCnt - 1); - } - - /* HOME button */ - if (buttons & WPAD_BUTTON_HOME) - Restart(); - - /* Plus Button - Leathl */ - if (buttons & WPAD_BUTTON_PLUS) - { - if(Wpad_TimeButton()) - { - installCnt = 0; - int i = 0; - while( i < fileCnt) - { - fatFile *file = &fileList[i]; - if (((file->isdir) == false) & (file->install == 0)) { - file->install = 1; - - installCnt += 1; - } - else if (((file->isdir) == false) & (file->install == 1)) { - file->install = 0; - - installCnt -= 1; - } - else if (((file->isdir) == false) & (file->install == 2)) { - file->install = 1; - - installCnt += 1; - uninstallCnt -= 1; - } - i++; - } - - } - else - { - fatFile *file = &fileList[selected]; - if (((file->isdir) == false) & (file->install == 0)) { - file->install = 1; - - installCnt += 1; - } - else if (((file->isdir) == false) & (file->install == 1)) { - file->install = 0; - - installCnt -= 1; - } - else if (((file->isdir) == false) & (file->install == 2)) { - file->install = 1; - - installCnt += 1; - uninstallCnt -= 1; - } - selected++; - - if (selected >= fileCnt) - selected = 0; - } - } - - /* Minus Button - Leathl */ - if (buttons & WPAD_BUTTON_MINUS) - { - if(Wpad_TimeButton()) - { - installCnt = 0; - int i = 0; - while( i < fileCnt) - { - fatFile *file = &fileList[i]; - if (((file->isdir) == false) & (file->install == 0)) { - file->install = 2; - - uninstallCnt += 1; - } - else if (((file->isdir) == false) & (file->install == 1)) { - file->install = 2; - - uninstallCnt += 1; - installCnt -= 1; - } - else if (((file->isdir) == false) & (file->install == 2)) { - file->install = 0; - - uninstallCnt -= 1; - } - i++; - } - - } - else - { - fatFile *file = &fileList[selected]; - if (((file->isdir) == false) & (file->install == 0)) { - file->install = 2; - - uninstallCnt += 1; - } - else if (((file->isdir) == false) & (file->install == 1)) { - file->install = 2; - - uninstallCnt += 1; - installCnt -= 1; - } - else if (((file->isdir) == false) & (file->install == 2)) { - file->install = 0; - - uninstallCnt -= 1; - } - selected++; - - if (selected >= fileCnt) - selected = 0; - } - } - - /* 1 Button - Leathl */ - if (buttons & WPAD_BUTTON_1) - { - fatFile *tmpFile = &fileList[selected]; - char *tmpCurPath = PeekCurrentDir (); - if (tmpCurPath != NULL) { - int res = Menu_FileOperations(tmpFile, tmpCurPath); - if (res != 0) - goto getList; - } - } - - - /* A button */ - if (buttons & WPAD_BUTTON_A) - { - fatFile *tmpFile = &fileList[selected]; - char *tmpCurPath; - if (tmpFile->isdir) // wiiNinja - { - if (strcmp (tmpFile->filename, "..") == 0) - { - selected = 0; - start = 0; - - // Previous dir - tmpCurPath = PopCurrentDir(&selected, &start); - if (tmpCurPath != NULL) - sprintf(tmpPath, "%s", tmpCurPath); - - installCnt = 0; - uninstallCnt = 0; - - goto getList; - } - else if (IsListFull () == true) - { - WaitPrompt ("Maximum number of directory levels is reached.\n"); - } - else - { - tmpCurPath = PeekCurrentDir (); - if (tmpCurPath != NULL) - { - if(gDirLevel>1) - sprintf(tmpPath, "%s/%s", tmpCurPath, tmpFile->filename); - else - sprintf(tmpPath, "%s%s", tmpCurPath, tmpFile->filename); - } - // wiiNinja: Need to PopCurrentDir - PushCurrentDir (tmpPath, selected, start); - selected = 0; - start = 0; - - installCnt = 0; - uninstallCnt = 0; - - goto getList; - } - } - else - { - //If at least one WAD is marked, goto batch screen - Leathl - if ((installCnt > 0) | (uninstallCnt > 0)) { - char *thisCurPath = PeekCurrentDir (); - if (thisCurPath != NULL) { - int res = Menu_BatchProcessWads(fileList, fileCnt, thisCurPath, installCnt, uninstallCnt); - - if (res == 1) { - int counter; - for (counter = 0; counter < fileCnt; counter++) { - fatFile *temp = &fileList[counter]; - temp->install = 0; - } - - installCnt = 0; - uninstallCnt = 0; - } - } - } - //else use standard wadmanage menu - Leathl - else { - tmpCurPath = PeekCurrentDir (); - if (tmpCurPath != NULL) - Menu_WadManage(tmpFile, tmpCurPath); - } - } - } - - /* B button */ - if (buttons & WPAD_BUTTON_B) - { - if(gDirLevel<=1) - { - return; - } - - char *tmpCurPath; - selected = 0; - start = 0; - // Previous dir - tmpCurPath = PopCurrentDir(&selected, &start); - if (tmpCurPath != NULL) - sprintf(tmpPath, "%s", tmpCurPath); - goto getList; - //return; - } - - /** Scrolling **/ - /* List scrolling */ - index = (selected - start); - - if (index >= ENTRIES_PER_PAGE) - start += index - (ENTRIES_PER_PAGE - 1); - if (index <= -1) - start += index; - } - -err: - printf("\n"); - printf(" Press any button to continue...\n"); - - free (tmpPath); - - /* Wait for button */ - WaitButtons(); -} - - -void Menu_Loop(void) -{ - u8 iosVersion; - if(AHBPROT_DISABLED) - IOSPATCH_Apply(); - else - { - /* Select IOS menu */ - Menu_SelectIOS(); - } - - /* Retrieve IOS version */ - iosVersion = IOS_GetVersion(); - - ndev = &ndevList[0]; - - /* NAND device menu */ - if ((iosVersion == CIOS_VERSION || iosVersion == 250) && IOS_GetRevision() >13) - { - Menu_NandDevice(); - } - for (;;) { - /* FAT device menu */ - Menu_FatDevice(); - - /* WAD list menu */ - Menu_WadList(); - } -} - -// Start of wiiNinja's added routines - -int PushCurrentDir (char *dirStr, int Selected, int Start) -{ - int retval = 0; - - // Store dirStr into the list and increment the gDirLevel - // WARNING: Make sure dirStr is no larger than MAX_FILE_PATH_LEN - if (gDirLevel < MAX_DIR_LEVELS) - { - strcpy (gDirList [gDirLevel], dirStr); - gSeleted[gDirLevel]=Selected; - gStart[gDirLevel]=Start; - gDirLevel++; - //if (gDirLevel >= MAX_DIR_LEVELS) - // gDirLevel = 0; - } - else - retval = -1; - - return (retval); -} - -char *PopCurrentDir(int *Selected, int *Start) -{ - if (gDirLevel > 1) - gDirLevel--; - else { - gDirLevel = 0; - } - *Selected = gSeleted[gDirLevel]; - *Start = gStart[gDirLevel]; - return PeekCurrentDir(); -} - -bool IsListFull (void) -{ - if (gDirLevel < MAX_DIR_LEVELS) - return (false); - else - return (true); -} - -char *PeekCurrentDir (void) -{ - // Return the current path - if (gDirLevel > 0) - return (gDirList [gDirLevel-1]); - else - return (NULL); -} - -void WaitPrompt (char *prompt) -{ - printf("\n%s", prompt); - printf(" Press any button to continue...\n"); - - /* Wait for button */ - WaitButtons(); -} - -u32 Pad_GetButtons(void) -{ - u32 buttons = 0, cnt; - - /* Scan pads */ - PAD_ScanPads(); - - /* Get pressed buttons */ - //for (cnt = 0; cnt < MAX_WIIMOTES; cnt++) - for (cnt = 0; cnt < 4; cnt++) - buttons |= PAD_ButtonsDown(cnt); - - return buttons; -} - -u32 WiiDRC_GetButtons(void) -{ - if(!WiiDRC_Inited() || !WiiDRC_Connected()) - return 0; - - /* Scan pads */ - WiiDRC_ScanPads(); - - /* Get pressed buttons */ - return WiiDRC_ButtonsDown(); -} - -// Routine to wait for a button from either the Wiimote or a gamecube -// controller. The return value will mimic the WPAD buttons to minimize -// the amount of changes to the original code, that is expecting only -// Wiimote button presses. Note that the "HOME" button on the Wiimote -// is mapped to the "SELECT" button on the Gamecube Ctrl. (wiiNinja 5/15/2009) -u32 WaitButtons(void) -{ - u32 buttons = 0; - u32 buttonsGC = 0; - u32 buttonsDRC = 0; - - /* Wait for button pressing */ - while (!buttons && !buttonsGC && !buttonsDRC) - { - // Wii buttons - buttons = Wpad_GetButtons(); - - // GC buttons - buttonsGC = Pad_GetButtons(); - - // DRC buttons - buttonsDRC = WiiDRC_GetButtons(); - - VIDEO_WaitVSync(); - } - - if(buttons & WPAD_CLASSIC_BUTTON_A) - buttons |= WPAD_BUTTON_A; - else if(buttons & WPAD_CLASSIC_BUTTON_B) - buttons |= WPAD_BUTTON_B; - else if(buttons & WPAD_CLASSIC_BUTTON_LEFT) - buttons |= WPAD_BUTTON_LEFT; - else if(buttons & WPAD_CLASSIC_BUTTON_RIGHT) - buttons |= WPAD_BUTTON_RIGHT; - else if(buttons & WPAD_CLASSIC_BUTTON_DOWN) - buttons |= WPAD_BUTTON_DOWN; - else if(buttons & WPAD_CLASSIC_BUTTON_UP) - buttons |= WPAD_BUTTON_UP; - else if(buttons & WPAD_CLASSIC_BUTTON_HOME) - buttons |= WPAD_BUTTON_HOME; - else if(buttons & (WPAD_CLASSIC_BUTTON_X | WPAD_CLASSIC_BUTTON_PLUS)) - buttons |= WPAD_BUTTON_PLUS; - else if(buttons & (WPAD_CLASSIC_BUTTON_Y | WPAD_CLASSIC_BUTTON_MINUS)) - buttons |= WPAD_BUTTON_MINUS; - else if(buttons & WPAD_CLASSIC_BUTTON_ZR) - buttons |= WPAD_BUTTON_1; - - if (buttonsGC) - { - if(buttonsGC & PAD_BUTTON_A) - buttons |= WPAD_BUTTON_A; - else if(buttonsGC & PAD_BUTTON_B) - buttons |= WPAD_BUTTON_B; - else if(buttonsGC & PAD_BUTTON_LEFT) - buttons |= WPAD_BUTTON_LEFT; - else if(buttonsGC & PAD_BUTTON_RIGHT) - buttons |= WPAD_BUTTON_RIGHT; - else if(buttonsGC & PAD_BUTTON_DOWN) - buttons |= WPAD_BUTTON_DOWN; - else if(buttonsGC & PAD_BUTTON_UP) - buttons |= WPAD_BUTTON_UP; - else if(buttonsGC & PAD_BUTTON_START) - buttons |= WPAD_BUTTON_HOME; - else if(buttonsGC & PAD_BUTTON_X) - buttons |= WPAD_BUTTON_PLUS; - else if(buttonsGC & PAD_BUTTON_Y) - buttons |= WPAD_BUTTON_MINUS; - else if(buttonsGC & PAD_TRIGGER_Z) - buttons |= WPAD_BUTTON_1; - } - - if (buttonsDRC) - { - if(buttonsDRC & WIIDRC_BUTTON_A) - buttons |= WPAD_BUTTON_A; - else if(buttonsDRC & WIIDRC_BUTTON_B) - buttons |= WPAD_BUTTON_B; - else if(buttonsDRC & WIIDRC_BUTTON_LEFT) - buttons |= WPAD_BUTTON_LEFT; - else if(buttonsDRC & WIIDRC_BUTTON_RIGHT) - buttons |= WPAD_BUTTON_RIGHT; - else if(buttonsDRC & WIIDRC_BUTTON_DOWN) - buttons |= WPAD_BUTTON_DOWN; - else if(buttonsDRC & WIIDRC_BUTTON_UP) - buttons |= WPAD_BUTTON_UP; - else if(buttonsDRC & WIIDRC_BUTTON_HOME) - buttons |= WPAD_BUTTON_HOME; - else if(buttonsDRC & (WIIDRC_BUTTON_X | WIIDRC_BUTTON_PLUS)) - buttons |= WPAD_BUTTON_PLUS; - else if(buttonsDRC & (WIIDRC_BUTTON_Y | WIIDRC_BUTTON_MINUS)) - buttons |= WPAD_BUTTON_MINUS; - else if(buttonsDRC & WIIDRC_BUTTON_ZR) - buttons |= WPAD_BUTTON_1; - } - - return buttons; -} // WaitButtons - - -void WiiLightControl (int state) -{ - switch (state) - { - case WII_LIGHT_ON: - /* Turn on Wii Light */ - WIILIGHT_SetLevel(255); - WIILIGHT_TurnOn(); - break; - - case WII_LIGHT_OFF: - default: - /* Turn off Wii Light */ - WIILIGHT_SetLevel(0); - WIILIGHT_TurnOn(); - WIILIGHT_Toggle(); - break; - } -} // WiiLightControl - diff --git a/data/appboot.bin b/data/appboot.bin index 5adac459423dac4432305c19396a35d0625a020f..08ab355d664e8e4627589f9249348f3528cbc19b 100644 GIT binary patch delta 33839 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 diff --git a/source/fat.c b/source/fat.c index 5c8081b..b20cecf 100644 --- a/source/fat.c +++ b/source/fat.c @@ -1,58 +1,104 @@ #include #include #include +#include +#include +#include +#include + //#include #include "fat.h" +#include "usbstorage.h" - -s32 Fat_Mount(fatDevice *dev) +typedef struct { - s32 ret; + /* Device prefix */ + char* prefix; - /* Initialize interface */ - ret = dev->interface->startup(); - if (!ret) - return -1; + /* Device name */ + char* name; - /* Mount device */ - ret = fatMountSimple(dev->mount, dev->interface); - if (!ret) - return -1; + /* Device available */ + bool isMounted; - return 0; + /* Device interface */ + const DISC_INTERFACE* interface; +} FatDevice; + +static FatDevice DeviceList[] = +{ + { "sd", "Wii SD Slot", false, &__io_wiisd }, + { "usb", "USB Mass Storage Device", false, &__io_usbstorage }, + { "usb2", "USB 2.0 Mass Storage Device", false, &__io_wiiums }, + { "gcsda", "SD Gecko (Slot A)", false, &__io_gcsda }, + { "gcsdb", "SD Gecko (Slot B)", false, &__io_gcsdb }, +}; + +static u32 gNumDevices = 0; +FatDevice* gDevices[(sizeof(DeviceList) / sizeof(FatDevice))]; + +void FatMount() +{ + FatUnmount(); + + s32 i; + for (i = 0; i < (sizeof(DeviceList) / sizeof(FatDevice)); i++) + { + gDevices[gNumDevices] = &DeviceList[i]; + + s32 ret = gDevices[gNumDevices]->interface->startup(); + + if (!ret) + continue; + + ret = fatMountSimple(gDevices[gNumDevices]->prefix, gDevices[gNumDevices]->interface); + + if (!ret) + continue; + + gDevices[gNumDevices]->isMounted = true; + gNumDevices++; + } } -void Fat_Unmount(fatDevice *dev) +void FatUnmount() { - /* Unmount device */ - fatUnmount(dev->mount); - - /* Shutdown interface */ - dev->interface->shutdown(); -} - -char *Fat_ToFilename(const char *filename) -{ - static char buffer[128]; - - u32 cnt, idx, len; - - /* Clear buffer */ - memset(buffer, 0, sizeof(buffer)); - - /* Get filename length */ - len = strlen(filename); - - for (cnt = idx = 0; idx < len; idx++) { - char c = filename[idx]; - - /* Valid characters */ - if ( (c >= '#' && c <= ')') || (c >= '-' && c <= '.') || - (c >= '0' && c <= '9') || (c >= 'A' && c <= 'z') || - (c >= 'a' && c <= 'z') || (c == '!') ) - buffer[cnt++] = c; + s32 i; + for (i = 0; i < FatGetDeviceCount(); i++) + { + fatUnmount(gDevices[i]->prefix); + gDevices[i]->interface->shutdown(); + gDevices[i]->isMounted = false; } - return buffer; + gNumDevices = 0; +} + +char* FatGetDeviceName(u8 index) +{ + if (index >= FatGetDeviceCount()) + return NULL; + + if (gDevices[index]->isMounted) + return gDevices[index]->name; + + return NULL; +} + + +char* FatGetDevicePrefix(u8 index) +{ + if (index >= FatGetDeviceCount()) + return NULL; + + if (gDevices[index]->isMounted) + return gDevices[index]->prefix; + + return NULL; +} + +s32 FatGetDeviceCount() +{ + return gNumDevices; } diff --git a/source/fat.h b/source/fat.h index bf869df..26538f2 100644 --- a/source/fat.h +++ b/source/fat.h @@ -1,27 +1,6 @@ #ifndef _FAT_H_ #define _FAT_H_ -/* libfat header */ -#include -#include - -/* SD headers */ -#include -#include - - -/* 'FAT Device' structure */ -typedef struct { - /* Device mount point */ - char *mount; - - /* Device name */ - char *name; - - /* Device interface */ - const DISC_INTERFACE *interface; -} fatDevice; - /* 'FAT File' structure */ typedef struct { /* Filename */ @@ -41,9 +20,11 @@ typedef struct { /* Prototypes */ -s32 Fat_Mount(fatDevice *); -void Fat_Unmount(fatDevice *); -char *Fat_ToFilename(const char *); + +void FatMount(); +void FatUnmount(); +char* FatGetDeviceName(u8 index); +char* FatGetDevicePrefix(u8 index); +s32 FatGetDeviceCount(); #endif - diff --git a/source/fileops.c b/source/fileops.c new file mode 100644 index 0000000..5b52bd7 --- /dev/null +++ b/source/fileops.c @@ -0,0 +1,80 @@ +#include +#include + +#include "fileops.h" + + +bool FSOPFileExists(const char* file) +{ + FILE* f; + f = fopen(file, "rb"); + if (f) + { + fclose(f); + return true; + } + return false; +} + +bool FSOPFolderExists(const char* path) +{ + DIR* dir; + dir = opendir(path); + if (dir) + { + closedir(dir); + return true; + } + return false; +} + +size_t FSOPGetFileSizeBytes(const char* path) +{ + FILE* f; + size_t size = 0; + + f = fopen(path, "rb"); + if (!f) + return 0; + + fseek(f, 0, SEEK_END); + size = ftell(f); + fclose(f); + + return size; +} + +void FSOPDeleteFile(const char* file) +{ + if (!FSOPFileExists(file)) + return; + + remove(file); +} + +void FSOPMakeFolder(const char* path) +{ + if (FSOPFolderExists(path)) + return; + + mkdir(path, S_IREAD | S_IWRITE); +} + +s32 FSOPReadOpenFile(FILE* fp, void* buffer, u32 offset, u32 length) +{ + fseek(fp, offset, SEEK_SET); + return fread(buffer, length, 1, fp); +} + +s32 FSOPReadOpenFileA(FILE* fp, void** buffer, u32 offset, u32 length) +{ + *buffer = memalign(32, length); + if (!*buffer) + return -1; + + s32 ret = FSOPReadOpenFile(fp, *buffer, offset, length); + if (ret < 0) + free(*buffer); + + return ret; +} diff --git a/source/fileops.h b/source/fileops.h new file mode 100644 index 0000000..b912721 --- /dev/null +++ b/source/fileops.h @@ -0,0 +1,17 @@ +#ifndef __FILEOPS_H__ +#define __FILEOPS_H__ + +#include +#include + +bool FSOPFileExists(const char* file); +bool FSOPFolderExists(const char* path); +size_t FSOPGetFileSizeBytes(const char* path); + +void FSOPDeleteFile(const char* file); +void FSOPMakeFolder(const char* path); + +s32 FSOPReadOpenFile(FILE* fp, void* buffer, u32 offset, u32 length); +s32 FSOPReadOpenFileA(FILE* fp, void** buffer, u32 offset, u32 length); + +#endif \ No newline at end of file diff --git a/source/globals.h b/source/globals.h index 1db1364..5172e99 100644 --- a/source/globals.h +++ b/source/globals.h @@ -3,7 +3,7 @@ // Constants #define CIOS_VERSION 249 -#define ENTRIES_PER_PAGE 14 +#define ENTRIES_PER_PAGE 12 #define MAX_FILE_PATH_LEN 1024 #define MAX_DIR_LEVELS 10 #define WAD_DIRECTORY "/" @@ -16,20 +16,8 @@ #define WM_CONFIG_FILE_PATH ":/wad/wm_config.txt" #define WM_BACKGROUND_PATH ":/wad/background.png" -// These are indices into the fatDevice fdevList -#define FAT_DEVICE_INDEX_WII_SD 0 -#define FAT_DEVICE_INDXE_USB 1 -#define FAT_DEVICE_INDEX_USB2 2 -#define FAT_DEVICE_INDEX_GC_SDA 3 -#define FAT_DEVICE_INDEX_GC_SDB 4 #define FAT_DEVICE_INDEX_INVALID -1 - -// These are the indices into the nandDevice ndevList -#define NAND_DEVICE_INDEX_DISABLE 0 -#define NAND_DEVICE_INDEX_SD 1 -#define NAND_DEVICE_INDEX_USB 2 #define NAND_DEVICE_INDEX_INVALID -1 - #define CIOS_VERSION_INVALID -1 // For the WiiLight @@ -52,7 +40,7 @@ typedef struct extern CONFIG gConfig; extern nandDevice ndevList[]; -extern fatDevice fdevList[]; +//extern fatDevice fdevList[]; #endif diff --git a/source/gui.c b/source/gui.c index 2305e9a..dffad46 100644 --- a/source/gui.c +++ b/source/gui.c @@ -7,6 +7,7 @@ #include "menu.h" #include "nand.h" #include "globals.h" +#include "fileops.h" /* Constants */ #define CONSOLE_XCOORD 70 @@ -14,36 +15,25 @@ #define CONSOLE_WIDTH 502 #define CONSOLE_HEIGHT 300 -bool file_exists(const char * filename) -{ - FILE * file; - if ((file = fopen(filename, "r"))) - { - fclose(file); - return true; - } - return false; -} - - s32 __Gui_DrawPng(void *img, u32 x, u32 y) { IMGCTX ctx = NULL; PNGUPROP imgProp; + char path[1024]; + s32 ret = -1; + s32 i; - s32 ret; - - fatDevice *fdev = &fdevList[0]; - ret = Fat_Mount(fdev); - if (file_exists("sd:/wad/background.png")) ctx = PNGU_SelectImageFromDevice ("sd:/wad/background.png"); - - if (ret < 0) + for (i = 0; i < FatGetDeviceCount(); i++) { - fdev = &fdevList[2]; - Fat_Mount(fdev); - if (file_exists("usb2:/wad/background.png")) ctx = PNGU_SelectImageFromDevice ("usb2:/wad/background.png"); + snprintf(path, sizeof(path), "%s:/wad/background.png", FatGetDevicePrefix(i)); + if (FSOPFileExists(path)) + { + ctx = PNGU_SelectImageFromDevice(path); + break; + } + } - + if(!ctx) { /* Select PNG data */ @@ -53,6 +43,7 @@ s32 __Gui_DrawPng(void *img, u32 x, u32 y) goto out; } } + /* Get image properties */ ret = PNGU_GetImageProperties(ctx, &imgProp); if (ret != PNGU_OK) { diff --git a/source/iospatch.c b/source/iospatch.c index 50d97e5..4895266 100644 --- a/source/iospatch.c +++ b/source/iospatch.c @@ -74,6 +74,8 @@ const u8 ES_TitleVersionCheck_old[] = { 0xD2, 0x01, 0x4E, 0x56 }; const u8 ES_TitleVersionCheck_patch[] = { 0xE0, 0x01, 0x4E, 0x56 }; const u8 ES_TitleDeleteCheck_old[] = { 0xD8, 0x00, 0x4A, 0x04 }; const u8 ES_TitleDeleteCheck_patch[] = { 0xE0, 0x00, 0x4A, 0x04 }; +const u8 isfs_permissions_old[] = { 0x9B, 0x05, 0x40, 0x03, 0x99, 0x05, 0x42, 0x8B, }; +const u8 isfs_permissions_patch[] = { 0x9B, 0x05, 0x40, 0x03, 0x1C, 0x0B, 0x42, 0x8B, }; //Following patches made my damysteryman for use with Wii U's vWii const u8 Kill_AntiSysTitleInstallv3_pt1_old[] = { 0x68, 0x1A, 0x2A, 0x01, 0xD0, 0x05 }; // Make sure that the pt1 @@ -97,7 +99,7 @@ u32 IOSPATCH_Apply() { if (AHBPROT_DISABLED) { disable_memory_protection(); //count += apply_patch("di_readlimit", di_readlimit_old, sizeof(di_readlimit_old), di_readlimit_patch, sizeof(di_readlimit_patch), 12); - //count += apply_patch("isfs_permissions", isfs_permissions_old, sizeof(isfs_permissions_old), isfs_permissions_patch, sizeof(isfs_permissions_patch), 0); + count += apply_patch("isfs_permissions", isfs_permissions_old, sizeof(isfs_permissions_old), isfs_permissions_patch, sizeof(isfs_permissions_patch), 0); //count += apply_patch("es_setuid", setuid_old, sizeof(setuid_old), setuid_patch, sizeof(setuid_patch), 0); //count += apply_patch("es_identify", es_identify_old, sizeof(es_identify_old), es_identify_patch, sizeof(es_identify_patch), 2); count += apply_patch("hash_check", hash_old, sizeof(hash_old), hash_patch, sizeof(hash_patch), 1); diff --git a/source/menu.c b/source/menu.c index 0ed273d..de46ab2 100644 --- a/source/menu.c +++ b/source/menu.c @@ -12,7 +12,6 @@ #include "nand.h" #include "restart.h" #include "title.h" -#include "usbstorage.h" #include "utils.h" #include "video.h" #include "wad.h" @@ -21,27 +20,16 @@ #include "globals.h" #include "iospatch.h" #include "appboot.h" - -/* FAT device list */ -//static fatDevice fdevList[] = { -fatDevice fdevList[] = { - { "sd", "Wii SD Slot", &__io_wiisd }, - { "usb", "USB Mass Storage Device", &__io_usbstorage }, - { "usb2", "USB 2.0 Mass Storage Device", &__io_wiiums }, - { "gcsda", "SD Gecko (Slot A)", &__io_gcsda }, - { "gcsdb", "SD Gecko (Slot B)", &__io_gcsdb }, -}; +#include "fileops.h" /* NAND device list */ -//static nandDevice ndevList[] = { -nandDevice ndevList[] = { +nandDevice ndevList[] = +{ { "Disable", 0, 0x00, 0x00 }, { "SD/SDHC Card", 1, 0xF0, 0xF1 }, { "USB 2.0 Mass Storage Device", 2, 0xF2, 0xF3 }, }; -/* FAT device */ -static fatDevice *fdev = NULL; static nandDevice *ndev = NULL; // wiiNinja: Define a buffer holding the previous path names as user @@ -50,9 +38,12 @@ static u8 gDirLevel = 0; static char gDirList [MAX_DIR_LEVELS][MAX_FILE_PATH_LEN]; static s32 gSeleted[MAX_DIR_LEVELS]; static s32 gStart[MAX_DIR_LEVELS]; +static char gMenuRegion = '\0'; +static u16 gMenuVersion = 0; +static u8 gSelected = 0; /* Macros */ -#define NB_FAT_DEVICES (sizeof(fdevList) / sizeof(fatDevice)) +//#define NB_FAT_DEVICES (sizeof(fdevList) / sizeof(fatDevice)) #define NB_NAND_DEVICES (sizeof(ndevList) / sizeof(nandDevice)) // Local prototypes: wiiNinja @@ -92,34 +83,6 @@ int __Menu_EntryCmp(const void *p1, const void *p2) return strcasecmp(f1->filename, f2->filename); } -static bool __FolderExists(const char *path) -{ - DIR *dir; - dir = opendir(path); - if(dir) - { - closedir(dir); - return true; - } - return false; -} - -static size_t __GetFileSizeBytes(const char *path) -{ - FILE *f; - size_t size = 0; - - f = fopen(path, "rb"); - if(!f) return 0; - - //Get file size - fseek(f, 0, SEEK_END); - size = ftell(f); - fclose(f); - - return size; -} - char gFileName[MAX_FILE_PATH_LEN]; s32 __Menu_RetrieveList(char *inPath, fatFile **outbuf, u32 *outlen) { @@ -165,7 +128,7 @@ s32 __Menu_RetrieveList(char *inPath, fatFile **outbuf, u32 *outlen) size_t fsize = 0; snprintf(gFileName, MAX_FILE_PATH_LEN, "%s/%s", inPath, ent->d_name); - if (__FolderExists(gFileName)) // wiiNinja + if (FSOPFolderExists(gFileName)) // wiiNinja { isdir = true; // Add only the item ".." which is the previous directory @@ -181,19 +144,19 @@ s32 __Menu_RetrieveList(char *inPath, fatFile **outbuf, u32 *outlen) { if(!strcasecmp(ent->d_name+strlen(ent->d_name)-4, ".wad")) { - fsize = __GetFileSizeBytes(gFileName); + fsize = FSOPGetFileSizeBytes(gFileName); addFlag = true; iswad = true; } if(!strcasecmp(ent->d_name+strlen(ent->d_name)-4, ".dol")) { - fsize = __GetFileSizeBytes(gFileName); + fsize = FSOPGetFileSizeBytes(gFileName); addFlag = true; isdol = true; } if(!strcasecmp(ent->d_name+strlen(ent->d_name)-4, ".elf")) { - fsize = __GetFileSizeBytes(gFileName); + fsize = FSOPGetFileSizeBytes(gFileName); addFlag = true; iself = true; } @@ -333,74 +296,94 @@ void Menu_SelectIOS(void) void Menu_FatDevice(void) { - int ret, selected = 0; + FatMount(); - /* Unmount FAT device */ - //if (fdev) - //Fat_Unmount(fdev); - //if (((fdevList[selected].mount[0] == 's') && (ndev->name[0] == 'S'))) - //selected++; - static const u16 konamiCode[] = { + const u16 konamiCode[] = + { WPAD_BUTTON_UP, WPAD_BUTTON_UP, WPAD_BUTTON_DOWN, WPAD_BUTTON_DOWN, WPAD_BUTTON_LEFT, WPAD_BUTTON_RIGHT, WPAD_BUTTON_LEFT, WPAD_BUTTON_RIGHT, WPAD_BUTTON_B, WPAD_BUTTON_A }; int codePosition = 0; + extern bool skipRegionSafetyCheck; /* Select source device */ if (gConfig.fatDeviceIndex < 0) { - for (;;) { + for (;;) + { /* Clear console */ Con_Clear(); + if (!FatGetDeviceCount()) + { + printf("\t[+] No source device: < %s >\n\n", FatGetDeviceName(gSelected)); + } + /* Selected device */ - fdev = &fdevList[selected]; - - printf("\t>> Select source device: < %s >\n\n", fdev->name); - + //printf("\tWii menu version: %d, region: %s\n\n", gMenuVersion, GetSysMenuRegionString(&gMenuRegion)); + printf("\t>> Select source device: < %s >\n\n", FatGetDeviceName(gSelected)); printf("\t Press LEFT/RIGHT to change the selected device.\n\n"); - printf("\t Press A button to continue.\n"); + printf("\t Press B button to remount source devices.\n"); printf("\t Press HOME button to restart.\n\n"); + if (skipRegionSafetyCheck) + { + printf("[+] WARNING: SM Region checks disabled!\n\n"); + printf("\t Press 1 button to reset.\n"); + } + + u32 buttons = WaitButtons(); - if (buttons & (WPAD_BUTTON_UP | WPAD_BUTTON_DOWN | WPAD_BUTTON_RIGHT | WPAD_BUTTON_LEFT | WPAD_BUTTON_A | WPAD_BUTTON_B)) { - if (buttons & konamiCode[codePosition]) - ++codePosition; - else - codePosition = 0; + if (buttons & (WPAD_BUTTON_UP | WPAD_BUTTON_DOWN | WPAD_BUTTON_RIGHT | WPAD_BUTTON_LEFT | WPAD_BUTTON_A | WPAD_BUTTON_B)) + { + if (!skipRegionSafetyCheck) + { + if (buttons & konamiCode[codePosition]) + ++codePosition; + else + codePosition = 0; + } } - - /* LEFT/RIGHT buttons */ - if (buttons & WPAD_BUTTON_LEFT) { - if ((--selected) <= -1) - selected = (NB_FAT_DEVICES - 1); - if ((fdevList[selected].mount[0] == 's') && (ndev->name[0] == 'S')) - selected--; - if ((fdevList[selected].mount[0] == 'u' && fdevList[selected].mount[3] == '2') && (ndev->name[0] == 'U')) - selected--; - if ((selected) <= -1) - selected = (NB_FAT_DEVICES - 1); + if (buttons & WPAD_BUTTON_LEFT) + { + if ((s8)(--gSelected) < 0) + gSelected = FatGetDeviceCount() - 1; } - if (buttons & WPAD_BUTTON_RIGHT) { - if ((++selected) >= NB_FAT_DEVICES) - selected = 0; - if ((fdevList[selected].mount[0] == 's') && (ndev->name[0] == 'S')) - selected++; - if ((fdevList[selected].mount[0] == 'u' && fdevList[selected].mount[3] == '2') && (ndev->name[0] == 'U')) - selected++; + else if (buttons & WPAD_BUTTON_1 && skipRegionSafetyCheck) + { + skipRegionSafetyCheck = false; } - - /* HOME button */ - if (buttons & WPAD_BUTTON_HOME) + else if (buttons & WPAD_BUTTON_RIGHT) + { + if ((++gSelected) >= FatGetDeviceCount()) + gSelected = 0; + } + else if (buttons & WPAD_BUTTON_HOME) + { Restart(); - - /* A button */ - if (buttons & WPAD_BUTTON_A) { - if (codePosition == sizeof(konamiCode) / sizeof(konamiCode[0])) { - extern bool skipRegionSafetyCheck; + } + else if (buttons & WPAD_BUTTON_B && !codePosition) + { + printf("\t\t[-] Mounting devices."); + usleep(500000); + printf("\r\t\t[\\]"); + usleep(500000); + printf("\r\t\t[|]"); + usleep(500000); + printf("\r\t\t[/]"); + usleep(500000); + printf("\r\t\t[-]"); + FatMount(); + gSelected = 0; + usleep(500000); + } + else if (buttons & WPAD_BUTTON_A) + { + if (codePosition == sizeof(konamiCode) / sizeof(konamiCode[0])) + { skipRegionSafetyCheck = true; printf("[+] Disabled SM region checks\n"); sleep(3); @@ -412,34 +395,12 @@ void Menu_FatDevice(void) else { sleep(5); - fdev = &fdevList[gConfig.fatDeviceIndex]; + if (gConfig.fatDeviceIndex < FatGetDeviceCount()) + gSelected = gConfig.fatDeviceIndex; } - printf("[+] Mounting %s, please wait...", fdev->name ); - fflush(stdout); - - /* Mount FAT device */ - - ret = Fat_Mount(fdev); - if (ret < 0) { - printf(" ERROR! (ret = %d)\n", ret); - goto err; - } else - printf(" OK!\n"); + printf("[+] Selected source device: %s.", FatGetDeviceName(gSelected)); sleep(2); - return; - -err: - - if(gConfig.fatDeviceIndex >= 0) gConfig.fatDeviceIndex = -1; - WiiLightControl (WII_LIGHT_OFF); - printf("\n"); - printf(" Press any button to continue...\n"); - - WaitButtons(); - - /* Prompt menu again */ - Menu_FatDevice(); } void Menu_NandDevice(void) @@ -845,45 +806,58 @@ void Menu_WadList(void) gDirLevel = 0; - // push root dir as base folder - sprintf(tmpPath, "%s:%s", fdev->mount, WAD_DIRECTORY); + // push root dir as base folderGetDevice() + //sprintf(tmpPath, "%s:%s", fdev->mount, WAD_DIRECTORY); + sprintf(tmpPath, "%s:%s", FatGetDevicePrefix(gSelected), WAD_DIRECTORY); PushCurrentDir(tmpPath,0,0); // if user provides startup directory, try it out first if (strcmp (WAD_DIRECTORY, gConfig.startupPath) != 0) { // replace root dir with provided startup directory - sprintf(tmpPath, "%s:%s", fdev->mount, gConfig.startupPath); - // If the directory can be successfully opened, it must exists - DIR *tmpDirPtr = opendir(tmpPath); - if (tmpDirPtr) - { - closedir (tmpDirPtr); - PushCurrentDir(tmpPath,0,0); - } - else // unable to open provided dir, stick with root dir - sprintf(tmpPath, "%s:%s", fdev->mount, WAD_DIRECTORY); + sprintf(tmpPath, "%s:%s", FatGetDevicePrefix(gSelected), gConfig.startupPath); + + if (FSOPFolderExists(tmpPath)) + PushCurrentDir(tmpPath, 0, 0); + else + sprintf(tmpPath, "%s:%s", FatGetDevicePrefix(gSelected), WAD_DIRECTORY); + + // If the directory can be successfully opened, it must exists + // DIR *tmpDirPtr = opendir(tmpPath); + // if (tmpDirPtr) + // { + // closedir (tmpDirPtr); + // PushCurrentDir(tmpPath,0,0); + // } + //else // unable to open provided dir, stick with root dir + //{ + // sprintf(tmpPath, "%s:%s", FatGetDevicePrefix(gSelected), WAD_DIRECTORY); + //} + } /* Retrieve filelist */ getList: - free (fileList); + free(fileList); fileList = NULL; ret = __Menu_RetrieveList(tmpPath, &fileList, &fileCnt); - if (ret < 0) { + if (ret < 0) + { printf(" ERROR! (ret = %d)\n", ret); goto err; } /* No files */ - if (!fileCnt) { + if (!fileCnt) + { printf(" No files found!\n"); goto err; } /* Set install-values to 0 - Leathl */ int counter; - for (counter = 0; counter < fileCnt; counter++) { + for (counter = 0; counter < fileCnt; counter++) + { fatFile *file = &fileList[counter]; file->install = 0; } @@ -898,8 +872,8 @@ getList: /** Print entries **/ cnt = strlen(tmpPath); - if(cnt>30) - index = cnt-30; + if(cnt > 30) + index = cnt - 30; else index = 0; @@ -916,13 +890,16 @@ getList: break; strncpy(str, file->filename, 40); //Only 40 chars to fit the screen - str[40]=0; + str[40] = 0; /* Print filename */ //printf("\t%2s %s (%.2f MB)\n", (cnt == selected) ? ">>" : " ", file->filename, filesize); if (file->isdir) // wiiNinja + { printf("\t%2s [%s]\n", (cnt == selected) ? ">>" : " ", str); - else { + } + else + { if(file->iswad) printf("\t%2s%s%s (%.2f MB)\n", (cnt == selected) ? ">>" : " ", (file->install == 1) ? "+" : ((file->install == 2) ? "-" : " "), str, filesize); else @@ -932,270 +909,285 @@ getList: printf("\n"); fatFile *file = &fileList[selected]; - if(file->iswad) - printf("[+] Press A to (un)install."); - else if(file->isdol || file->iself) - printf("[+] Press A to launch dol/elf."); - else if(file->isdir) - printf("[+] Press A to Enter directory."); - if(gDirLevel>1) + + if (file->iswad) + printf("[+] Press A to (un)install."); + else if (file->isdol || file->iself) + printf("[+] Press A to launch dol/elf."); + else if (file->isdir) + printf("[+] Press A to Enter directory."); + + if (gDirLevel > 1) printf(" Press B to go up-level DIR.\n"); else printf(" Press B to select a device.\n"); - if(file->iswad) printf(" Use +/X and -/Y to (un)mark. Press 1/Z/ZR for delete menu."); + + if (file->iswad) + printf(" Use +/X and -/Y to (un)mark. Press 1/Z/ZR for delete menu."); /** Controls **/ u32 buttons = WaitButtons(); /* DPAD buttons */ - if (buttons & WPAD_BUTTON_UP) { - selected--; - - if (selected <= -1) + if (buttons & WPAD_BUTTON_UP) + { + if (--selected < 0) selected = (fileCnt - 1); } - if (buttons & WPAD_BUTTON_LEFT) { - selected = selected + ENTRIES_PER_PAGE; + else if (buttons & WPAD_BUTTON_LEFT) + { + selected += ENTRIES_PER_PAGE; if (selected >= fileCnt) selected = 0; } - if (buttons & WPAD_BUTTON_DOWN) { - selected ++; - - if (selected >= fileCnt) + else if (buttons & WPAD_BUTTON_DOWN) + { + if (++selected >= fileCnt) selected = 0; } - if (buttons & WPAD_BUTTON_RIGHT) { - selected = selected - ENTRIES_PER_PAGE; + else if (buttons & WPAD_BUTTON_RIGHT) + { + selected -= ENTRIES_PER_PAGE; - if (selected <= -1) + if (selected < 0) selected = (fileCnt - 1); } - - /* HOME button */ - if (buttons & WPAD_BUTTON_HOME) + else if (buttons & WPAD_BUTTON_HOME) + { Restart(); - if(file->iswad) { - /* Plus Button - Leathl */ - if (buttons & WPAD_BUTTON_PLUS) + } + + if (file->iswad) { - if(Wpad_TimeButton()) + /* Plus Button - Leathl */ + if (buttons & WPAD_BUTTON_PLUS) { - installCnt = 0; - int i = 0; - while( i < fileCnt) - { - fatFile *file = &fileList[i]; - if (((file->isdir) == false) & (file->install == 0)) { - file->install = 1; + if(Wpad_TimeButton()) + { + installCnt = 0; + int i = 0; + while( i < fileCnt) + { + fatFile *file = &fileList[i]; + if (((file->isdir) == false) && (file->install == 0)) + { + file->install = 1; + installCnt += 1; + } + else if (((file->isdir) == false) && (file->install == 1)) + { + file->install = 0; + installCnt--; + } + else if (((file->isdir) == false) && (file->install == 2)) + { + file->install = 1; - installCnt += 1; - } - else if (((file->isdir) == false) & (file->install == 1)) { - file->install = 0; + installCnt++; + uninstallCnt--; + } - installCnt -= 1; - } - else if (((file->isdir) == false) & (file->install == 2)) { - file->install = 1; - - installCnt += 1; - uninstallCnt -= 1; - } - i++; - } + i++; + } + } + else + { + fatFile *file = &fileList[selected]; + if (((file->isdir) == false) && (file->install == 0)) + { + file->install = 1; + installCnt++; + } + else if (((file->isdir) == false) & (file->install == 1)) + { + file->install = 0; + installCnt--; + } + else if (((file->isdir) == false) & (file->install == 2)) + { + file->install = 1; + installCnt++; + uninstallCnt--; + } + + selected++; + if (selected >= fileCnt) + selected = 0; + } } - else + + /* Minus Button - Leathl */ + else if (buttons & WPAD_BUTTON_MINUS) { - fatFile *file = &fileList[selected]; - if (((file->isdir) == false) & (file->install == 0)) { - file->install = 1; + if(Wpad_TimeButton()) + { + installCnt = 0; + int i = 0; + + while( i < fileCnt) + { + fatFile *file = &fileList[i]; + if (((file->isdir) == false) && (file->install == 0)) + { + file->install = 2; + uninstallCnt++; + } + else if (((file->isdir) == false) && (file->install == 1)) + { + file->install = 2; + uninstallCnt++; + installCnt--; + } + else if (((file->isdir) == false) & (file->install == 2)) + { + file->install = 0; + uninstallCnt--; + } - installCnt += 1; - } - else if (((file->isdir) == false) & (file->install == 1)) { - file->install = 0; - - installCnt -= 1; - } - else if (((file->isdir) == false) & (file->install == 2)) { - file->install = 1; - - installCnt += 1; - uninstallCnt -= 1; - } - selected++; - - if (selected >= fileCnt) - selected = 0; + i++; + } + } + else + { + fatFile *file = &fileList[selected]; + if (((file->isdir) == false) && (file->install == 0)) + { + file->install = 2; + uninstallCnt++; + } + else if (((file->isdir) == false) && (file->install == 1)) + { + file->install = 2; + uninstallCnt++; + installCnt--; + } + else if (((file->isdir) == false) && (file->install == 2)) + { + file->install = 0; + uninstallCnt -= 1; + } + + selected++; + if (selected >= fileCnt) + selected = 0; + } } - } - - /* Minus Button - Leathl */ - if (buttons & WPAD_BUTTON_MINUS) - { - if(Wpad_TimeButton()) - { - installCnt = 0; - int i = 0; - while( i < fileCnt) - { - fatFile *file = &fileList[i]; - if (((file->isdir) == false) & (file->install == 0)) { - file->install = 2; - - uninstallCnt += 1; - } - else if (((file->isdir) == false) & (file->install == 1)) { - file->install = 2; - - uninstallCnt += 1; - installCnt -= 1; - } - else if (((file->isdir) == false) & (file->install == 2)) { - file->install = 0; - - uninstallCnt -= 1; - } - i++; - } - - } - else - { - fatFile *file = &fileList[selected]; - if (((file->isdir) == false) & (file->install == 0)) { - file->install = 2; - - uninstallCnt += 1; - } - else if (((file->isdir) == false) & (file->install == 1)) { - file->install = 2; - - uninstallCnt += 1; - installCnt -= 1; - } - else if (((file->isdir) == false) & (file->install == 2)) { - file->install = 0; - - uninstallCnt -= 1; - } - selected++; - - if (selected >= fileCnt) - selected = 0; - } - } } + /* 1 Button - Leathl */ if (buttons & WPAD_BUTTON_1) { fatFile *tmpFile = &fileList[selected]; - char *tmpCurPath = PeekCurrentDir (); - if (tmpCurPath != NULL) { + char *tmpCurPath = PeekCurrentDir(); + if (tmpCurPath != NULL) + { int res = Menu_FileOperations(tmpFile, tmpCurPath); if (res != 0) goto getList; } } - /* A button */ - if (buttons & WPAD_BUTTON_A) + else if (buttons & WPAD_BUTTON_A) { - fatFile *tmpFile = &fileList[selected]; - char *tmpCurPath; - if (tmpFile->isdir) // wiiNinja + fatFile *tmpFile = &fileList[selected]; + char *tmpCurPath; + if (tmpFile->isdir) // wiiNinja + { + if (strcmp (tmpFile->filename, "..") == 0) { - if (strcmp (tmpFile->filename, "..") == 0) - { - selected = 0; - start = 0; + selected = 0; + start = 0; - // Previous dir - tmpCurPath = PopCurrentDir(&selected, &start); - if (tmpCurPath != NULL) - sprintf(tmpPath, "%s", tmpCurPath); + // Previous dir + tmpCurPath = PopCurrentDir(&selected, &start); + if (tmpCurPath != NULL) + sprintf(tmpPath, "%s", tmpCurPath); - installCnt = 0; - uninstallCnt = 0; + installCnt = 0; + uninstallCnt = 0; - goto getList; - } - else if (IsListFull () == true) - { - WaitPrompt ("Maximum number of directory levels is reached.\n"); - } - else - { - tmpCurPath = PeekCurrentDir (); - if (tmpCurPath != NULL) - { - if(gDirLevel>1) - sprintf(tmpPath, "%s/%s", tmpCurPath, tmpFile->filename); - else - sprintf(tmpPath, "%s%s", tmpCurPath, tmpFile->filename); - } - // wiiNinja: Need to PopCurrentDir - PushCurrentDir (tmpPath, selected, start); - selected = 0; - start = 0; - - installCnt = 0; - uninstallCnt = 0; - - goto getList; - } + goto getList; + } + else if (IsListFull() == true) + { + WaitPrompt ("Maximum number of directory levels is reached.\n"); } else { - //If at least one WAD is marked, goto batch screen - Leathl - if ((installCnt > 0) | (uninstallCnt > 0)) { - char *thisCurPath = PeekCurrentDir (); - if (thisCurPath != NULL) { - int res = Menu_BatchProcessWads(fileList, fileCnt, thisCurPath, installCnt, uninstallCnt); + tmpCurPath = PeekCurrentDir (); + if (tmpCurPath != NULL) + { + if(gDirLevel > 1) + sprintf(tmpPath, "%s/%s", tmpCurPath, tmpFile->filename); + else + sprintf(tmpPath, "%s%s", tmpCurPath, tmpFile->filename); + } + + // wiiNinja: Need to PopCurrentDir + PushCurrentDir (tmpPath, selected, start); + selected = 0; + start = 0; - if (res == 1) { - int counter; - for (counter = 0; counter < fileCnt; counter++) { - fatFile *temp = &fileList[counter]; - temp->install = 0; - } + installCnt = 0; + uninstallCnt = 0; - installCnt = 0; - uninstallCnt = 0; + goto getList; + } + } + else + { + //If at least one WAD is marked, goto batch screen - Leathl + if ((installCnt > 0) || (uninstallCnt > 0)) + { + char *thisCurPath = PeekCurrentDir (); + if (thisCurPath != NULL) + { + int res = Menu_BatchProcessWads(fileList, fileCnt, thisCurPath, installCnt, uninstallCnt); + + if (res == 1) + { + int counter; + for (counter = 0; counter < fileCnt; counter++) + { + fatFile *temp = &fileList[counter]; + temp->install = 0; } + + installCnt = 0; + uninstallCnt = 0; } } - //else use standard wadmanage menu - Leathl - else { - tmpCurPath = PeekCurrentDir (); - if (tmpCurPath != NULL) - Menu_WadManage(tmpFile, tmpCurPath); - } } + //else use standard wadmanage menu - Leathl + else + { + tmpCurPath = PeekCurrentDir (); + if (tmpCurPath != NULL) + Menu_WadManage(tmpFile, tmpCurPath); + } + } } /* B button */ - if (buttons & WPAD_BUTTON_B) + else if (buttons & WPAD_BUTTON_B) { - if(gDirLevel<=1) - { + if (gDirLevel <= 1) return; - } char *tmpCurPath; selected = 0; start = 0; + // Previous dir tmpCurPath = PopCurrentDir(&selected, &start); if (tmpCurPath != NULL) sprintf(tmpPath, "%s", tmpCurPath); + goto getList; - //return; } /** Scrolling **/ @@ -1204,7 +1196,7 @@ getList: if (index >= ENTRIES_PER_PAGE) start += index - (ENTRIES_PER_PAGE - 1); - if (index <= -1) + else if (index < 0) start += index; } @@ -1212,7 +1204,7 @@ err: printf("\n"); printf(" Press any button to continue...\n"); - free (tmpPath); + free(tmpPath); /* Wait for button */ WaitButtons(); @@ -1220,8 +1212,10 @@ err: void Menu_Loop(void) { u8 iosVersion; - if(AHBPROT_DISABLED) + if (AHBPROT_DISABLED) + { IOSPATCH_Apply(); + } else { /* Select IOS menu */ @@ -1230,6 +1224,7 @@ void Menu_Loop(void) /* Retrieve IOS version */ iosVersion = IOS_GetVersion(); + GetSysMenuRegion(&gMenuVersion, &gMenuRegion); ndev = &ndevList[0]; diff --git a/source/nand.c b/source/nand.c index a5c99be..a7447dc 100644 --- a/source/nand.c +++ b/source/nand.c @@ -1,10 +1,58 @@ #include +#include #include +#include +#include #include "nand.h" +#include "fileops.h" + +#define BLOCK 2048 /* Buffer */ static u32 inbuf[8] ATTRIBUTE_ALIGN(32); +static bool gNandInitialized = false; + +#if 0 +static void NANDFATify(char* ptr, const char* str) +{ + char ctr; + while ((ctr = *(str++)) != '\0') + { + const char* esc; + switch (ctr) + { + case '"': + esc = "&qt;"; + break; + case '*': + esc = "&st;"; + break; + case ':': + esc = "&cl;"; + break; + case '<': + esc = "<"; + break; + case '>': + esc = ">"; + break; + case '?': + esc = "&qm;"; + break; + case '|': + esc = "&vb;"; + break; + default: + *(ptr++) = ctr; + continue; + } + strcpy(ptr, esc); + ptr += 4; + } + *ptr = '\0'; +} +#endif s32 Nand_Mount(nandDevice *dev) @@ -83,4 +131,337 @@ s32 Nand_Disable(void) IOS_Close(fd); return ret; -} +} + +bool NANDInitialize() +{ + if(!gNandInitialized) + { + if (ISFS_Initialize() == ISFS_OK) + gNandInitialized = true; + } + + return gNandInitialized; +} + +u8* NANDReadFromFile(const char* path, u32 offset, u32 length, u32* size) +{ + *size = ISFS_EINVAL; + + if (NANDInitialize()) + { + s32 fd = IOS_Open(path, 1); + + if (fd < 0) + { + *size = fd; + return NULL; + } + + if (!length) + length = IOS_Seek(fd, 0, SEEK_END); + + u8* data = (u8*)memalign(0x40, length); + if (!data) + { + *size = 0; + IOS_Close(fd); + return NULL; + } + + *size = IOS_Seek(fd, offset, SEEK_SET); + if (*size < 0) + { + IOS_Close(fd); + free(data); + return NULL; + } + + *size = IOS_Read(fd, data, length); + IOS_Close(fd); + if (*size != length) + { + free(data); + return NULL; + } + + return data; + } + + return NULL; +} + +u8* NANDLoadFile(const char* path, u32* size) +{ + return NANDReadFromFile(path, 0, 0, size); +} + +s32 NANDWriteFileSafe(const char* path, u8* data, u32 size) +{ + NANDInitialize(); + + char* tmpPath = (char*)memalign(0x40, ISFS_MAXPATH); + u32 i; + + for (i = strlen(path); i > 0; --i) + { + if (path[i] == '/') + break; + } + + sprintf(tmpPath, "/tmp%s", path + i); + + s32 ret = ISFS_CreateFile(tmpPath, 0, 3, 3, 3); + if (ret == -105) + { + ISFS_Delete(tmpPath); + ret = ISFS_CreateFile(tmpPath, 0, 3, 3, 3); + + if (ret < 0) + { + free(tmpPath); + return ret; + } + } + else + { + if (ret < 0) + { + free(tmpPath); + return ret; + } + } + + s32 fd = IOS_Open(tmpPath, 2); + if (fd < 0) + { + free(tmpPath); + return fd; + } + + ret = IOS_Write(fd, data, size); + + + IOS_Close(fd); + if (ret != size) + { + free(tmpPath); + return ret - 3; + } + + if (strcmp(tmpPath, path)) + ret = ISFS_Rename(tmpPath, path); + else + ret = 0; + + free(tmpPath); + return ret; +} + +s32 NANDBackUpFile(const char* src, const char* dst, u32* size) +{ + NANDInitialize(); + u8* buffer = NANDLoadFile(src, size); + if (!buffer) + return *size; + + s32 ret = NANDWriteFileSafe(dst, buffer, *size); + + free(buffer); + return ret; +} + +s32 NANDGetFileSize(const char* path, u32* size) +{ + NANDInitialize(); + s32 fd = IOS_Open(path, 1); + + if (fd < 0) + { + *size = 0; + return fd; + } + + *size = IOS_Seek(fd, 0, SEEK_END); + return IOS_Close(fd); +} + +s32 NANDDeleteFile(const char* path) +{ + NANDInitialize(); + return ISFS_Delete(path); +} + +#if 0 +s32 NANDGetNameList(const char* src, NameList** entries, s32* count) +{ + *count = 0; + u32 numEntries = 0; + char currentEntry[ISFS_MAXPATH]; + char entryPath[ISFS_MAXPATH + 1]; + + s32 ret = ISFS_ReadDir(src, NULL, &numEntries); + + if (ret < 0) + return ret; + + char* names = (char*)memalign(0x40, ISFS_MAXPATH * numEntries); + + if (!names) + return ISFS_ENOMEM; + + ret = ISFS_ReadDir(src, names, &numEntries); + if (ret < 0) + { + free(names); + return ret; + } + + *count = numEntries; + + free(*entries); + *entries = (NameList*)memalign(0x20, sizeof(NameList) * numEntries); + if (!*entries) + { + free(names); + return ISFS_ENOMEM; + } + + s32 i, j, k; + u32 dummy; + for (i = 0, k = 0; i < numEntries; i++) + { + for (j = 0; names[k] != 0; j++, k++) + currentEntry[j] = names[k]; + + currentEntry[j] = 0; + k++; + + strcpy((*entries)[i].name, currentEntry); + + if (src[strlen(src) - 1] == '/') + snprintf(entryPath, sizeof(entryPath), "%s%s", src, currentEntry); + else + snprintf(entryPath, sizeof(entryPath), "%s/%s", src, currentEntry); + + ret = ISFS_ReadDir(entryPath, NULL, &dummy); + (*entries)[i].type = ret < 0 ? 0 : 1; + } + + free(names); + return 0; +} + +s32 NANDDumpFile(const char* src, const char* dst) +{ + s32 fd = ISFS_Open(src, ISFS_OPEN_READ); + if (fd < 0) + return fd; + + fstats* status = (fstats*)memalign(32, sizeof(fstats)); + if (status == NULL) + return ISFS_ENOMEM; + + s32 ret = ISFS_GetFileStats(fd, status); + if (ret < 0) + { + ISFS_Close(fd); + free(status); + return ret; + } + + FSOPDeleteFile(dst); + + FILE* file = fopen(dst, "wb"); + + if (!file) + { + ISFS_Close(fd); + free(status); + return ISFS_EINVAL; + } + + u8* buffer = (u8*)memalign(32, BLOCK); + if (!buffer) + { + ISFS_Close(fd); + free(status); + return ISFS_ENOMEM; + } + + u32 toRead = status->file_length; + while (toRead > 0) + { + u32 size = toRead < BLOCK ? toRead : BLOCK; + + ret = ISFS_Read(fd, buffer, size); + if (ret < 0) + { + ISFS_Close(fd); + fclose(file); + free(status); + free(buffer); + return ret; + } + + ret = fwrite(buffer, 1, size, file); + if (ret < 0) + { + ISFS_Close(fd); + fclose(file); + free(status); + free(buffer); + return ret; + } + + toRead -= size; + } + + fclose(file); + ISFS_Close(fd); + free(status); + free(buffer); + + return ISFS_OK; +} + +s32 NANDDumpFolder(const char* src, const char* dst) +{ + NameList* names = NULL; + s32 count = 0; + s32 i; + + char nSrc[ISFS_MAXPATH + 1]; + char nDst[1024]; + char tDst[1024]; + + NANDGetNameList(src, &names, &count); + FSOPMakeFolder(dst); + + for (i = 0; i < count; i++) + { + + if (src[strlen(src) - 1] == '/') + snprintf(nSrc, sizeof(nSrc), "%s%s", src, names[i].name); + else + snprintf(nSrc, sizeof(nSrc), "%s/%s", src, names[i].name); + + if (!names[i].type) + { + NANDFATify(tDst, nSrc); + snprintf(nDst, sizeof(nDst), "%s%s", dst, tDst); + NANDDumpFile(nSrc, nDst); + } + else + { + NANDFATify(tDst, nSrc); + snprintf(nDst, sizeof(nDst), "%s%s", dst, tDst); + FSOPMakeFolder(nDst); + NANDDumpFolder(nSrc, dst); + } + } + + free(names); + return 0; +} +#endif diff --git a/source/nand.h b/source/nand.h index 0b769d8..fb9af97 100644 --- a/source/nand.h +++ b/source/nand.h @@ -12,7 +12,13 @@ typedef struct { /* Un/mount command */ u32 mountCmd; u32 umountCmd; -} nandDevice; +} nandDevice; + +typedef struct +{ + char name[ISFS_MAXPATH]; + int type; +} NameList; /* Prototypes */ @@ -20,5 +26,18 @@ s32 Nand_Mount(nandDevice *); s32 Nand_Unmount(nandDevice *); s32 Nand_Enable(nandDevice *); s32 Nand_Disable(void); +bool NANDInitialize(); +u8* NANDReadFromFile(const char* path, u32 offset, u32 length, u32* size); +u8* NANDLoadFile(const char* path, u32* size); +s32 NANDWriteFileSafe(const char* path, u8* data, u32 size); +s32 NANDBackUpFile(const char* src, const char* dst, u32* size); +s32 NANDGetFileSize(const char* path, u32* size); +s32 NANDDeleteFile(const char* path); + +#if 0 +s32 NANDGetNameList(const char* src, NameList** entries, s32* count); +s32 NANDDumpFile(const char* src, const char* dst); +s32 NANDDumpFolder(const char* src, const char* dst); +#endif #endif diff --git a/source/sha1.c b/source/sha1.c index 7ce9e6d..41ebaa6 100644 --- a/source/sha1.c +++ b/source/sha1.c @@ -175,3 +175,17 @@ void SHA1(unsigned char *ptr, unsigned int size, unsigned char *outbuf) { SHA1Update(&ctx, ptr, size); SHA1Final(outbuf, &ctx); } + +int CompareHash(unsigned char* first, unsigned int firstSize, unsigned char* second, unsigned int secondSize) +{ + unsigned int HashA[5] = { 0, 0, 0, 0, 0, }; + unsigned int HashB[5] = { 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, }; + + SHA1(first, firstSize, (unsigned char*)HashA); + SHA1(second, secondSize, (unsigned char*)HashB); + + //printf("Hash 1: %.8X, %.8X, %.8X, %.8X, %.8X", HashA[0], HashA[1], HashA[2], HashA[3], HashA[4]); + //printf("Hash 2: %.8X, %.8X, %.8X, %.8X, %.8X", HashB[0], HashB[1], HashB[2], HashB[3], HashB[4]); + + return memcmp(HashA, HashB, sizeof(HashA)); +} diff --git a/source/sha1.h b/source/sha1.h index 35b3388..333571c 100644 --- a/source/sha1.h +++ b/source/sha1.h @@ -2,5 +2,6 @@ #define _SHA1_H_ void SHA1(unsigned char *, unsigned int, unsigned char *); +int CompareHash(unsigned char* first, unsigned int firstSize, unsigned char* second, unsigned int secondSize); #endif diff --git a/source/wad-manager.c b/source/wad-manager.c index 2ab749f..be5ef1b 100644 --- a/source/wad-manager.c +++ b/source/wad-manager.c @@ -166,6 +166,8 @@ int main(int argc, char **argv) /* Set video mode */ Video_SetMode(); + FatMount(); + /* Initialize console */ Gui_InitConsole(); @@ -193,6 +195,8 @@ int main(int argc, char **argv) /* Menu loop */ Menu_Loop(); + FatUnmount(); + /* Restart Wii */ Restart_Wait(); @@ -200,30 +204,27 @@ int main(int argc, char **argv) } -int ReadConfigFile (char *configFilePath) +int ReadConfigFile(char* configFilePath) { - int retval = 0; - FILE *fptr; - char *tmpStr = malloc (MAX_FILE_PATH_LEN); - char tmpOutStr [40], path[128]; - int i; + int retval = 0; + FILE* fptr; + char* tmpStr = malloc(MAX_FILE_PATH_LEN); + char tmpOutStr[40], path[128]; + s32 i; + s32 ret = -1; + bool found = false; if (tmpStr == NULL) return (-1); - fatDevice *fdev = &fdevList[0]; - int ret = Fat_Mount(fdev); - snprintf(path, sizeof(path), "%s%s", fdev->mount, configFilePath); - - if (ret < 0) + // Just check if at least one device is available + for (i = 0; i < FatGetDeviceCount(); i++) { - fdev = &fdevList[2]; - ret = Fat_Mount(fdev); - snprintf(path, sizeof(path), "%s%s", fdev->mount, configFilePath); - //snprintf(path, sizeof(path), "%s%s", fdev->mount, configFilePath); + snprintf(path, sizeof(path), "%s%s", FatGetDevicePrefix(i), configFilePath); + found = true; } - if (ret < 0) + if (!found) { printf(" ERROR! (ret = %d)\n", ret); // goto err; @@ -281,7 +282,8 @@ int ReadConfigFile (char *configFilePath) GetStringParam (tmpOutStr, tmpStr, MAX_FAT_DEVICE_LENGTH); for (i = 0; i < 5; i++) { - if (strncmp (fdevList[i].mount, tmpOutStr, 4) == 0) + //if (strncmp (fdevList[i].mount, tmpOutStr, 4) == 0) + if (strncmp(FatGetDevicePrefix(i), tmpOutStr, 4) == 0) { gConfig.fatDeviceIndex = i; } @@ -313,11 +315,11 @@ int ReadConfigFile (char *configFilePath) //printf ("Config file is not found\n"); // This is for testing only //WaitButtons(); } - Fat_Unmount(fdev); + //Fat_Unmount(fdev); } // Free memory - free (tmpStr); + free(tmpStr); return (retval); } // ReadConfig diff --git a/source/wad.c b/source/wad.c index ddd186b..942f55e 100644 --- a/source/wad.c +++ b/source/wad.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "sys.h" #include "title.h" @@ -10,6 +11,9 @@ #include "video.h" #include "wad.h" #include "wpad.h" +#include "nand.h" +#include "fileops.h" +#include "sha1.h" // Turn upper and lower into a full title ID #define TITLE_ID(x,y) (((u64)(x) << 32) | (y)) @@ -18,32 +22,34 @@ // Turn upper and lower into a full title ID #define TITLE_LOWER(x) ((u32)(x)) -typedef struct { - int version; - int region; - -} SMRegion; - -SMRegion regionlist[] = { - {33, 'X'}, - {128, 'J'}, {97, 'E'}, {130, 'P'}, - {162, 'P'}, - {192, 'J'}, {193, 'E'}, {194, 'P'}, - {224, 'J'}, {225, 'E'}, {226, 'P'}, - {256, 'J'}, {257, 'E'}, {258, 'P'}, - {288, 'J'}, {289, 'E'}, {290, 'P'}, - {352, 'J'}, {353, 'E'}, {354, 'P'}, {326, 'K'}, - {384, 'J'}, {385, 'E'}, {386, 'P'}, - {390, 'K'}, - {416, 'J'}, {417, 'E'}, {418, 'P'}, - {448, 'J'}, {449, 'E'}, {450, 'P'}, {454, 'K'}, - {480, 'J'}, {481, 'E'}, {482, 'P'}, {486, 'K'}, - {512, 'E'}, {513, 'E'}, {514, 'P'}, {518, 'K'}, +const char RegionLookupList[16] = +{ + 'J', 'E', 'P', 0, 0, 0, 'K', 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -#define NB_SM (sizeof(regionlist) / sizeof(SMRegion)) +const u16 versionList[] = +{ +// J E P K + + 64, 33, 66, // 1.0 + 128, 97, 130, // 2.0 + 162, // 2.1 + 192, 193, 194, // 2.2 + 224, 225, 226, // 3.0 + 256, 257, 258, // 3.1 + 288, 289, 290, // 3.2 + 352, 353, 354, 326, // 3.3 + 384, 385, 386, // 3.4 + 390, // 3.5 + 416, 417, 418, // 4.0 + 448, 449, 450, 454, // 4.1 + 480, 481, 482, 486, // 4.2 + 512, 513, 514, 518, // 4.3 +}; u32 WaitButtons(void); +static u32 gPriiloaderSize = 0; +static bool gForcedInstall = false; u32 be32(const u8 *p) { @@ -125,33 +131,280 @@ u64 get_title_ios(u64 title) { return 0; } -int get_sm_region_basic() +s32 GetSysMenuRegion(u16* version, char* region) { - u32 tmd_size; - - u64 title = TITLE_ID(1, 2); - static u8 tmd_buf[MAX_SIGNED_TMD_SIZE] ATTRIBUTE_ALIGN(32); - - int ret = ES_GetStoredTMDSize(title, &tmd_size); - - // Some of this code adapted from bushing's title_lister.c - signed_blob *s_tmd = (signed_blob *)tmd_buf; - ret = ES_GetStoredTMD(title, s_tmd, tmd_size); - if (ret < 0){ - //printf("Error! ES_GetStoredTMD: %d\n", ret); - return -1; - } - tmd *t = SIGNATURE_PAYLOAD(s_tmd); - ret = t->title_version; - int i = 0; - while( i <= NB_SM) - { - if( regionlist[i].version == ret) return regionlist[i].region; - i++; - } + u16 v = 0; + s32 ret = Title_GetVersion(0x100000002LL, &v); + + if (ret < 0) + return ret; + + if (version) + *version = v; + + if (region) + *region = RegionLookupList[(v & 0x0F)]; + return 0; } +const char* GetSysMenuRegionString(const char* region) +{ + switch (*region) + { + case 'J': return "Japan (NTSC-J)"; + case 'E': return "USA (NTSC-U/C)"; + case 'P': return "Europe (PAL)"; + case 'K': return "Korea (NTSC-K)"; + } + + return "Unknown"; +} + +static char* GetTitleExec(u64 tId, bool tweaked) +{ + u32 size; + const u8 buffer[MAX_SIGNED_TMD_SIZE] ATTRIBUTE_ALIGN(32); + + s32 ret = ES_GetStoredTMDSize(0x100000002, &size); + signed_blob* tmdRaw = (signed_blob*)buffer; + + ret = ES_GetStoredTMD(0x100000002, tmdRaw, size); + if (ret < 0) + { + printf("Error! ES_GetStoredTMDSize: Failed! (Error: %d)\n", ret); + return NULL; + } + + tmd* smTMD = SIGNATURE_PAYLOAD(tmdRaw); + + char* path = (char*)memalign(0x40, ISFS_MAXPATH); + if (!path) + return NULL; + + if(tweaked) + sprintf(path, "/title/%08x/%08x/content/1%.7x.app", TITLE_UPPER(tId), TITLE_LOWER(tId), smTMD->contents[smTMD->boot_index].cid); + else + sprintf(path, "/title/%08x/%08x/content/%.8x.app", TITLE_UPPER(tId), TITLE_LOWER(tId), smTMD->contents[smTMD->boot_index].cid); + + return path; +} + +static inline bool IsPriiloaderInstalled() +{ + char* path = GetTitleExec(0x100000002LL, true); + if (!path) + return false; + + + u32 size = 0; + NANDGetFileSize(path, &size); + free(path); + + if (size > 0) + return true; + else + return false; +} + +static bool BackUpPriiloader() +{ + char* path = GetTitleExec(0x100000002LL, false); + if (!path) + return false; + + u32 size = 0; + s32 ret = NANDBackUpFile(path, "/tmp/priiload.app", &size); + free(path); + + if (ret < 0) + { + printf("Error! NANDBackUpFile: Failed! (Error: %d)\n", ret); + return false; + } + + ret = NANDGetFileSize("/tmp/priiload.app", &gPriiloaderSize); + + return (gPriiloaderSize == size); +} + +static bool MoveMenu(bool restore) +{ + char* srcPath = GetTitleExec(0x100000002LL, restore); + if (!srcPath) + return false; + + char* dstPath = GetTitleExec(0x100000002LL, !restore); + if (!dstPath) + { + free(srcPath); + return false; + } + + u32 size = 0; + s32 ret = NANDBackUpFile(srcPath, dstPath, &size); + if (ret < 0) + { + free(srcPath); + free(dstPath); + printf("Error! NANDBackUpFile: Failed! (Error: %d)\n", ret); + return false; + } + + u32 checkSize = 0; + ret = NANDGetFileSize(dstPath, &checkSize); + + free(srcPath); + free(dstPath); + + return (checkSize == size); +} + +static bool RestorePriiloader() +{ + char* dstPath = GetTitleExec(0x100000002LL, false); + if (!dstPath) + return false; + + u32 size = 0; + s32 ret = NANDBackUpFile("/tmp/priiload.app", dstPath, &size); + if (ret < 0) + { + free(dstPath); + printf("Error! NANDBackUpFile: Failed! (Error: %d)\n", ret); + return false; + } + + u32 checkSize = 0; + ret = NANDGetFileSize(dstPath, &checkSize); + + free(dstPath); + + return (checkSize == size && checkSize == gPriiloaderSize); +} + +static void PrintCleanupResult(s32 result) +{ + + if (result < 0) + { + switch (result) + { + case -102: + { + printf(" Acces denied.\n"); + } break; + case -106: + { + printf(" Not found.\n"); + } break; + default: + { + printf(" Error: %d\n", result); + } break; + } + } + else + { + printf(" OK!\n"); + } + + sleep(1); +} + +static void CleanupPriiloaderLeftOvers(bool retain) +{ + if (!retain) + { + printf("\n\t\tCleanup Priiloader leftover files...\n"); + printf("\r\t\t>> Password file..."); + PrintCleanupResult(NANDDeleteFile("/title/00000001/00000002/data/password.txt")); + printf("\r\t\t>> Settings file..."); + PrintCleanupResult(NANDDeleteFile("/title/00000001/00000002/data/loader.ini")); + printf("\r\t\t>> Ticket..."); + PrintCleanupResult(NANDDeleteFile("/title/00000001/00000002/data/ticket")); + printf("\r\t\t>> File: main.nfo..."); + PrintCleanupResult(NANDDeleteFile("/title/00000001/00000002/data/main.nfo")); + printf("\r\t\t>> File: main.bin..."); + PrintCleanupResult(NANDDeleteFile("/title/00000001/00000002/data/main.bin")); + } + + printf("\n\t\tRemoving Priiloader hacks...\n"); + + printf("\r\t\t>> File: hacks_s.ini..."); + PrintCleanupResult(NANDDeleteFile("/title/00000001/00000002/data/hacks_s.ini")); + printf("\r\t\t>> File: hacks.ini..."); + PrintCleanupResult(NANDDeleteFile("/title/00000001/00000002/data/hacks.ini")); + printf("\r\t\t>> File: hacksh_s.ini..."); + PrintCleanupResult(NANDDeleteFile("/title/00000001/00000002/data/hacksh_s.ini")); + printf("\r\t\t>> File: hackshas.ini..."); + PrintCleanupResult(NANDDeleteFile("/title/00000001/00000002/data/hackshas.ini")); + + if (retain) + { + printf("\n\t\tPriiloader hacks will be reset!\n"); + printf("\t\tRemember to set them again.\n"); + } +} + +static bool CompareHashes(bool priiloader) +{ + char* dstPath = NULL; + char* srcPath = GetTitleExec(0x100000002LL, false); + + if (!srcPath) + return false; + + if (priiloader) + { + dstPath = (char*)memalign(0x40, ISFS_MAXPATH); + if (!dstPath) + { + free(srcPath); + return false; + } + + strcpy(dstPath, "/tmp/priiload.app"); + } + else + { + dstPath = GetTitleExec(0x100000002LL, true); + if (!dstPath) + { + free(srcPath); + return false; + } + } + + u32 sizeA = 0; + u32 sizeB = 0; + u8* dataA = NANDLoadFile(srcPath, &sizeA); + if (!dataA) + { + free(srcPath); + free(dstPath); + return false; + } + + u8* dataB = NANDLoadFile(dstPath, &sizeB); + if (!dataA) + { + free(srcPath); + free(dstPath); + free(dataA); + return false; + } + + bool ret = !CompareHash(dataA, sizeA, dataB, sizeB); + + free(srcPath); + free(dstPath); + free(dataA); + free(dataB); + + return ret; +} + /* 'WAD Header' structure */ typedef struct { /* Header length */ @@ -174,45 +427,6 @@ typedef struct { /* Variables */ static u8 wadBuffer[BLOCK_SIZE] ATTRIBUTE_ALIGN(32); - -s32 __Wad_ReadFile(FILE *fp, void *outbuf, u32 offset, u32 len) -{ - s32 ret; - - /* Seek to offset */ - fseek(fp, offset, SEEK_SET); - - /* Read data */ - ret = fread(outbuf, len, 1, fp); - if (ret < 0) - return ret; - - return 0; -} - -s32 __Wad_ReadAlloc(FILE *fp, void **outbuf, u32 offset, u32 len) -{ - void *buffer = NULL; - s32 ret; - - /* Allocate memory */ - buffer = memalign(32, len); - if (!buffer) - return -1; - - /* Read file */ - ret = __Wad_ReadFile(fp, buffer, offset, len); - if (ret < 0) { - free(buffer); - return ret; - } - - /* Set pointer */ - *outbuf = buffer; - - return 0; -} - s32 __Wad_GetTitleID(FILE *fp, wadHeader *header, u64 *tid) { signed_blob *p_tik = NULL; @@ -227,8 +441,8 @@ s32 __Wad_GetTitleID(FILE *fp, wadHeader *header, u64 *tid) offset += round_up(header->crl_len, 64); /* Read ticket */ - ret = __Wad_ReadAlloc(fp, (void *)&p_tik, offset, header->tik_len); - if (ret < 0) + ret = FSOPReadOpenFileA(fp, (void*)&p_tik, offset, header->tik_len); + if (ret != 1) goto out; /* Ticket data */ @@ -273,15 +487,17 @@ s32 Wad_Install(FILE *fp) u32 cnt, offset = 0; int ret; u64 tid; + bool retainPriiloader = false; + bool cleanupPriiloader = false; printf("\t\t>> Reading WAD data..."); fflush(stdout); - ret = __Wad_ReadAlloc(fp, (void *)&header, offset, sizeof(wadHeader)); - if (ret >= 0) - offset += round_up(header->header_len, 64); + ret = FSOPReadOpenFileA(fp, (void*)&header, offset, sizeof(wadHeader)); + if (ret != 1) + goto err; else - goto err; + offset += round_up(header->header_len, 64); //Don't try to install boot2 __Wad_GetTitleID(fp, header, &tid); @@ -294,31 +510,31 @@ s32 Wad_Install(FILE *fp) } /* WAD certificates */ - ret = __Wad_ReadAlloc(fp, (void *)&p_certs, offset, header->certs_len); - if (ret >= 0) - offset += round_up(header->certs_len, 64); + ret = FSOPReadOpenFileA(fp, (void*)&p_certs, offset, header->certs_len); + if (ret != 1) + goto err; else - goto err; - + offset += round_up(header->certs_len, 64); + /* WAD crl */ if (header->crl_len) { - ret = __Wad_ReadAlloc(fp, (void *)&p_crl, offset, header->crl_len); - if (ret < 0) + ret = FSOPReadOpenFileA(fp, (void*)&p_crl, offset, header->crl_len); + if (ret != 1) goto err; else offset += round_up(header->crl_len, 64); } /* WAD ticket */ - ret = __Wad_ReadAlloc(fp, (void *)&p_tik, offset, header->tik_len); - if (ret < 0) + ret = FSOPReadOpenFileA(fp, (void*)&p_tik, offset, header->tik_len); + if (ret != 1) goto err; else offset += round_up(header->tik_len, 64); /* WAD TMD */ - ret = __Wad_ReadAlloc(fp, (void *)&p_tmd, offset, header->tmd_len); - if (ret < 0) + ret = FSOPReadOpenFileA(fp, (void*)&p_tmd, offset, header->tmd_len); + if (ret != 1) goto err; else offset += round_up(header->tmd_len, 64); @@ -338,7 +554,7 @@ s32 Wad_Install(FILE *fp) if(get_title_ios(TITLE_ID(1, 2)) == tid) { - if ( ( tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001) ) + if (( tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001)) { printf("\n I won't install a stub System Menu IOS\n"); ret = -999; @@ -348,7 +564,7 @@ s32 Wad_Install(FILE *fp) if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | 'E')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | 'P')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | 'J')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | 'K'))) { - if ( ( tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001) ) + if ((tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001)) { printf("\n I won't install a stub EULA IOS\n"); ret = -999; @@ -358,7 +574,7 @@ s32 Wad_Install(FILE *fp) if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | 'E')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | 'P')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | 'J')) || tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | 'K'))) { - if ( ( tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001) ) + if ((tmd_data->num_contents == 3) && (tmd_data->contents[0].type == 1 && tmd_data->contents[1].type == 0x8001 && tmd_data->contents[2].type == 0x8001)) { printf("\n I won't install a stub rgsel IOS\n"); ret = -999; @@ -385,30 +601,36 @@ s32 Wad_Install(FILE *fp) if (tid == TITLE_ID(1, 2)) { - if (skipRegionSafetyCheck) goto skipChecks; + char region = 0; + u16 version = 0; - if(get_sm_region_basic() == 0) + if (skipRegionSafetyCheck || gForcedInstall) + goto skipChecks; + + GetSysMenuRegion(&version, ®ion); + if(region == 0) { - printf("\n Can't get the SM region\n Please check the site for updates\n"); + printf("\n Unkown SM region\n Please check the site for updates\n"); ret = -999; goto err; } + int i, ret = -1; - for(i = 0; i <= NB_SM; i++) + for(i = 0; i < sizeof(versionList); i++) { - if( regionlist[i].version == tmd_data->title_version) + if(versionList[i] == tmd_data->title_version) { ret = 1; break; } } - if(ret -1) + if(ret != 1) { - printf("\n Can't get the SM region\n Please check the site for updates\n"); + printf("\n Unknown SM region\n Please check the site for updates\n"); ret = -999; goto err; } - if(get_sm_region_basic() != regionlist[i].region) + if(region != RegionLookupList[(tmd_data->title_version & 0x0F)]) { printf("\n I won't install the wrong regions SM\n"); ret = -999; @@ -424,8 +646,57 @@ skipChecks: goto err; } } + + if (!gForcedInstall && IsPriiloaderInstalled()) + { + cleanupPriiloader = true; + printf("\n Priiloader is installed next to the system menu.\n\n"); + printf(" Press A to retain Priiloader or B to remove."); + + u32 buttons = WaitButtons(); + + if ((buttons & WPAD_BUTTON_A)) + { + retainPriiloader = (BackUpPriiloader() && CompareHashes(true)); + if (retainPriiloader) + { + Con_ClearLine(); + printf("\r[+] Priiloader will be retained.\n"); + fflush(stdout); + } + else + { + Con_ClearLine(); + printf("\r Couldn't backup Priiloader.\n"); + fflush(stdout); + + printf("\n Press A to continue or B to skip"); + + u32 buttons = WaitButtons(); + + if (!(buttons & WPAD_BUTTON_A)) + { + ret = -990; + goto err; + } + } + } + + if (!retainPriiloader) + { + Con_ClearLine(); + printf("\r[+] Priiloader will be removed.\n"); + fflush(stdout); + } + } } + if (gForcedInstall) + { + gForcedInstall = false; + cleanupPriiloader = true; + } + /* Fix ticket */ __Wad_FixTicket(p_tik); @@ -479,8 +750,8 @@ skipChecks: size = BLOCK_SIZE; /* Read data */ - ret = __Wad_ReadFile(fp, &wadBuffer, offset, size); - if (ret < 0) + ret = FSOPReadOpenFile(fp, &wadBuffer, offset, size); + if (ret != 1) goto err; /* Install data */ @@ -506,13 +777,106 @@ skipChecks: /* Finish title install */ ret = ES_AddTitleFinish(); - if (ret >= 0) { + if (ret >= 0) + { printf(" OK!\n"); + + if (retainPriiloader) + { + printf("\r\t\t>> Moving System Menu..."); + if (MoveMenu(false)) + { + printf(" OK!\n"); + + printf("\r\t\t>> Check System Menu executable hashes..."); + + s32 restoreMenu = 0; + + if (CompareHashes(false)) + { + printf(" OK!\n"); + } + else + { + printf(" Failed!\n"); + restoreMenu = 1; + } + + printf("\r\t\t>> Restore Priiloader..."); + if (!restoreMenu && RestorePriiloader()) + { + printf(" OK!\n"); + printf("\r\t\t>> Check Priiloader executable hashes..."); + if (CompareHashes(true)) + { + printf(" OK!\n"); + } + else + { + printf(" Failed!\n"); + restoreMenu = 2; + } + } + else + { + printf(" Failed!\n"); + restoreMenu = 2; + } + + if (restoreMenu) + { + printf("\r\t\t>> Restore System Menu..."); + bool restored = true; + switch (restoreMenu) + { + case 2: + { + restored = (MoveMenu(true) && CompareHashes(false)); + } + case 1: + { + if (restored) + { + char* path = GetTitleExec(0x100000002LL, true); + NANDDeleteFile(path); + free(path); + } + } + } + + if (restored) + { + printf(" OK!\n"); + } + else + { + printf(" Failed!\n"); + printf("\n\t\t>> Reinstalling System Menu...\n\n"); + sleep(3); + printf("\t\t>> Priiloader will be removed!\n\n"); + + gForcedInstall = true; + cleanupPriiloader = false; + } + } + } + else + { + printf(" Failed!\n"); + printf("\n\t\t>> Priiloader will be removed!\n\n"); + } + } + + if (cleanupPriiloader) + { + CleanupPriiloaderLeftOvers(retainPriiloader); + } + goto out; } err: - printf(" ERROR! (ret = %d)\n", ret); + printf("\n ERROR! (ret = %d)\n", ret); /* Cancel install */ ES_AddTitleCancel(); @@ -525,6 +889,9 @@ out: free(p_tik); free(p_tmd); + if (gForcedInstall) + return Wad_Install(fp); + return ret; } @@ -541,8 +908,9 @@ s32 Wad_Uninstall(FILE *fp) fflush(stdout); /* WAD header */ - ret = __Wad_ReadAlloc(fp, (void *)&header, 0, sizeof(wadHeader)); - if (ret < 0) { + ret = FSOPReadOpenFileA(fp, (void*)&header, 0, sizeof(wadHeader)); + if (ret != 1) + { printf(" ERROR! (ret = %d)\n", ret); goto out; } @@ -592,32 +960,36 @@ s32 Wad_Uninstall(FILE *fp) goto out; } } + + char region = 0; + GetSysMenuRegion(NULL, ®ion); + if((tid == TITLE_ID(0x10008, 0x48414B00 | 'E') || tid == TITLE_ID(0x10008, 0x48414B00 | 'P') || tid == TITLE_ID(0x10008, 0x48414B00 | 'J') || tid == TITLE_ID(0x10008, 0x48414B00 | 'K') - || (tid == TITLE_ID(0x10008, 0x48414C00 | 'E') || tid == TITLE_ID(0x10008, 0x48414C00 | 'P') || tid == TITLE_ID(0x10008, 0x48414C00 | 'J') || tid == TITLE_ID(0x10008, 0x48414C00 | 'K'))) && get_sm_region_basic() == 0) + || (tid == TITLE_ID(0x10008, 0x48414C00 | 'E') || tid == TITLE_ID(0x10008, 0x48414C00 | 'P') || tid == TITLE_ID(0x10008, 0x48414C00 | 'J') || tid == TITLE_ID(0x10008, 0x48414C00 | 'K'))) && region == 0) { - printf("\n Can't get the SM region\n Please check the site for updates\n"); + printf("\n Unkown SM region\n Please check the site for updates\n"); ret = -999; goto out; } - if(tid == TITLE_ID(0x10008, 0x48414B00 | get_sm_region_basic())) + if(tid == TITLE_ID(0x10008, 0x48414B00 | region)) { printf("\n I won't uninstall the EULA\n"); ret = -999; goto out; } - if(tid == TITLE_ID(0x10008, 0x48414C00 | get_sm_region_basic())) + if(tid == TITLE_ID(0x10008, 0x48414C00 | region)) { printf("\n I won't uninstall rgsel\n"); ret = -999; goto out; } - if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | get_sm_region_basic()))) + if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414B00 | region))) { printf("\n I won't uninstall the EULAs IOS\n"); ret = -999; goto out; } - if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | get_sm_region_basic()))) + if(tid == get_title_ios(TITLE_ID(0x10008, 0x48414C00 | region))) { printf("\n I won't uninstall the rgsel IOS\n"); ret = -999; diff --git a/source/wad.h b/source/wad.h index f5b2586..106f33f 100644 --- a/source/wad.h +++ b/source/wad.h @@ -2,7 +2,9 @@ #define _WAD_H_ /* Prototypes */ -s32 Wad_Install(FILE *); -s32 Wad_Uninstall(FILE *); +s32 Wad_Install(FILE* fp); +s32 Wad_Uninstall(FILE* fp); +s32 GetSysMenuRegion(u16* version, char* region); +const char* GetSysMenuRegionString(const char* region); #endif