A native AT Protocol / Bluesky client for the Nintendo Wii U, built as Aroma homebrew with devkitPro/WUT and SDL2.
Version 0.14.0
Cobalt exists because I looked at a Wii U's weak little PowerPC tri-core and decided that, somehow, it was going to post to Bluesky.
That is not a metaphor for the project. I deliberately wanted to make AT Protocol work on hardware that was never designed for it, and I wanted to do it properly rather than turning the Wii U into a thin browser target.
Cobalt treats the Wii U as the platform it is rather than as a browser target: the TV and GamePad are both first-class displays. With both on they divide the work — the TV reads, the GamePad is the remote — and Off-TV Play gives the GamePad the whole interface on its own.
Cobalt uses Wolfram, my C AT Protocol SDK, for its AT Protocol implementation. The project sits alongside the rest of my mineral and material-named projects and deliberately echoes Channel Blue, the Wii counterpart.
Usable on real hardware.
Cobalt is installed and running on a Wii U, and the core Bluesky workflow is usable end-to-end. Host-side compilation, unit tests, link checks, mock-PDS tests and the desktop simulator provide additional coverage, but they do not replace testing on the console.
- TV + GamePad and Off-TV Play
- Light and dark themes, three post-text sizes and a high-contrast mode, set in Settings and saved on the SD card, with one row to reset all of them
- A Credits screen with the full repository address for every project in the stack
- Diagnostics covering paths, networking, TLS, Wolfram and session state
- App-password sign-in with an on-screen keyboard
- Browser-based OAuth sign-in through a hosted Wolfram OAuth node: Cobalt shows a pairing link, you open it on a phone or computer, and the PDS handles your password and MFA
- Persistent encrypted sessions and sign-out
- Updates through the Homebrew App Store, which Cobalt is published in
- Home timeline with paging, reposts and threads
- Posting, replies, quote posts and reply gates
- Threads: write up to eight posts in a row and publish them as one thread
- Likes and reposts, including undo
- Notifications and mark-as-seen
- Profiles, follows and unfollows
- Followers, following and profile tabs
- Pinned posts
- Links shown as a QR code (Wolfram's encoder), so a phone can open what the console cannot
- Avatars
- Post, reply, quote and gallery images with alt text (the first four of a gallery)
- A full-size image viewer, from the post menu, on both screens
- Who liked or reposted a post, from the post menu
- Link-card previews, including hydrated article, gallery and livestream cards from the AppView
- Actor search
- Custom feed browsing
- Read-only lists and list members
- Mute and block, including browsable lists
- Post search
Some Bluesky features do not currently fit Cobalt's Wii U target:
- Video and GIFs — there is no suitable decoder in the current dependency and performance budget.
- Push notifications — there is no modern push service available to a Wii U homebrew application.
These are platform constraints rather than features merely waiting in the backlog. What is done and what is open, next to Indigo and Platinum, is in docs/PARITY.md.
Cobalt requires a Wii U with Aroma installed. It does not install or facilitate the exploit required to run Aroma.
For networked AT Protocol features, you need one of two ways to sign in:
- A Bluesky app password. Cobalt does not use your normal account password.
- An OAuth sign-in through a hosted Wolfram OAuth node. Put the node's URL in the service field, leave the app password empty, and open the pairing link Cobalt shows on another device. Someone has to run the node; Cobalt does not do OAuth on the console itself.
Recommended: Download Cobalt from the Homebrew App Store (HBAS)
using the hb-appstore client on your Wii U. Search for "Cobalt" and install it
directly — this is the easiest and safest way to get updates.
If Cobalt is not yet visible in the store in your region, you can install it manually from a GitHub release.
Download cobalt-<version>.wuhb from the
latest GitHub release and
place it at:
sd:/wiiu/apps/cobalt.wuhb
For a complete bundle with a pre-generated entropy seed, make bundle creates:
dist/wiiu/
└── apps/
├── cobalt.wuhb
└── cobalt/
└── entropy.bin
The entropy seed is generated for that installation and must not be shared between consoles. Copy both to your SD card as above.
If you install only the .wuhb (from HBAS or from a GitHub release) there is no
seed, and Cobalt will not go online without one. The first time it starts it
asks you to scribble on the GamePad screen until a bar fills, builds a seed
from that, saves it, and asks you to start it again. I have not measured how
much randomness a scribble carries, so treat that as better than nothing rather
than as a proof; make bundle is still the better way. I have only run this on
the host, not on a console.
Cobalt is listed in the store already: packages/Cobalt in
fortheusers/wiiu-hbas-repo carries version 0.12.0, and a later release updates
it with tools/submit-hbas.sh. docs/homebrew-app-store.md
covers the package and what there was no way to check from here.
Cobalt can also be loaded in Cemu for development. Cemu is useful for broad application and UI checks, but it does not reproduce every GamePad, networking, TLS or hardware condition of a real Wii U.
Cobalt has no updater of its own. Updates come from the Homebrew App Store: the
package there carries each release's .wuhb, and a newer version is what tells
hb-appstore to offer it. Nothing is downloaded or replaced from inside Cobalt,
and Cobalt never restarts itself — you update it from the store, or by putting a
new .wuhb over the old one by hand as above.
The store's package is generated by tools/make-hbas-listing.py and never
carries entropy.bin, so a store install asks for a scribble on first run like
any other .wuhb-only install. docs/homebrew-app-store.md
has the rest.
Cobalt starts on the Home menu. Sign in from there; Requirements covers the two ways in. Every screen names its own controls in the footer, and on the GamePad each of those prompts is also a button you can tap. The ones that mean the same thing everywhere:
| Control | Does |
|---|---|
| D-pad up and down | move the selection |
| D-pad left | like the selected post |
| D-pad right | repost the selected post |
| A | open or confirm |
| B | back |
| Y | the selected post's menu: the author's profile, its images, quote, who liked or reposted it |
| X | write a new post from the timeline |
| + | reload the timeline |
| Touch | select a row, or press a footer prompt; drag a finger up or down a list to scroll it, a row for every row's height dragged |
Aroma keeps the HOME button for itself, so Cobalt never sees it. Quit from Cobalt's own Home menu instead. The same menu switches between TV + GamePad and Off-TV Play.
The user guide goes through each screen with screenshots.
Cobalt signs in with a Bluesky app password, through
com.atproto.server.createSession, or through OAuth on a Wolfram OAuth node,
as described under Requirements. Either way, the session it
gets back is stored the same way.
Persistent state is kept under:
| File | Contents |
|---|---|
sd:/wiiu/apps/cobalt/session.dat |
Encrypted PDS session |
sd:/wiiu/apps/cobalt/device.key |
Per-installation encryption key |
sd:/wiiu/apps/cobalt/entropy.bin |
Per-installation entropy seed |
sd:/wiiu/apps/cobalt/cobalt.log |
Debug log |
Signing out overwrites the session and key before removing them.
The session encryption is deliberately limited by the Wii U's security model: the homebrew environment provides no application-accessible keystore, so the key lives alongside the encrypted session. It protects against incidental exposure of the session file, not someone who has the whole SD card.
The Wii U's available mbedTLS entropy source is not suitable for Cobalt's cryptographic needs. Cobalt therefore provisions 64 bytes of entropy per installation and uses its own deterministic generator for subsequent draws, while also providing the required entropy to Wolfram and its TLS transport.
The seed is rotated on boot. A missing seed prevents network authentication rather than silently falling back to weaker randomness.
- devkitPro with devkitPPC and the WUT SDK
- the Wii U SDL2, SDL2_ttf and SDL2_image portlibs
- Wii U curl and mbedTLS
- a sibling checkout of Wolfram
- OpenSSL for generating the per-installation entropy seed
Build Wolfram for Wii U first, then build Cobalt:
git clone https://github.com/ewanc26/wolfram ../wolfram
cd ../wolfram
cmake -S . -B build-wiiu \
-DCMAKE_TOOLCHAIN_FILE=$PWD/.devdeps/wiiu.cmake \
-DWOLFRAM_BUILD_WIIU=ON \
-DWOLFRAM_BUILD_TESTS=OFF \
-DWOLFRAM_BUILD_EXAMPLES=OFF
cmake --build build-wiiu -j8 --target wolfram
cd ../cobalt
makeCobalt looks for Wolfram at ../wolfram/build-wiiu by default. Set
WOLFRAM_ROOT and WOLFRAM_BUILD to override those paths.
A Cobalt build can technically be produced without Wolfram, but the AT Protocol functionality is then omitted. A normal networked build should therefore have Wolfram available.
| Command | Purpose |
|---|---|
make |
Build the Wii U application |
make bundle |
Build the application and create a per-installation entropy seed |
make test |
Run the host-side test suite |
make cacert |
Refresh the bundled TLS trust store |
make run |
Print the expected installation/push location |
make clean |
Remove generated build and test output |
The Wii U curl port does not provide a system certificate store that Cobalt
can rely on. The build therefore fetches a Mozilla CA bundle into
romfs/cacert.pem.
The bundle is generated rather than committed, so it can be refreshed as the Mozilla trust set changes:
make cacertAn offline build can still complete without the bundle, but HTTPS requests will fail certificate verification on the console.
There is no complete Wii U emulator in Cobalt's workflow, so the repository keeps platform-independent checks on the build machine and uses the real console for hardware-specific behaviour.
make testThis runs the host compile sweep, Wolfram link check when a host Wolfram build is available, and the unit tests.
The test suite covers platform-independent logic including credential storage, service URL handling, keyboard text handling, feed and notification parsing, post layout, image-cache behaviour and other application logic. The Wolfram configuration is also syntax-checked when a sibling Wolfram checkout is available.
Additional checks are available when the corresponding Wolfram host build is present:
make -C tests e2e
make -C tests snapshot
make -C tests simThe live simulator runs the real application and renderer in two Wii U-sized windows against a mock PDS. It is useful for UI work without a console, but complements rather than replaces hardware testing.
See tests/README.md for the host-test requirements and individual targets.
src/
├── main.c entry point and frame loop
├── app/ screens and application state
├── ui/ rendering, theme and on-screen keyboard
├── input/ VPAD and controller input
├── net/ Wii U network status and HTTP
├── atproto/ Wolfram-backed session and protocol integration
├── cache/ credential storage
└── util/ logging, paths, entropy and time handling
tools/ asset generation and trust-store fetching
tests/ host checks, unit tests and simulator
romfs/ bundled application data and fonts
AGENTS.md contains the deeper architectural notes and platform constraints behind the implementation.
The GamePad is a first-class display and input surface, including touch input and a dedicated on-screen keyboard. With both screens on it holds the compact list of what is on the TV, the actions and the keyboard, so a post can be read at TV size while it is chosen and answered on the pad.
The footer prompts are controls rather than captions: on the GamePad, tapping a hint pill presses the button it names. That is what makes them readable by someone holding the pad instead of memorised by someone using a Pro Controller with the GamePad out of view.
Image alt text is surfaced directly in the UI because Wii U homebrew does not provide Cobalt with a usable system screen reader. Images with alt text are marked with an explicit ALT badge, and the focused post can display the first image's alt text as a caption.
The post menu's "View image" opens a picture contain-fitted to the whole surface — the TV's 720p where the card thumbnail was a fraction of that — with the alt text beneath and Left/Right cycling when a post carries several.
See CONTRIBUTING.md before making changes. Keep changes focused, preserve the existing platform architecture, and run the relevant host-side checks before opening a pull request.
Cobalt is licensed under the GNU Affero General Public License v3.0. See LICENSE.
Bundled fonts and icon assets have their own licences where applicable; see romfs/FONTS.md for attribution and licence details.