2016-12-10 15:32:03 +01:00
|
|
|
#include "fsutil.h"
|
|
|
|
#include "fsinit.h"
|
|
|
|
#include "fsdrive.h"
|
|
|
|
#include "fsperm.h"
|
2016-10-30 16:20:09 +01:00
|
|
|
#include "sddata.h"
|
2017-01-27 18:32:52 +01:00
|
|
|
#include "vff.h"
|
2016-12-10 15:32:03 +01:00
|
|
|
#include "virtual.h"
|
2016-12-13 17:10:39 +01:00
|
|
|
#include "image.h"
|
2016-04-25 02:46:32 +02:00
|
|
|
#include "sha.h"
|
2016-07-13 19:59:36 +02:00
|
|
|
#include "sdmmc.h"
|
2016-04-05 20:34:50 +02:00
|
|
|
#include "ff.h"
|
2016-12-10 15:32:03 +01:00
|
|
|
#include "ui.h"
|
2019-06-03 01:37:10 +02:00
|
|
|
#include "swkbd.h"
|
2016-03-11 01:29:14 +01:00
|
|
|
|
2018-04-22 12:28:37 -05:00
|
|
|
#define SKIP_CUR (1UL<<10)
|
|
|
|
#define OVERWRITE_CUR (1UL<<11)
|
2016-07-27 00:19:12 +02:00
|
|
|
|
2017-06-21 01:04:36 +02:00
|
|
|
#define _MAX_FS_OPT 8 // max file selector options
|
|
|
|
|
2016-07-13 19:59:36 +02:00
|
|
|
// Volume2Partition resolution table
|
|
|
|
PARTITION VolToPart[] = {
|
2017-04-24 01:57:34 +02:00
|
|
|
{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0},
|
2016-07-13 19:59:36 +02:00
|
|
|
{5, 0}, {6, 0}, {7, 0}, {8, 0}, {9, 0}
|
|
|
|
};
|
|
|
|
|
|
|
|
uint64_t GetSDCardSize() {
|
|
|
|
if (sdmmc_sdcard_init() != 0) return 0;
|
|
|
|
return (u64) getMMCDevice(1)->total_size * 512;
|
|
|
|
}
|
|
|
|
|
2017-01-18 18:07:12 +01:00
|
|
|
bool FormatSDCard(u64 hidden_mb, u32 cluster_size, const char* label) {
|
2016-07-13 19:59:36 +02:00
|
|
|
u8 mbr[0x200] = { 0 };
|
2017-05-31 23:26:45 +02:00
|
|
|
u8 ncsd[0x200] = { 0 };
|
2016-07-13 19:59:36 +02:00
|
|
|
u8 mbrdata[0x42] = {
|
|
|
|
0x80, 0x01, 0x01, 0x00, 0x0C, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
2017-11-07 01:13:13 +01:00
|
|
|
0x80, 0x01, 0x01, 0x00, 0xDA, 0xFE, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
2016-07-13 19:59:36 +02:00
|
|
|
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;
|
2016-07-26 20:58:41 +02:00
|
|
|
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;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-07-26 20:58:41 +02:00
|
|
|
// FAT size check
|
|
|
|
if (fat_size < 0x80000) { // minimum free space: 256MB
|
2016-12-08 13:18:25 +01:00
|
|
|
ShowPrompt(false, "Error: SD card is too small");
|
2016-07-13 19:59:36 +02:00
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-02-27 16:53:45 +09:00
|
|
|
// Write protection check
|
|
|
|
if (SD_WRITE_PROTECTED) {
|
|
|
|
ShowPrompt(false, "SD card is write protected!\nCan't continue.");
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-07-13 19:59:36 +02:00
|
|
|
// build the MBR
|
2016-07-26 20:58:41 +02:00
|
|
|
memcpy(mbrdata + 0x08, &fat_sector, 4);
|
|
|
|
memcpy(mbrdata + 0x0C, &fat_size, 4);
|
|
|
|
memcpy(mbrdata + 0x18, &emu_sector, 4);
|
|
|
|
memcpy(mbrdata + 0x1C, &emu_size, 4);
|
2016-07-13 19:59:36 +02:00
|
|
|
memcpy(mbr + 0x1BE, mbrdata, 0x42);
|
2017-03-13 20:20:20 +01:00
|
|
|
if (hidden_mb) memcpy(mbr, "GATEWAYNAND", 12); // legacy
|
2016-07-13 19:59:36 +02:00
|
|
|
else memset(mbr + 0x1CE, 0, 0x10);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-07-13 19:59:36 +02:00
|
|
|
// one last warning....
|
2017-02-22 23:27:34 +01:00
|
|
|
// 0:/Nintendo 3DS/ write permission is ignored here, this warning is enough
|
|
|
|
if (!ShowUnlockSequence(5, "!WARNING!\n \nProceeding will format this SD.\nThis will irreversibly delete\nALL data on it."))
|
2016-07-13 19:59:36 +02:00
|
|
|
return false;
|
2020-08-24 21:27:19 -07:00
|
|
|
ShowString("Formatting SD, please wait...");
|
|
|
|
|
2016-07-13 19:59:36 +02:00
|
|
|
// write the MBR to disk
|
|
|
|
// !this assumes a fully deinitialized file system!
|
2017-05-31 23:26:45 +02:00
|
|
|
if ((sdmmc_sdcard_init() != 0) || (sdmmc_sdcard_writesectors(0, 1, mbr) != 0) ||
|
|
|
|
(emu_size && ((sdmmc_nand_readsectors(0, 1, ncsd) != 0) || (sdmmc_sdcard_writesectors(1, 1, ncsd) != 0)))) {
|
2016-12-08 13:18:25 +01:00
|
|
|
ShowPrompt(false, "Error: SD card i/o failure");
|
2016-07-13 19:59:36 +02:00
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-07-13 19:59:36 +02:00
|
|
|
// format the SD card
|
2017-04-24 01:57:34 +02:00
|
|
|
VolToPart[0].pt = 1; // workaround to prevent FatFS rebuilding the MBR
|
2016-12-10 15:32:03 +01:00
|
|
|
InitSDCardFS();
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
|
|
|
if (!buffer) bkpt; // will not happen
|
2019-12-11 00:08:35 +01:00
|
|
|
MKFS_PARM opt0, opt1;
|
|
|
|
opt0.fmt = opt1.fmt = FM_FAT32;
|
|
|
|
opt0.au_size = cluster_size;
|
|
|
|
opt1.au_size = 0;
|
|
|
|
opt0.align = opt1.align = 0;
|
|
|
|
opt0.n_fat = opt1.n_fat = 1;
|
|
|
|
opt0.n_root = opt1.n_root = 0;
|
2020-08-24 21:27:19 -07:00
|
|
|
bool ret = ((f_mkfs("0:", &opt0, buffer, STD_BUFFER_SIZE) == FR_OK) ||
|
2019-12-11 00:08:35 +01:00
|
|
|
(f_mkfs("0:", &opt1, buffer, STD_BUFFER_SIZE) == FR_OK)) &&
|
2017-05-31 23:14:20 +02:00
|
|
|
(f_setlabel((label) ? label : "0:GM9SD") == FR_OK);
|
2018-01-24 23:32:06 +01:00
|
|
|
free(buffer);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-12-10 15:32:03 +01:00
|
|
|
DeinitSDCardFS();
|
2017-04-24 01:57:34 +02:00
|
|
|
VolToPart[0].pt = 0; // revert workaround to prevent SD mount problems
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-07-13 19:59:36 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-02-20 19:58:36 +01:00
|
|
|
bool SetupBonusDrive(void) {
|
2017-02-22 18:44:39 +01:00
|
|
|
if (!ShowUnlockSequence(3, "Format the bonus drive?\nThis will irreversibly delete\nALL data on it."))
|
2017-02-20 19:58:36 +01:00
|
|
|
return false;
|
|
|
|
ShowString("Formatting drive, please wait...");
|
|
|
|
if (GetMountState() & IMG_NAND) InitImgFS(NULL);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
|
|
|
if (!buffer) bkpt;
|
2019-12-11 00:08:35 +01:00
|
|
|
bool ret = (f_mkfs("8:", NULL, buffer, STD_BUFFER_SIZE) == FR_OK);
|
2018-01-24 23:32:06 +01:00
|
|
|
free(buffer);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-02-20 19:58:36 +01:00
|
|
|
if (ret) {
|
|
|
|
f_setlabel("8:BONUS");
|
|
|
|
InitExtFS();
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-12-13 17:10:39 +01:00
|
|
|
bool FileUnlock(const char* path) {
|
2016-12-08 22:19:42 +01:00
|
|
|
FIL file;
|
2017-11-22 03:13:20 +01:00
|
|
|
FRESULT res;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-12-10 15:32:03 +01:00
|
|
|
if (!(DriveType(path) & DRV_FAT)) return true; // can't really check this
|
2017-11-22 03:13:20 +01:00
|
|
|
if ((res = fx_open(&file, path, FA_READ | FA_OPEN_EXISTING)) != FR_OK) {
|
2021-08-02 14:50:46 -05:00
|
|
|
char pathstr[UTF_BUFFER_BYTESIZE(32)];
|
2016-12-13 17:10:39 +01:00
|
|
|
TruncateString(pathstr, path, 32, 8);
|
2020-08-24 21:27:19 -07:00
|
|
|
if (GetMountState() && (res == FR_LOCKED) &&
|
2016-12-13 17:10:39 +01:00
|
|
|
(ShowPrompt(true, "%s\nFile is currently mounted.\nUnmount to unlock?", pathstr))) {
|
|
|
|
InitImgFS(NULL);
|
|
|
|
if (fx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
|
|
|
return false;
|
|
|
|
} else return false;
|
|
|
|
}
|
|
|
|
fx_close(&file);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-12-13 17:10:39 +01:00
|
|
|
return true;
|
2016-12-08 22:19:42 +01:00
|
|
|
}
|
|
|
|
|
2017-06-26 01:44:16 +02:00
|
|
|
bool FileSetData(const char* path, const void* data, size_t size, size_t foffset, bool create) {
|
2017-01-27 18:32:52 +01:00
|
|
|
UINT bw;
|
2016-05-27 01:21:05 +02:00
|
|
|
if (!CheckWritePermissions(path)) return false;
|
2017-01-27 18:32:52 +01:00
|
|
|
if ((DriveType(path) & DRV_FAT) && create) f_unlink(path);
|
|
|
|
return (fvx_qwrite(path, data, foffset, size, &bw) == FR_OK) && (bw == size);
|
2016-02-26 19:43:30 +01:00
|
|
|
}
|
|
|
|
|
2017-06-26 01:44:16 +02:00
|
|
|
size_t FileGetData(const char* path, void* data, size_t size, size_t foffset) {
|
2017-01-27 18:32:52 +01:00
|
|
|
UINT br;
|
|
|
|
if (fvx_qread(path, data, foffset, size, &br) != FR_OK) br = 0;
|
|
|
|
return br;
|
2016-03-11 01:29:14 +01:00
|
|
|
}
|
|
|
|
|
2016-04-10 15:55:39 +02:00
|
|
|
size_t FileGetSize(const char* path) {
|
2017-06-06 21:14:19 +02:00
|
|
|
FILINFO fno;
|
|
|
|
if (fvx_stat(path, &fno) != FR_OK)
|
|
|
|
return 0;
|
|
|
|
return fno.fsize;
|
2016-04-10 15:55:39 +02:00
|
|
|
}
|
|
|
|
|
2021-09-30 13:12:54 -04:00
|
|
|
bool FileGetSha(const char* path, u8* hash, u64 offset, u64 size, bool sha1) {
|
2016-04-25 02:46:32 +02:00
|
|
|
bool ret = true;
|
2017-01-27 18:32:52 +01:00
|
|
|
FIL file;
|
|
|
|
u64 fsize;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-01-27 18:32:52 +01:00
|
|
|
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
|
|
|
return false;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-01-27 18:32:52 +01:00
|
|
|
fsize = fvx_size(&file);
|
2017-08-23 02:04:53 +02:00
|
|
|
if (offset + size > fsize) return false;
|
|
|
|
if (!size) size = fsize - offset;
|
|
|
|
fvx_lseek(&file, offset);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
u32 bufsiz = min(STD_BUFFER_SIZE, fsize);
|
|
|
|
u8* buffer = (u8*) malloc(bufsiz);
|
|
|
|
if (!buffer) return false;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-04-25 02:46:32 +02:00
|
|
|
ShowProgress(0, 0, path);
|
2021-09-30 13:12:54 -04:00
|
|
|
sha_init(sha1 ? SHA1_MODE : SHA256_MODE);
|
2018-01-24 23:32:06 +01:00
|
|
|
for (u64 pos = 0; (pos < size) && ret; pos += bufsiz) {
|
|
|
|
UINT read_bytes = min(bufsiz, size - pos);
|
2017-01-27 18:32:52 +01:00
|
|
|
UINT bytes_read = 0;
|
2018-01-24 23:32:06 +01:00
|
|
|
if (fvx_read(&file, buffer, read_bytes, &bytes_read) != FR_OK)
|
2017-01-27 18:32:52 +01:00
|
|
|
ret = false;
|
2017-08-23 02:04:53 +02:00
|
|
|
if (!ShowProgress(pos + bytes_read, size, path))
|
2017-01-27 18:32:52 +01:00
|
|
|
ret = false;
|
2018-01-24 23:32:06 +01:00
|
|
|
sha_update(buffer, bytes_read);
|
2016-04-25 02:46:32 +02:00
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2021-09-30 13:12:54 -04:00
|
|
|
sha_get(hash);
|
2017-01-27 18:32:52 +01:00
|
|
|
fvx_close(&file);
|
2018-01-24 23:32:06 +01:00
|
|
|
free(buffer);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-04-25 02:46:32 +02:00
|
|
|
ShowProgress(1, 1, path);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-04-25 02:46:32 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2016-12-11 15:40:44 +01:00
|
|
|
u32 FileFindData(const char* path, u8* data, u32 size_data, u32 offset_file) {
|
2017-01-27 18:32:52 +01:00
|
|
|
FIL file; // used for FAT & virtual
|
2017-01-21 21:31:38 +01:00
|
|
|
u64 found = (u64) -1;
|
|
|
|
u64 fsize = FileGetSize(path);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-01-27 18:32:52 +01:00
|
|
|
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
|
|
|
|
return found;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
|
|
|
if (!buffer) return false;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-12-11 15:40:44 +01:00
|
|
|
// main routine
|
2016-07-12 18:33:52 +02:00
|
|
|
for (u32 pass = 0; pass < 2; pass++) {
|
|
|
|
bool show_progress = false;
|
2017-01-21 21:31:38 +01:00
|
|
|
u64 pos = (pass == 0) ? offset_file : 0;
|
|
|
|
u64 search_end = (pass == 0) ? fsize : offset_file + size_data;
|
2016-07-12 18:33:52 +02:00
|
|
|
search_end = (search_end > fsize) ? fsize : search_end;
|
2018-01-24 23:32:06 +01:00
|
|
|
for (; (pos < search_end) && (found == (u64) -1); pos += STD_BUFFER_SIZE - (size_data - 1)) {
|
|
|
|
UINT read_bytes = min(STD_BUFFER_SIZE, search_end - pos);
|
2017-01-27 18:32:52 +01:00
|
|
|
UINT btr;
|
|
|
|
fvx_lseek(&file, pos);
|
2018-01-24 23:32:06 +01:00
|
|
|
if ((fvx_read(&file, buffer, read_bytes, &btr) != FR_OK) || (btr != read_bytes))
|
2017-01-27 18:32:52 +01:00
|
|
|
break;
|
2016-12-11 15:40:44 +01:00
|
|
|
for (u32 i = 0; i + size_data <= read_bytes; i++) {
|
2018-01-24 23:32:06 +01:00
|
|
|
if (memcmp(buffer + i, data, size_data) == 0) {
|
2016-07-12 18:33:52 +02:00
|
|
|
found = pos + i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-01-21 21:31:38 +01:00
|
|
|
if (!show_progress && (found == (u64) -1) && (pos + read_bytes < fsize)) {
|
2016-07-12 18:33:52 +02:00
|
|
|
ShowProgress(0, 0, path);
|
|
|
|
show_progress = true;
|
|
|
|
}
|
|
|
|
if (show_progress && (!ShowProgress(pos + read_bytes, fsize, path)))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
free(buffer);
|
2017-01-27 18:32:52 +01:00
|
|
|
fvx_close(&file);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-07-12 18:33:52 +02:00
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
bool FileInjectFile(const char* dest, const char* orig, u64 off_dest, u64 off_orig, u64 size, u32* flags) {
|
2016-06-17 19:28:43 +02:00
|
|
|
FIL ofile;
|
|
|
|
FIL dfile;
|
2017-08-23 01:24:55 +02:00
|
|
|
bool allow_expand = (flags && (*flags & ALLOW_EXPAND));
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-06-17 19:28:43 +02:00
|
|
|
if (!CheckWritePermissions(dest)) return false;
|
2017-09-13 02:07:40 +02:00
|
|
|
if (strncasecmp(dest, orig, 256) == 0) {
|
2016-06-18 15:23:21 +02:00
|
|
|
ShowPrompt(false, "Error: Can't inject file into itself");
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-01-27 18:32:52 +01:00
|
|
|
// open destination / origin
|
2017-08-23 01:24:55 +02:00
|
|
|
if (fvx_open(&dfile, dest, FA_WRITE | ((allow_expand) ? FA_OPEN_ALWAYS : FA_OPEN_EXISTING)) != FR_OK)
|
2017-01-27 18:32:52 +01:00
|
|
|
return false;
|
|
|
|
if ((fvx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK) &&
|
|
|
|
(!FileUnlock(orig) || (fvx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK))) {
|
|
|
|
fvx_close(&dfile);
|
|
|
|
return false;
|
2016-06-17 19:28:43 +02:00
|
|
|
}
|
2017-06-09 01:45:00 +02:00
|
|
|
fvx_lseek(&dfile, off_dest);
|
|
|
|
fvx_lseek(&ofile, off_orig);
|
|
|
|
if (!size && (off_orig < fvx_size(&ofile)))
|
|
|
|
size = fvx_size(&ofile) - off_orig;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-06-17 19:28:43 +02:00
|
|
|
// check file limits
|
2017-08-23 01:24:55 +02:00
|
|
|
if (!allow_expand && (off_dest + size > fvx_size(&dfile))) {
|
2016-06-17 19:28:43 +02:00
|
|
|
ShowPrompt(false, "Operation would write beyond end of file");
|
2017-01-27 18:32:52 +01:00
|
|
|
fvx_close(&dfile);
|
|
|
|
fvx_close(&ofile);
|
2016-06-17 19:28:43 +02:00
|
|
|
return false;
|
2017-06-09 01:45:00 +02:00
|
|
|
} else if (off_orig + size > fvx_size(&ofile)) {
|
|
|
|
ShowPrompt(false, "Not enough data in file");
|
|
|
|
fvx_close(&dfile);
|
|
|
|
fvx_close(&ofile);
|
|
|
|
return false;
|
2016-06-17 19:28:43 +02:00
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
|
|
|
if (!buffer) return false;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 01:45:00 +02:00
|
|
|
bool ret = true;
|
2016-06-17 19:49:19 +02:00
|
|
|
ShowProgress(0, 0, orig);
|
2018-01-24 23:32:06 +01:00
|
|
|
for (u64 pos = 0; (pos < size) && ret; pos += STD_BUFFER_SIZE) {
|
|
|
|
UINT read_bytes = min(STD_BUFFER_SIZE, size - pos);
|
2016-06-17 19:28:43 +02:00
|
|
|
UINT bytes_read = read_bytes;
|
|
|
|
UINT bytes_written = read_bytes;
|
2018-01-24 23:32:06 +01:00
|
|
|
if ((fvx_read(&ofile, buffer, read_bytes, &bytes_read) != FR_OK) ||
|
|
|
|
(fvx_write(&dfile, buffer, read_bytes, &bytes_written) != FR_OK) ||
|
2017-01-27 18:32:52 +01:00
|
|
|
(bytes_read != bytes_written))
|
2016-06-17 19:28:43 +02:00
|
|
|
ret = false;
|
2017-06-09 01:45:00 +02:00
|
|
|
if (ret && !ShowProgress(pos + bytes_read, size, orig)) {
|
|
|
|
if (flags && (*flags & NO_CANCEL)) {
|
2017-09-08 13:30:16 +02:00
|
|
|
ShowPrompt(false, "Cancel is not allowed here");
|
2017-06-30 03:06:57 +02:00
|
|
|
} else ret = !ShowPrompt(true, "B button detected. Cancel?");
|
|
|
|
ShowProgress(0, 0, orig);
|
|
|
|
ShowProgress(pos + bytes_read, size, orig);
|
2017-06-09 01:45:00 +02:00
|
|
|
}
|
2016-06-17 19:28:43 +02:00
|
|
|
}
|
|
|
|
ShowProgress(1, 1, orig);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
free(buffer);
|
2017-01-27 18:32:52 +01:00
|
|
|
fvx_close(&dfile);
|
|
|
|
fvx_close(&ofile);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-06-17 19:28:43 +02:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-01-02 02:26:49 +01:00
|
|
|
bool FileSetByte(const char* dest, u64 offset, u64 size, u8 fillbyte, u32* flags) {
|
|
|
|
FIL dfile;
|
|
|
|
bool allow_expand = (flags && (*flags & ALLOW_EXPAND));
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-02 02:26:49 +01:00
|
|
|
if (!CheckWritePermissions(dest)) return false;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-02 02:26:49 +01:00
|
|
|
// open destination
|
|
|
|
if (fvx_open(&dfile, dest, FA_WRITE | ((allow_expand) ? FA_OPEN_ALWAYS : FA_OPEN_EXISTING)) != FR_OK)
|
|
|
|
return false;
|
|
|
|
fvx_lseek(&dfile, offset);
|
|
|
|
if (!size && (offset < fvx_size(&dfile)))
|
|
|
|
size = fvx_size(&dfile) - offset;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-02 02:26:49 +01:00
|
|
|
// check file limits
|
|
|
|
if (!allow_expand && (offset + size > fvx_size(&dfile))) {
|
|
|
|
ShowPrompt(false, "Operation would write beyond end of file");
|
|
|
|
fvx_close(&dfile);
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
u32 bufsiz = min(STD_BUFFER_SIZE, size);
|
|
|
|
u8* buffer = (u8*) malloc(bufsiz);
|
|
|
|
if (!buffer) return false;
|
|
|
|
memset(buffer, fillbyte, bufsiz);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-02 02:26:49 +01:00
|
|
|
bool ret = true;
|
|
|
|
ShowProgress(0, 0, dest);
|
2018-01-24 23:32:06 +01:00
|
|
|
for (u64 pos = 0; (pos < size) && ret; pos += bufsiz) {
|
|
|
|
UINT write_bytes = min(bufsiz, size - pos);
|
2018-01-02 02:26:49 +01:00
|
|
|
UINT bytes_written = write_bytes;
|
2018-01-24 23:32:06 +01:00
|
|
|
if ((fvx_write(&dfile, buffer, write_bytes, &bytes_written) != FR_OK) ||
|
2018-01-02 02:26:49 +01:00
|
|
|
(write_bytes != bytes_written))
|
|
|
|
ret = false;
|
|
|
|
if (ret && !ShowProgress(pos + bytes_written, size, dest)) {
|
|
|
|
if (flags && (*flags & NO_CANCEL)) {
|
|
|
|
ShowPrompt(false, "Cancel is not allowed here");
|
|
|
|
} else ret = !ShowPrompt(true, "B button detected. Cancel?");
|
|
|
|
ShowProgress(0, 0, dest);
|
|
|
|
ShowProgress(pos + bytes_written, size, dest);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ShowProgress(1, 1, dest);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
free(buffer);
|
2018-01-02 02:26:49 +01:00
|
|
|
fvx_close(&dfile);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-02 02:26:49 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-08-01 02:03:05 +02:00
|
|
|
bool FileCreateDummy(const char* cpath, const char* filename, u64 size) {
|
|
|
|
char npath[256]; // 256 is the maximum length of a full path
|
|
|
|
if (!CheckWritePermissions(cpath)) return false;
|
2018-01-02 02:26:49 +01:00
|
|
|
if (filename) snprintf(npath, 255, "%s/%s", cpath, filename);
|
|
|
|
else snprintf(npath, 255, "%s", cpath);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-08-01 02:03:05 +02:00
|
|
|
// create dummy file (fail if already existing)
|
|
|
|
// then, expand the file size via cluster preallocation
|
|
|
|
FIL dfile;
|
|
|
|
if (fx_open(&dfile, npath, FA_WRITE | FA_CREATE_NEW) != FR_OK)
|
|
|
|
return false;
|
|
|
|
f_lseek(&dfile, size > 0xFFFFFFFF ? 0xFFFFFFFF : (FSIZE_t) size);
|
|
|
|
f_sync(&dfile);
|
|
|
|
fx_close(&dfile);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-07-18 00:16:41 +02:00
|
|
|
return (fa_stat(npath, NULL) == FR_OK);
|
2017-08-01 02:03:05 +02:00
|
|
|
}
|
|
|
|
|
2017-02-28 16:46:24 +01:00
|
|
|
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);
|
2018-07-18 00:16:41 +02:00
|
|
|
if (fa_mkdir(npath) != FR_OK) return false;
|
|
|
|
return (fa_stat(npath, NULL) == FR_OK);
|
2017-02-28 16:46:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool DirInfoWorker(char* fpath, bool virtual, u64* tsize, u32* tdirs, u32* tfiles) {
|
|
|
|
char* fname = fpath + strnlen(fpath, 256 - 1);
|
|
|
|
bool ret = true;
|
|
|
|
if (virtual) {
|
|
|
|
VirtualDir vdir;
|
|
|
|
VirtualFile vfile;
|
|
|
|
if (!GetVirtualDir(&vdir, fpath)) return false; // get dir reader object
|
|
|
|
while (ReadVirtualDir(&vfile, &vdir)) {
|
|
|
|
if (vfile.flags & VFLAG_DIR) {
|
|
|
|
(*tdirs)++;
|
|
|
|
*(fname++) = '/';
|
|
|
|
GetVirtualFilename(fname, &vfile, (256 - 1) - (fname - fpath));
|
|
|
|
if (!DirInfoWorker(fpath, virtual, tsize, tdirs, tfiles)) ret = false;
|
|
|
|
*(--fname) = '\0';
|
|
|
|
} else {
|
|
|
|
*tsize += vfile.size;
|
|
|
|
(*tfiles)++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
DIR pdir;
|
|
|
|
FILINFO fno;
|
|
|
|
if (fa_opendir(&pdir, fpath) != FR_OK) return false; // get dir reader object
|
|
|
|
while (f_readdir(&pdir, &fno) == FR_OK) {
|
|
|
|
if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0))
|
|
|
|
continue; // filter out virtual entries
|
|
|
|
if (fno.fname[0] == 0) break; // end of dir
|
|
|
|
if (fno.fattrib & AM_DIR) {
|
|
|
|
(*tdirs)++;
|
|
|
|
*(fname++) = '/';
|
|
|
|
strncpy(fname, fno.fname, (256 - 1) - (fname - fpath));
|
|
|
|
if (!DirInfoWorker(fpath, virtual, tsize, tdirs, tfiles)) ret = false;
|
|
|
|
*(--fname) = '\0';
|
|
|
|
} else {
|
|
|
|
*tsize += fno.fsize;
|
|
|
|
(*tfiles)++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
f_closedir(&pdir);
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-02-28 16:46:24 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DirInfo(const char* path, u64* tsize, u32* tdirs, u32* tfiles) {
|
|
|
|
bool virtual = (DriveType(path) & DRV_VIRTUAL);
|
|
|
|
char fpath[256];
|
2018-05-24 00:14:37 +02:00
|
|
|
strncpy(fpath, path, 256);
|
|
|
|
fpath[255] = '\0';
|
2017-02-28 16:46:24 +01:00
|
|
|
*tsize = *tdirs = *tfiles = 0;
|
|
|
|
bool res = DirInfoWorker(fpath, virtual, tsize, tdirs, tfiles);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2017-06-21 01:04:36 +02:00
|
|
|
bool PathExist(const char* path) {
|
|
|
|
return (fvx_stat(path, NULL) == FR_OK);
|
|
|
|
}
|
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer, u32 bufsiz) {
|
2017-06-09 16:21:41 +02:00
|
|
|
bool to_virtual = GetVirtualSource(dest);
|
2017-06-09 01:45:00 +02:00
|
|
|
bool silent = (flags && (*flags & SILENT));
|
2018-04-22 12:28:37 -05:00
|
|
|
bool append = (flags && (*flags & APPEND_ALL));
|
|
|
|
bool calcsha = (flags && (*flags & CALC_SHA) && !append);
|
2021-09-30 13:12:54 -04:00
|
|
|
bool sha1 = (flags && (*flags & USE_SHA1));
|
2017-06-09 16:21:41 +02:00
|
|
|
bool ret = false;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// check destination write permission (special paths only)
|
|
|
|
if (((*dest == '1') || (strncmp(dest, "0:/Nintendo 3DS", 16) == 0)) &&
|
2020-08-24 21:27:19 -07:00
|
|
|
(!flags || !(*flags & OVERRIDE_PERM)) &&
|
2017-06-09 16:21:41 +02:00
|
|
|
!CheckWritePermissions(dest)) return false;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-12-02 12:45:23 +01:00
|
|
|
FILINFO fno;
|
2017-06-06 21:14:19 +02:00
|
|
|
if (fvx_stat(orig, &fno) != FR_OK) return false; // origin does not exist
|
2017-06-09 16:21:41 +02:00
|
|
|
if (move && (to_virtual || fno.fattrib & AM_VRT)) return false; // trying to move a virtual file
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-03-02 22:05:28 +01:00
|
|
|
// path string (for output)
|
2021-08-02 14:50:46 -05:00
|
|
|
char deststr[UTF_BUFFER_BYTESIZE(36)];
|
2017-03-02 22:05:28 +01:00
|
|
|
TruncateString(deststr, dest, 36, 8);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-02-29 02:09:31 +01:00
|
|
|
// the copy process takes place here
|
2017-06-30 03:06:57 +02:00
|
|
|
if (!ShowProgress(0, 0, orig) && !(flags && (*flags & NO_CANCEL))) {
|
|
|
|
if (ShowPrompt(true, "%s\nB button detected. Cancel?", deststr)) return false;
|
|
|
|
ShowProgress(0, 0, orig);
|
|
|
|
}
|
2017-06-09 16:21:41 +02:00
|
|
|
if (move && fvx_stat(dest, NULL) != FR_OK) { // moving if dest not existing
|
|
|
|
ret = (fvx_rename(orig, dest) == FR_OK);
|
2016-04-29 02:21:36 +02:00
|
|
|
} else if (fno.fattrib & AM_DIR) { // processing folders (same for move & copy)
|
2016-02-29 02:09:31 +01:00
|
|
|
DIR pdir;
|
|
|
|
char* fname = orig + strnlen(orig, 256);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-04-22 12:28:37 -05:00
|
|
|
if (append) {
|
|
|
|
if (!silent) ShowPrompt(false, "%s\nError: Cannot append a folder", deststr);
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-03-02 17:22:44 +01:00
|
|
|
// create the destination folder if it does not already exist
|
2017-06-09 16:21:41 +02:00
|
|
|
if (fvx_opendir(&pdir, dest) != FR_OK) {
|
|
|
|
if (fvx_mkdir(dest) != FR_OK) {
|
2017-06-09 01:45:00 +02:00
|
|
|
if (!silent) ShowPrompt(false, "%s\nError: Overwriting file with dir", deststr);
|
2016-12-02 12:45:23 +01:00
|
|
|
return false;
|
|
|
|
}
|
2017-06-09 16:21:41 +02:00
|
|
|
} else fvx_closedir(&pdir);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-06 21:14:19 +02:00
|
|
|
if (fvx_opendir(&pdir, orig) != FR_OK)
|
2016-02-29 02:09:31 +01:00
|
|
|
return false;
|
2016-03-02 14:01:20 +01:00
|
|
|
*(fname++) = '/';
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-06 21:14:19 +02:00
|
|
|
while (fvx_readdir(&pdir, &fno) == FR_OK) {
|
2016-02-29 02:09:31 +01:00
|
|
|
if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0))
|
|
|
|
continue; // filter out virtual entries
|
2016-07-20 00:27:38 +02:00
|
|
|
strncpy(fname, fno.fname, 256 - (fname - orig));
|
2016-02-29 02:09:31 +01:00
|
|
|
if (fno.fname[0] == 0) {
|
|
|
|
ret = true;
|
|
|
|
break;
|
2017-06-09 16:21:41 +02:00
|
|
|
} else {
|
|
|
|
// recursively process directory
|
|
|
|
char* oname = strrchr(orig, '/');
|
|
|
|
char* dname = dest + strnlen(dest, 255);
|
|
|
|
if (oname == NULL) return false; // not a proper origin path
|
2018-03-16 18:46:53 +01:00
|
|
|
strncpy(dname, oname, 256 - (dname - dest)); // copy name plus preceding '/'
|
2018-01-24 23:32:06 +01:00
|
|
|
bool res = PathMoveCopyRec(dest, orig, flags, move, buffer, bufsiz);
|
2018-03-16 18:46:53 +01:00
|
|
|
*dname = '\0';
|
2017-06-09 16:21:41 +02:00
|
|
|
if (!res) break;
|
2016-02-29 02:09:31 +01:00
|
|
|
}
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-06 21:14:19 +02:00
|
|
|
fvx_closedir(&pdir);
|
2018-01-24 23:32:06 +01:00
|
|
|
*(--fname) = '\0';
|
2016-04-29 02:21:36 +02:00
|
|
|
} else if (move) { // moving if destination exists
|
2017-06-09 16:21:41 +02:00
|
|
|
if (fvx_stat(dest, &fno) != FR_OK) return false;
|
2016-04-29 02:21:36 +02:00
|
|
|
if (fno.fattrib & AM_DIR) {
|
2017-06-09 01:45:00 +02:00
|
|
|
if (!silent) ShowPrompt(false, "%s\nError: Overwriting dir with file", deststr);
|
2016-04-29 02:21:36 +02:00
|
|
|
return false;
|
|
|
|
}
|
2017-06-09 16:21:41 +02:00
|
|
|
if (fvx_unlink(dest) != FR_OK) return false;
|
|
|
|
ret = (fvx_rename(orig, dest) == FR_OK);
|
2016-04-29 02:21:36 +02:00
|
|
|
} else { // copying files
|
2016-02-29 02:09:31 +01:00
|
|
|
FIL ofile;
|
|
|
|
FIL dfile;
|
2018-04-22 12:28:37 -05:00
|
|
|
u64 osize;
|
|
|
|
u64 dsize;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-06 21:14:19 +02:00
|
|
|
if (fvx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK) {
|
|
|
|
if (!FileUnlock(orig) || (fvx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK))
|
2016-12-13 17:10:39 +01:00
|
|
|
return false;
|
|
|
|
ShowProgress(0, 0, orig); // reinit progress bar
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-04-22 12:28:37 -05:00
|
|
|
if ((!append || (fvx_open(&dfile, dest, FA_WRITE | FA_OPEN_EXISTING) != FR_OK)) &&
|
|
|
|
(fvx_open(&dfile, dest, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)) {
|
2017-06-09 01:45:00 +02:00
|
|
|
if (!silent) ShowPrompt(false, "%s\nError: Cannot open destination file", deststr);
|
2017-06-09 16:21:41 +02:00
|
|
|
fvx_close(&ofile);
|
2016-02-29 02:09:31 +01:00
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-08-15 16:39:15 +02:00
|
|
|
ret = true; // destination file exists by now, so we need to handle deletion
|
2018-04-22 12:28:37 -05:00
|
|
|
osize = fvx_size(&ofile);
|
2018-10-29 01:33:20 +01:00
|
|
|
dsize = append ? fvx_size(&dfile) : 0; // always 0 if not appending to file
|
2018-04-22 12:28:37 -05:00
|
|
|
if ((fvx_lseek(&dfile, (osize + dsize)) != FR_OK) || (fvx_sync(&dfile) != FR_OK) || (fvx_tell(&dfile) != (osize + dsize))) { // check space via cluster preallocation
|
2017-08-15 16:39:15 +02:00
|
|
|
if (!silent) ShowPrompt(false, "%s\nError: Not enough space available", deststr);
|
|
|
|
ret = false;
|
2017-06-09 16:21:41 +02:00
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-04-22 12:28:37 -05:00
|
|
|
fvx_lseek(&dfile, dsize);
|
2017-06-09 16:21:41 +02:00
|
|
|
fvx_sync(&dfile);
|
2017-06-06 21:14:19 +02:00
|
|
|
fvx_lseek(&ofile, 0);
|
|
|
|
fvx_sync(&ofile);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2021-09-30 13:12:54 -04:00
|
|
|
if (calcsha) sha_init(sha1 ? SHA1_MODE : SHA256_MODE);
|
2018-04-22 12:28:37 -05:00
|
|
|
for (u64 pos = 0; (pos < osize) && ret; pos += bufsiz) {
|
2016-02-29 02:09:31 +01:00
|
|
|
UINT bytes_read = 0;
|
2020-08-24 21:27:19 -07:00
|
|
|
UINT bytes_written = 0;
|
2018-01-24 23:32:06 +01:00
|
|
|
if ((fvx_read(&ofile, buffer, bufsiz, &bytes_read) != FR_OK) ||
|
|
|
|
(fvx_write(&dfile, buffer, bytes_read, &bytes_written) != FR_OK) ||
|
2017-06-09 01:45:00 +02:00
|
|
|
(bytes_read != bytes_written))
|
2016-02-29 02:09:31 +01:00
|
|
|
ret = false;
|
2018-04-22 12:28:37 -05:00
|
|
|
|
|
|
|
u64 current = pos + bytes_read;
|
|
|
|
u64 total = osize;
|
|
|
|
if (ret && !ShowProgress(current, total, orig)) {
|
2017-06-09 01:45:00 +02:00
|
|
|
if (flags && (*flags & NO_CANCEL)) {
|
2017-09-08 13:30:16 +02:00
|
|
|
ShowPrompt(false, "%s\nCancel is not allowed here", deststr);
|
2017-06-30 03:06:57 +02:00
|
|
|
} else ret = !ShowPrompt(true, "%s\nB button detected. Cancel?", deststr);
|
|
|
|
ShowProgress(0, 0, orig);
|
2018-04-22 12:28:37 -05:00
|
|
|
ShowProgress(current, total, orig);
|
2017-06-09 01:45:00 +02:00
|
|
|
}
|
2018-04-22 12:28:37 -05:00
|
|
|
if (calcsha)
|
2018-01-24 23:32:06 +01:00
|
|
|
sha_update(buffer, bytes_read);
|
2016-02-29 02:09:31 +01:00
|
|
|
}
|
2016-03-02 17:22:44 +01:00
|
|
|
ShowProgress(1, 1, orig);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-06 21:14:19 +02:00
|
|
|
fvx_close(&ofile);
|
2017-06-09 16:21:41 +02:00
|
|
|
fvx_close(&dfile);
|
2018-04-22 12:28:37 -05:00
|
|
|
if (!ret && ((dsize == 0) || (fvx_lseek(&dfile, dsize) != FR_OK) || (f_truncate(&dfile) != FR_OK))) {
|
|
|
|
fvx_unlink(dest);
|
|
|
|
} else if (!to_virtual && calcsha) {
|
2021-09-30 13:12:54 -04:00
|
|
|
u8 hash[0x20];
|
2017-02-28 17:58:48 +01:00
|
|
|
char* ext_sha = dest + strnlen(dest, 256);
|
2021-09-30 13:12:54 -04:00
|
|
|
snprintf(ext_sha, 256 - (ext_sha - dest), ".sha%c", sha1 ? '1' : '\0');
|
|
|
|
sha_get(hash);
|
|
|
|
FileSetData(dest, hash, sha1 ? 20 : 32, 0, true);
|
2017-02-28 17:58:48 +01:00
|
|
|
}
|
2016-02-29 02:09:31 +01:00
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2016-02-29 02:09:31 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
bool PathMoveCopy(const char* dest, const char* orig, u32* flags, bool move) {
|
|
|
|
// check permissions
|
|
|
|
if (!flags || !(*flags & OVERRIDE_PERM)) {
|
|
|
|
if (!CheckWritePermissions(dest)) return false;
|
|
|
|
if (move && !CheckDirWritePermissions(orig)) return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// reset local flags
|
|
|
|
if (flags) *flags = *flags & ~(SKIP_CUR|OVERWRITE_CUR);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// preparations
|
|
|
|
int ddrvtype = DriveType(dest);
|
2016-10-29 16:02:07 +02:00
|
|
|
int odrvtype = DriveType(orig);
|
2017-06-09 16:21:41 +02:00
|
|
|
char ldest[256]; // 256 is the maximum length of a full path
|
|
|
|
char lorig[256];
|
2021-02-05 11:21:55 +01:00
|
|
|
strncpy(ldest, dest, 256);
|
|
|
|
strncpy(lorig, orig, 256);
|
2021-08-02 14:50:46 -05:00
|
|
|
char deststr[UTF_BUFFER_BYTESIZE(36)];
|
2017-06-09 16:21:41 +02:00
|
|
|
TruncateString(deststr, ldest, 36, 8);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// moving only for regular FAT drives (= not alias drives)
|
|
|
|
if (move && !(ddrvtype & odrvtype & DRV_STDFAT)) {
|
|
|
|
ShowPrompt(false, "Error: Only FAT files can be moved");
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// is destination part of origin?
|
|
|
|
u32 olen = strnlen(lorig, 255);
|
2017-09-13 02:07:40 +02:00
|
|
|
if ((strncasecmp(ldest, lorig, olen) == 0) && (ldest[olen] == '/')) {
|
2017-06-09 16:21:41 +02:00
|
|
|
ShowPrompt(false, "%s\nError: Destination is part of origin", deststr);
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
if (!(ddrvtype & DRV_VIRTUAL)) { // FAT destination handling
|
|
|
|
// get destination name
|
|
|
|
char* dname = strrchr(ldest, '/');
|
|
|
|
if (!dname) return false;
|
|
|
|
dname++;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// check & fix destination == origin
|
2017-09-13 02:07:40 +02:00
|
|
|
while (strncasecmp(ldest, lorig, 255) == 0) {
|
2019-06-03 01:37:10 +02:00
|
|
|
if (!ShowKeyboardOrPrompt(dname, 255 - (dname - ldest), "%s\nDestination equals origin\nChoose another name?", deststr))
|
2017-06-09 16:21:41 +02:00
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// check if destination exists
|
2018-04-22 12:28:37 -05:00
|
|
|
if (flags && !(*flags & (OVERWRITE_CUR|OVERWRITE_ALL|APPEND_ALL)) && (fa_stat(ldest, NULL) == FR_OK)) {
|
2017-06-09 16:21:41 +02:00
|
|
|
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"};
|
|
|
|
u32 user_select = ShowSelectPrompt((*flags & ASK_ALL) ? 5 : 3, optionstr,
|
|
|
|
"Destination already exists:\n%s", deststr);
|
|
|
|
if (user_select == 1) {
|
|
|
|
do {
|
2019-06-03 01:37:10 +02:00
|
|
|
if (!ShowKeyboardOrPrompt(dname, 255 - (dname - ldest), "Choose new destination name"))
|
2017-06-09 16:21:41 +02:00
|
|
|
return false;
|
|
|
|
} while (fa_stat(ldest, 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;
|
|
|
|
}
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// ensure the destination path exists
|
|
|
|
if (flags && (*flags & BUILD_PATH)) fvx_rmkpath(ldest);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
// setup buffer
|
|
|
|
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
2018-02-05 23:16:58 +01:00
|
|
|
if (!buffer) {
|
|
|
|
ShowPrompt(false, "Out of memory.");
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// actual move / copy operation
|
2017-09-13 02:07:40 +02:00
|
|
|
bool same_drv = (strncasecmp(lorig, ldest, 2) == 0);
|
2018-01-24 23:32:06 +01:00
|
|
|
bool res = PathMoveCopyRec(ldest, lorig, flags, move && same_drv, buffer, STD_BUFFER_SIZE);
|
2017-06-09 01:45:00 +02:00
|
|
|
if (move && res && (!flags || !(*flags&SKIP_CUR))) PathDelete(lorig);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
free(buffer);
|
2017-06-09 16:21:41 +02:00
|
|
|
return res;
|
|
|
|
} else { // virtual destination handling
|
|
|
|
// can't write an SHA file to a virtual destination
|
2018-10-29 01:33:20 +01:00
|
|
|
if (flags) *flags &= ~CALC_SHA;
|
2017-09-13 02:07:40 +02:00
|
|
|
bool force_unmount = false;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-09-13 02:07:40 +02:00
|
|
|
// handle NAND image unmounts
|
2020-05-23 21:04:18 -04:00
|
|
|
if ((ddrvtype & (DRV_SYSNAND|DRV_EMUNAND|DRV_IMAGE)) && !(GetVirtualSource(dest) & (VRT_DISADIFF | VRT_BDRI))) {
|
2017-09-13 02:07:40 +02:00
|
|
|
FILINFO fno;
|
2018-01-24 23:32:06 +01:00
|
|
|
// virtual NAND files over 4 MB require unmount, totally arbitrary limit (hacky!)
|
2017-09-13 02:07:40 +02:00
|
|
|
if ((fvx_stat(ldest, &fno) == FR_OK) && (fno.fsize > 4 * 1024 * 1024))
|
|
|
|
force_unmount = true;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// prevent illegal operations
|
2017-09-13 02:07:40 +02:00
|
|
|
if (force_unmount && (odrvtype & ddrvtype & (DRV_SYSNAND|DRV_EMUNAND|DRV_IMAGE))) {
|
2017-01-09 20:07:36 +01:00
|
|
|
ShowPrompt(false, "Copy operation is not allowed");
|
2020-08-24 21:27:19 -07:00
|
|
|
return false;
|
2016-11-30 15:56:22 +01:00
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// check destination == origin
|
2017-09-13 02:07:40 +02:00
|
|
|
if (strncasecmp(ldest, lorig, 255) == 0) {
|
2017-06-09 16:21:41 +02:00
|
|
|
ShowPrompt(false, "%s\nDestination equals origin", deststr);
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
// setup buffer
|
|
|
|
u8* buffer = (u8*) malloc(STD_BUFFER_SIZE);
|
2018-02-05 23:16:58 +01:00
|
|
|
if (!buffer) {
|
|
|
|
ShowPrompt(false, "Out of memory.");
|
|
|
|
return false;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// actual virtual copy operation
|
2017-09-13 02:07:40 +02:00
|
|
|
if (force_unmount) DismountDriveType(DriveType(ldest)&(DRV_SYSNAND|DRV_EMUNAND|DRV_IMAGE));
|
2018-01-24 23:32:06 +01:00
|
|
|
bool res = PathMoveCopyRec(ldest, lorig, flags, false, buffer, STD_BUFFER_SIZE);
|
2017-09-13 02:07:40 +02:00
|
|
|
if (force_unmount) InitExtFS();
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-24 23:32:06 +01:00
|
|
|
free(buffer);
|
2017-06-09 16:21:41 +02:00
|
|
|
return res;
|
|
|
|
}
|
2016-04-29 02:21:36 +02:00
|
|
|
}
|
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
bool PathCopy(const char* destdir, const char* orig, u32* flags) {
|
|
|
|
// build full destination path (on top of destination directory)
|
|
|
|
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));
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
// virtual destination special handling
|
2020-05-23 21:04:18 -04:00
|
|
|
if (GetVirtualSource(destdir) & ~VRT_BDRI) {
|
2017-06-09 16:21:41 +02:00
|
|
|
u64 osize = FileGetSize(orig);
|
|
|
|
VirtualFile dvfile;
|
|
|
|
if (!osize) return false;
|
2020-06-24 23:49:22 -04:00
|
|
|
if (!GetVirtualFile(&dvfile, dest, FA_WRITE)) {
|
2017-06-09 16:21:41 +02:00
|
|
|
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;
|
|
|
|
snprintf(dest, 255, "%s/%s", destdir, dvfile.name);
|
|
|
|
} else if (osize < dvfile.size) { // if origin is smaller than destination...
|
2021-08-02 14:50:46 -05:00
|
|
|
char deststr[UTF_BUFFER_BYTESIZE(36)];
|
|
|
|
char origstr[UTF_BUFFER_BYTESIZE(36)];
|
2017-06-09 16:21:41 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2016-03-21 23:19:23 +01:00
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
return PathMoveCopy(dest, orig, flags, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool PathMove(const char* destdir, const char* orig, u32* flags) {
|
|
|
|
// build full destination path (on top of destination directory)
|
|
|
|
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));
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-09 16:21:41 +02:00
|
|
|
return PathMoveCopy(dest, orig, flags, true);
|
2016-02-29 02:09:31 +01:00
|
|
|
}
|
|
|
|
|
2016-02-29 15:47:45 +01:00
|
|
|
bool PathDelete(const char* path) {
|
2017-02-15 16:34:25 +01:00
|
|
|
if (!CheckDirWritePermissions(path)) return false;
|
2017-06-06 21:14:19 +02:00
|
|
|
return (fvx_runlink(path) == FR_OK);
|
2016-02-29 02:09:31 +01:00
|
|
|
}
|
|
|
|
|
2016-03-14 23:38:43 +01:00
|
|
|
bool PathRename(const char* path, const char* newname) {
|
|
|
|
char npath[256]; // 256 is the maximum length of a full path
|
|
|
|
char* oldname = strrchr(path, '/');
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-02-15 16:34:25 +01:00
|
|
|
if (!CheckDirWritePermissions(path)) return false;
|
2016-03-14 23:38:43 +01:00
|
|
|
if (!oldname) return false;
|
|
|
|
oldname++;
|
|
|
|
strncpy(npath, path, oldname - path);
|
2016-03-28 20:10:43 +02:00
|
|
|
strncpy(npath + (oldname - path), newname, 255 - (oldname - path));
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-07-04 22:52:10 +02:00
|
|
|
if (fvx_rename(path, npath) != FR_OK) return false;
|
2019-12-11 00:08:35 +01:00
|
|
|
if ((strncasecmp(path, npath, 256) != 0) &&
|
|
|
|
((fvx_stat(path, NULL) == FR_OK) || (fvx_stat(npath, NULL) != FR_OK)))
|
|
|
|
return false; // safety check
|
2018-07-04 22:52:10 +02:00
|
|
|
return true;
|
2016-03-14 23:38:43 +01:00
|
|
|
}
|
|
|
|
|
2017-11-06 22:47:55 +08:00
|
|
|
bool PathAttr(const char* path, u8 attr, u8 mask) {
|
|
|
|
if (!CheckDirWritePermissions(path)) return false;
|
|
|
|
return (f_chmod(path, attr, mask) == FR_OK);
|
|
|
|
}
|
|
|
|
|
2018-09-08 17:31:54 +09:00
|
|
|
bool FileSelectorWorker(char* result, const char* text, const char* path, const char* pattern, u32 flags, void* buffer, bool new_style) {
|
2018-01-24 23:32:06 +01:00
|
|
|
DirStruct* contents = (DirStruct*) buffer;
|
2017-06-21 01:04:36 +02:00
|
|
|
char path_local[256];
|
|
|
|
strncpy(path_local, path, 256);
|
2018-05-24 00:14:37 +02:00
|
|
|
path_local[255] = '\0';
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-01-09 01:39:37 +01:00
|
|
|
bool no_dirs = flags & NO_DIRS;
|
|
|
|
bool no_files = flags & NO_FILES;
|
|
|
|
bool hide_ext = flags & HIDE_EXT;
|
|
|
|
bool select_dirs = flags & SELECT_DIRS;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-21 01:04:36 +02:00
|
|
|
// main loop
|
|
|
|
while (true) {
|
|
|
|
u32 n_found = 0;
|
|
|
|
u32 pos = 0;
|
|
|
|
GetDirContents(contents, path_local);
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-21 01:04:36 +02:00
|
|
|
while (pos < contents->n_entries) {
|
2021-08-02 14:50:46 -05:00
|
|
|
char opt_names[_MAX_FS_OPT+1][UTF_BUFFER_BYTESIZE(32)];
|
2018-09-08 17:31:54 +09:00
|
|
|
DirEntry* res_entry[MAX_DIR_ENTRIES+1] = { NULL };
|
2017-06-21 01:04:36 +02:00
|
|
|
u32 n_opt = 0;
|
|
|
|
for (; pos < contents->n_entries; pos++) {
|
|
|
|
DirEntry* entry = &(contents->entry[pos]);
|
2017-10-24 23:58:41 +02:00
|
|
|
if (((entry->type == T_DIR) && no_dirs) ||
|
2018-01-09 01:39:37 +01:00
|
|
|
((entry->type == T_FILE) && (no_files || (fvx_match_name(entry->name, pattern) != FR_OK))) ||
|
2017-10-24 23:58:41 +02:00
|
|
|
(entry->type == T_DOTDOT) || (strncmp(entry->name, "._", 2) == 0))
|
2017-06-21 01:04:36 +02:00
|
|
|
continue;
|
2018-09-08 17:31:54 +09:00
|
|
|
if (!new_style && n_opt == _MAX_FS_OPT) {
|
2017-06-21 01:04:36 +02:00
|
|
|
snprintf(opt_names[n_opt++], 32, "[more...]");
|
|
|
|
break;
|
|
|
|
}
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2018-09-08 17:31:54 +09:00
|
|
|
if (!new_style) {
|
|
|
|
char temp_str[256];
|
|
|
|
snprintf(temp_str, 256, "%s", entry->name);
|
|
|
|
if (hide_ext && (entry->type == T_FILE)) {
|
|
|
|
char* dot = strrchr(temp_str, '.');
|
|
|
|
if (dot) *dot = '\0';
|
|
|
|
}
|
|
|
|
TruncateString(opt_names[n_opt], temp_str, 32, 8);
|
2017-06-21 01:04:36 +02:00
|
|
|
}
|
|
|
|
res_entry[n_opt++] = entry;
|
|
|
|
n_found++;
|
|
|
|
}
|
2018-09-08 17:31:54 +09:00
|
|
|
if ((pos >= contents->n_entries) && (n_opt < n_found) && !new_style)
|
2017-06-21 01:04:36 +02:00
|
|
|
snprintf(opt_names[n_opt++], 32, "[more...]");
|
|
|
|
if (!n_opt) break;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2017-06-21 01:04:36 +02:00
|
|
|
const char* optionstr[_MAX_FS_OPT+1] = { NULL };
|
|
|
|
for (u32 i = 0; i <= _MAX_FS_OPT; i++) optionstr[i] = opt_names[i];
|
2018-09-08 17:31:54 +09:00
|
|
|
u32 user_select = new_style ? ShowFileScrollPrompt(n_opt, (const DirEntry**)res_entry, hide_ext, "%s", text)
|
|
|
|
: ShowSelectPrompt(n_opt, optionstr, "%s", text);
|
2017-06-21 01:04:36 +02:00
|
|
|
if (!user_select) return false;
|
|
|
|
DirEntry* res_local = res_entry[user_select-1];
|
|
|
|
if (res_local && (res_local->type == T_DIR)) { // selected dir
|
2018-01-09 01:39:37 +01:00
|
|
|
if (select_dirs) {
|
|
|
|
strncpy(result, res_local->path, 256);
|
|
|
|
return true;
|
2018-09-08 17:31:54 +09:00
|
|
|
} else if (FileSelectorWorker(result, text, res_local->path, pattern, flags, buffer, new_style)) {
|
2017-06-21 01:04:36 +02:00
|
|
|
return true;
|
2018-01-09 01:39:37 +01:00
|
|
|
}
|
2017-06-21 01:04:36 +02:00
|
|
|
break;
|
|
|
|
} else if (res_local && (res_local->type == T_FILE)) { // selected file
|
|
|
|
strncpy(result, res_local->path, 256);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!n_found) { // not a single matching entry found
|
2021-08-02 14:50:46 -05:00
|
|
|
char pathstr[UTF_BUFFER_BYTESIZE(32)];
|
2017-06-21 01:04:36 +02:00
|
|
|
TruncateString(pathstr, path_local, 32, 8);
|
|
|
|
ShowPrompt(false, "%s\nNo usable entries found.", pathstr);
|
2020-08-24 21:27:19 -07:00
|
|
|
return false;
|
2017-06-21 01:04:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-08 17:31:54 +09:00
|
|
|
bool FileSelector(char* result, const char* text, const char* path, const char* pattern, u32 flags, bool new_style) {
|
2018-01-24 23:32:06 +01:00
|
|
|
void* buffer = (void*) malloc(sizeof(DirStruct));
|
|
|
|
if (!buffer) return false;
|
2020-08-24 21:27:19 -07:00
|
|
|
|
2021-02-05 11:21:55 +01:00
|
|
|
// for this to work, result needs to be at least 256 bytes in size
|
2018-09-08 17:31:54 +09:00
|
|
|
bool ret = FileSelectorWorker(result, text, path, pattern, flags, buffer, new_style);
|
2018-01-24 23:32:06 +01:00
|
|
|
free(buffer);
|
|
|
|
return ret;
|
|
|
|
}
|