initial commit

This commit is contained in:
2024-07-14 20:20:03 +03:00
commit 31953464e7
95 changed files with 4688 additions and 0 deletions

BIN
source/.DS_Store vendored Normal file

Binary file not shown.

59
source/Main.cpp Normal file
View File

@@ -0,0 +1,59 @@
#include <MainApplication.hpp>
#include <data.h>
#include <fs.h>
static int nxlink_sock = -1;
extern "C" void userAppInit() {
appletInitialize();
hidInitialize();
nsInitialize();
setsysInitialize();
setInitialize();
accountInitialize(AccountServiceType_Administrator);
pmshellInitialize();
socketInitializeDefault();
pdmqryInitialize();
nxlink_sock = nxlinkStdio();
printf("userAppInit\n");
}
extern "C" void userAppExit() {
if (nxlink_sock != -1) {
close(nxlink_sock);
}
appletExit();
hidExit();
nsExit();
setsysExit();
setExit();
accountExit();
pmshellExit();
socketExit();
pdmqryExit();
}
// Main entrypoint
int main() {
printf("main\n");
// First create our renderer, where one can customize SDL or other stuff's
// initialization.
auto renderer_opts = pu::ui::render::RendererInitOptions(
SDL_INIT_EVERYTHING, pu::ui::render::RendererHardwareFlags);
renderer_opts.UseImage(pu::ui::render::IMGAllFlags);
renderer_opts.UseAudio(pu::ui::render::MixerAllFlags);
renderer_opts.UseTTF();
auto renderer = pu::ui::render::Renderer::New(renderer_opts);
data::init();
fs::init();
// Create our main application from the renderer
auto main = ui::MainApplication::New(renderer);
main->Prepare();
main->Show();
return 0;
}

View File

@@ -0,0 +1,21 @@
#include <string>
#include <switch.h>
#include <switch/services/hid.h>
#include <vector>
#include <MainApplication.hpp>
namespace ui {
MainApplication *mainApp;
void MainApplication::OnLoad() {
mainApp = this;
this->usersLayout = UsersLayout::New();
this->titlesLayout = TitlesLayout::New();
this->usersLayout->SetOnInput(
std::bind(&UsersLayout::onInput, this->usersLayout, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4));
this->titlesLayout->SetOnInput(std::bind(&TitlesLayout::onInput, this->titlesLayout,std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4));
this->LoadLayout(this->usersLayout);
}
}

71
source/TitlesLayout.cpp Normal file
View File

@@ -0,0 +1,71 @@
#include <MainApplication.hpp>
#include <cinttypes>
#include <cstdio>
#include <data.h>
static std::vector<uint64_t> accSids, devSids, bcatSids, cacheSids;
//Sort save create tids alphabetically by title from data
static struct
{
bool operator()(const uint64_t& tid1, const uint64_t& tid2)
{
std::string tid1Title = data::getTitleNameByTID(tid1);
std::string tid2Title = data::getTitleNameByTID(tid2);
uint32_t pointA = 0, pointB = 0;
for(unsigned i = 0, j = 0; i < tid1Title.length(); )
{
ssize_t tid1Cnt = decode_utf8(&pointA, (const uint8_t *)&tid1Title.c_str()[i]);
ssize_t tid2Cnt = decode_utf8(&pointB, (const uint8_t *)&tid2Title.c_str()[j]);
pointA = tolower(pointA), pointB = tolower(pointB);
if(pointA != pointB)
return pointA < pointB;
i += tid1Cnt, j += tid2Cnt;
}
return false;
}
} sortCreateTIDs;
namespace ui {
extern MainApplication *mainApp;
void TitlesLayout::InitTitles() {
this->titlesMenu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94, 7);
const data::user *u = data::getCurrentUser();
for(const data::userTitleInfo& t : u->titleInfo) {
auto titleItem = pu::ui::elm::MenuItem::New(data::getTitleNameByTID(t.tid).c_str());
titleItem->SetColor(COLOR("#FFFFFFFF"));
titlesMenu->AddItem(titleItem);
}
this->Add(this->titlesMenu);
this->SetBackgroundColor(BACKGROUND_COLOR);
}
void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
if (Down & HidNpadButton_Plus) {
mainApp->Close();
return;
}
if (Down & HidNpadButton_A) {
printf("current game index is %i\n", this->titlesMenu->GetSelectedIndex());
data::setTitleIndex(this->titlesMenu->GetSelectedIndex());
int opt = mainApp->CreateShowDialog("", "What do you want?", { "Transfer", "Receive" }, true);
printf("opt is %d\n", opt);
switch (opt) {
case 0: {
// Transfert selected
break;
}
case 1: {
// Receive selected
break;
}
}
}
}
}

39
source/UsersLayout.cpp Normal file
View File

@@ -0,0 +1,39 @@
#include <cstdio>
#include <data.h>
#include <MainApplication.hpp>
namespace ui {
extern MainApplication *mainApp;
UsersLayout::UsersLayout() : Layout::Layout() {
this->usersMenu = pu::ui::elm::Menu::New(0, 0, 1280, COLOR("#67000000"), COLOR("#170909FF"), 94,
7);
this->usersMenu->SetScrollbarColor(COLOR("#170909FF"));
for (data::user &u: data::users) {
auto username = pu::ui::elm::MenuItem::New(u.getUsername() + ": " + std::to_string(u.titleInfo.size()));
username->SetColor(COLOR("#FFFFFFFF"));
this->usersMenu->AddItem(username);
}
this->SetBackgroundColor(BACKGROUND_COLOR);
this->Add(this->usersMenu);
}
int32_t UsersLayout::GetCurrentIndex() {
return this->usersMenu->GetSelectedIndex();
}
void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
if (Down & HidNpadButton_Plus) {
mainApp->Close();
return;
}
if (Down & HidNpadButton_A) {
printf("current index is %i\n", this->usersMenu->GetSelectedIndex());
data::setUserIndex(this->usersMenu->GetSelectedIndex());
mainApp->titlesLayout->InitTitles();
mainApp->LoadLayout(mainApp->titlesLayout);
}
}
}

352
source/data.cpp Normal file
View File

