mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 21:52:48 +00:00
Improved virtual directory handling
This commit is contained in:
parent
525b5b8810
commit
924dd8216e
58
source/dir.c
Normal file
58
source/dir.c
Normal file
@ -0,0 +1,58 @@
|
||||
#include "dir.h"
|
||||
|
||||
void DirEntryCpy(DirEntry* dest, const DirEntry* orig) {
|
||||
memcpy(dest, orig, sizeof(DirEntry));
|
||||
dest->name = dest->path + (orig->name - orig->path);
|
||||
}
|
||||
|
||||
void SortDirStruct(DirStruct* contents) {
|
||||
for (u32 s = 0; s < contents->n_entries; s++) {
|
||||
DirEntry* cmp0 = &(contents->entry[s]);
|
||||
DirEntry* min0 = cmp0;
|
||||
if (cmp0->type == T_DOTDOT) continue;
|
||||
for (u32 c = s + 1; c < contents->n_entries; c++) {
|
||||
DirEntry* cmp1 = &(contents->entry[c]);
|
||||
if (min0->type != cmp1->type) {
|
||||
if (min0->type > cmp1->type)
|
||||
min0 = cmp1;
|
||||
continue;
|
||||
}
|
||||
if (strncasecmp(min0->name, cmp1->name, 256) > 0)
|
||||
min0 = cmp1;
|
||||
}
|
||||
if (min0 != cmp0) {
|
||||
DirEntry swap; // swap entries and fix names
|
||||
DirEntryCpy(&swap, cmp0);
|
||||
DirEntryCpy(cmp0, min0);
|
||||
DirEntryCpy(min0, &swap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inspired by http://www.geeksforgeeks.org/wildcard-character-matching/
|
||||
bool MatchName(const char *pattern, const char *path) {
|
||||
// handling non asterisk chars
|
||||
for (; *pattern != '*'; pattern++, path++) {
|
||||
if ((*pattern == '\0') && (*path == '\0')) {
|
||||
return true; // end reached simultaneously, match found
|
||||
} else if ((*pattern == '\0') || (*path == '\0')) {
|
||||
return false; // end reached on only one, failure
|
||||
} else if ((*pattern != '?') && (tolower(*pattern) != tolower(*path))) {
|
||||
return false; // chars don't match, failure
|
||||
}
|
||||
}
|
||||
// handling the asterisk (matches one or more chars in path)
|
||||
if ((*(pattern+1) == '?') || (*(pattern+1) == '*')) {
|
||||
return false; // stupid user shenanigans, failure
|
||||
} else if (*path == '\0') {
|
||||
return false; // asterisk, but end reached on path, failure
|
||||
} else if (*(pattern+1) == '\0') {
|
||||
return true; // nothing after the asterisk, match found
|
||||
} else { // we couldn't really go without recursion here
|
||||
for (path++; *path != '\0'; path++) {
|
||||
if (MatchName(pattern + 1, path)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
29
source/dir.h
Normal file
29
source/dir.h
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#define MAX_DIR_ENTRIES 1024
|
||||
|
||||
typedef enum {
|
||||
T_ROOT,
|
||||
T_DIR,
|
||||
T_FILE,
|
||||
T_DOTDOT
|
||||
} EntryType;
|
||||
|
||||
typedef struct {
|
||||
char* name; // should point to the correct portion of the path
|
||||
char path[256];
|
||||
u64 size;
|
||||
EntryType type;
|
||||
u8 marked;
|
||||
} DirEntry;
|
||||
|
||||
typedef struct {
|
||||
u32 n_entries;
|
||||
DirEntry entry[MAX_DIR_ENTRIES];
|
||||
} DirStruct;
|
||||
|
||||
void DirEntryCpy(DirEntry* dest, const DirEntry* orig);
|
||||
void SortDirStruct(DirStruct* contents);
|
||||
bool MatchName(const char *pattern, const char *path);
|
81
source/fs.c
81
source/fs.c
@ -1029,63 +1029,6 @@ void CreateScreenshot() {
|
||||
FileSetData(filename, MAIN_BUFFER, 54 + (400 * 240 * 3 * 2), 0, true);
|
||||
}
|
||||
|
||||
void DirEntryCpy(DirEntry* dest, const DirEntry* orig) {
|
||||
memcpy(dest, orig, sizeof(DirEntry));
|
||||
dest->name = dest->path + (orig->name - orig->path);
|
||||
}
|
||||
|
||||
void SortDirStruct(DirStruct* contents) {
|
||||
for (u32 s = 0; s < contents->n_entries; s++) {
|
||||
DirEntry* cmp0 = &(contents->entry[s]);
|
||||
DirEntry* min0 = cmp0;
|
||||
if (cmp0->type == T_DOTDOT) continue;
|
||||
for (u32 c = s + 1; c < contents->n_entries; c++) {
|
||||
DirEntry* cmp1 = &(contents->entry[c]);
|
||||
if (min0->type != cmp1->type) {
|
||||
if (min0->type > cmp1->type)
|
||||
min0 = cmp1;
|
||||
continue;
|
||||
}
|
||||
if (strncasecmp(min0->name, cmp1->name, 256) > 0)
|
||||
min0 = cmp1;
|
||||
}
|
||||
if (min0 != cmp0) {
|
||||
DirEntry swap; // swap entries and fix names
|
||||
DirEntryCpy(&swap, cmp0);
|
||||
DirEntryCpy(cmp0, min0);
|
||||
DirEntryCpy(min0, &swap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// inspired by http://www.geeksforgeeks.org/wildcard-character-matching/
|
||||
bool MatchName(const char *pattern, const char *path) {
|
||||
// handling non asterisk chars
|
||||
for (; *pattern != '*'; pattern++, path++) {
|
||||
if ((*pattern == '\0') && (*path == '\0')) {
|
||||
return true; // end reached simultaneously, match found
|
||||
} else if ((*pattern == '\0') || (*path == '\0')) {
|
||||
return false; // end reached on only one, failure
|
||||
} else if ((*pattern != '?') && (tolower(*pattern) != tolower(*path))) {
|
||||
return false; // chars don't match, failure
|
||||
}
|
||||
}
|
||||
// handling the asterisk (matches one or more chars in path)
|
||||
if ((*(pattern+1) == '?') || (*(pattern+1) == '*')) {
|
||||
return false; // stupid user shenanigans, failure
|
||||
} else if (*path == '\0') {
|
||||
return false; // asterisk, but end reached on path, failure
|
||||
} else if (*(pattern+1) == '\0') {
|
||||
return true; // nothing after the asterisk, match found
|
||||
} else { // we couldn't really go without recursion here
|
||||
for (path++; *path != '\0'; path++) {
|
||||
if (MatchName(pattern + 1, path)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetRootDirContentsWorker(DirStruct* contents) {
|
||||
static const char* drvname[] = {
|
||||
"SDCARD",
|
||||
@ -1103,7 +1046,7 @@ bool GetRootDirContentsWorker(DirStruct* contents) {
|
||||
u32 n_entries = 0;
|
||||
|
||||
// virtual root objects hacked in
|
||||
for (u32 pdrv = 0; (pdrv < NORM_FS+VIRT_FS) && (n_entries < MAX_ENTRIES); pdrv++) {
|
||||
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);
|
||||
@ -1124,24 +1067,6 @@ bool GetRootDirContentsWorker(DirStruct* contents) {
|
||||
return contents->n_entries;
|
||||
}
|
||||
|
||||
bool GetVirtualDirContentsWorker(DirStruct* contents, const char* path, const char* pattern) {
|
||||
if (strchr(path, '/')) return false; // only top level paths
|
||||
for (u32 n = 0; (n < virtualFileList_size) && (contents->n_entries < MAX_ENTRIES); n++) {
|
||||
VirtualFile vfile;
|
||||
DirEntry* entry = &(contents->entry[contents->n_entries]);
|
||||
if (pattern && !MatchName(pattern, virtualFileList[n])) continue;
|
||||
snprintf(entry->path, 256, "%s/%s", path, virtualFileList[n]);
|
||||
if (!FindVirtualFile(&vfile, entry->path, 0)) continue;
|
||||
entry->name = entry->path + strnlen(path, 256) + 1;
|
||||
entry->size = vfile.size;
|
||||
entry->type = T_FILE;
|
||||
entry->marked = 0;
|
||||
contents->n_entries++;
|
||||
}
|
||||
|
||||
return true; // not much we can check here
|
||||
}
|
||||
|
||||
bool GetDirContentsWorker(DirStruct* contents, char* fpath, int fnsize, const char* pattern, bool recursive) {
|
||||
DIR pdir;
|
||||
FILINFO fno;
|
||||
@ -1171,7 +1096,7 @@ bool GetDirContentsWorker(DirStruct* contents, char* fpath, int fnsize, const ch
|
||||
entry->size = fno.fsize;
|
||||
}
|
||||
entry->marked = 0;
|
||||
if (contents->n_entries >= MAX_ENTRIES) {
|
||||
if (contents->n_entries >= MAX_DIR_ENTRIES) {
|
||||
ret = true; // Too many entries, still okay
|
||||
break;
|
||||
}
|
||||
@ -1201,7 +1126,7 @@ void SearchDirContents(DirStruct* contents, const char* path, const char* patter
|
||||
contents->entry->size = 0;
|
||||
contents->n_entries = 1;
|
||||
if (DriveType(path) & DRV_VIRTUAL) {
|
||||
if (!GetVirtualDirContentsWorker(contents, path, pattern))
|
||||
if (!GetVirtualDirContents(contents, path, pattern))
|
||||
contents->n_entries = 0;
|
||||
} else {
|
||||
char fpath[256]; // 256 is the maximum length of a full path
|
||||
|
28
source/fs.h
28
source/fs.h
@ -1,15 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
typedef enum {
|
||||
T_ROOT,
|
||||
T_DIR,
|
||||
T_FILE,
|
||||
T_DOTDOT
|
||||
} EntryType;
|
||||
|
||||
#define MAX_ENTRIES 1024
|
||||
#include "dir.h"
|
||||
|
||||
// primary drive types
|
||||
#define DRV_UNKNOWN (0<<0)
|
||||
@ -43,19 +35,6 @@ typedef enum {
|
||||
#define SKIP_ALL (1<<1)
|
||||
#define OVERWRITE_ALL (1<<2)
|
||||
|
||||
typedef struct {
|
||||
char* name; // should point to the correct portion of the path
|
||||
char path[256];
|
||||
u64 size;
|
||||
EntryType type;
|
||||
u8 marked;
|
||||
} DirEntry;
|
||||
|
||||
typedef struct {
|
||||
u32 n_entries;
|
||||
DirEntry entry[MAX_ENTRIES];
|
||||
} DirStruct;
|
||||
|
||||
bool InitSDCardFS();
|
||||
bool InitExtFS();
|
||||
void DeinitExtFS();
|
||||
@ -133,8 +112,5 @@ uint64_t GetPartitionOffsetSector(const char* path);
|
||||
/** Function to identify the type of a drive **/
|
||||
int DriveType(const char* path);
|
||||
|
||||
/** Check for soecial search drive **/
|
||||
/** Check for special search drive **/
|
||||
bool IsSearchDrive(const char* path);
|
||||
|
||||
/** Helper function for copying DirEntry structs */
|
||||
void DirEntryCpy(DirEntry* dest, const DirEntry* orig);
|
||||
|
@ -26,8 +26,16 @@ bool CheckVirtualDrive(const char* path) {
|
||||
return virtual_src; // this is safe for SysNAND & memory
|
||||
}
|
||||
|
||||
bool FindVirtualFile(VirtualFile* vfile, const char* path, u32 size)
|
||||
{
|
||||
bool ReadVirtualDir(VirtualFile* vfile, u32 virtual_src) {
|
||||
if (virtual_src & (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND)) {
|
||||
return ReadVNandDir(vfile, virtual_src);
|
||||
} else if (virtual_src & VRT_MEMORY) {
|
||||
return ReadVMemDir(vfile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindVirtualFile(VirtualFile* vfile, const char* path, u32 size) {
|
||||
// get / fix the name
|
||||
char* fname = strchr(path, '/');
|
||||
if (!fname) return false;
|
||||
@ -39,17 +47,38 @@ bool FindVirtualFile(VirtualFile* vfile, const char* path, u32 size)
|
||||
if (!virtual_src || (fname - path != 3))
|
||||
return false;
|
||||
|
||||
// get virtual file struct from appropriate function
|
||||
if (virtual_src & (VRT_SYSNAND|VRT_EMUNAND|VRT_IMGNAND)) {
|
||||
if (!FindVNandFile(vfile, virtual_src, fname, size)) return false;
|
||||
} else if (virtual_src & VRT_MEMORY) {
|
||||
if (!FindVMemFile(vfile, fname, size)) return false;
|
||||
} else return false;
|
||||
// read virtual dir, match the path / size
|
||||
ReadVirtualDir(NULL, virtual_src); // reset dir reader
|
||||
while (ReadVirtualDir(vfile, virtual_src)) {
|
||||
vfile->flags |= virtual_src; // add source flag
|
||||
if (((strncasecmp(fname, vfile->name, 32) == 0) ||
|
||||
(size && (vfile->size == size)))) // search by size should be a last resort solution
|
||||
return true; // file found
|
||||
}
|
||||
|
||||
// add the virtual source to the virtual file flags
|
||||
vfile->flags |= virtual_src;
|
||||
// failed if arriving
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
bool GetVirtualDirContents(DirStruct* contents, const char* path, const char* pattern) {
|
||||
u32 virtual_src = GetVirtualSource(path);
|
||||
if (!virtual_src) return false; // not a virtual path
|
||||
if (strchr(path, '/')) return false; // only top level paths
|
||||
|
||||
VirtualFile vfile;
|
||||
ReadVirtualDir(NULL, virtual_src); // reset dir reader
|
||||
while ((contents->n_entries < MAX_DIR_ENTRIES) && (ReadVirtualDir(&vfile, virtual_src))) {
|
||||
DirEntry* entry = &(contents->entry[contents->n_entries]);
|
||||
if (pattern && !MatchName(pattern, vfile.name)) continue;
|
||||
snprintf(entry->path, 256, "%s/%s", path, vfile.name);
|
||||
entry->name = entry->path + strnlen(path, 256) + 1;
|
||||
entry->size = vfile.size;
|
||||
entry->type = T_FILE;
|
||||
entry->marked = 0;
|
||||
contents->n_entries++;
|
||||
}
|
||||
|
||||
return true; // not much we can check here
|
||||
}
|
||||
|
||||
int ReadVirtualFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count, u32* bytes_read)
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include "dir.h"
|
||||
#include "nand.h"
|
||||
|
||||
#define VRT_SYSNAND NAND_SYSNAND
|
||||
@ -10,14 +11,6 @@
|
||||
|
||||
#define VFLAG_A9LH_AREA (1<<20)
|
||||
|
||||
static const char* virtualFileList[] = { // must have a match in virtualFileTemplates[]
|
||||
"twln.bin", "twlp.bin", "agbsave.bin", "firm0.bin", "firm1.bin", "ctrnand_fat.bin",
|
||||
"ctrnand_full.bin", "nand.bin", "nand_minsize.bin", "nand_hdr.bin", "twlmbr.bin", "sector0x96.bin",
|
||||
"itcm.mem", "arm9.mem", "arm9ext.mem", "vram.mem", "dsp.mem", "axiwram.mem",
|
||||
"fcram.mem", "fcramext.mem", "dtcm.mem", "bootrom_unp.mem"
|
||||
};
|
||||
static const u32 virtualFileList_size = sizeof(virtualFileList) / sizeof(char*);
|
||||
|
||||
// virtual file flag (subject to change):
|
||||
// bits 0...9 : reserved for NAND virtual sources and info
|
||||
// bits 10...19: reserved for other virtual sources
|
||||
@ -34,5 +27,6 @@ typedef struct {
|
||||
u32 GetVirtualSource(const char* path);
|
||||
bool CheckVirtualDrive(const char* path);
|
||||
bool FindVirtualFile(VirtualFile* vfile, const char* path, u32 size);
|
||||
bool GetVirtualDirContents(DirStruct* contents, const char* path, const char* pattern);
|
||||
int ReadVirtualFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count, u32* bytes_read);
|
||||
int WriteVirtualFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count, u32* bytes_written);
|
||||
|
@ -19,28 +19,31 @@ static const VirtualFile vMemFileTemplates[] = {
|
||||
{ "bootrom_unp.mem" , 0xFFFF0000, 0x00008000, 0xFF, 0 }
|
||||
};
|
||||
|
||||
bool FindVMemFile(VirtualFile* vfile, const char* name, u32 size) {
|
||||
// parse the template list, get the correct one
|
||||
u32 n_templates = sizeof(vMemFileTemplates) / sizeof(VirtualFile);
|
||||
const VirtualFile* curr_template = NULL;
|
||||
for (u32 i = 0; i < n_templates; i++) {
|
||||
curr_template = &vMemFileTemplates[i];
|
||||
if (((strncasecmp(name, curr_template->name, 32) == 0) ||
|
||||
(size && (curr_template->size == size)))) // search by size should be a last resort solution
|
||||
break;
|
||||
curr_template = NULL;
|
||||
}
|
||||
if (!curr_template) return false;
|
||||
bool ReadVMemDir(VirtualFile* vfile) {
|
||||
static int num = -1;
|
||||
int n_templates = sizeof(vMemFileTemplates) / sizeof(VirtualFile);
|
||||
const VirtualFile* templates = vMemFileTemplates;
|
||||
|
||||
if (!vfile) { // NULL pointer -> reset dir reader / internal number
|
||||
num = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
while (++num < n_templates) {
|
||||
// copy current template to vfile
|
||||
memcpy(vfile, curr_template, sizeof(VirtualFile));
|
||||
memcpy(vfile, templates + num, sizeof(VirtualFile));
|
||||
|
||||
// process special flag
|
||||
if ((vfile->flags & VFLAG_N3DS_ONLY) && (GetUnitPlatform() != PLATFORM_N3DS))
|
||||
return false; // this is not on O3DS consoles
|
||||
|
||||
// found if arriving here
|
||||
return true;
|
||||
}
|
||||
if (num >= n_templates) return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int ReadVMemFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count) {
|
||||
u32 foffset = vfile->offset + offset;
|
||||
|
@ -3,6 +3,6 @@
|
||||
#include "common.h"
|
||||
#include "virtual.h"
|
||||
|
||||
bool FindVMemFile(VirtualFile* vfile, const char* name, u32 size);
|
||||
bool ReadVMemDir(VirtualFile* vfile);
|
||||
int ReadVMemFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count);
|
||||
int WriteVMemFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count);
|
||||
|
@ -34,42 +34,47 @@ bool CheckVNandDrive(u32 nand_src) {
|
||||
return GetNandSizeSectors(nand_src);
|
||||
}
|
||||
|
||||
bool FindVNandFile(VirtualFile* vfile, u32 nand_src, const char* name, u32 size) {
|
||||
// get virtual type (O3DS/N3DS/NO3DS)
|
||||
u32 virtual_type = CheckNandType(nand_src);
|
||||
// workaround if CheckNandType() comes up with no result (empty EmuNAND)
|
||||
if (!virtual_type) virtual_type = (GetUnitPlatform() == PLATFORM_3DS) ? NAND_TYPE_O3DS : NAND_TYPE_N3DS;
|
||||
bool ReadVNandDir(VirtualFile* vfile, u32 nand_src) {
|
||||
static int num = -1;
|
||||
int n_templates = sizeof(vNandFileTemplates) / sizeof(VirtualFile);
|
||||
const VirtualFile* templates = vNandFileTemplates;
|
||||
|
||||
// parse the template list, get the correct one
|
||||
u32 n_templates = sizeof(vNandFileTemplates) / sizeof(VirtualFile);
|
||||
const VirtualFile* curr_template = NULL;
|
||||
for (u32 i = 0; i < n_templates; i++) {
|
||||
curr_template = &vNandFileTemplates[i];
|
||||
if ((curr_template->flags & virtual_type) && ((strncasecmp(name, curr_template->name, 32) == 0) ||
|
||||
(size && (curr_template->size == size)))) // search by size should be a last resort solution
|
||||
break;
|
||||
curr_template = NULL;
|
||||
if (!vfile) { // NULL pointer -> reset dir reader / internal number
|
||||
num = -1;
|
||||
return true;
|
||||
}
|
||||
if (!curr_template) return false;
|
||||
|
||||
while (++num < n_templates) {
|
||||
// get NAND type (O3DS/N3DS/NO3DS), workaround for empty EmuNAND
|
||||
u32 nand_type = CheckNandType(nand_src);
|
||||
if (!nand_type) nand_type = (GetUnitPlatform() == PLATFORM_3DS) ? NAND_TYPE_O3DS : NAND_TYPE_N3DS;
|
||||
|
||||
// copy current template to vfile
|
||||
memcpy(vfile, curr_template, sizeof(VirtualFile));
|
||||
memcpy(vfile, templates + num, sizeof(VirtualFile));
|
||||
|
||||
// process special flags
|
||||
// process / check special flags
|
||||
if (!(vfile->flags & nand_type))
|
||||
continue; // virtual file has wrong NAND type
|
||||
if ((vfile->keyslot == 0x05) && !CheckSlot0x05Crypto())
|
||||
return false; // keyslot 0x05 not properly set up
|
||||
continue; // keyslot 0x05 not properly set up
|
||||
if ((vfile->flags & VFLAG_NEEDS_OTP) && !CheckSector0x96Crypto())
|
||||
return false; // sector 0x96 crypto not set up
|
||||
if (!(nand_src & VRT_SYSNAND) || (*(vu32*) 0x101401C0))
|
||||
vfile->flags &= ~VFLAG_A9LH_AREA; // flag is meaningless outside of A9LH / SysNAND
|
||||
if (vfile->flags & VFLAG_NAND_SIZE) {
|
||||
if ((nand_src != NAND_SYSNAND) && (GetNandSizeSectors(NAND_SYSNAND) != GetNandSizeSectors(nand_src)))
|
||||
return false; // EmuNAND/ImgNAND is too small
|
||||
continue; // EmuNAND/ImgNAND is too small
|
||||
vfile->size = GetNandSizeSectors(NAND_SYSNAND) * 0x200;
|
||||
}
|
||||
|
||||
// found if arriving here
|
||||
vfile->flags |= nand_src;
|
||||
return true;
|
||||
}
|
||||
if (num >= n_templates) return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int ReadVNandFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count) {
|
||||
u32 foffset = vfile->offset + offset;
|
||||
|
@ -4,6 +4,6 @@
|
||||
#include "virtual.h"
|
||||
|
||||
bool CheckVNandDrive(u32 nand_src);
|
||||
bool FindVNandFile(VirtualFile* vfile, u32 nand_src, const char* name, u32 size);
|
||||
bool ReadVNandDir(VirtualFile* vfile, u32 nand_src);
|
||||
int ReadVNandFile(const VirtualFile* vfile, u8* buffer, u32 offset, u32 count);
|
||||
int WriteVNandFile(const VirtualFile* vfile, const u8* buffer, u32 offset, u32 count);
|
||||
|
Loading…
x
Reference in New Issue
Block a user