Skip to content

Releases: Marvbuster/sphaira-connect

Sphaira Connect 1.2.0

Choose a tag to compare

@github-actions github-actions released this 26 Jul 14:44

Sphaira Connect macOS v1.2 Build 1

Date: 2026-07-26 | Status: Done | Minimum macOS: 26.5
MARKETING_VERSION: 1.2 | CURRENT_PROJECT_VERSION: 1
Commit: 3063c66large remaining-time countdown, extracted view files

Summary

Adds a large, prominent remaining-time countdown while either mode is actively running — a
transfer in Sphaira Install, or an injection in RCM Payload. RCM injection did not previously
track throughput at all; it now feeds the same Core/ProgressTracker the installer uses.
RootView.swift was split further to stay under the 400-line contract limit as part of this work.

Caught in local testing before this ever shipped: the first pass placed the badge as a floating
overlay (it covered the mode switcher instead of the window growing to fit it), and animated its
raw, per-tick value directly — since a fresh sample arrives roughly every 100 ms but each animation
takes 300 ms, the badge never finished one transition before the next one interrupted it, looking
restless for the entire transfer rather than just at the start. Both are fixed below.

Features & Changes

Type Description
FEATURE Large remaining-time countdown, part of the normal content layout above the mode switcher (the window grows to fit it, exactly like the queue or the progress panel) whenever a transfer or injection is actively running — .monospacedDigit() plus .contentTransition(.numericText(countsDown: true)) for the native counting-down digit animation (Features/Views/CountdownBadgeView.swift, Features/Views/RootView.swift) — see [[ui/window-and-views]]
FEATURE The displayed countdown value refreshes at most once a second (RootView.refreshCountdown(with:)), decoupled from the underlying ~100 ms sample rate, so each animation has time to settle instead of being interrupted by the next one — a real "clock ticking down" instead of a scrubbing number. The raw per-tick estimate is additionally withheld (shown as an en dash) until ProgressTracker.tickCount reaches 3, so the exponential smoothing has a moment to settle before anything is displayed at all (Core/ProgressTracker.swift, Features/Views/RootView.swift) — see [[protocol/progress-tracking]]
FEATURE RCM injection now computes throughput: RCMInjectorModel.inject() feeds RCMTransport's cumulative (sent, total) callback into a Core/ProgressTracker, the same tracker and smoothing the installer already used (Features/RCMInjectorModel.swift) — see [[protocol/progress-tracking]]
CHANGE RootView's mode switcher (segmented Sphaira Install / RCM Payload picker with its matched-geometry highlight) extracted into ModeSwitcherView to keep RootView.swift under the 400-line limit from CONTRACT.md (Features/Views/ModeSwitcherView.swift)

Tests

63 tests passing (was 62) — adds tickCountCountsEmissions to ProgressTrackerTests. The rest of
this feature sits in the SwiftUI/Features layer, outside the SphairaCoreTests boundary (see
[[operations/testing-strategy]]); verified via a clean Debug and Release build. Not yet
re-confirmed against real hardware.

Installation

Open the DMG and drag the app into your Applications folder
(or unpack the ZIP). On first launch, right-click → Open:
the app is ad-hoc signed and not notarised, so Gatekeeper would
otherwise refuse to start it.

File SHA-256
Sphaira-Connect-1.2.0-arm64.dmg e2d007eb309ba78c652616172c5a38bbe91ce1f2a2b602c8a6ba9438fa0ed6ff
Sphaira-Connect-1.2.0-arm64.zip 85927a80a206c8f23f575adbfba73f0c095e4c65bfad868a4182e0b47e3694a1

Sphaira Connect 1.1.0

Choose a tag to compare

@github-actions github-actions released this 26 Jul 12:09

Sphaira Connect macOS v1.1 Build 1

Date: 2026-07-26 | Status: Done | Minimum macOS: 26.5
MARKETING_VERSION: 1.1 | CURRENT_PROJECT_VERSION: 1
Commit: 370aadaRCM payload injection, official Hekate/Fusée presets

Summary

