# NXST Architecture ## Layer Diagram ``` ┌──────────────────────────────┐ │ ui/ │ │ TitlesLayout, UsersLayout │ │ TransferOverlay, HeaderBar │ └──────────────┬───────────────┘ │ calls ┌──────────────▼───────────────┐ │ service/ │ │ TransferService │ └──────┬───────────────┬───────┘ │ │ ┌────────────▼──┐ ┌───────▼────────┐ │ infra/net/ │ │ infra/fs/ │ │ TransferService│ │ io, directory │ │ (sender thread │ │ filesystem │ │ recv threads) │ │ handles │ └────────────┬──┘ └───────┬────────┘ │ │ ┌────────────▼───────────────▼────────┐ │ domain/ │ │ Title, Account, TransferState │ │ Result, protocol constants │ └─────────────────────────────────────┘ │ ┌────────────────────▼────────────────┐ │ libnx, Plutonium, SDL2, devkitpro│ └─────────────────────────────────────┘ ``` **Hard layering rules:** - `domain/` depends on nothing in the project. - `infra/` depends only on `domain/`. - `service/` depends on `domain/` + `infra/`. - `ui/` depends only on `service/` + `domain/` — must not include ``, ``, ``, or call `recv`/`send` directly. - `app/` (MainApplication, main.cpp) wires everything together. --- ## Key Types ### `nxst::TransferService` (`include/nxst/service/transfer_service.hpp`) Owns all network state for both transfer directions. One instance lives in `MainApplication`. | Method | Description | |--------|-------------| | `startSend(index, uid)` | Backup save, discover receiver, stream files | | `startReceive(index, uid, title_name)` | Listen for sender, receive files, restore save | | `cancelSend()` / `cancelReceive()` | Interrupt in-flight transfer (socket shutdown) | | `isSendDone()` / `isReceiveDone()` | Polled by UI render loop | | `restoreSucceeded()` / `restoreError()` | Restore outcome after receive | **Threading model:** ``` UI thread (Plutonium event loop) └─ startSend() └─ senderEntry [pthread, detached] ├─ findServer() — UDP multicast, 100 ms poll slices ├─ io::backup() — creates local save backup └─ TCP send loop └─ startReceive() ├─ broadcastEntry [pthread, detached] — UDP multicast listener └─ acceptEntry [pthread, detached] ├─ TCP receive loop └─ io::restore() — mounts save, clears, copies, commits ``` Cancellation: `cancelSend()` / `cancelReceive()` call `shutdown(SHUT_RDWR)` on all open sockets. Blocking `read`/`recvfrom`/`accept` return errors immediately. Atomic flags are checked at loop boundaries. ### `nxst::Result` (`include/nxst/domain/result.hpp`) 85-line tagged-union result type. No exceptions, no `std::variant`. Used for `io::backup` and `io::restore` return values. ```cpp auto result = io::backup(title_index, uid); if (!result.isOk()) { failSend("Backup failed: " + result.error()); return; } fs::path dir = result.value(); ``` `Result` specialisation available for operations that succeed without a value. ### `nxst::FsFileSystemHandle` / `nxst::FileHandle` (`include/nxst/infra/fs/handles.hpp`) RAII wrappers ensuring `fsFsClose` / `fclose` are called on all exit paths. --- ## Threading Invariants - All `std::atomic` members in `TransferService` use sequential-consistency (default). No explicit `memory_order` needed at this concurrency level. - `TransferState::status` is a `std::string` protected by `std::mutex status_mutex`. All other `TransferState` fields are atomics. - `pthread_create` + `pthread_detach` is used throughout (libnx's supported path). Threads are never joined — they signal completion via `state.done = true` and set their active flag to false. - Cancel is safe to call from any thread. --- ## File Map ``` include/nxst/ ├── app/ │ ├── main.hpp — global state (g_currentTime, g_sortMode, …) │ └── main_application.hpp — MainApplication : pu::ui::Application ├── domain/ │ ├── account.hpp — AccountUid, Account::ids(), Account::username() │ ├── common.hpp — StringUtils (UTF-8/16, elide, accents) │ ├── protocol.hpp — wire constants (ports, sentinel, buffer size) │ ├── result.hpp — Result tagged union │ ├── title.hpp — Title, getTitle(), getTitleCount() │ ├── transfer_state.hpp — TransferState (atomics + mutex-guarded status) │ └── util.hpp — StringUtils helpers, blinkLed ├── infra/ │ ├── fs/ │ │ ├── directory.hpp — Directory iterator (libnx FsDir) │ │ ├── filesystem.hpp — FileSystem::mount/unmount wrappers │ │ ├── handles.hpp — FsFileSystemHandle, FileHandle (RAII) │ │ └── io.hpp — io::backup, io::restore, io::copyFile, … │ ├── net/ │ │ └── socket.hpp — Socket RAII wrapper (int fd) │ └── sys/ │ └── logger.hpp — nxst::log::info/warn/error (printf-checked) ├── service/ │ └── transfer_service.hpp — TransferService └── ui/ ├── card.hpp — Card UI component ├── const.h — layout/color/font constants ├── header_bar.hpp — HeaderBar (title + user avatar row) ├── hint_bar.hpp — HintBar (button legend) ├── theme.hpp — color, radius, spacing, type tokens ├── titles_layout.hpp — TitlesLayout ├── transfer_overlay.hpp — TransferOverlay (progress modal) ├── ui_context.hpp — UiContext (selected user state) └── users_layout.hpp — UsersLayout ``` --- ## Build ```bash # Configure (once — toolchain preset reads $DEVKITPRO automatically) cmake --preset switch # Build cmake --build build # Send to Switch via nxlink cmake --build build --target send ``` Output: `build/NXST.nro` See `README.md` for full build prerequisites.