another stage of refactoring
CI / Build NRO (push) Successful in 30s
CI / Format check (push) Failing after 33s
CI / Layering check (push) Successful in 1s

This commit is contained in:
2026-05-12 09:59:43 +03:00
parent 6f8ede035f
commit 898e127c1d
45 changed files with 1982 additions and 2663 deletions
+1
View File
@@ -26,3 +26,4 @@ IncludeCategories:
SpacesBeforeTrailingComments: 2 SpacesBeforeTrailingComments: 2
Cpp11BracedListStyle: true Cpp11BracedListStyle: true
Standard: c++17 Standard: c++17
NamespaceIndentation: All
+1
View File
@@ -1,5 +1,6 @@
.DS_Store .DS_Store
.serena .serena
.idea/
# Prerequisites # Prerequisites
*.d *.d
-115
View File
@@ -1,115 +0,0 @@
# Phase 6 cleanup — unfinished items
Leftover from the architectural refactor. All items are small, contained, and build on
the already-present `Result<T>` + RAII infrastructure.
## Status
| # | Item | File(s) | Status |
|---|------|---------|--------|
| 1 | `AccountProfileHandle` RAII + apply in `account.cpp` | `handles.hpp`, `account.cpp` | [x] |
| 2 | `FILE*` in `account.cpp::iconPath``FileHandle` | `account.cpp` | [x] |
| 3 | Split `io::restore` into three focused functions | `io.cpp`, `io.hpp` | [x] |
| 4 | `sendAll`/`recvAll``nxst::Result<void>` | `transfer_service.cpp` | dropped — callers don't propagate error, `bool` is correct |
---
## Item 1 — `AccountProfileHandle` RAII
**File:** `include/nxst/infra/fs/handles.hpp`
Add after `FileHandle`:
```cpp
// RAII wrapper for AccountProfile — auto-closes on destruction.
struct AccountProfileHandle {
AccountProfile profile{};
bool valid{false};
AccountProfileHandle() = default;
~AccountProfileHandle() {
if (valid)
accountProfileClose(&profile);
}
AccountProfileHandle(const AccountProfileHandle&) = delete;
AccountProfileHandle& operator=(const AccountProfileHandle&) = delete;
AccountProfile* get() { return &profile; }
};
```
**Apply in `account.cpp`:**
`getUser()` (line ~62): replace raw `AccountProfile profile` + manual `accountProfileClose` with `AccountProfileHandle`.
`iconPath()` (line ~98): same — wrap `AccountProfile profile``AccountProfileHandle`.
---
## Item 2 — `FILE*` in `iconPath` → `FileHandle`
**File:** `src/domain/account.cpp`, lines ~115-119
```cpp
// before
FILE* f = fopen(path, "wb");
if (!f) return "";
fwrite(buf.data(), 1, outSize, f);
fclose(f);
// after
nxst::FileHandle f(fopen(path, "wb"));
if (!f) return "";
fwrite(buf.data(), 1, outSize, f.get());
```
No `fclose` needed — `FileHandle` dtor handles it.
---
## Item 3 — Split `io::restore`
**Current:** `io.cpp:236-317` — one 80-line function doing mount, validate, clear, copy, commit.
**Split into three `static` helpers in `io.cpp` (not exposed in header):**
```
static nxst::Result<void> clearSaveRoot(const std::string& dst_path)
— removes all files/dirs under save:/
static nxst::Result<void> extractAndCommit(const std::string& src_path, const std::string& dst_path)
— copyDirectory + fsdevCommitDevice
io::restore (public)
— mount, validate src, call clearSaveRoot, call extractAndCommit, return success
```
`io.hpp` declaration unchanged — `io::restore` signature stays the same.
---
## Item 4 — `sendAll`/`recvAll` → `nxst::Result<void>`
**File:** `src/service/transfer_service.cpp`, lines 32-55
```cpp
// before
static bool sendAll(int sock, const void* buf, size_t len) { ... return true/false; }
static bool recvAll(int sock, void* buf, size_t len) { ... return true/false; }
// after
static nxst::Result<void> sendAll(int sock, const void* buf, size_t len);
static nxst::Result<void> recvAll(int sock, void* buf, size_t len);
```
All callers: `if (!sendAll(...))``if (!sendAll(...).isOk())`.
---
## Verification
```
cmake --build build/ # must produce NXST.elf + NXST.nro with zero warnings
```
No observable behavior change — pure refactor.
+2 -2
View File
@@ -39,8 +39,8 @@ Logs are written to `/switch/NXST/log.log`.
**Prerequisites:** [devkitPro](https://devkitpro.org/wiki/Getting_Started) with `switch-dev` and `switch-portlibs` packages, plus `cmake ≥ 3.20`. **Prerequisites:** [devkitPro](https://devkitpro.org/wiki/Getting_Started) with `switch-dev` and `switch-portlibs` packages, plus `cmake ≥ 3.20`.
```bash ```bash
# Clone with submodules (Plutonium UI) # Clone (Plutonium UI fetched automatically by CMake)
git clone --recurse-submodules https://github.com/your-username/NXST.git git clone https://github.com/your-username/NXST.git
cd NXST cd NXST
# Configure (once) # Configure (once)
+3 -3
View File
@@ -38,7 +38,7 @@ Sender polls in 100 ms slices for up to 3 seconds. Cancel is checked each slice.
|-----------|-------| |-----------|-------|
| Port | `8080` | | Port | `8080` |
| Direction | sender connects → receiver listens | | Direction | sender connects → receiver listens |
| Buffer size | 65 536 bytes (`proto::BUF_SIZE`) | | Buffer size | 65 536 bytes (`proto::kBufSize`) |
**Connection:** **Connection:**
@@ -64,14 +64,14 @@ Sender polls in 100 ms slices for up to 3 seconds. Cancel is checked each slice.
Files are sent sequentially. The stream ends with a sentinel frame: Files are sent sequentially. The stream ends with a sentinel frame:
``` ```
filename_len == 0 (proto::EOF_SENTINEL) filename_len == 0 (proto::kEofSentinel)
``` ```
No `filename` or `file_size` field follows the sentinel. No `filename` or `file_size` field follows the sentinel.
**Constraints:** **Constraints:**
- `filename_len > proto::MAX_FILENAME` (4 096) is treated as a protocol error; the receiver aborts. - `filename_len > proto::kMaxFilename` (4 096) is treated as a protocol error; the receiver aborts.
- Filenames are full paths as produced by `io::backup` (e.g. `/switch/NXST/<title>/<user>/...`). - Filenames are full paths as produced by `io::backup` (e.g. `/switch/NXST/<title>/<user>/...`).
- On the receiver, the username path component is rewritten to match the local user's nickname before writing to disk. - On the receiver, the username path component is rewritten to match the local user's nickname before writing to disk.
-24
View File
@@ -1,24 +0,0 @@
#pragma once
#include <memory>
#include <switch.h>
#include <nxst/domain/account.hpp>
#include <nxst/domain/title.hpp>
#include <nxst/domain/util.hpp>
#include <nxst/infra/sys/logger.hpp>
#include <nxst/ui/const.h>
typedef enum { SORT_ALPHA, SORT_LAST_PLAYED, SORT_PLAY_TIME, SORT_MODES_COUNT } sort_t;
inline float g_currentTime = 0;
inline bool g_backupScrollEnabled = 0;
inline bool g_notificationLedAvailable = false;
inline bool g_shouldExitNetworkLoop = false;
inline std::string g_selectedCheatKey;
inline std::vector<std::string> g_selectedCheatCodes;
inline u32 g_username_dotsize;
inline sort_t g_sortMode = SORT_ALPHA;
inline std::string g_currentFile = "";
inline bool g_isTransferringFile = false;
inline const std::string g_emptySave = "New...";
+2 -2
View File
@@ -8,7 +8,7 @@
namespace ui { namespace ui {
class MainApplication : public pu::ui::Application { class MainApplication : public pu::ui::Application {
public: public:
using Application::Application; using Application::Application;
@@ -19,5 +19,5 @@ class MainApplication : public pu::ui::Application {
UsersLayout::Ref users_layout; UsersLayout::Ref users_layout;
TitlesLayout::Ref titles_layout; TitlesLayout::Ref titles_layout;
nxst::TransferService transfer; nxst::TransferService transfer;
}; };
} // namespace ui } // namespace ui
+14 -50
View File
@@ -1,59 +1,25 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#pragma once #pragma once
#include <map>
#include <string.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include <switch.h> #include <switch.h>
#define USER_ICON_SIZE 64 // Hash and comparison support for AccountUid as map/unordered_map key.
namespace std { namespace std {
template <> struct hash<AccountUid> { template <> struct hash<AccountUid> {
size_t operator()(const AccountUid& a) const { size_t operator()(const AccountUid& a) const noexcept {
return ((hash<u64>()(a.uid[0]) ^ (hash<u64>()(a.uid[1]) << 1)) >> 1); return (hash<u64>()(a.uid[0]) ^ (hash<u64>()(a.uid[1]) << 1)) >> 1;
} }
}; };
} // namespace std } // namespace std
inline bool operator==(const AccountUid& x, const AccountUid& y) { inline bool operator==(const AccountUid& x, const AccountUid& y) {
return x.uid[0] == y.uid[0] && x.uid[1] == y.uid[1]; return x.uid[0] == y.uid[0] && x.uid[1] == y.uid[1];
} }
inline bool operator==(const AccountUid& x, u64 y) {
return x.uid[0] == y && x.uid[1] == y;
}
inline bool operator<(const AccountUid& x, const AccountUid& y) { inline bool operator<(const AccountUid& x, const AccountUid& y) {
if (x.uid[0] != y.uid[0]) return x.uid[0] != y.uid[0] ? x.uid[0] < y.uid[0] : x.uid[1] < y.uid[1];
return x.uid[0] < y.uid[0];
return x.uid[1] < y.uid[1];
} }
struct User { struct User {
@@ -61,12 +27,10 @@ struct User {
std::string name; std::string name;
}; };
namespace Account { namespace account {
Result init(void); Result init();
void exit(void); void exit();
std::vector<AccountUid> ids();
std::vector<AccountUid> ids(void); std::string username(AccountUid id);
AccountUid selectAccount(void); std::string iconPath(AccountUid id);
std::string username(AccountUid id); } // namespace account
std::string iconPath(AccountUid id);
} // namespace Account
-61
View File
@@ -1,61 +0,0 @@
/*
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#pragma once
#include <algorithm>
#include <arpa/inet.h>
#include <codecvt>
#include <cstdio>
#include <locale>
#include <memory>
#include <netinet/in.h>
#include <stdarg.h>
#include <string.h>
#include <string>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#define ATEXIT(func) atexit((void (*)())func)
namespace DateTime {
std::string timeStr(void);
std::string dateTimeStr(void);
std::string logDateTime(void);
} // namespace DateTime
namespace StringUtils {
bool containsInvalidChar(const std::string& str);
std::string escapeJson(const std::string& s);
std::string format(const std::string fmt_str, ...);
std::string removeForbiddenCharacters(std::string src);
std::string UTF16toUTF8(const std::u16string& src);
void ltrim(std::string& s);
void rtrim(std::string& s);
void trim(std::string& s);
} // namespace StringUtils
char* getConsoleIP(void);
+11 -12
View File
@@ -1,17 +1,16 @@
#pragma once #pragma once
#include <cstdint>
namespace proto { namespace proto {
constexpr uint16_t TCP_PORT = 8080; constexpr uint16_t kTcpPort = 8080;
constexpr uint16_t MULTICAST_PORT = 8081; constexpr uint16_t kMulticastPort = 8081;
constexpr char MULTICAST_GROUP[] = "239.0.0.1"; constexpr char kMulticastGroup[] = "239.0.0.1";
constexpr size_t BUF_SIZE = 65536; constexpr size_t kBufSize = 65536;
constexpr uint32_t MAX_FILENAME = 4096; constexpr uint32_t kMaxFilename = 4096;
constexpr uint32_t EOF_SENTINEL = 0; constexpr uint32_t kEofSentinel = 0;
// Wire layout per file: // Wire layout per file:
// [filename_len : uint32_t LE] — 0 == end-of-stream // [filename_len : uint32_t LE] — 0 == end-of-stream
// [filename : filename_len bytes] // [filename : filename_len bytes]
// [file_size : uint64_t LE] // [file_size : uint64_t LE]
// [file_data : file_size bytes] // [file_data : file_size bytes]
} // namespace proto } // namespace proto
+5 -5
View File
@@ -4,7 +4,7 @@
namespace nxst { namespace nxst {
template <class T, class E = std::string> class Result { template <class T, class E = std::string> class Result {
bool ok; bool ok;
alignas(T) alignas(E) unsigned char storage[sizeof(T) > sizeof(E) ? sizeof(T) : sizeof(E)]; alignas(T) alignas(E) unsigned char storage[sizeof(T) > sizeof(E) ? sizeof(T) : sizeof(E)];
@@ -57,10 +57,10 @@ template <class T, class E = std::string> class Result {
const E& error() const { const E& error() const {
return *reinterpret_cast<const E*>(storage); return *reinterpret_cast<const E*>(storage);
} }
}; };
// Specialisation for Result<void> // Specialisation for Result<void>
template <class E> class Result<void, E> { template <class E> class Result<void, E> {
bool ok; bool ok;
alignas(E) unsigned char storage[sizeof(E)]; alignas(E) unsigned char storage[sizeof(E)];
@@ -103,6 +103,6 @@ template <class E> class Result<void, E> {
const E& error() const { const E& error() const {
return *reinterpret_cast<const E*>(storage); return *reinterpret_cast<const E*>(storage);
} }
}; };
} // namespace nxst } // namespace nxst
+38 -70
View File
@@ -1,32 +1,5 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#pragma once #pragma once
#include <algorithm>
#include <stdlib.h>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
@@ -34,57 +7,52 @@
#include <switch.h> #include <switch.h>
#include <nxst/domain/account.hpp>
#include <nxst/infra/fs/filesystem.hpp>
#include <nxst/infra/fs/io.hpp>
class Title { class Title {
public: public:
void init(u8 saveDataType, u64 titleid, AccountUid userID, const std::string& name, void init(u8 save_data_type, u64 title_id, AccountUid uid, const std::string& name,
const std::string& author); const std::string& author);
~Title() = default;
std::string author(void); std::string author() const;
std::pair<std::string, std::string> displayName(void); std::pair<std::string, std::string> displayName() const;
u64 id(void); u64 id() const;
std::string name(void); std::string name() const;
std::string path(void); std::string path() const;
u64 playTimeNanoseconds(void); u64 playTimeNanoseconds() const;
std::string playTime(void); std::string playTime() const;
void playTimeNanoseconds(u64 playTimeNanoseconds); void playTimeNanoseconds(u64 ns);
u32 lastPlayedTimestamp(void); u32 lastPlayedTimestamp() const;
void lastPlayedTimestamp(u32 lastPlayedTimestamp); void lastPlayedTimestamp(u32 ts);
std::string fullPath(size_t index); std::string fullPath(size_t index) const;
void refreshDirectories(void); void refreshDirectories();
u64 saveId(); u64 saveId() const;
void saveId(u64 id); void saveId(u64 id);
std::vector<std::string> saves(void); std::vector<std::string> saves() const;
u8 saveDataType(void); u8 saveDataType() const;
AccountUid userId(void); AccountUid userId() const;
std::string userName(void); std::string userName() const;
private: private:
u64 mId; u64 m_id{0};
u64 mSaveId; u64 m_save_id{0};
AccountUid mUserId; AccountUid m_uid{};
std::string mUserName; std::string m_user_name;
std::string mName; std::string m_name;
std::string mSafeName; std::string m_safe_name;
std::string mAuthor; std::string m_author;
std::string mPath; std::string m_path;
std::vector<std::string> mSaves; std::vector<std::string> m_saves;
std::vector<std::string> mFullSavePaths; std::vector<std::string> m_full_save_paths;
u8 mSaveDataType; u8 m_save_data_type{0};
std::pair<std::string, std::string> mDisplayName; std::pair<std::string, std::string> m_display_name;
u64 mPlayTimeNanoseconds; u64 m_play_time_ns{0};
u32 mLastPlayedTimestamp; u32 m_last_played_ts{0};
}; };
bool areTitlesLoaded();
void loadTitles();
void sortTitles();
void rotateSortMode();
void getTitle(Title& dst, AccountUid uid, size_t i); void getTitle(Title& dst, AccountUid uid, size_t i);
size_t getTitleCount(AccountUid uid); size_t getTitleCount(AccountUid uid);
void loadTitles(void);
bool areTitlesLoaded(void);
void sortTitles(void);
void rotateSortMode(void);
void refreshDirectories(u64 id); void refreshDirectories(u64 id);
std::unordered_map<std::string, std::string> getCompleteTitleList(void); std::unordered_map<std::string, std::string> getCompleteTitleList();
+16 -45
View File
@@ -1,51 +1,22 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#pragma once #pragma once
#include <sys/stat.h>
#include <switch.h> #include <switch.h>
#include <nxst/domain/account.hpp> void servicesExit();
#include <nxst/domain/common.hpp> Result servicesInit();
#include <nxst/infra/fs/io.hpp>
// debug
#include <arpa/inet.h>
#include <sys/errno.h>
#include <sys/socket.h>
void servicesExit(void);
Result servicesInit(void);
HidsysNotificationLedPattern blinkLedPattern(u8 times);
void blinkLed(u8 times); void blinkLed(u8 times);
namespace StringUtils { namespace string_utils {
std::string removeAccents(std::string str); bool containsInvalidChar(const std::string& str);
std::string removeNotAscii(std::string str); std::string format(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
std::u16string UTF8toUTF16(const char* src); std::string removeForbiddenCharacters(std::string src);
std::string elide(const std::string& s, size_t maxChars); std::string UTF16toUTF8(const std::u16string& src);
} // namespace StringUtils void ltrim(std::string& s);
void rtrim(std::string& s);
void trim(std::string& s);
std::string removeAccents(std::string str);
std::string removeNotAscii(std::string str);
std::u16string UTF8toUTF16(const char* src);
std::string elide(const std::string& s, size_t max_chars);
} // namespace string_utils
+20 -43
View File
@@ -1,55 +1,32 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#pragma once #pragma once
#include <dirent.h>
#include <errno.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include <switch.h> #include <switch.h>
struct DirectoryEntry {
std::string name;
bool directory;
};
class Directory { class Directory {
public: public:
Directory(const std::string& root); explicit Directory(const std::string& path);
~Directory() = default;
Result error(void); bool good() const {
std::string entry(size_t index); return m_good;
bool folder(size_t index); }
bool good(void); Result error() const {
size_t size(void); return m_error;
}
size_t size() const {
return m_entries.size();
}
std::string entry(size_t i) const;
bool folder(size_t i) const;
private: private:
std::vector<struct DirectoryEntry> mList; struct Entry {
Result mError; std::string name;
bool mGood; bool is_dir;
};
std::vector<Entry> m_entries;
Result m_error{0};
bool m_good{false};
}; };
+6 -33
View File
@@ -1,36 +1,9 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#pragma once #pragma once
#include <switch.h> #include <switch.h>
#include <nxst/domain/account.hpp> namespace file_system {
Result mount(FsFileSystem* fs, u64 title_id, AccountUid uid);
namespace FileSystem { int mount(FsFileSystem fs);
Result mount(FsFileSystem* fileSystem, u64 titleID, AccountUid userID); void unmount();
int mount(FsFileSystem fs); } // namespace file_system
void unmount(void);
} // namespace FileSystem
+9 -9
View File
@@ -5,8 +5,8 @@
namespace nxst { namespace nxst {
// RAII wrapper for FsFileSystem — auto-closes on destruction. // RAII wrapper for FsFileSystem — auto-closes on destruction.
struct FsFileSystemHandle { struct FsFileSystemHandle {
FsFileSystem fs{}; FsFileSystem fs{};
bool valid{false}; bool valid{false};
@@ -26,10 +26,10 @@ struct FsFileSystemHandle {
void release() { void release() {
valid = false; valid = false;
} // transfer ownership to devfs } // transfer ownership to devfs
}; };
// RAII wrapper for FILE* — auto-fclose on destruction. // RAII wrapper for FILE* — auto-fclose on destruction.
struct FileHandle { struct FileHandle {
FILE* ptr{nullptr}; FILE* ptr{nullptr};
explicit FileHandle(FILE* file) : ptr(file) {} explicit FileHandle(FILE* file) : ptr(file) {}
@@ -47,10 +47,10 @@ struct FileHandle {
FILE* get() const { FILE* get() const {
return ptr; return ptr;
} }
}; };
// RAII wrapper for AccountProfile — auto-closes on destruction. // RAII wrapper for AccountProfile — auto-closes on destruction.
struct AccountProfileHandle { struct AccountProfileHandle {
AccountProfile profile{}; AccountProfile profile{};
bool valid{false}; bool valid{false};
@@ -66,6 +66,6 @@ struct AccountProfileHandle {
AccountProfile* get() { AccountProfile* get() {
return &profile; return &profile;
} }
}; };
} // namespace nxst } // namespace nxst
+10 -44
View File
@@ -1,53 +1,19 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#pragma once #pragma once
#include <dirent.h> #include <string>
#include <sys/stat.h>
#include <unistd.h>
#include <switch.h> #include <switch.h>
#include <nxst/domain/account.hpp>
#include <nxst/domain/result.hpp> #include <nxst/domain/result.hpp>
#include <nxst/domain/title.hpp>
#include <nxst/domain/util.hpp>
#include <nxst/infra/fs/directory.hpp>
#define BUFFER_SIZE 0x80000
namespace io { namespace io {
nxst::Result<std::string> backup(size_t index, AccountUid uid); nxst::Result<std::string> backup(size_t index, AccountUid uid);
nxst::Result<std::string> restore(size_t index, AccountUid uid, size_t cellIndex, nxst::Result<std::string> restore(size_t index, AccountUid uid, const std::string& title_name);
const std::string& nameFromCell);
Result copyDirectory(const std::string& srcPath, const std::string& dstPath); Result copyDirectory(const std::string& src, const std::string& dst);
void copyFile(const std::string& srcPath, const std::string& dstPath); void copyFile(const std::string& src, const std::string& dst);
Result createDirectory(const std::string& path); Result createDirectory(const std::string& path);
Result deleteFolderRecursively(const std::string& path); Result deleteFolderRecursively(const std::string& path);
bool directoryExists(const std::string& path); bool directoryExists(const std::string& path);
bool fileExists(const std::string& path); bool fileExists(const std::string& path);
} // namespace io } // namespace io
@@ -1 +0,0 @@
#pragma once
@@ -1 +0,0 @@
#pragma once
+6 -50
View File
@@ -1,59 +1,15 @@
#pragma once #pragma once
#include <cstring>
#include <string>
// New API — use these going forward. // New API — use these going forward.
namespace nxst::log { namespace nxst::log {
enum class Level { Debug, Info, Warn, Error }; enum class Level { Debug, Info, Warn, Error };
void write(Level level, const char* fmt, ...) __attribute__((format(printf, 2, 3))); void write(Level level, const char* fmt, ...) __attribute__((format(printf, 2, 3)));
void debug(const char* fmt, ...) __attribute__((format(printf, 1, 2))); void debug(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
void info(const char* fmt, ...) __attribute__((format(printf, 1, 2))); void info(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
void warn(const char* fmt, ...) __attribute__((format(printf, 1, 2))); void warn(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
void error(const char* fmt, ...) __attribute__((format(printf, 1, 2))); void error(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
// No-op: writes are immediate. Kept for source compatibility during migration.
inline void flush() {}
} // namespace nxst::log } // namespace nxst::log
// Backward-compat shim — existing Logger::getInstance().log(...) call sites compile
// unchanged. Format args are dropped (same behavior as broken original). Migrate
// call sites to nxst::log::* in Phase 3.
struct Logger {
static Logger& getInstance() {
static Logger instance;
return instance;
}
// clang-tidy naming suppressed: these must match existing call sites during migration.
static constexpr const char* INFO = "[INFO]"; // NOLINT(readability-identifier-naming)
static constexpr const char* DEBUG = "[DEBUG]"; // NOLINT(readability-identifier-naming)
static constexpr const char* ERROR = "[ERROR]"; // NOLINT(readability-identifier-naming)
static constexpr const char* WARN = "[WARN]"; // NOLINT(readability-identifier-naming)
static void flush() {
nxst::log::flush();
}
// Args intentionally dropped — format string still logged for visibility.
template <typename... Args>
void log(const std::string& level, const std::string& fmt, Args&&... /*args*/) {
if (level == ERROR)
nxst::log::error("%s", fmt.c_str());
else if (level == WARN)
nxst::log::warn("%s", fmt.c_str());
else if (level == DEBUG)
nxst::log::debug("%s", fmt.c_str());
else
nxst::log::info("%s", fmt.c_str());
}
Logger() = default;
~Logger() = default;
Logger(const Logger&) = delete; // NOLINT(modernize-use-equals-delete)
Logger& operator=(const Logger&) = delete; // NOLINT(modernize-use-equals-delete)
};
+2 -2
View File
@@ -9,7 +9,7 @@
namespace nxst { namespace nxst {
class TransferService { class TransferService {
public: public:
int startSend(size_t title_index, AccountUid uid); int startSend(size_t title_index, AccountUid uid);
void cancelSend(); void cancelSend();
@@ -108,6 +108,6 @@ class TransferService {
void runBroadcast(); void runBroadcast();
void runAccept(int server_fd); void runAccept(int server_fd);
std::string replaceUsername(const std::string& file_path) const; std::string replaceUsername(const std::string& file_path) const;
}; };
} // namespace nxst } // namespace nxst
-18
View File
@@ -1,18 +0,0 @@
#pragma once
#include <pu/Plutonium>
#include <nxst/ui/theme.hpp>
namespace ui {
class Card {
public:
pu::ui::elm::Rectangle::Ref bg;
Card(pu::ui::Layout* parent, int x, int y, int w, int h, pu::ui::Color color = theme::color::BgSurface,
int rad = theme::radius::lg) {
bg = pu::ui::elm::Rectangle::New(x, y, w, h, color, rad);
parent->Add(bg);
}
};
} // namespace ui
-6
View File
@@ -1,6 +0,0 @@
#pragma once
#include <nxst/ui/theme.hpp>
#define COLOR(hex) pu::ui::Color::FromHex(hex)
#define BACKGROUND_COLOR theme::color::BgBase
+3 -4
View File
@@ -3,11 +3,10 @@
#include <nxst/domain/account.hpp> #include <nxst/domain/account.hpp>
#include <nxst/ui/theme.hpp> #include <nxst/ui/theme.hpp>
#include <nxst/ui/ui_context.hpp>
namespace ui { namespace ui {
class HeaderBar { class HeaderBar {
private: private:
pu::ui::elm::Rectangle::Ref bg; pu::ui::elm::Rectangle::Ref bg;
pu::ui::elm::Rectangle::Ref divider; pu::ui::elm::Rectangle::Ref divider;
@@ -62,7 +61,7 @@ class HeaderBar {
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);
@@ -79,5 +78,5 @@ class HeaderBar {
void SetSubtitle(const std::string& text) { void SetSubtitle(const std::string& text) {
subtitle->SetText(text); subtitle->SetText(text);
} }
}; };
} // namespace ui } // namespace ui
+6 -6
View File
@@ -8,12 +8,12 @@
namespace ui { namespace ui {
struct Hint { struct Hint {
std::string glyph; std::string glyph;
std::string label; std::string label;
}; };
class HintBar { class HintBar {
private: private:
pu::ui::Layout* parent; pu::ui::Layout* parent;
pu::ui::elm::Rectangle::Ref bg; pu::ui::elm::Rectangle::Ref bg;
@@ -23,8 +23,8 @@ class HintBar {
public: public:
HintBar(pu::ui::Layout* p) : parent(p) { HintBar(pu::ui::Layout* p) : parent(p) {
using namespace theme; using namespace theme;
bg = pu::ui::elm::Rectangle::New(0, layout::ScreenH - layout::HintH, layout::ScreenW, layout::HintH, bg = pu::ui::elm::Rectangle::New(0, layout::ScreenH - layout::HintH, layout::ScreenW,
color::BgSurface); layout::HintH, color::BgSurface);
divider = pu::ui::elm::Rectangle::New(0, layout::ScreenH - layout::HintH, layout::ScreenW, 1, divider = pu::ui::elm::Rectangle::New(0, layout::ScreenH - layout::HintH, layout::ScreenW, 1,
color::Divider); color::Divider);
parent->Add(bg); parent->Add(bg);
@@ -52,5 +52,5 @@ class HintBar {
labels.push_back(tb); labels.push_back(tb);
} }
} }
}; };
} // namespace ui } // namespace ui
+58 -58
View File
@@ -5,75 +5,75 @@
#include <pu/Plutonium> #include <pu/Plutonium>
namespace theme { namespace theme {
using pu::ui::Color; using pu::ui::Color;
namespace color { namespace color {
constexpr Color BgBase{0x10, 0x14, 0x1C, 0xFF}; constexpr Color BgBase{0x10, 0x14, 0x1C, 0xFF};
constexpr Color BgSurface{0x18, 0x1F, 0x2A, 0xFF}; constexpr Color BgSurface{0x18, 0x1F, 0x2A, 0xFF};
constexpr Color BgSurface2{0x22, 0x2B, 0x39, 0xFF}; constexpr Color BgSurface2{0x22, 0x2B, 0x39, 0xFF};
constexpr Color Scrim{0x00, 0x00, 0x00, 0xB8}; constexpr Color Scrim{0x00, 0x00, 0x00, 0xB8};
constexpr Color Primary{0xE2, 0x4B, 0x55, 0xFF}; constexpr Color Primary{0xE2, 0x4B, 0x55, 0xFF};
constexpr Color PrimaryDim{0x9C, 0x33, 0x3A, 0xFF}; constexpr Color PrimaryDim{0x9C, 0x33, 0x3A, 0xFF};
constexpr Color Accent{0x4A, 0xC2, 0xE0, 0xFF}; constexpr Color Accent{0x4A, 0xC2, 0xE0, 0xFF};
constexpr Color TextPrimary{0xF2, 0xF4, 0xF8, 0xFF}; constexpr Color TextPrimary{0xF2, 0xF4, 0xF8, 0xFF};
constexpr Color TextSecondary{0xB6, 0xBE, 0xCB, 0xFF}; constexpr Color TextSecondary{0xB6, 0xBE, 0xCB, 0xFF};
constexpr Color TextMuted{0x70, 0x7A, 0x8C, 0xFF}; constexpr Color TextMuted{0x70, 0x7A, 0x8C, 0xFF};
constexpr Color Success{0x55, 0xC8, 0x8A, 0xFF}; constexpr Color Success{0x55, 0xC8, 0x8A, 0xFF};
constexpr Color Error{0xE0, 0x6C, 0x6C, 0xFF}; constexpr Color Error{0xE0, 0x6C, 0x6C, 0xFF};
constexpr Color Warning{0xE6, 0xB4, 0x55, 0xFF}; constexpr Color Warning{0xE6, 0xB4, 0x55, 0xFF};
constexpr Color Divider{0x2A, 0x33, 0x42, 0xFF}; constexpr Color Divider{0x2A, 0x33, 0x42, 0xFF};
constexpr Color FocusRing{0xE2, 0x4B, 0x55, 0xFF}; constexpr Color FocusRing{0xE2, 0x4B, 0x55, 0xFF};
} // namespace color } // namespace color
namespace space { namespace space {
constexpr int xs = 4; constexpr int xs = 4;
constexpr int sm = 8; constexpr int sm = 8;
constexpr int md = 16; constexpr int md = 16;
constexpr int lg = 24; constexpr int lg = 24;
constexpr int xl = 32; constexpr int xl = 32;
constexpr int xxl = 48; constexpr int xxl = 48;
} // namespace space } // namespace space
namespace radius { namespace radius {
constexpr int sm = 6; constexpr int sm = 6;
constexpr int md = 12; constexpr int md = 12;
constexpr int lg = 20; constexpr int lg = 20;
constexpr int pill = 9999; constexpr int pill = 9999;
} // namespace radius } // namespace radius
namespace type { namespace type {
constexpr int Display = 38; constexpr int Display = 38;
constexpr int Title = 30; constexpr int Title = 30;
constexpr int Body = 25; constexpr int Body = 25;
constexpr int Label = 20; constexpr int Label = 20;
constexpr int Caption = 18; constexpr int Caption = 18;
inline std::string font(int size) { inline std::string font(int size) {
return "DefaultFont@" + std::to_string(size); return "DefaultFont@" + std::to_string(size);
} }
} // namespace type } // namespace type
namespace layout { namespace layout {
constexpr int ScreenW = 1280; constexpr int ScreenW = 1280;
constexpr int ScreenH = 720; constexpr int ScreenH = 720;
constexpr int HeaderH = 72; constexpr int HeaderH = 72;
constexpr int HintH = 56; constexpr int HintH = 56;
constexpr int ContentTop = HeaderH; constexpr int ContentTop = HeaderH;
constexpr int ContentH = ScreenH - HeaderH - HintH; constexpr int ContentH = ScreenH - HeaderH - HintH;
} // namespace layout } // namespace layout
namespace motion { namespace motion {
constexpr int FadeFrames = 20; constexpr int FadeFrames = 20;
constexpr int SlideFrames = 14; constexpr int SlideFrames = 14;
constexpr int SpinnerFrames = 72; constexpr int SpinnerFrames = 72;
} // namespace motion } // namespace motion
namespace font { namespace font {
constexpr const char* Default = "Inter"; constexpr const char* Default = "Inter";
constexpr const char* Medium = "InterMedium"; constexpr const char* Medium = "InterMedium";
} // namespace font } // namespace font
} // namespace theme } // namespace theme
+4 -5
View File
@@ -7,16 +7,15 @@
#include <nxst/domain/account.hpp> #include <nxst/domain/account.hpp>
#include <nxst/domain/title.hpp> #include <nxst/domain/title.hpp>
#include <nxst/ui/const.h>
#include <nxst/ui/header_bar.hpp> #include <nxst/ui/header_bar.hpp>
#include <nxst/ui/hint_bar.hpp> #include <nxst/ui/hint_bar.hpp>
namespace ui { namespace ui {
enum class TitlesFocus { List, Actions }; enum class TitlesFocus { List, Actions };
enum class TitlesAction { Transfer, Receive }; enum class TitlesAction { Transfer, Receive };
class TitlesLayout : public pu::ui::Layout { class TitlesLayout : public pu::ui::Layout {
private: private:
pu::ui::elm::Menu::Ref titlesMenu; pu::ui::elm::Menu::Ref titlesMenu;
std::unordered_map<AccountUid, std::vector<pu::ui::elm::MenuItem::Ref>> menuCache; std::unordered_map<AccountUid, std::vector<pu::ui::elm::MenuItem::Ref>> menuCache;
@@ -59,5 +58,5 @@ class TitlesLayout : public pu::ui::Layout {
void onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos); void onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos);
PU_SMART_CTOR(TitlesLayout) PU_SMART_CTOR(TitlesLayout)
}; };
} // namespace ui } // namespace ui
+3 -3
View File
@@ -6,7 +6,7 @@
namespace ui { namespace ui {
class TransferOverlay : public pu::ui::Overlay { class TransferOverlay : public pu::ui::Overlay {
private: private:
pu::ui::elm::Rectangle::Ref card; pu::ui::elm::Rectangle::Ref card;
pu::ui::elm::TextBlock::Ref titleText; pu::ui::elm::TextBlock::Ref titleText;
@@ -67,7 +67,7 @@ class TransferOverlay : public pu::ui::Overlay {
PU_SMART_CTOR(TransferOverlay) PU_SMART_CTOR(TransferOverlay)
void SetStatus(const std::string& status) { void SetStatus(const std::string& status) {
statusText->SetText(StringUtils::elide(status, 56)); statusText->SetText(string_utils::elide(status, 56));
} }
void SetProgress(double val) { void SetProgress(double val) {
@@ -79,6 +79,6 @@ class TransferOverlay : public pu::ui::Overlay {
progressBar->SetVisible(visible); progressBar->SetVisible(visible);
indeterminateText->SetVisible(!visible); indeterminateText->SetVisible(!visible);
} }
}; };
} // namespace ui } // namespace ui
-14
View File
@@ -1,14 +0,0 @@
#pragma once
#include <optional>
#include <string>
#include <switch.h>
#include <nxst/domain/account.hpp>
namespace ui {
struct UiContext {
std::optional<AccountUid> selectedUser;
std::string selectedUserName;
};
} // namespace ui
+2 -3
View File
@@ -2,13 +2,12 @@
#include <pu/Plutonium> #include <pu/Plutonium>
#include <nxst/ui/const.h>
#include <nxst/ui/header_bar.hpp> #include <nxst/ui/header_bar.hpp>
#include <nxst/ui/hint_bar.hpp> #include <nxst/ui/hint_bar.hpp>
namespace ui { namespace ui {
class UsersLayout : public pu::ui::Layout { class UsersLayout : public pu::ui::Layout {
private: private:
pu::ui::elm::Menu::Ref usersMenu; pu::ui::elm::Menu::Ref usersMenu;
pu::ui::elm::Rectangle::Ref loadingBg; pu::ui::elm::Rectangle::Ref loadingBg;
@@ -24,5 +23,5 @@ class UsersLayout : public pu::ui::Layout {
int32_t GetCurrentIndex(); int32_t GetCurrentIndex();
PU_SMART_CTOR(UsersLayout) PU_SMART_CTOR(UsersLayout)
}; };
} // namespace ui } // namespace ui
+1 -2
View File
@@ -1,11 +1,10 @@
#include <unistd.h> #include <unistd.h>
#include <nxst/app/main.hpp>
#include <nxst/app/main_application.hpp> #include <nxst/app/main_application.hpp>
#include <nxst/domain/util.hpp> #include <nxst/domain/util.hpp>
namespace ui { namespace ui {
extern MainApplication* mainApp; extern MainApplication* mainApp;
} }
static int nxlink_sock = -1; static int nxlink_sock = -1;
+6 -10
View File
@@ -1,24 +1,20 @@
#include <string>
#include <switch/services/hid.h>
#include <vector>
#include <switch.h> #include <switch.h>
#include <nxst/app/main_application.hpp> #include <nxst/app/main_application.hpp>
namespace ui { namespace ui {
MainApplication* mainApp; MainApplication* mainApp;
void MainApplication::OnLoad() { void MainApplication::OnLoad() {
mainApp = this; mainApp = this;
this->users_layout = UsersLayout::New(); this->users_layout = UsersLayout::New();
this->titles_layout = TitlesLayout::New(); this->titles_layout = TitlesLayout::New();
this->users_layout->SetOnInput(std::bind(&UsersLayout::onInput, this->users_layout, std::placeholders::_1, this->users_layout->SetOnInput(std::bind(&UsersLayout::onInput, this->users_layout,
std::placeholders::_2, std::placeholders::_3, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_4)); std::placeholders::_3, std::placeholders::_4));
this->titles_layout->SetOnInput(std::bind(&TitlesLayout::onInput, this->titles_layout, this->titles_layout->SetOnInput(std::bind(&TitlesLayout::onInput, this->titles_layout,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_1, std::placeholders::_2,
std::placeholders::_3, std::placeholders::_4)); std::placeholders::_3, std::placeholders::_4));
this->LoadLayout(this->users_layout); this->LoadLayout(this->users_layout);
} }
} // namespace ui } // namespace ui
+32 -74
View File
@@ -1,38 +1,16 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <cstdio> #include <cstdio>
#include <cstring>
#include <map>
#include <sys/stat.h> #include <sys/stat.h>
#include <vector>
#include <nxst/domain/account.hpp> #include <nxst/domain/account.hpp>
#include <nxst/infra/fs/handles.hpp> #include <nxst/infra/fs/handles.hpp>
static std::map<AccountUid, User> mUsers; static std::map<AccountUid, User> s_users;
Result Account::init(void) { 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;
@@ -40,51 +18,50 @@ Result Account::init(void) {
AccountUid uids[8]; AccountUid uids[8];
s32 count = 0; s32 count = 0;
accountListAllUsers(uids, 8, &count); accountListAllUsers(uids, 8, &count);
for (s32 i = 0; i < count; i++) { for (s32 i = 0; i < count; ++i) {
Account::username(uids[i]); // populates mUsers as side effect username(uids[i]); // populate cache
} }
return 0; return 0;
} }
void Account::exit(void) { void account::exit() {
accountExit(); accountExit();
} }
std::vector<AccountUid> Account::ids(void) { std::vector<AccountUid> account::ids() {
std::vector<AccountUid> v; std::vector<AccountUid> result;
for (auto& value : mUsers) { result.reserve(s_users.size());
v.push_back(value.second.id); for (const auto& pair : s_users) {
result.push_back(pair.second.id);
} }
return v; return result;
} }
static User getUser(AccountUid id) { static User fetchUser(AccountUid id) {
User user{id, ""}; User user{id, ""};
nxst::AccountProfileHandle profile; nxst::AccountProfileHandle profile;
AccountProfileBase profilebase; AccountProfileBase base{};
memset(&profilebase, 0, sizeof(profilebase));
if (R_SUCCEEDED(accountGetProfile(profile.get(), id))) { if (R_SUCCEEDED(accountGetProfile(profile.get(), id))) {
profile.valid = true; profile.valid = true;
if (R_SUCCEEDED(accountProfileGet(profile.get(), NULL, &profilebase))) { if (R_SUCCEEDED(accountProfileGet(profile.get(), nullptr, &base))) {
user.name = std::string(profilebase.nickname); user.name = std::string(base.nickname);
} }
} }
return user; return user;
} }
std::string Account::username(AccountUid id) { std::string account::username(AccountUid id) {
std::map<AccountUid, User>::const_iterator got = mUsers.find(id); auto it = s_users.find(id);
if (got == mUsers.end()) { if (it == s_users.end()) {
User user = getUser(id); User user = fetchUser(id);
mUsers.insert({id, user}); s_users.emplace(id, user);
return user.name; return user.name;
} }
return it->second.name;
return got->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]);
@@ -101,37 +78,18 @@ std::string Account::iconPath(AccountUid id) {
return ""; return "";
profile.valid = true; profile.valid = true;
u32 imgSize = 0; u32 img_size = 0;
if (R_FAILED(accountProfileGetImageSize(profile.get(), &imgSize)) || imgSize == 0) if (R_FAILED(accountProfileGetImageSize(profile.get(), &img_size)) || img_size == 0)
return ""; return "";
std::vector<u8> buf(imgSize); std::vector<u8> buf(img_size);
u32 outSize = 0; u32 out_size = 0;
if (R_FAILED(accountProfileLoadImage(profile.get(), buf.data(), imgSize, &outSize)) || outSize == 0) if (R_FAILED(accountProfileLoadImage(profile.get(), buf.data(), img_size, &out_size)) || out_size == 0)
return ""; return "";
nxst::FileHandle f(fopen(path, "wb")); nxst::FileHandle f(fopen(path, "wb"));
if (!f) if (!f)
return ""; return "";
fwrite(buf.data(), 1, outSize, f.get()); fwrite(buf.data(), 1, out_size, f.get());
return std::string(path); return std::string(path);
} }
AccountUid Account::selectAccount(void) {
LibAppletArgs args;
libappletArgsCreate(&args, 0x10000);
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);
if (R_SUCCEEDED(res)) {
u64 lres = *(u64*)st_out;
AccountUid uid = *(AccountUid*)&st_out[8];
if (lres == 0)
return uid;
}
return AccountUid{};
}
-121
View File
@@ -1,121 +0,0 @@
/*
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <nxst/domain/common.hpp>
std::string DateTime::timeStr(void) {
time_t unixTime;
struct tm timeStruct;
time(&unixTime);
localtime_r(&unixTime, &timeStruct);
return StringUtils::format("%02i:%02i:%02i", timeStruct.tm_hour, timeStruct.tm_min, timeStruct.tm_sec);
}
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);
}
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);
}
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) {
static const std::string illegalChars = ".,!\\/:?*\"<>|";
for (size_t i = 0, sz = src.length(); i < sz; i++) {
if (illegalChars.find(src[i]) != std::string::npos) {
src[i] = ' ';
}
}
size_t i;
for (i = src.length() - 1; i > 0 && src[i] == ' '; i--)
;
src.erase(i + 1, src.length() - i);
return src;
}
std::string StringUtils::format(const std::string fmt_str, ...) {
va_list ap;
char* fp = NULL;
va_start(ap, fmt_str);
vasprintf(&fp, fmt_str.c_str(), ap);
va_end(ap);
std::unique_ptr<char[]> formatted(fp);
return std::string(formatted.get());
}
bool StringUtils::containsInvalidChar(const std::string& str) {
for (size_t i = 0, sz = str.length(); i < sz; i++) {
if (!isascii(str[i])) {
return true;
}
}
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::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) {
ltrim(s);
rtrim(s);
}
char* getConsoleIP(void) {
struct in_addr in;
in.s_addr = gethostid();
return inet_ntoa(in);
}
+158 -214
View File
@@ -1,260 +1,206 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint #include <algorithm>
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew #include <cstring>
* #include <vector>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <nxst/app/main.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/infra/fs/directory.hpp>
#include <nxst/infra/sys/logger.hpp>
using sort_t = enum { SortAlpha, SortLastPlayed, SortPlayTime, SortModesCount };
static constexpr const char* kEmptySave = "New...";
static sort_t s_sort_mode = SortAlpha;
static std::unordered_map<AccountUid, std::vector<Title>> titles; static std::unordered_map<AccountUid, std::vector<Title>> titles;
static bool s_titlesLoaded = false; static bool s_titles_loaded = false;
bool areTitlesLoaded(void) { void Title::init(u8 save_data_type, u64 title_id, AccountUid uid, const std::string& name,
return s_titlesLoaded;
}
void Title::init(u8 saveDataType, u64 id, AccountUid userID, const std::string& name,
const std::string& author) { const std::string& author) {
mId = id; m_id = title_id;
mUserId = userID; m_uid = uid;
mSaveDataType = saveDataType; m_save_data_type = save_data_type;
mUserName = Account::username(userID); m_user_name = account::username(uid);
mAuthor = author; m_author = author;
mName = name; m_name = name;
mSafeName = StringUtils::containsInvalidChar(name) ? StringUtils::format("0x%016llX", mId) m_safe_name = string_utils::containsInvalidChar(name) ? string_utils::format("0x%016llX", m_id)
: StringUtils::removeForbiddenCharacters(name); : string_utils::removeForbiddenCharacters(name);
mPath = "sdmc:/switch/NXST/saves/" + StringUtils::format("0x%016llX", mId) + " " + mSafeName; m_path = "sdmc:/switch/NXST/saves/" + string_utils::format("0x%016llX", m_id) + " " + m_safe_name;
std::string aname = StringUtils::removeAccents(mName); std::string aname = string_utils::removeAccents(m_name);
size_t pos = aname.rfind(":"); m_display_name = {aname, ""};
mDisplayName = std::make_pair(aname, "");
if (pos != std::string::npos) { size_t colon = aname.rfind(':');
std::string name1 = aname.substr(0, pos); if (colon != std::string::npos) {
std::string name2 = aname.substr(pos + 1); std::string head = aname.substr(0, colon);
StringUtils::trim(name1); std::string tail = aname.substr(colon + 1);
StringUtils::trim(name2); string_utils::trim(head);
mDisplayName.first = name1; string_utils::trim(tail);
mDisplayName.second = name2; m_display_name = {head, tail};
} else { } else {
// check for parenthesis size_t open = aname.rfind('(');
size_t pos1 = aname.rfind("("); size_t close = aname.rfind(')');
size_t pos2 = aname.rfind(")"); if (open != std::string::npos && close != std::string::npos && close > open) {
if (pos1 != std::string::npos && pos2 != std::string::npos) { std::string head = aname.substr(0, open);
std::string name1 = aname.substr(0, pos1); std::string paren = aname.substr(open + 1, close - open - 1);
std::string name2 = aname.substr(pos1 + 1, pos2 - 1 - pos1); string_utils::trim(head);
StringUtils::trim(name1); string_utils::trim(paren);
StringUtils::trim(name2); m_display_name = {head, paren};
mDisplayName.first = name1;
mDisplayName.second = name2;
} }
} }
refreshDirectories(); refreshDirectories();
} }
u8 Title::saveDataType(void) { u8 Title::saveDataType() const {
return mSaveDataType; return m_save_data_type;
}
u64 Title::id() const {
return m_id;
}
u64 Title::saveId() const {
return m_save_id;
}
void Title::saveId(u64 id) {
m_save_id = id;
}
AccountUid Title::userId() const {
return m_uid;
}
std::string Title::userName() const {
return m_user_name;
}
std::string Title::author() const {
return m_author;
}
std::string Title::name() const {
return m_name;
}
std::pair<std::string, std::string> Title::displayName() const {
return m_display_name;
}
std::string Title::path() const {
return m_path;
}
std::string Title::fullPath(size_t index) const {
return m_full_save_paths.at(index);
}
std::vector<std::string> Title::saves() const {
return m_saves;
}
u64 Title::playTimeNanoseconds() const {
return m_play_time_ns;
}
void Title::playTimeNanoseconds(u64 ns) {
m_play_time_ns = ns;
}
u32 Title::lastPlayedTimestamp() const {
return m_last_played_ts;
}
void Title::lastPlayedTimestamp(u32 ts) {
m_last_played_ts = ts;
} }
u64 Title::id(void) { std::string Title::playTime() const {
return mId; const u64 minutes = m_play_time_ns / 60000000000ULL;
return string_utils::format("%d", minutes / 60) + ":" + string_utils::format("%02d", minutes % 60) +
" hours";
} }
u64 Title::saveId(void) { void Title::refreshDirectories() {
return mSaveId; m_saves.clear();
} m_full_save_paths.clear();
void Title::saveId(u64 saveId) { Directory savelist(m_path);
mSaveId = saveId;
}
AccountUid Title::userId(void) {
return mUserId;
}
std::string Title::userName(void) {
return mUserName;
}
std::string Title::author(void) {
return mAuthor;
}
std::string Title::name(void) {
return mName;
}
std::pair<std::string, std::string> Title::displayName(void) {
return mDisplayName;
}
std::string Title::path(void) {
return mPath;
}
std::string Title::fullPath(size_t index) {
return mFullSavePaths.at(index);
}
std::vector<std::string> Title::saves() {
return mSaves;
}
u64 Title::playTimeNanoseconds(void) {
return mPlayTimeNanoseconds;
}
std::string Title::playTime(void) {
const u64 playTimeMinutes = mPlayTimeNanoseconds / 60000000000;
return StringUtils::format("%d", playTimeMinutes / 60) + ":" +
StringUtils::format("%02d", playTimeMinutes % 60) + " hours";
}
void Title::playTimeNanoseconds(u64 playTimeNanoseconds) {
mPlayTimeNanoseconds = playTimeNanoseconds;
}
u32 Title::lastPlayedTimestamp(void) {
return mLastPlayedTimestamp;
}
void Title::lastPlayedTimestamp(u32 lastPlayedTimestamp) {
mLastPlayedTimestamp = lastPlayedTimestamp;
}
void Title::refreshDirectories(void) {
mSaves.clear();
mFullSavePaths.clear();
Directory savelist(mPath);
if (savelist.good()) { if (savelist.good()) {
for (size_t i = 0, sz = savelist.size(); i < sz; i++) { for (size_t i = 0; i < savelist.size(); ++i) {
if (savelist.folder(i)) { if (savelist.folder(i)) {
mSaves.push_back(savelist.entry(i)); m_saves.push_back(savelist.entry(i));
mFullSavePaths.push_back(mPath + "/" + savelist.entry(i)); m_full_save_paths.push_back(m_path + "/" + savelist.entry(i));
} }
} }
std::sort(m_saves.rbegin(), m_saves.rend());
std::sort(mSaves.rbegin(), mSaves.rend()); std::sort(m_full_save_paths.rbegin(), m_full_save_paths.rend());
std::sort(mFullSavePaths.rbegin(), mFullSavePaths.rend()); m_saves.insert(m_saves.begin(), kEmptySave);
mSaves.insert(mSaves.begin(), g_emptySave); m_full_save_paths.insert(m_full_save_paths.begin(), kEmptySave);
mFullSavePaths.insert(mFullSavePaths.begin(), g_emptySave);
} else { } else {
Logger::getInstance().log(Logger::ERROR, nxst::log::error("Could not read save directory for title %s", m_name.c_str());
"Couldn't retrieve the extdata directory list for the title " + name());
} }
} }
void loadTitles(void) { bool areTitlesLoaded() {
if (s_titlesLoaded) return s_titles_loaded;
return; }
s_titlesLoaded = true;
void loadTitles() {
if (s_titles_loaded)
return;
s_titles_loaded = true;
titles.clear(); titles.clear();
FsSaveDataInfoReader reader; FsSaveDataInfoReader reader;
FsSaveDataInfo info;
s64 total_entries = 0;
size_t outsize = 0;
NacpLanguageEntry* nle = NULL;
NsApplicationControlData* nsacd = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData));
if (nsacd == NULL) {
return;
}
memset(nsacd, 0, sizeof(NsApplicationControlData));
Result res = fsOpenSaveDataInfoReader(&reader, FsSaveDataSpaceId_User); Result res = fsOpenSaveDataInfoReader(&reader, FsSaveDataSpaceId_User);
if (R_FAILED(res)) { if (R_FAILED(res))
free(nsacd);
return; return;
}
while (1) { std::vector<u8> nacp_buf(sizeof(NsApplicationControlData), 0);
res = fsSaveDataInfoReaderRead(&reader, &info, 1, &total_entries); auto* nsacd = reinterpret_cast<NsApplicationControlData*>(nacp_buf.data());
if (R_FAILED(res) || total_entries == 0) {
FsSaveDataInfo info{};
s64 count = 0;
while (true) {
res = fsSaveDataInfoReaderRead(&reader, &info, 1, &count);
if (R_FAILED(res) || count == 0)
break; break;
} if (info.save_data_type != FsSaveDataType_Account)
continue;
if (info.save_data_type == FsSaveDataType_Account) {
u64 tid = info.application_id; u64 tid = info.application_id;
u64 sid = info.save_data_id;
AccountUid uid = info.uid; AccountUid uid = info.uid;
// if (mFilterIds.find(tid) == mFilterIds.end()) { size_t outsize = 0;
NacpLanguageEntry* nle = nullptr;
memset(nsacd, 0, sizeof(NsApplicationControlData));
res = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, nsacd, res = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, nsacd,
sizeof(NsApplicationControlData), &outsize); sizeof(NsApplicationControlData), &outsize);
if (R_SUCCEEDED(res) && !(outsize < sizeof(nsacd->nacp))) { if (R_FAILED(res) || outsize < sizeof(nsacd->nacp))
res = nacpGetLanguageEntry(&nsacd->nacp, &nle); continue;
if (R_SUCCEEDED(res) && nle != NULL) { if (R_FAILED(nacpGetLanguageEntry(&nsacd->nacp, &nle)) || !nle)
Title title; continue;
title.init(info.save_data_type, tid, uid, std::string(nle->name),
std::string(nle->author));
title.saveId(sid);
// load play statistics Title title;
PdmPlayStatistics stats; title.init(info.save_data_type, tid, uid, std::string(nle->name), std::string(nle->author));
res = pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(tid, uid, false, &stats); title.saveId(info.save_data_id);
if (R_SUCCEEDED(res)) {
PdmPlayStatistics stats{};
if (R_SUCCEEDED(pdmqryQueryPlayStatisticsByApplicationIdAndUserAccountId(tid, uid, false, &stats))) {
title.playTimeNanoseconds(stats.playtime); title.playTimeNanoseconds(stats.playtime);
title.lastPlayedTimestamp(stats.last_timestamp_user); title.lastPlayedTimestamp(stats.last_timestamp_user);
} }
// loadIcon(tid, nsacd, outsize - sizeof(nsacd->nacp)); auto it = titles.find(uid);
// check if the vector is already created
std::unordered_map<AccountUid, std::vector<Title>>::iterator it = titles.find(uid);
if (it != titles.end()) { if (it != titles.end()) {
// found
it->second.push_back(title); it->second.push_back(title);
} else { } else {
// not found, insert into map titles.emplace(uid, std::vector<Title>{title});
std::vector<Title> v;
v.push_back(title);
titles.emplace(uid, v);
}
}
}
nle = NULL;
// }
} }
} }
free(nsacd);
fsSaveDataInfoReaderClose(&reader); fsSaveDataInfoReaderClose(&reader);
sortTitles(); sortTitles();
} }
void sortTitles(void) { void sortTitles() {
for (auto& vect : titles) { for (auto& pair : titles) {
std::sort(vect.second.begin(), vect.second.end(), [](Title& l, Title& r) { std::sort(pair.second.begin(), pair.second.end(), [](const Title& l, const Title& r) {
switch (g_sortMode) { switch (s_sort_mode) {
case SORT_LAST_PLAYED: case SortLastPlayed:
return l.lastPlayedTimestamp() > r.lastPlayedTimestamp(); return l.lastPlayedTimestamp() > r.lastPlayedTimestamp();
case SORT_PLAY_TIME: case SortPlayTime:
return l.playTimeNanoseconds() > r.playTimeNanoseconds(); return l.playTimeNanoseconds() > r.playTimeNanoseconds();
case SORT_ALPHA: case SortAlpha:
default: default:
return l.name() < r.name(); return l.name() < r.name();
} }
@@ -262,38 +208,36 @@ void sortTitles(void) {
} }
} }
void rotateSortMode(void) { void rotateSortMode() {
g_sortMode = static_cast<sort_t>((g_sortMode + 1) % SORT_MODES_COUNT); s_sort_mode = static_cast<sort_t>((s_sort_mode + 1) % SortModesCount);
sortTitles(); 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); auto it = titles.find(uid);
if (it != titles.end() && i < getTitleCount(uid)) { if (it != titles.end() && i < it->second.size())
dst = it->second.at(i); dst = it->second[i];
}
} }
size_t getTitleCount(AccountUid uid) { size_t getTitleCount(AccountUid uid) {
std::unordered_map<AccountUid, std::vector<Title>>::iterator it = titles.find(uid); auto it = titles.find(uid);
return it != titles.end() ? it->second.size() : 0; return it != titles.end() ? it->second.size() : 0;
} }
void refreshDirectories(u64 id) { void refreshDirectories(u64 id) {
for (auto& pair : titles) { for (auto& pair : titles) {
for (size_t i = 0; i < pair.second.size(); i++) { for (auto& title : pair.second) {
if (pair.second.at(i).id() == id) { if (title.id() == id)
pair.second.at(i).refreshDirectories(); title.refreshDirectories();
}
} }
} }
} }
std::unordered_map<std::string, std::string> getCompleteTitleList(void) { 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 (auto value : pair.second) { for (const auto& title : pair.second) {
map.insert({StringUtils::format("0x%016llX", value.id()), value.name()}); map.emplace(string_utils::format("0x%016llX", title.id()), title.name());
} }
} }
return map; return map;
+192 -101
View File
@@ -1,159 +1,250 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint #include <algorithm>
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew #include <cctype>
* #include <cstdarg>
* This program is free software: you can redistribute it and/or modify #include <cstdio>
* it under the terms of the GNU General Public License as published by #include <string>
* the Free Software Foundation, either version 3 of the License, or #include <unordered_map>
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <nxst/app/main.hpp> #include <nxst/domain/account.hpp>
#include <nxst/app/main_application.hpp>
#include <nxst/domain/util.hpp> #include <nxst/domain/util.hpp>
#include <nxst/infra/fs/io.hpp>
#include <nxst/infra/sys/logger.hpp> #include <nxst/infra/sys/logger.hpp>
void servicesExit(void) { static bool s_notification_led_available = false;
Logger::getInstance().flush();
Account::exit(); void servicesExit() {
account::exit();
plExit(); plExit();
romfsExit(); romfsExit();
} }
Result servicesInit(void) { Result servicesInit() {
io::createDirectory("sdmc:/switch"); io::createDirectory("sdmc:/switch");
io::createDirectory("sdmc:/switch/NXST"); io::createDirectory("sdmc:/switch/NXST");
io::createDirectory("sdmc:/switch/NXST/saves"); io::createDirectory("sdmc:/switch/NXST/saves");
if (appletGetAppletType() != AppletType_Application) { if (appletGetAppletType() != AppletType_Application) {
Logger::getInstance().log(Logger::WARN, "Please do not run NXST in applet mode."); nxst::log::warn("Please do not run NXST in applet mode.");
} }
Result res = 0; Result res = 0;
romfsInit(); romfsInit();
padConfigureInput(1, HidNpadStyleSet_NpadStandard); padConfigureInput(1, HidNpadStyleSet_NpadStandard);
hidInitializeTouchScreen(); hidInitializeTouchScreen();
if (R_FAILED(res = plInitialize(PlServiceType_User))) { if (R_FAILED(res = plInitialize(PlServiceType_User))) {
Logger::getInstance().log(Logger::ERROR, "plInitialize failed. Result code 0x%08lX.", 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);
Logger::getInstance().log(Logger::ERROR, "Account::init failed. Result code 0x%08lX.", res);
return res; return res;
} }
if (R_FAILED(res = nsInitialize())) { if (R_FAILED(res = nsInitialize())) {
Logger::getInstance().log(Logger::ERROR, "nsInitialize failed. Result code 0x{:08X}.", res); nxst::log::error("nsInitialize failed. Result code 0x%08X.", res);
return res; return res;
} }
if (R_SUCCEEDED(hidsysInitialize())) {
if (R_SUCCEEDED(res = hidsysInitialize())) { s_notification_led_available = true;
g_notificationLedAvailable = true;
} else { } else {
Logger::getInstance().log(Logger::INFO, "Notification led not available. Result code 0x{:08X}.", res); nxst::log::info("Notification LED not available.");
} }
Logger::getInstance().log(Logger::INFO, "NXST loading completed!"); nxst::log::info("NXST loading completed.");
return 0; return 0;
} }
std::u16string StringUtils::UTF8toUTF16(const char* src) { bool string_utils::containsInvalidChar(const std::string& str) {
char16_t tmp[256] = {0}; for (unsigned char c : str) {
utf8_to_utf16((uint16_t*)tmp, (uint8_t*)src, 256); if (!isascii(c))
return std::u16string(tmp); return true;
}
return false;
} }
// https://stackoverflow.com/questions/14094621/change-all-accented-letters-to-normal-letters-in-c std::string string_utils::format(const char* fmt, ...) {
std::string StringUtils::removeAccents(std::string str) { va_list a1, a2;
std::u16string src = UTF8toUTF16(str.c_str()); va_start(a1, fmt);
const std::u16string illegal = va_copy(a2, a1);
UTF8toUTF16("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüūýþÿ"); int n = vsnprintf(nullptr, 0, fmt, a1);
const std::u16string fixed = va_end(a1);
UTF8toUTF16("AAAAAAECEEEEIIIIDNOOOOOx0UUUUYPsaaaaaaeceeeeiiiiOnooooo/0uuuuuypy"); if (n < 0) {
va_end(a2);
for (size_t i = 0, sz = src.length(); i < sz; i++) { return {};
size_t index = illegal.find(src[i]);
if (index != std::string::npos) {
src[i] = fixed[index];
} }
} std::string buf(static_cast<size_t>(n), '\0');
vsnprintf(buf.data(), static_cast<size_t>(n) + 1, fmt, a2);
return UTF16toUTF8(src); va_end(a2);
return buf;
} }
std::string StringUtils::removeNotAscii(std::string str) { std::string string_utils::removeForbiddenCharacters(std::string src) {
for (size_t i = 0, sz = str.length(); i < sz; i++) { static constexpr std::string_view kForbidden = ".,!\\/:?*\"<>|";
if (!isascii(str[i])) { for (char& c : src) {
str[i] = ' '; if (kForbidden.find(c) != std::string_view::npos)
c = ' ';
} }
auto last = src.find_last_not_of(' ');
if (last != std::string::npos)
src.erase(last + 1);
return src;
}
static size_t encodeUtf8(char* out, char32_t cp) {
if (cp < 0x80) {
out[0] = static_cast<char>(cp);
return 1;
}
if (cp < 0x800) {
out[0] = static_cast<char>(0xC0 | (cp >> 6));
out[1] = static_cast<char>(0x80 | (cp & 0x3F));
return 2;
}
if (cp < 0x10000) {
out[0] = static_cast<char>(0xE0 | (cp >> 12));
out[1] = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
out[2] = static_cast<char>(0x80 | (cp & 0x3F));
return 3;
}
out[0] = static_cast<char>(0xF0 | (cp >> 18));
out[1] = static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
out[2] = static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
out[3] = static_cast<char>(0x80 | (cp & 0x3F));
return 4;
}
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) {
char32_t cp = src[i];
if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < src.size()) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (src[++i] - 0xDC00);
}
char buf[4];
result.append(buf, encodeUtf8(buf, cp));
}
return result;
}
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 string_utils::rtrim(std::string& s) {
s.erase(std::find_if(s.rbegin(), s.rend(),
[](unsigned char c) {
return !std::isspace(c);
})
.base(),
s.end());
}
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 string_utils::UTF8toUTF16(const char* src) {
std::u16string result;
while (*src != '\0') {
char32_t cp = 0;
unsigned char c = static_cast<unsigned char>(*src);
if (c < 0x80) {
cp = c;
++src;
} else if (c < 0xE0) {
cp = static_cast<char32_t>(c & 0x1F) << 6 | (src[1] & 0x3F);
src += 2;
} else if (c < 0xF0) {
cp = static_cast<char32_t>(c & 0x0F) << 12 | static_cast<char32_t>(src[1] & 0x3F) << 6 |
(src[2] & 0x3F);
src += 3;
} else {
cp = static_cast<char32_t>(c & 0x07) << 18 | static_cast<char32_t>(src[1] & 0x3F) << 12 |
static_cast<char32_t>(src[2] & 0x3F) << 6 | (src[3] & 0x3F);
src += 4;
}
if (cp < 0x10000) {
result += static_cast<char16_t>(cp);
} else {
cp -= 0x10000;
result += static_cast<char16_t>(0xD800 | (cp >> 10));
result += static_cast<char16_t>(0xDC00 | (cp & 0x3FF));
}
}
return result;
}
// Replaces Latin characters with diacritics with their ASCII base equivalents.
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'},
{u'Î', u'I'}, {u'Ï', u'I'}, {u'Ð', u'D'}, {u'Ñ', u'N'}, {u'Ò', u'O'}, {u'Ó', u'O'}, {u'Ô', u'O'},
{u'Õ', u'O'}, {u'Ö', u'O'}, {u'Ø', u'O'}, {u'Ù', u'U'}, {u'Ú', u'U'}, {u'Û', u'U'}, {u'Ü', u'U'},
{u'Ý', u'Y'}, {u'ß', u's'}, {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'i'}, {u'ï', u'i'}, {u'ñ', u'n'}, {u'ò', u'o'}, {u'ó', u'o'},
{u'ô', u'o'}, {u'õ', u'o'}, {u'ö', u'o'}, {u'ø', u'o'}, {u'ù', u'u'}, {u'ú', u'u'}, {u'û', u'u'},
{u'ü', u'u'}, {u'ū', u'u'}, {u'ý', u'y'}, {u'ÿ', u'y'},
};
std::u16string wide = UTF8toUTF16(str.c_str());
for (char16_t& ch : wide) {
auto it = kMap.find(ch);
if (it != kMap.end())
ch = it->second;
}
return string_utils::UTF16toUTF8(wide);
}
std::string string_utils::removeNotAscii(std::string str) {
for (char& c : str) {
if (!isascii(static_cast<unsigned char>(c)))
c = ' ';
} }
return str; return str;
} }
std::string StringUtils::elide(const std::string& s, size_t maxChars) { std::string string_utils::elide(const std::string& s, size_t max_chars) {
if (s.size() <= maxChars || maxChars < 6) if (s.size() <= max_chars || max_chars < 6)
return s; return s;
constexpr const char* dots = "..."; size_t budget = max_chars - 3;
size_t budget = maxChars - 3;
size_t head = (budget + 1) / 2; size_t head = (budget + 1) / 2;
size_t tail = budget - head; size_t tail = budget - head;
return s.substr(0, head) + dots + s.substr(s.size() - tail); return s.substr(0, head) + "..." + s.substr(s.size() - tail);
} }
HidsysNotificationLedPattern blinkLedPattern(u8 times) { static HidsysNotificationLedPattern makeLedPattern(u8 times) {
HidsysNotificationLedPattern pattern; HidsysNotificationLedPattern p{};
memset(&pattern, 0, sizeof(pattern)); p.baseMiniCycleDuration = 0x1;
p.totalMiniCycles = 0x2;
pattern.baseMiniCycleDuration = 0x1; // 12.5ms. p.totalFullCycles = times;
pattern.totalMiniCycles = 0x2; // 2 mini cycles. p.startIntensity = 0x0;
pattern.totalFullCycles = times; // Repeat n times. p.miniCycles[0] = {0xF, 0xF, 0x0};
pattern.startIntensity = 0x0; // 0%. p.miniCycles[1] = {0x0, 0xF, 0x0};
return p;
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) { if (!s_notification_led_available)
return;
PadState pad; PadState pad;
padInitializeDefault(&pad); padInitializeDefault(&pad);
s32 n; s32 n = 0;
HidsysUniquePadId uniquePadIds[2] = {0}; HidsysUniquePadId pads[2]{};
HidsysNotificationLedPattern pattern = blinkLedPattern(times); HidsysNotificationLedPattern pattern = makeLedPattern(times);
memset(uniquePadIds, 0, sizeof(uniquePadIds));
Result res = hidsysGetUniquePadsFromNpad( Result res = hidsysGetUniquePadsFromNpad(padIsHandheld(&pad) ? HidNpadIdType_Handheld : HidNpadIdType_No1,
padIsHandheld(&pad) ? HidNpadIdType_Handheld : HidNpadIdType_No1, uniquePadIds, 2, &n); pads, 2, &n);
if (R_SUCCEEDED(res)) { if (R_SUCCEEDED(res)) {
for (s32 i = 0; i < n; i++) { for (s32 i = 0; i < n; ++i) {
hidsysSetNotificationLedPattern(&pattern, uniquePadIds[i]); hidsysSetNotificationLedPattern(&pattern, pads[i]);
}
} }
} }
} }
+17 -59
View File
@@ -1,69 +1,27 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint #include <cerrno>
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew #include <dirent.h>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <nxst/infra/fs/directory.hpp> #include <nxst/infra/fs/directory.hpp>
Directory::Directory(const std::string& root) { Directory::Directory(const std::string& path) {
mGood = false; DIR* d = opendir(path.c_str());
mError = 0; if (!d) {
mList.clear(); m_error = static_cast<Result>(errno);
return;
DIR* dir = opendir(root.c_str()); }
struct dirent* ent; struct dirent* ent;
while ((ent = readdir(d)) != nullptr) {
if (dir == NULL) { m_entries.push_back({ent->d_name, ent->d_type == DT_DIR});
mError = (Result)errno;
} else {
while ((ent = readdir(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);
}
closedir(dir);
mGood = true;
} }
closedir(d);
m_good = true;
} }
Result Directory::error(void) { std::string Directory::entry(size_t i) const {
return mError; return i < m_entries.size() ? m_entries[i].name : "";
} }
bool Directory::good(void) { bool Directory::folder(size_t i) const {
return mGood; return i < m_entries.size() && m_entries[i].is_dir;
}
std::string Directory::entry(size_t index) {
return index < mList.size() ? mList.at(index).name : "";
}
bool Directory::folder(size_t index) {
return index < mList.size() ? mList.at(index).directory : false;
}
size_t Directory::size(void) {
return mList.size();
} }
+5 -30
View File
@@ -1,39 +1,14 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <nxst/infra/fs/filesystem.hpp> #include <nxst/infra/fs/filesystem.hpp>
Result FileSystem::mount(FsFileSystem* fileSystem, u64 titleID, AccountUid userID) { Result file_system::mount(FsFileSystem* fs, u64 title_id, AccountUid uid) {
return fsOpen_SaveData(fileSystem, titleID, userID); return fsOpen_SaveData(fs, title_id, uid);
} }
int FileSystem::mount(FsFileSystem fs) { int file_system::mount(FsFileSystem fs) {
return fsdevMountDevice("save", fs); return fsdevMountDevice("save", fs);
} }
void FileSystem::unmount(void) { void file_system::unmount() {
fsdevUnmountDevice("save"); fsdevUnmountDevice("save");
} }
+75 -87
View File
@@ -1,56 +1,36 @@
/* // Copyright (C) 2024-2026 NXST contributors
* This file is part of Checkpoint #include <cstdio>
* Copyright (C) 2017-2021 Bernardo Giordano, FlagBrew #include <sys/stat.h>
* #include <unistd.h>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include <vector> #include <vector>
#include <nxst/app/main.hpp> #include <nxst/domain/account.hpp>
#include <nxst/domain/title.hpp>
#include <nxst/domain/util.hpp>
#include <nxst/infra/fs/directory.hpp>
#include <nxst/infra/fs/filesystem.hpp>
#include <nxst/infra/fs/handles.hpp> #include <nxst/infra/fs/handles.hpp>
#include <nxst/infra/fs/io.hpp> #include <nxst/infra/fs/io.hpp>
#include <nxst/infra/sys/logger.hpp> #include <nxst/infra/sys/logger.hpp>
static constexpr size_t kBufSize = 0x80000;
bool io::fileExists(const std::string& path) { bool io::fileExists(const std::string& path) {
struct stat buffer; struct stat buffer;
return (stat(path.c_str(), &buffer) == 0); 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")); nxst::FileHandle src(fopen(srcPath.c_str(), "rb"));
if (!src) { if (!src) {
nxst::log::error("Failed to open source file %s during copy with errno %d. Skipping...", nxst::log::error("Failed to open source file %s during copy with errno %d. Skipping...",
srcPath.c_str(), errno); srcPath.c_str(), errno);
g_isTransferringFile = false;
return; return;
} }
nxst::FileHandle dst(fopen(dstPath.c_str(), "wb")); nxst::FileHandle dst(fopen(dstPath.c_str(), "wb"));
if (!dst) { if (!dst) {
nxst::log::error("Failed to open destination file %s during copy with errno %d. Skipping...", nxst::log::error("Failed to open destination file %s during copy with errno %d. Skipping...",
dstPath.c_str(), errno); dstPath.c_str(), errno);
g_isTransferringFile = false;
return; return;
} }
@@ -58,28 +38,28 @@ void io::copyFile(const std::string& srcPath, const std::string& dstPath) {
u64 sz = (u64)ftell(src.get()); u64 sz = (u64)ftell(src.get());
rewind(src.get()); rewind(src.get());
std::vector<u8> buf(BUFFER_SIZE); std::vector<u8> buf(kBufSize);
u64 offset = 0; u64 offset = 0;
size_t slashpos = srcPath.rfind('/');
g_currentFile = srcPath.substr(slashpos + 1, srcPath.length() - slashpos - 1);
while (offset < sz) { while (offset < sz) {
u32 count = (u32)fread(buf.data(), 1, BUFFER_SIZE, src.get()); u32 count = (u32)fread(buf.data(), 1, kBufSize, src.get());
if (count == 0) { if (count == 0) {
nxst::log::error("fread returned 0 for %s at offset %llu/%llu (errno %d). Aborting.", 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; break;
} }
fwrite(buf.data(), 1, count, dst.get()); size_t written = fwrite(buf.data(), 1, count, dst.get());
if (written != count) {
nxst::log::error("fwrite incomplete for %s (%zu/%u bytes). Aborting.", dstPath.c_str(), written,
count);
break;
}
offset += count; offset += count;
} }
if (dstPath.rfind("save:/", 0) == 0) { if (dstPath.rfind("save:/", 0) == 0) {
fsdevCommitDevice("save"); fsdevCommitDevice("save");
} }
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) {
@@ -96,24 +76,27 @@ Result io::copyDirectory(const std::string& srcPath, const std::string& dstPath)
std::string newdst = dstPath + items.entry(i); std::string newdst = dstPath + items.entry(i);
if (items.folder(i)) { if (items.folder(i)) {
res = io::createDirectory(newdst); res = createDirectory(newdst);
if (R_SUCCEEDED(res)) { if (R_SUCCEEDED(res)) {
newsrc += "/"; newsrc += "/";
newdst += "/"; newdst += "/";
res = io::copyDirectory(newsrc, newdst); res = copyDirectory(newsrc, newdst);
} else { } else {
quit = true; quit = true;
} }
} else { } else {
io::copyFile(newsrc, newdst); copyFile(newsrc, newdst);
} }
} }
return 0; return res;
} }
Result io::createDirectory(const std::string& path) { Result io::createDirectory(const std::string& path) {
mkdir(path.c_str(), 0777); if (mkdir(path.c_str(), 0777) != 0 && errno != EEXIST) {
nxst::log::error("mkdir failed for %s (errno %d).", path.c_str(), errno);
return 1;
}
return 0; return 0;
} }
@@ -128,20 +111,21 @@ Result io::deleteFolderRecursively(const std::string& path) {
return dir.error(); return dir.error();
} }
Result res = 0;
for (size_t i = 0, sz = dir.size(); i < sz; i++) { for (size_t i = 0, sz = dir.size(); i < sz; i++) {
if (dir.folder(i)) { if (dir.folder(i)) {
std::string newpath = path + "/" + dir.entry(i) + "/"; std::string sub = path + "/" + dir.entry(i);
deleteFolderRecursively(newpath); Result sub_res = deleteFolderRecursively(sub + "/");
newpath = path + dir.entry(i); if (R_FAILED(sub_res))
rmdir(newpath.c_str()); res = sub_res;
rmdir(sub.c_str());
} else { } else {
std::string newpath = path + dir.entry(i); std::remove((path + dir.entry(i)).c_str());
std::remove(newpath.c_str());
} }
} }
rmdir(path.c_str()); rmdir(path.c_str());
return 0; return res;
} }
nxst::Result<std::string> io::backup(size_t index, AccountUid uid) { nxst::Result<std::string> io::backup(size_t index, AccountUid uid) {
@@ -151,54 +135,61 @@ nxst::Result<std::string> io::backup(size_t index, AccountUid uid) {
nxst::log::info("Started backup of %s. Title id: 0x%016lX; User id: 0x%lX%lX.", title.name().c_str(), 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]); title.id(), title.userId().uid[1], title.userId().uid[0]);
nxst::FsFileSystemHandle fsHandle; nxst::FsFileSystemHandle fs_handle;
Result res = FileSystem::mount(fsHandle.get(), title.id(), title.userId()); Result res = file_system::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%08lX. " 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.",
res, title.id(), title.userId().uid[1], title.userId().uid[0]); res, title.id(), title.userId().uid[1], title.userId().uid[0]);
return nxst::Result<std::string>::failure("Failed to mount save."); return nxst::Result<std::string>::failure("Failed to mount save.");
} }
fsHandle.valid = true; fs_handle.valid = true;
if (FileSystem::mount(*fsHandle.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.", 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]);
FileSystem::unmount(); file_system::unmount();
return nxst::Result<std::string>::failure("Failed to mount save."); return nxst::Result<std::string>::failure("Failed to mount save.");
} }
fsHandle.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(title.userId()))); string_utils::removeNotAscii(string_utils::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;
std::string tmp_path = dst_path + ".tmp"; std::string tmp_path = dst_path + ".tmp";
if (io::directoryExists(tmp_path)) { if (io::directoryExists(tmp_path)) {
io::deleteFolderRecursively(tmp_path + "/"); if (R_FAILED(io::deleteFolderRecursively(tmp_path + "/")))
nxst::log::warn("Failed to clean up stale tmp dir %s.", tmp_path.c_str());
} }
io::createDirectory(tmp_path); res = io::createDirectory(tmp_path);
res = io::copyDirectory("save:/", tmp_path + "/");
if (R_FAILED(res)) { 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)) {
file_system::unmount();
io::deleteFolderRecursively(tmp_path + "/"); io::deleteFolderRecursively(tmp_path + "/");
nxst::log::error("Failed to copy directory to %s with result 0x%08lX.", 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.");
} }
if (io::directoryExists(dst_path)) { if (io::directoryExists(dst_path)) {
io::deleteFolderRecursively(dst_path + "/"); if (R_FAILED(io::deleteFolderRecursively(dst_path + "/")))
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) {
FileSystem::unmount(); file_system::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());
FileSystem::unmount(); file_system::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);
@@ -246,7 +237,7 @@ static nxst::Result<void> clearSaveRoot(const std::string& dst_path) {
Result res = fsdevCommitDevice("save"); Result res = fsdevCommitDevice("save");
if (R_FAILED(res)) { if (R_FAILED(res)) {
nxst::log::error("Failed to commit save after clearing with result 0x%08lX.", res); nxst::log::error("Failed to commit save after clearing with result 0x%08X.", res);
return nxst::Result<void>::failure("Failed to commit save after delete."); return nxst::Result<void>::failure("Failed to commit save after delete.");
} }
return nxst::Result<void>::success(); return nxst::Result<void>::success();
@@ -255,22 +246,19 @@ static nxst::Result<void> clearSaveRoot(const std::string& dst_path) {
static nxst::Result<void> extractAndCommit(const std::string& src_path, const std::string& dst_path) { static nxst::Result<void> extractAndCommit(const std::string& src_path, const std::string& dst_path) {
Result res = io::copyDirectory(src_path, dst_path); Result res = io::copyDirectory(src_path, dst_path);
if (R_FAILED(res)) { if (R_FAILED(res)) {
nxst::log::error("Failed to copy %s to save:/ with result 0x%08lX.", src_path.c_str(), res); nxst::log::error("Failed to copy %s to save:/ with result 0x%08X.", src_path.c_str(), res);
return nxst::Result<void>::failure("Failed to restore save."); return nxst::Result<void>::failure("Failed to restore save.");
} }
res = fsdevCommitDevice("save"); res = fsdevCommitDevice("save");
if (R_FAILED(res)) { if (R_FAILED(res)) {
nxst::log::error("Failed to commit save with result 0x%08lX.", res); nxst::log::error("Failed to commit save with result 0x%08X.", res);
return nxst::Result<void>::failure("Failed to commit to save device."); return nxst::Result<void>::failure("Failed to commit to save device.");
} }
return nxst::Result<void>::success(); return nxst::Result<void>::success();
} }
nxst::Result<std::string> io::restore(size_t index, AccountUid uid, size_t cellIndex, nxst::Result<std::string> io::restore(size_t index, AccountUid uid, const std::string& title_name) {
const std::string& nameFromCell) {
(void)cellIndex;
Title title; Title title;
getTitle(title, uid, index); getTitle(title, uid, index);
@@ -279,32 +267,32 @@ nxst::Result<std::string> io::restore(size_t index, AccountUid uid, size_t cellI
createSaveIfNeeded(title.id(), uid); createSaveIfNeeded(title.id(), uid);
nxst::FsFileSystemHandle fsHandle; nxst::FsFileSystemHandle fs_handle;
Result res = FileSystem::mount(fsHandle.get(), title.id(), uid); Result res = file_system::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%08lX. " 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.",
res, title.id(), uid.uid[1], uid.uid[0]); res, title.id(), uid.uid[1], uid.uid[0]);
return nxst::Result<std::string>::failure("Failed to mount save."); return nxst::Result<std::string>::failure("Failed to mount save.");
} }
fsHandle.valid = true; fs_handle.valid = true;
if (FileSystem::mount(*fsHandle.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.", 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]);
FileSystem::unmount(); file_system::unmount();
return nxst::Result<std::string>::failure("Failed to mount save."); return nxst::Result<std::string>::failure("Failed to mount save.");
} }
fsHandle.release(); // devfs now owns the kernel handle 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 + "/"; 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) {
FileSystem::unmount(); file_system::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.");
} }
@@ -312,19 +300,19 @@ nxst::Result<std::string> io::restore(size_t index, AccountUid uid, size_t cellI
auto clear_res = clearSaveRoot(dst_path); auto clear_res = clearSaveRoot(dst_path);
if (!clear_res.isOk()) { if (!clear_res.isOk()) {
FileSystem::unmount(); file_system::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()) {
FileSystem::unmount(); file_system::unmount();
return nxst::Result<std::string>::failure(extract_res.error()); return nxst::Result<std::string>::failure(extract_res.error());
} }
blinkLed(4); blinkLed(4);
FileSystem::unmount(); file_system::unmount();
nxst::log::info("Restore succeeded."); nxst::log::info("Restore succeeded.");
return nxst::Result<std::string>::success(nameFromCell + "\nhas been restored successfully."); return nxst::Result<std::string>::success(title_name + "\nhas been restored successfully.");
} }
-1
View File
@@ -1 +0,0 @@
// Logic moved to src/service/transfer_service.cpp
-1
View File
@@ -1 +0,0 @@
// Logic moved to src/service/transfer_service.cpp
+15 -15
View File
@@ -7,15 +7,15 @@
namespace { namespace {
std::mutex g_log_mutex; std::mutex g_log_mutex;
#if defined(__SWITCH__) #if defined(__SWITCH__)
constexpr const char* kLogPath = "/switch/NXST/log.log"; constexpr const char* kLogPath = "/switch/NXST/log.log";
#else #else
constexpr const char* kLogPath = "nxst.log"; constexpr const char* kLogPath = "nxst.log";
#endif #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]; char msg[2048];
vsnprintf(msg, sizeof(msg), fmt, args); vsnprintf(msg, sizeof(msg), fmt, args);
@@ -34,13 +34,13 @@ void writeEntry(const char* tag, const char* fmt, va_list args) {
fprintf(log_file, "[%s]%s %s\n", time_str, tag, msg); fprintf(log_file, "[%s]%s %s\n", time_str, tag, msg);
fclose(log_file); fclose(log_file);
} }
} }
} // namespace } // namespace
namespace nxst::log { namespace nxst::log {
void write(Level level, const char* fmt, ...) { void write(Level level, const char* fmt, ...) {
const char* tag = "[INFO] "; const char* tag = "[INFO] ";
switch (level) { switch (level) {
case Level::Debug: case Level::Debug:
@@ -60,31 +60,31 @@ void write(Level level, const char* fmt, ...) {
va_start(args, fmt); va_start(args, fmt);
writeEntry(tag, fmt, args); writeEntry(tag, fmt, args);
va_end(args); va_end(args);
} }
void debug(const char* fmt, ...) { void debug(const char* fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
writeEntry("[DEBUG]", fmt, args); writeEntry("[DEBUG]", fmt, args);
va_end(args); va_end(args);
} }
void info(const char* fmt, ...) { void info(const char* fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
writeEntry("[INFO] ", fmt, args); writeEntry("[INFO] ", fmt, args);
va_end(args); va_end(args);
} }
void warn(const char* fmt, ...) { void warn(const char* fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
writeEntry("[WARN] ", fmt, args); writeEntry("[WARN] ", fmt, args);
va_end(args); va_end(args);
} }
void error(const char* fmt, ...) { void error(const char* fmt, ...) {
va_list args; va_list args;
va_start(args, fmt); va_start(args, fmt);
writeEntry("[ERROR]", fmt, args); writeEntry("[ERROR]", fmt, args);
va_end(args); va_end(args);
} }
} // namespace nxst::log } // namespace nxst::log
+56 -56
View File
@@ -27,9 +27,9 @@ namespace fs = std::filesystem;
namespace nxst { namespace nxst {
// ─── File-transfer helpers ──────────────────────────────────────────────────── // ─── File-transfer helpers ────────────────────────────────────────────────────
static bool sendAll(int sock, const void* buf, size_t len) { static bool sendAll(int sock, const void* buf, size_t len) {
size_t sent = 0; size_t sent = 0;
while (sent < len) { while (sent < len) {
ssize_t n = send(sock, static_cast<const char*>(buf) + sent, len - sent, 0); ssize_t n = send(sock, static_cast<const char*>(buf) + sent, len - sent, 0);
@@ -38,9 +38,9 @@ static bool sendAll(int sock, const void* buf, size_t len) {
sent += n; sent += n;
} }
return true; return true;
} }
static bool recvAll(int sock, void* buf, size_t len) { static bool recvAll(int sock, void* buf, size_t len) {
size_t got = 0; size_t got = 0;
while (got < len) { while (got < len) {
ssize_t n = read(sock, static_cast<char*>(buf) + got, len - got); ssize_t n = read(sock, static_cast<char*>(buf) + got, len - got);
@@ -49,9 +49,9 @@ static bool recvAll(int sock, void* buf, size_t len) {
got += n; got += n;
} }
return true; return true;
} }
static bool sendFile(int sock, const fs::path& filepath, TransferState& state) { static bool sendFile(int sock, const fs::path& filepath, TransferState& state) {
std::ifstream infile(filepath, std::ios::binary | std::ios::ate); std::ifstream infile(filepath, std::ios::binary | std::ios::ate);
if (!infile.is_open()) if (!infile.is_open())
return false; return false;
@@ -67,10 +67,10 @@ static bool sendFile(int sock, const fs::path& filepath, TransferState& state) {
if (!sendAll(sock, &file_size, sizeof(file_size))) if (!sendAll(sock, &file_size, sizeof(file_size)))
return false; return false;
std::vector<char> buffer(proto::BUF_SIZE); std::vector<char> buffer(proto::kBufSize);
uint64_t remaining = file_size; uint64_t remaining = file_size;
while (remaining > 0) { while (remaining > 0) {
size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::BUF_SIZE); size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::kBufSize);
infile.read(buffer.data(), (std::streamsize)to_read); infile.read(buffer.data(), (std::streamsize)to_read);
std::streamsize count = infile.gcount(); std::streamsize count = infile.gcount();
if (count <= 0) if (count <= 0)
@@ -81,9 +81,9 @@ static bool sendFile(int sock, const fs::path& filepath, TransferState& state) {
remaining -= (uint64_t)count; remaining -= (uint64_t)count;
} }
return true; return true;
} }
static void mkdirs(const std::string& path) { static void mkdirs(const std::string& path) {
for (size_t i = 1; i < path.size(); i++) { for (size_t i = 1; i < path.size(); i++) {
if (path[i] == '/') { if (path[i] == '/') {
std::string component = path.substr(0, i); std::string component = path.substr(0, i);
@@ -91,9 +91,9 @@ static void mkdirs(const std::string& path) {
} }
} }
mkdir(path.c_str(), 0777); 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('/'); size_t last_slash = rel_path.rfind('/');
if (last_slash != std::string::npos) { if (last_slash != std::string::npos) {
std::string dir = rel_path.substr(0, last_slash); std::string dir = rel_path.substr(0, last_slash);
@@ -103,10 +103,10 @@ static void receiveFile(int sock, const std::string& rel_path, uint64_t file_siz
FILE* outfile = fopen(rel_path.c_str(), "wb"); FILE* outfile = fopen(rel_path.c_str(), "wb");
if (!outfile) { if (!outfile) {
std::vector<char> drain(proto::BUF_SIZE); std::vector<char> drain(proto::kBufSize);
uint64_t remaining = file_size; uint64_t remaining = file_size;
while (remaining > 0) { while (remaining > 0) {
size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::BUF_SIZE); size_t to_read = (size_t)std::min(remaining, (uint64_t)proto::kBufSize);
ssize_t n = read(sock, drain.data(), to_read); ssize_t n = read(sock, drain.data(), to_read);
if (n <= 0) if (n <= 0)
break; break;
@@ -118,10 +118,10 @@ static void receiveFile(int sock, const std::string& rel_path, uint64_t file_siz
state.bytes_total.store(file_size); state.bytes_total.store(file_size);
state.bytes_done.store(0); state.bytes_done.store(0);
std::vector<char> buffer(proto::BUF_SIZE); std::vector<char> buffer(proto::kBufSize);
uint64_t total = 0; uint64_t total = 0;
while (total < file_size) { while (total < file_size) {
size_t to_read = (size_t)std::min(file_size - total, (uint64_t)proto::BUF_SIZE); size_t to_read = (size_t)std::min(file_size - total, (uint64_t)proto::kBufSize);
ssize_t n = read(sock, buffer.data(), to_read); ssize_t n = read(sock, buffer.data(), to_read);
if (n <= 0) if (n <= 0)
break; break;
@@ -130,17 +130,17 @@ static void receiveFile(int sock, const std::string& rel_path, uint64_t file_siz
state.bytes_done.store(total); state.bytes_done.store(total);
} }
fclose(outfile); fclose(outfile);
} }
// ─── Sender ────────────────────────────────────────────────────────────────── // ─── Sender ──────────────────────────────────────────────────────────────────
void TransferService::failSend(const std::string& reason) { void TransferService::failSend(const std::string& reason) {
sender_state.fail_reason = reason; sender_state.fail_reason = reason;
sender_state.connection_failed.store(true); sender_state.connection_failed.store(true);
sender_state.done.store(true); sender_state.done.store(true);
} }
int TransferService::findServer(char* out_ip) { int TransferService::findServer(char* out_ip) {
int udp_fd = socket(AF_INET, SOCK_DGRAM, 0); int udp_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_fd < 0) if (udp_fd < 0)
return -1; return -1;
@@ -154,8 +154,8 @@ int TransferService::findServer(char* out_ip) {
sockaddr_in addr{}; sockaddr_in addr{};
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
addr.sin_port = htons(proto::MULTICAST_PORT); addr.sin_port = htons(proto::kMulticastPort);
addr.sin_addr.s_addr = inet_addr(proto::MULTICAST_GROUP); addr.sin_addr.s_addr = inet_addr(proto::kMulticastGroup);
if (sendto(udp_fd, "DISCOVER_SERVER", 15, 0, (sockaddr*)&addr, sizeof(addr)) < 0) { if (sendto(udp_fd, "DISCOVER_SERVER", 15, 0, (sockaddr*)&addr, sizeof(addr)) < 0) {
releaseUdp(); releaseUdp();
@@ -190,9 +190,9 @@ int TransferService::findServer(char* out_ip) {
} }
releaseUdp(); releaseUdp();
return -1; return -1;
} }
void* TransferService::senderEntry(void* arg) { void* TransferService::senderEntry(void* arg) {
auto* a = static_cast<SenderArgs*>(arg); auto* a = static_cast<SenderArgs*>(arg);
TransferService* svc = a->svc; TransferService* svc = a->svc;
size_t idx = a->title_index; size_t idx = a->title_index;
@@ -200,9 +200,9 @@ void* TransferService::senderEntry(void* arg) {
delete a; delete a;
svc->runSender(idx, uid); svc->runSender(idx, uid);
return nullptr; return nullptr;
} }
void TransferService::runSender(size_t title_index, AccountUid uid) { void TransferService::runSender(size_t title_index, AccountUid uid) {
sender_active.store(true); sender_active.store(true);
auto finish = [this]() { auto finish = [this]() {
@@ -252,7 +252,7 @@ void TransferService::runSender(size_t title_index, AccountUid uid) {
sockaddr_in serv{}; sockaddr_in serv{};
serv.sin_family = AF_INET; serv.sin_family = AF_INET;
serv.sin_port = htons(proto::TCP_PORT); serv.sin_port = htons(proto::kTcpPort);
if (inet_pton(AF_INET, server_ip, &serv.sin_addr) <= 0 || if (inet_pton(AF_INET, server_ip, &serv.sin_addr) <= 0 ||
connect(tcp_fd, (sockaddr*)&serv, sizeof(serv)) < 0) { connect(tcp_fd, (sockaddr*)&serv, sizeof(serv)) < 0) {
if (!sender_state.cancelled.load()) if (!sender_state.cancelled.load())
@@ -278,15 +278,15 @@ void TransferService::runSender(size_t title_index, AccountUid uid) {
} }
} }
uint32_t sentinel = proto::EOF_SENTINEL; uint32_t sentinel = proto::kEofSentinel;
sendAll(tcp_fd, &sentinel, sizeof(sentinel)); sendAll(tcp_fd, &sentinel, sizeof(sentinel));
releaseTcp(); releaseTcp();
sender_state.setStatus(""); sender_state.setStatus("");
return finish(); return finish();
} }
int TransferService::startSend(size_t title_index, AccountUid uid) { int TransferService::startSend(size_t title_index, AccountUid uid) {
sender_state.reset(); sender_state.reset();
sender_state.setStatus("Searching for receiver..."); sender_state.setStatus("Searching for receiver...");
@@ -298,9 +298,9 @@ int TransferService::startSend(size_t title_index, AccountUid uid) {
} }
pthread_detach(thread); pthread_detach(thread);
return 0; return 0;
} }
void TransferService::cancelSend() { void TransferService::cancelSend() {
sender_state.cancelled.store(true); sender_state.cancelled.store(true);
int udp = sender_udp_sock.exchange(-1); int udp = sender_udp_sock.exchange(-1);
if (udp >= 0) { if (udp >= 0) {
@@ -312,14 +312,14 @@ void TransferService::cancelSend() {
shutdown(tcp, SHUT_RDWR); shutdown(tcp, SHUT_RDWR);
close(tcp); close(tcp);
} }
} }
// ─── Receiver ──────────────────────────────────────────────────────────────── // ─── Receiver ────────────────────────────────────────────────────────────────
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 =
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('/'); 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;
@@ -330,14 +330,14 @@ std::string TransferService::replaceUsername(const std::string& file_path) const
#else #else
return file_path; return file_path;
#endif #endif
} }
void* TransferService::broadcastEntry(void* arg) { void* TransferService::broadcastEntry(void* arg) {
static_cast<TransferService*>(arg)->runBroadcast(); static_cast<TransferService*>(arg)->runBroadcast();
return nullptr; return nullptr;
} }
void TransferService::runBroadcast() { void TransferService::runBroadcast() {
receiver_broadcast_active.store(true); receiver_broadcast_active.store(true);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr); pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, nullptr); pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, nullptr);
@@ -361,7 +361,7 @@ void TransferService::runBroadcast() {
sockaddr_in addr{}; sockaddr_in addr{};
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(proto::MULTICAST_PORT); addr.sin_port = htons(proto::kMulticastPort);
if (bind(udp, (sockaddr*)&addr, sizeof(addr)) < 0) { if (bind(udp, (sockaddr*)&addr, sizeof(addr)) < 0) {
releaseUdp(); releaseUdp();
@@ -370,7 +370,7 @@ void TransferService::runBroadcast() {
} }
ip_mreq group{}; ip_mreq group{};
group.imr_multiaddr.s_addr = inet_addr(proto::MULTICAST_GROUP); group.imr_multiaddr.s_addr = inet_addr(proto::kMulticastGroup);
group.imr_interface.s_addr = htonl(INADDR_ANY); group.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(udp, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) { if (setsockopt(udp, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group)) < 0) {
releaseUdp(); releaseUdp();
@@ -397,18 +397,18 @@ void TransferService::runBroadcast() {
releaseUdp(); releaseUdp();
receiver_broadcast_active.store(false); receiver_broadcast_active.store(false);
} }
void* TransferService::acceptEntry(void* arg) { void* TransferService::acceptEntry(void* arg) {
auto* a = static_cast<AcceptArgs*>(arg); auto* a = static_cast<AcceptArgs*>(arg);
TransferService* svc = a->svc; TransferService* svc = a->svc;
int server_fd = a->server_fd; int server_fd = a->server_fd;
delete a; delete a;
svc->runAccept(server_fd); svc->runAccept(server_fd);
return nullptr; return nullptr;
} }
void TransferService::runAccept(int server_fd) { void TransferService::runAccept(int server_fd) {
receiver_accept_active.store(true); receiver_accept_active.store(true);
receiver_listen_sock.store(server_fd); receiver_listen_sock.store(server_fd);
@@ -427,9 +427,9 @@ void TransferService::runAccept(int server_fd) {
uint32_t filename_len = 0; uint32_t filename_len = 0;
if (!recvAll(client_sock, &filename_len, sizeof(filename_len))) if (!recvAll(client_sock, &filename_len, sizeof(filename_len)))
break; break;
if (filename_len == proto::EOF_SENTINEL) if (filename_len == proto::kEofSentinel)
break; break;
if (filename_len > proto::MAX_FILENAME) if (filename_len > proto::kMaxFilename)
break; break;
std::vector<char> filename_buf(filename_len + 1, '\0'); std::vector<char> filename_buf(filename_len + 1, '\0');
@@ -457,7 +457,7 @@ void TransferService::runAccept(int server_fd) {
if (!receiver_state.cancelled.load()) { if (!receiver_state.cancelled.load()) {
#ifdef __SWITCH__ #ifdef __SWITCH__
receiver_state.setStatus("Restoring..."); receiver_state.setStatus("Restoring...");
auto result = io::restore(restore_title_index, restore_uid, 0, restore_title_name); auto result = io::restore(restore_title_index, restore_uid, restore_title_name);
restore_ok = result.isOk(); restore_ok = result.isOk();
restore_error = result.isOk() ? "" : result.error(); restore_error = result.isOk() ? "" : result.error();
#else #else
@@ -468,9 +468,9 @@ void TransferService::runAccept(int server_fd) {
receiver_state.done.store(true); receiver_state.done.store(true);
receiver_accept_active.store(false); receiver_accept_active.store(false);
} }
int TransferService::startReceive(size_t title_index, AccountUid uid, std::string title_name) { int TransferService::startReceive(size_t title_index, AccountUid uid, std::string title_name) {
receiver_state.reset(); receiver_state.reset();
receiver_state.setStatus("Waiting for connection..."); receiver_state.setStatus("Waiting for connection...");
restore_title_index = title_index; restore_title_index = title_index;
@@ -497,7 +497,7 @@ int TransferService::startReceive(size_t title_index, AccountUid uid, std::strin
sockaddr_in addr{}; sockaddr_in addr{};
addr.sin_family = AF_INET; addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY; addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(proto::TCP_PORT); addr.sin_port = htons(proto::kTcpPort);
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(); cancelReceive();
@@ -514,9 +514,9 @@ int TransferService::startReceive(size_t title_index, AccountUid uid, std::strin
pthread_detach(accept_thread); pthread_detach(accept_thread);
server.release(); server.release();
return 0; return 0;
} }
void TransferService::cancelReceive() { void TransferService::cancelReceive() {
receiver_state.cancelled.store(true); receiver_state.cancelled.store(true);
int sock = receiver_client_sock.exchange(-1); int sock = receiver_client_sock.exchange(-1);
if (sock >= 0) { if (sock >= 0) {
@@ -535,6 +535,6 @@ void TransferService::cancelReceive() {
} }
if (receiver_broadcast_active.load()) if (receiver_broadcast_active.load())
pthread_cancel(receiver_bcast_thread); pthread_cancel(receiver_bcast_thread);
} }
} // namespace nxst } // namespace nxst
+39 -39
View File
@@ -1,23 +1,22 @@
#include <nxst/app/main_application.hpp> #include <nxst/app/main_application.hpp>
#include <nxst/domain/util.hpp> #include <nxst/domain/util.hpp>
#include <nxst/ui/const.h>
#include <nxst/ui/transfer_overlay.hpp> #include <nxst/ui/transfer_overlay.hpp>
namespace ui { namespace ui {
extern MainApplication* mainApp; extern MainApplication* mainApp;
namespace { namespace {
constexpr int ListX = theme::space::lg; constexpr int ListX = theme::space::lg;
constexpr int ListW = 760; constexpr int ListW = 760;
constexpr int PanelX = ListX + ListW + theme::space::xl; constexpr int PanelX = ListX + ListW + theme::space::xl;
constexpr int PanelW = theme::layout::ScreenW - PanelX - theme::space::lg; constexpr int PanelW = theme::layout::ScreenW - PanelX - theme::space::lg;
constexpr int ContentY = theme::layout::ContentTop + theme::space::md; constexpr int ContentY = theme::layout::ContentTop + theme::space::md;
constexpr int ContentH = theme::layout::ContentH - 2 * theme::space::md; constexpr int ContentH = theme::layout::ContentH - 2 * theme::space::md;
constexpr int BtnH = 56; constexpr int BtnH = 56;
constexpr int BtnW = PanelW - 2 * theme::space::lg; constexpr int BtnW = PanelW - 2 * theme::space::lg;
} // namespace } // namespace
TitlesLayout::TitlesLayout() : Layout::Layout() { TitlesLayout::TitlesLayout() {
using namespace theme; using namespace theme;
this->titlesMenu = this->titlesMenu =
@@ -49,8 +48,8 @@ TitlesLayout::TitlesLayout() : Layout::Layout() {
this->btnTransferBg = this->btnTransferBg =
pu::ui::elm::Rectangle::New(PanelX + space::lg, btnY, BtnW, BtnH, color::BgSurface2, radius::md); pu::ui::elm::Rectangle::New(PanelX + space::lg, btnY, BtnW, BtnH, color::BgSurface2, radius::md);
this->Add(this->btnTransferBg); this->Add(this->btnTransferBg);
this->btnTransferText = this->btnTransferText = pu::ui::elm::TextBlock::New(PanelX + space::lg + space::md, btnY + 14,
pu::ui::elm::TextBlock::New(PanelX + space::lg + space::md, btnY + 14, "Transfer to another device"); "Transfer to another device");
this->btnTransferText->SetFont(type::font(type::Body)); this->btnTransferText->SetFont(type::font(type::Body));
this->btnTransferText->SetColor(color::TextSecondary); this->btnTransferText->SetColor(color::TextSecondary);
this->Add(this->btnTransferText); this->Add(this->btnTransferText);
@@ -65,8 +64,8 @@ TitlesLayout::TitlesLayout() : Layout::Layout() {
this->btnReceiveText->SetColor(color::TextSecondary); this->btnReceiveText->SetColor(color::TextSecondary);
this->Add(this->btnReceiveText); this->Add(this->btnReceiveText);
this->panelFooter = pu::ui::elm::TextBlock::New(PanelX + space::lg, ContentY + ContentH - space::lg - 18, this->panelFooter = pu::ui::elm::TextBlock::New(
"Save data only"); PanelX + space::lg, ContentY + ContentH - space::lg - 18, "Save data only");
this->panelFooter->SetFont(type::font(type::Caption)); this->panelFooter->SetFont(type::font(type::Caption));
this->panelFooter->SetColor(color::TextMuted); this->panelFooter->SetColor(color::TextMuted);
this->Add(this->panelFooter); this->Add(this->panelFooter);
@@ -88,9 +87,9 @@ TitlesLayout::TitlesLayout() : Layout::Layout() {
this->header = std::make_unique<HeaderBar>(this, "Save Transfer"); this->header = std::make_unique<HeaderBar>(this, "Save Transfer");
this->hints = std::make_unique<HintBar>(this); this->hints = std::make_unique<HintBar>(this);
this->updateHints(); this->updateHints();
} }
void TitlesLayout::InitTitles(AccountUid uid) { void TitlesLayout::InitTitles(AccountUid uid) {
using namespace theme; using namespace theme;
this->current_uid = uid; this->current_uid = uid;
@@ -136,19 +135,19 @@ void TitlesLayout::InitTitles(AccountUid uid) {
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() {
if (this->titlesMenu->GetItems().empty()) if (this->titlesMenu->GetItems().empty())
return; return;
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(StringUtils::elide(title.name(), 24)); this->panelTitle->SetText(string_utils::elide(title.name(), 24));
} }
void TitlesLayout::refreshButtons() { void TitlesLayout::refreshButtons() {
using namespace theme; using namespace theme;
const bool active = (focus == TitlesFocus::Actions); const bool active = (focus == TitlesFocus::Actions);
if (active && action == TitlesAction::Transfer) { if (active && action == TitlesAction::Transfer) {
@@ -167,17 +166,17 @@ void TitlesLayout::refreshButtons() {
this->btnReceiveBg->SetColor(color::BgSurface2); this->btnReceiveBg->SetColor(color::BgSurface2);
this->btnReceiveText->SetColor(color::TextSecondary); this->btnReceiveText->SetColor(color::TextSecondary);
} }
} }
void TitlesLayout::updateHints() { void TitlesLayout::updateHints() {
if (focus == TitlesFocus::List) { if (focus == TitlesFocus::List) {
this->hints->SetHints({{"A", "Choose action"}, {"B", "Back"}, {"+", "Quit"}}); this->hints->SetHints({{"A", "Choose action"}, {"B", "Back"}, {"+", "Quit"}});
} else { } else {
this->hints->SetHints({{"A", "Confirm"}, {"B", "Back"}}); this->hints->SetHints({{"A", "Confirm"}, {"B", "Back"}});
} }
} }
void TitlesLayout::runTransfer(int index, Title& title) { void TitlesLayout::runTransfer(int index, Title& title) {
(void)title; (void)title;
auto ovl = TransferOverlay::New("Transferring save data..."); auto ovl = TransferOverlay::New("Transferring save data...");
this->titlesMenu->SetVisible(false); this->titlesMenu->SetVisible(false);
@@ -211,12 +210,12 @@ void TitlesLayout::runTransfer(int index, Title& title) {
} else { } else {
mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true); mainApp->CreateShowDialog("Transfer", "Save data sent successfully!", {"OK"}, true);
} }
} }
void TitlesLayout::runReceive(int index, Title& title) { void TitlesLayout::runReceive(int index, Title& title) {
if (mainApp->transfer.startReceive((size_t)index, this->current_uid, title.name()) != 0) { if (mainApp->transfer.startReceive((size_t)index, this->current_uid, title.name()) != 0) {
mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.", {"OK"}, mainApp->CreateShowDialog("Receive", "Failed to start receiver.\nCheck network connection.",
true); {"OK"}, true);
return; return;
} }
auto ovl = TransferOverlay::New("Receiving save data..."); auto ovl = TransferOverlay::New("Receiving save data...");
@@ -239,14 +238,15 @@ void TitlesLayout::runReceive(int index, Title& title) {
if (mainApp->transfer.isReceiveCancelled()) { if (mainApp->transfer.isReceiveCancelled()) {
mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true); mainApp->CreateShowDialog("Receive", "Transfer cancelled.", {"OK"}, true);
} else if (mainApp->transfer.restoreSucceeded()) { } else if (mainApp->transfer.restoreSucceeded()) {
mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"}, true); mainApp->CreateShowDialog("Receive", "Save data received and restored successfully!", {"OK"},
} else {
mainApp->CreateShowDialog("Receive", "Restore failed:\n" + mainApp->transfer.restoreError(), {"OK"},
true); 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 TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
(void)Up; (void)Up;
(void)Held; (void)Held;
(void)Pos; (void)Pos;
@@ -308,5 +308,5 @@ void TitlesLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
} }
} }
} }
} }
} // namespace ui } // namespace ui
+12 -12
View File
@@ -1,9 +1,9 @@
#include <nxst/app/main_application.hpp> #include <nxst/app/main_application.hpp>
namespace ui { namespace ui {
extern MainApplication* mainApp; extern MainApplication* mainApp;
UsersLayout::UsersLayout() : Layout::Layout() { UsersLayout::UsersLayout() : Layout::Layout() {
using namespace theme; using namespace theme;
this->usersMenu = pu::ui::elm::Menu::New(0, layout::ContentTop + space::md, layout::ScreenW, this->usersMenu = pu::ui::elm::Menu::New(0, layout::ContentTop + space::md, layout::ScreenW,
@@ -11,8 +11,8 @@ UsersLayout::UsersLayout() : Layout::Layout() {
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);
} }
@@ -20,8 +20,8 @@ UsersLayout::UsersLayout() : Layout::Layout() {
this->loadingBg = pu::ui::elm::Rectangle::New(0, 0, layout::ScreenW, layout::ScreenH, color::Scrim); this->loadingBg = pu::ui::elm::Rectangle::New(0, 0, layout::ScreenW, layout::ScreenH, color::Scrim);
this->loadingBg->SetVisible(false); this->loadingBg->SetVisible(false);
this->loadingText = this->loadingText = pu::ui::elm::TextBlock::New(layout::ScreenW / 2 - 120, layout::ScreenH / 2 - 12,
pu::ui::elm::TextBlock::New(layout::ScreenW / 2 - 120, layout::ScreenH / 2 - 12, "Loading saves..."); "Loading saves...");
this->loadingText->SetFont(type::font(type::Body)); this->loadingText->SetFont(type::font(type::Body));
this->loadingText->SetColor(color::TextSecondary); this->loadingText->SetColor(color::TextSecondary);
this->loadingText->SetVisible(false); this->loadingText->SetVisible(false);
@@ -34,20 +34,20 @@ UsersLayout::UsersLayout() : Layout::Layout() {
this->header = std::make_unique<HeaderBar>(this, "Select a user"); this->header = std::make_unique<HeaderBar>(this, "Select a user");
this->hints = std::make_unique<HintBar>(this); this->hints = std::make_unique<HintBar>(this);
this->hints->SetHints({{"A", "Select"}, {"+", "Quit"}}); this->hints->SetHints({{"A", "Select"}, {"+", "Quit"}});
} }
int32_t UsersLayout::GetCurrentIndex() { int32_t UsersLayout::GetCurrentIndex() {
return this->usersMenu->GetSelectedIndex(); return this->usersMenu->GetSelectedIndex();
} }
void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) { void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
if (Down & HidNpadButton_Plus) { if (Down & HidNpadButton_Plus) {
mainApp->Close(); mainApp->Close();
return; return;
} }
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);
@@ -65,5 +65,5 @@ void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
mainApp->titles_layout->InitTitles(uid); mainApp->titles_layout->InitTitles(uid);
mainApp->LoadLayout(mainApp->titles_layout); mainApp->LoadLayout(mainApp->titles_layout);
} }
} }
} // namespace ui } // namespace ui