Sphaira Connect gains a second mode alongside the existing USB installer: manual RCM
payload injection for unpatched Erista/V1 Switch consoles, based on the publicly
documented Fusée Gelée protocol. The app builds the RCM image itself (header, address
table, intermezzo relocator, user payload), detects the distinct APX device
(0955:7321), transfers it over libusb and triggers the exploit with an oversized
control transfer — never automatically, only on an explicit button press. On top of
manual .bin selection, two persistent presets (Hekate, Fusée) can fetch the current
or four previous official releases directly from GitHub, verifying size and SHA-256
digest before use. The shared log line type used by both the installer and the RCM
view was factored out along the way.

Features & Changes

Type Description
FEATURE RCM image builder: 4-byte header (rcmLength), 96-entry address table pointing at the intermezzo relocator, the 92-byte relocator itself, and the user payload appended and packet-aligned to 0x1000 (Core/RCMPayload.swift)
FEATURE APX-mode device detection via VID 0x0955 / PID 0x7321, polled every 2 seconds independent of the installer's own Switch monitor (USB/RCMTransport.swift, Features/RCMInjectorModel.swift)
FEATURE RCM transport: reads the 16-byte device ID, writes the image in 0x1000-byte chunks with parity padding for an even packet count, then triggers Fusée Gelée with an oversized control transfer (USB/RCMTransport.swift)
FEATURE RCMTriggerOutcome classifies the libusb result of the trigger transfer into "payload started" (expected disconnect-style errors), "device responded" (likely patched) and "USB failure", replacing an inline check with a dedicated, independently testable type (Core/RCMTriggerOutcome.swift)
FEATURE RCMInjectorModel mirrors InstallerModel's event-stream architecture for the RCM flow: preparing/injecting/succeeded/failed phases, progress and log, all routed through one AsyncStream (Features/RCMInjectorModel.swift)
FEATURE Manual .bin selection via drag-and-drop or NSOpenPanel, restricted to the .bin extension and validated as a regular, readable file before use (Features/Views/RCMPayloadPickerView.swift)
FEATURE Persistent Hekate and Fusée presets: the chosen file path survives app restarts via UserDefaults, is re-validated on launch, and falls back cleanly if the file has moved or been deleted (Features/RCMInjectorModel.swift)
FEATURE Official-release download menus for both presets: the current and four previous stable releases are listed from the GitHub API (CTCaer/hekate, Atmosphere-NX/Atmosphere), the correct asset is picked by name (rejecting the 8 GB Hekate variant and non-fusee.bin Atmosphère assets), and any path-traversing asset name is rejected outright (Core/HekateRelease.swift, Core/FuseeRelease.swift, Features/HekateDownloader.swift, Features/FuseeDownloader.swift)
FEATURE Downloaded payloads are size-capped at 2 MB and checked against the SHA-256 digest GitHub publishes for the asset, when present, before being written into Application Support (Features/HekateDownloader.swift, Features/FuseeDownloader.swift)
FEATURE Mode switch in the main window (Sphaira installation ⇄ RCM payload), each with its own device monitor, status pill and log, switching monitors cleanly on change (Features/Views/RootView.swift)
CHANGE InstallerModel.LogLine extracted into a shared AppLogLine, now used by both InstallerModel and RCMInjectorModel so the log console view has a single type to render (Shared/AppLogLine.swift, Features/InstallerModel.swift, Features/Views/LogConsoleView.swift)
CHANGE About panel and README/NOTICE credit the Fusée Gelée protocol and the ironfoil reference implementation; README documents the new RCM flow, the preset downloads and the project's licensing stance in a new "Open source, no agenda" section (Sphaira_ConnectApp.swift, README.md, README.de.md, NOTICE.md)

Tests

62 tests passing (up from 52), adding RCMPayloadTests, RCMTriggerOutcomeTests,
HekateReleaseTests and FuseeReleaseTests — including explicit coverage for
rejecting the 8 GB Hekate variant, rejecting non-fusee.bin assets, and rejecting
path-traversing asset names (../hekate_ctcaer_v1.bin).

Installation

Open the DMG and drag the app into your Applications folder
(or unpack the ZIP). On first launch, right-click → Open:
the app is ad-hoc signed and not notarised, so Gatekeeper would
otherwise refuse to start it.

