2012-02-27 17:05:10 +01:00
|
|
|
/*////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
fsop contains coomprensive set of function for file and folder handling
|
|
|
|
|
|
|
|
en exposed s_fsop fsop structure can be used by callback to update operation status
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <ogcsys.h>
|
|
|
|
#include <ogc/lwp_watchdog.h>
|
2012-07-27 19:26:49 +02:00
|
|
|
#include <malloc.h>
|
2012-02-27 17:05:10 +01:00
|
|
|
#include <dirent.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/statvfs.h>
|
|
|
|
|
2012-08-05 15:48:15 +02:00
|
|
|
#include "fileOps/fileOps.h"
|
2012-12-08 17:17:35 +01:00
|
|
|
#include "gecko/gecko.hpp"
|
2012-08-05 15:48:15 +02:00
|
|
|
#include "loader/utils.h"
|
2013-06-29 18:54:21 +02:00
|
|
|
#include "memory/mem2.hpp"
|
2012-02-27 17:05:10 +01:00
|
|
|
|
2012-03-03 18:16:11 +01:00
|
|
|
#define SET(a, b) a = b; DCFlushRange(&a, sizeof(a));
|
|
|
|
#define STACKSIZE 8192
|
|
|
|
|
|
|
|
static u8 *buff = NULL;
|
|
|
|
static FILE *fs = NULL, *ft = NULL;
|
|
|
|
static u32 block = 32768;
|
|
|
|
static u32 blockIdx = 0;
|
|
|
|
static u32 blockInfo[2] = {0,0};
|
|
|
|
static u32 blockReady = 0;
|
2012-05-12 18:03:14 +02:00
|
|
|
static s32 stopThread;
|
2012-03-05 10:48:13 +01:00
|
|
|
static u64 folderSize = 0;
|
2012-06-11 16:48:28 +02:00
|
|
|
u64 FolderProgressBytes;
|
2012-02-27 17:05:10 +01:00
|
|
|
|
|
|
|
// return false if the file doesn't exist
|
2012-11-18 14:40:26 +01:00
|
|
|
bool fsop_GetFileSizeBytes(const char *path, size_t *filesize) // for me stats st_size report always 0 :(
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
|
|
|
FILE *f;
|
|
|
|
size_t size = 0;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
f = fopen(path, "rb");
|
2012-05-13 17:13:33 +02:00
|
|
|
if(!f)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
2012-05-13 17:13:33 +02:00
|
|
|
if(filesize)
|
|
|
|
*filesize = size;
|
2012-02-27 17:05:10 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Get file size
|
2012-06-11 16:48:28 +02:00
|
|
|
fseek(f, 0, SEEK_END);
|
2012-02-27 17:05:10 +01:00
|
|
|
size = ftell(f);
|
2012-05-13 17:13:33 +02:00
|
|
|
if(filesize)
|
|
|
|
*filesize = size;
|
|
|
|
fclose(f);
|
2012-06-11 16:48:28 +02:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Recursive fsop_GetFolderBytes
|
|
|
|
*/
|
2012-08-26 17:20:51 +02:00
|
|
|
u64 fsop_GetFolderBytes(const char *source)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
|
|
|
DIR *pdir;
|
|
|
|
struct dirent *pent;
|
|
|
|
char newSource[300];
|
|
|
|
u64 bytes = 0;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
|
|
|
pdir = opendir(source);
|
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
while((pent = readdir(pdir)) != NULL)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
|
|
|
// Skip it
|
2012-11-18 14:40:26 +01:00
|
|
|
if(pent->d_name[0] == '.')
|
2012-02-27 17:05:10 +01:00
|
|
|
continue;
|
2012-05-04 14:30:43 +02:00
|
|
|
snprintf(newSource, sizeof(newSource), "%s/%s", source, pent->d_name);
|
2012-02-27 17:05:10 +01:00
|
|
|
// If it is a folder... recurse...
|
2012-06-11 16:48:28 +02:00
|
|
|
if(fsop_DirExist(newSource))
|
|
|
|
bytes += fsop_GetFolderBytes(newSource);
|
2012-02-27 17:05:10 +01:00
|
|
|
else // It is a file !
|
|
|
|
{
|
|
|
|
size_t s;
|
2012-06-11 16:48:28 +02:00
|
|
|
fsop_GetFileSizeBytes(newSource, &s);
|
2012-02-27 17:05:10 +01:00
|
|
|
bytes += s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
closedir(pdir);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
return bytes;
|
2012-03-03 18:16:11 +01:00
|
|
|
}
|
2012-02-27 17:05:10 +01:00
|
|
|
|
2012-08-26 17:20:51 +02:00
|
|
|
u32 fsop_GetFolderKb(const char *source)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
2012-06-11 16:48:28 +02:00
|
|
|
u32 ret = (u32)round((double)fsop_GetFolderBytes (source) / 1000.0);
|
2012-02-27 17:05:10 +01:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-11-01 17:39:42 +01:00
|
|
|
u32 fsop_GetFreeSpaceKb(const char *path) // Return free kb on the device passed
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
|
|
|
struct statvfs s;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
|
|
|
statvfs(path, &s);
|
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
u32 ret = (u32)round(((double)s.f_bfree / 1000.0) * s.f_bsize);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
return ret ;
|
|
|
|
}
|
|
|
|
|
2012-04-08 17:54:34 +02:00
|
|
|
bool fsop_FileExist(const char *fn)
|
|
|
|
{
|
|
|
|
FILE * f;
|
|
|
|
f = fopen(fn, "rb");
|
2012-06-11 16:48:28 +02:00
|
|
|
if(f)
|
2012-04-08 17:54:34 +02:00
|
|
|
{
|
|
|
|
fclose(f);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-08-16 14:47:25 +02:00
|
|
|
bool fsop_DirExist(const char *path)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
|
|
|
DIR *dir;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
dir = opendir(path);
|
|
|
|
if(dir)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
|
|
|
closedir(dir);
|
|
|
|
return true;
|
|
|
|
}
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-06-07 20:18:21 +02:00
|
|
|
|
2012-11-18 14:40:26 +01:00
|
|
|
void fsop_MakeFolder(const char *path)
|
2012-06-07 20:18:21 +02:00
|
|
|
{
|
2012-06-21 16:35:18 +02:00
|
|
|
if(fsop_DirExist(path))
|
|
|
|
return;
|
2012-06-18 18:11:46 +02:00
|
|
|
gprintf("Folder path to create: %s\n", path);
|
2012-06-21 16:35:18 +02:00
|
|
|
mkdir(path, S_IREAD | S_IWRITE);
|
2012-06-07 20:18:21 +02:00
|
|
|
}
|
|
|
|
|
2012-05-12 18:03:14 +02:00
|
|
|
static void *thread_CopyFileReader()
|
2012-03-03 18:16:11 +01:00
|
|
|
{
|
|
|
|
u32 rb;
|
|
|
|
stopThread = 0;
|
|
|
|
DCFlushRange(&stopThread, sizeof(stopThread));
|
|
|
|
do
|
|
|
|
{
|
2012-06-11 16:48:28 +02:00
|
|
|
SET(rb, fread(&buff[blockIdx*block], 1, block, fs));
|
|
|
|
SET(blockInfo[blockIdx], rb);
|
|
|
|
SET(blockReady, 1);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
while(blockReady && !stopThread)
|
|
|
|
usleep(1);
|
2012-03-03 18:16:11 +01:00
|
|
|
}
|
2012-06-11 16:48:28 +02:00
|
|
|
while(stopThread == 0);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
|
|
|
stopThread = -1;
|
|
|
|
DCFlushRange(&stopThread, sizeof(stopThread));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-11-18 14:40:26 +01:00
|
|
|
bool fsop_CopyFile(const char *source, const char *target, progress_callback_t spinner, void *spinner_data)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
2012-11-18 14:40:26 +01:00
|
|
|
gprintf("Creating file: %s\n", target);
|
2012-02-27 17:05:10 +01:00
|
|
|
int err = 0;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
u32 size;
|
2012-06-11 16:48:28 +02:00
|
|
|
u32 rb, wb;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
fs = fopen(source, "rb");
|
2012-06-11 16:48:28 +02:00
|
|
|
if(!fs)
|
2012-02-27 17:05:10 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
ft = fopen(target, "wt");
|
2012-06-11 16:48:28 +02:00
|
|
|
if(!ft)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
2012-05-13 17:13:33 +02:00
|
|
|
fclose(fs);
|
2012-02-27 17:05:10 +01:00
|
|
|
return false;
|
|
|
|
}
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
//Get file size
|
2012-06-11 16:48:28 +02:00
|
|
|
fseek(fs, 0, SEEK_END);
|
2012-02-27 17:05:10 +01:00
|
|
|
size = ftell(fs);
|
|
|
|
|
|
|
|
if (size == 0)
|
|
|
|
{
|
2012-05-13 17:13:33 +02:00
|
|
|
fclose(fs);
|
|
|
|
fclose(ft);
|
2012-02-27 17:05:10 +01:00
|
|
|
return true;
|
|
|
|
}
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
// Return to beginning....
|
|
|
|
fseek(fs, 0, SEEK_SET);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-05-13 19:25:26 +02:00
|
|
|
u8 *threadStack = NULL;
|
2012-03-03 18:16:11 +01:00
|
|
|
lwp_t hthread = LWP_THREAD_NULL;
|
|
|
|
|
2012-07-27 19:26:49 +02:00
|
|
|
buff = malloc(block * 2);
|
2012-05-13 19:25:26 +02:00
|
|
|
if(buff == NULL)
|
|
|
|
return false;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
|
|
|
blockIdx = 0;
|
|
|
|
blockReady = 0;
|
|
|
|
blockInfo[0] = 0;
|
|
|
|
blockInfo[1] = 0;
|
2012-06-11 16:48:28 +02:00
|
|
|
u32 bytes = 0;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-07-27 19:26:49 +02:00
|
|
|
threadStack = malloc(STACKSIZE);
|
2012-05-13 19:25:26 +02:00
|
|
|
if(threadStack == NULL)
|
2012-07-27 19:26:49 +02:00
|
|
|
{
|
|
|
|
free(buff);
|
2012-05-13 19:25:26 +02:00
|
|
|
return false;
|
2012-07-27 19:26:49 +02:00
|
|
|
}
|
2012-05-13 19:25:26 +02:00
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
LWP_CreateThread(&hthread, thread_CopyFileReader, NULL, threadStack, STACKSIZE, 30);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
while(stopThread != 0)
|
|
|
|
usleep(5);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
|
|
|
u32 bi;
|
2012-02-27 17:05:10 +01:00
|
|
|
do
|
|
|
|
{
|
2012-06-11 16:48:28 +02:00
|
|
|
while(!blockReady)
|
|
|
|
usleep(1); // Let's wait for incoming block from the thread
|
|
|
|
|
2012-03-03 18:16:11 +01:00
|
|
|
bi = blockIdx;
|
|
|
|
|
|
|
|
// let's th thread to read the next buff
|
2012-06-11 16:48:28 +02:00
|
|
|
SET(blockIdx, 1 - blockIdx);
|
|
|
|
SET(blockReady, 0);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
|
|
|
rb = blockInfo[bi];
|
|
|
|
// write current block
|
|
|
|
wb = fwrite(&buff[bi*block], 1, rb, ft);
|
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
if(wb != wb || rb == 0)
|
|
|
|
err = 1;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
bytes += rb;
|
|
|
|
if(spinner)
|
|
|
|
{
|
|
|
|
FolderProgressBytes += rb;
|
|
|
|
spinner(FolderProgressBytes, folderSize, spinner_data);
|
|
|
|
}
|
2012-02-27 17:05:10 +01:00
|
|
|
}
|
2012-06-11 16:48:28 +02:00
|
|
|
while(bytes < size && err == 0);
|
2012-02-27 17:05:10 +01:00
|
|
|
|
2012-03-03 18:16:11 +01:00
|
|
|
stopThread = 1;
|
|
|
|
DCFlushRange(&stopThread, sizeof(stopThread));
|
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
while(stopThread != -1)
|
|
|
|
usleep(5);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
|
|
|
LWP_JoinThread(hthread, NULL);
|
2012-07-27 19:26:49 +02:00
|
|
|
free(threadStack);
|
2012-03-03 18:16:11 +01:00
|
|
|
|
|
|
|
stopThread = 1;
|
|
|
|
DCFlushRange(&stopThread, sizeof(stopThread));
|
|
|
|
|
2012-05-13 17:13:33 +02:00
|
|
|
fclose(fs);
|
|
|
|
fclose(ft);
|
2012-07-27 19:26:49 +02:00
|
|
|
free(buff);
|
2012-02-27 17:05:10 +01:00
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
if(err)
|
2012-03-03 18:16:11 +01:00
|
|
|
{
|
2012-06-11 16:48:28 +02:00
|
|
|
unlink(target);
|
2012-03-03 18:16:11 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
Recursive copyfolder
|
|
|
|
*/
|
2012-11-18 14:40:26 +01:00
|
|
|
static bool doCopyFolder(const char *source, const char *target, progress_callback_t spinner, void *spinner_data)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
|
|
|
DIR *pdir;
|
|
|
|
struct dirent *pent;
|
|
|
|
char newSource[300], newTarget[300];
|
|
|
|
bool ret = true;
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
// If target folder doesn't exist, create it !
|
2012-11-18 14:40:26 +01:00
|
|
|
fsop_MakeFolder(target);
|
2012-02-27 17:05:10 +01:00
|
|
|
|
2012-03-03 18:16:11 +01:00
|
|
|
pdir = opendir(source);
|
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
while((pent = readdir(pdir)) != NULL && ret == true)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
|
|
|
// Skip it
|
2012-11-18 14:40:26 +01:00
|
|
|
if(pent->d_name[0] == '.')
|
2012-02-27 17:05:10 +01:00
|
|
|
continue;
|
2012-05-04 14:30:43 +02:00
|
|
|
snprintf(newSource, sizeof(newSource), "%s/%s", source, pent->d_name);
|
|
|
|
snprintf(newTarget, sizeof(newTarget), "%s/%s", target, pent->d_name);
|
2012-06-11 16:48:28 +02:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
// If it is a folder... recurse...
|
2012-06-07 20:18:21 +02:00
|
|
|
if(fsop_DirExist(newSource))
|
2012-03-02 18:45:08 +01:00
|
|
|
ret = doCopyFolder(newSource, newTarget, spinner, spinner_data);
|
2012-02-27 17:05:10 +01:00
|
|
|
else // It is a file !
|
2012-03-02 18:45:08 +01:00
|
|
|
ret = fsop_CopyFile(newSource, newTarget, spinner, spinner_data);
|
2012-02-27 17:05:10 +01:00
|
|
|
}
|
2012-03-03 18:16:11 +01:00
|
|
|
|
2012-02-27 17:05:10 +01:00
|
|
|
closedir(pdir);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2012-03-05 10:48:13 +01:00
|
|
|
|
2012-11-18 14:40:26 +01:00
|
|
|
bool fsop_CopyFolder(const char *source, const char *target, progress_callback_t spinner, void *spinner_data)
|
2012-02-27 17:05:10 +01:00
|
|
|
{
|
2012-03-03 18:16:11 +01:00
|
|
|
gprintf("DML game USB->SD job started!\n");
|
2012-02-27 17:05:10 +01:00
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
FolderProgressBytes = 0;
|
2012-03-05 10:48:13 +01:00
|
|
|
folderSize = fsop_GetFolderBytes(source);
|
2012-03-03 18:16:11 +01:00
|
|
|
return doCopyFolder(source, target, spinner, spinner_data);
|
2012-02-27 17:05:10 +01:00
|
|
|
}
|
2012-03-05 10:48:13 +01:00
|
|
|
|
2012-11-18 14:40:26 +01:00
|
|
|
static inline void fsop_silentDelete(const char *source)
|
|
|
|
{
|
|
|
|
remove(source);
|
|
|
|
}
|
|
|
|
|
2012-08-20 21:15:52 +02:00
|
|
|
void fsop_deleteFolder(const char *source)
|
2012-03-05 10:48:13 +01:00
|
|
|
{
|
|
|
|
DIR *pdir;
|
|
|
|
struct dirent *pent;
|
|
|
|
char newSource[300];
|
|
|
|
|
|
|
|
pdir = opendir(source);
|
|
|
|
|
2012-06-11 16:48:28 +02:00
|
|
|
while((pent = readdir(pdir)) != NULL)
|
2012-03-05 10:48:13 +01:00
|
|
|
{
|
|
|
|
// Skip it
|
2012-11-18 14:40:26 +01:00
|
|
|
if(pent->d_name[0] == '.')
|
2012-03-05 10:48:13 +01:00
|
|
|
continue;
|
2012-06-07 20:18:21 +02:00
|
|
|
snprintf(newSource, sizeof(newSource), "%s/%s", source, pent->d_name);
|
2012-03-05 10:48:13 +01:00
|
|
|
// If it is a folder... recurse...
|
2012-06-07 20:18:21 +02:00
|
|
|
if(fsop_DirExist(newSource))
|
2012-11-18 14:40:26 +01:00
|
|
|
{
|
|
|
|
closedir(pdir);
|
2012-03-05 10:48:13 +01:00
|
|
|
fsop_deleteFolder(newSource);
|
2012-11-18 14:40:26 +01:00
|
|
|
pdir = opendir(source);
|
|
|
|
}
|
|
|
|
else // It is a file !
|
|
|
|
{
|
|
|
|
closedir(pdir);
|
|
|
|
fsop_silentDelete(newSource);
|
|
|
|
pdir = opendir(source);
|
|
|
|
}
|
2012-03-05 10:48:13 +01:00
|
|
|
}
|
|
|
|
closedir(pdir);
|
2012-11-18 14:40:26 +01:00
|
|
|
gprintf("Deleting directory: %s\n", source);
|
2012-03-05 10:48:13 +01:00
|
|
|
unlink(source);
|
|
|
|
}
|
2012-05-24 20:48:26 +02:00
|
|
|
|
2012-07-22 21:18:00 +02:00
|
|
|
void fsop_deleteFile(const char *source)
|
2012-05-24 20:48:26 +02:00
|
|
|
{
|
2012-11-18 14:40:26 +01:00
|
|
|
if(!fsop_FileExist(source))
|
|
|
|
return;
|
|
|
|
gprintf("Deleting file: %s\n", source);
|
|
|
|
fsop_silentDelete(source);
|
2012-05-24 20:48:26 +02:00
|
|
|
}
|
2013-06-29 18:54:21 +02:00
|
|
|
|
|
|
|
u8 *fsop_ReadFile(const char *path, u32 *size)
|
|
|
|
{
|
|
|
|
*(size) = 0;
|
|
|
|
if(!fsop_FileExist(path))
|
|
|
|
return NULL;
|
|
|
|
gprintf("Reading file: %s\n", path);
|
|
|
|
|
|
|
|
FILE *f = fopen(path, "rb");
|
|
|
|
fseek(f, 0, SEEK_END);
|
|
|
|
u32 filesize = ftell(f);
|
|
|
|
u8 *mem = (u8*)MEM2_alloc(filesize);
|
|
|
|
rewind(f);
|
|
|
|
fread(mem, filesize, 1, f);
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
*(size) = filesize;
|
|
|
|
return mem;
|
|
|
|
}
|
|
|
|
|
2013-06-30 20:40:49 +02:00
|
|
|
bool fsop_WriteFile(const char *path, const void *mem, const u32 size)
|
2013-06-29 18:54:21 +02:00
|
|
|
{
|
|
|
|
if(mem == NULL || size == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
FILE *f = fopen(path, "wb");
|
|
|
|
if(f == NULL)
|
|
|
|
return false;
|
|
|
|
gprintf("Writing file: %s\n", path);
|
|
|
|
|
|
|
|
fwrite(mem, size, 1, f);
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|