another stage of refactoring
This commit is contained in:
@@ -26,3 +26,4 @@ IncludeCategories:
|
|||||||
SpacesBeforeTrailingComments: 2
|
SpacesBeforeTrailingComments: 2
|
||||||
Cpp11BracedListStyle: true
|
Cpp11BracedListStyle: true
|
||||||
Standard: c++17
|
Standard: c++17
|
||||||
|
NamespaceIndentation: All
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
.serena
|
.serena
|
||||||
|
.idea/
|
||||||
|
|
||||||
# Prerequisites
|
# Prerequisites
|
||||||
*.d
|
*.d
|
||||||
|
|||||||
@@ -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.
|
|
||||||
@@ -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
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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...";
|
|
||||||
@@ -1,43 +1,15 @@
|
|||||||
/*
|
// 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
|
||||||
@@ -46,14 +18,8 @@ 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);
|
|
||||||
AccountUid selectAccount(void);
|
|
||||||
std::string username(AccountUid id);
|
std::string username(AccountUid id);
|
||||||
std::string iconPath(AccountUid id);
|
std::string iconPath(AccountUid id);
|
||||||
} // namespace Account
|
} // namespace account
|
||||||
|
|||||||
@@ -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);
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
#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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
bool containsInvalidChar(const std::string& str);
|
||||||
|
std::string format(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||||
|
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);
|
||||||
std::string removeAccents(std::string str);
|
std::string removeAccents(std::string str);
|
||||||
std::string removeNotAscii(std::string str);
|
std::string removeNotAscii(std::string str);
|
||||||
std::u16string UTF8toUTF16(const char* src);
|
std::u16string UTF8toUTF16(const char* src);
|
||||||
std::string elide(const std::string& s, size_t maxChars);
|
std::string elide(const std::string& s, size_t max_chars);
|
||||||
} // namespace StringUtils
|
} // namespace string_utils
|
||||||
|
|||||||
@@ -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};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 {
|
|
||||||
Result mount(FsFileSystem* fileSystem, u64 titleID, AccountUid userID);
|
|
||||||
int mount(FsFileSystem fs);
|
int mount(FsFileSystem fs);
|
||||||
void unmount(void);
|
void unmount();
|
||||||
} // namespace FileSystem
|
} // namespace file_system
|
||||||
|
|||||||
@@ -1,51 +1,17 @@
|
|||||||
/*
|
// 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);
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
#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 {
|
||||||
@@ -14,46 +12,4 @@ 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)
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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,7 +3,6 @@
|
|||||||
|
|
||||||
#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 {
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
#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>
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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,7 +2,6 @@
|
|||||||
|
|
||||||
#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>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#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>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
#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>
|
||||||
@@ -13,9 +9,9 @@ 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));
|
||||||
|
|||||||
+32
-74
@@ -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{};
|
|
||||||
}
|
|
||||||
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadTitles() {
|
||||||
|
if (s_titles_loaded)
|
||||||
return;
|
return;
|
||||||
s_titlesLoaded = true;
|
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;
|
||||||
|
|||||||
+190
-99
@@ -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);
|
||||||
|
va_end(a2);
|
||||||
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
return UTF16toUTF8(src);
|
std::string string_utils::removeForbiddenCharacters(std::string src) {
|
||||||
|
static constexpr std::string_view kForbidden = ".,!\\/:?*\"<>|";
|
||||||
|
for (char& c : src) {
|
||||||
|
if (kForbidden.find(c) != std::string_view::npos)
|
||||||
|
c = ' ';
|
||||||
|
}
|
||||||
|
auto last = src.find_last_not_of(' ');
|
||||||
|
if (last != std::string::npos)
|
||||||
|
src.erase(last + 1);
|
||||||
|
return src;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string StringUtils::removeNotAscii(std::string str) {
|
static size_t encodeUtf8(char* out, char32_t cp) {
|
||||||
for (size_t i = 0, sz = str.length(); i < sz; i++) {
|
if (cp < 0x80) {
|
||||||
if (!isascii(str[i])) {
|
out[0] = static_cast<char>(cp);
|
||||||
str[i] = ' ';
|
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
@@ -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();
|
|
||||||
}
|
}
|
||||||
@@ -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
@@ -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 +0,0 @@
|
|||||||
// Logic moved to src/service/transfer_service.cpp
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// Logic moved to src/service/transfer_service.cpp
|
|
||||||
@@ -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)
|
||||||
@@ -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;
|
||||||
@@ -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();
|
||||||
@@ -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,7 +278,7 @@ 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();
|
||||||
@@ -319,7 +319,7 @@ void TransferService::cancelSend() {
|
|||||||
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;
|
||||||
@@ -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();
|
||||||
@@ -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
|
||||||
@@ -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();
|
||||||
|
|||||||
+13
-13
@@ -1,6 +1,5 @@
|
|||||||
#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 {
|
||||||
@@ -17,7 +16,7 @@ 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);
|
||||||
@@ -136,7 +135,7 @@ 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() {
|
||||||
@@ -145,7 +144,7 @@ void TitlesLayout::refreshPanel() {
|
|||||||
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() {
|
||||||
@@ -215,8 +214,8 @@ void TitlesLayout::runTransfer(int index, Title& title) {
|
|||||||
|
|
||||||
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,10 +238,11 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -47,7 +47,7 @@ void UsersLayout::onInput(u64 Down, u64 Up, u64 Held, pu::ui::TouchPoint Pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|||||||
Reference in New Issue
Block a user