Compare commits

..

1 Commits

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