@@ -0,0 +1,352 @@
#include <vector>
#include <unordered_map>
#include <string>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <ctime>
#include <switch.h>
#include "data.h"
#include "fs/file.h"
#include "util.h"
#include "type.h"
//FsSaveDataSpaceId_All doesn't work for SD
static const unsigned saveOrder[] = {0, 1, 2, 3, 4, 100, 101};
int selUser = 0, selData = 0;
//User vector
std::vector <data::user> data::users;
//For other save types
static bool sysBCATPushed = false, tempPushed = false;
std::unordered_map <uint64_t, data::titleInfo> data::titles;
//Sorts titles by sortType
static struct {
bool operator()(const data::userTitleInfo &a, const data::userTitleInfo &b) {
std::string titleA = data::getTitleNameByTID(a.tid);
std::string titleB = data::getTitleNameByTID(b.tid);
uint32_t pointA, pointB;
for (unsigned i = 0, j = 0; i < titleA.length();) {
ssize_t aCnt = decode_utf8(&pointA, (const uint8_t *) &titleA.data()[i]);
ssize_t bCnt = decode_utf8(&pointB, (const uint8_t *) &titleB.data()[j]);
pointA = tolower(pointA), pointB = tolower(pointB);
if (pointA != pointB)
return pointA < pointB;
i += aCnt;
j += bCnt;
}
return false;
}
} sortTitles;
//Returns -1 for new
static int getUserIndex(const AccountUid &id) {
u128 nId = util::accountUIDToU128(id);
for (unsigned i = 0; i < data::users.size(); i++)
if (data::users[i].getUID128() == nId) return i;
return -1;
}
static inline bool accountSystemSaveCheck(const FsSaveDataInfo &_inf) {
if (_inf.save_data_type == FsSaveDataType_System && util::accountUIDToU128(_inf.uid) != 0)
return false;
return true;
}
//Minimal init/test to avoid loading and creating things I don't need
static bool testMount(const FsSaveDataInfo &_inf) {
bool ret = false;
if ((ret = fs::mountSave(_inf)))
fs::unmountSave();
return ret;
}
static inline void addTitleToList(const uint64_t &tid) {
uint64_t outSize = 0;
NsApplicationControlData *ctrlData = new NsApplicationControlData;
NacpLanguageEntry *ent;
Result ctrlRes = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, ctrlData,
sizeof(NsApplicationControlData), &outSize);
Result nacpRes = nacpGetLanguageEntry(&ctrlData->nacp, &ent);
size_t iconSize = outSize - sizeof(ctrlData->nacp);
if (R_SUCCEEDED(ctrlRes) && !(outSize < sizeof(ctrlData->nacp)) && R_SUCCEEDED(nacpRes) && iconSize > 0) {
//Copy nacp
memcpy(&data::titles[tid].nacp, &ctrlData->nacp, sizeof(NacpStruct));
//Setup 'shortcuts' to strings
NacpLanguageEntry *ent;
nacpGetLanguageEntry(&data::titles[tid].nacp, &ent);
if (strlen(ent->name) == 0)
data::titles[tid].title = ctrlData->nacp.lang[SetLanguage_ENUS].name;
else
data::titles[tid].title = ent->name;
data::titles[tid].author = ent->author;
if ((data::titles[tid].safeTitle = util::safeString(ent->name)) == "")
data::titles[tid].safeTitle = util::getIDStr(tid);
} else {
memset(&data::titles[tid].nacp, 0, sizeof(NacpStruct));
data::titles[tid].title = util::getIDStr(tid);
data::titles[tid].author = "Someone?";
data::titles[tid].safeTitle = util::getIDStr(tid);
}
delete ctrlData;
}
static inline bool titleIsLoaded(const uint64_t &tid) {
auto findTid = data::titles.find(tid);
return findTid == data::titles.end() ? false : true;
}
static void loadUserAccounts() {
s32 total = 0;
AccountUid *uids = new AccountUid[8];
if (R_SUCCEEDED(accountListAllUsers(uids, 8, &total))) {
for (int i = 0; i < total; i++)
data::users.emplace_back(uids[i], "", "");
}
delete[] uids;
}
//This can load titles installed without having save data
static void loadTitlesFromRecords() {
NsApplicationRecord nsRecord;
int32_t entryCount = 0, recordOffset = 0;
while (R_SUCCEEDED(nsListApplicationRecord(&nsRecord, 1, recordOffset++, &entryCount)) && entryCount > 0) {
if (!titleIsLoaded(nsRecord.application_id))
addTitleToList(nsRecord.application_id);
}
}
bool data::loadUsersTitles(bool clearUsers) {
static unsigned systemUserCount = 4;
FsSaveDataInfoReader it;
FsSaveDataInfo info;
s64 total = 0;
//Clear titles
for (data::user &u: data::users)
u.titleInfo.clear();
if (clearUsers) {
systemUserCount = 4;
data::users.clear();
loadUserAccounts();
sysBCATPushed = false;
tempPushed = false;
users.emplace_back(util::u128ToAccountUID(3), "Device", "Device");
users.emplace_back(util::u128ToAccountUID(2), "BCAT", "BCAT");
users.emplace_back(util::u128ToAccountUID(5), "Cache", "Cache");
users.emplace_back(util::u128ToAccountUID(0), "System", "System");
}
for (unsigned i = 0; i < 7; i++) {
if (R_FAILED(fsOpenSaveDataInfoReader(&it, (FsSaveDataSpaceId) saveOrder[i])))
continue;
while (R_SUCCEEDED(fsSaveDataInfoReaderRead(&it, &info, 1, &total)) && total != 0) {
uint64_t tid = 0;
if (info.save_data_type == FsSaveDataType_System || info.save_data_type == FsSaveDataType_SystemBcat)
tid = info.system_save_data_id;
else
tid = info.application_id;
if (!titleIsLoaded(tid))
addTitleToList(tid);
//Don't bother with this stuff
if (!accountSystemSaveCheck(info) || !testMount(info))
continue;
switch (info.save_data_type) {
case FsSaveDataType_Bcat:
info.uid = util::u128ToAccountUID(2);
break;
case FsSaveDataType_Device:
info.uid = util::u128ToAccountUID(3);
break;
case FsSaveDataType_SystemBcat:
info.uid = util::u128ToAccountUID(4);
if (!sysBCATPushed) {
++systemUserCount;
sysBCATPushed = true;
users.emplace_back(util::u128ToAccountUID(4), "System BCAT",
"System BCAT");
}
break;
case FsSaveDataType_Cache:
info.uid = util::u128ToAccountUID(5);
break;
case FsSaveDataType_Temporary:
info.uid = util::u128ToAccountUID(6);
if (!tempPushed) {
++systemUserCount;
tempPushed = true;
users.emplace_back(util::u128ToAccountUID(6), "Temporary",
"Temporary");
}
break;
}
int u = getUserIndex(info.uid);
if (u == -1) {
users.emplace(data::users.end() - systemUserCount, info.uid, "", "");
u = getUserIndex(info.uid);
}
PdmPlayStatistics playStats;
if (info.save_data_type == FsSaveDataType_Account || info.save_data_type == FsSaveDataType_Device)
pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(info.application_id, info.uid, false,
&playStats);
else
memset(&playStats, 0, sizeof(PdmPlayStatistics));
users[u].addUserTitleInfo(tid, &info, &playStats);
}
fsSaveDataInfoReaderClose(&it);
}
//Get reference to device save user
unsigned devPos = getUserIndex(util::u128ToAccountUID(3));
data::user &dev = data::users[devPos];
for (unsigned i = 0; i < devPos; i++) {
//Not needed but makes this easier to read
data::user &u = data::users[i];
u.titleInfo.insert(u.titleInfo.end(), dev.titleInfo.begin(), dev.titleInfo.end());
}
data::sortUserTitles();
return true;
}
void data::sortUserTitles() {
for (data::user &u: data::users)
std::sort(u.titleInfo.begin(), u.titleInfo.end(), sortTitles);
}
void data::init() {
data::loadUsersTitles(true);
}
void data::exit() {
/*Still needed for planned future revisions*/
}
void data::setUserIndex(unsigned _sUser) {
selUser = _sUser;
}
data::user *data::getCurrentUser() {
return &users[selUser];
}
unsigned data::getCurrentUserIndex() {
return selUser;
}
void data::setTitleIndex(unsigned _sTitle) {
selData = _sTitle;
}
data::userTitleInfo *data::getCurrentUserTitleInfo() {
return &users[selUser].titleInfo[selData];
}
unsigned data::getCurrentUserTitleInfoIndex() {
return selData;
}
data::titleInfo *data::getTitleInfoByTID(const uint64_t &tid) {
if (titles.find(tid) != titles.end())
return &titles[tid];
return NULL;
}
std::string data::getTitleNameByTID(const uint64_t &tid) {
return titles[tid].title;
}
std::string data::getTitleSafeNameByTID(const uint64_t &tid) {
return titles[tid].safeTitle;
}
int data::getTitleIndexInUser(const data::user &u, const uint64_t &tid) {
for (unsigned i = 0; i < u.titleInfo.size(); i++) {
if (u.titleInfo[i].tid == tid)
return i;
}
return -1;
}
data::user::user(const AccountUid &_id, const std::string &_backupName, const std::string &_safeBackupName) {
userID = _id;
uID128 = util::accountUIDToU128(_id);
AccountProfile prof;
AccountProfileBase base;
if (R_SUCCEEDED(accountGetProfile(&prof, userID)) && R_SUCCEEDED(accountProfileGet(&prof, NULL, &base))) {
username = base.nickname;
userSafe = util::safeString(username);
if (userSafe.empty()) {
char tmp[32];
sprintf(tmp, "Acc%08X", (uint32_t) uID128);
userSafe = tmp;
}
uint32_t jpgSize = 0;
accountProfileGetImageSize(&prof, &jpgSize);
uint8_t *jpegData = new uint8_t[jpgSize];
accountProfileLoadImage(&prof, jpegData, jpgSize, &jpgSize);
delete[] jpegData;
accountProfileClose(&prof);
} else {
username = _backupName.empty() ? util::getIDStr((uint64_t) uID128) : _backupName;
userSafe = _safeBackupName.empty() ? util::getIDStr((uint64_t) uID128) : _safeBackupName;
}
titles.reserve(64);
}
void data::user::setUID(const AccountUid &_id) {
userID = _id;
uID128 = util::accountUIDToU128(_id);
}
void data::user::addUserTitleInfo(const uint64_t &tid, const FsSaveDataInfo *_saveInfo, const PdmPlayStatistics *_stats) {
data::userTitleInfo newInfo;
newInfo.tid = tid;
memcpy(&newInfo.saveInfo, _saveInfo, sizeof(FsSaveDataInfo));
memcpy(&newInfo.playStats, _stats, sizeof(PdmPlayStatistics));
titleInfo.push_back(newInfo);
}
void data::dispStats() {
data::user *cu = data::getCurrentUser();
data::userTitleInfo *d = data::getCurrentUserTitleInfo();
//Easiest/laziest way to do this
std::string stats = std::to_string(users.size()) + "\n";
for (data::user &u: data::users) {
stats += u.getUsername() + ": " + std::to_string(u.titleInfo.size()) + "\n";
}
}

519
source/fs.cpp Normal file
View File

@@ -0,0 +1,519 @@
#include <switch.h>
#include "fs.h"
#include "threads.h"
#include "util.h"
static std::string wd = "sdmc:/NXST/";
static FSFILE *debLog;
static FsFileSystem sv;
static std::vector <std::string> 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);
}

266
source/fs/dir.cpp Normal file
View File

