#include #include "fs.h" #include "threads.h" #include "util.h" static std::string wd = "sdmc:/NXST/"; static FSFILE *debLog; static FsFileSystem sv; static std::vector pathFilter; void fs::init() { mkDirRec(wd); fs::logOpen(); } bool fs::mountSave(const FsSaveDataInfo &_m) { Result svOpen; FsSaveDataAttribute attr = {0}; switch (_m.save_data_type) { case FsSaveDataType_System: case FsSaveDataType_SystemBcat: { attr.uid = _m.uid; attr.system_save_data_id = _m.system_save_data_id; attr.save_data_type = _m.save_data_type; svOpen = fsOpenSaveDataFileSystemBySystemSaveDataId(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); } break; case FsSaveDataType_Account: { attr.uid = _m.uid; attr.application_id = _m.application_id; attr.save_data_type = _m.save_data_type; attr.save_data_rank = _m.save_data_rank; attr.save_data_index = _m.save_data_index; svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); } break; case FsSaveDataType_Device: { attr.application_id = _m.application_id; attr.save_data_type = FsSaveDataType_Device; svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); } break; case FsSaveDataType_Bcat: { attr.application_id = _m.application_id; attr.save_data_type = FsSaveDataType_Bcat; svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); } break; case FsSaveDataType_Cache: { attr.application_id = _m.application_id; attr.save_data_type = FsSaveDataType_Cache; attr.save_data_index = _m.save_data_index; svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); } break; case FsSaveDataType_Temporary: { attr.application_id = _m.application_id; attr.save_data_type = _m.save_data_type; svOpen = fsOpenSaveDataFileSystem(&sv, (FsSaveDataSpaceId) _m.save_data_space_id, &attr); } break; default: svOpen = 1; break; } return R_SUCCEEDED(svOpen) && fsdevMountDevice("sv", sv) != -1; } bool fs::commitToDevice(const std::string &dev) { bool ret = true; Result res = fsdevCommitDevice(dev.c_str()); if (R_FAILED(res)) { fs::logWrite("Error committing file to device -> 0x%X\n", res); ret = false; } return ret; } std::string fs::getWorkDir() { return wd; } void fs::setWorkDir(const std::string &_w) { wd = _w; } void fs::loadPathFilters(const uint64_t &tid) { char path[256]; // sprintf(path, "sdmc:/config/JKSV/0x%016lX_filter.txt", tid); if (fs::fileExists(path)) { fs::dataFile filter(path); while (filter.readNextLine(false)) pathFilter.push_back(filter.getLine()); } } bool fs::pathIsFiltered(const std::string &_path) { if (pathFilter.empty()) return false; for (std::string &_p: pathFilter) { if (_path == _p) return true; } return false; } void fs::freePathFilters() { pathFilter.clear(); } void fs::createSaveData(FsSaveDataType _type, uint64_t _tid, AccountUid _uid, threadInfo *t) { data::titleInfo *tinfo = data::getTitleInfoByTID(_tid); if (t) t->status->setStatus("Creating Save Data for #%s#...", tinfo->title.c_str()); uint16_t cacheIndex = 0; std::string indexStr; if (_type == FsSaveDataType_Cache && !(indexStr = util::getStringInput(SwkbdType_NumPad, "0", "Enter cache index", 2, 0, NULL)).empty()) cacheIndex = strtoul(indexStr.c_str(), NULL, 10); else if (_type == FsSaveDataType_Cache && indexStr.empty()) { if (t) t->finished = true; return; } FsSaveDataAttribute attr; memset(&attr, 0, sizeof(FsSaveDataAttribute)); attr.application_id = _tid; attr.uid = _uid; attr.system_save_data_id = 0; attr.save_data_type = _type; attr.save_data_rank = 0; attr.save_data_index = cacheIndex; FsSaveDataCreationInfo crt; memset(&crt, 0, sizeof(FsSaveDataCreationInfo)); int64_t saveSize = 0, journalSize = 0; switch (_type) { case FsSaveDataType_Account: saveSize = tinfo->nacp.user_account_save_data_size; journalSize = tinfo->nacp.user_account_save_data_journal_size; break; case FsSaveDataType_Device: saveSize = tinfo->nacp.device_save_data_size; journalSize = tinfo->nacp.device_save_data_journal_size; break; case FsSaveDataType_Bcat: saveSize = tinfo->nacp.bcat_delivery_cache_storage_size; journalSize = tinfo->nacp.bcat_delivery_cache_storage_size; break; case FsSaveDataType_Cache: saveSize = 32 * 1024 * 1024; if (tinfo->nacp.cache_storage_journal_size > tinfo->nacp.cache_storage_data_and_journal_size_max) journalSize = tinfo->nacp.cache_storage_journal_size; else journalSize = tinfo->nacp.cache_storage_data_and_journal_size_max; break; default: if (t) t->finished = true; return; break; } crt.save_data_size = saveSize; crt.journal_size = journalSize; crt.available_size = 0x4000; crt.owner_id = _type == FsSaveDataType_Bcat ? 0x010000000000000C : tinfo->nacp.save_data_owner_id; crt.flags = 0; crt.save_data_space_id = FsSaveDataSpaceId_User; FsSaveDataMetaInfo meta; memset(&meta, 0, sizeof(FsSaveDataMetaInfo)); if (_type != FsSaveDataType_Bcat) { meta.size = 0x40060; meta.type = FsSaveDataMetaType_Thumbnail; } Result res = 0; if (R_SUCCEEDED(res = fsCreateSaveDataFileSystem(&attr, &crt, &meta))) { util::createTitleDirectoryByTID(_tid); data::loadUsersTitles(false); } else { fs::logWrite("SaveCreate Failed -> %X\n", res); } } static void createSaveData_t(void *a) { threadInfo *t = (threadInfo *) a; fs::svCreateArgs *crt = (fs::svCreateArgs *) t->argPtr; fs::createSaveData(crt->type, crt->tid, crt->account, t); delete crt; t->finished = true; } void fs::createSaveDataThreaded(FsSaveDataType _type, uint64_t _tid, AccountUid _uid) { fs::svCreateArgs *send = new fs::svCreateArgs; send->type = _type; send->tid = _tid; send->account = _uid; threads::newThread(createSaveData_t, send, NULL); } bool fs::extendSaveData(const data::userTitleInfo *tinfo, uint64_t extSize, threadInfo *t) { if (t) t->status->setStatus("Extending Save Data for #%s#...", data::getTitleNameByTID(tinfo->tid).c_str()); uint64_t journal = fs::getJournalSizeMax(tinfo); uint64_t saveID = tinfo->saveInfo.save_data_id; FsSaveDataSpaceId space = (FsSaveDataSpaceId) tinfo->saveInfo.save_data_space_id; Result res = 0; if (R_FAILED((res = fsExtendSaveDataFileSystem(space, saveID, extSize, journal)))) { int64_t totalSize = 0; fs::mountSave(tinfo->saveInfo); fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &totalSize); fs::unmountSave(); fs::logWrite("Extend Failed: %uMB to %uMB -> %X\n", totalSize / 1024 / 1024, extSize / 1024 / 1024, res); // ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataExtendFailed", 0)); return false; } return true; } static void extendSaveData_t(void *a) { threadInfo *t = (threadInfo *) a; fs::svExtendArgs *e = (fs::svExtendArgs *) t->argPtr; fs::extendSaveData(e->tinfo, e->extSize, t); delete e; t->finished = true; } void fs::extendSaveDataThreaded(const data::userTitleInfo *tinfo, uint64_t extSize) { fs::svExtendArgs *send = new fs::svExtendArgs; send->tinfo = tinfo; send->extSize = extSize; threads::newThread(extendSaveData_t, send, NULL); } uint64_t fs::getJournalSize(const data::userTitleInfo *tinfo) { uint64_t ret = 0; data::titleInfo *t = data::getTitleInfoByTID(tinfo->tid); switch (tinfo->saveInfo.save_data_type) { case FsSaveDataType_Account: ret = t->nacp.user_account_save_data_journal_size; break; case FsSaveDataType_Device: ret = t->nacp.device_save_data_journal_size; break; case FsSaveDataType_Bcat: ret = t->nacp.bcat_delivery_cache_storage_size; break; case FsSaveDataType_Cache: if (t->nacp.cache_storage_journal_size > 0) ret = t->nacp.cache_storage_journal_size; else ret = t->nacp.cache_storage_data_and_journal_size_max; break; default: ret = BUFF_SIZE; break; } return ret; } uint64_t fs::getJournalSizeMax(const data::userTitleInfo *tinfo) { uint64_t ret = 0; data::titleInfo *extend = data::getTitleInfoByTID(tinfo->tid); switch (tinfo->saveInfo.save_data_type) { case FsSaveDataType_Account: if (extend->nacp.user_account_save_data_journal_size_max > extend->nacp.user_account_save_data_journal_size) ret = extend->nacp.user_account_save_data_journal_size_max; else ret = extend->nacp.user_account_save_data_journal_size; break; case FsSaveDataType_Bcat: ret = extend->nacp.bcat_delivery_cache_storage_size; break; case FsSaveDataType_Cache: if (extend->nacp.cache_storage_data_and_journal_size_max > extend->nacp.cache_storage_journal_size) ret = extend->nacp.cache_storage_data_and_journal_size_max; else ret = extend->nacp.cache_storage_journal_size; break; case FsSaveDataType_Device: if (extend->nacp.device_save_data_journal_size_max > extend->nacp.device_save_data_journal_size) ret = extend->nacp.device_save_data_journal_size_max; else ret = extend->nacp.device_save_data_journal_size; break; default: //will just fail ret = 0; break; } return ret; } static void wipeSave_t(void *a) { threadInfo *t = (threadInfo *) a; t->status->setStatus("Resetting save data..."); fs::delDir("sv:/"); fs::commitToDevice("sv"); t->finished = true; } void fs::wipeSave() { threads::newThread(wipeSave_t, NULL, NULL); } void fs::createNewBackup(void *a) { if (!fs::dirNotEmpty("sv:/")) { // ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popSaveIsEmpty", 0)); return; } uint64_t held = threads::padKeysHeld(); data::user *u = data::getCurrentUser(); data::userTitleInfo *d = data::getCurrentUserTitleInfo(); data::titleInfo *t = data::getTitleInfoByTID(d->tid); std::string out; const std::string dict[] = { util::getDateTime(util::DATE_FMT_YMD), util::getDateTime(util::DATE_FMT_YDM), util::getDateTime(util::DATE_FMT_HOYSTE), util::getDateTime(util::DATE_FMT_JHK), util::getDateTime(util::DATE_FMT_ASC), u->getUsernameSafe(), t->safeTitle, util::generateAbbrev(d->tid), ".zip" }; std::string defaultText = u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD); out = util::getStringInput(SwkbdType_QWERTY, defaultText, "Enter a new name", 64, 9, dict); out = util::safeString(out); if (!out.empty()) { std::string ext = util::getExtensionFromString(out); std::string path = util::generatePathByTID(d->tid) + out; fs::mkDir(path); path += "/"; fs::copyDirToDirThreaded("sv:/", path); // ui::fldRefreshMenu(); } } void fs::overwriteBackup(void *a) { threadInfo *t = (threadInfo *) a; std::string *dst = (std::string *) t->argPtr; bool saveHasFiles = fs::dirNotEmpty("sv:/"); if (fs::isDir(*dst) && saveHasFiles) { fs::delDir(*dst); fs::mkDir(*dst); dst->append("/"); fs::copyDirToDirThreaded("sv:/", *dst); } delete dst; t->finished = true; } void fs::restoreBackup(void *a) { threadInfo *t = (threadInfo *) a; std::string *restore = (std::string *) t->argPtr; data::user *u = data::getCurrentUser(); data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); if ((utinfo->saveInfo.save_data_type != FsSaveDataType_System)) { bool saveHasFiles = fs::dirNotEmpty("sv:/"); if (saveHasFiles) { std::string autoFolder = util::generatePathByTID(utinfo->tid) + "/AUTO - " + u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD) + "/"; fs::mkDir(autoFolder.substr(0, autoFolder.length() - 1)); fs::copyDirToDirThreaded("sv:/", autoFolder); } if (fs::isDir(*restore)) { restore->append("/"); if (fs::dirNotEmpty(*restore)) { t->status->setStatus("Calculating save data size..."); unsigned dirCount = 0, fileCount = 0; uint64_t saveSize = 0; int64_t availSize = 0; fs::getDirProps(*restore, dirCount, fileCount, saveSize); fsFsGetTotalSpace(fsdevGetDeviceFileSystem("sv"), "/", &availSize); if ((int) saveSize > availSize) { data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); fs::unmountSave(); fs::extendSaveData(utinfo, saveSize + 0x500000, t); fs::mountSave(utinfo->saveInfo); } fs::wipeSave(); fs::copyDirToDirCommitThreaded(*restore, "sv:/", "sv"); } // else // ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("popFolderIsEmpty", 0)); } else { std::string dstPath = "sv:/" + util::getFilenameFromPath(*restore); fs::copyFileCommitThreaded(*restore, dstPath, "sv"); } } delete restore; t->finished = true; } void fs::deleteBackup(void *a) { threadInfo *t = (threadInfo *) a; std::string *deletePath = (std::string *) t->argPtr; std::string backupName = util::getFilenameFromPath(*deletePath); t->status->setStatus("Deleting..."); data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo(); if (fs::isDir(*deletePath)) { *deletePath += "/"; fs::delDir(*deletePath); // ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), backupName.c_str()); } else { fs::delfile(*deletePath); // ui::showPopMessage(POP_FRAME_DEFAULT, ui::getUICString("saveDataBackupDeleted", 0), backupName.c_str()); } // ui::fldRefreshMenu(); delete deletePath; t->finished = true; } void fs::dumpAllUserSaves(void *a) { threadInfo *t = (threadInfo *) a; fs::copyArgs *c = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0); t->argPtr = c; data::user *u = data::getCurrentUser(); for (unsigned i = 0; i < u->titleInfo.size(); i++) { bool saveMounted = fs::mountSave(u->titleInfo[i].saveInfo); util::createTitleDirectoryByTID(u->titleInfo[i].tid); if (saveMounted && fs::dirNotEmpty("sv:/")) { fs::loadPathFilters(u->titleInfo[i].tid); std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD) + "/"; fs::mkDir(dst.substr(0, dst.length() - 1)); fs::copyDirToDir("sv:/", dst, t); fs::freePathFilters(); } fs::unmountSave(); } fs::copyArgsDestroy(c); t->finished = true; } void fs::dumpAllUsersAllSaves(void *a) { threadInfo *t = (threadInfo *) a; fs::copyArgs *c = fs::copyArgsCreate("", "", "", NULL, NULL, false, false, 0); t->argPtr = c; unsigned curUser = 0; while (data::users[curUser].getUID128() != 2) { data::user *u = &data::users[curUser++]; for (unsigned i = 0; i < u->titleInfo.size(); i++) { bool saveMounted = fs::mountSave(u->titleInfo[i].saveInfo); util::createTitleDirectoryByTID(u->titleInfo[i].tid); if (saveMounted && fs::dirNotEmpty("sv:/")) { fs::loadPathFilters(u->titleInfo[i].tid); std::string dst = util::generatePathByTID(u->titleInfo[i].tid) + u->getUsernameSafe() + " - " + util::getDateTime(util::DATE_FMT_YMD) + "/"; fs::mkDir(dst.substr(0, dst.length() - 1)); fs::copyDirToDir("sv:/", dst, t); fs::freePathFilters(); } fs::unmountSave(); } } fs::copyArgsDestroy(c); t->finished = true; } void fs::logOpen() { std::string logPath = wd + "log.txt"; debLog = fsfopen(logPath.c_str(), FsOpenMode_Write); fsfclose(debLog); } void fs::logWrite(const char *fmt, ...) { std::string logPath = wd + "log.txt"; debLog = fsfopen(logPath.c_str(), FsOpenMode_Append | FsOpenMode_Write); char tmp[256]; va_list args; va_start(args, fmt); vsprintf(tmp, fmt, args); va_end(args); fsfwrite(tmp, 1, strlen(tmp), debLog); fsfclose(debLog); }