A native AT Protocol / Bluesky client for the Nintendo Wii U, built as Aroma homebrew with devkitPro/WUT and SDL2.
Version 0.9.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 support for normal two-screen use and Off-TV Play.
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
- 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 from this repository's releases, only when you ask and confirm
- 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 and quote images with alt text
- 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
- 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.
The normal WUHB installation is:
sd:/wiiu/apps/cobalt.wuhb
For a complete bundle, 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.
If you install only the .wuhb (from a release, or from the Homebrew App Store
once Cobalt is listed there) 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.
I have prepared a Homebrew App Store listing but it is not submitted yet, so Cobalt is not in the store at the moment.
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.
Home, then Updates, checks this repository's latest GitHub release. If there is
a newer one it shows the version and notes and does nothing until you press A.
The download is checked against the SHA-256 published in the release before it
is used, and it replaces cobalt.wuhb when you quit Cobalt; start it again from
the Wii U Menu. The previous build is kept as
sd:/wiiu/apps/cobalt/update/cobalt.wuhb.old until the new one has started, and
if an update is interrupted the next launch puts it back.
Each release's update.json is signed. A GitHub Actions job signs it with an
Ed25519 key that exists only as a repository secret and attaches
update.json.sig; Cobalt checks that signature against the public key built in
(src/update/update_key.h) before it reads the manifest, and refuses a release
that has no signature or a wrong one. The manifest carries the file's SHA-256,
so the signature covers the download too. Releases up to 0.5.0 are not signed,
so an older Cobalt that predates this check still takes them on the SHA-256
alone. I have only run this on the host, not on a console. Installing a .wuhb
by hand still works exactly as above.
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.
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.