Viridite Setup Controls Updating Adding a game Managing a game Forwarders Starting a game Architecture Building from source Submitting a report More

Setup

  1. Download the latest bundle and drag its contents onto your SD card's /switch/ folder.
  2. Place .apk files in sdmc:/Viridite/apks/.
  3. Launch Viridite from hbmenu (Atmosphere CFW required).
  4. Navigate with D-pad, left stick, or touch; press A or tap a game to launch it.
  5. If a launch fails, check sdmc:/Viridite/compat_log.txt for the full error log.

Controls

Docked play works for games with their own controller support. Android games are built for touch, and the Switch touchscreen only works in handheld mode. But some games ship real gamepad support, and where they do, Viridite drives it from the Switch pad — so those play docked. Hill Climb Racing is the first title wired up this way. Everything else is handheld-only, and the launcher says so in the footer when docked.

In the launcher

InputAction
D-pad / either stick / swipeMove through the game list
L / R / ZL / ZRPage a screenful at a time
Stick clickJump to the top of the list
A / tap a gameLaunch
BManage the selected game — or back out of any screen
XReinstall and launch (re-extracts the APK)
YRescan the APK folder
About and credits
+Quit

A USB keyboard works too — arrows and Page Up/Down to move, Enter to launch, Escape to back out. Every screen accepts the same inputs; there is no screen that only answers the D-pad.

Choosing how to play

For a game with controller support, the launcher asks before starting it — showing the controller it can actually see attached (Pro Controller, handheld, a Joy‑Con pair, or a single Joy‑Con held sideways) alongside a touch-screen option. It only asks when there's a genuine choice: docked with one controller, or a game with no controller path, goes straight to launching.

Whichever you pick is also used to patch the game's own controller-help screen, so the diagram it shows is the controller in your hands rather than the Android pad it was drawn for.

Hill Climb Racing

InputAction
R / ZRAccelerate
L / ZLBrake and reverse
D-pad / face buttonsMenus
+Pause

That mapping was read out of the game's own binary rather than guessed: its touch handler and its controller handler write the same two pieces of state, and the accelerator is the right-hand pedal on screen.

Updating

Viridite checks for a newer release when it starts, on a background thread, so the game list stays responsive while it does. If a newer version exists you're asked whether to install it; nothing is downloaded or replaced unless you say yes.

Installing replaces the launcher and both Translation Core binaries together — a launcher from one release paired with a Core from another isn't a combination anyone tests. The new files are written and checked before anything existing is touched, so a failed update leaves your install exactly as it was.

A console with no network connection simply skips the check and says so in the log. Nothing about the launcher depends on being online.

Managing a game

Press B on any game for its settings, which mirror Android's own app-info screen:

ActionRemovesKeeps
Clear cacheExtracted libraries and assetsSaves and settings
Clear storageEverything for that game, saves includedNothing
DeleteThe APK and all of its dataNothing

Each shows how much space it would actually reclaim, measured off the card, and each needs a second press to confirm. Clearing the cache just means the game re-extracts next time it launches — progress is untouched.

There's also a per-game framerate cap here, for titles that run hot or drain battery faster than they need to.

Adding a game

Viridite ships no games. You supply the APK.

Copy the .apk (or .xapk) onto your SD card at /Viridite/apks/ and it appears in the launcher next time you open it. That is the whole process — there is no import step, no conversion, and nothing to register.

Split APKs are handled: an .xapk is unpacked and its config.* splits merged automatically, so games distributed that way work without being reassembled by hand first.

The game must contain native ARM code — arm64-v8a or armeabi-v7a. Viridite runs the game’s own machine code directly, so a title written purely in Java or Kotlin has nothing for it to run. The launcher tells you which architecture an APK contains before you launch it.

Where you get the APK is your responsibility. Use a copy you have lawfully obtained. Viridite does not download games, does not link to pirated sources, and cannot grant you rights in someone else’s software — see Legal.

