Files
NXST/docs/ARCHITECTURE.md
T
DragonSpirit b2de532bce
CI / Build NRO (push) Failing after 27s
CI / Format check (push) Successful in 11s
CI / Layering check (push) Successful in 1s
finish refactor, add docs and CI
2026-04-27 03:11:25 +03:00

7.2 KiB

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<T>, 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 <arpa/inet.h>, <sys/socket.h>, <pthread.h>, 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<T, E> (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.

auto result = io::backup(title_index, uid);
if (!result.isOk()) {
    failSend("Backup failed: " + result.error());
    return;
}
fs::path dir = result.value();

Result<void, E> 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<T, E> 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

# 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.