mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 05:32:47 +00:00
427 lines
12 KiB
C
427 lines
12 KiB
C
#include "virtual.h"
|
|
#include "ffconf.h"
|
|
#include "vff.h"
|
|
|
|
#if FF_USE_LFN != 0
|
|
#define _MAX_FN_LEN (FF_MAX_LFN)
|
|
#else
|
|
#define _MAX_FN_LEN (8+3)
|
|
#endif
|
|
|
|
#define _VFIL_ENABLED (!_FS_TINY)
|
|
#define _VDIR_ENABLED ((sizeof(DIR) - sizeof(FFOBJID) >= sizeof(VirtualDir)) && (FF_USE_LFN != 0))
|
|
|
|
#define VFIL(fp) ((VirtualFile*) (void*) fp->buf)
|
|
#define VDIR(dp) ((VirtualDir*) (void*) &(dp->dptr))
|
|
|
|
FRESULT fvx_open (FIL* fp, const TCHAR* path, BYTE mode) {
|
|
#if _VFIL_ENABLED
|
|
VirtualFile* vfile = VFIL(fp);
|
|
memset(fp, 0, sizeof(FIL));
|
|
if (GetVirtualFile(vfile, path, mode)) {
|
|
fp->obj.fs = NULL;
|
|
fp->obj.objsize = vfile->size;
|
|
fp->fptr = 0;
|
|
return FR_OK;
|
|
}
|
|
#endif
|
|
return fx_open ( fp, path, mode );
|
|
}
|
|
|
|
FRESULT fvx_read (FIL* fp, void* buff, UINT btr, UINT* br) {
|
|
#if _VFIL_ENABLED
|
|
if (fp->obj.fs == NULL) {
|
|
VirtualFile* vfile = VFIL(fp);
|
|
int res = ReadVirtualFile(vfile, buff, fp->fptr, btr, (u32*) br);
|
|
fp->fptr += *br;
|
|
return res;
|
|
}
|
|
#endif
|
|
return fx_read ( fp, buff, btr, br );
|
|
}
|
|
|
|
FRESULT fvx_write (FIL* fp, const void* buff, UINT btw, UINT* bw) {
|
|
#if _VFIL_ENABLED
|
|
if (fp->obj.fs == NULL) {
|
|
VirtualFile* vfile = VFIL(fp);
|
|
int res = WriteVirtualFile(vfile, buff, fp->fptr, btw, (u32*) bw);
|
|
fp->fptr += *bw;
|
|
return res;
|
|
}
|
|
#endif
|
|
return fx_write ( fp, buff, btw, bw );
|
|
}
|
|
|
|
FRESULT fvx_close (FIL* fp) {
|
|
#if _VFIL_ENABLED
|
|
if (fp->obj.fs == NULL) return FR_OK;
|
|
#endif
|
|
return fx_close( fp );
|
|
}
|
|
|
|
FRESULT fvx_lseek (FIL* fp, FSIZE_t ofs) {
|
|
#if _VFIL_ENABLED
|
|
if (fp->obj.fs == NULL) {
|
|
if (fvx_size(fp) >= ofs) {
|
|
fp->fptr = ofs;
|
|
return FR_OK;
|
|
} else return FR_DENIED;
|
|
}
|
|
#endif
|
|
return f_lseek( fp, ofs );
|
|
}
|
|
|
|
FRESULT fvx_sync (FIL* fp) {
|
|
#if _VFIL_ENABLED
|
|
if (fp->obj.fs == NULL) return FR_OK;
|
|
#endif
|
|
return f_sync( fp );
|
|
}
|
|
|
|
FRESULT fvx_stat (const TCHAR* path, FILINFO* fno) {
|
|
if (GetVirtualSource(path)) {
|
|
VirtualFile vfile;
|
|
if (!GetVirtualFile(&vfile, path, FA_READ)) return FR_NO_PATH;
|
|
if (fno) {
|
|
fno->fsize = vfile.size;
|
|
fno->fdate = (1<<5)|(1<<0); // 1 for month / day
|
|
fno->ftime = 0;
|
|
fno->fattrib = (vfile.flags & VFLAG_DIR) ? (AM_DIR|AM_VRT) : AM_VRT;
|
|
// could be better...
|
|
if (FF_USE_LFN != 0) GetVirtualFilename(fno->fname, &vfile, FF_MAX_LFN + 1);
|
|
}
|
|
return FR_OK;
|
|
} else return fa_stat( path, fno );
|
|
}
|
|
|
|
FRESULT fvx_rename (const TCHAR* path_old, const TCHAR* path_new) {
|
|
if ((GetVirtualSource(path_old)) || CheckAliasDrive(path_old)) return FR_DENIED;
|
|
return f_rename( path_old, path_new );
|
|
}
|
|
|
|
FRESULT fvx_unlink (const TCHAR* path) {
|
|
if (GetVirtualSource(path)) {
|
|
VirtualFile vfile;
|
|
if (!GetVirtualFile(&vfile, path, FA_READ)) return FR_NO_PATH;
|
|
if (DeleteVirtualFile(&vfile) != 0) return FR_DENIED;
|
|
return FR_OK;
|
|
} else return fa_unlink( path );
|
|
}
|
|
|
|
FRESULT fvx_mkdir (const TCHAR* path) {
|
|
if (GetVirtualSource(path)) return FR_DENIED;
|
|
return fa_mkdir( path );
|
|
}
|
|
|
|
FRESULT fvx_opendir (DIR* dp, const TCHAR* path) {
|
|
if (_VDIR_ENABLED) {
|
|
VirtualDir* vdir = VDIR(dp);
|
|
memset(dp, 0, sizeof(DIR));
|
|
if (GetVirtualDir(vdir, path))
|
|
return FR_OK;
|
|
}
|
|
return fa_opendir( dp, path );
|
|
}
|
|
|
|
FRESULT fvx_closedir (DIR* dp) {
|
|
if (_VDIR_ENABLED) {
|
|
if (dp->obj.fs == NULL) return FR_OK;
|
|
}
|
|
return f_closedir( dp );
|
|
}
|
|
|
|
FRESULT fvx_readdir (DIR* dp, FILINFO* fno) {
|
|
if (_VDIR_ENABLED) {
|
|
if (dp->obj.fs == NULL) {
|
|
VirtualDir* vdir = VDIR(dp);
|
|
VirtualFile vfile;
|
|
if (ReadVirtualDir(&vfile, vdir)) {
|
|
fno->fsize = vfile.size;
|
|
fno->fdate = fno->ftime = 0;
|
|
fno->fattrib = (vfile.flags & VFLAG_DIR) ? (AM_DIR|AM_VRT) : AM_VRT;
|
|
GetVirtualFilename(fno->fname, &vfile, FF_MAX_LFN + 1);
|
|
} else *(fno->fname) = 0;
|
|
return FR_OK;
|
|
}
|
|
}
|
|
return f_readdir( dp, fno );
|
|
}
|
|
|
|
FRESULT fvx_qread (const TCHAR* path, void* buff, FSIZE_t ofs, UINT btr, UINT* br) {
|
|
FIL fp;
|
|
FRESULT res;
|
|
UINT brt = 0;
|
|
|
|
res = fvx_open(&fp, path, FA_READ | FA_OPEN_EXISTING);
|
|
if (res != FR_OK) return res;
|
|
|
|
res = fvx_lseek(&fp, ofs);
|
|
if (res != FR_OK) {
|
|
fvx_close(&fp);
|
|
return res;
|
|
}
|
|
|
|
res = fvx_read(&fp, buff, btr, &brt);
|
|
fvx_close(&fp);
|
|
|
|
if (br) *br = brt;
|
|
else if ((res == FR_OK) && (brt != btr)) res = FR_DENIED;
|
|
|
|
return res;
|
|
}
|
|
|
|
FRESULT fvx_qwrite (const TCHAR* path, const void* buff, FSIZE_t ofs, UINT btw, UINT* bw) {
|
|
FIL fp;
|
|
FRESULT res;
|
|
UINT bwt = 0;
|
|
|
|
res = fvx_open(&fp, path, FA_WRITE | FA_OPEN_ALWAYS);
|
|
if (res != FR_OK) return res;
|
|
|
|
res = fvx_lseek(&fp, ofs);
|
|
if (res != FR_OK) {
|
|
fvx_close(&fp);
|
|
return res;
|
|
}
|
|
|
|
res = fvx_write(&fp, buff, btw, &bwt);
|
|
fvx_close(&fp);
|
|
|
|
if (bw) *bw = bwt;
|
|
else if ((res == FR_OK) && (bwt != btw)) res = FR_DENIED;
|
|
|
|
return res;
|
|
}
|
|
|
|
FRESULT fvx_qcreate (const TCHAR* path, UINT btc) {
|
|
FIL fp;
|
|
FRESULT res;
|
|
|
|
res = fvx_open(&fp, path, FA_WRITE | FA_CREATE_ALWAYS);
|
|
if (res != FR_OK) return res;
|
|
|
|
res = fvx_lseek(&fp, btc);
|
|
fvx_close(&fp);
|
|
|
|
return res;
|
|
}
|
|
|
|
/* // untested / unused, might come in handy at a later point
|
|
FRESULT fvx_qfill (const TCHAR* path, const void* buff, UINT btb) {
|
|
FIL fp;
|
|
FRESULT res;
|
|
UINT bwtt = 0;
|
|
UINT fsiz = 0;
|
|
|
|
res = fvx_open(&fp, path, FA_WRITE | FA_OPEN_EXISTING);
|
|
if (res != FR_OK) return res;
|
|
|
|
fsiz = fvx_size(&fp);
|
|
while (bwtt < fsiz) {
|
|
UINT btw = ((fsiz - bwtt) >= btb) ? btb : (fsiz - bwtt);
|
|
UINT bwt;
|
|
|
|
res = fvx_write(&fp, buff, btw, &bwt);
|
|
if ((res == FR_OK) && (bwt != btw)) res = FR_DENIED;
|
|
if (res != FR_OK) break;
|
|
bwtt += bwt;
|
|
}
|
|
fvx_close(&fp);
|
|
|
|
return res;
|
|
}*/
|
|
|
|
FSIZE_t fvx_qsize (const TCHAR* path) {
|
|
FILINFO fno;
|
|
return (fvx_stat(path, &fno) == FR_OK) ? fno.fsize : 0;
|
|
}
|
|
|
|
#if !_LFN_UNICODE // this will not work for unicode
|
|
FRESULT worker_fvx_rmkdir (TCHAR* tpath) {
|
|
DIR tmp_dir;
|
|
if (fa_opendir(&tmp_dir, tpath) != FR_OK) {
|
|
TCHAR* slash = strrchr(tpath, '/');
|
|
if (!slash) return FR_DENIED;
|
|
*slash = '\0';
|
|
FRESULT res;
|
|
if ((res = worker_fvx_rmkdir(tpath)) != FR_OK) return res;
|
|
*slash = '/';
|
|
return fa_mkdir(tpath);
|
|
} else {
|
|
f_closedir(&tmp_dir);
|
|
return FR_OK;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
FRESULT fvx_rmkdir (const TCHAR* path) {
|
|
#if !_LFN_UNICODE // this will not work for unicode
|
|
TCHAR tpath[_MAX_FN_LEN+1];
|
|
if (strlen(path) > _MAX_FN_LEN) return FR_INVALID_NAME;
|
|
strcpy(tpath, path);
|
|
return worker_fvx_rmkdir( tpath );
|
|
#else
|
|
return FR_DENIED;
|
|
#endif
|
|
}
|
|
|
|
FRESULT fvx_rmkpath (const TCHAR* path) {
|
|
#if !_LFN_UNICODE // this will not work for unicode
|
|
TCHAR tpath[_MAX_FN_LEN+1];
|
|
if (strlen(path) > _MAX_FN_LEN) return FR_INVALID_NAME;
|
|
strcpy(tpath, path);
|
|
TCHAR* slash = strrchr(tpath, '/');
|
|
if (!slash) return FR_DENIED;
|
|
*slash = '\0';
|
|
return worker_fvx_rmkdir( tpath );
|
|
#else
|
|
return FR_DENIED;
|
|
#endif
|
|
}
|
|
|
|
#if !_LFN_UNICODE // this will not work for unicode
|
|
FRESULT worker_fvx_runlink (TCHAR* tpath) {
|
|
FILINFO fno;
|
|
FRESULT res;
|
|
|
|
// this code handles directory content deletion
|
|
if ((res = fvx_stat(tpath, &fno)) != FR_OK) return res; // tpath does not exist
|
|
if (fno.fattrib & AM_DIR) { // process folder contents
|
|
DIR pdir;
|
|
TCHAR* fname = tpath + strnlen(tpath, 255);
|
|
|
|
|
|
if ((res = fa_opendir(&pdir, tpath)) != FR_OK) return res;
|
|
*(fname++) = '/';
|
|
|
|
while (fvx_readdir(&pdir, &fno) == FR_OK) {
|
|
if ((strncmp(fno.fname, ".", 2) == 0) || (strncmp(fno.fname, "..", 3) == 0))
|
|
continue; // filter out virtual entries
|
|
strcpy(fname, fno.fname);
|
|
if (fno.fname[0] == 0) {
|
|
break;
|
|
} else { // return value won't matter
|
|
worker_fvx_runlink(tpath);
|
|
}
|
|
}
|
|
fvx_closedir(&pdir);
|
|
*(--fname) = '\0';
|
|
}
|
|
|
|
return fvx_unlink( tpath );
|
|
}
|
|
#endif
|
|
|
|
FRESULT fvx_runlink (const TCHAR* path) {
|
|
#if !_LFN_UNICODE // this will not work for unicode
|
|
TCHAR tpath[_MAX_FN_LEN+1];
|
|
if (strlen(path) > _MAX_FN_LEN) return FR_INVALID_NAME;
|
|
strcpy(tpath, path);
|
|
return worker_fvx_runlink( tpath );
|
|
#else
|
|
return FR_DENIED;
|
|
#endif
|
|
}
|
|
|
|
// inspired by http://www.geeksforgeeks.org/wildcard-character-matching/
|
|
FRESULT fvx_match_name(const TCHAR* path, const TCHAR* pattern) {
|
|
// handling non asterisk chars
|
|
for (; *pattern != '*'; pattern++, path++) {
|
|
if ((*pattern == '\0') && (*path == '\0')) {
|
|
return FR_OK; // end reached simultaneously, match found
|
|
} else if ((*pattern == '\0') || (*path == '\0')) {
|
|
return FR_NO_FILE; // end reached on only one, failure
|
|
} else if ((*pattern != '?') && (tolower(*pattern) != tolower(*path))) {
|
|
return FR_NO_FILE; // chars don't match, failure
|
|
}
|
|
}
|
|
// handling the asterisk (matches one or more chars in path)
|
|
if ((*(pattern+1) == '?') || (*(pattern+1) == '*')) {
|
|
return FR_NO_FILE; // stupid user shenanigans, failure
|
|
} else if (*path == '\0') {
|
|
return FR_NO_FILE; // asterisk, but end reached on path, failure
|
|
} else if (*(pattern+1) == '\0') {
|
|
return FR_OK; // nothing after the asterisk, match found
|
|
} else { // we couldn't really go without recursion here
|
|
for (path++; *path != '\0'; path++) {
|
|
if (fvx_match_name(path, pattern + 1) == FR_OK) return FR_OK;
|
|
}
|
|
}
|
|
|
|
return FR_NO_FILE;
|
|
}
|
|
|
|
FRESULT fvx_preaddir (DIR* dp, FILINFO* fno, const TCHAR* pattern) {
|
|
FRESULT res;
|
|
while ((res = fvx_readdir(dp, fno)) == FR_OK)
|
|
if (!pattern || !*(fno->fname) || (fvx_match_name(fno->fname, pattern) == FR_OK)) break;
|
|
return res;
|
|
}
|
|
|
|
FRESULT fvx_findpath (TCHAR* path, const TCHAR* pattern, BYTE mode) {
|
|
if (strlen(pattern) > _MAX_FN_LEN) return FR_INVALID_NAME;
|
|
strcpy(path, pattern);
|
|
TCHAR* fname = strrchr(path, '/');
|
|
if (!fname) return FR_DENIED;
|
|
*fname = '\0';
|
|
|
|
TCHAR* npattern = strrchr(pattern, '/');
|
|
if (!npattern) return FR_DENIED;
|
|
npattern++;
|
|
|
|
DIR pdir;
|
|
FILINFO fno;
|
|
FRESULT res;
|
|
if ((res = fvx_opendir(&pdir, path)) != FR_OK) return res;
|
|
|
|
*(fname++) = '/';
|
|
*fname = '\0';
|
|
|
|
while ((fvx_preaddir(&pdir, &fno, npattern) == FR_OK) && *(fno.fname)) {
|
|
int cmp = strncmp(fno.fname, fname, _MAX_FN_LEN);
|
|
if (((mode & FN_HIGHEST) && (cmp > 0)) || ((mode & FN_LOWEST) && (cmp < 0)) || !(*fname))
|
|
strcpy(fname, fno.fname);
|
|
if (!(mode & (FN_HIGHEST|FN_LOWEST))) break;
|
|
}
|
|
fvx_closedir( &pdir );
|
|
|
|
return (*fname) ? FR_OK : FR_NO_PATH;
|
|
}
|
|
|
|
FRESULT fvx_findnopath (TCHAR* path, const TCHAR* pattern) {
|
|
if (strlen(pattern) > _MAX_FN_LEN) return FR_INVALID_NAME;
|
|
strcpy(path, pattern);
|
|
TCHAR* fname = strrchr(path, '/');
|
|
if (!fname) return FR_DENIED;
|
|
fname++;
|
|
|
|
TCHAR* rep[16];
|
|
u32 n_rep = 0;
|
|
for (u32 i = 0; fname[i]; i++) {
|
|
if (fname[i] == '?') {
|
|
rep[n_rep] = &(fname[i]);
|
|
*rep[n_rep++] = '0';
|
|
}
|
|
if (n_rep >= 16) return FR_DENIED;
|
|
}
|
|
if (!n_rep) return (fvx_stat(path, NULL) == FR_OK) ? FR_NO_PATH : FR_OK;
|
|
|
|
while (fvx_stat(path, NULL) == FR_OK) {
|
|
for (int i = n_rep - 1; (i >= 0); i--) {
|
|
if (*(rep[i]) == '9') {
|
|
if (!i) return FR_NO_PATH;
|
|
*(rep[i]) = '0';
|
|
} else {
|
|
(*(rep[i]))++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FR_OK;
|
|
}
|
|
|
|
bool fvx_opened(const FIL* fp) {
|
|
return (fp->obj.fs != NULL);
|
|
}
|