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

This commit is contained in:
2026-04-27 01:49:41 +03:00
parent dc65a4c8a9
commit b2de532bce
47 changed files with 1964 additions and 1470 deletions
+14 -11
View File
@@ -1,9 +1,12 @@
#include <nxst/app/main_application.hpp>
#include <nxst/domain/util.hpp>
#include <nxst/app/main.hpp>
#include <unistd.h>
namespace ui { extern MainApplication* mainApp; }
#include <nxst/app/main.hpp>
#include <nxst/app/main_application.hpp>
#include <nxst/domain/util.hpp>
namespace ui {
extern MainApplication* mainApp;
}
static int nxlink_sock = -1;
@@ -25,9 +28,9 @@ extern "C" void userAppExit() {
if (ui::mainApp) {
ui::mainApp->transfer.cancelReceive();
ui::mainApp->transfer.cancelSend();
for (int i = 0; i < 150 &&
(!ui::mainApp->transfer.isReceiveWorkersIdle() ||
!ui::mainApp->transfer.isSendWorkersIdle()); i++) {
for (int i = 0; i < 150 && (!ui::mainApp->transfer.isReceiveWorkersIdle() ||
!ui::mainApp->transfer.isSendWorkersIdle());
i++) {
usleep(10000);
}
}
@@ -57,8 +60,8 @@ int main() {
// 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);
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();
@@ -68,7 +71,7 @@ int main() {
renderer_opts.SetExtraDefaultFontSize(theme::type::Title);
renderer_opts.SetExtraDefaultFontSize(theme::type::Display);
auto renderer = pu::ui::render::Renderer::New(renderer_opts);
auto renderer = pu::ui::render::Renderer::New(renderer_opts);
// Create our main application from the renderer
auto main = ui::MainApplication::New(renderer);
@@ -78,5 +81,5 @@ int main() {
main->Show();
servicesExit();
return 0;
return 0;
}
+17 -14
View File
@@ -1,21 +1,24 @@
#include <string>
#include <switch.h>
#include <switch/services/hid.h>
#include <vector>
#include <switch.h>
#include <nxst/app/main_application.hpp>
namespace ui {
MainApplication *mainApp;
MainApplication* mainApp;
void MainApplication::OnLoad() {
mainApp = this;
this->users_layout = UsersLayout::New();
this->titles_layout = TitlesLayout::New();
this->users_layout->SetOnInput(
std::bind(&UsersLayout::onInput, this->users_layout, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4));
this->titles_layout->SetOnInput(std::bind(&TitlesLayout::onInput, this->titles_layout, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4));
this->LoadLayout(this->users_layout);
}
}
void MainApplication::OnLoad() {
mainApp = this;
this->users_layout = UsersLayout::New();
this->titles_layout = TitlesLayout::New();
this->users_layout->SetOnInput(std::bind(&UsersLayout::onInput, this->users_layout, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3,
std::placeholders::_4));
this->titles_layout->SetOnInput(std::bind(&TitlesLayout::onInput, this->titles_layout,
std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4));
this->LoadLayout(this->users_layout);
}
} // namespace ui
+25 -26
View File
@@ -24,16 +24,17 @@
* reasonable ways as different from the original version.
*/
#include <nxst/domain/account.hpp>
#include <sys/stat.h>
#include <cstdio>
#include <sys/stat.h>
#include <nxst/domain/account.hpp>
static std::map<AccountUid, User> mUsers;
Result Account::init(void)
{
Result Account::init(void) {
Result res = accountInitialize(AccountServiceType_Application);
if (R_FAILED(res)) return res;
if (R_FAILED(res))
return res;
AccountUid uids[8];
s32 count = 0;
@@ -44,13 +45,11 @@ Result Account::init(void)
return 0;
}
void Account::exit(void)
{
void Account::exit(void) {
accountExit();
}
std::vector<AccountUid> Account::ids(void)
{
std::vector<AccountUid> Account::ids(void) {
std::vector<AccountUid> v;
for (auto& value : mUsers) {
v.push_back(value.second.id);
@@ -58,8 +57,7 @@ std::vector<AccountUid> Account::ids(void)
return v;
}
static User getUser(AccountUid id)
{
static User getUser(AccountUid id) {
User user{id, ""};
AccountProfile profile;
AccountProfileBase profilebase;
@@ -74,8 +72,7 @@ static User getUser(AccountUid id)
return user;
}
std::string Account::username(AccountUid id)
{
std::string Account::username(AccountUid id) {
std::map<AccountUid, User>::const_iterator got = mUsers.find(id);
if (got == mUsers.end()) {
User user = getUser(id);
@@ -86,21 +83,21 @@ std::string Account::username(AccountUid id)
return got->second.name;
}
std::string Account::iconPath(AccountUid id)
{
std::string Account::iconPath(AccountUid id) {
char path[128];
snprintf(path, sizeof(path), "sdmc:/switch/NXST/cache/%016lX%016lX.jpg",
id.uid[0], id.uid[1]);
snprintf(path, sizeof(path), "sdmc:/switch/NXST/cache/%016lX%016lX.jpg", id.uid[0], id.uid[1]);
struct stat st;
if (stat(path, &st) == 0 && st.st_size > 0) return std::string(path);
if (stat(path, &st) == 0 && st.st_size > 0)
return std::string(path);
mkdir("sdmc:/switch", 0755);
mkdir("sdmc:/switch/NXST", 0755);
mkdir("sdmc:/switch/NXST/cache", 0755);
AccountProfile profile;
if (R_FAILED(accountGetProfile(&profile, id))) return "";
if (R_FAILED(accountGetProfile(&profile, id)))
return "";
u32 imgSize = 0;
if (R_FAILED(accountProfileGetImageSize(&profile, &imgSize)) || imgSize == 0) {
@@ -112,26 +109,28 @@ std::string Account::iconPath(AccountUid id)
u32 outSize = 0;
Result r = accountProfileLoadImage(&profile, buf.data(), imgSize, &outSize);
accountProfileClose(&profile);
if (R_FAILED(r) || outSize == 0) return "";
if (R_FAILED(r) || outSize == 0)
return "";
FILE* f = fopen(path, "wb");
if (!f) return "";
if (!f)
return "";
fwrite(buf.data(), 1, outSize, f);
fclose(f);
return std::string(path);
}
AccountUid Account::selectAccount(void)
{
AccountUid Account::selectAccount(void) {
LibAppletArgs args;
libappletArgsCreate(&args, 0x10000);
u8 st_in[0xA0] = {0};
u8 st_in[0xA0] = {0};
u8 st_out[0x18] = {0};
size_t repsz;
Result res = libappletLaunch(AppletId_LibraryAppletPlayerSelect, &args, st_in, 0xA0, st_out, 0x18, &repsz);
Result res =
libappletLaunch(AppletId_LibraryAppletPlayerSelect, &args, st_in, 0xA0, st_out, 0x18, &repsz);
if (R_SUCCEEDED(res)) {
u64 lres = *(u64*)st_out;
u64 lres = *(u64*)st_out;
AccountUid uid = *(AccountUid*)&st_out[8];
if (lres == 0)
return uid;
+25 -28
View File
@@ -26,8 +26,7 @@
#include <nxst/domain/common.hpp>
std::string DateTime::timeStr(void)
{
std::string DateTime::timeStr(void) {
time_t unixTime;
struct tm timeStruct;
time(&unixTime);
@@ -35,35 +34,32 @@ std::string DateTime::timeStr(void)
return StringUtils::format("%02i:%02i:%02i", timeStruct.tm_hour, timeStruct.tm_min, timeStruct.tm_sec);
}
std::string DateTime::dateTimeStr(void)
{
std::string DateTime::dateTimeStr(void) {
time_t unixTime;
struct tm timeStruct;
time(&unixTime);
localtime_r(&unixTime, &timeStruct);
return StringUtils::format("%04i%02i%02i-%02i%02i%02i", timeStruct.tm_year + 1900, timeStruct.tm_mon + 1, timeStruct.tm_mday, timeStruct.tm_hour,
timeStruct.tm_min, timeStruct.tm_sec);
return StringUtils::format("%04i%02i%02i-%02i%02i%02i", timeStruct.tm_year + 1900, timeStruct.tm_mon + 1,
timeStruct.tm_mday, timeStruct.tm_hour, timeStruct.tm_min, timeStruct.tm_sec);
}
std::string DateTime::logDateTime(void)
{
std::string DateTime::logDateTime(void) {
time_t unixTime;
struct tm timeStruct;
time(&unixTime);
localtime_r(&unixTime, &timeStruct);
return StringUtils::format("%04i-%02i-%02i %02i:%02i:%02i", timeStruct.tm_year + 1900, timeStruct.tm_mon + 1, timeStruct.tm_mday,
timeStruct.tm_hour, timeStruct.tm_min, timeStruct.tm_sec);
return StringUtils::format("%04i-%02i-%02i %02i:%02i:%02i", timeStruct.tm_year + 1900,
timeStruct.tm_mon + 1, timeStruct.tm_mday, timeStruct.tm_hour,
timeStruct.tm_min, timeStruct.tm_sec);
}
std::string StringUtils::UTF16toUTF8(const std::u16string& src)
{
std::string StringUtils::UTF16toUTF8(const std::u16string& src) {
static std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
std::string dst = convert.to_bytes(src);
return dst;
}
std::string StringUtils::removeForbiddenCharacters(std::string src)
{
std::string StringUtils::removeForbiddenCharacters(std::string src) {
static const std::string illegalChars = ".,!\\/:?*\"<>|";
for (size_t i = 0, sz = src.length(); i < sz; i++) {
if (illegalChars.find(src[i]) != std::string::npos) {
@@ -79,8 +75,7 @@ std::string StringUtils::removeForbiddenCharacters(std::string src)
return src;
}
std::string StringUtils::format(const std::string fmt_str, ...)
{
std::string StringUtils::format(const std::string fmt_str, ...) {
va_list ap;
char* fp = NULL;
va_start(ap, fmt_str);
@@ -90,8 +85,7 @@ std::string StringUtils::format(const std::string fmt_str, ...)
return std::string(formatted.get());
}
bool StringUtils::containsInvalidChar(const std::string& str)
{
bool StringUtils::containsInvalidChar(const std::string& str) {
for (size_t i = 0, sz = str.length(); i < sz; i++) {
if (!isascii(str[i])) {
return true;
@@ -100,24 +94,27 @@ bool StringUtils::containsInvalidChar(const std::string& str)
return false;
}
void StringUtils::ltrim(std::string& s)
{
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); }));
void StringUtils::ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
return !std::isspace(ch);
}));
}
void StringUtils::rtrim(std::string& s)
{
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end());
void StringUtils::rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
[](int ch) {
return !std::isspace(ch);
})
.base(),
s.end());
}
void StringUtils::trim(std::string& s)
{
void StringUtils::trim(std::string& s) {
ltrim(s);
rtrim(s);
}
char* getConsoleIP(void)
{
char* getConsoleIP(void) {
struct in_addr in;
in.s_addr = gethostid();
return inet_ntoa(in);
+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) {
+30 -36
View File
@@ -24,21 +24,19 @@
* reasonable ways as different from the original version.
*/
#include <nxst/app/main.hpp>
#include <nxst/app/main_application.hpp>
#include <nxst/domain/util.hpp>
#include <nxst/infra/sys/logger.hpp>
#include <nxst/app/main_application.hpp>
#include <nxst/app/main.hpp>
void servicesExit(void)
{
void servicesExit(void) {
Logger::getInstance().flush();
Account::exit();
plExit();
romfsExit();
}
Result servicesInit(void)
{
Result servicesInit(void) {
io::createDirectory("sdmc:/switch");
io::createDirectory("sdmc:/switch/NXST");
io::createDirectory("sdmc:/switch/NXST/saves");
@@ -71,30 +69,28 @@ Result servicesInit(void)
if (R_SUCCEEDED(res = hidsysInitialize())) {
g_notificationLedAvailable = true;
}
else {
} else {
Logger::getInstance().log(Logger::INFO, "Notification led not available. Result code 0x{:08X}.", res);
}
Logger::getInstance().log(Logger::INFO, "NXST loading completed!");
return 0;
}
std::u16string StringUtils::UTF8toUTF16(const char* src)
{
std::u16string StringUtils::UTF8toUTF16(const char* src) {
char16_t tmp[256] = {0};
utf8_to_utf16((uint16_t*)tmp, (uint8_t*)src, 256);
return std::u16string(tmp);
}
// https://stackoverflow.com/questions/14094621/change-all-accented-letters-to-normal-letters-in-c
std::string StringUtils::removeAccents(std::string str)
{
std::string StringUtils::removeAccents(std::string str) {
std::u16string src = UTF8toUTF16(str.c_str());
const std::u16string illegal = UTF8toUTF16("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüūýþÿ");
const std::u16string fixed = UTF8toUTF16("AAAAAAECEEEEIIIIDNOOOOOx0UUUUYPsaaaaaaeceeeeiiiiOnooooo/0uuuuuypy");
const std::u16string illegal =
UTF8toUTF16("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüūýþÿ");
const std::u16string fixed =
UTF8toUTF16("AAAAAAECEEEEIIIIDNOOOOOx0UUUUYPsaaaaaaeceeeeiiiiOnooooo/0uuuuuypy");
for (size_t i = 0, sz = src.length(); i < sz; i++) {
size_t index = illegal.find(src[i]);
@@ -106,8 +102,7 @@ std::string StringUtils::removeAccents(std::string str)
return UTF16toUTF8(src);
}
std::string StringUtils::removeNotAscii(std::string str)
{
std::string StringUtils::removeNotAscii(std::string str) {
for (size_t i = 0, sz = str.length(); i < sz; i++) {
if (!isascii(str[i])) {
str[i] = ' ';
@@ -116,9 +111,9 @@ std::string StringUtils::removeNotAscii(std::string str)
return str;
}
std::string StringUtils::elide(const std::string& s, size_t maxChars)
{
if (s.size() <= maxChars || maxChars < 6) return s;
std::string StringUtils::elide(const std::string& s, size_t maxChars) {
if (s.size() <= maxChars || maxChars < 6)
return s;
constexpr const char* dots = "...";
size_t budget = maxChars - 3;
size_t head = (budget + 1) / 2;
@@ -126,36 +121,35 @@ std::string StringUtils::elide(const std::string& s, size_t maxChars)
return s.substr(0, head) + dots + s.substr(s.size() - tail);
}
HidsysNotificationLedPattern blinkLedPattern(u8 times)
{
HidsysNotificationLedPattern blinkLedPattern(u8 times) {
HidsysNotificationLedPattern pattern;
memset(&pattern, 0, sizeof(pattern));
pattern.baseMiniCycleDuration = 0x1; // 12.5ms.
pattern.totalMiniCycles = 0x2; // 2 mini cycles.
pattern.totalFullCycles = times; // Repeat n times.
pattern.startIntensity = 0x0; // 0%.
pattern.baseMiniCycleDuration = 0x1; // 12.5ms.
pattern.totalMiniCycles = 0x2; // 2 mini cycles.
pattern.totalFullCycles = times; // Repeat n times.
pattern.startIntensity = 0x0; // 0%.
pattern.miniCycles[0].ledIntensity = 0xF; // 100%.
pattern.miniCycles[0].transitionSteps = 0xF; // 15 steps. Total 187.5ms.
pattern.miniCycles[0].finalStepDuration = 0x0; // Forced 12.5ms.
pattern.miniCycles[1].ledIntensity = 0x0; // 0%.
pattern.miniCycles[1].transitionSteps = 0xF; // 15 steps. Total 187.5ms.
pattern.miniCycles[1].finalStepDuration = 0x0; // Forced 12.5ms.
pattern.miniCycles[0].ledIntensity = 0xF; // 100%.
pattern.miniCycles[0].transitionSteps = 0xF; // 15 steps. Total 187.5ms.
pattern.miniCycles[0].finalStepDuration = 0x0; // Forced 12.5ms.
pattern.miniCycles[1].ledIntensity = 0x0; // 0%.
pattern.miniCycles[1].transitionSteps = 0xF; // 15 steps. Total 187.5ms.
pattern.miniCycles[1].finalStepDuration = 0x0; // Forced 12.5ms.
return pattern;
}
void blinkLed(u8 times)
{
void blinkLed(u8 times) {
if (g_notificationLedAvailable) {
PadState pad;
padInitializeDefault(&pad);
s32 n;
HidsysUniquePadId uniquePadIds[2] = {0};
HidsysUniquePadId uniquePadIds[2] = {0};
HidsysNotificationLedPattern pattern = blinkLedPattern(times);
memset(uniquePadIds, 0, sizeof(uniquePadIds));
Result res = hidsysGetUniquePadsFromNpad(padIsHandheld(&pad) ? HidNpadIdType_Handheld : HidNpadIdType_No1, uniquePadIds, 2, &n);
Result res = hidsysGetUniquePadsFromNpad(
padIsHandheld(&pad) ? HidNpadIdType_Handheld : HidNpadIdType_No1, uniquePadIds, 2, &n);
if (R_SUCCEEDED(res)) {
for (s32 i = 0; i < n; i++) {
hidsysSetNotificationLedPattern(&pattern, uniquePadIds[i]);
+10 -17
View File
@@ -26,9 +26,8 @@
#include <nxst/infra/fs/directory.hpp>
Directory::Directory(const std::string& root)
{
mGood = false;
Directory::Directory(const std::string& root) {
mGood = false;
mError = 0;
mList.clear();
@@ -37,11 +36,10 @@ Directory::Directory(const std::string& root)
if (dir == NULL) {
mError = (Result)errno;
}
else {
} else {
while ((ent = readdir(dir))) {
std::string name = std::string(ent->d_name);
bool directory = ent->d_type == DT_DIR;
std::string name = std::string(ent->d_name);
bool directory = ent->d_type == DT_DIR;
struct DirectoryEntry de = {name, directory};
mList.push_back(de);
}
@@ -50,27 +48,22 @@ Directory::Directory(const std::string& root)
}
}
Result Directory::error(void)
{
Result Directory::error(void) {
return mError;
}
bool Directory::good(void)
{
bool Directory::good(void) {
return mGood;
}
std::string Directory::entry(size_t index)
{
std::string Directory::entry(size_t index) {
return index < mList.size() ? mList.at(index).name : "";
}
bool Directory::folder(size_t index)
{
bool Directory::folder(size_t index) {
return index < mList.size() ? mList.at(index).directory : false;
}
size_t Directory::size(void)
{
size_t Directory::size(void) {
return mList.size();
}
+3 -6
View File
@@ -26,17 +26,14 @@
#include <nxst/infra/fs/filesystem.hpp>
Result FileSystem::mount(FsFileSystem* fileSystem, u64 titleID, AccountUid userID)
{
Result FileSystem::mount(FsFileSystem* fileSystem, u64 titleID, AccountUid userID) {
return fsOpen_SaveData(fileSystem, titleID, userID);
}
int FileSystem::mount(FsFileSystem fs)
{
int FileSystem::mount(FsFileSystem fs) {
return fsdevMountDevice("save", fs);
}
void FileSystem::unmount(void)
{
void FileSystem::unmount(void) {
fsdevUnmountDevice("save");
}
+34 -47
View File
@@ -24,20 +24,19 @@
* reasonable ways as different from the original version.
*/
#include <nxst/infra/fs/io.hpp>
#include <nxst/infra/fs/handles.hpp>
#include <nxst/app/main.hpp>
#include <nxst/infra/sys/logger.hpp>
#include <vector>
bool io::fileExists(const std::string& path)
{
#include <nxst/app/main.hpp>
#include <nxst/infra/fs/handles.hpp>
#include <nxst/infra/fs/io.hpp>
#include <nxst/infra/sys/logger.hpp>
bool io::fileExists(const std::string& path) {
struct stat buffer;
return (stat(path.c_str(), &buffer) == 0);
}
void io::copyFile(const std::string& srcPath, const std::string& dstPath)
{
void io::copyFile(const std::string& srcPath, const std::string& dstPath) {
g_isTransferringFile = true;
nxst::FileHandle src(fopen(srcPath.c_str(), "rb"));
@@ -63,14 +62,13 @@ void io::copyFile(const std::string& srcPath, const std::string& dstPath)
u64 offset = 0;
size_t slashpos = srcPath.rfind('/');
g_currentFile = srcPath.substr(slashpos + 1, srcPath.length() - slashpos - 1);
g_currentFile = srcPath.substr(slashpos + 1, srcPath.length() - slashpos - 1);
while (offset < sz) {
u32 count = (u32)fread(buf.data(), 1, BUFFER_SIZE, src.get());
if (count == 0) {
nxst::log::error("fread returned 0 for %s at offset %llu/%llu (errno %d). Aborting.",
srcPath.c_str(), (unsigned long long)offset,
(unsigned long long)sz, errno);
srcPath.c_str(), (unsigned long long)offset, (unsigned long long)sz, errno);
break;
}
fwrite(buf.data(), 1, count, dst.get());
@@ -84,10 +82,9 @@ void io::copyFile(const std::string& srcPath, const std::string& dstPath)
g_isTransferringFile = false;
}
Result io::copyDirectory(const std::string& srcPath, const std::string& dstPath)
{
Result io::copyDirectory(const std::string& srcPath, const std::string& dstPath) {
Result res = 0;
bool quit = false;
bool quit = false;
Directory items(srcPath);
if (!items.good()) {
@@ -104,12 +101,10 @@ Result io::copyDirectory(const std::string& srcPath, const std::string& dstPath)
newsrc += "/";
newdst += "/";
res = io::copyDirectory(newsrc, newdst);
}
else {
} else {
quit = true;
}
}
else {
} else {
io::copyFile(newsrc, newdst);
}
}
@@ -117,20 +112,17 @@ Result io::copyDirectory(const std::string& srcPath, const std::string& dstPath)
return 0;
}
Result io::createDirectory(const std::string& path)
{
Result io::createDirectory(const std::string& path) {
mkdir(path.c_str(), 0777);
return 0;
}
bool io::directoryExists(const std::string& path)
{
bool io::directoryExists(const std::string& path) {
struct stat sb;
return (stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode));
}
Result io::deleteFolderRecursively(const std::string& path)
{
Result io::deleteFolderRecursively(const std::string& path) {
Directory dir(path);
if (!dir.good()) {
return dir.error();
@@ -142,8 +134,7 @@ Result io::deleteFolderRecursively(const std::string& path)
deleteFolderRecursively(newpath);
newpath = path + dir.entry(i);
rmdir(newpath.c_str());
}
else {
} else {
std::string newpath = path + dir.entry(i);
std::remove(newpath.c_str());
}
@@ -153,14 +144,12 @@ Result io::deleteFolderRecursively(const std::string& path)
return 0;
}
nxst::Result<std::string> io::backup(size_t index, AccountUid uid)
{
nxst::Result<std::string> io::backup(size_t index, AccountUid uid) {
Title title;
getTitle(title, uid, index);
nxst::log::info("Started backup of %s. Title id: 0x%016lX; User id: 0x%lX%lX.",
title.name().c_str(), title.id(),
title.userId().uid[1], title.userId().uid[0]);
nxst::log::info("Started backup of %s. Title id: 0x%016lX; User id: 0x%lX%lX.", title.name().c_str(),
title.id(), title.userId().uid[1], title.userId().uid[0]);
nxst::FsFileSystemHandle fsHandle;
Result res = FileSystem::mount(fsHandle.get(), title.id(), title.userId());
@@ -180,7 +169,8 @@ nxst::Result<std::string> io::backup(size_t index, AccountUid uid)
}
fsHandle.release(); // devfs now owns the kernel handle
std::string suggestion = StringUtils::removeNotAscii(StringUtils::removeAccents(Account::username(title.userId())));
std::string suggestion =
StringUtils::removeNotAscii(StringUtils::removeAccents(Account::username(title.userId())));
io::createDirectory(title.path());
std::string dst_path = title.path() + "/" + suggestion;
@@ -215,15 +205,13 @@ nxst::Result<std::string> io::backup(size_t index, AccountUid uid)
}
// Creates the save data filesystem for a title if it doesn't exist yet.
static void createSaveIfNeeded(u64 title_id, AccountUid uid)
{
static void createSaveIfNeeded(u64 title_id, AccountUid uid) {
std::vector<u8> nsacd_buf(sizeof(NsApplicationControlData), 0);
auto* nsacd = reinterpret_cast<NsApplicationControlData*>(nsacd_buf.data());
size_t outsize = 0;
if (!R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_Storage,
title_id, nsacd,
sizeof(NsApplicationControlData), &outsize))) {
if (!R_SUCCEEDED(nsGetApplicationControlData(NsApplicationControlSource_Storage, title_id, nsacd,
sizeof(NsApplicationControlData), &outsize))) {
return;
}
@@ -231,30 +219,29 @@ static void createSaveIfNeeded(u64 title_id, AccountUid uid)
FsSaveDataAttribute attr = {};
attr.application_id = title_id;
attr.uid = uid;
attr.uid = uid;
attr.save_data_type = FsSaveDataType_Account;
attr.save_data_rank = FsSaveDataRank_Primary;
FsSaveDataCreationInfo create_info = {};
create_info.save_data_size = (s64)nsacd->nacp.user_account_save_data_size;
create_info.journal_size = (s64)nsacd->nacp.user_account_save_data_journal_size;
create_info.available_size = 0x4000;
create_info.owner_id = nsacd->nacp.save_data_owner_id;
create_info.save_data_size = (s64)nsacd->nacp.user_account_save_data_size;
create_info.journal_size = (s64)nsacd->nacp.user_account_save_data_journal_size;
create_info.available_size = 0x4000;
create_info.owner_id = nsacd->nacp.save_data_owner_id;
create_info.save_data_space_id = FsSaveDataSpaceId_User;
fsCreateSaveDataFileSystem(&attr, &create_info, &meta);
}
nxst::Result<std::string> io::restore(size_t index, AccountUid uid, size_t cellIndex, const std::string& nameFromCell)
{
nxst::Result<std::string> io::restore(size_t index, AccountUid uid, size_t cellIndex,
const std::string& nameFromCell) {
(void)cellIndex;
Title title;
getTitle(title, uid, index);
nxst::log::info("Started restore of %s. Title id: 0x%016lX; User id: 0x%lX%lX.",
title.name().c_str(), title.id(),
title.userId().uid[1], title.userId().uid[0]);
nxst::log::info("Started restore of %s. Title id: 0x%016lX; User id: 0x%lX%lX.", title.name().c_str(),
title.id(), title.userId().uid[1], title.userId().uid[0]);
createSaveIfNeeded(title.id(), uid);
+42 -16
View File
@@ -1,10 +1,10 @@
#include <nxst/infra/sys/logger.hpp>
#include <cstdarg>
#include <cstdio>
#include <ctime>
#include <mutex>
#include <nxst/infra/sys/logger.hpp>
namespace {
std::mutex g_log_mutex;
@@ -15,8 +15,7 @@ constexpr const char* kLogPath = "/switch/NXST/log.log";
constexpr const char* kLogPath = "nxst.log";
#endif
void writeEntry(const char* tag, const char* fmt, va_list args)
{
void writeEntry(const char* tag, const char* fmt, va_list args) {
char msg[2048];
vsnprintf(msg, sizeof(msg), fmt, args);
@@ -37,18 +36,25 @@ void writeEntry(const char* tag, const char* fmt, va_list args)
}
}
} // namespace
} // namespace
namespace nxst::log {
void write(Level level, const char* fmt, ...)
{
void write(Level level, const char* fmt, ...) {
const char* tag = "[INFO] ";
switch (level) {
case Level::Debug: tag = "[DEBUG]"; break;
case Level::Info: tag = "[INFO] "; break;
case Level::Warn: tag = "[WARN] "; break;
case Level::Error: tag = "[ERROR]"; break;
case Level::Debug:
tag = "[DEBUG]";
break;
case Level::Info:
tag = "[INFO] ";
break;
case Level::Warn:
tag = "[WARN] ";
break;
case Level::Error:
tag = "[ERROR]";
break;
}
va_list args;
va_start(args, fmt);
@@ -56,9 +62,29 @@ void write(Level level, const char* fmt, ...)
va_end(args);
}
void debug(const char* fmt, ...) { va_list args; va_start(args, fmt); writeEntry("[DEBUG]", fmt, args); va_end(args); }
void info (const char* fmt, ...) { va_list args; va_start(args, fmt); writeEntry("[INFO] ", fmt, args); va_end(args); }
void warn (const char* fmt, ...) { va_list args; va_start(args, fmt); writeEntry("[WARN] ", fmt, args); va_end(args); }
void error(const char* fmt, ...) { va_list args; va_start(args, fmt); writeEntry("[ERROR]", fmt, args); va_end(args); }
void debug(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
writeEntry("[DEBUG]", fmt, args);
va_end(args);
}
void info(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
writeEntry("[INFO] ", fmt, args);
va_end(args);
}
void warn(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
writeEntry("[WARN] ", fmt, args);
va_end(args);
}
void error(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
writeEntry("[ERROR]", fmt, args);
va_end(args);
}
} // namespace nxst::log
} // namespace nxst::log
+128 -71
View File
@@ -1,5 +1,3 @@
#include <nxst/service/transfer_service.hpp>
#include <arpa/inet.h>
#include <chrono>
#include <cstring>
@@ -12,11 +10,14 @@
#include <unistd.h>
#include <vector>
#include <nxst/service/transfer_service.hpp>
#ifdef __SWITCH__
#include <switch.h>
#include <nxst/infra/fs/io.hpp>
#include <nxst/domain/util.hpp>
#include <nxst/domain/account.hpp>
#include <nxst/domain/util.hpp>
#include <nxst/infra/fs/io.hpp>
#endif
#include <nxst/domain/protocol.hpp>
@@ -32,7 +33,8 @@ static bool sendAll(int sock, const void* buf, size_t len) {
size_t sent = 0;
while (sent < len) {
ssize_t n = send(sock, static_cast<const char*>(buf) + sent, len - sent, 0);
if (n <= 0) return false;
if (n <= 0)
return false;
sent += n;
}
return true;
@@ -42,7 +44,8 @@ static bool recvAll(int sock, void* buf, size_t len) {
size_t got = 0;
while (got < len) {
ssize_t n = read(sock, static_cast<char*>(buf) + got, len - got);
if (n <= 0) return false;
if (n <= 0)
return false;
got += n;
}
return true;
@@ -50,15 +53,19 @@ static bool recvAll(int sock, void* buf, size_t len) {
static bool sendFile(int sock, const fs::path& filepath, TransferState& state) {
std::ifstream infile(filepath, std::ios::binary | std::ios::ate);
if (!infile.is_open()) return false;
if (!infile.is_open())
return false;
uint32_t filename_len = (uint32_t)filepath.string().size();
uint64_t file_size = (uint64_t)infile.tellg();
uint64_t file_size = (uint64_t)infile.tellg();
infile.seekg(0, std::ios::beg);
if (!sendAll(sock, &filename_len, sizeof(filename_len))) return false;
if (!sendAll(sock, filepath.c_str(), filename_len)) return false;
if (!sendAll(sock, &file_size, sizeof(file_size))) return false;
if (!sendAll(sock, &filename_len, sizeof(filename_len)))
return false;
if (!sendAll(sock, filepath.c_str(), filename_len))
return false;
if (!sendAll(sock, &file_size, sizeof(file_size)))
return false;
std::vector<char> buffer(proto::BUF_SIZE);
uint64_t remaining = file_size;
@@ -66,8 +73,10 @@ static bool sendFile(int sock, const fs::path& filepath, TransferState& state) {
size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::BUF_SIZE);
infile.read(buffer.data(), (std::streamsize)to_read);
std::streamsize count = infile.gcount();
if (count <= 0) break;
if (!sendAll(sock, buffer.data(), (size_t)count)) return false;
if (count <= 0)
break;
if (!sendAll(sock, buffer.data(), (size_t)count))
return false;
state.bytes_done.fetch_add((uint64_t)count);
remaining -= (uint64_t)count;
}
@@ -84,12 +93,12 @@ static void mkdirs(const std::string& path) {
mkdir(path.c_str(), 0777);
}
static void receiveFile(int sock, const std::string& rel_path, uint64_t file_size,
TransferState& state) {
static void receiveFile(int sock, const std::string& rel_path, uint64_t file_size, TransferState& state) {
size_t last_slash = rel_path.rfind('/');
if (last_slash != std::string::npos) {
std::string dir = rel_path.substr(0, last_slash);
if (!dir.empty()) mkdirs(dir);
if (!dir.empty())
mkdirs(dir);
}
FILE* outfile = fopen(rel_path.c_str(), "wb");
@@ -99,7 +108,8 @@ static void receiveFile(int sock, const std::string& rel_path, uint64_t file_siz
while (remaining > 0) {
size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::BUF_SIZE);
ssize_t n = read(sock, drain.data(), to_read);
if (n <= 0) break;
if (n <= 0)
break;
remaining -= (uint64_t)n;
}
return;
@@ -113,7 +123,8 @@ static void receiveFile(int sock, const std::string& rel_path, uint64_t file_siz
while (total < file_size) {
size_t to_read = (size_t)std::min(file_size - total, (uint64_t)proto::BUF_SIZE);
ssize_t n = read(sock, buffer.data(), to_read);
if (n <= 0) break;
if (n <= 0)
break;
fwrite(buffer.data(), 1, (size_t)n, outfile);
total += (uint64_t)n;
state.bytes_done.store(total);
@@ -131,17 +142,19 @@ void TransferService::failSend(const std::string& reason) {
int TransferService::findServer(char* out_ip) {
int udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_fd < 0) return -1;
if (udp_fd < 0)
return -1;
sender_udp_sock.store(udp_fd);
auto releaseUdp = [&]() {
int owned = sender_udp_sock.exchange(-1);
if (owned == udp_fd) close(udp_fd);
if (owned == udp_fd)
close(udp_fd);
};
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(proto::MULTICAST_PORT);
addr.sin_family = AF_INET;
addr.sin_port = htons(proto::MULTICAST_PORT);
addr.sin_addr.s_addr = inet_addr(proto::MULTICAST_GROUP);
if (sendto(udp_fd, "DISCOVER_SERVER", 15, 0, (sockaddr*)&addr, sizeof(addr)) < 0) {
@@ -152,16 +165,19 @@ int TransferService::findServer(char* out_ip) {
// Poll in 100ms slices so cancel races within 100ms
auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(3);
while (std::chrono::steady_clock::now() < deadline) {
if (sender_state.cancelled.load()) { releaseUdp(); return -1; }
if (sender_state.cancelled.load()) {
releaseUdp();
return -1;
}
struct timeval tv{0, 100000};
fd_set fds;
FD_ZERO(&fds);
FD_SET(udp_fd, &fds);
if (select(udp_fd + 1, &fds, nullptr, nullptr, &tv) > 0) {
sockaddr_in from{};
socklen_t fromlen = sizeof(from);
char buf[256];
ssize_t n = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen);
socklen_t fromlen = sizeof(from);
char buf[256];
ssize_t n = recvfrom(udp_fd, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen);
if (n > 0) {
buf[n] = '\0';
if (strcmp(buf, "SERVER_HERE") == 0) {
@@ -179,8 +195,8 @@ int TransferService::findServer(char* out_ip) {
void* TransferService::senderEntry(void* arg) {
auto* a = static_cast<SenderArgs*>(arg);
TransferService* svc = a->svc;
size_t idx = a->title_index;
AccountUid uid = a->uid;
size_t idx = a->title_index;
AccountUid uid = a->uid;
delete a;
svc->runSender(idx, uid);
return nullptr;
@@ -200,7 +216,8 @@ void TransferService::runSender(size_t title_index, AccountUid uid) {
failSend("No receiver found.\nMake sure the other Switch is in Receive mode.");
return finish();
}
if (sender_state.cancelled.load()) return finish();
if (sender_state.cancelled.load())
return finish();
sender_state.setStatus("Creating backup...");
#ifdef __SWITCH__
@@ -212,24 +229,30 @@ void TransferService::runSender(size_t title_index, AccountUid uid) {
fs::path directory = backup_result.value();
#else
fs::path directory = ".";
(void)title_index; (void)uid;
(void)title_index;
(void)uid;
#endif
if (sender_state.cancelled.load()) return finish();
if (sender_state.cancelled.load())
return finish();
sender_state.setStatus("Connecting...");
int tcp_fd = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_fd < 0) { failSend("Failed to open socket."); return finish(); }
if (tcp_fd < 0) {
failSend("Failed to open socket.");
return finish();
}
sender_tcp_sock.store(tcp_fd);
auto releaseTcp = [&]() {
int owned = sender_tcp_sock.exchange(-1);
if (owned == tcp_fd) close(tcp_fd);
if (owned == tcp_fd)
close(tcp_fd);
};
sockaddr_in serv{};
serv.sin_family = AF_INET;
serv.sin_port = htons(proto::TCP_PORT);
serv.sin_port = htons(proto::TCP_PORT);
if (inet_pton(AF_INET, server_ip, &serv.sin_addr) <= 0 ||
connect(tcp_fd, (sockaddr*)&serv, sizeof(serv)) < 0) {
if (!sender_state.cancelled.load())
@@ -245,11 +268,13 @@ void TransferService::runSender(size_t title_index, AccountUid uid) {
sender_state.bytes_total.store(total);
for (const auto& entry : fs::recursive_directory_iterator(directory)) {
if (sender_state.cancelled.load()) break;
if (sender_state.cancelled.load())
break;
const fs::path& p = entry.path();
if (fs::is_regular_file(p)) {
sender_state.setStatus(p.filename().string());
if (!sendFile(tcp_fd, p, sender_state)) break;
if (!sendFile(tcp_fd, p, sender_state))
break;
}
}
@@ -278,19 +303,26 @@ int TransferService::startSend(size_t title_index, AccountUid uid) {
void TransferService::cancelSend() {
sender_state.cancelled.store(true);
int udp = sender_udp_sock.exchange(-1);
if (udp >= 0) { shutdown(udp, SHUT_RDWR); close(udp); }
if (udp >= 0) {
shutdown(udp, SHUT_RDWR);
close(udp);
}
int tcp = sender_tcp_sock.exchange(-1);
if (tcp >= 0) { shutdown(tcp, SHUT_RDWR); close(tcp); }
if (tcp >= 0) {
shutdown(tcp, SHUT_RDWR);
close(tcp);
}
}
// ─── Receiver ────────────────────────────────────────────────────────────────
std::string TransferService::replaceUsername(const std::string& file_path) const {
#ifdef __SWITCH__
std::string username = StringUtils::removeNotAscii(
StringUtils::removeAccents(Account::username(restore_uid)));
std::string username =
StringUtils::removeNotAscii(StringUtils::removeAccents(Account::username(restore_uid)));
size_t last_slash = file_path.rfind('/');
if (last_slash == std::string::npos) return file_path;
if (last_slash == std::string::npos)
return file_path;
size_t prev_slash = file_path.rfind('/', last_slash - 1);
if (prev_slash == std::string::npos)
return username + file_path.substr(last_slash);
@@ -311,21 +343,25 @@ void TransferService::runBroadcast() {
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, nullptr);
int udp = socket(AF_INET, SOCK_DGRAM, 0);
if (udp < 0) { receiver_broadcast_active.store(false); return; }
if (udp < 0) {
receiver_broadcast_active.store(false);
return;
}
receiver_bcast_sock.store(udp);
auto releaseUdp = [&]() {
int owned = receiver_bcast_sock.exchange(-1);
if (owned == udp) close(udp);
if (owned == udp)
close(udp);
};
struct timeval tv{0, 20000}; // 20ms poll so cancel/exit wins race with socketExit
setsockopt(udp, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(proto::MULTICAST_PORT);
addr.sin_port = htons(proto::MULTICAST_PORT);
if (bind(udp, (sockaddr*)&addr, sizeof(addr)) < 0) {
releaseUdp();
@@ -348,7 +384,8 @@ void TransferService::runBroadcast() {
while (true) {
ssize_t n = recvfrom(udp, buf, sizeof(buf) - 1, 0, (sockaddr*)&from, &fromlen);
if (n < 0) {
if (receiver_state.cancelled.load()) break;
if (receiver_state.cancelled.load())
break;
continue;
}
buf[n] = '\0';
@@ -365,7 +402,7 @@ void TransferService::runBroadcast() {
void* TransferService::acceptEntry(void* arg) {
auto* a = static_cast<AcceptArgs*>(arg);
TransferService* svc = a->svc;
int server_fd = a->server_fd;
int server_fd = a->server_fd;
delete a;
svc->runAccept(server_fd);
return nullptr;
@@ -380,41 +417,48 @@ void TransferService::runAccept(int server_fd) {
int client_sock = accept(server_fd, (sockaddr*)&client_addr, &client_len);
int owned_listen = receiver_listen_sock.exchange(-1);
if (owned_listen == server_fd) close(server_fd);
if (owned_listen == server_fd)
close(server_fd);
if (client_sock >= 0) {
receiver_client_sock.store(client_sock);
while (true) {
uint32_t filename_len = 0;
if (!recvAll(client_sock, &filename_len, sizeof(filename_len))) break;
if (filename_len == proto::EOF_SENTINEL) break;
if (filename_len > proto::MAX_FILENAME) break;
if (!recvAll(client_sock, &filename_len, sizeof(filename_len)))
break;
if (filename_len == proto::EOF_SENTINEL)
break;
if (filename_len > proto::MAX_FILENAME)
break;
std::vector<char> filename_buf(filename_len + 1, '\0');
if (!recvAll(client_sock, filename_buf.data(), filename_len)) break;
if (!recvAll(client_sock, filename_buf.data(), filename_len))
break;
std::string filename_str(filename_buf.data(), filename_len);
filename_str = replaceUsername(filename_str);
{
size_t sl = filename_str.rfind('/');
receiver_state.setStatus(
sl != std::string::npos ? filename_str.substr(sl + 1) : filename_str);
receiver_state.setStatus(sl != std::string::npos ? filename_str.substr(sl + 1)
: filename_str);
}
uint64_t file_size = 0;
if (!recvAll(client_sock, &file_size, sizeof(file_size))) break;
if (!recvAll(client_sock, &file_size, sizeof(file_size)))
break;
receiveFile(client_sock, filename_str, file_size, receiver_state);
}
int owned = receiver_client_sock.exchange(-1);
if (owned == client_sock) close(client_sock);
if (owned == client_sock)
close(client_sock);
if (!receiver_state.cancelled.load()) {
#ifdef __SWITCH__
receiver_state.setStatus("Restoring...");
auto result = io::restore(restore_title_index, restore_uid, 0, restore_title_name);
restore_ok = result.isOk();
restore_ok = result.isOk();
restore_error = result.isOk() ? "" : result.error();
#else
restore_ok = true;
@@ -430,29 +474,32 @@ int TransferService::startReceive(size_t title_index, AccountUid uid, std::strin
receiver_state.reset();
receiver_state.setStatus("Waiting for connection...");
restore_title_index = title_index;
restore_uid = uid;
restore_title_name = std::move(title_name);
restore_ok = false;
restore_uid = uid;
restore_title_name = std::move(title_name);
restore_ok = false;
restore_error.clear();
pthread_t bcast_thread;
if (pthread_create(&bcast_thread, nullptr, broadcastEntry, this) != 0) return 1;
if (pthread_create(&bcast_thread, nullptr, broadcastEntry, this) != 0)
return 1;
receiver_bcast_thread = bcast_thread;
pthread_detach(bcast_thread);
Socket server(socket(AF_INET, SOCK_STREAM, 0));
if (!server.valid()) { cancelReceive(); return 1; }
if (!server.valid()) {
cancelReceive();
return 1;
}
int yes = 1;
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(proto::TCP_PORT);
addr.sin_port = htons(proto::TCP_PORT);
if (bind(server, (sockaddr*)&addr, sizeof(addr)) < 0 ||
listen(server, 3) < 0) {
if (bind(server, (sockaddr*)&addr, sizeof(addr)) < 0 || listen(server, 3) < 0) {
cancelReceive();
return 1;
}
@@ -472,12 +519,22 @@ int TransferService::startReceive(size_t title_index, AccountUid uid, std::strin
void TransferService::cancelReceive() {
receiver_state.cancelled.store(true);
int sock = receiver_client_sock.exchange(-1);
if (sock >= 0) { shutdown(sock, SHUT_RDWR); close(sock); }
if (sock >= 0) {
shutdown(sock, SHUT_RDWR);
close(sock);
}
int lsock = receiver_listen_sock.exchange(-1);
if (lsock >= 0) { shutdown(lsock, SHUT_RDWR); close(lsock); }
if (lsock >= 0) {
shutdown(lsock, SHUT_RDWR);
close(lsock);
}
int bsock = receiver_bcast_sock.exchange(-1);
if (bsock >= 0) { shutdown(bsock, SHUT_RDWR); close(bsock); }
if (receiver_broadcast_active.load()) pthread_cancel(receiver_bcast_thread);
if (bsock >= 0) {
shutdown(bsock, SHUT_RDWR);
close(bsock);
}
if (receiver_broadcast_active.load())
pthread_cancel(receiver_bcast_thread);
}
} // namespace nxst
} // namespace nxst
+281 -279
View File
@@ -1,310 +1,312 @@
#include <nxst/app/main_application.hpp>
#include <nxst/domain/util.hpp>
#include <nxst/ui/transfer_overlay.hpp>
#include <nxst/ui/const.h>
#include <nxst/ui/transfer_overlay.hpp>
namespace ui {
extern MainApplication *mainApp;
extern MainApplication* mainApp;
namespace {
constexpr int ListX = theme::space::lg;
constexpr int ListW = 760;
constexpr int PanelX = ListX + ListW + theme::space::xl;
constexpr int PanelW = theme::layout::ScreenW - PanelX - theme::space::lg;
constexpr int ContentY = theme::layout::ContentTop + theme::space::md;
constexpr int ContentH = theme::layout::ContentH - 2 * theme::space::md;
constexpr int BtnH = 56;
constexpr int BtnW = PanelW - 2 * theme::space::lg;
}
namespace {
constexpr int ListX = theme::space::lg;
constexpr int ListW = 760;
constexpr int PanelX = ListX + ListW + theme::space::xl;
constexpr int PanelW = theme::layout::ScreenW - PanelX - theme::space::lg;
constexpr int ContentY = theme::layout::ContentTop + theme::space::md;
constexpr int ContentH = theme::layout::ContentH - 2 * theme::space::md;
constexpr int BtnH = 56;
constexpr int BtnW = PanelW - 2 * theme::space::lg;
} // namespace
TitlesLayout::TitlesLayout() : Layout::Layout() {
using namespace theme;
TitlesLayout::TitlesLayout() : Layout::Layout() {
using namespace theme;
this->titlesMenu = pu::ui::elm::Menu::New(
ListX, ContentY, ListW,
color::BgBase, color::BgSurface2,
88, 6);
this->titlesMenu->SetScrollbarColor(color::Primary);
this->titlesMenu->SetItemsFocusColor(color::BgSurface2);
this->titlesMenu->SetOnSelectionChanged([this]() { this->refreshPanel(); });
this->SetBackgroundColor(color::BgBase);
this->Add(this->titlesMenu);
this->panelBg = pu::ui::elm::Rectangle::New(
PanelX, ContentY, PanelW, ContentH, color::BgSurface, radius::lg);
this->Add(this->panelBg);
this->panelTitle = pu::ui::elm::TextBlock::New(
PanelX + space::lg, ContentY + space::lg, "");
this->panelTitle->SetFont(type::font(type::Title));
this->panelTitle->SetColor(color::TextPrimary);
this->Add(this->panelTitle);
this->panelHint = pu::ui::elm::TextBlock::New(
PanelX + space::lg, ContentY + space::lg + 48, "Pick an action:");
this->panelHint->SetFont(type::font(type::Body));
this->panelHint->SetColor(color::TextSecondary);
this->Add(this->panelHint);
int btnY = ContentY + 200;
this->btnTransferBg = pu::ui::elm::Rectangle::New(
PanelX + space::lg, btnY, BtnW, BtnH, color::BgSurface2, radius::md);
this->Add(this->btnTransferBg);
this->btnTransferText = pu::ui::elm::TextBlock::New(
PanelX + space::lg + space::md, btnY + 14, "Transfer to another device");
this->btnTransferText->SetFont(type::font(type::Body));
this->btnTransferText->SetColor(color::TextSecondary);
this->Add(this->btnTransferText);
int btnY2 = btnY + BtnH + space::md;
this->btnReceiveBg = pu::ui::elm::Rectangle::New(
PanelX + space::lg, btnY2, BtnW, BtnH, color::BgSurface2, radius::md);
this->Add(this->btnReceiveBg);
this->btnReceiveText = pu::ui::elm::TextBlock::New(
PanelX + space::lg + space::md, btnY2 + 14, "Receive from another device");
this->btnReceiveText->SetFont(type::font(type::Body));
this->btnReceiveText->SetColor(color::TextSecondary);
this->Add(this->btnReceiveText);
this->panelFooter = pu::ui::elm::TextBlock::New(
PanelX + space::lg,
ContentY + ContentH - space::lg - 18,
"Save data only");
this->panelFooter->SetFont(type::font(type::Caption));
this->panelFooter->SetColor(color::TextMuted);
this->Add(this->panelFooter);
this->emptyText = pu::ui::elm::TextBlock::New(
ListX + ListW / 2 - 280, ContentY + ContentH / 2 - 40,
"No save data on this profile");
this->emptyText->SetFont(type::font(type::Display));
this->emptyText->SetColor(color::TextPrimary);
this->emptyText->SetVisible(false);
this->Add(this->emptyText);
this->emptySub = pu::ui::elm::TextBlock::New(
ListX + ListW / 2 - 220, ContentY + ContentH / 2 + 16,
"Play something first, then come back.");
this->emptySub->SetFont(type::font(type::Body));
this->emptySub->SetColor(color::TextMuted);
this->emptySub->SetVisible(false);
this->Add(this->emptySub);
this->header = std::make_unique<HeaderBar>(this, "Save Transfer");
this->hints = std::make_unique<HintBar>(this);
this->updateHints();
}
void TitlesLayout::InitTitles(AccountUid uid) {
using namespace theme;
this->current_uid = uid;
auto it = this->menuCache.find(uid);
std::vector<pu::ui::elm::MenuItem::Ref>* items;
if (it != this->menuCache.end()) {
items = &it->second;
} else {
std::vector<pu::ui::elm::MenuItem::Ref> built;
for (size_t i = 0; i < getTitleCount(uid); i++) {
Title title;
getTitle(title, uid, i);
auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str());
titleItem->SetColor(color::TextPrimary);
built.push_back(titleItem);
}
auto inserted = this->menuCache.emplace(uid, std::move(built));
items = &inserted.first->second;
}
this->titlesMenu->ClearItems();
for (auto& item : *items) {
this->titlesMenu->AddItem(item);
}
this->titlesMenu->SetSelectedIndex(0);
const bool empty = items->empty();
this->titlesMenu->SetVisible(!empty);
this->panelBg->SetVisible(!empty);
this->panelTitle->SetVisible(!empty);
this->panelHint->SetVisible(!empty);
this->btnTransferBg->SetVisible(!empty);
this->btnTransferText->SetVisible(!empty);
this->btnReceiveBg->SetVisible(!empty);
this->btnReceiveText->SetVisible(!empty);
this->panelFooter->SetVisible(!empty);
this->emptyText->SetVisible(empty);
this->emptySub->SetVisible(empty);
this->focus = TitlesFocus::List;
this->action = TitlesAction::Transfer;
this->titlesMenu =
pu::ui::elm::Menu::New(ListX, ContentY, ListW, color::BgBase, color::BgSurface2, 88, 6);
this->titlesMenu->SetScrollbarColor(color::Primary);
this->titlesMenu->SetItemsFocusColor(color::BgSurface2);
this->titlesMenu->SetOnSelectionChanged([this]() {
this->refreshPanel();
this->refreshButtons();
this->updateHints();
});
this->SetBackgroundColor(color::BgBase);
this->Add(this->titlesMenu);
this->header->SetUser(uid, Account::username(uid));
this->panelBg =
pu::ui::elm::Rectangle::New(PanelX, ContentY, PanelW, ContentH, color::BgSurface, radius::lg);
this->Add(this->panelBg);
this->panelTitle = pu::ui::elm::TextBlock::New(PanelX + space::lg, ContentY + space::lg, "");
this->panelTitle->SetFont(type::font(type::Title));
this->panelTitle->SetColor(color::TextPrimary);
this->Add(this->panelTitle);
this->panelHint =
pu::ui::elm::TextBlock::New(PanelX + space::lg, ContentY + space::lg + 48, "Pick an action:");
this->panelHint->SetFont(type::font(type::Body));
this->panelHint->SetColor(color::TextSecondary);
this->Add(this->panelHint);
int btnY = ContentY + 200;
this->btnTransferBg =
pu::ui::elm::Rectangle::New(PanelX + space::lg, btnY, BtnW, BtnH, color::BgSurface2, radius::md);
this->Add(this->btnTransferBg);
this->btnTransferText =
pu::ui::elm::TextBlock::New(PanelX + space::lg + space::md, btnY + 14, "Transfer to another device");
this->btnTransferText->SetFont(type::font(type::Body));
this->btnTransferText->SetColor(color::TextSecondary);
this->Add(this->btnTransferText);
int btnY2 = btnY + BtnH + space::md;
this->btnReceiveBg =
pu::ui::elm::Rectangle::New(PanelX + space::lg, btnY2, BtnW, BtnH, color::BgSurface2, radius::md);
this->Add(this->btnReceiveBg);
this->btnReceiveText = pu::ui::elm::TextBlock::New(PanelX + space::lg + space::md, btnY2 + 14,
"Receive from another device");
this->btnReceiveText->SetFont(type::font(type::Body));
this->btnReceiveText->SetColor(color::TextSecondary);
this->Add(this->btnReceiveText);
this->panelFooter = pu::ui::elm::TextBlock::New(PanelX + space::lg, ContentY + ContentH - space::lg - 18,
"Save data only");
this->panelFooter->SetFont(type::font(type::Caption));
this->panelFooter->SetColor(color::TextMuted);
this->Add(this->panelFooter);
this->emptyText = pu::ui::elm::TextBlock::New(ListX + ListW / 2 - 280, ContentY + ContentH / 2 - 40,
"No save data on this profile");
this->emptyText->SetFont(type::font(type::Display));
this->emptyText->SetColor(color::TextPrimary);
this->emptyText->SetVisible(false);
this->Add(this->emptyText);
this->emptySub = pu::ui::elm::TextBlock::New(ListX + ListW / 2 - 220, ContentY + ContentH / 2 + 16,
"Play something first, then come back.");
this->emptySub->SetFont(type::font(type::Body));
this->emptySub->SetColor(color::TextMuted);
this->emptySub->SetVisible(false);
this->Add(this->emptySub);
this->header = std::make_unique<HeaderBar>(this, "Save Transfer");
this->hints = std::make_unique<HintBar>(this);
this->updateHints();
}
void TitlesLayout::InitTitles(AccountUid uid) {
using namespace theme;
this->current_uid = uid;
auto it = this->menuCache.find(uid);
std::vector<pu::ui::elm::MenuItem::Ref>* items;
if (it != this->menuCache.end()) {
items = &it->second;
} else {
std::vector<pu::ui::elm::MenuItem::Ref> built;
for (size_t i = 0; i < getTitleCount(uid); i++) {
Title title;
getTitle(title, uid, i);
auto titleItem = pu::ui::elm::MenuItem::New(title.name().c_str());
titleItem->SetColor(color::TextPrimary);
built.push_back(titleItem);
}
auto inserted = this->menuCache.emplace(uid, std::move(built));
items = &inserted.first->second;
}
void TitlesLayout::refreshPanel() {
if (this->titlesMenu->GetItems().empty()) return;
int idx = this->titlesMenu->GetSelectedIndex();
Title title;
getTitle(title, this->current_uid, idx);
this->panelTitle->SetText(StringUtils::elide(title.name(), 24));
this->titlesMenu->ClearItems();
for (auto& item : *items) {
this->titlesMenu->AddItem(item);
}
this->titlesMenu->SetSelectedIndex(0);
void TitlesLayout::refreshButtons() {
using namespace theme;
const bool active = (focus == TitlesFocus::Actions);
if (active && action == TitlesAction::Transfer) {
this->btnTransferBg->SetColor(color::Primary);
this->btnTransferText->SetColor(color::TextPrimary);
this->btnReceiveBg->SetColor(color::BgSurface2);
this->btnReceiveText->SetColor(color::TextSecondary);
} else if (active && action == TitlesAction::Receive) {
this->btnTransferBg->SetColor(color::BgSurface2);
this->btnTransferText->SetColor(color::TextSecondary);
this->btnReceiveBg->SetColor(color::Accent);
this->btnReceiveText->SetColor(color::BgBase);
} else {
this->btnTransferBg->SetColor(color::BgSurface2);
this->btnTransferText->SetColor(color::TextSecondary);
this->btnReceiveBg->SetColor(color::BgSurface2);
this->btnReceiveText->SetColor(color::TextSecondary);
}
const bool empty = items->empty();
this->titlesMenu->SetVisible(!empty);
this->panelBg->SetVisible(!empty);
this->panelTitle->SetVisible(!empty);
this->panelHint->SetVisible(!empty);
this->btnTransferBg->SetVisible(!empty);
this->btnTransferText->SetVisible(!empty);
this->btnReceiveBg->SetVisible(!empty);
this->btnReceiveText->SetVisible(!empty);
this->panelFooter->SetVisible(!empty);
this->emptyText->SetVisible(empty);
this->emptySub->SetVisible(empty);
this->focus = TitlesFocus::List;
this->action = TitlesAction::Transfer;
this->refreshPanel();
this->refreshButtons();
this->updateHints();
this->header->SetUser(uid, Account::username(uid));
}
void TitlesLayout::refreshPanel() {
if (this->titlesMenu->GetItems().empty())
return;
int idx = this->titlesMenu->GetSelectedIndex();
Title title;
getTitle(title, this->current_uid, idx);
this->panelTitle->SetText(StringUtils::elide(title.name(), 24));
}
void TitlesLayout::refreshButtons() {
using namespace theme;
const bool active = (focus == TitlesFocus::Actions);
if (active && action == TitlesAction::Transfer) {
this->btnTransferBg->SetColor(color::Primary);
this->btnTransferText->SetColor(color::TextPrimary);
this->btnReceiveBg->SetColor(color::BgSurface2);
this->btnReceiveText->SetColor(color::TextSecondary);
} else if (active && action == TitlesAction::Receive) {
this->btnTransferBg->SetColor(color::BgSurface2);
this->btnTransferText->SetColor(color::TextSecondary);
this->btnReceiveBg->SetColor(color::Accent);
this->btnReceiveText->SetColor(color::BgBase);
} else {
this->btnTransferBg->SetColor(color::BgSurface2);
this->btnTransferText->SetColor(color::TextSecondary);
this->btnReceiveBg->SetColor(color::BgSurface2);
this->btnReceiveText->SetColor(color::TextSecondary);
}
}
void TitlesLayout::updateHints() {
if (focus == TitlesFocus::List) {
this->hints->SetHints({{"A", "Choose action"}, {"B", "Back"}, {"+", "Quit"}});
} else {
this->hints->SetHints({{"A", "Confirm"}, {"B", "Back"}});
}
void TitlesLayout::updateHints() {
if (focus == TitlesFocus::List) {
this->hints->SetHints({{"A", "Choose action"}, {"B", "Back"}, {"+", "Quit"}});
} else {
this->hints->SetHints({{"A", "Confirm"}, {"B", "Back"}});
}
}
void TitlesLayout::runTransfer(int index, Title& title) {
(void)title;
auto ovl = TransferOverlay::New("Transferring save data...");
this->titlesMenu->SetVisible(false);
mainApp->StartOverlay(ovl);
this->LockInput();
if (mainApp->transfer.startSend((size_t)index, this->current_uid) != 0) {
mainApp->EndOverlay();
this->titlesMenu->SetVisible(true);
this->UnlockInput();
mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true);
return;
}
while (!mainApp->transfer.isSendDone()) {
ovl->SetStatus(mainApp->transfer.sendStatusText());
ovl->SetProgressVisible(mainApp->transfer.isSendProgressKnown());
ovl->SetProgress(mainApp->transfer.sendProgress());
mainApp->CallForRender();
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
mainApp->transfer.cancelSend();
}
svcSleepThread(16666666LL);
}
void TitlesLayout::runTransfer(int index, Title& title) {
(void)title;
auto ovl = TransferOverlay::New("Transferring save data...");
this->titlesMenu->SetVisible(false);
mainApp->StartOverlay(ovl);
this->LockInput();
if (mainApp->transfer.startSend((size_t)index, this->current_uid) != 0) {
mainApp->EndOverlay();
this->titlesMenu->SetVisible(true);
this->UnlockInput();
if (mainApp->transfer.isSendConnectionFailed()) {
mainApp->CreateShowDialog("Transfer", mainApp->transfer.sendFailReason(), {"OK"}, true);
} else if (mainApp->transfer.isSendCancelled()) {
mainApp->CreateShowDialog("Transfer", "Transfer cancelled.", {"OK"}, true);
} else {
mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true);
}
mainApp->CreateShowDialog("Transfer", "Failed to start transfer.", {"OK"}, true);
return;
}
void TitlesLayout::runReceive(int index, Title& title) {
if (mainApp->transfer.startReceive((size_t)index, this->current_uid, title.name()) != 0) {
mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.", {"OK"}, true);
return;
}
auto ovl = TransferOverlay::New("Receiving save data...");
this->titlesMenu->SetVisible(false);
mainApp->StartOverlay(ovl);
this->LockInput();
while (!mainApp->transfer.isReceiveDone()) {
ovl->SetStatus(mainApp->transfer.receiveStatusText());
ovl->SetProgress(mainApp->transfer.receiveProgress());
mainApp->CallForRender();
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
mainApp->transfer.cancelReceive();
}
svcSleepThread(16666666LL);
}
mainApp->EndOverlay();
this->titlesMenu->SetVisible(true);
this->UnlockInput();
if (mainApp->transfer.isReceiveCancelled()) {
mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true);
} else if (mainApp->transfer.restoreSucceeded()) {
mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true);
} else {
mainApp->CreateShowDialog("Receive", "Restore failed:\n" + mainApp->transfer.restoreError(), {"OK"}, true);
}
}
void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
(void)Up; (void)Held; (void)Pos;
if (m_inputLocked) return;
if (Down & HidNpadButton_Plus) {
while (!mainApp->transfer.isSendDone()) {
ovl->SetStatus(mainApp->transfer.sendStatusText());
ovl->SetProgressVisible(mainApp->transfer.isSendProgressKnown());
ovl->SetProgress(mainApp->transfer.sendProgress());
mainApp->CallForRender();
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
mainApp->transfer.cancelSend();
}
svcSleepThread(16666666LL);
}
mainApp->EndOverlay();
this->titlesMenu->SetVisible(true);
this->UnlockInput();
if (mainApp->transfer.isSendConnectionFailed()) {
mainApp->CreateShowDialog("Transfer", mainApp->transfer.sendFailReason(), {"OK"}, true);
} else if (mainApp->transfer.isSendCancelled()) {
mainApp->CreateShowDialog("Transfer", "Transfer cancelled.", {"OK"}, true);
} else {
mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true);
}
}
void TitlesLayout::runReceive(int index, Title& title) {
if (mainApp->transfer.startReceive((size_t)index, this->current_uid, title.name()) != 0) {
mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.", {"OK"},
true);
return;
}
auto ovl = TransferOverlay::New("Receiving save data...");
this->titlesMenu->SetVisible(false);
mainApp->StartOverlay(ovl);
this->LockInput();
while (!mainApp->transfer.isReceiveDone()) {
ovl->SetStatus(mainApp->transfer.receiveStatusText());
ovl->SetProgress(mainApp->transfer.receiveProgress());
mainApp->CallForRender();
if (mainApp->GetButtonsDown() & HidNpadButton_B) {
mainApp->transfer.cancelReceive();
mainApp->Close();
}
svcSleepThread(16666666LL);
}
mainApp->EndOverlay();
this->titlesMenu->SetVisible(true);
this->UnlockInput();
if (mainApp->transfer.isReceiveCancelled()) {
mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true);
} else if (mainApp->transfer.restoreSucceeded()) {
mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true);
} else {
mainApp->CreateShowDialog("Receive", "Restore failed:\n" + mainApp->transfer.restoreError(), {"OK"},
true);
}
}
void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
(void)Up;
(void)Held;
(void)Pos;
if (m_inputLocked)
return;
if (Down & HidNpadButton_Plus) {
mainApp->transfer.cancelSend();
mainApp->transfer.cancelReceive();
mainApp->Close();
return;
}
if (focus == TitlesFocus::List) {
if (Down & HidNpadButton_B) {
this->header->SetUser(std::nullopt, "");
mainApp->LoadLayout(mainApp->users_layout);
return;
}
if (focus == TitlesFocus::List) {
if (Down & HidNpadButton_B) {
this->header->SetUser(std::nullopt, "");
mainApp->LoadLayout(mainApp->users_layout);
if (Down & HidNpadButton_A) {
if (this->titlesMenu->GetItems().empty())
return;
}
if (Down & HidNpadButton_A) {
if (this->titlesMenu->GetItems().empty()) return;
this->lockedListIndex = this->titlesMenu->GetSelectedIndex();
this->focus = TitlesFocus::Actions;
this->action = TitlesAction::Transfer;
this->refreshButtons();
this->updateHints();
return;
}
} else {
if (this->titlesMenu->GetSelectedIndex() != this->lockedListIndex) {
this->titlesMenu->SetSelectedIndex(this->lockedListIndex);
}
if (Down & HidNpadButton_B) {
this->focus = TitlesFocus::List;
this->refreshButtons();
this->updateHints();
return;
}
if (Down & (HidNpadButton_Up | HidNpadButton_Down |
HidNpadButton_StickLUp | HidNpadButton_StickLDown)) {
this->action = (action == TitlesAction::Transfer)
? TitlesAction::Receive : TitlesAction::Transfer;
this->refreshButtons();
return;
}
if (Down & HidNpadButton_A) {
int idx = this->titlesMenu->GetSelectedIndex();
Title title;
getTitle(title, this->current_uid, idx);
TitlesAction chosen = action;
this->focus = TitlesFocus::List;
this->refreshButtons();
this->updateHints();
if (chosen == TitlesAction::Transfer) {
this->runTransfer(idx, title);
} else {
this->runReceive(idx, title);
}
this->lockedListIndex = this->titlesMenu->GetSelectedIndex();
this->focus = TitlesFocus::Actions;
this->action = TitlesAction::Transfer;
this->refreshButtons();
this->updateHints();
return;
}
} else {
if (this->titlesMenu->GetSelectedIndex() != this->lockedListIndex) {
this->titlesMenu->SetSelectedIndex(this->lockedListIndex);
}
if (Down & HidNpadButton_B) {
this->focus = TitlesFocus::List;
this->refreshButtons();
this->updateHints();
return;
}
if (Down &
(HidNpadButton_Up | HidNpadButton_Down | HidNpadButton_StickLUp | HidNpadButton_StickLDown)) {
this->action =
(action == TitlesAction::Transfer) ? TitlesAction::Receive : TitlesAction::Transfer;
this->refreshButtons();
return;
}
if (Down & HidNpadButton_A) {
int idx = this->titlesMenu->GetSelectedIndex();
Title title;
getTitle(title, this->current_uid, idx);
TitlesAction chosen = action;
this->focus = TitlesFocus::List;
this->refreshButtons();
this->updateHints();
if (chosen == TitlesAction::Transfer) {
this->runTransfer(idx, title);
} else {
this->runReceive(idx, title);
}
}
}
}
} // namespace ui
+55 -61
View File
@@ -1,75 +1,69 @@
#include <nxst/app/main_application.hpp>
namespace ui {
extern MainApplication *mainApp;
extern MainApplication* mainApp;
UsersLayout::UsersLayout() : Layout::Layout() {
using namespace theme;
UsersLayout::UsersLayout() : Layout::Layout() {
using namespace theme;
this->usersMenu = pu::ui::elm::Menu::New(
0, layout::ContentTop + space::md,
layout::ScreenW,
color::BgBase, color::BgSurface2,
88, 6);
this->usersMenu->SetScrollbarColor(color::Primary);
this->usersMenu->SetItemsFocusColor(color::BgSurface2);
this->usersMenu = pu::ui::elm::Menu::New(0, layout::ContentTop + space::md, layout::ScreenW,
color::BgBase, color::BgSurface2, 88, 6);
this->usersMenu->SetScrollbarColor(color::Primary);
this->usersMenu->SetItemsFocusColor(color::BgSurface2);
for (AccountUid const& uid : Account::ids()) {
auto item = pu::ui::elm::MenuItem::New(Account::username(uid));
item->SetColor(color::TextPrimary);
this->usersMenu->AddItem(item);
}
this->loadingBg = pu::ui::elm::Rectangle::New(
0, 0, layout::ScreenW, layout::ScreenH, color::Scrim);
this->loadingBg->SetVisible(false);
this->loadingText = pu::ui::elm::TextBlock::New(
layout::ScreenW / 2 - 120,
layout::ScreenH / 2 - 12,
"Loading saves...");
this->loadingText->SetFont(type::font(type::Body));
this->loadingText->SetColor(color::TextSecondary);
this->loadingText->SetVisible(false);
this->SetBackgroundColor(color::BgBase);
this->Add(this->usersMenu);
this->Add(this->loadingBg);
this->Add(this->loadingText);
this->header = std::make_unique<HeaderBar>(this, "Select a user");
this->hints = std::make_unique<HintBar>(this);
this->hints->SetHints({{"A", "Select"}, {"+", "Quit"}});
for (AccountUid const& uid : Account::ids()) {
auto item = pu::ui::elm::MenuItem::New(Account::username(uid));
item->SetColor(color::TextPrimary);
this->usersMenu->AddItem(item);
}
int32_t UsersLayout::GetCurrentIndex() {
return this->usersMenu->GetSelectedIndex();
this->loadingBg = pu::ui::elm::Rectangle::New(0, 0, layout::ScreenW, layout::ScreenH, color::Scrim);
this->loadingBg->SetVisible(false);
this->loadingText =
pu::ui::elm::TextBlock::New(layout::ScreenW / 2 - 120, layout::ScreenH / 2 - 12, "Loading saves...");
this->loadingText->SetFont(type::font(type::Body));
this->loadingText->SetColor(color::TextSecondary);
this->loadingText->SetVisible(false);
this->SetBackgroundColor(color::BgBase);
this->Add(this->usersMenu);
this->Add(this->loadingBg);
this->Add(this->loadingText);
this->header = std::make_unique<HeaderBar>(this, "Select a user");
this->hints = std::make_unique<HintBar>(this);
this->hints->SetHints({{"A", "Select"}, {"+", "Quit"}});
}
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;
}
void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
if (Down & HidNpadButton_Plus) {
mainApp->Close();
return;
if (Down & HidNpadButton_A) {
AccountUid uid = Account::ids().at(this->usersMenu->GetSelectedIndex());
if (!areTitlesLoaded()) {
this->usersMenu->SetVisible(false);
this->loadingBg->SetVisible(true);
this->loadingText->SetVisible(true);
mainApp->CallForRender();
loadTitles();
this->loadingBg->SetVisible(false);
this->loadingText->SetVisible(false);
this->usersMenu->SetVisible(true);
}
if (Down & HidNpadButton_A) {
AccountUid uid = Account::ids().at(this->usersMenu->GetSelectedIndex());
if (!areTitlesLoaded()) {
this->usersMenu->SetVisible(false);
this->loadingBg->SetVisible(true);
this->loadingText->SetVisible(true);
mainApp->CallForRender();
loadTitles();
this->loadingBg->SetVisible(false);
this->loadingText->SetVisible(false);
this->usersMenu->SetVisible(true);
}
mainApp->titles_layout->InitTitles(uid);
mainApp->LoadLayout(mainApp->titles_layout);
}
mainApp->titles_layout->InitTitles(uid);
mainApp->LoadLayout(mainApp->titles_layout);
}
}
} // namespace ui