@@ -0,0 +1,266 @@
#include <switch.h>
#include <algorithm>
#include "fs.h"
#include "util.h"
#include <threads.h>
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;
void fs::mkDir(const std::string& _p)
{
mkdir(_p.c_str(), 777);
}
void fs::mkDirRec(const std::string& _p)
{
//skip first slash
size_t pos = _p.find('/', 0) + 1;
while((pos = _p.find('/', pos)) != _p.npos)
{
fs::mkDir(_p.substr(0, pos).c_str());
++pos;
}
}
void fs::delDir(const std::string& path)
{
dirList list(path);
for(unsigned i = 0; i < list.getCount(); i++)
{
if(pathIsFiltered(path + list.getItem(i)))
continue;
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::dirNotEmpty(const std::string& _dir)
{
fs::dirList tmp(_dir);
return tmp.getCount() > 0;
}
bool fs::isDir(const std::string& _path)
{
struct stat s;
return stat(_path.c_str(), &s) == 0 && S_ISDIR(s.st_mode);
}
void fs::copyDirToDir(const std::string& src, const std::string& dst, threadInfo *t)
{
if(t)
t->status->setStatus("Opening '#%s#'...");
fs::dirList *list = new fs::dirList(src);
for(unsigned i = 0; i < list->getCount(); i++)
{
if(pathIsFiltered(src + list->getItem(i)))
continue;
if(list->isDir(i))
{
std::string newSrc = src + list->getItem(i) + "/";
std::string newDst = dst + list->getItem(i) + "/";
fs::mkDir(newDst.substr(0, newDst.length() - 1));
fs::copyDirToDir(newSrc, newDst, t);
}
else
{
std::string fullSrc = src + list->getItem(i);
std::string fullDst = dst + list->getItem(i);
if(t)
t->status->setStatus("Copying '#%s#'...");
fs::copyFile(fullSrc, fullDst, t);
}
}
delete list;
}
static void copyDirToDir_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
fs::copyDirToDir(in->src, in->dst, t);
if(in->cleanup)
fs::copyArgsDestroy(in);
t->finished = true;
}
void fs::copyDirToDirThreaded(const std::string& src, const std::string& dst)
{
fs::copyArgs *send = fs::copyArgsCreate(src, dst, "", NULL, NULL, true, false, 0);
threads::newThread(copyDirToDir_t, send, fs::fileDrawFunc);
}
void fs::copyDirToDirCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t)
{
if(t)
t->status->setStatus("Opening '#%s#'...");
fs::dirList *list = new fs::dirList(src);
for(unsigned i = 0; i < list->getCount(); i++)
{
if(pathIsFiltered(src + list->getItem(i)))
continue;
if(list->isDir(i))
{
std::string newSrc = src + list->getItem(i) + "/";
std::string newDst = dst + list->getItem(i) + "/";
fs::mkDir(newDst.substr(0, newDst.length() - 1));
fs::copyDirToDirCommit(newSrc, newDst, dev, t);
}
else
{
std::string fullSrc = src + list->getItem(i);
std::string fullDst = dst + list->getItem(i);
if(t)
t->status->setStatus("Copying '#%s#'...");
fs::copyFileCommit(fullSrc, fullDst, dev, t);
}
}
delete list;
}
static void copyDirToDirCommit_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
fs::copyDirToDirCommit(in->src, in->dst, in->dev, t);
if(in->cleanup)
fs::copyArgsDestroy(in);
t->finished = true;
}
void fs::copyDirToDirCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev)
{
fs::copyArgs *send = fs::copyArgsCreate(src, dst, dev, NULL, NULL, true, false, 0);
threads::newThread(copyDirToDirCommit_t, send, fs::fileDrawFunc);
}
void fs::getDirProps(const std::string& path, unsigned& dirCount, unsigned& fileCount, uint64_t& totalSize)
{
fs::dirList *d = new fs::dirList(path);
for(unsigned i = 0; i < d->getCount(); i++)
{
if(d->isDir(i))
{
++dirCount;
std::string newPath = path + d->getItem(i) + "/";
fs::getDirProps(newPath, dirCount, fileCount, totalSize);
}
else
{
++fileCount;
std::string filePath = path + d->getItem(i);
totalSize += fs::fsize(filePath);
}
}
delete d;
}
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;
}
std::string fs::dirItem::getName() const
{
size_t extPos = itm.find_last_of('.'), slPos = itm.find_last_of('/');
if(extPos == itm.npos)
return "";
return itm.substr(slPos + 1, extPos);
}
std::string fs::dirItem::getExt() const
{
return util::getExtensionFromString(itm);
}
fs::dirList::dirList(const std::string& _path)
{
DIR *d;
struct dirent *ent;
path = _path;
d = opendir(path.c_str());
while((ent = readdir(d)))
item.emplace_back(path, ent->d_name);
closedir(d);
std::sort(item.begin(), item.end(), sortDirList);
}
void fs::dirList::reassign(const std::string& _path)
{
DIR *d;
struct dirent *ent;
path = _path;
d = opendir(path.c_str());
item.clear();
while((ent = readdir(d)))
item.emplace_back(path, ent->d_name);
closedir(d);
std::sort(item.begin(), item.end(), sortDirList);
}
void fs::dirList::rescan()
{
item.clear();
DIR *d;
struct dirent *ent;
d = opendir(path.c_str());
while((ent = readdir(d)))
item.emplace_back(path, ent->d_name);
closedir(d);
std::sort(item.begin(), item.end(), sortDirList);
}

383
source/fs/file.cpp Normal file
View File