If a game works, or fails in an interesting way, tell us: the compatibility list is built entirely from reports.

Forwarders

Installing a game writes a forwarder to /switch/Viridite Games/<package>.nro. hbmenu lists it with the game’s own name, icon and version, and selecting it goes straight into the game rather than into Viridite’s list.

Uninstalling removes it again, and every time the launcher scans your APKs it reconciles that folder against what is actually installed — so it can never show an entry for a game that is no longer there.

Every forwarder is the same small stub binary. What differs is the metadata appended to it and the filename, which is the Android package name; the stub reads that back from its own path and hands off to the Translation Core. Nothing is compiled on your console.

The author line credits the game’s developer first, then Viridite — Fingersoft | Viridite Contributors — and the version shown is the game’s version, not Viridite’s, because the entry describes the game it will start.

Starting a game

Launching a game runs a loading screen while the APK is unpacked and its libraries are mapped. When the game is genuinely ready, the gem animation you have been watching completes: it pulses, breaks, and the game’s own icon bursts through, against a short jingle. The screen falls to black and the game fades up out of it.

The jingle is a MIDI file rendered on the console rather than a recording — a few hundred bytes of notes, synthesised as struck crystal, which is what Viridite is named after. You can rewrite it: it lives in tools/make_jingle.py in the launcher repository.

Themes

Viridite’s launcher is built on Material 3 (Material You). Every colour in the interface is a Material role — not a hand-picked value — generated from tonal palettes derived from a seed colour, so text always keeps a readable contrast against whatever sits behind it, whichever theme you pick.

Press ZL on the game list (or P on a keyboard) to open the theme picker. Six schemes ship — Viridite, Mint and Violet, each in light and dark. Each row previews itself in its own colours. Your choice is remembered in sdmc:/Viridite/theme.txt.

Type is Roboto Flex and the icons are Material Symbols Rounded, both from Google Fonts and bundled with the launcher — nothing is downloaded at runtime.

Self-test

Hold Y for five seconds on the game list to run a self-test. It checks the things that otherwise only show up as a failed launch: that both Translation Core binaries are present and are real builds rather than truncated copies, that the bundled artwork and system font load, that the SD card is genuinely writable (written, read back and deleted — a full or read-only card passes a simple existence check), free space, and every game’s manifest, install and architecture.

It will tell you, for example, that a game is marked installed but has no native libraries behind it — a half-finished extract that looks fine in the list and then fails at launch. Results are written to sdmc:/Viridite/selftest.txt and also copied into launcher_log.txt.

Deep test

With a game highlighted, press X on the self-test screen. Viridite hands over to the Translation Core, which loads that game exactly as a real launch does — extracting, mapping every native library, relocating, and resolving every imported symbol — then stops before any of the game’s own code runs, and hands straight back with the result.

It takes a couple of seconds and reports the load time and the number of unresolved symbols. Zero unresolved is what you want; a number climbing after an update means a function the game needs has gone missing from the compatibility layer.

Screen orientation

Android games declare which way up they expect to be, and Viridite honours it:

  • Docked — landscape, always. A television does not rotate.
  • Handheld, Joy-Cons attached — landscape too. You are holding it like a gamepad, and turning the picture would put the controls on the wrong edges.
  • Handheld, Joy-Cons detached — follows the console.

A portrait game under a landscape rule is drawn at its own aspect down the middle of the screen with black bars either side, never stretched.

One hardware note: a Switch console has no motion sensor of its own — the accelerometers live in the Joy-Cons. While attached they are rigidly part of the console and report its orientation faithfully; detached, they report their own, which says nothing about how the console is being held. So on a standard Switch the one case that asks for auto-rotation is the one case that cannot sense it, and Viridite holds the last known orientation rather than spinning the screen because a Joy-Con was set down on a table. A Switch Lite has a built-in sensor and does not have this problem.

Architecture — launcher + Translation Core

