Compare commits

..

10 Commits

Author SHA1 Message Date
DragonSpirit d410c4355d another stage of refactoring
CI / Build NRO (push) Has been cancelled
CI / Format check (push) Has been cancelled
CI / Layering check (push) Has been cancelled
2026-05-12 09:59:43 +03:00
DragonSpirit 6f8ede035f refactor: phase 6 cleanup — RAII handles and io::restore split
- AccountProfileHandle RAII wrapper in handles.hpp; applied in
  account.cpp (getUser, iconPath) replacing manual accountProfileClose
- FILE* in iconPath replaced with existing FileHandle RAII wrapper
- io::restore split into clearSaveRoot + extractAndCommit static helpers;
  public signature unchanged
- sendAll/recvAll kept as bool — callers don't propagate the error string,
  Result<void> would add noise without benefit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 14:56:38 +03:00
DragonSpirit 836956394a chore: remove stale deps/** linguist-vendored from .gitattributes
CI / Build NRO (push) Successful in 31s
CI / Format check (push) Successful in 50s
CI / Layering check (push) Successful in 2s
deps/ directory was removed with asprintf cleanup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 13:02:30 +03:00
DragonSpirit 9b18a32b0c build: replace Plutonium submodule with ExternalProject_Add fetch
Plutonium is now downloaded at build time via ExternalProject_Add
(GIT_REPOSITORY + GIT_TAG) instead of being a git submodule.
Removes lib/, .gitmodules, and the linguist-vendored entry for it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-03 13:00:36 +03:00
DragonSpirit 82df796a4a build: migrate Plutonium to ExternalProject_Add + IMPORTED target
CI / Build NRO (push) Successful in 36s
CI / Format check (push) Successful in 48s
CI / Layering check (push) Successful in 1s
Replaces add_custom_command/add_custom_target with ExternalProject_Add,
which correctly declares BUILD_BYPRODUCTS for Ninja and propagates include
paths via INTERFACE_INCLUDE_DIRECTORIES on the IMPORTED STATIC target.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 20:57:27 +03:00
DragonSpirit 33a1ce73af chore: remove vendored deps/asprintf
CI / Build NRO (push) Successful in 2m54s
CI / Format check (push) Successful in 5m18s
CI / Layering check (push) Successful in 2s
vasprintf/asprintf are provided by devkitpro newlib (<stdio.h>).
deps/asprintf was never compiled by CMake (GLOB_RECURSE covers src/ only).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 20:49:42 +03:00
DragonSpirit 9339e7dbfe finish refactor, add docs and CI
CI / Build NRO (push) Successful in 29s
CI / Format check (push) Successful in 11s
CI / Layering check (push) Successful in 1s
2026-04-27 03:22:12 +03:00
DragonSpirit dc65a4c8a9 refactor: phases 5 & 6 — TransferService, Result<T>, RAII
Phase 5 — TransferService extraction:
- New nxst::TransferService owns all sender/receiver state, threads, and atomics
  (previously 11 file-statics across transfer_sender.cpp + transfer_receiver.cpp)
- startReceive() calls io::restore internally; UI never touches infra/fs or infra/net
- TitlesLayout routes all transfer ops through mainApp->transfer
- g_currentUId removed from global scope; TitlesLayout::InitTitles(AccountUid)
- MainApplication gains transfer member; layout refs renamed to snake_case
- userAppExit() updated to cancel via service

Phase 6 — Result<T> + RAII:
- include/nxst/domain/result.hpp: 85-line tagged-union Result<T,E> + Result<void,E>
- include/nxst/infra/fs/handles.hpp: FsFileSystemHandle (auto fsFsClose),
  FileHandle (auto fclose) — eliminates manual close on every error path
- io::backup / io::restore return nxst::Result<std::string> (was tuple<bool,Result,string>)
- new u8[] + malloc in copyFile/restore replaced with std::vector<u8>
- NACP save-creation extracted to createSaveIfNeeded() helper in io.cpp

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 01:27:16 +03:00
DragonSpirit 895fee6235 phase 5: transfer service 2026-04-27 01:21:16 +03:00
DragonSpirit b5c506cf03 refactor: phase 0 & 1 2026-04-26 20:02:15 +03:00
14 changed files with 66 additions and 65 deletions
+2 -2
View File
@@ -27,10 +27,10 @@ struct User {
std::string name; std::string name;
}; };
namespace account { namespace Account {
Result init(); Result init();
void exit(); void exit();
std::vector<AccountUid> ids(); std::vector<AccountUid> ids();
std::string username(AccountUid id); std::string username(AccountUid id);
std::string iconPath(AccountUid id); std::string iconPath(AccountUid id);
} // namespace account } // namespace Account
+2 -2
View File
@@ -7,7 +7,7 @@ void servicesExit();
Result servicesInit(); Result servicesInit();
void blinkLed(u8 times); void blinkLed(u8 times);
namespace string_utils { namespace StringUtils {
bool containsInvalidChar(const std::string& str); bool containsInvalidChar(const std::string& str);
std::string format(const char* fmt, ...) __attribute__((format(printf, 1, 2))); std::string format(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
std::string removeForbiddenCharacters(std::string src); std::string removeForbiddenCharacters(std::string src);
@@ -19,4 +19,4 @@ namespace string_utils {
std::string removeNotAscii(std::string str); std::string removeNotAscii(std::string str);
std::u16string UTF8toUTF16(const char* src); std::u16string UTF8toUTF16(const char* src);
std::string elide(const std::string& s, size_t max_chars); std::string elide(const std::string& s, size_t max_chars);
} // namespace string_utils } // namespace StringUtils
+2 -2
View File
@@ -2,8 +2,8 @@
#pragma once #pragma once
#include <switch.h> #include <switch.h>
namespace file_system { namespace FileSystem {
Result mount(FsFileSystem* fs, u64 title_id, AccountUid uid); Result mount(FsFileSystem* fs, u64 title_id, AccountUid uid);
int mount(FsFileSystem fs); int mount(FsFileSystem fs);
void unmount(); void unmount();
} // namespace file_system } // namespace FileSystem
+1
View File
@@ -1,5 +1,6 @@
#pragma once #pragma once
// New API — use these going forward. // New API — use these going forward.
namespace nxst::log { namespace nxst::log {
+1 -1
View File
@@ -61,7 +61,7 @@ namespace ui {
userName->SetVisible(show); userName->SetVisible(show);
if (show) { if (show) {
userName->SetText(name); userName->SetText(name);
std::string path = account::iconPath(*uid); std::string path = Account::iconPath(*uid);
if (!path.empty()) { if (!path.empty()) {
avatar->SetImage(path); avatar->SetImage(path);
avatar->SetWidth(32); avatar->SetWidth(32);
+1 -1
View File
@@ -67,7 +67,7 @@ namespace ui {
PU_SMART_CTOR(TransferOverlay) PU_SMART_CTOR(TransferOverlay)
void SetStatus(const std::string& status) { void SetStatus(const std::string& status) {
statusText->SetText(string_utils::elide(status, 56)); statusText->SetText(StringUtils::elide(status, 56));
} }
void SetProgress(double val) { void SetProgress(double val) {
+5 -5
View File
@@ -10,7 +10,7 @@
static std::map<AccountUid, User> s_users; static std::map<AccountUid, User> s_users;
Result account::init() { Result Account::init() {
Result res = accountInitialize(AccountServiceType_Application); Result res = accountInitialize(AccountServiceType_Application);
if (R_FAILED(res)) if (R_FAILED(res))
return res; return res;
@@ -24,11 +24,11 @@ Result account::init() {
return 0; return 0;
} }
void account::exit() { void Account::exit() {
accountExit(); accountExit();
} }
std::vector<AccountUid> account::ids() { std::vector<AccountUid> Account::ids() {
std::vector<AccountUid> result; std::vector<AccountUid> result;
result.reserve(s_users.size()); result.reserve(s_users.size());
for (const auto& pair : s_users) { for (const auto& pair : s_users) {
@@ -51,7 +51,7 @@ static User fetchUser(AccountUid id) {
return user; return user;
} }
std::string account::username(AccountUid id) { std::string Account::username(AccountUid id) {
auto it = s_users.find(id); auto it = s_users.find(id);
if (it == s_users.end()) { if (it == s_users.end()) {
User user = fetchUser(id); User user = fetchUser(id);
@@ -61,7 +61,7 @@ std::string account::username(AccountUid id) {
return it->second.name; return it->second.name;
} }
std::string account::iconPath(AccountUid id) { std::string Account::iconPath(AccountUid id) {
char path[128]; 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]);
+12 -11
View File
@@ -4,6 +4,7 @@
#include <vector> #include <vector>
#include "nxst/domain/account.hpp" #include "nxst/domain/account.hpp"
#include <nxst/domain/util.hpp>
#include <nxst/domain/title.hpp> #include <nxst/domain/title.hpp>
#include <nxst/domain/util.hpp> #include <nxst/domain/util.hpp>
#include <nxst/infra/fs/directory.hpp> #include <nxst/infra/fs/directory.hpp>
@@ -21,22 +22,22 @@ void Title::init(u8 save_data_type, u64 title_id, AccountUid uid, const std::str
m_id = title_id; m_id = title_id;
m_uid = uid; m_uid = uid;
m_save_data_type = save_data_type; m_save_data_type = save_data_type;
m_user_name = account::username(uid); m_user_name = Account::username(uid);
m_author = author; m_author = author;
m_name = name; m_name = name;
m_safe_name = string_utils::containsInvalidChar(name) ? string_utils::format("0x%016llX", m_id) m_safe_name = StringUtils::containsInvalidChar(name) ? StringUtils::format("0x%016llX", m_id)
: string_utils::removeForbiddenCharacters(name); : StringUtils::removeForbiddenCharacters(name);
m_path = "sdmc:/switch/NXST/saves/" + string_utils::format("0x%016llX", m_id) + " " + m_safe_name; m_path = "sdmc:/switch/NXST/saves/" + StringUtils::format("0x%016llX", m_id) + " " + m_safe_name;
std::string aname = string_utils::removeAccents(m_name); std::string aname = StringUtils::removeAccents(m_name);
m_display_name = {aname, ""}; m_display_name = {aname, ""};
size_t colon = aname.rfind(':'); size_t colon = aname.rfind(':');
if (colon != std::string::npos) { if (colon != std::string::npos) {
std::string head = aname.substr(0, colon); std::string head = aname.substr(0, colon);
std::string tail = aname.substr(colon + 1); std::string tail = aname.substr(colon + 1);
string_utils::trim(head); StringUtils::trim(head);
string_utils::trim(tail); StringUtils::trim(tail);
m_display_name = {head, tail}; m_display_name = {head, tail};
} else { } else {
size_t open = aname.rfind('('); size_t open = aname.rfind('(');
@@ -44,8 +45,8 @@ void Title::init(u8 save_data_type, u64 title_id, AccountUid uid, const std::str
if (open != std::string::npos && close != std::string::npos && close > open) { if (open != std::string::npos && close != std::string::npos && close > open) {
std::string head = aname.substr(0, open); std::string head = aname.substr(0, open);
std::string paren = aname.substr(open + 1, close - open - 1); std::string paren = aname.substr(open + 1, close - open - 1);
string_utils::trim(head); StringUtils::trim(head);
string_utils::trim(paren); StringUtils::trim(paren);
m_display_name = {head, paren}; m_display_name = {head, paren};
} }
} }
@@ -104,7 +105,7 @@ void Title::lastPlayedTimestamp(u32 ts) {
std::string Title::playTime() const { std::string Title::playTime() const {
const u64 minutes = m_play_time_ns / 60000000000ULL; const u64 minutes = m_play_time_ns / 60000000000ULL;
return string_utils::format("%d", minutes / 60) + ":" + string_utils::format("%02d", minutes % 60) + return StringUtils::format("%d", minutes / 60) + ":" + StringUtils::format("%02d", minutes % 60) +
" hours"; " hours";
} }
@@ -236,7 +237,7 @@ std::unordered_map<std::string, std::string> getCompleteTitleList() {
std::unordered_map<std::string, std::string> map; std::unordered_map<std::string, std::string> map;
for (const auto& pair : titles) { for (const auto& pair : titles) {
for (const auto& title : pair.second) { for (const auto& title : pair.second) {
map.emplace(string_utils::format("0x%016llX", title.id()), title.name()); map.emplace(StringUtils::format("0x%016llX", title.id()), title.name());
} }
} }
return map; return map;
+15 -15
View File
@@ -14,7 +14,7 @@
static bool s_notification_led_available = false; static bool s_notification_led_available = false;
void servicesExit() { void servicesExit() {
account::exit(); Account::exit();
plExit(); plExit();
romfsExit(); romfsExit();
} }
@@ -37,8 +37,8 @@ Result servicesInit() {
nxst::log::error("plInitialize failed. Result code 0x%08X.", res); nxst::log::error("plInitialize failed. Result code 0x%08X.", res);
return res; return res;
} }
if (R_FAILED(res = account::init())) { if (R_FAILED(res = Account::init())) {
nxst::log::error("account::init failed. Result code 0x%08X.", res); nxst::log::error("Account::init failed. Result code 0x%08X.", res);
return res; return res;
} }
if (R_FAILED(res = nsInitialize())) { if (R_FAILED(res = nsInitialize())) {
@@ -55,7 +55,7 @@ Result servicesInit() {
return 0; return 0;
} }
bool string_utils::containsInvalidChar(const std::string& str) { bool StringUtils::containsInvalidChar(const std::string& str) {
for (unsigned char c : str) { for (unsigned char c : str) {
if (!isascii(c)) if (!isascii(c))
return true; return true;
@@ -63,7 +63,7 @@ bool string_utils::containsInvalidChar(const std::string& str) {
return false; return false;
} }
std::string string_utils::format(const char* fmt, ...) { std::string StringUtils::format(const char* fmt, ...) {
va_list a1, a2; va_list a1, a2;
va_start(a1, fmt); va_start(a1, fmt);
va_copy(a2, a1); va_copy(a2, a1);
@@ -79,7 +79,7 @@ std::string string_utils::format(const char* fmt, ...) {
return buf; return buf;
} }
std::string string_utils::removeForbiddenCharacters(std::string src) { std::string StringUtils::removeForbiddenCharacters(std::string src) {
static constexpr std::string_view kForbidden = ".,!\\/:?*\"<>|"; static constexpr std::string_view kForbidden = ".,!\\/:?*\"<>|";
for (char& c : src) { for (char& c : src) {
if (kForbidden.find(c) != std::string_view::npos) if (kForbidden.find(c) != std::string_view::npos)
@@ -114,7 +114,7 @@ static size_t encodeUtf8(char* out, char32_t cp) {
return 4; return 4;
} }
std::string string_utils::UTF16toUTF8(const std::u16string& src) { std::string StringUtils::UTF16toUTF8(const std::u16string& src) {
std::string result; std::string result;
result.reserve(src.size() * 2); result.reserve(src.size() * 2);
for (size_t i = 0; i < src.size(); ++i) { for (size_t i = 0; i < src.size(); ++i) {
@@ -128,13 +128,13 @@ std::string string_utils::UTF16toUTF8(const std::u16string& src) {
return result; return result;
} }
void string_utils::ltrim(std::string& s) { void StringUtils::ltrim(std::string& s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char c) { s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char c) {
return !std::isspace(c); return !std::isspace(c);
})); }));
} }
void string_utils::rtrim(std::string& s) { void StringUtils::rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(), s.erase(std::find_if(s.rbegin(), s.rend(),
[](unsigned char c) { [](unsigned char c) {
return !std::isspace(c); return !std::isspace(c);
@@ -143,13 +143,13 @@ void string_utils::rtrim(std::string& s) {
s.end()); s.end());
} }
void string_utils::trim(std::string& s) { void StringUtils::trim(std::string& s) {
ltrim(s); ltrim(s);
rtrim(s); rtrim(s);
} }
// Decodes a UTF-8 string to UTF-16, handling surrogate pairs for codepoints > U+FFFF. // Decodes a UTF-8 string to UTF-16, handling surrogate pairs for codepoints > U+FFFF.
std::u16string string_utils::UTF8toUTF16(const char* src) { std::u16string StringUtils::UTF8toUTF16(const char* src) {
std::u16string result; std::u16string result;
while (*src != '\0') { while (*src != '\0') {
char32_t cp = 0; char32_t cp = 0;
@@ -181,7 +181,7 @@ std::u16string string_utils::UTF8toUTF16(const char* src) {
} }
// Replaces Latin characters with diacritics with their ASCII base equivalents. // Replaces Latin characters with diacritics with their ASCII base equivalents.
std::string string_utils::removeAccents(std::string str) { std::string StringUtils::removeAccents(std::string str) {
static const std::unordered_map<char16_t, char16_t> kMap = { static const std::unordered_map<char16_t, char16_t> kMap = {
{u'À', u'A'}, {u'Á', u'A'}, {u'Â', u'A'}, {u'Ã', u'A'}, {u'Ä', u'A'}, {u'Å', u'A'}, {u'Æ', u'E'}, {u'À', u'A'}, {u'Á', u'A'}, {u'Â', u'A'}, {u'Ã', u'A'}, {u'Ä', u'A'}, {u'Å', u'A'}, {u'Æ', u'E'},
{u'Ç', u'C'}, {u'È', u'E'}, {u'É', u'E'}, {u'Ê', u'E'}, {u'Ë', u'E'}, {u'Ì', u'I'}, {u'Í', u'I'}, {u'Ç', u'C'}, {u'È', u'E'}, {u'É', u'E'}, {u'Ê', u'E'}, {u'Ë', u'E'}, {u'Ì', u'I'}, {u'Í', u'I'},
@@ -199,10 +199,10 @@ std::string string_utils::removeAccents(std::string str) {
if (it != kMap.end()) if (it != kMap.end())
ch = it->second; ch = it->second;
} }
return string_utils::UTF16toUTF8(wide); return StringUtils::UTF16toUTF8(wide);
} }
std::string string_utils::removeNotAscii(std::string str) { std::string StringUtils::removeNotAscii(std::string str) {
for (char& c : str) { for (char& c : str) {
if (!isascii(static_cast<unsigned char>(c))) if (!isascii(static_cast<unsigned char>(c)))
c = ' '; c = ' ';
@@ -210,7 +210,7 @@ std::string string_utils::removeNotAscii(std::string str) {
return str; return str;
} }
std::string string_utils::elide(const std::string& s, size_t max_chars) { std::string StringUtils::elide(const std::string& s, size_t max_chars) {
if (s.size() <= max_chars || max_chars < 6) if (s.size() <= max_chars || max_chars < 6)
return s; return s;
size_t budget = max_chars - 3; size_t budget = max_chars - 3;
+3 -3
View File
@@ -1,14 +1,14 @@
// Copyright (C) 2024-2026 NXST contributors // Copyright (C) 2024-2026 NXST contributors
#include <nxst/infra/fs/filesystem.hpp> #include <nxst/infra/fs/filesystem.hpp>
Result file_system::mount(FsFileSystem* fs, u64 title_id, AccountUid uid) { Result FileSystem::mount(FsFileSystem* fs, u64 title_id, AccountUid uid) {
return fsOpen_SaveData(fs, title_id, uid); return fsOpen_SaveData(fs, title_id, uid);
} }
int file_system::mount(FsFileSystem fs) { int FileSystem::mount(FsFileSystem fs) {
return fsdevMountDevice("save", fs); return fsdevMountDevice("save", fs);
} }
void file_system::unmount() { void FileSystem::unmount() {
fsdevUnmountDevice("save"); fsdevUnmountDevice("save");
} }
+16 -17
View File
@@ -136,7 +136,7 @@ nxst::Result<std::string> io::backup(size_t index, AccountUid uid) {
title.id(), title.userId().uid[1], title.userId().uid[0]); title.id(), title.userId().uid[1], title.userId().uid[0]);
nxst::FsFileSystemHandle fs_handle; nxst::FsFileSystemHandle fs_handle;
Result res = file_system::mount(fs_handle.get(), title.id(), title.userId()); Result res = FileSystem::mount(fs_handle.get(), title.id(), title.userId());
if (R_FAILED(res)) { if (R_FAILED(res)) {
nxst::log::error("Failed to mount filesystem during backup with result 0x%08X. " nxst::log::error("Failed to mount filesystem during backup with result 0x%08X. "
"Title id: 0x%016lX; User id: 0x%lX%lX.", "Title id: 0x%016lX; User id: 0x%lX%lX.",
@@ -145,16 +145,16 @@ nxst::Result<std::string> io::backup(size_t index, AccountUid uid) {
} }
fs_handle.valid = true; fs_handle.valid = true;
if (file_system::mount(*fs_handle.get()) == -1) { if (FileSystem::mount(*fs_handle.get()) == -1) {
nxst::log::error("Failed to mount devfs during backup. Title id: 0x%016lX; User id: 0x%lX%lX.", nxst::log::error("Failed to mount devfs during backup. Title id: 0x%016lX; User id: 0x%lX%lX.",
title.id(), title.userId().uid[1], title.userId().uid[0]); title.id(), title.userId().uid[1], title.userId().uid[0]);
file_system::unmount(); FileSystem::unmount();
return nxst::Result<std::string>::failure("Failed to mount save."); return nxst::Result<std::string>::failure("Failed to mount save.");
} }
fs_handle.release(); // devfs now owns the kernel handle fs_handle.release(); // devfs now owns the kernel handle
std::string suggestion = std::string suggestion =
string_utils::removeNotAscii(string_utils::removeAccents(account::username(title.userId()))); StringUtils::removeNotAscii(StringUtils::removeAccents(Account::username(title.userId())));
io::createDirectory(title.path()); io::createDirectory(title.path());
std::string dst_path = title.path() + "/" + suggestion; std::string dst_path = title.path() + "/" + suggestion;
@@ -166,13 +166,13 @@ nxst::Result<std::string> io::backup(size_t index, AccountUid uid) {
} }
res = io::createDirectory(tmp_path); res = io::createDirectory(tmp_path);
if (R_FAILED(res)) { if (R_FAILED(res)) {
file_system::unmount(); FileSystem::unmount();
nxst::log::error("Failed to create tmp dir %s.", tmp_path.c_str()); nxst::log::error("Failed to create tmp dir %s.", tmp_path.c_str());
return nxst::Result<std::string>::failure("Failed to create tmp directory."); return nxst::Result<std::string>::failure("Failed to create tmp directory.");
} }
res = copyDirectory("save:/", tmp_path + "/"); res = copyDirectory("save:/", tmp_path + "/");
if (R_FAILED(res)) { if (R_FAILED(res)) {
file_system::unmount(); FileSystem::unmount();
io::deleteFolderRecursively(tmp_path + "/"); io::deleteFolderRecursively(tmp_path + "/");
nxst::log::error("Failed to copy directory to %s with result 0x%08X.", tmp_path.c_str(), res); nxst::log::error("Failed to copy directory to %s with result 0x%08X.", tmp_path.c_str(), res);
return nxst::Result<std::string>::failure("Failed to backup save."); return nxst::Result<std::string>::failure("Failed to backup save.");
@@ -183,13 +183,13 @@ nxst::Result<std::string> io::backup(size_t index, AccountUid uid) {
nxst::log::warn("Failed to remove old backup at %s.", dst_path.c_str()); nxst::log::warn("Failed to remove old backup at %s.", dst_path.c_str());
} }
if (rename(tmp_path.c_str(), dst_path.c_str()) != 0) { if (rename(tmp_path.c_str(), dst_path.c_str()) != 0) {
file_system::unmount(); FileSystem::unmount();
nxst::log::error("Failed to rename temp backup to %s.", dst_path.c_str()); nxst::log::error("Failed to rename temp backup to %s.", dst_path.c_str());
return nxst::Result<std::string>::failure("Failed to finalise backup."); return nxst::Result<std::string>::failure("Failed to finalise backup.");
} }
refreshDirectories(title.id()); refreshDirectories(title.id());
file_system::unmount(); FileSystem::unmount();
nxst::log::info("Backup succeeded."); nxst::log::info("Backup succeeded.");
return nxst::Result<std::string>::success(dst_path); return nxst::Result<std::string>::success(dst_path);
@@ -268,7 +268,7 @@ nxst::Result<std::string> io::restore(size_t index, AccountUid uid, const std::s
createSaveIfNeeded(title.id(), uid); createSaveIfNeeded(title.id(), uid);
nxst::FsFileSystemHandle fs_handle; nxst::FsFileSystemHandle fs_handle;
Result res = file_system::mount(fs_handle.get(), title.id(), uid); Result res = FileSystem::mount(fs_handle.get(), title.id(), uid);
if (R_FAILED(res)) { if (R_FAILED(res)) {
nxst::log::error("Failed to mount filesystem during restore with result 0x%08X. " nxst::log::error("Failed to mount filesystem during restore with result 0x%08X. "
"Title id: 0x%016lX; User id: 0x%lX%lX.", "Title id: 0x%016lX; User id: 0x%lX%lX.",
@@ -277,23 +277,22 @@ nxst::Result<std::string> io::restore(size_t index, AccountUid uid, const std::s
} }
fs_handle.valid = true; fs_handle.valid = true;
if (file_system::mount(*fs_handle.get()) == -1) { if (FileSystem::mount(*fs_handle.get()) == -1) {
nxst::log::error("Failed to mount devfs during restore. Title id: 0x%016lX; User id: 0x%lX%lX.", nxst::log::error("Failed to mount devfs during restore. Title id: 0x%016lX; User id: 0x%lX%lX.",
title.id(), uid.uid[1], uid.uid[0]); title.id(), uid.uid[1], uid.uid[0]);
file_system::unmount(); FileSystem::unmount();
return nxst::Result<std::string>::failure("Failed to mount save."); return nxst::Result<std::string>::failure("Failed to mount save.");
} }
fs_handle.release(); // devfs now owns the kernel handle fs_handle.release(); // devfs now owns the kernel handle
std::string suggestion = std::string suggestion = StringUtils::removeNotAscii(StringUtils::removeAccents(Account::username(uid)));
string_utils::removeNotAscii(string_utils::removeAccents(account::username(uid)));
std::string src_path = title.path() + "/" + suggestion + "/"; std::string src_path = title.path() + "/" + suggestion + "/";
const std::string dst_path = "save:/"; const std::string dst_path = "save:/";
{ {
Directory src_check(src_path); Directory src_check(src_path);
if (!src_check.good() || src_check.size() == 0) { if (!src_check.good() || src_check.size() == 0) {
file_system::unmount(); FileSystem::unmount();
nxst::log::error("Restore source is empty or missing: %s", src_path.c_str()); nxst::log::error("Restore source is empty or missing: %s", src_path.c_str());
return nxst::Result<std::string>::failure("Restore source is empty or missing."); return nxst::Result<std::string>::failure("Restore source is empty or missing.");
} }
@@ -301,18 +300,18 @@ nxst::Result<std::string> io::restore(size_t index, AccountUid uid, const std::s
auto clear_res = clearSaveRoot(dst_path); auto clear_res = clearSaveRoot(dst_path);
if (!clear_res.isOk()) { if (!clear_res.isOk()) {
file_system::unmount(); FileSystem::unmount();
return nxst::Result<std::string>::failure(clear_res.error()); return nxst::Result<std::string>::failure(clear_res.error());
} }
auto extract_res = extractAndCommit(src_path, dst_path); auto extract_res = extractAndCommit(src_path, dst_path);
if (!extract_res.isOk()) { if (!extract_res.isOk()) {
file_system::unmount(); FileSystem::unmount();
return nxst::Result<std::string>::failure(extract_res.error()); return nxst::Result<std::string>::failure(extract_res.error());
} }
blinkLed(4); blinkLed(4);
file_system::unmount(); FileSystem::unmount();
nxst::log::info("Restore succeeded."); nxst::log::info("Restore succeeded.");
return nxst::Result<std::string>::success(title_name + "\nhas been restored successfully."); return nxst::Result<std::string>::success(title_name + "\nhas been restored successfully.");
+1 -1
View File
@@ -319,7 +319,7 @@ namespace nxst {
std::string TransferService::replaceUsername(const std::string& file_path) const { std::string TransferService::replaceUsername(const std::string& file_path) const {
#ifdef __SWITCH__ #ifdef __SWITCH__
std::string username = std::string username =
string_utils::removeNotAscii(string_utils::removeAccents(account::username(restore_uid))); StringUtils::removeNotAscii(StringUtils::removeAccents(Account::username(restore_uid)));
size_t last_slash = file_path.rfind('/'); size_t last_slash = file_path.rfind('/');
if (last_slash == std::string::npos) if (last_slash == std::string::npos)
return file_path; return file_path;
+2 -2
View File
@@ -135,7 +135,7 @@ namespace ui {
this->refreshButtons(); this->refreshButtons();
this->updateHints(); this->updateHints();
this->header->SetUser(uid, account::username(uid)); this->header->SetUser(uid, Account::username(uid));
} }
void TitlesLayout::refreshPanel() { void TitlesLayout::refreshPanel() {
@@ -144,7 +144,7 @@ namespace ui {
int idx = this->titlesMenu->GetSelectedIndex(); int idx = this->titlesMenu->GetSelectedIndex();
Title title; Title title;
getTitle(title, this->current_uid, idx); getTitle(title, this->current_uid, idx);
this->panelTitle->SetText(string_utils::elide(title.name(), 24)); this->panelTitle->SetText(StringUtils::elide(title.name(), 24));
} }
void TitlesLayout::refreshButtons() { void TitlesLayout::refreshButtons() {
+3 -3
View File
@@ -11,8 +11,8 @@ namespace ui {
this->usersMenu->SetScrollbarColor(color::Primary); this->usersMenu->SetScrollbarColor(color::Primary);
this->usersMenu->SetItemsFocusColor(color::BgSurface2); this->usersMenu->SetItemsFocusColor(color::BgSurface2);
for (AccountUid const& uid : account::ids()) { for (AccountUid const& uid : Account::ids()) {
auto item = pu::ui::elm::MenuItem::New(account::username(uid)); auto item = pu::ui::elm::MenuItem::New(Account::username(uid));
item->SetColor(color::TextPrimary); item->SetColor(color::TextPrimary);
this->usersMenu->AddItem(item); this->usersMenu->AddItem(item);
} }
@@ -47,7 +47,7 @@ namespace ui {
} }
if (Down & HidNpadButton_A) { if (Down & HidNpadButton_A) {
AccountUid uid = account::ids().at(this->usersMenu->GetSelectedIndex()); AccountUid uid = Account::ids().at(this->usersMenu->GetSelectedIndex());
if (!areTitlesLoaded()) { if (!areTitlesLoaded()) {
this->usersMenu->SetVisible(false); this->usersMenu->SetVisible(false);