@@ -0,0 +1,383 @@
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <switch.h>
#include <unistd.h>
#include <cstdarg>
#include <mutex>
#include <condition_variable>
#include <sys/stat.h>
#include "fs.h"
#include "util.h"
#include "data.h"
#include <threads.h>
static std::string wd = "sdmc:/JKSV/";
typedef struct
{
std::mutex bufferLock;
std::condition_variable cond;
std::vector<uint8_t> sharedBuffer;
std::string dst, dev;
bool bufferIsFull = false;
unsigned int filesize = 0, writeLimit = 0;
} fileCpyThreadArgs;
static void writeFile_t(void *a)
{
fileCpyThreadArgs *in = (fileCpyThreadArgs *)a;
size_t written = 0;
std::vector<uint8_t> localBuffer;
std::FILE *out = std::fopen(in->dst.c_str(), "wb");
while(written < in->filesize)
{
std::unique_lock<std::mutex> buffLock(in->bufferLock);
in->cond.wait(buffLock, [in]{ return in->bufferIsFull;});
localBuffer.clear();
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
in->sharedBuffer.clear();
in->bufferIsFull = false;
buffLock.unlock();
in->cond.notify_one();
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
}
fclose(out);
}
static void writeFileCommit_t(void *a)
{
fileCpyThreadArgs *in = (fileCpyThreadArgs *)a;
size_t written = 0, journalCount = 0;
std::vector<uint8_t> localBuffer;
FILE *out = fopen(in->dst.c_str(), "wb");
while(written < in->filesize)
{`
std::unique_lock<std::mutex> buffLock(in->bufferLock);
in->cond.wait(buffLock, [in]{ return in->bufferIsFull; });
localBuffer.clear();
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
in->sharedBuffer.clear();
in->bufferIsFull = false;
buffLock.unlock();
in->cond.notify_one();
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
journalCount += written;
if(journalCount >= in->writeLimit)
{
journalCount = 0;
fclose(out);
fs::commitToDevice(in->dev.c_str());
out = fopen(in->dst.c_str(), "ab");
}
}
fclose(out);
}
fs::copyArgs *fs::copyArgsCreate(const std::string& src, const std::string& dst, const std::string& dev, zipFile z, unzFile unz, bool _cleanup, bool _trimZipPath, uint8_t _trimPlaces)
{
copyArgs *ret = new copyArgs;
ret->src = src;
ret->dst = dst;
ret->dev = dev;
ret->z = z;
ret->unz = unz;
ret->cleanup = _cleanup;
ret->offset = 0;
ret->trimZipPath = _trimZipPath;
ret->trimZipPlaces = _trimPlaces;
return ret;
}
void fs::copyArgsDestroy(copyArgs *c)
{
// delete c->prog;
delete c;
c = NULL;
}
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("(=,");
if(pPos != line.npos)
{
lPos = pPos;
name.assign(line.begin(), line.begin() + lPos);
}
else
name = line;
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
ret = line.substr(pos1, lPos++ - pos1);
util::replaceStr(ret, "\\n", "\n");
return ret;
}
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& src, const std::string& dst, threadInfo *t)
{
fs::copyArgs *c = NULL;
size_t filesize = fs::fsize(src);
if(t)
{
c = (fs::copyArgs *)t->argPtr;
c->offset = 0;
}
FILE *fsrc = fopen(src.c_str(), "rb");
if(!fsrc)
{
fclose(fsrc);
return;
}
fileCpyThreadArgs thrdArgs;
thrdArgs.dst = dst;
thrdArgs.filesize = filesize;
uint8_t *buff = new uint8_t[BUFF_SIZE];
std::vector<uint8_t> transferBuffer;
Thread writeThread;
threadCreate(&writeThread, writeFile_t, &thrdArgs, NULL, 0x40000, 0x2E, 2);
threadStart(&writeThread);
size_t readIn = 0;
uint64_t readCount = 0;
while((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0)
{
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
readCount += readIn;
if(c)
c->offset = readCount;
if(transferBuffer.size() >= TRANSFER_BUFFER_LIMIT || readCount == filesize)
{
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
thrdArgs.cond.wait(buffLock, [&thrdArgs]{ return thrdArgs.bufferIsFull == false; });
thrdArgs.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end());
transferBuffer.clear();
thrdArgs.bufferIsFull = true;
buffLock.unlock();
thrdArgs.cond.notify_one();
}
}
threadWaitForExit(&writeThread);
threadClose(&writeThread);
fclose(fsrc);
delete[] buff;
}
static void copyFileThreaded_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
t->status->setStatus("Copy file", in->src.c_str());
fs::copyFile(in->src, in->dst, t);
if(in->cleanup)
fs::copyArgsDestroy(in);
t->finished = true;
}
void fs::copyFileThreaded(const std::string& src, const std::string& dst)
{
fs::copyArgs *send = fs::copyArgsCreate(src, dst, "", NULL, NULL, true, false, 0);
threads::newThread(copyFileThreaded_t, send, fs::fileDrawFunc);
}
void fs::copyFileCommit(const std::string& src, const std::string& dst, const std::string& dev, threadInfo *t)
{
fs::copyArgs *c = NULL;
size_t filesize = fs::fsize(src);
if(t)
{
c = (fs::copyArgs *)t->argPtr;
c->offset = 0;
// c->prog->setMax(filesize);
// c->prog->update(0);
}
FILE *fsrc = fopen(src.c_str(), "rb");
if(!fsrc)
{
fclose(fsrc);
return;
}
fileCpyThreadArgs thrdArgs;
thrdArgs.dst = dst;
thrdArgs.dev = dev;
thrdArgs.filesize = filesize;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
uint64_t journalSpace = fs::getJournalSize(utinfo);
thrdArgs.writeLimit = (journalSpace - 0x100000) < TRANSFER_BUFFER_LIMIT ? journalSpace - 0x100000 : TRANSFER_BUFFER_LIMIT;
Thread writeThread;
threadCreate(&writeThread, writeFileCommit_t, &thrdArgs, NULL, 0x040000, 0x2E, 2);
uint8_t *buff = new uint8_t[BUFF_SIZE];
size_t readIn = 0;
uint64_t readCount = 0;
std::vector<uint8_t> transferBuffer;
threadStart(&writeThread);
while((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0)
{
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
readCount += readIn;
if(c)
c->offset = readCount;
if(transferBuffer.size() >= thrdArgs.writeLimit || readCount == filesize)
{
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
thrdArgs.cond.wait(buffLock, [&thrdArgs]{ return thrdArgs.bufferIsFull == false; });
thrdArgs.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end());
transferBuffer.clear();
thrdArgs.bufferIsFull = true;
buffLock.unlock();
thrdArgs.cond.notify_one();
}
}
threadWaitForExit(&writeThread);
threadClose(&writeThread);
fclose(fsrc);
fs::commitToDevice(dev);
delete[] buff;
}
static void copyFileCommit_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *in = (fs::copyArgs *)t->argPtr;
t->status->setStatus("Copy file", in->src.c_str());
fs::copyFileCommit(in->src, in->dst, in->dev, t);
if(in->cleanup)
fs::copyArgsDestroy(in);
t->finished = true;
}
void fs::copyFileCommitThreaded(const std::string& src, const std::string& dst, const std::string& dev)
{
fs::copyArgs *send = fs::copyArgsCreate(src, dst, dev, NULL, NULL, true, false, 0);
threads::newThread(copyFileCommit_t, send, fs::fileDrawFunc);
}
void fs::fileDrawFunc(void *a)
{
threadInfo *t = (threadInfo *)a;
if(!t->finished && t->argPtr)
{
copyArgs *c = (copyArgs *)t->argPtr;
std::string tmp;
t->status->getStatus(tmp);
c->argLock();
// c->prog->update(c->offset);
// c->prog->draw(tmp);
c->argUnlock();
}
}
void fs::delfile(const std::string& path)
{
remove(path.c_str());
}
void fs::getShowFileProps(const std::string& _path)
{
size_t size = fs::fsize(_path);
// ui::showMessage(ui::getUICString("fileModeFileProperties", 0), _path.c_str(), util::getSizeString(size).c_str());
}
bool fs::fileExists(const std::string& path)
{
bool ret = false;
FILE *test = fopen(path.c_str(), "rb");
if(test != NULL)
ret = true;
fclose(test);
return ret;
}
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);
}
fclose(get);
return ret;
}

150
source/fs/fsfile.c Normal file
View File

@@ -0,0 +1,150 @@
#include <switch.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include "fs/fsfile.h"
char *getDeviceFromPath(char *dev, size_t _max, const char *path)
{
memset(dev, 0, _max);
char *c = strchr(path, ':');
if(c - path > _max)
return NULL;
//probably not good? idk
memcpy(dev, path, c - path);
return dev;
}
char *getFilePath(char *pathOut, size_t _max, const char *path)
{
memset(pathOut, 0, _max);
char *c = strchr(path, '/');
size_t pLength = strlen(c);
if(pLength > _max)
return NULL;
memcpy(pathOut, c, pLength);
return pathOut;
}
bool fsMkDir(const char *_p)
{
char devStr[16];
char path[FS_MAX_PATH];
if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(path, FS_MAX_PATH, _p))
return false;
Result res = fsFsCreateDirectory(fsdevGetDeviceFileSystem(devStr), path);
return res == 0;
}
int fsremove(const char *_p)
{
char devStr[16];
char path[FS_MAX_PATH];
if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(path, FS_MAX_PATH, _p))
return -1;
Result res = fsFsDeleteFile(fsdevGetDeviceFileSystem(devStr), path);
return res;
}
Result fsDelDirRec(const char *_p)
{
char devStr[16];
char path[FS_MAX_PATH];
if(!getDeviceFromPath(devStr, 16, _p) || ! getFilePath(path, FS_MAX_PATH, _p))
return 1;
return fsFsDeleteDirectoryRecursively(fsdevGetDeviceFileSystem(devStr), path);
}
bool fsfcreate(const char *_p, int64_t crSize)
{
char devStr[16];
char filePath[FS_MAX_PATH];
if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(filePath, FS_MAX_PATH, _p))
return false;
FsFileSystem *s = fsdevGetDeviceFileSystem(devStr);
if(s == NULL)
return false;
Result res = fsFsCreateFile(s, filePath, crSize, 0);
if(R_SUCCEEDED(res))
res = fsdevCommitDevice(devStr);
return R_SUCCEEDED(res) ? true : false;
}
FSFILE *fsfopen(const char *_p, uint32_t mode)
{
char devStr[16];
char filePath[FS_MAX_PATH];
if(!getDeviceFromPath(devStr, 16, _p) || !getFilePath(filePath, FS_MAX_PATH, _p))
return NULL;
FsFileSystem *s = fsdevGetDeviceFileSystem(devStr);
if(s == NULL)
return NULL;
if(mode == FsOpenMode_Write)
{
fsFsDeleteFile(s, filePath);
fsFsCreateFile(s, filePath, 0, 0);
}
FSFILE *ret = malloc(sizeof(FSFILE));
ret->error = fsFsOpenFile(s, filePath, mode, &ret->_f);
if(R_FAILED(ret->error))
{
free(ret);
return NULL;
}
fsFileGetSize(&ret->_f, &ret->fsize);
ret->offset = (mode & FsOpenMode_Append) ? ret->fsize : 0;
return ret;
}
FSFILE *fsfopenWithSystem(FsFileSystem *_s, const char *_p, uint32_t mode)
{
if(mode & FsOpenMode_Write)
{
fsFsDeleteFile(_s, _p);
fsFsCreateFile(_s, _p, 0, 0);
}
else if(mode & FsOpenMode_Append)
fsFsCreateFile(_s, _p, 0, 0);
FSFILE *ret = malloc(sizeof(FSFILE));
ret->error = fsFsOpenFile(_s, _p, mode, &ret->_f);
if(R_FAILED(ret->error))
{
free(ret);
return NULL;
}
fsFileGetSize(&ret->_f, &ret->fsize);
ret->offset = (mode & FsOpenMode_Append) ? ret->fsize : 0;
return ret;
}
size_t fsfwrite(const void *buf, size_t sz, size_t count, FSFILE *_f)
{
size_t fullSize = sz * count;
if(_f->offset + fullSize > _f->fsize)
{
s64 newSize = (_f->fsize + fullSize) - (_f->fsize - _f->offset);
fsFileSetSize(&_f->_f, newSize);
_f->fsize = newSize;
}
_f->error = fsFileWrite(&_f->_f, _f->offset, buf, fullSize, FsWriteOption_Flush);
_f->offset += fullSize;
return fullSize;
}

251
source/fs/zip.cpp Normal file
View File

@@ -0,0 +1,251 @@
#include <switch.h>
#include <time.h>
#include <mutex>
#include <vector>
#include <condition_variable>
#include "fs.h"
#include "util.h"
#include "threads.h"
typedef struct
{
std::mutex buffLock;
std::condition_variable cond;
std::vector<uint8_t> sharedBuffer;
std::string dst, dev;
bool bufferIsFull = false;
unzFile unz;
unsigned int fileSize, writeLimit = 0;
} unzThrdArgs;
static void writeFileFromZip_t(void *a)
{
unzThrdArgs *in = (unzThrdArgs *)a;
std::vector<uint8_t> localBuffer;
unsigned int written = 0, journalCount = 0;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
uint64_t journalSpace = fs::getJournalSize(utinfo);
FILE *out = fopen(in->dst.c_str(), "wb");
while(written < in->fileSize)
{
std::unique_lock<std::mutex> buffLock(in->buffLock);
in->cond.wait(buffLock, [in]{ return in->bufferIsFull; });
localBuffer.clear();
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
in->sharedBuffer.clear();
in->bufferIsFull = false;
buffLock.unlock();
in->cond.notify_one();
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
journalCount += written;
if(journalCount >= in->writeLimit)
{
journalCount = 0;
fclose(out);
fs::commitToDevice(in->dev);
out = fopen(in->dst.c_str(), "ab");
}
}
fclose(out);
}
void fs::copyDirToZip(const std::string& src, zipFile dst, bool trimPath, int trimPlaces, threadInfo *t)
{
fs::copyArgs *c = NULL;
if(t)
{
t->status->setStatus("threadStatusOpeningFolder");
c = (fs::copyArgs *)t->argPtr;
}
fs::dirList *list = new fs::dirList(src);
for(unsigned i = 0; i < list->getCount(); i++)
{
std::string itm = list->getItem(i);
if(fs::pathIsFiltered(src + itm))
continue;
if(list->isDir(i))
{
std::string newSrc = src + itm + "/";
fs::copyDirToZip(newSrc, dst, trimPath, trimPlaces, t);
}
else
{
time_t raw;
time(&raw);
tm *locTime = localtime(&raw);
zip_fileinfo inf = { (unsigned)locTime->tm_sec, (unsigned)locTime->tm_min, (unsigned)locTime->tm_hour,
(unsigned)locTime->tm_mday, (unsigned)locTime->tm_mon, (unsigned)(1900 + locTime->tm_year), 0, 0, 0 };
std::string filename = src + itm;
size_t zipNameStart = 0;
if(trimPath)
util::trimPath(filename, trimPlaces);
else
zipNameStart = filename.find_first_of('/') + 1;
if(t)
t->status->setStatus("threadStatusAddingFileToZip");
int zipOpenFile = zipOpenNewFileInZip64(dst, filename.substr(zipNameStart, filename.npos).c_str(), &inf, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0);
if(zipOpenFile == ZIP_OK)
{
std::string fullSrc = src + itm;
if(c)
{
c->offset = 0;
// c->prog->setMax(fs::fsize(fullSrc));
// c->prog->update(0);
}
FILE *fsrc = fopen(fullSrc.c_str(), "rb");
size_t readIn = 0;
uint8_t *buff = new uint8_t[ZIP_BUFF_SIZE];
while((readIn = fread(buff, 1, ZIP_BUFF_SIZE, fsrc)) > 0)
{
zipWriteInFileInZip(dst, buff, readIn);
if(c)
c->offset += readIn;
}
delete[] buff;
fclose(fsrc);
}
}
}
}
void copyDirToZip_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
fs::copyDirToZip(c->src, c->z, c->trimZipPath, c->trimZipPlaces, t);
if(c->cleanup)
{
zipClose(c->z, NULL);
delete c;
}
t->finished = true;
}
void fs::copyDirToZipThreaded(const std::string& src, zipFile dst, bool trimPath, int trimPlaces)
{
fs::copyArgs *send = fs::copyArgsCreate(src, "", "", dst, NULL, true, false, 0);
threads::newThread(copyDirToZip_t, send, fs::fileDrawFunc);
}
void fs::copyZipToDir(unzFile src, const std::string& dst, const std::string& dev, threadInfo *t)
{
fs::copyArgs *c = NULL;
if(t)
c = (fs::copyArgs *)t->argPtr;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
uint64_t journalSize = getJournalSize(utinfo), writeCount = 0;
char filename[FS_MAX_PATH];
uint8_t *buff = new uint8_t[BUFF_SIZE];
int readIn = 0;
unz_file_info64 info;
do
{
unzGetCurrentFileInfo64(src, &info, filename, FS_MAX_PATH, NULL, 0, NULL, 0);
if(unzOpenCurrentFile(src) == UNZ_OK)
{
if(t)
t->status->setStatus("threadStatusDecompressingFile");
if(c)
{
// c->prog->setMax(info.uncompressed_size);
// c->prog->update(0);
c->offset = 0;
}
std::string fullDst = dst + filename;
fs::mkDirRec(fullDst.substr(0, fullDst.find_last_of('/') + 1));
unzThrdArgs unzThrd;
unzThrd.dst = fullDst;
unzThrd.fileSize = info.uncompressed_size;
unzThrd.dev = dev;
unzThrd.writeLimit = (journalSize - 0x100000) < TRANSFER_BUFFER_LIMIT ? (journalSize - 0x100000) : TRANSFER_BUFFER_LIMIT;
Thread writeThread;
threadCreate(&writeThread, writeFileFromZip_t, &unzThrd, NULL, 0x8000, 0x2B, 2);
threadStart(&writeThread);
std::vector<uint8_t> transferBuffer;
uint64_t readCount = 0;
while((readIn = unzReadCurrentFile(src, buff, BUFF_SIZE)) > 0)
{
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
readCount += readIn;
if(c)
c->offset += readIn;
if(transferBuffer.size() >= unzThrd.writeLimit || readCount == info.uncompressed_size)
{
std::unique_lock<std::mutex> buffLock(unzThrd.buffLock);
unzThrd.cond.wait(buffLock, [&unzThrd]{ return unzThrd.bufferIsFull == false; });
unzThrd.sharedBuffer.assign(transferBuffer.begin(), transferBuffer.end());
transferBuffer.clear();
unzThrd.bufferIsFull = true;
unzThrd.cond.notify_one();
}
}
threadWaitForExit(&writeThread);
threadClose(&writeThread);
fs::commitToDevice(dev);
}
}
while(unzGoToNextFile(src) != UNZ_END_OF_LIST_OF_FILE);
delete[] buff;
}
static void copyZipToDir_t(void *a)
{
threadInfo *t = (threadInfo *)a;
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
fs::copyZipToDir(c->unz, c->dst, c->dev, t);
if(c->cleanup)
{
unzClose(c->unz);
delete c;
}
t->finished = true;
}
void fs::copyZipToDirThreaded(unzFile src, const std::string& dst, const std::string& dev)
{
fs::copyArgs *send = fs::copyArgsCreate("", dst, dev, NULL, src, true, false, 0);
threads::newThread(copyZipToDir_t, send, fs::fileDrawFunc);
}
uint64_t fs::getZipTotalSize(unzFile unz)
{
uint64_t ret = 0;
if(unzGoToFirstFile(unz) == UNZ_OK)
{
unz_file_info64 finfo;
char filename[FS_MAX_PATH];
do
{
unzGetCurrentFileInfo64(unz, &finfo, filename, FS_MAX_PATH, NULL, 0, NULL, 0);
ret += finfo.uncompressed_size;
} while(unzGoToNextFile(unz) != UNZ_END_OF_LIST_OF_FILE);
unzGoToFirstFile(unz);
}
return ret;
}
bool fs::zipNotEmpty(unzFile unz)
{
return unzGoToFirstFile(unz) == UNZ_OK;
}

543
source/ldn.cpp Normal file
View File

@@ -0,0 +1,543 @@
#include "ldn.h"
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include "fs.h"
#include "file.h"
#include "util.h"
static const u8 sec_data[0x10] = {0x04, 0xb9, 0x9d, 0x4d, 0x58, 0xbc,
0x65, 0xe1, 0x77, 0x13, 0xc2, 0xb8,
0xd1, 0xb8, 0xec, 0xf6};
Result create_network(const LdnSecurityConfig *sec_config,
const LdnUserConfig *user_config,
const LdnNetworkConfig *netconfig, const void *advert,
size_t advert_size) {
Result rc = 0;
rc = ldnOpenAccessPoint();
if (R_FAILED(rc)) {
return rc;
}
rc = ldnCreateNetwork(sec_config, user_config, netconfig);
if (R_FAILED(rc)) {
goto error_close;
}
rc = ldnSetAdvertiseData(advert, advert_size);
if (R_FAILED(rc))
goto error_close;
return rc;
error_close:
ldnCloseAccessPoint();
return rc;
}
Result connect_network(const LdnScanFilter *filter,
const LdnSecurityConfig *sec_config,
const LdnUserConfig *user_config, const void *advert,
size_t advert_size) {
Result rc = 0;
s32 total_out = 0;
LdnNetworkInfo netinfo_list[0x18];
rc = ldnOpenStation();
if (R_SUCCEEDED(rc)) {
rc = ldnScan(0, filter, netinfo_list, 0x18, &total_out);
}
// In an actual app you'd display the output netinfo_list and let the user
// select which one to connect to, however in this example we'll just
// connect to the first one.
if (R_SUCCEEDED(rc) && !total_out) {
rc = MAKERESULT(Module_Libnx, LibnxError_NotFound);
}
if (R_SUCCEEDED(rc)) { // Handle this / parse it with any method you want.
if (netinfo_list[0].advertise_data_size != advert_size ||
memcmp(netinfo_list[0].advertise_data, advert, advert_size) != 0) {
rc = MAKERESULT(Module_Libnx, LibnxError_NotFound);
}
}
if (R_SUCCEEDED(rc)) {
rc = ldnConnect(sec_config, user_config, 0, 0, &netinfo_list[0]);
}
if (R_FAILED(rc))
ldnCloseStation();
return rc;
}
static void leave_network(void) {
Result rc = 0;
LdnState state;
rc = ldnGetState(&state);
if (R_SUCCEEDED(rc)) {
if (state == LdnState_AccessPointOpened ||
state == LdnState_AccessPointCreated) {
if (state == LdnState_AccessPointCreated) {
rc = ldnDestroyNetwork();
}
rc = ldnCloseAccessPoint();
}
if (state == LdnState_StationOpened || state == LdnState_StationConnected) {
if (state == LdnState_StationConnected) {
rc = ldnDisconnect();
}
rc = ldnCloseStation();
}
}
}
void LDN::destroyLDN() {
leave_network();
ldnExit();
}
LDN::LDNCommunicate* LDN::createCommunicate(void)
{
LDN::LDNCommunicate* comm = new LDN::LDNCommunicate;
comm->serverFD = -1;
comm->commFD = -1;
return comm;
}
void LDN::destroyCommunicate(LDNCommunicate *comm) {
if (comm->commFD >= 0)
close(comm->commFD);
if (comm->serverFD >= 0)
close(comm->serverFD);
delete comm;
}
Result LDN::createLDNServer(LDNCommunicate *comm) {
Result rc;
LdnSecurityConfig sec_config = {0};
LdnUserConfig user_config = {0};
LdnNetworkConfig netconfig = {0};
LdnState state;
data::user *user = data::getCurrentUser();
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
/*
* Use Title ID to the advert, so if advert not match
* LDN connect between client and server will bind fail
*/
std::string advertStr = std::to_string(utinfo->tid);
int serverSocket;
// send nickname to make sure
strncpy(user_config.nickname, user->getUsername().c_str(), 0x20 - 1);
netconfig.local_communication_id = -1;
netconfig.participant_max = 2; // Adjust as needed.
sec_config.type = 1;
sec_config.data_size = sizeof(sec_data);
memcpy(sec_config.data, sec_data, sizeof(sec_data));
rc = ldnInitialize(LdnServiceType_User);
if (R_FAILED(rc)) {
goto out;
}
rc = ldnGetState(&state);
if (!R_SUCCEEDED(rc) || state != LdnState_Initialized) {
goto ldn_out;
}
rc = create_network(&sec_config, &user_config, &netconfig, advertStr.c_str(),
advertStr.length());
if (R_FAILED(rc)) {
goto ldn_out;
}
return rc;
ldn_out:
ldnExit();
out:
return rc;
}
Result LDN::createLDNClient(LDNCommunicate *comm) {
Result rc;
LdnUserConfig user_config = {0};
LdnSecurityConfig sec_config = {0};
LdnNetworkConfig netconfig = {0};
LdnScanFilter filter = {0};
LdnState state;
data::userTitleInfo *utinfo = data::getCurrentUserTitleInfo();
/*
* Use Title ID to the advert, so if advert not match
* LDN connect between client and server will bind fail
*/
std::string advertStr = std::to_string(utinfo->tid);
data::user *user = data::getCurrentUser();
strncpy(user_config.nickname, user->getUsername().c_str(), 0x20 - 1);
rc = ldnInitialize(LdnServiceType_User);
if (R_FAILED(rc)) {
goto out;
}
netconfig.local_communication_id = -1;
netconfig.participant_max = 2; // Adjust as needed.
sec_config.type = 1;
sec_config.data_size = sizeof(sec_data);
memcpy(sec_config.data, sec_data, sizeof(sec_data));
filter.local_communication_id = -1;
filter.userdata_filter = netconfig.userdata_filter;
filter.flags =
LdnScanFilterFlags_LocalCommunicationId | LdnScanFilterFlags_UserData;
rc = ldnGetState(&state);
if (!R_SUCCEEDED(rc) || state != LdnState_Initialized) {
goto ldnOut;
}
rc = connect_network(&filter, &sec_config, &user_config, advertStr.c_str(),
advertStr.length());
out:
return rc;
ldnOut:
ldnExit();
return rc;
}
int LDN::bindClient(int serverFD) {
struct sockaddr_in clientAddr;
socklen_t length = sizeof(clientAddr);
int cSocket =
accept(serverFD, (struct sockaddr *)&clientAddr, &length);
if (cSocket < 0) {
return -1;
}
return cSocket;
}
int LDN::bindServer(struct sockaddr_in *serverAddr) {
/// sockfd
int sock_cli = socket(AF_INET, SOCK_STREAM, 0);
int ret;
if ((ret = connect(sock_cli, (struct sockaddr *)serverAddr,
sizeof(*serverAddr))) < 0) {
close(sock_cli);
return -1;
}
return sock_cli;
}
static void reciveBuf(char *buf, ssize_t size, int socketfd) {
ssize_t offset = 0, ssize;
while (offset < size) {
ssize =
size - offset < SOCKET_BUFFER_SIZE ? size - offset : SOCKET_BUFFER_SIZE;
ssize_t done = recv(socketfd, buf + offset, ssize, 0);
if (done == -1) {
break;
}
offset += done;
}
}
static void sendBuf(const char *buf, ssize_t size, int socketfd) {
ssize_t offset = 0, ssize;
while (offset < size) {
ssize =
size - offset < SOCKET_BUFFER_SIZE ? size - offset : SOCKET_BUFFER_SIZE;
ssize_t done = send(socketfd, buf + offset, ssize, 0);
if (done == -1) {
break;
}
offset += done;
}
}
bool LDN::waitForOK(int socketfd) {
commMeta meta;
reciveBuf((char *)&meta, sizeof(meta), socketfd);
if (meta.type == UPDATE_OK)
return true;
return false;
}
bool LDN::waitForDONE(int socketfd) {
commMeta meta;
reciveBuf((char *)&meta, sizeof(meta), socketfd);
if (meta.type == UPDATE_DONE)
return true;
return false;
}
void LDN::sendOK(int socket_fd) {
commMeta meta;
meta.type = UPDATE_OK;
sendBuf((char *)&meta, sizeof(meta), socket_fd);
}
void LDN::sendDONE(int socket_fd) {
commMeta meta;
meta.type = UPDATE_DONE;
sendBuf((char *)&meta, sizeof(meta), socket_fd);
}
void LDN::sendAbort(int socket_fd) {
commMeta meta;
meta.type = UPDATE_ABORT;
sendBuf((char *)&meta, sizeof(meta), socket_fd);
}
void LDN::reciveMeta(commMeta *meta, int socketfd) {
bzero(meta, sizeof(commMeta));
reciveBuf((char *)meta, sizeof(commMeta), socketfd);
sendOK(socketfd);
}
void LDN::sendMeta(commMeta *meta, int socketfd) {
sendBuf((char *)meta, sizeof(commMeta), socketfd);
waitForOK(socketfd);
}
static void copySaveFileToRemote_t(void *a) {
LDN::LDNfcopyArgs *in = (LDN::LDNfcopyArgs *)a;
LDN::LDNCommunicate *comm = in->comm;
size_t written = 0;
std::vector<uint8_t> localBuffer;
int fSocket = LDN::bindClient(comm->serverFD);
if (fSocket < 0)
return;
while (written < in->filesize) {
std::unique_lock<std::mutex> buffLock(in->bufferLock);
in->cond.wait(buffLock, [in] { return in->bufferIsFull; });
localBuffer.clear();
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
in->sharedBuffer.clear();
in->bufferIsFull = false;
buffLock.unlock();
in->cond.notify_one();
written += send(fSocket, localBuffer.data(), localBuffer.size(), 0);
}
close(fSocket);
}
void LDN::copySaveFileToRemote(const std::string &local, threadInfo *t) {
fs::copyArgs *c = NULL;
c = (fs::copyArgs *)t->argPtr;
LDN::LDNCommunicate *comm = c->comm;
commMeta cm;
std::string zipPath = fs::getWorkDir() + "tempSave.zip";
//Trim this to unzip direct in direct sv:/
int zipTrim = util::getTotalPlacesInPath("sv:/");
t->status->setStatus("LDNStatus");
zipFile zip = zipOpen64(zipPath.c_str(), 0);
fs::copyDirToZip(local, zip, true, zipTrim, t);
zipClose(zip, NULL);
size_t filesize = fs::fsize(zipPath);
t->status->setStatus("LDNStatus");
c->offset = 0;
FILE *fsrc = fopen(zipPath.c_str(), "rb");
if(!fsrc)
{
sendAbort(comm->commFD);
fclose(fsrc);
return;
}
cm.type = UPDATE_FILE;
cm.fsz = filesize;
sendMeta(&cm, comm->commFD);
LDNfcopyArgs thrdArgs;
thrdArgs.comm = comm;
thrdArgs.filesize = filesize;
uint8_t *buff = new uint8_t[BUFF_SIZE];
std::vector<uint8_t> transferBuffer;
Thread writeThread;
threadCreate(&writeThread, copySaveFileToRemote_t, &thrdArgs, NULL,
0x40000, 0x2E, 2);
threadStart(&writeThread);
size_t readIn = 0;
uint64_t readCount = 0;
while ((readIn = fread(buff, 1, BUFF_SIZE, fsrc)) > 0) {
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
readCount += readIn;
if (c)
c->offset = readCount;
if (transferBuffer.size() >= TRANSFER_BUFFER_LIMIT ||
readCount == filesize) {
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
thrdArgs.cond.wait(
buffLock, [&thrdArgs] { return thrdArgs.bufferIsFull == false; });
thrdArgs.sharedBuffer.assign(transferBuffer.begin(),
transferBuffer.end());
transferBuffer.clear();
thrdArgs.bufferIsFull = true;
buffLock.unlock();
thrdArgs.cond.notify_one();
}
}
threadWaitForExit(&writeThread);
threadClose(&writeThread);
fclose(fsrc);
fs::delfile(zipPath);
delete[] buff;
t->status->setStatus("LDNStatus");
// wait for client recived sure
// otherwise, may lost package
LDN::sendDONE(comm->commFD);
LDN::waitForDONE(comm->commFD);
}
static void copyRemoteFileWrite_t(void *a) {
LDN::LDNfcopyArgs *in = (LDN::LDNfcopyArgs *)a;
size_t written = 0;
std::vector<uint8_t> localBuffer;
FILE *out = fopen(in->dst.c_str(), "wb");
while (written < in->filesize) {
std::unique_lock<std::mutex> buffLock(in->bufferLock);
in->cond.wait(buffLock, [in] { return in->bufferIsFull; });
localBuffer.clear();
localBuffer.assign(in->sharedBuffer.begin(), in->sharedBuffer.end());
in->sharedBuffer.clear();
in->bufferIsFull = false;
buffLock.unlock();
in->cond.notify_one();
written += fwrite(localBuffer.data(), 1, localBuffer.size(), out);
}
fclose(out);
}
static void copyRemoteSaveFileCommit(LDN::commMeta *meta, threadInfo *t) {
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
LDN::LDNCommunicate *comm = c->comm;
std::string fullPath = fs::getWorkDir() + "tempSave.zip";
size_t filesize = meta->fsz;
c->offset = 0;
int fSocket = LDN::bindServer(&comm->serverAddr);
if (fSocket < 0)
return;
t->status->setStatus("LDNStatus");
LDN::LDNfcopyArgs thrdArgs;
thrdArgs.dst = fullPath;
thrdArgs.filesize = filesize;
uint8_t *buff = new uint8_t[BUFF_SIZE];
std::vector<uint8_t> transferBuffer;
Thread writeThread;
threadCreate(&writeThread, copyRemoteFileWrite_t, &thrdArgs, NULL, 0x40000,
0x2E, 2);
threadStart(&writeThread);
size_t readIn = 0;
uint64_t readCount = 0;
while ((readIn = recv(fSocket, buff, BUFF_SIZE, 0)) > 0) {
transferBuffer.insert(transferBuffer.end(), buff, buff + readIn);
readCount += readIn;
if (c)
c->offset = readCount;
if (transferBuffer.size() >= TRANSFER_BUFFER_LIMIT ||
readCount == filesize) {
std::unique_lock<std::mutex> buffLock(thrdArgs.bufferLock);
thrdArgs.cond.wait(
buffLock, [&thrdArgs] { return thrdArgs.bufferIsFull == false; });
thrdArgs.sharedBuffer.assign(transferBuffer.begin(),
transferBuffer.end());
transferBuffer.clear();
thrdArgs.bufferIsFull = true;
buffLock.unlock();
thrdArgs.cond.notify_one();
}
}
threadWaitForExit(&writeThread);
threadClose(&writeThread);
t->status->setStatus("LDNStatus");
unzFile unz = unzOpen64(fullPath.c_str());
if(unz && fs::zipNotEmpty(unz)) {
t->status->setStatus("threadStatusCalculatingSaveSize");
uint64_t saveSize = fs::getZipTotalSize(unz);
int64_t availSize = 0;
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::delDir("sv:/");
fs::commitToDevice("sv");
// fs::copyZipToDir(unz, "sv:/", "sv", t);
}
// if (unz)
// unzClose(unz);
close(fSocket);
delete[] buff;
fs::delfile(fullPath);
LDN::waitForDONE(comm->commFD);
LDN::sendDONE(comm->commFD);
}
void LDN::copyRemoteSaveFile(threadInfo *t) {
fs::copyArgs *c = (fs::copyArgs *)t->argPtr;
LDN::LDNCommunicate *comm = (LDN::LDNCommunicate *)c->comm;
LDN::commMeta meta;
t->status->setStatus("LDNStatus");
reciveMeta(&meta, comm->commFD);
if (meta.type == UPDATE_ABORT)
return;
if (meta.type == UPDATE_FILE) {
copyRemoteSaveFileCommit(&meta, t);
}
}

69
source/threads.cpp Normal file
View File

@@ -0,0 +1,69 @@
#include <string>
#include <unordered_map>
#include <cstdio>
#include <cstring>
#include <vector>
#include <sys/stat.h>
#include <switch.h>
#include "threads.h"
#include "util.h"
static threads::threadProcMngr *threadMngr;
threads::threadProcMngr::~threadProcMngr()
{
for(threadInfo *t : threads)
{
threadWaitForExit(&t->thrd);
threadClose(&t->thrd);
delete t->status;
delete t;
}
}
threadInfo *threads::threadProcMngr::newThread(ThreadFunc func, void *args, funcPtr _drawfunc)
{
threadInfo *t = new threadInfo;
t->status = new threadStatus;
t->running = false;
t->finished = false;
t->thrdFunc = func;
t->drawFunc = _drawfunc;
t->argPtr = args;
mutexLock(&threadLock);
threads.push_back(t);
mutexUnlock(&threadLock);
return threads[threads.size() - 1];
}
void threads::threadProcMngr::update()
{
if(!threads.empty())
{
Result res = 0;
threadInfo *t = threads[0];
if(!t->running && R_SUCCEEDED((res = threadCreate(&t->thrd, t->thrdFunc, t, NULL, 0x80000, 0x2B, 1))))
{
threadStart(&t->thrd);
t->running = true;
}
else if(!t->running && R_FAILED(res))//Should kill the thread that failed.
t->finished = true;
else if(t->finished)
{
threadWaitForExit(&t->thrd);
threadClose(&t->thrd);
delete t->status;
delete t;
mutexLock(&threadLock);
threads.erase(threads.begin());
mutexUnlock(&threadLock);
}
}
}
threadInfo *threads::newThread(ThreadFunc func, void *args, funcPtr _drawFunc)
{
return threadMngr->newThread(func, args, _drawFunc);
}

351
source/util.cpp Normal file
View File

@@ -0,0 +1,351 @@
#include <string>
#include <cstdio>
#include <ctime>
#include <sys/stat.h>
#include <json-c/json.h>
#include "fs/file.h"
#include "data.h"
#include "util.h"
#include "type.h"
static const uint32_t verboten[] = { L',', L'/', L'\\', L'<', L'>', L':', L'"', L'|', L'?', L'*', L'', L'©', L'®'};
static bool isVerboten(const uint32_t& t)
{
for(unsigned i = 0; i < 13; i++)
{
if(t == verboten[i])
return true;
}
return false;
}
void util::replaceStr(std::string& _str, const std::string& _find, const std::string& _rep)
{
size_t pos = 0;
while((pos = _str.find(_find)) != _str.npos)
_str.replace(pos, _find.length(), _rep);
}
//Used to split version tag git
static void getVersionFromTag(const std::string& tag, unsigned& _year, unsigned& _month, unsigned& _day)
{
_month = strtoul(tag.substr(0, 2).c_str(), NULL, 10);
_day = strtoul(tag.substr(3, 5).c_str(), NULL, 10);
_year = strtoul(tag.substr(6, 10).c_str(), NULL, 10);
}
//Missing swkbd config funcs for now
typedef enum
{
SwkbdPosStart = 0,
SwkbdPosEnd = 1
} SwkbdInitPos;
typedef struct
{
uint16_t read[0x32 / sizeof(uint16_t)];
uint16_t word[0x32 / sizeof(uint16_t)];
} dictWord;
void swkbdDictWordCreate(dictWord *w, const char *read, const char *word)
{
memset(w, 0, sizeof(*w));
utf8_to_utf16(w->read, (uint8_t *)read, (sizeof(w->read) / sizeof(uint16_t)) - 1);
utf8_to_utf16(w->word, (uint8_t *)word, (sizeof(w->word) / sizeof(uint16_t)) - 1);
}
uint32_t replaceChar(uint32_t c)
{
switch(c)
{
case L'é':
return 'e';
break;
}
return c;
}
static inline void replaceCharCStr(char *_s, char _find, char _rep)
{
size_t strLength = strlen(_s);
for(unsigned i = 0; i < strLength; i++)
{
if(_s[i] == _find)
_s[i] = _rep;
}
}
std::string util::getDateTime(int fmt)
{
char ret[128];
time_t raw;
time(&raw);
tm *Time = localtime(&raw);
switch(fmt)
{
case DATE_FMT_YMD:
sprintf(ret, "%04d.%02d.%02d @ %02d.%02d.%02d", Time->tm_year + 1900, Time->tm_mon + 1, Time->tm_mday, Time->tm_hour, Time->tm_min, Time->tm_sec);
break;
case DATE_FMT_YDM:
sprintf(ret, "%04d.%02d.%02d @ %02d.%02d.%02d", Time->tm_year + 1900, Time->tm_mday, Time->tm_mon + 1, Time->tm_hour, Time->tm_min, Time->tm_sec);
break;
case DATE_FMT_HOYSTE:
sprintf(ret, "%02d.%02d.%04d", Time->tm_mday, Time->tm_mon + 1, Time->tm_year + 1900);
break;
case DATE_FMT_JHK:
sprintf(ret, "%04d%02d%02d_%02d%02d", Time->tm_year + 1900, Time->tm_mon + 1, Time->tm_mday, Time->tm_hour, Time->tm_min);
break;
case DATE_FMT_ASC:
strcpy(ret, asctime(Time));
replaceCharCStr(ret, ':', '_');
replaceCharCStr(ret, '\n', 0x00);
break;
}
return std::string(ret);
}
//void util::copyDirListToMenu(const fs::dirList& d, ui::menu& m)
//{
// m.reset();
// m.addOpt(NULL, ".");
// m.addOpt(NULL, "..");
// for(unsigned i = 0; i < d.getCount(); i++)
// {
// if(d.isDir(i))
// m.addOpt(NULL, "D " + d.getItem(i));
// else
// m.addOpt(NULL, "F " + d.getItem(i));
// }
//}
void util::removeLastFolderFromString(std::string& _path)
{
unsigned last = _path.find_last_of('/', _path.length() - 2);
_path.erase(last + 1, _path.length());
}
size_t util::getTotalPlacesInPath(const std::string& _path)
{
//Skip device
size_t pos = _path.find_first_of('/'), ret = 0;
while((pos = _path.find_first_of('/', ++pos)) != _path.npos)
++ret;
return ret;
}
void util::trimPath(std::string& _path, uint8_t _places)
{
size_t pos = _path.find_first_of('/');
for(int i = 0; i < _places; i++)
pos = _path.find_first_of('/', ++pos);
_path = _path.substr(++pos, _path.npos);
}
std::string util::safeString(const std::string& s)
{
std::string ret = "";
for(unsigned i = 0; i < s.length(); )
{
uint32_t tmpChr = 0;
ssize_t untCnt = decode_utf8(&tmpChr, (uint8_t *)&s.data()[i]);
i += untCnt;
tmpChr = replaceChar(tmpChr);
if(isVerboten(tmpChr))
ret += ' ';
else if(!isASCII(tmpChr))
return ""; //return empty string so titledata::init defaults to titleID
else
ret += (char)tmpChr;
}
//Check for spaces at end
while(ret[ret.length() - 1] == ' ' || ret[ret.length() - 1] == '.')
ret.erase(ret.length() - 1, 1);
return ret;
}
static inline std::string getTimeString(const uint32_t& _h, const uint32_t& _m)
{
char tmp[32];
sprintf(tmp, "%02d:%02d", _h, _m);
return std::string(tmp);
}
std::string util::getStringInput(SwkbdType _type, const std::string& def, const std::string& head, size_t maxLength, unsigned dictCnt, const std::string dictWords[])
{
SwkbdConfig swkbd;
swkbdCreate(&swkbd, dictCnt);
swkbdConfigSetBlurBackground(&swkbd, true);
swkbdConfigSetInitialText(&swkbd, def.c_str());
swkbdConfigSetHeaderText(&swkbd, head.c_str());
swkbdConfigSetGuideText(&swkbd, head.c_str());
swkbdConfigSetInitialCursorPos(&swkbd, SwkbdPosEnd);
swkbdConfigSetType(&swkbd, _type);
swkbdConfigSetStringLenMax(&swkbd, maxLength);
swkbdConfigSetKeySetDisableBitmask(&swkbd, SwkbdKeyDisableBitmask_Backslash | SwkbdKeyDisableBitmask_Percent);
swkbdConfigSetDicFlag(&swkbd, 1);
if(dictCnt > 0)
{
dictWord words[dictCnt];
for(unsigned i = 0; i < dictCnt; i++)
swkbdDictWordCreate(&words[i], dictWords[i].c_str(), dictWords[i].c_str());
swkbdConfigSetDictionary(&swkbd, (SwkbdDictWord *)words, dictCnt);
}
char out[maxLength + 1];
memset(out, 0, maxLength + 1);
swkbdShow(&swkbd, out, maxLength + 1);
swkbdClose(&swkbd);
return std::string(out);
}
std::string util::getExtensionFromString(const std::string& get)
{
size_t ext = get.find_last_of('.');
if(ext != get.npos)
return get.substr(ext + 1, get.npos);
else
return "";
}
std::string util::getFilenameFromPath(const std::string& get)
{
size_t nameStart = get.find_last_of('/');
if(nameStart != get.npos)
return get.substr(nameStart + 1, get.npos);
else
return "";
}
std::string util::generateAbbrev(const uint64_t& tid)
{
data::titleInfo *tmp = data::getTitleInfoByTID(tid);
size_t titleLength = tmp->safeTitle.length();
char temp[titleLength + 1];
memset(temp, 0, titleLength + 1);
memcpy(temp, tmp->safeTitle.c_str(), titleLength);
std::string ret;
char *tok = strtok(temp, " ");
while(tok)
{
if(isASCII(tok[0]))
ret += tok[0];
tok = strtok(NULL, " ");
}
return ret;
}
void util::stripChar(char _c, std::string& _s)
{
size_t pos = 0;
while((pos = _s.find(_c)) != _s.npos)
_s.erase(pos, 1);
}
void util::replaceButtonsInString(std::string& rep)
{
replaceStr(rep, "[A]", "\ue0e0");
replaceStr(rep, "[B]", "\ue0e1");
replaceStr(rep, "[X]", "\ue0e2");
replaceStr(rep, "[Y]", "\ue0e3");
replaceStr(rep, "[L]", "\ue0e4");
replaceStr(rep, "[R]", "\ue0e5");
replaceStr(rep, "[ZL]", "\ue0e6");
replaceStr(rep, "[ZR]", "\ue0e7");
replaceStr(rep, "[SL]", "\ue0e8");
replaceStr(rep, "[SR]", "\ue0e9");
replaceStr(rep, "[DPAD]", "\ue0ea");
replaceStr(rep, "[DUP]", "\ue0eb");
replaceStr(rep, "[DDOWN]", "\ue0ec");
replaceStr(rep, "[DLEFT]", "\ue0ed");
replaceStr(rep, "[DRIGHT]", "\ue0ee");
replaceStr(rep, "[+]", "\ue0ef");
replaceStr(rep, "[-]", "\ue0f0");
}
void util::sysBoost()
{
if(R_FAILED(clkrstInitialize()))
return;
ClkrstSession cpu, gpu, ram;
clkrstOpenSession(&cpu, PcvModuleId_CpuBus, 3);
clkrstOpenSession(&gpu, PcvModuleId_GPU, 3);
clkrstOpenSession(&ram, PcvModuleId_EMC, 3);
clkrstSetClockRate(&cpu, util::CPU_SPEED_1785MHz);
clkrstSetClockRate(&gpu, util::GPU_SPEED_76MHz);
clkrstSetClockRate(&ram, util::RAM_SPEED_1600MHz);
clkrstCloseSession(&cpu);
clkrstCloseSession(&gpu);
clkrstCloseSession(&ram);
clkrstExit();
}
void util::sysNormal()
{
if(R_FAILED(clkrstInitialize()))
return;
ClkrstSession cpu, gpu, ram;
clkrstOpenSession(&cpu, PcvModuleId_CpuBus, 3);
clkrstOpenSession(&gpu, PcvModuleId_GPU, 3);
clkrstOpenSession(&ram, PcvModuleId_EMC, 3);
clkrstSetClockRate(&cpu, util::CPU_SPEED_1020MHz);
clkrstSetClockRate(&gpu, util::GPU_SPEED_76MHz);
clkrstSetClockRate(&ram, util::RAM_SPEED_1331MHz);
clkrstCloseSession(&cpu);
clkrstCloseSession(&gpu);
clkrstCloseSession(&ram);
clkrstExit();
}
std::string util::getSizeString(const uint64_t& _size)
{
char sizeStr[32];
if(_size >= 0x40000000)
sprintf(sizeStr, "%.2fGB", (float)_size / 1024.0f / 1024.0f / 1024.0f);
else if(_size >= 0x100000)
sprintf(sizeStr, "%.2fMB", (float)_size / 1024.0f / 1024.0f);
else if(_size >= 0x400)
sprintf(sizeStr, "%.2fKB", (float)_size / 1024.0f);
else
sprintf(sizeStr, "%lu Bytes", _size);
return std::string(sizeStr);
}
Result util::accountDeleteUser(AccountUid *uid)
{
Service *account = accountGetServiceSession();
struct
{
AccountUid uid;
} in = {*uid};
return serviceDispatchIn(account, 203, in);
}