File SHA-256
Sphaira-Connect-1.1.0-arm64.dmg f2df971e29b64b9e170f61545829a7044623aea56a68973d60d326431755fd49
Sphaira-Connect-1.1.0-arm64.zip 33dc933f2a19497264c8c475a9f3b7bbf517c6d2d9ce390f1bd7a12b1032dcb4

Sphaira Connect 1.0.0

Choose a tag to compare

@github-actions github-actions released this 26 Jul 10:14

Sphaira Connect macOS v1.0 Build 1

Datum: 26.07.2026 | Status: Fertig | Minimales macOS: 26.5
MARKETING_VERSION: 1.0 | CURRENT_PROJECT_VERSION: 1
Commit: 77527a1feat: Sphaira Connect — native macOS-App für USB-Installation

Zusammenfassung

Erstes Release. Sphaira Connect ersetzt das Python-Skript usb_install.py durch eine
native SwiftUI-App: Sie spricht das SPH0-Protokoll direkt gegen statisch gelinktes
libusb, nimmt NSP/XCI/NSZ/XCZ per Drag-and-drop entgegen und überträgt sie an die
Homebrew-App Sphaira auf der Nintendo Switch. Die Kernlogik liegt SwiftUI- und
USB-frei unter Sphaira Connect/Core/ und wird von einem eigenständigen
SwiftPM-Testpaket mit 52 Tests geprüft, das die Quellen per Symlink einbindet —
ohne zweite Kopie der Dateien.

Der zweite Teil des Releases sind die Korrekturen aus den ersten Praxisläufen: der
Fortschrittsbalken stand bei XCI sofort auf 100 %, die Abschlussmeldung blieb nach
Sitzungsende aus, und ein hängender Schreibvorgang machte „Abbrechen" wirkungslos.

Features & Änderungen