Viridite is split into two pieces, in two separate repos, that chain-load into each other rather than being one monolithic binary:

Why split it this way: a Switch process runs in one execution state (AArch64 or AArch32) for its whole lifetime, so the picker and a hypothetical future 32-bit engine can't be one process. Splitting the picker from the engine means adding a real 32-bit engine later is "point the launcher at a new NRO," not "rewrite everything." Splitting them into separate repos keeps the launcher (small, stable) decoupled from the engine (where almost all development happens) — "the app won't launch" and "the game crashes" issues land in the right place.

Building from source

Requires devkitPro with devkitA64 and libnx installed.

Viridite is split across two repositories as well as two NRO binaries — clone both as siblings to build everything and get a drag-to-SD-card layout:

export DEVKITPRO=/opt/devkitpro
git clone https://github.com/Viridite/Viridite.git
git clone https://github.com/Viridite/VNX-Translation-Core.git
cd Viridite
./build_all.sh

Output: testingbuild/Viridite.nro (copy to sdmc:/switch/) and testingbuild/Viridite/ (copy the whole folder to sdmc:/switch/Viridite/).

Just want the launcher on its own? make in the Viridite repo builds Viridite.nro by itself.

Dependencies (via pacman/devkitPro)

switch-sdl2 switch-sdl2_image switch-sdl2_ttf
switch-libpng switch-libjpeg-turbo switch-minizip
switch-mesa switch-glad switch-curl switch-mbedtls

Prebuilt releases (no toolchain needed) are published on the Releases page — each one bundles the launcher plus both Translation Core builds, ready to drag onto an SD card. Releases are built and published automatically by a GitHub Actions workflow (manually triggered by org staff) that also generates an in-depth changelog and refreshes the in-app contributor credits from every repo in the org.

Launching a game directly

The mechanism forwarders are built on is available by hand too. Pass a game’s package name (e.g. com.fingersoft.hillclimb) as the first argument when launching Viridite.nro and it boots straight into that game, skipping the app-list picker — the same idea as a RetroArch forwarder jumping straight to a ROM+core rather than RetroArch’s own browser.

This is libnx’s standard envSetNextLoad(path, argv) chain-load, which is what every homebrew forwarder uses. If the requested package isn’t installed, Viridite falls back to the normal picker with an on-screen notice.

Since forwarders are generated automatically on install, you rarely need this — it is useful for your own scripts and shortcuts.

Submitting a compatibility report

Tried a game and want it on the compatibility page? Use the submission form — no GitHub account needed. Attach the .apk you tested directly (uploads straight to storage — real APKs running 50-150MB are no problem) or paste a download link, say where you got it from, and attach or paste the three log files from sdmc:/Viridite/: launcher_log.txt, compat_log.txt, and <package>/log.txt. There's no length limit on the log fields either way.

It's handled entirely by automation, and there's no maintainer in the loop:

  1. The form posts to a small relay (a Cloudflare Worker) that queues the submission in compat-reports and kicks off a GitHub Action — no GitHub login involved anywhere in this step.
  2. The APK is fetched, validated, and sanity-checked as a real ZIP.
  3. Its package name, version, display name, and icon are read straight out of the manifest — not typed in, so they can't drift from what was actually tested.
  4. The Play Store listing is checked to confirm it's actually a game.
  5. The three logs are scanned for frame stalls, crash/error signatures, and whether the game ever rendered a frame at all, producing a verdict: Playable, Runs with issues, Fails to launch, or Inconclusive.
  6. The result — logs, a generated report, the APK's SHA-256, and an icon — is published to compat-reports and shows up on the compatibility page within a few minutes. A repeat submission for the same package+version replaces the previous one.

If you gave a GitHub username, it shows up on the Credits page and in the launcher's own About screen under Testers — that field isn't verified, it's just for display.

More

The full technical history — every root-caused bug, every hardware measurement, the complete changelog — lives in the Viridite README.