#include "ui.h" #include "fs.h" #include "virtual.h" #include "vgame.h" #include "sddata.h" #include "image.h" #include "sha.h" #include "sdmmc.h" #include "ff.h" #define NORM_FS 10 #define IMGN_FS 3 // image normal filesystems #define VIRT_FS 9 #define SKIP_CUR (1<<3) #define OVERWRITE_CUR (1<<4) // Volume2Partition resolution table PARTITION VolToPart[] = { {0, 1}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0} }; // don't use this area for anything else! static FATFS* fs = (FATFS*)0x20316000; // write permissions - careful with this static u32 write_permissions = PERM_BASE; // number of currently open file systems static bool fs_mounted[NORM_FS] = { false }; // last search pattern & path static char search_pattern[256] = { 0 }; static char search_path[256] = { 0 }; bool InitSDCardFS() { fs_mounted[0] = (f_mount(fs, "0:", 1) == FR_OK); return fs_mounted[0]; } bool InitExtFS() { if (!fs_mounted[0]) return false; for (u32 i = 1; i < NORM_FS; i++) { char fsname[8]; snprintf(fsname, 7, "%lu:", i); if (fs_mounted[i]) continue; fs_mounted[i] = (f_mount(fs + i, fsname, 1) == FR_OK); } SetupNandSdDrive("A:", "0:", "1:/private/movable.sed", 0); SetupNandSdDrive("B:", "0:", "4:/private/movable.sed", 1); return true; } bool InitImgFS(const char* path) { // deinit image filesystem for (u32 i = NORM_FS - 1; i >= NORM_FS - IMGN_FS; i--) { char fsname[8]; snprintf(fsname, 7, "%lu:", i); if (!fs_mounted[i]) continue; f_mount(NULL, fsname, 1); fs_mounted[i] = false; } // (re)mount image, done if path == NULL MountImage(path); InitVGameDrive(); if (!GetMountState()) return false; // reinit image filesystem for (u32 i = NORM_FS - IMGN_FS; i < NORM_FS; i++) { char fsname[8]; snprintf(fsname, 7, "%lu:", i); fs_mounted[i] = (f_mount(fs + i, fsname, 1) == FR_OK); } return true; } bool InitRamDriveFS() { u32 pdrv = NORM_FS - IMGN_FS; char fsname[8]; snprintf(fsname, 7, "%lu:", pdrv); InitImgFS(NULL); MountRamDrive(); fs_mounted[pdrv] = (f_mount(fs + pdrv, fsname, 1) == FR_OK); if (!fs_mounted[pdrv] && (GetMountState() == IMG_RAMDRV)) { f_mkfs(fsname, FM_ANY, 0, MAIN_BUFFER, MAIN_BUFFER_SIZE); // format ramdrive if required f_mount(NULL, fsname, 1); fs_mounted[pdrv] = (f_mount(fs + pdrv, fsname, 1) == FR_OK); } return true; } void DeinitExtFS() { SetupNandSdDrive(NULL, NULL, NULL, 0); SetupNandSdDrive(NULL, NULL, NULL, 1); for (u32 i = NORM_FS - 1; i > 0; i--) { if (fs_mounted[i]) { char fsname[8]; snprintf(fsname, 7, "%lu:", i); f_mount(NULL, fsname, 1); fs_mounted[i] = false; } if ((i == NORM_FS - IMGN_FS) && (GetMountState() != IMG_RAMDRV)) { // unmount image MountImage(NULL); InitVGameDrive(); } } } void DeinitSDCardFS() { if (GetMountState() != IMG_RAMDRV) { MountImage(NULL); InitVGameDrive(); } if (fs_mounted[0]) { f_mount(NULL, "0:", 1); fs_mounted[0] = false; } } void DismountDriveType(u32 type) { // careful with this - no safety checks if (type & DriveType(GetMountPath())) InitImgFS(NULL); // image is mounted from type -> unmount image drive, too for (u32 i = NORM_FS - 1; i > 0; i--) { char fsname[8]; snprintf(fsname, 7, "%lu:", i); if (!fs_mounted[i] || !(type & DriveType(fsname))) continue; f_mount(NULL, fsname, 1); fs_mounted[i] = false; } } void SetFSSearch(const char* pattern, const char* path) { if (pattern && path) { strncpy(search_pattern, pattern, 256); strncpy(search_path, path, 256); } else *search_pattern = *search_path = '\0'; } int PathToNumFS(const char* path) { int fsnum = *path - (int) '0'; if ((fsnum < 0) || (fsnum >= NORM_FS) || (path[1] != ':')) { // this check is not required /* if (!GetVirtualSource(path) && !CheckAliasDrive(path) && !IsSearchDrive(path)) ShowPrompt(false, "Invalid path (%s)", path); */ return -1; } return fsnum; } int PathToNumFSA(const char* path) { char alias[256]; dealias_path(alias, path); return PathToNumFS(alias); } bool IsSearchDrive(const char* path) { return *search_pattern && *search_path && (strncmp(path, "Z:", 3) == 0); } int DriveType(const char* path) { int type = DRV_UNKNOWN; int pdrv = PathToNumFS(path); if ((pdrv >= 0) && (pdrv < NORM_FS)) { if (!fs_mounted[pdrv]) { type = DRV_UNKNOWN; } else if (pdrv == 0) { type = DRV_FAT | DRV_SDCARD | DRV_STDFAT; } else if ((pdrv == 7) && (GetMountState() == IMG_RAMDRV)) { type = DRV_FAT | DRV_RAMDRIVE | DRV_STDFAT; } else if ((pdrv >= 1) && (pdrv <= 3)) { type = DRV_FAT | DRV_SYSNAND | DRV_STDFAT; } else if ((pdrv >= 4) && (pdrv <= 6)) { type = DRV_FAT | DRV_EMUNAND | DRV_STDFAT; } else if ((pdrv >= 7) && (pdrv <= 9) && ((GetMountState() == IMG_FAT) || (GetMountState() == IMG_NAND))) { type = DRV_FAT | DRV_IMAGE | DRV_STDFAT; } } else if (CheckVirtualDrive(path)) { int vsrc = GetVirtualSource(path); if (vsrc == VRT_SYSNAND) { type = DRV_VIRTUAL | DRV_SYSNAND; } else if (vsrc == VRT_EMUNAND) { type = DRV_VIRTUAL | DRV_EMUNAND; } else if (vsrc == VRT_IMGNAND) { type = DRV_VIRTUAL | DRV_IMAGE; } else if (vsrc == VRT_XORPAD) { type = DRV_VIRTUAL | DRV_XORPAD; } else if (vsrc == VRT_MEMORY) { type = DRV_VIRTUAL | DRV_MEMORY; } else if (vsrc == VRT_GAME) { type = DRV_VIRTUAL | DRV_GAME | DRV_IMAGE; } } else if (CheckAliasDrive(path)) { type = DRV_FAT | DRV_ALIAS; } else if (IsSearchDrive(path)) { type = DRV_SEARCH; } return type; } uint64_t GetSDCardSize() { if (sdmmc_sdcard_init() != 0) return 0; return (u64) getMMCDevice(1)->total_size * 512; } bool FormatSDCard(u64 hidden_mb, u32 cluster_size) { u8 mbr[0x200] = { 0 }; u8 mbrdata[0x42] = { 0x80, 0x01, 0x01, 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x01, 0x00, 0x1C, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA }; u32 sd_size = getMMCDevice(1)->total_size; u32 emu_sector = 1; u32 emu_size = (u32) ((hidden_mb * 1024 * 1024) / 512); u32 fat_sector = align(emu_sector + emu_size, 0x2000); // align to 4MB u32 fat_size = (fat_sector < sd_size) ? sd_size - fat_sector : 0; // FAT size check if (fat_size < 0x80000) { // minimum free space: 256MB ShowPrompt(false, "ERROR: SD card is too small"); return false; } sd_size = fat_size; // build the MBR memcpy(mbrdata + 0x08, &fat_sector, 4); memcpy(mbrdata + 0x0C, &fat_size, 4); memcpy(mbrdata + 0x18, &emu_sector, 4); memcpy(mbrdata + 0x1C, &emu_size, 4); memcpy(mbr + 0x1BE, mbrdata, 0x42); if (hidden_mb) memcpy(mbr, "GATEWAYNAND", 12); else memset(mbr + 0x1CE, 0, 0x10); // one last warning.... if (!ShowUnlockSequence(3, "!WARNING!\n \nProceeding will format this SD.\nThis will irreversibly delete\nALL data on it.\n")) return false; ShowString("Formatting SD, please wait..."); // write the MBR to disk // !this assumes a fully deinitialized file system! if ((sdmmc_sdcard_init() != 0) || (sdmmc_sdcard_writesectors(0, 1, mbr) != 0)) { ShowPrompt(false, "ERROR: SD card i/o failure"); return false; } // format the SD card f_mount(fs, "0:", 1); UINT c_size = cluster_size; bool ret = (f_mkfs("0:", FM_FAT32, c_size, MAIN_BUFFER, MAIN_BUFFER_SIZE) == FR_OK) && (f_setlabel("0:GM9SD") == FR_OK); f_mount(NULL, "0:", 1); return ret; } bool CheckWritePermissions(const char* path) { char area_name[16]; int drvtype = DriveType(path); u32 perm; // check mounted image write permissions if ((drvtype & DRV_IMAGE) && !CheckWritePermissions(GetMountPath())) return false; // endless loop when mounted file inside image, but not possible // check drive type, get permission type if (drvtype & DRV_SYSNAND) { perm = PERM_SYSNAND; snprintf(area_name, 16, "the SysNAND"); // check virtual file flags (if any) VirtualFile vfile; if (GetVirtualFile(&vfile, path) && (vfile.flags & VFLAG_A9LH_AREA)) { perm = PERM_A9LH; snprintf(area_name, 16, "A9LH regions"); } } else if (drvtype & DRV_EMUNAND) { perm = PERM_EMUNAND; snprintf(area_name, 16, "the EmuNAND"); } else if (drvtype & DRV_GAME) { perm = PERM_GAME; snprintf(area_name, 16, "game images"); } else if (drvtype & DRV_XORPAD) { perm = PERM_XORPAD; snprintf(area_name, 16, "XORpads"); } else if (drvtype & DRV_IMAGE) { perm = PERM_IMAGE; snprintf(area_name, 16, "images"); } else if (drvtype & DRV_MEMORY) { perm = PERM_MEMORY; snprintf(area_name, 16, "memory areas"); } else if ((drvtype & DRV_ALIAS) || (strncmp(path, "0:/Nintendo 3DS", 15) == 0)) { perm = PERM_SDDATA; snprintf(area_name, 16, "SD system data"); } else if (drvtype & DRV_SDCARD) { perm = PERM_SDCARD; snprintf(area_name, 16, "the SD card"); } else if (drvtype & DRV_RAMDRIVE) { perm = PERM_RAMDRIVE; snprintf(area_name, 16, "the RAM drive"); } else { return false; } // check permission, return if already set if ((write_permissions & perm) == perm) return true; // ask the user if (!ShowPrompt(true, "Writing to %s is locked!\nUnlock it now?", area_name)) return false; return SetWritePermissions(perm, true); } bool SetWritePermissions(u32 perm, bool add_perm) { if ((write_permissions & perm) == perm) { // write permissions already given if (!add_perm) write_permissions = perm; return true; } switch (perm) { case PERM_BASE: if (!ShowUnlockSequence(1, "You want to enable base\nwriting permissions.")) return false; break; case PERM_SDCARD: if (!ShowUnlockSequence(1, "You want to enable SD card\nwriting permissions.")) return false; break; case PERM_RAMDRIVE: if (!ShowUnlockSequence(1, "You want to enable RAM drive\nwriting permissions.")) return false; case PERM_EMUNAND: if (!ShowUnlockSequence(2, "You want to enable EmuNAND\nwriting permissions.")) return false; break; case PERM_IMAGE: if (!ShowUnlockSequence(2, "You want to enable image\nwriting permissions.")) return false; break; case PERM_GAME: ShowPrompt(false, "Unlock write permission for\ngame images is not allowed."); return false; break; case PERM_XORPAD: ShowPrompt(false, "Unlock write permission for\nXORpad drive is not allowed."); return false; break; #ifndef SAFEMODE case PERM_SYSNAND: if (!ShowUnlockSequence(3, "!Better be careful!\n \nYou want to enable SysNAND\nwriting permissions.\nThis enables you to do some\nreally dangerous stuff!")) return false; break; case PERM_A9LH: if (!ShowUnlockSequence(5, "!THIS IS YOUR ONLY WARNING!\n \nYou want to enable A9LH area\nwriting permissions.\nThis enables you to OVERWRITE\nyour A9LH installation!")) return false; break; case PERM_MEMORY: if (!ShowUnlockSequence(4, "!Better be careful!\n \nYou want to enable memory\nwriting permissions.\nWriting to certain areas may\nlead to unexpected results.")) return false; break; case PERM_SDDATA: if (!ShowUnlockSequence(2, "You want to enable SD data\nwriting permissions.")) return false; break; case PERM_ALL: if (!ShowUnlockSequence(3, "!Better be careful!\n \nYou want to enable ALL\nwriting permissions.\nThis enables you to do some\nreally dangerous stuff!")) return false; break; default: ShowPrompt(false, "Unlock write permission is not allowed."); return false; break; #else case PERM_ALL: perm &= ~(PERM_SYSNAND|PERM_MEMORY); if (!ShowUnlockSequence(2, "You want to enable EmuNAND &\nimage writing permissions.\nKeep backups, just in case.")) return false; break; default: ShowPrompt(false, "Can't unlock write permission.\nTry GodMode9 instead!"); return false; break; #endif } write_permissions = add_perm ? write_permissions | perm : perm; return true; } u32 GetWritePermissions() { return write_permissions; } bool FileSetData(const char* path, const u8* data, size_t size, size_t foffset, bool create) { int drvtype = DriveType(path); if (!CheckWritePermissions(path)) return false; if (drvtype & DRV_FAT) { UINT bytes_written = 0; FIL file; if (fx_open(&file, path, FA_WRITE | (create ? FA_CREATE_ALWAYS : FA_OPEN_ALWAYS)) != FR_OK) return false; f_lseek(&file, foffset); fx_write(&file, data, size, &bytes_written); fx_close(&file); return (bytes_written == size); } else if (drvtype & DRV_VIRTUAL) { VirtualFile vfile; if (!GetVirtualFile(&vfile, path)) return 0; return (WriteVirtualFile(&vfile, data, foffset, size, NULL) == 0); } return false; } size_t FileGetData(const char* path, u8* data, size_t size, size_t foffset) { int drvtype = DriveType(path); if (drvtype & DRV_FAT) { UINT bytes_read = 0; FIL file; if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) return 0; f_lseek(&file, foffset); if (fx_read(&file, data, size, &bytes_read) != FR_OK) { fx_close(&file); return 0; } fx_close(&file); return bytes_read; } else if (drvtype & DRV_VIRTUAL) { u32 bytes_read = 0; VirtualFile vfile; if (!GetVirtualFile(&vfile, path)) return 0; return (ReadVirtualFile(&vfile, data, foffset, size, &bytes_read) == 0) ? bytes_read : 0; } return 0; } size_t FileGetSize(const char* path) { int drvtype = DriveType(path); if (drvtype & DRV_FAT) { FILINFO fno; if (fa_stat(path, &fno) != FR_OK) return 0; return fno.fsize; } else if (drvtype & DRV_VIRTUAL) { VirtualFile vfile; if (!GetVirtualFile(&vfile, path)) return 0; return vfile.size; } return 0; } bool FileGetSha256(const char* path, u8* sha256) { bool ret = true; ShowProgress(0, 0, path); if (DriveType(path) & DRV_VIRTUAL) { // for virtual files VirtualFile vfile; u32 fsize; if (!GetVirtualFile(&vfile, path)) return false; fsize = vfile.size; sha_init(SHA256_MODE); for (size_t pos = 0; (pos < fsize) && ret; pos += MAIN_BUFFER_SIZE) { UINT read_bytes = min(MAIN_BUFFER_SIZE, fsize - pos); if (ReadVirtualFile(&vfile, MAIN_BUFFER, pos, read_bytes, NULL) != 0) ret = false; if (!ShowProgress(pos + read_bytes, fsize, path)) ret = false; sha_update(MAIN_BUFFER, read_bytes); } sha_get(sha256); } else { // for regular FAT files FIL file; size_t fsize; if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) return false; fsize = f_size(&file); f_lseek(&file, 0); f_sync(&file); sha_init(SHA256_MODE); for (size_t pos = 0; (pos < fsize) && ret; pos += MAIN_BUFFER_SIZE) { UINT bytes_read = 0; if (fx_read(&file, MAIN_BUFFER, MAIN_BUFFER_SIZE, &bytes_read) != FR_OK) ret = false; if (!ShowProgress(pos + bytes_read, fsize, path)) ret = false; sha_update(MAIN_BUFFER, bytes_read); } sha_get(sha256); fx_close(&file); } ShowProgress(1, 1, path); return ret; } u32 FileFindData(const char* path, u8* data, u32 size, u32 offset) { u32 found = (u32) -1; u32 fsize = FileGetSize(path); for (u32 pass = 0; pass < 2; pass++) { bool show_progress = false; u32 pos = (pass == 0) ? offset : 0; u32 search_end = (pass == 0) ? fsize : offset + size; search_end = (search_end > fsize) ? fsize : search_end; for (; (pos < search_end) && (found == (u32) -1); pos += MAIN_BUFFER_SIZE - (size - 1)) { UINT read_bytes = min(MAIN_BUFFER_SIZE, search_end - pos); if (FileGetData(path, MAIN_BUFFER, read_bytes, pos) != read_bytes) break; for (u32 i = 0; i + size <= read_bytes; i++) { if (memcmp(MAIN_BUFFER + i, data, size) == 0) { found = pos + i; break; } } if (!show_progress && (found == (u32) -1) && (pos + read_bytes < fsize)) { ShowProgress(0, 0, path); show_progress = true; } if (show_progress && (!ShowProgress(pos + read_bytes, fsize, path))) break; } } return found; } bool FileInjectFile(const char* dest, const char* orig, u32 offset) { VirtualFile dvfile; VirtualFile ovfile; FIL ofile; FIL dfile; size_t osize; size_t dsize; bool vdest; bool vorig; bool ret; if (!CheckWritePermissions(dest)) return false; if (strncmp(dest, orig, 256) == 0) { ShowPrompt(false, "Error: Can't inject file into itself"); return false; } // open destination if (DriveType(dest) & DRV_VIRTUAL) { vdest = true; if (!GetVirtualFile(&dvfile, dest)) return false; dsize = dvfile.size; } else { vdest = false; if (fx_open(&dfile, dest, FA_WRITE | FA_OPEN_EXISTING) != FR_OK) return false; dsize = f_size(&dfile); f_lseek(&dfile, offset); f_sync(&dfile); } // open origin if (DriveType(orig) & DRV_VIRTUAL) { vorig = true; if (!GetVirtualFile(&ovfile, orig)) { if (!vdest) fx_close(&dfile); return false; } osize = ovfile.size; } else { vorig = false; if (fx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK) { if (!vdest) fx_close(&dfile); return false; } osize = f_size(&ofile); f_lseek(&ofile, 0); f_sync(&ofile); } // check file limits if (offset + osize > dsize) { ShowPrompt(false, "Operation would write beyond end of file"); if (!vdest) fx_close(&dfile); if (!vorig) fx_close(&ofile); return false; } ret = true; ShowProgress(0, 0, orig); for (size_t pos = 0; (pos < osize) && ret; pos += MAIN_BUFFER_SIZE) { UINT read_bytes = min(MAIN_BUFFER_SIZE, osize - pos); UINT bytes_read = read_bytes; UINT bytes_written = read_bytes; if ((!vorig && (fx_read(&ofile, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK)) || (vorig && ReadVirtualFile(&ovfile, MAIN_BUFFER, pos, read_bytes, NULL) != 0)) ret = false; if (!ShowProgress(pos + (bytes_read / 2), osize, orig)) ret = false; if ((!vdest && (fx_write(&dfile, MAIN_BUFFER, read_bytes, &bytes_written) != FR_OK)) || (vdest && WriteVirtualFile(&dvfile, MAIN_BUFFER, offset + pos, read_bytes, NULL) != 0)) ret = false; if (bytes_read != bytes_written) ret = false; } ShowProgress(1, 1, orig); if (!vdest) fx_close(&dfile); if (!vorig) fx_close(&ofile); return ret; } bool PathCopyVrtToVrt(const char* destdir, const char* orig) { VirtualFile dvfile; VirtualFile ovfile; bool ret = true; char dest[256]; // maximum path name length in FAT char* oname = strrchr(orig, '/'); if (oname == NULL) return false; // not a proper origin path snprintf(dest, 255, "%s/%s", destdir, (++oname)); char deststr[36 + 1]; char origstr[36 + 1]; TruncateString(deststr, dest, 36, 8); TruncateString(origstr, orig, 36, 8); if (!GetVirtualFile(&dvfile, dest) || !GetVirtualFile(&ovfile, orig)) return false; u32 osize = ovfile.size; if (dvfile.size != osize) { // almost impossible, but so what... ShowPrompt(false, "Virtual file size mismatch:\n%s\n%s", origstr, deststr); return false; } else if (strncmp(dest, orig, 256) == 0) { // destination == origin ShowPrompt(false, "Origin equals destination:\n%s\n%s", origstr, deststr); return false; } if ((dvfile.keyslot == ovfile.keyslot) && (dvfile.offset == ovfile.offset)) // this improves copy times dvfile.keyslot = ovfile.keyslot = 0xFF; // unmount critical NAND drives DismountDriveType(DriveType(destdir)&(DRV_SYSNAND|DRV_EMUNAND|DRV_IMAGE)); if (!ShowProgress(0, 0, orig)) ret = false; for (size_t pos = 0; (pos < osize) && ret; pos += MAIN_BUFFER_SIZE) { UINT read_bytes = min(MAIN_BUFFER_SIZE, osize - pos); if (ReadVirtualFile(&ovfile, MAIN_BUFFER, pos, read_bytes, NULL) != 0) ret = false; if (!ShowProgress(pos + (read_bytes / 2), osize, orig)) ret = false; if (WriteVirtualFile(&dvfile, MAIN_BUFFER, pos, read_bytes, NULL) != 0) ret = false; } ShowProgress(1, 1, orig); InitExtFS(); return ret; } bool PathCopyFatToVrt(const char* destdir, const char* orig) { VirtualFile dvfile; FIL ofile; bool ret = true; char dest[256]; // maximum path name length in FAT char* oname = strrchr(orig, '/'); if (oname == NULL) return false; // not a proper origin path snprintf(dest, 255, "%s/%s", destdir, (++oname)); // FAT file size FILINFO fno; if (fa_stat(orig, &fno) != FR_OK) return false; // file does not exist u32 osize = fno.fsize; // virtual file if (!GetVirtualFile(&dvfile, dest)) { VirtualDir vdir; if (!GetVirtualDir(&vdir, destdir)) return false; while (true) { // search by size should be a last resort solution if (!ReadVirtualDir(&dvfile, &vdir)) return false; if (dvfile.size == osize) break; // file found } if (!ShowPrompt(true, "Entry not found: %s\nInject into %s instead?", dest, dvfile.name)) return false; } else if (dvfile.size != osize) { // handling for differing sizes char deststr[36 + 1]; char origstr[36 + 1]; char osizestr[32]; char dsizestr[32]; TruncateString(deststr, dest, 36, 8); TruncateString(origstr, orig, 36, 8); FormatBytes(osizestr, osize); FormatBytes(dsizestr, dvfile.size); if (dvfile.size > osize) { if (!ShowPrompt(true, "File smaller than available space:\n%s (%s)\n%s (%s)\nContinue?", origstr, osizestr, deststr, dsizestr)) return false; } else { ShowPrompt(false, "File bigger than available space:\n%s (%s)\n%s (%s)", origstr, osizestr, deststr, dsizestr); return false; } } // FAT file if (fx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK) return false; f_lseek(&ofile, 0); f_sync(&ofile); // unmount critical NAND drives DismountDriveType(DriveType(destdir)&(DRV_SYSNAND|DRV_EMUNAND|DRV_IMAGE)); if (!ShowProgress(0, 0, orig)) ret = false; for (size_t pos = 0; (pos < osize) && ret; pos += MAIN_BUFFER_SIZE) { UINT bytes_read = 0; if (fx_read(&ofile, MAIN_BUFFER, MAIN_BUFFER_SIZE, &bytes_read) != FR_OK) ret = false; if (!ShowProgress(pos + (bytes_read / 2), osize, orig)) ret = false; if (WriteVirtualFile(&dvfile, MAIN_BUFFER, pos, bytes_read, NULL) != 0) ret = false; } ShowProgress(1, 1, orig); fx_close(&ofile); InitExtFS(); return ret; } bool PathCopyVrtToFat(char* dest, char* orig, u32* flags) { VirtualFile vfile; FILINFO fno; bool ret = false; if (fa_stat(dest, &fno) != FR_OK) { // is root or destination does not exist DIR tmp_dir; // check if root if (fa_opendir(&tmp_dir, dest) != FR_OK) return false; f_closedir(&tmp_dir); } else if (!(fno.fattrib & AM_DIR)) return false; // destination is not a directory (must be at this point) // build full destination path (on top of destination directory) char* oname = strrchr(orig, '/'); char* dname = dest + strnlen(dest, 255); if (oname == NULL) return false; // not a proper origin path oname++; *(dname++) = '/'; strncpy(dname, oname, 256 - (dname - dest)); // open / check virtual file if (!GetVirtualFile(&vfile, orig)) return false; // check if destination exists if (flags && !(*flags & (OVERWRITE_CUR|OVERWRITE_ALL)) && (fa_stat(dest, NULL) == FR_OK)) { if (*flags & SKIP_ALL) { *flags |= SKIP_CUR; return true; } const char* optionstr[5] = {"Choose new name", "Overwrite file(s)", "Skip file(s)", "Overwrite all", "Skip all"}; char namestr[36 + 1]; TruncateString(namestr, dest, 36, 8); u32 user_select = ShowSelectPrompt((*flags & ASK_ALL) ? 5 : 3, optionstr, "Destination already exists:\n%s", namestr); if (user_select == 1) { do { if (!ShowStringPrompt(dname, 255 - (dname - dest), "Choose new destination name")) return false; } while (fa_stat(dest, NULL) == FR_OK); } else if (user_select == 2) { *flags |= OVERWRITE_CUR; } else if (user_select == 3) { *flags |= SKIP_CUR; return true; } else if (user_select == 4) { *flags |= OVERWRITE_ALL; } else if (user_select == 5) { *flags |= (SKIP_CUR|SKIP_ALL); return true; } else { return false; } } // the copy process takes place here if (!ShowProgress(0, 0, orig)) return false; if (vfile.flags & VFLAG_DIR) { // processing folders DIR pdir; VirtualDir vdir; char* fname = orig + strnlen(orig, 256); // create the destination folder if it does not already exist if (fa_opendir(&pdir, dest) != FR_OK) { if (f_mkdir(dest) != FR_OK) { ShowPrompt(false, "Error: Overwriting file with dir"); return false; } } else f_closedir(&pdir); if (!OpenVirtualDir(&vdir, &vfile)) return false; *(fname++) = '/'; while (true) { if (!ReadVirtualDir(&vfile, &vdir)) { ret = true; break; } char name[256]; if (!GetVirtualFilename(name, &vfile, 256)) break; strncpy(fname, name, 256 - (fname - orig)); if (!PathCopyVrtToFat(dest, orig, flags)) break; } } else { // copying files FIL dfile; u32 osize = vfile.size; if (GetFreeSpace(dest) < osize) { ShowPrompt(false, "Error: File is too big for destination"); return false; } if (fx_open(&dfile, dest, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) { ShowPrompt(false, "Error: Cannot create destination file"); return false; } f_lseek(&dfile, 0); f_sync(&dfile); ret = true; for (size_t pos = 0; (pos < osize) && ret; pos += MAIN_BUFFER_SIZE) { UINT read_bytes = min(MAIN_BUFFER_SIZE, osize - pos); UINT bytes_written = 0; if (ReadVirtualFile(&vfile, MAIN_BUFFER, pos, read_bytes, NULL) != 0) ret = false; if (!ShowProgress(pos + (read_bytes / 2), osize, orig)) ret = false; if (fx_write(&dfile, MAIN_BUFFER, read_bytes, &bytes_written) != FR_OK) ret = false; if (read_bytes != bytes_written) ret = false; } ShowProgress(1, 1, orig); fx_close(&dfile); if (!ret) f_unlink(dest); } *(--dname) = '\0'; return ret; } bool PathCopyWorker(char* dest, char* orig, u32* flags, bool move) { FILINFO fno; bool ret = false; if (fa_stat(dest, &fno) != FR_OK) { // is root or destination does not exist DIR tmp_dir; // check if root if (fa_opendir(&tmp_dir, dest) != FR_OK) return false; f_closedir(&tmp_dir); } else if (!(fno.fattrib & AM_DIR)) return false; // destination is not a directory (must be at this point) if (fa_stat(orig, &fno) != FR_OK) return false; // origin does not exist // build full destination path (on top of destination directory) char* oname = strrchr(orig, '/'); char* dname = dest + strnlen(dest, 255); if (oname == NULL) return false; // not a proper origin path oname++; *(dname++) = '/'; strncpy(dname, oname, 256 - (dname - dest)); // check if destination is part of or equal origin while (strncmp(dest, orig, 255) == 0) { if (!ShowStringPrompt(dname, 255 - (dname - dest), "Destination is equal to origin\nChoose another name?")) return false; } if (strncmp(dest, orig, strnlen(orig, 255)) == 0) { if ((dest[strnlen(orig, 255)] == '/') || (dest[strnlen(orig, 255)] == '\0')) { ShowPrompt(false, "Error: Destination is part of origin"); return false; } } // check if destination exists if (flags && !(*flags & (OVERWRITE_CUR|OVERWRITE_ALL)) && (fa_stat(dest, NULL) == FR_OK)) { if (*flags & SKIP_ALL) { *flags |= SKIP_CUR; return true; } const char* optionstr[5] = {"Choose new name", "Overwrite file(s)", "Skip file(s)", "Overwrite all", "Skip all"}; char namestr[36 + 1]; TruncateString(namestr, dest, 36, 8); u32 user_select = ShowSelectPrompt((*flags & ASK_ALL) ? 5 : 3, optionstr, "Destination already exists:\n%s", namestr); if (user_select == 1) { do { if (!ShowStringPrompt(dname, 255 - (dname - dest), "Choose new destination name")) return false; } while (fa_stat(dest, NULL) == FR_OK); } else if (user_select == 2) { *flags |= OVERWRITE_CUR; } else if (user_select == 3) { *flags |= SKIP_CUR; return true; } else if (user_select == 4) { *flags |= OVERWRITE_ALL; } else if (user_select == 5) { *flags |= (SKIP_CUR|SKIP_ALL); return true; } else { return false; } } // the copy process takes place here if (!ShowProgress(0, 0, orig)) return false; if (move && fa_stat(dest, NULL) != FR_OK) { // moving if dest not existing ret = (f_rename(orig, dest) == FR_OK); } else if (fno.fattrib & AM_DIR) { // processing folders (same for move & copy) DIR pdir; char* fname = orig + strnlen(orig, 256); // create the destination folder if it does not already exist if (fa_opendir(&pdir, dest) != FR_OK) { if (f_mkdir(dest) != FR_OK) { ShowPrompt(false, "Error: Overwriting file with dir"); return false; } } else f_closedir(&pdir); if (fa_opendir(&pdir, orig) != FR_OK) return false; *(fname++) = '/'; while (f_readdir(&pdir, &fno) == FR_OK) { if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0)) continue; // filter out virtual entries strncpy(fname, fno.fname, 256 - (fname - orig)); if (fno.fname[0] == 0) { ret = true; break; } else if (!PathCopyWorker(dest, orig, flags, move)) { break; } } f_closedir(&pdir); } else if (move) { // moving if destination exists if (fa_stat(dest, &fno) != FR_OK) return false; if (fno.fattrib & AM_DIR) { ShowPrompt(false, "Error: Overwriting dir with file"); return false; } if (f_unlink(dest) != FR_OK) return false; ret = (f_rename(orig, dest) == FR_OK); } else { // copying files FIL ofile; FIL dfile; size_t fsize; if (fx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK) return false; fsize = f_size(&ofile); if (GetFreeSpace(dest) < fsize) { ShowPrompt(false, "Error: File is too big for destination"); fx_close(&ofile); return false; } if (fx_open(&dfile, dest, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) { ShowPrompt(false, "Error: Cannot create destination file"); fx_close(&ofile); return false; } f_lseek(&dfile, 0); f_sync(&dfile); f_lseek(&ofile, 0); f_sync(&ofile); ret = true; for (size_t pos = 0; (pos < fsize) && ret; pos += MAIN_BUFFER_SIZE) { UINT bytes_read = 0; UINT bytes_written = 0; if (fx_read(&ofile, MAIN_BUFFER, MAIN_BUFFER_SIZE, &bytes_read) != FR_OK) ret = false; if (!ShowProgress(pos + (bytes_read / 2), fsize, orig)) ret = false; if (fx_write(&dfile, MAIN_BUFFER, bytes_read, &bytes_written) != FR_OK) ret = false; if (bytes_read != bytes_written) ret = false; } ShowProgress(1, 1, orig); fx_close(&ofile); fx_close(&dfile); if (!ret) f_unlink(dest); } *(--dname) = '\0'; return ret; } bool PathCopy(const char* destdir, const char* orig, u32* flags) { if (!CheckWritePermissions(destdir)) return false; if (flags) *flags = *flags & ~(SKIP_CUR|OVERWRITE_CUR); // reset local flags int ddrvtype = DriveType(destdir); int odrvtype = DriveType(orig); if (!(ddrvtype & DRV_VIRTUAL)) { // FAT / virtual to FAT char fdpath[256]; // 256 is the maximum length of a full path char fopath[256]; strncpy(fdpath, destdir, 255); strncpy(fopath, orig, 255); bool res = (odrvtype & DRV_VIRTUAL) ? PathCopyVrtToFat(fdpath, fopath, flags) : PathCopyWorker(fdpath, fopath, flags, false); return res; } else if (!(odrvtype & DRV_VIRTUAL)) { // FAT to virtual if (!(odrvtype & (DRV_SDCARD|DRV_RAMDRIVE))) { ShowPrompt(false, "Only files from SD card or\nramdrive are accepted"); return false; } return PathCopyFatToVrt(destdir, orig); } else return PathCopyVrtToVrt(destdir, orig); // virtual to virtual } bool PathMove(const char* destdir, const char* orig, u32* flags) { if (!CheckWritePermissions(destdir)) return false; if (!CheckWritePermissions(orig)) return false; if (flags) *flags = *flags & ~(SKIP_CUR|OVERWRITE_CUR); // reset local flags // moving only for regular FAT drives (= not alias drives) if (!(DriveType(destdir) & DriveType(orig) & DRV_STDFAT)) { ShowPrompt(false, "Error: Moving is not possible here"); return false; } else { char fdpath[256]; // 256 is the maximum length of a full path char fopath[256]; strncpy(fdpath, destdir, 255); strncpy(fopath, orig, 255); bool same_drv = (PathToNumFS(orig) == PathToNumFS(destdir)); bool res = PathCopyWorker(fdpath, fopath, flags, same_drv); if (res && (!flags || !(*flags&SKIP_CUR))) PathDelete(orig); return res; } } bool PathDeleteWorker(char* fpath) { FILINFO fno; // this code handles directory content deletion if (fa_stat(fpath, &fno) != FR_OK) return false; // fpath does not exist if (fno.fattrib & AM_DIR) { // process folder contents DIR pdir; char* fname = fpath + strnlen(fpath, 255); if (fa_opendir(&pdir, fpath) != FR_OK) return false; *(fname++) = '/'; while (f_readdir(&pdir, &fno) == FR_OK) { if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0)) continue; // filter out virtual entries strncpy(fname, fno.fname, fpath + 255 - fname); if (fno.fname[0] == 0) { break; } else { // return value won't matter PathDeleteWorker(fpath); } } f_closedir(&pdir); *(--fname) = '\0'; } return (f_unlink(fpath) == FR_OK); } bool PathDelete(const char* path) { char fpath[256]; // 256 is the maximum length of a full path if (!CheckWritePermissions(path)) return false; strncpy(fpath, path, 256); return PathDeleteWorker(fpath); } bool PathRename(const char* path, const char* newname) { char npath[256]; // 256 is the maximum length of a full path char* oldname = strrchr(path, '/'); if (!CheckWritePermissions(path)) return false; if (!oldname) return false; oldname++; strncpy(npath, path, oldname - path); strncpy(npath + (oldname - path), newname, 255 - (oldname - path)); return (f_rename(path, npath) == FR_OK); } bool DirCreate(const char* cpath, const char* dirname) { char npath[256]; // 256 is the maximum length of a full path if (!CheckWritePermissions(cpath)) return false; snprintf(npath, 255, "%s/%s", cpath, dirname); return (f_mkdir(npath) == FR_OK); } void CreateScreenshot() { const u8 bmp_header[54] = { 0x42, 0x4D, 0x36, 0xCA, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCA, 0x08, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x12, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; u8* buffer = MAIN_BUFFER + 54; u8* buffer_t = buffer + (400 * 240 * 3); char filename[16]; static u32 n = 0; for (; n < 1000; n++) { snprintf(filename, 16, "0:/snap%03i.bmp", (int) n); if (fa_stat(filename, NULL) != FR_OK) break; } if (n >= 1000) return; memcpy(MAIN_BUFFER, bmp_header, 54); memset(buffer, 0x1F, 400 * 240 * 3 * 2); for (u32 x = 0; x < 400; x++) for (u32 y = 0; y < 240; y++) memcpy(buffer_t + (y*400 + x) * 3, TOP_SCREEN + (x*240 + y) * 3, 3); for (u32 x = 0; x < 320; x++) for (u32 y = 0; y < 240; y++) memcpy(buffer + (y*400 + x + 40) * 3, BOT_SCREEN + (x*240 + y) * 3, 3); FileSetData(filename, MAIN_BUFFER, 54 + (400 * 240 * 3 * 2), 0, true); } bool GetRootDirContentsWorker(DirStruct* contents) { static const char* drvname[] = { "SDCARD", "SYSNAND CTRNAND", "SYSNAND TWLN", "SYSNAND TWLP", "EMUNAND CTRNAND", "EMUNAND TWLN", "EMUNAND TWLP", "IMGNAND CTRNAND", "IMGNAND TWLN", "IMGNAND TWLP", "GAME IMAGE", "SYSNAND SD", "EMUNAND SD", "SYSNAND VIRTUAL", "EMUNAND VIRTUAL", "IMGNAND VIRTUAL", "NAND XORPADS", "MEMORY VIRTUAL", "LAST SEARCH" }; static const char* drvnum[] = { "0:", "1:", "2:", "3:", "4:", "5:", "6:", "7:", "8:", "9:", "G:", "A:", "B:", "S:", "E:", "I:", "X:", "M:", "Z:" }; u32 n_entries = 0; // virtual root objects hacked in for (u32 pdrv = 0; (pdrv < NORM_FS+VIRT_FS) && (n_entries < MAX_DIR_ENTRIES); pdrv++) { DirEntry* entry = &(contents->entry[n_entries]); if (!DriveType(drvnum[pdrv])) continue; // drive not available memset(entry->path, 0x00, 64); snprintf(entry->path + 0, 4, drvnum[pdrv]); if ((pdrv == 7) && ((GetMountState() == IMG_FAT) || (GetMountState() == IMG_RAMDRV))) snprintf(entry->path + 4, 32, "[%s] %s", drvnum[pdrv], // FAT image / RAM drive special handling (GetMountState() == IMG_FAT) ? "FAT IMAGE" : "RAMDRIVE"); else if (pdrv == 10) // Game drive special handling snprintf(entry->path + 4, 32, "[%s] %s %s", drvnum[pdrv], (GetMountState() == GAME_CIA) ? "CIA" : (GetMountState() == GAME_NCSD) ? "NCSD" : (GetMountState() == GAME_NCCH) ? "NCCH" : "UNK", drvname[pdrv]); else snprintf(entry->path + 4, 32, "[%s] %s", drvnum[pdrv], drvname[pdrv]); entry->name = entry->path + 4; entry->size = GetTotalSpace(entry->path); entry->type = T_ROOT; entry->marked = 0; n_entries++; } contents->n_entries = n_entries; return contents->n_entries; } bool GetDirContentsWorker(DirStruct* contents, char* fpath, int fnsize, const char* pattern, bool recursive) { DIR pdir; FILINFO fno; char* fname = fpath + strnlen(fpath, fnsize - 1); bool ret = false; if (fa_opendir(&pdir, fpath) != FR_OK) return false; (fname++)[0] = '/'; while (f_readdir(&pdir, &fno) == FR_OK) { if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0)) continue; // filter out virtual entries strncpy(fname, fno.fname, (fnsize - 1) - (fname - fpath)); if (fno.fname[0] == 0) { ret = true; break; } else if (!pattern || MatchName(pattern, fname)) { DirEntry* entry = &(contents->entry[contents->n_entries]); strncpy(entry->path, fpath, 256); entry->name = entry->path + (fname - fpath); if (fno.fattrib & AM_DIR) { entry->type = T_DIR; entry->size = 0; } else { entry->type = T_FILE; entry->size = fno.fsize; } entry->marked = 0; if (contents->n_entries >= MAX_DIR_ENTRIES) { ret = true; // Too many entries, still okay break; } contents->n_entries++; } if (recursive && (fno.fattrib & AM_DIR)) { if (!GetDirContentsWorker(contents, fpath, fnsize, pattern, recursive)) break; } } f_closedir(&pdir); return ret; } void SearchDirContents(DirStruct* contents, const char* path, const char* pattern, bool recursive) { contents->n_entries = 0; if (!(*path)) { // root directory if (!GetRootDirContentsWorker(contents)) contents->n_entries = 0; // not required, but so what? } else { // create virtual '..' entry contents->entry->name = contents->entry->path + 8; strncpy(contents->entry->path, "*?*?*", 8); strncpy(contents->entry->name, "..", 4); contents->entry->type = T_DOTDOT; contents->entry->size = 0; contents->n_entries = 1; // search the path char fpath[256]; // 256 is the maximum length of a full path strncpy(fpath, path, 256); if (DriveType(path) & DRV_VIRTUAL) { if (!GetVirtualDirContents(contents, fpath, 256, pattern, recursive)) contents->n_entries = 0; } else { if (!GetDirContentsWorker(contents, fpath, 256, pattern, recursive)) contents->n_entries = 0; } SortDirStruct(contents); } } void GetDirContents(DirStruct* contents, const char* path) { if (IsSearchDrive(path)) { ShowString("Searching, please wait..."); SearchDirContents(contents, search_path, search_pattern, true); ClearScreenF(true, false, COLOR_STD_BG); } else SearchDirContents(contents, path, NULL, false); } uint64_t GetFreeSpace(const char* path) { DWORD free_clusters; FATFS *fs_ptr; char fsname[4] = { '\0' }; int pdrv = PathToNumFSA(path); if (pdrv < 0) return 0; snprintf(fsname, 3, "%i:", pdrv); if (f_getfree(fsname, &free_clusters, &fs_ptr) != FR_OK) return 0; return (uint64_t) free_clusters * fs[pdrv].csize * _MAX_SS; } uint64_t GetTotalSpace(const char* path) { int pdrv = PathToNumFSA(path); if (pdrv < 0) return 0; return (uint64_t) (fs[pdrv].n_fatent - 2) * fs[pdrv].csize * _MAX_SS; } uint64_t GetPartitionOffsetSector(const char* path) { int pdrv = PathToNumFSA(path); if (pdrv < 0) return -1; return (uint64_t) fs[pdrv].volbase; }