Typ Beschreibung
FEATURE SPH0-Paketformat nativ implementiert: 24 Bytes, sechs UInt32 little-endian, Magic 0x53504830, CRC32C über die ersten 20 Bytes (Core/SphairaProtocol.swift)
FEATURE CRC-32C (Castagnoli, gespiegeltes Polynom 0x82F63B78) als tabellenbasierte, nonisolated Implementierung ohne Fremdbibliothek (Core/CRC32C.swift)
FEATURE Sitzungsablauf als Zustandsautomat: Namensliste senden, danach Kommandoschleife über open und quit, Dateiblöcke auf Anfrage der Switch (USB/InstallSession.swift) — siehe Wiki protocol/install-session-flow
FEATURE libusb-Anbindung mit Bulk-Transfers, Endpunkte aus der aktiven Konfiguration ermittelt, Rückfall auf 0x81/0x01 (USB/USBTransport.swift)
FEATURE libusb statisch gelinkt (/opt/homebrew/lib/libusb-1.0.a) über Bridging-Header; App-Sandbox aus, Hardened Runtime an (Sphaira Connect.xcodeproj/project.pbxproj, USB/SphairaConnect-Bridging-Header.h)
FEATURE Geräteerkennung über VID 0x057E / PID 0x3000, Statusanzeige pollt alle 2 Sekunden abseits des MainActors (USB/LibUSBProbe.swift, Features/InstallerModel.swift)
FEATURE Warteschlange per Drag-and-drop oder Auswahldialog; Ordner werden rekursiv aufgelöst, Duplikate über den standardisierten Pfad verworfen, nur nsp/xci/nsz/xcz übernommen, Reihenfolge stabil alphabetisch (Core/InstallItem.swift, Features/Views/DropZoneView.swift)
FEATURE Warteschlangenliste mit Zustand je Titel (wartet / überträgt / fertig / fehlgeschlagen), Einzelentfernung und „alle entfernen" — im laufenden Betrieb gesperrt (Features/Views/QueueListView.swift)
FEATURE Fortschrittsanzeige mit Datei- und Gesamtanteil, geglättetem Durchsatz und geschätzter Restzeit; Aktualisierung höchstens alle 100 ms (Core/ProgressTracker.swift, Features/Views/ProgressPanelView.swift)
FEATURE Deutsche Größen-, Durchsatz- und Zeitformatierung mit Dezimalkomma, ohne NumberFormatter (Core/ByteFormat.swift)
FEATURE Aufklappbares Protokoll mit Zeitstempeln, begrenzt auf die letzten 500 Zeilen (Features/Views/LogConsoleView.swift, Features/InstallerModel.swift)
FEATURE Abbruch jederzeit möglich: Das Abbruch-Flag ist über ein NSLock atomar und wird im USB-Thread zwischen den Transfers geprüft, ohne auf MainActor-Zustand zu warten (Features/InstallerModel.swift, USB/USBTransport.swift)
FEATURE Deutschsprachige Fehlertexte für alle Fehlerfälle — Switch nicht gefunden, Zugriff verweigert, USB-Fehler mit Code, kurzes Paket, falsche Magic, Prüfsummenfehler, unerwarteter Befehl, unlesbare Datei, Abbruch (Core/InstallError.swift)
FEATURE Liquid-Glass-Oberfläche mit eigenem Designsystem: Farb- und Abstands-Token, Glas-Buttons, Fenster folgt dem Inhalt animiert (Shared/DesignTokens.swift, Shared/GlassButton.swift, Shared/WindowStyleConfigurator.swift, Features/Views/RootView.swift)
FEATURE Eigenständiges SwiftPM-Testpaket mit 52 Tests über Paketformat, CRC32C, Ordnerscan, Fortschritt, Formatierung und Fehlertexte; bindet Sphaira Connect/Core/ per Symlink ein (SphairaCoreTests/Package.swift)
FIX Fortschritt zählte die höchste von der Switch angefragte Position statt der real gesendeten Bytes — bei XCI stand der Balken dadurch sofort auf 100 %. ProgressTracker.record summiert jetzt nur übertragene Blöcke und klemmt auf die Dateigröße, da die Switch Bereiche mehrfach anfordern darf (Core/ProgressTracker.swift)
FIX close() blockierte nach Sitzungsende und hielt die Abschlussmeldung auf, wodurch „Abbrechen" aktiv blieb. Der libusb-Abbau läuft jetzt asynchron auf einer Hintergrund-Queue, die Referenzen werden vorher gelöst (USB/USBTransport.swift)
FIX Schreibvorgänge hatten kein Zeitlimit, ein Abbruch griff mitten im Block nicht. Bulk-Writes laufen jetzt mit 5000 ms Zeitlimit, Timeouts gelten als Gelegenheit zur Abbruchprüfung, bereits übertragene Teilbytes werden mitgezählt (USB/USBTransport.swift)
FIX Rückmeldungen der Sitzung liefen über einzelne unstrukturierte Tasks und kamen dadurch ungeordnet an. Alle Ereignisse laufen jetzt durch genau einen AsyncStream, die Zahl fertiger Dateien zählt ein gesperrter Zähler auf dem Sitzungs-Thread (Features/InstallerModel.swift)
FIX Endet der Ereignisstrom ohne Abschlussmeldung, blieb die Oberfläche auf „läuft" stehen; ein Rückfall setzt die Phase jetzt auf „bereit" und schreibt eine Protokollzeile (Features/InstallerModel.swift)
FIX Der Gesamtfortschritt sprang über eine gescheiterte Datei hinweg — gezählt werden jetzt nur vollständig übertragene Dateien (USB/InstallSession.swift)
FIX Bei einem Dateifehler geht ein Fehlerpaket an die Switch, die Kommandoschleife beantwortet danach den Quit-Befehl regulär; die Sitzung endet geordnet statt mit abruptem USB-Abriss (USB/InstallSession.swift)
CHANGE Die SwiftData-Vorlage des Xcode-Templates entfällt: Item.swift gelöscht, ContentView.swift und Sphaira_ConnectApp.swift auf RootView umgestellt

Installation

DMG öffnen und die App in den Programme-Ordner ziehen
(oder das ZIP entpacken). Beim ersten Start Rechtsklick →
Öffnen: Die App ist ad-hoc signiert und nicht notarisiert,
sonst verweigert Gatekeeper den Start.

Datei SHA-256
Sphaira-Connect-1.0.0-arm64.dmg f251e116a0d13f04c2b61e0f6f11bef57057883c9e6e419bbb1742e0daa68719
Sphaira-Connect-1.0.0-arm64.zip 63077e570550d9268800cc847136f298886f9b4cefc14d1735257712519ace28