initial commit
This commit is contained in:
352
source/data.cpp
Normal file
352
source/data.cpp
Normal 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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user