finish refactor, add docs and CI
CI / Build NRO (push) Failing after 3s
CI / Format check (push) Successful in 29s
CI / Layering check (push) Successful in 1s

This commit is contained in:
2026-04-27 01:49:41 +03:00
parent dc65a4c8a9
commit 12b990472d
47 changed files with 1964 additions and 1470 deletions
+89 -112
View File
@@ -24,40 +24,39 @@
* reasonable ways as different from the original version.
*/
#include <nxst/domain/title.hpp>
#include <nxst/app/main.hpp>
#include <nxst/domain/title.hpp>
static std::unordered_map<AccountUid, std::vector<Title>> titles;
static bool s_titlesLoaded = false;
bool areTitlesLoaded(void)
{
bool areTitlesLoaded(void) {
return s_titlesLoaded;
}
void Title::init(u8 saveDataType, u64 id, AccountUid userID, const std::string& name, const std::string& author)
{
mId = id;
mUserId = userID;
void Title::init(u8 saveDataType, u64 id, AccountUid userID, const std::string& name,
const std::string& author) {
mId = id;
mUserId = userID;
mSaveDataType = saveDataType;
mUserName = Account::username(userID);
mAuthor = author;
mName = name;
mSafeName = StringUtils::containsInvalidChar(name) ? StringUtils::format("0x%016llX", mId) : StringUtils::removeForbiddenCharacters(name);
mPath = "sdmc:/switch/NXST/saves/" + StringUtils::format("0x%016llX", mId) + " " + mSafeName;
mUserName = Account::username(userID);
mAuthor = author;
mName = name;
mSafeName = StringUtils::containsInvalidChar(name) ? StringUtils::format("0x%016llX", mId)
: StringUtils::removeForbiddenCharacters(name);
mPath = "sdmc:/switch/NXST/saves/" + StringUtils::format("0x%016llX", mId) + " " + mSafeName;
std::string aname = StringUtils::removeAccents(mName);
size_t pos = aname.rfind(":");
mDisplayName = std::make_pair(aname, "");
size_t pos = aname.rfind(":");
mDisplayName = std::make_pair(aname, "");
if (pos != std::string::npos) {
std::string name1 = aname.substr(0, pos);
std::string name2 = aname.substr(pos + 1);
StringUtils::trim(name1);
StringUtils::trim(name2);
mDisplayName.first = name1;
mDisplayName.first = name1;
mDisplayName.second = name2;
}
else {
} else {
// check for parenthesis
size_t pos1 = aname.rfind("(");
size_t pos2 = aname.rfind(")");
@@ -66,7 +65,7 @@ void Title::init(u8 saveDataType, u64 id, AccountUid userID, const std::string&
std::string name2 = aname.substr(pos1 + 1, pos2 - 1 - pos1);
StringUtils::trim(name1);
StringUtils::trim(name2);
mDisplayName.first = name1;
mDisplayName.first = name1;
mDisplayName.second = name2;
}
}
@@ -74,94 +73,77 @@ void Title::init(u8 saveDataType, u64 id, AccountUid userID, const std::string&
refreshDirectories();
}
u8 Title::saveDataType(void)
{
u8 Title::saveDataType(void) {
return mSaveDataType;
}
u64 Title::id(void)
{
u64 Title::id(void) {
return mId;
}
u64 Title::saveId(void)
{
u64 Title::saveId(void) {
return mSaveId;
}
void Title::saveId(u64 saveId)
{
void Title::saveId(u64 saveId) {
mSaveId = saveId;
}
AccountUid Title::userId(void)
{
AccountUid Title::userId(void) {
return mUserId;
}
std::string Title::userName(void)
{
std::string Title::userName(void) {
return mUserName;
}
std::string Title::author(void)
{
std::string Title::author(void) {
return mAuthor;
}
std::string Title::name(void)
{
std::string Title::name(void) {
return mName;
}
std::pair<std::string, std::string> Title::displayName(void)
{
std::pair<std::string, std::string> Title::displayName(void) {
return mDisplayName;
}
std::string Title::path(void)
{
std::string Title::path(void) {
return mPath;
}
std::string Title::fullPath(size_t index)
{
std::string Title::fullPath(size_t index) {
return mFullSavePaths.at(index);
}
std::vector<std::string> Title::saves()
{
std::vector<std::string> Title::saves() {
return mSaves;
}
u64 Title::playTimeNanoseconds(void)
{
u64 Title::playTimeNanoseconds(void) {
return mPlayTimeNanoseconds;
}
std::string Title::playTime(void)
{
std::string Title::playTime(void) {
const u64 playTimeMinutes = mPlayTimeNanoseconds / 60000000000;
return StringUtils::format("%d", playTimeMinutes / 60) + ":" + StringUtils::format("%02d", playTimeMinutes % 60) + " hours";
return StringUtils::format("%d", playTimeMinutes / 60) + ":" +
StringUtils::format("%02d", playTimeMinutes % 60) + " hours";
}
void Title::playTimeNanoseconds(u64 playTimeNanoseconds)
{
void Title::playTimeNanoseconds(u64 playTimeNanoseconds) {
mPlayTimeNanoseconds = playTimeNanoseconds;
}
u32 Title::lastPlayedTimestamp(void)
{
u32 Title::lastPlayedTimestamp(void) {
return mLastPlayedTimestamp;
}
void Title::lastPlayedTimestamp(u32 lastPlayedTimestamp)
{
void Title::lastPlayedTimestamp(u32 lastPlayedTimestamp) {
mLastPlayedTimestamp = lastPlayedTimestamp;
}
void Title::refreshDirectories(void)
{
void Title::refreshDirectories(void) {
mSaves.clear();
mFullSavePaths.clear();
@@ -178,15 +160,15 @@ void Title::refreshDirectories(void)
std::sort(mFullSavePaths.rbegin(), mFullSavePaths.rend());
mSaves.insert(mSaves.begin(), g_emptySave);
mFullSavePaths.insert(mFullSavePaths.begin(), g_emptySave);
}
else {
Logger::getInstance().log(Logger::ERROR, "Couldn't retrieve the extdata directory list for the title " + name());
} else {
Logger::getInstance().log(Logger::ERROR,
"Couldn't retrieve the extdata directory list for the title " + name());
}
}
void loadTitles(void)
{
if (s_titlesLoaded) return;
void loadTitles(void) {
if (s_titlesLoaded)
return;
s_titlesLoaded = true;
titles.clear();
@@ -194,9 +176,9 @@ void loadTitles(void)
FsSaveDataInfoReader reader;
FsSaveDataInfo info;
s64 total_entries = 0;
size_t outsize = 0;
size_t outsize = 0;
NacpLanguageEntry* nle = NULL;
NacpLanguageEntry* nle = NULL;
NsApplicationControlData* nsacd = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData));
if (nsacd == NULL) {
return;
@@ -216,43 +198,44 @@ void loadTitles(void)
}
if (info.save_data_type == FsSaveDataType_Account) {
u64 tid = info.application_id;
u64 sid = info.save_data_id;
u64 tid = info.application_id;
u64 sid = info.save_data_id;
AccountUid uid = info.uid;
// if (mFilterIds.find(tid) == mFilterIds.end()) {
res = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, nsacd, sizeof(NsApplicationControlData), &outsize);
if (R_SUCCEEDED(res) && !(outsize < sizeof(nsacd->nacp))) {
res = nacpGetLanguageEntry(&nsacd->nacp, &nle);
if (R_SUCCEEDED(res) && nle != NULL) {
Title title;
title.init(info.save_data_type, tid, uid, std::string(nle->name), std::string(nle->author));
title.saveId(sid);
res = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, nsacd,
sizeof(NsApplicationControlData), &outsize);
if (R_SUCCEEDED(res) && !(outsize < sizeof(nsacd->nacp))) {
res = nacpGetLanguageEntry(&nsacd->nacp, &nle);
if (R_SUCCEEDED(res) && nle != NULL) {
Title title;
title.init(info.save_data_type, tid, uid, std::string(nle->name),
std::string(nle->author));
title.saveId(sid);
// load play statistics
PdmPlayStatistics stats;
res = pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(tid, uid, false, &stats);
if (R_SUCCEEDED(res)) {
title.playTimeNanoseconds(stats.playtime);
title.lastPlayedTimestamp(stats.last_timestamp_user);
}
// load play statistics
PdmPlayStatistics stats;
res = pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(tid, uid, false, &stats);
if (R_SUCCEEDED(res)) {
title.playTimeNanoseconds(stats.playtime);
title.lastPlayedTimestamp(stats.last_timestamp_user);
}
// loadIcon(tid, nsacd, outsize - sizeof(nsacd->nacp));
// loadIcon(tid, nsacd, outsize - sizeof(nsacd->nacp));
// check if the vector is already created
std::unordered_map<AccountUid, std::vector<Title>>::iterator it = titles.find(uid);
if (it != titles.end()) {
// found
it->second.push_back(title);
}
else {
// not found, insert into map
std::vector<Title> v;
v.push_back(title);
titles.emplace(uid, v);
}
// check if the vector is already created
std::unordered_map<AccountUid, std::vector<Title>>::iterator it = titles.find(uid);
if (it != titles.end()) {
// found
it->second.push_back(title);
} else {
// not found, insert into map
std::vector<Title> v;
v.push_back(title);
titles.emplace(uid, v);
}
}
nle = NULL;
}
nle = NULL;
// }
}
}
@@ -263,45 +246,40 @@ void loadTitles(void)
sortTitles();
}
void sortTitles(void)
{
void sortTitles(void) {
for (auto& vect : titles) {
std::sort(vect.second.begin(), vect.second.end(), [](Title& l, Title& r) {
switch (g_sortMode) {
case SORT_LAST_PLAYED:
return l.lastPlayedTimestamp() > r.lastPlayedTimestamp();
case SORT_PLAY_TIME:
return l.playTimeNanoseconds() > r.playTimeNanoseconds();
case SORT_ALPHA:
default:
return l.name() < r.name();
case SORT_LAST_PLAYED:
return l.lastPlayedTimestamp() > r.lastPlayedTimestamp();
case SORT_PLAY_TIME:
return l.playTimeNanoseconds() > r.playTimeNanoseconds();
case SORT_ALPHA:
default:
return l.name() < r.name();
}
});
}
}
void rotateSortMode(void)
{
void rotateSortMode(void) {
g_sortMode = static_cast<sort_t>((g_sortMode + 1) % SORT_MODES_COUNT);
sortTitles();
}
void getTitle(Title& dst, AccountUid uid, size_t i)
{
void getTitle(Title& dst, AccountUid uid, size_t i) {
std::unordered_map<AccountUid, std::vector<Title>>::iterator it = titles.find(uid);
if (it != titles.end() && i < getTitleCount(uid)) {
dst = it->second.at(i);
}
}
size_t getTitleCount(AccountUid uid)
{
size_t getTitleCount(AccountUid uid) {
std::unordered_map<AccountUid, std::vector<Title>>::iterator it = titles.find(uid);
return it != titles.end() ? it->second.size() : 0;
}
void refreshDirectories(u64 id)
{
void refreshDirectories(u64 id) {
for (auto& pair : titles) {
for (size_t i = 0; i < pair.second.size(); i++) {
if (pair.second.at(i).id() == id) {
@@ -311,8 +289,7 @@ void refreshDirectories(u64 id)
}
}
std::unordered_map<std::string, std::string> getCompleteTitleList(void)
{
std::unordered_map<std::string, std::string> getCompleteTitleList(void) {
std::unordered_map<std::string, std::string> map;
for (const auto& pair : titles) {
for (auto value : pair.second) {