Everyone who's put code, hardware testing, or compatibility reports into Viridite — pulled live from GitHub, same list the launcher's own About screen shows.
Groundwork
Viridite is not built from nothing. These are the projects,
specifications and reverse-engineering efforts it stands on — several of them are
the reason a given part works at all, and a few are the reason a bug got found.
Prior art — the same idea, done before
so_util — TheOfficialFloW
(Andy Nguyen). The original ELF-loading technique this whole scene rests on, first
used to run Android games natively on PS Vita.
Rinnegatamante, Bythos, frangarcj,
CBPS and the wider Vita homebrew scene — who extended it across 45+ Android-to-Vita
ports.
max_nx — the direct Switch
precedent, running Max Payne Mobile's real Android binary natively. Two concrete
techniques came from reading it: a preflight check that the JIT syscalls exist before
attempting a load, and writing a distinctive poison address into unresolved imports so
reaching one crashes unmistakably instead of as an ordinary null dereference.
ffd_nx — confirmation the
approach generalises past a single game.
Switch platform
switchbrew wiki — the reverse-engineered
documentation of Horizon OS. Syscall semantics, memory-type tables, NRO layout and the
application-record model behind forwarders all come from here.
libnx — beyond using it,
reading it settles questions. Disassembling its exception entry stub is how we learned
that svcReturnFromException runs before the user handler, which
ended an entire line of investigation into a loading hang.
Atmosphère —
the CFW everything runs under, and the crash-report format used for post-mortems.
libtesla and
nx-ovlloader — the overlay
runtime. Reading libtesla is how we found an overlay can reposition its own layer,
which is what lets an install card sit over a HOME-menu tile instead of in a sidebar.
Android
AOSP — the binary XML and
resources.arsc formats the APK parser implements, and bionic,
whose libc surface the shim table is written against.
Material Design 3 — colour roles, the
tonal-palette model, and the shape and motion scales the launcher is built to.
Google Fonts — Roboto Flex and
Material Symbols Rounded, bundled under the OFL.
Libraries and tools
SDL2, SDL2_ttf, SDL2_image; minizip/zlib;
libcurl over the Switch ssl sysmodule;
stb.
newlib and its dlmalloc-derived allocator — whose chunk layout
and sysmalloc behaviour had to be understood in detail to diagnose the
Brain It On! fault. The boundary-tag invariant the heap checker validates comes
straight from that design.
GNU binutils — objdump, readelf and nm. Most of the real
diagnosis in this project has been reading AArch64 disassembly, of game binaries and of
our own, rather than adding logging.
Loading credits…
Written with Claude
Nearly all of Viridite's code was written by Claude, Anthropic's AI, working against problems found on real hardware — the ELF loader, the JNI/Bionic shim, the audio backend, this website. A human sets direction, runs every build on an actual Switch, and decides what ships. That split is the project, not a footnote to it, so it belongs on this page rather than only in the README.
(function () {
// Each source contributes a ROLE to a person, rather than its own section.
// The same person working across four repos is one human who did four
// things, not four separate credits.
var SOURCES = [
{ repo: "Viridite", role: "Launcher", cls: "team-cores" },
{ repo: "VNX-Translation-Core", role: "Core", cls: "team-cores" },
{ repo: "website", role: "Website", cls: "team-website" },
{ repo: "compat-reports", role: "Infra", cls: "team-other" },
];
var TESTER_ROLE = { role: "Testing", cls: "team-testing" };
// Automated accounts (github-actions[bot], dependabot[bot], …) appear in the
// contributors API but have no user page, so their avatar 404s. They aren't
// people to credit anyway.
function isBot(login) {
return /\[bot\]$/i.test(login) || login === "github-actions";
}
var people = Object.create(null);
// GitHub logins are case-insensitive, and the tester name is a free-text
// field on the submission form (optional and unverified), so "Aaronateataco"
// typed there and "aaronateataco" from the API are one person and must not
// become two cards. Key on the lowercased login; let the contributors API,
// which returns the canonical spelling, decide how the name is displayed.
function record(login, role, cls, commits, authoritative) {
if (login == null) return;
login = String(login).trim();
if (!login || isBot(login)) return;
var key = login.toLowerCase();
var p = people[key] || (people[key] = {
login: login, authoritative: false, commits: 0, roles: [], seen: Object.create(null)
});
if (authoritative && !p.authoritative) { p.login = login; p.authoritative = true; }
p.commits += commits || 0;
if (!p.seen[role]) { p.seen[role] = true; p.roles.push({ role: role, cls: cls }); }
}
function el(tag, cls, text) {
var n = document.createElement(tag);
if (cls) n.className = cls;
if (text != null) n.textContent = text;
return n;
}
function cardFor(p) {
var a = el("a", "credit-card");
a.href = "https://github.com/" + encodeURIComponent(p.login);
a.target = "_blank";
a.rel = "noopener";
var img = document.createElement("img");
img.src = "https://github.com/" + encodeURIComponent(p.login) + ".png?size=104";
img.alt = "";
img.loading = "lazy";
img.decoding = "async";
img.width = 52; img.height = 52;
img.onerror = function () {
var f = el("span", "credit-avatar-fallback", (p.login[0] || "?").toUpperCase());
f.setAttribute("aria-hidden", "true");
if (img.parentNode) img.parentNode.replaceChild(f, img);
};
a.appendChild(img);
var body = el("div", "credit-body");
body.appendChild(el("span", "credit-name", p.login));
if (p.commits > 0) {
body.appendChild(el("span", "credit-meta", p.commits === 1 ? "1 commit" : p.commits.toLocaleString() + " commits"));
}
var roles = el("div", "credit-roles");
p.roles.forEach(function (r) { roles.appendChild(el("span", "pill team-pill " + r.cls, r.role)); });
body.appendChild(roles);
a.appendChild(body);
return a;
}
function fetchJson(url) {
return fetch(url).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; });
}
var jobs = SOURCES.map(function (s) {
return fetchJson("https://api.github.com/repos/Viridite/" + s.repo + "/contributors?per_page=100")
.then(function (list) {
if (!Array.isArray(list)) return false;
list.forEach(function (c) { record(c.login, s.role, s.cls, c.contributions, true); });
return true;
});
});
jobs.push(
fetchJson("https://raw.githubusercontent.com/Viridite/compat-reports/main/data/games.json?v=" + Date.now())
.then(function (games) {
if (!Array.isArray(games)) return false;
games.forEach(function (g) { record(g.submitted_by, TESTER_ROLE.role, TESTER_ROLE.cls, 0, false); });
return true;
})
);
Promise.all(jobs).then(function (results) {
var reel = document.getElementById("reel");
var wrap = document.getElementById("reel-wrap");
var hint = document.getElementById("credit-hint");
var list = Object.keys(people).map(function (k) { return people[k]; });
if (!list.length) {
// Distinguish "nobody yet" from "GitHub wouldn't answer" — the
// unauthenticated API allows 60 requests/hour per IP, so a visitor who
// reloads a few times can genuinely hit the ceiling.
var anyOk = results.some(Boolean);
reel.innerHTML = "";
reel.appendChild(el("p", null, anyOk
? "No contributors to show yet."
: "Couldn't reach GitHub just now (its API rate-limits anonymous requests). Try again in a few minutes."));
reel.firstChild.style.textAlign = "center";
reel.firstChild.style.color = "var(--text-mute)";
return;
}
// Most commits first; testers with no commits sort alphabetically after.
list.sort(function (a, b) {
if (b.commits !== a.commits) return b.commits - a.commits;
return a.login.toLowerCase().localeCompare(b.login.toLowerCase());
});
reel.innerHTML = "";
var group = el("div", "credit-group");
group.appendChild(el("h3", null, list.length === 1 ? "Contributor" : "Contributors"));
group.appendChild(el("p", "credit-note",
"Counted once each, with every area they've worked on."));
var grid = el("div", "credit-grid");
list.forEach(function (p) { grid.appendChild(cardFor(p)); });
group.appendChild(grid);
reel.appendChild(group);
hint.textContent = list.length === 1
? "One person so far — want to be on this list? Contributions and test reports both count."
: list.length + " people. Contributions and test reports both count.";
// Only turn this into a rolling-credits reel once there's genuinely more
// than fits, and never for visitors who've asked for reduced motion.
var reduceMotion = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduceMotion) return;
wrap.classList.add("is-scrolling");
if (reel.scrollHeight <= reel.clientHeight + 4) {
wrap.classList.remove("is-scrolling"); // fits — leave it static
return;
}
var paused = false, resumeTimer = null, manuallyPaused = false;
function nudge() {
if (manuallyPaused) return;
paused = true;
clearTimeout(resumeTimer);
resumeTimer = setTimeout(function () { paused = false; }, 4000);
}
reel.addEventListener("wheel", nudge, { passive: true });
reel.addEventListener("touchstart", nudge, { passive: true });
reel.addEventListener("mouseenter", function () { if (!manuallyPaused) paused = true; });
reel.addEventListener("mouseleave", function () { if (!manuallyPaused) paused = false; });
// WCAG 2.2.2: motion that runs longer than five seconds needs a way to
// stop it that doesn't depend on hovering — keyboard users can't hover.
var btn = document.getElementById("reel-pause");
btn.addEventListener("click", function () {
manuallyPaused = !manuallyPaused;
paused = manuallyPaused;
btn.setAttribute("aria-pressed", manuallyPaused ? "true" : "false");
btn.textContent = manuallyPaused ? "Resume scrolling" : "Pause scrolling";
});
setInterval(function () {
if (paused) return;
var atBottom = reel.scrollTop + reel.clientHeight >= reel.scrollHeight - 1;
reel.scrollTop = atBottom ? 0 : reel.scrollTop + 1;
}, 40);
});
})();