mirror of
https://github.com/J-D-K/JKSV.git
synced 2026-03-21 17:24:37 -05:00
704 lines
16 KiB
C++
704 lines
16 KiB
C++
#include <cstdio>
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <switch.h>
|
|
#include <dirent.h>
|
|
#include <unistd.h>
|
|
#include <cstdarg>
|
|
#include <minizip/zip.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include "file.h"
|
|
#include "util.h"
|
|
#include "ui.h"
|
|
#include "gfx.h"
|
|
#include "data.h"
|
|
|
|
#define BUFF_SIZE 512 * 1024
|
|
|
|
static std::string wd;
|
|
|
|
static FSFILE *log;
|
|
|
|
static FsFileSystem sv;
|
|
|
|
static struct
|
|
{
|
|
bool operator()(const fs::dirItem& a, const fs::dirItem& b)
|
|
{
|
|
if(a.isDir() != b.isDir())
|
|
return a.isDir();
|
|
|
|
for(unsigned i = 0; i < a.getItm().length(); i++)
|
|
{
|
|
char charA = tolower(a.getItm()[i]);
|
|
char charB = tolower(b.getItm()[i]);
|
|
if(charA != charB)
|
|
return charA < charB;
|
|
}
|
|
return false;
|
|
}
|
|
} sortDirList;
|
|
|
|
typedef struct
|
|
{
|
|
std::string from, to, dev;
|
|
uint64_t *pos;
|
|
bool fin;
|
|
} copyArgs;
|
|
|
|
static copyArgs *copyArgsCreate(const std::string& _f, const std::string& _t, const std::string& _dev, uint64_t *_p)
|
|
{
|
|
copyArgs *ret = new copyArgs;
|
|
ret->from = _f;
|
|
ret->to = _t;
|
|
ret->dev = _dev;
|
|
ret->pos = _p;
|
|
ret->fin = false;
|
|
return ret;
|
|
}
|
|
|
|
static void copyfile_t(void *a)
|
|
{
|
|
copyArgs *args = (copyArgs *)a;
|
|
|
|
FILE *f = fopen(args->from.c_str(), "rb");
|
|
FILE *t = fopen(args->to.c_str(), "wb");
|
|
if(f == NULL || t == NULL)
|
|
{
|
|
fclose(f);
|
|
fclose(t);
|
|
args->fin = true;
|
|
return;
|
|
}
|
|
|
|
size_t read = 0;
|
|
unsigned char *buff = new unsigned char[BUFF_SIZE];
|
|
while((read = fread(buff, 1, BUFF_SIZE, f)))
|
|
{
|
|
fwrite(buff, 1, read, t);
|
|
*args->pos += read;
|
|
}
|
|
delete[] buff;
|
|
fclose(f);
|
|
fclose(t);
|
|
|
|
args->fin = true;
|
|
}
|
|
|
|
static void copyfileFS_t(void *a)
|
|
{
|
|
copyArgs *args = (copyArgs *)a;
|
|
|
|
FSFILE *f = fsfopen(args->from.c_str(), FsOpenMode_Read);
|
|
FSFILE *t = fsfopen(args->to.c_str(), FsOpenMode_Write);
|
|
if(f == NULL || t == NULL)
|
|
{
|
|
fsfclose(f);
|
|
fsfclose(t);
|
|
args->fin = true;
|
|
return;
|
|
}
|
|
|
|
size_t read = 0;
|
|
unsigned char *buff = new unsigned char[BUFF_SIZE];
|
|
while((read = fsfread(buff, 1, BUFF_SIZE, f)))
|
|
{
|
|
fsfwrite(buff, 1, read, t);
|
|
*args->pos += read;
|
|
}
|
|
delete[] buff;
|
|
fsfclose(f);
|
|
fsfclose(t);
|
|
|
|
args->fin = true;
|
|
}
|
|
|
|
static void copyfileCommit_t(void *a)
|
|
{
|
|
copyArgs *args = (copyArgs *)a;
|
|
|
|
FILE *f = fopen(args->from.c_str(), "rb");
|
|
FILE *t = fopen(args->to.c_str(), "wb");
|
|
if(f == NULL || t == NULL)
|
|
{
|
|
fclose(f);
|
|
fclose(t);
|
|
args->fin = true;
|
|
return;
|
|
}
|
|
|
|
size_t read = 0;
|
|
unsigned char *buff = new unsigned char[BUFF_SIZE];
|
|
while((read = fread(buff, 1, BUFF_SIZE, f)))
|
|
{
|
|
fwrite(buff, 1, read, t);
|
|
*args->pos += read;
|
|
}
|
|
delete[] buff;
|
|
fclose(f);
|
|
fclose(t);
|
|
|
|
fsdevCommitDevice(args->dev.c_str());
|
|
|
|
args->fin = true;
|
|
}
|
|
|
|
static void copyfileCommitFS_t(void *a)
|
|
{
|
|
copyArgs *args = (copyArgs *)a;
|
|
|
|
FSFILE *f = fsfopen(args->from.c_str(), FsOpenMode_Read);
|
|
FSFILE *t = fsfopen(args->to.c_str(), FsOpenMode_Write);
|
|
if(f == NULL || t == NULL)
|
|
{
|
|
fsfclose(f);
|
|
fsfclose(t);
|
|
args->fin = true;
|
|
return;
|
|
}
|
|
|
|
size_t read = 0;
|
|
unsigned char *buff = new unsigned char[BUFF_SIZE];
|
|
while((read = fsfread(buff, 1, BUFF_SIZE, f)))
|
|
{
|
|
fsfwrite(buff, 1, read, t);
|
|
*args->pos += read;
|
|
}
|
|
delete[] buff;
|
|
fsfclose(f);
|
|
fsfclose(t);
|
|
|
|
fsdevCommitDevice(args->dev.c_str());
|
|
|
|
args->fin = true;
|
|
}
|
|
|
|
static void mkdirRec(const std::string& _p)
|
|
{
|
|
//skip first slash
|
|
size_t pos = _p.find('/', 0) + 1;
|
|
while((pos = _p.find('/', pos)) != _p.npos)
|
|
{
|
|
std::string create;
|
|
create.assign(_p.begin(), _p.begin() + pos);
|
|
mkdir(create.c_str(), 777);
|
|
++pos;
|
|
}
|
|
}
|
|
|
|
|
|
void fs::init()
|
|
{
|
|
if(fs::fileExists("sdmc:/switch/jksv_dir.txt"))
|
|
{
|
|
char tmp[256];
|
|
FILE *getDir = fopen("sdmc:/switch/jksv_dir.txt", "r");
|
|
fgets(tmp, 256, getDir);
|
|
fclose(getDir);
|
|
wd = tmp;
|
|
util::stripChar('\n', wd);
|
|
util::stripChar('\r', wd);
|
|
mkdirRec(wd);
|
|
}
|
|
else
|
|
{
|
|
mkdir("sdmc:/JKSV", 777);
|
|
wd = "sdmc:/JKSV/";
|
|
}
|
|
fs::logOpen();
|
|
}
|
|
|
|
void fs::exit()
|
|
{
|
|
fs::logClose();
|
|
}
|
|
|
|
bool fs::mountSave(const data::user& usr, const data::titledata& open)
|
|
{
|
|
Result svOpen;
|
|
switch(open.getType())
|
|
{
|
|
case FsSaveDataType_System:
|
|
svOpen = fsOpen_SystemSaveData(&sv, FsSaveDataSpaceId_System, open.getID(), usr.getUID());
|
|
break;
|
|
|
|
case FsSaveDataType_Account:
|
|
svOpen = fsOpen_SaveData(&sv, open.getID(), usr.getUID());
|
|
break;
|
|
|
|
case FsSaveDataType_Bcat:
|
|
svOpen = fsOpen_BcatSaveData(&sv, open.getID());
|
|
break;
|
|
|
|
case FsSaveDataType_Device:
|
|
svOpen = fsOpen_DeviceSaveData(&sv, open.getID());
|
|
break;
|
|
|
|
case FsSaveDataType_Temporary:
|
|
svOpen = fsOpen_TemporaryStorage(&sv);
|
|
break;
|
|
|
|
case FsSaveDataType_Cache:
|
|
svOpen = fsOpen_CacheStorage(&sv, open.getID(), open.getSaveIndex());
|
|
break;
|
|
|
|
case FsSaveDataType_SystemBcat:
|
|
svOpen = fsOpen_SystemBcatSaveData(&sv, open.getID());
|
|
break;
|
|
|
|
default:
|
|
svOpen = 1;
|
|
break;
|
|
}
|
|
return R_SUCCEEDED(svOpen) && fsdevMountDevice("sv", sv) != -1;
|
|
}
|
|
|
|
fs::dirItem::dirItem(const std::string& pathTo, const std::string& sItem)
|
|
{
|
|
itm = sItem;
|
|
|
|
std::string fullPath = pathTo + sItem;
|
|
struct stat s;
|
|
if(stat(fullPath.c_str(), &s) == 0 && S_ISDIR(s.st_mode))
|
|
dir = true;
|
|
}
|
|
|
|
fs::dirList::dirList(const std::string& _path)
|
|
{
|
|
path = _path;
|
|
d = opendir(path.c_str());
|
|
|
|
while((ent = readdir(d)))
|
|
item.push_back(dirItem(path, ent->d_name));
|
|
|
|
closedir(d);
|
|
|
|
std::sort(item.begin(), item.end(), sortDirList);
|
|
}
|
|
|
|
void fs::dirList::reassign(const std::string& _path)
|
|
{
|
|
path = _path;
|
|
|
|
d = opendir(path.c_str());
|
|
|
|
item.clear();
|
|
|
|
while((ent = readdir(d)))
|
|
item.push_back(dirItem(path, ent->d_name));
|
|
|
|
closedir(d);
|
|
|
|
std::sort(item.begin(), item.end(), sortDirList);
|
|
}
|
|
|
|
void fs::dirList::rescan()
|
|
{
|
|
item.clear();
|
|
d = opendir(path.c_str());
|
|
|
|
while((ent = readdir(d)))
|
|
item.push_back(dirItem(path, ent->d_name));
|
|
|
|
closedir(d);
|
|
|
|
std::sort(item.begin(), item.end(), sortDirList);
|
|
}
|
|
|
|
fs::dataFile::dataFile(const std::string& _path)
|
|
{
|
|
f = fopen(_path.c_str(), "r");
|
|
if(f != NULL)
|
|
opened = true;
|
|
}
|
|
|
|
fs::dataFile::~dataFile()
|
|
{
|
|
fclose(f);
|
|
}
|
|
|
|
bool fs::dataFile::readNextLine(bool proc)
|
|
{
|
|
bool ret = false;
|
|
char tmp[1024];
|
|
while(fgets(tmp, 1024, f))
|
|
{
|
|
if(tmp[0] != '#' && tmp[0] != '\n' && tmp[0] != '\r')
|
|
{
|
|
line = tmp;
|
|
ret = true;
|
|
break;
|
|
}
|
|
}
|
|
util::stripChar('\n', line);
|
|
util::stripChar('\r', line);
|
|
if(proc)
|
|
procLine();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void fs::dataFile::procLine()
|
|
{
|
|
size_t pPos = line.find_first_of('('), ePos = line.find_first_of('=');
|
|
if(pPos != line.npos || ePos != line.npos)
|
|
{
|
|
lPos = ePos < pPos ? ePos : pPos;
|
|
name.assign(line.begin(), line.begin() + lPos);
|
|
}
|
|
else
|
|
name = "NULL";
|
|
util::stripChar(' ', name);
|
|
++lPos;
|
|
}
|
|
|
|
std::string fs::dataFile::getNextValueStr()
|
|
{
|
|
std::string ret = "";
|
|
//Skip all spaces until we hit actual text
|
|
size_t pos1 = line.find_first_not_of(", ", lPos);
|
|
//If reading from quotes
|
|
if(line[pos1] == '"')
|
|
lPos = line.find_first_of('"', ++pos1);
|
|
else
|
|
lPos = line.find_first_of(",;\n", pos1);//Set lPos to end of string we want. This should just set lPos to the end of the line if it fails, which is ok
|
|
|
|
return line.substr(pos1, lPos++ - pos1);
|
|
}
|
|
|
|
int fs::dataFile::getNextValueInt()
|
|
{
|
|
int ret = 0;
|
|
std::string no = getNextValueStr();
|
|
if(no[0] == '0' && tolower(no[1]) == 'x')
|
|
ret = strtoul(no.c_str(), NULL, 16);
|
|
else
|
|
ret = strtoul(no.c_str(), NULL, 10);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void fs::copyFile(const std::string& from, const std::string& to)
|
|
{
|
|
Thread cpyThread;
|
|
uint64_t gProg = 0;
|
|
copyArgs *thr = copyArgsCreate(from, to, "", &gProg);
|
|
if(data::directFsCmd)
|
|
threadCreate(&cpyThread, copyfileFS_t, thr, NULL, 0x4000, 0x2B, 1);
|
|
else
|
|
threadCreate(&cpyThread, copyfile_t, thr, NULL, 0x4000, 0x2B, 1);
|
|
|
|
ui::progBar fileProg(fsize(from));
|
|
threadStart(&cpyThread);
|
|
while(!thr->fin)
|
|
{
|
|
fileProg.update(*thr->pos);
|
|
gfxBeginFrame();
|
|
fileProg.draw(from, ui::copyHead);
|
|
gfxEndFrame();
|
|
}
|
|
threadClose(&cpyThread);
|
|
delete thr;
|
|
}
|
|
|
|
void fs::copyFileCommit(const std::string& from, const std::string& to, const std::string& dev)
|
|
{
|
|
Thread cpyThread;
|
|
uint64_t gProg = 0;
|
|
copyArgs *thr = copyArgsCreate(from, to, dev, &gProg);
|
|
if(data::directFsCmd)
|
|
threadCreate(&cpyThread, copyfileCommitFS_t, thr, NULL, 0x4000, 0x2B, 1);
|
|
else
|
|
threadCreate(&cpyThread, copyfileCommit_t, thr, NULL, 0x4000, 0x2B, 1);
|
|
|
|
ui::progBar fileProg(fsize(from));
|
|
threadStart(&cpyThread);
|
|
while(!thr->fin)
|
|
{
|
|
fileProg.update(*thr->pos);
|
|
gfxBeginFrame();
|
|
fileProg.draw(from, ui::copyHead);
|
|
gfxEndFrame();
|
|
}
|
|
threadClose(&cpyThread);
|
|
delete thr;
|
|
}
|
|
|
|
void fs::copyDirToDir(const std::string& from, const std::string& to)
|
|
{
|
|
dirList list(from);
|
|
|
|
for(unsigned i = 0; i < list.getCount(); i++)
|
|
{
|
|
if(list.isDir(i))
|
|
{
|
|
std::string newFrom = from + list.getItem(i) + "/";
|
|
std::string newTo = to + list.getItem(i);
|
|
mkdir(newTo.c_str(), 0777);
|
|
newTo += "/";
|
|
|
|
copyDirToDir(newFrom, newTo);
|
|
}
|
|
else
|
|
{
|
|
std::string fullFrom = from + list.getItem(i);
|
|
std::string fullTo = to + list.getItem(i);
|
|
|
|
if(hasFreeSpace(fullTo, fsize(fullFrom)))
|
|
copyFile(fullFrom, fullTo);
|
|
else
|
|
ui::showMessage("*Error*", "Not enough free space to copy #%s#!", fullFrom.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void copyFileToZip(const std::string& from, zipFile *to)
|
|
{
|
|
FILE *cpy = fopen(from.c_str(), "rb");
|
|
|
|
size_t readIn = 0;
|
|
uint8_t *inBuff= new uint8_t[BUFF_SIZE];
|
|
while((readIn = fread(inBuff, 1, BUFF_SIZE, cpy)) > 0)
|
|
{
|
|
if(zipWriteInFileInZip(*to, inBuff, readIn) != 0)
|
|
ui::showMessage("fail", "here");
|
|
}
|
|
|
|
delete[] inBuff;
|
|
fclose(cpy);
|
|
}
|
|
|
|
void fs::copyDirToZip(const std::string& from, zipFile *to)
|
|
{
|
|
fs::dirList list(from);
|
|
|
|
for(unsigned i = 0; i < list.getCount(); i++)
|
|
{
|
|
if(list.isDir(i))
|
|
{
|
|
std::string newFrom = from + list.getItem(i) + "/";
|
|
fs::copyDirToZip(newFrom, to);
|
|
}
|
|
else
|
|
{
|
|
zip_fileinfo inf = { 0 };
|
|
std::string filename = from + list.getItem(i);
|
|
size_t devPos = filename.find_first_of('/') + 1;
|
|
if(zipOpenNewFileInZip(*to, filename.substr(devPos, filename.length()).c_str(), &inf, NULL, 0, NULL, 0, "", Z_DEFLATED, Z_DEFAULT_COMPRESSION) == ZIP_OK)
|
|
copyFileToZip(std::string(from) + list.getItem(i).c_str(), to);
|
|
zipCloseFileInZip(*to);
|
|
}
|
|
}
|
|
}
|
|
|
|
void fs::copyDirToDirCommit(const std::string& from, const std::string& to, const std::string& dev)
|
|
{
|
|
dirList list(from);
|
|
|
|
for(unsigned i = 0; i < list.getCount(); i++)
|
|
{
|
|
if(list.isDir(i))
|
|
{
|
|
std::string newFrom = from + list.getItem(i) + "/";
|
|
std::string newTo = to + list.getItem(i);
|
|
mkdir(newTo.c_str(), 0777);
|
|
newTo += "/";
|
|
|
|
copyDirToDirCommit(newFrom, newTo, dev);
|
|
}
|
|
else
|
|
{
|
|
std::string fullFrom = from + list.getItem(i);
|
|
std::string fullTo = to + list.getItem(i);
|
|
|
|
if(hasFreeSpace(fullTo, fsize(fullFrom)))
|
|
copyFileCommit(fullFrom, fullTo, dev);
|
|
else
|
|
ui::showMessage("*Error*", "Not enough free space to copy #%s#!", fullFrom.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
void fs::delfile(const std::string& path)
|
|
{
|
|
if(data::directFsCmd)
|
|
fsremove(path.c_str());
|
|
else
|
|
remove(path.c_str());
|
|
}
|
|
|
|
void fs::delDir(const std::string& path)
|
|
{
|
|
dirList list(path);
|
|
for(unsigned i = 0; i < list.getCount(); i++)
|
|
{
|
|
if(list.isDir(i))
|
|
{
|
|
std::string newPath = path + list.getItem(i) + "/";
|
|
delDir(newPath);
|
|
|
|
std::string delPath = path + list.getItem(i);
|
|
rmdir(delPath.c_str());
|
|
}
|
|
else
|
|
{
|
|
std::string delPath = path + list.getItem(i);
|
|
std::remove(delPath.c_str());
|
|
}
|
|
}
|
|
rmdir(path.c_str());
|
|
}
|
|
|
|
bool fs::dumpAllUserSaves(const data::user& u)
|
|
{
|
|
for(unsigned i = 0; i < u.titles.size(); i++)
|
|
{
|
|
hidScanInput();
|
|
|
|
if(hidKeysHeld(CONTROLLER_P1_AUTO) & KEY_B)
|
|
return false;
|
|
|
|
if(fs::mountSave(u, u.titles[i]))
|
|
{
|
|
u.titles[i].createDir();
|
|
|
|
//sdmc:/JKSV/[title]/[user] - [date]/
|
|
std::string outPath = u.titles[i].getPath() + u.getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_ASC);
|
|
mkdir(outPath.c_str(), 777);
|
|
outPath += "/";
|
|
fs::copyDirToDir("sv:/", outPath);
|
|
|
|
fsdevUnmountDevice("sv");
|
|
}
|
|
}
|
|
|
|
return true;//?
|
|
}
|
|
|
|
std::string fs::getFileProps(const std::string& _path)
|
|
{
|
|
std::string ret = "";
|
|
|
|
FILE *get = fopen(_path.c_str(), "rb");
|
|
if(get != NULL)
|
|
{
|
|
//Size
|
|
fseek(get, 0, SEEK_END);
|
|
unsigned fileSize = ftell(get);
|
|
fseek(get, 0, SEEK_SET);
|
|
|
|
fclose(get);
|
|
|
|
//Probably add more later
|
|
|
|
char tmp[256];
|
|
std::sprintf(tmp, "Path: #%s#\nSize: %u", _path.c_str(), fileSize);
|
|
|
|
ret = tmp;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void fs::getDirProps(const std::string& _path, uint32_t& dirCount, uint32_t& fileCount, uint64_t& totalSize)
|
|
{
|
|
fs::dirList list(_path);
|
|
|
|
for(unsigned i = 0; i < list.getCount(); i++)
|
|
{
|
|
if(list.isDir(i))
|
|
{
|
|
dirCount++;
|
|
std::string newPath = _path + list.getItem(i) + "/";
|
|
uint32_t dirAdd = 0, fileAdd = 0;
|
|
uint64_t sizeAdd = 0;
|
|
|
|
getDirProps(newPath, dirAdd, fileAdd, sizeAdd);
|
|
dirCount += dirAdd;
|
|
fileCount += fileAdd;
|
|
totalSize += sizeAdd;
|
|
}
|
|
else
|
|
{
|
|
fileCount++;
|
|
std::string filePath = _path + list.getItem(i);
|
|
|
|
FILE *gSize = fopen(filePath.c_str(), "rb");
|
|
fseek(gSize, 0, SEEK_END);
|
|
size_t fSize = ftell(gSize);
|
|
fclose(gSize);
|
|
|
|
totalSize += fSize;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool fs::fileExists(const std::string& path)
|
|
{
|
|
FILE *test = fopen(path.c_str(), "rb");
|
|
if(test != NULL)
|
|
{
|
|
fclose(test);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
size_t fs::fsize(const std::string& _f)
|
|
{
|
|
size_t ret = 0;
|
|
FILE *get = fopen(_f.c_str(), "rb");
|
|
if(get != NULL)
|
|
{
|
|
fseek(get, 0, SEEK_END);
|
|
ret = ftell(get);
|
|
fseek(get, 0, SEEK_SET);
|
|
}
|
|
fclose(get);
|
|
return ret;
|
|
}
|
|
|
|
bool fs::hasFreeSpace(const std::string& _f, int needed)
|
|
{
|
|
s64 free = 0;
|
|
//grab device from _f
|
|
size_t endDevPos = _f.find(':', 0);
|
|
std::string dev;
|
|
dev.assign(_f.begin(), _f.begin() + endDevPos);
|
|
fsFsGetFreeSpace(fsdevGetDeviceFileSystem(dev.c_str()), "/", &free);
|
|
|
|
return free > needed;
|
|
}
|
|
|
|
std::string fs::getWorkDir() { return wd; }
|
|
|
|
bool fs::isDir(const std::string& _path)
|
|
{
|
|
struct stat s;
|
|
return stat(_path.c_str(), &s) == 0 && S_ISDIR(s.st_mode);
|
|
}
|
|
|
|
void fs::logOpen()
|
|
{
|
|
std::string logPath = wd + "log.txt";
|
|
remove(logPath.c_str());
|
|
log = fsfopen(logPath.c_str(), FsOpenMode_Write);
|
|
}
|
|
|
|
void fs::logWrite(const char *fmt, ...)
|
|
{
|
|
char tmp[256];
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vsprintf(tmp, fmt, args);
|
|
va_end(args);
|
|
fsfwrite(tmp, 1, strlen(tmp), log);
|
|
}
|
|
|
|
void fs::logClose()
|
|
{
|
|
fsfclose(log);
|
|
}
|
|
|