Vita Deck is a homebrew launcher for the PS Vita with a Steam Deck style interface. It scans the games installed on the console, shows them in a carousel over a browsable library, and launches the one you pick. Only games: tools and utilities stay out of the way.
It's native C on top of VitaSDK and vita2d, compiled into a .vpk that installs like any other app.
Why
The Vita's LiveArea mixes games, emulators, utilities and settings in the same grid of bubbles. If the console is mostly for playing, you want what the Steam Deck has: one big game up front, the rest of the library below, and one button to jump in.
That was the idea. The interesting part showed up later, when it turned out the console doesn't store half the information that screen needs.
The stack
Nothing exotic, which is exactly what you want when the platform is already unusual:
- C (gnu11) for the whole launcher, no C++ and no extra runtime.
- VitaSDK as the ARM cross toolchain, plus vita2d as a thin 2D layer over the GPU.
- libpng and FreeType for images and crisp text.
- CMake + Ninja for the build, with asset validation at configure time.
- Python + Pillow for PC-side tools: artwork generation and layout previews.
- ASan / UBSan for host tests of the pure logic.
And one constraint that shapes everything else: 512 MB of RAM, 128 MB of video memory and an ARM CPU from 2011.
Reading what's on the console
Everything installed lives in ux0:app/<TITLEID>/, and each folder has a param.sfo with the title's name and category. I wrote my own parser for it: 168 lines, no dependencies, and built on the assumption that any field in a third-party file can be garbage, so it never reads past its buffers.
To split games from tools I used Sony's ID scheme: retail games are PCS + a letter + five digits, and homebrew avoids that namespace by convention. That filters out VitaShell and friends. It isn't perfect, so there's an exceptions file to force a title in or out, and the log lists everything it hid.
Geometry apart from drawing
The decision I'm happiest with. All the layout maths (carousel, shelf and grid) lives in one module with no dependency on vita2d or global state. Give it a scroll position and an index and it returns a rectangle.
Two things came for free from that:
- Touch hit-testing uses the exact same rectangles the renderer draws, so they can never drift apart.
- A PC preview. A small tool compiles that same module, asks it for the real coordinates and colours, and paints a PNG with Pillow. Iterating on the design went from "copy the vpk, install, look" to one command and two seconds.
The carousel has a subtle detail: the focused cover is drawn landscape (2:1) and the rest portrait (2:3), and the animation morphs between the two while keeping the gap between cards constant. That doesn't happen on its own, and it's one of the invariants the tests check.
The real problem: cover art
About 60% of the project, and I didn't see it coming. The Vita doesn't store box art. Each game ships a 128×128 square icon and, with luck, a landscape LiveArea background. A Steam-style UI is basically a grid of portrait covers, so the whole premise rests on material that doesn't exist.
The answer was a three-level cascade that works without the user doing anything:
- User artwork if there is any.
- The game's LiveArea background for the big landscape hero. This was the key find: it is real landscape art, so most games get a decent hero out of the box.
- A generated cover: the game's icon on a panel tinted with the icon's average colour, with the name below. Not real art, but every game is recognisable and has the right shape.
For anyone who wants real art, a Python tool imports a folder of images or downloads them from SteamGridDB, crops them to size and builds the folder to copy to the console.
Nothing is ever stretched: the launcher always draws the largest centred region that matches the target ratio. Images load lazily, a couple of PNGs per frame starting with the ones closest to the cursor, under a 22 MiB video memory budget.
Text, launching and play time
- Text. The system font is a bitmap and looks pixelated at headline sizes, so I bundled Liberation Sans and render it with FreeType at the exact pixel size. The system font stays loaded as a fallback for Japanese titles the Latin font can't cover.
- Launching goes through the shell URI, the same route VitaShell uses, and the launcher exits to hand over the console.
- Play time. The Vita doesn't track hours played, and the launcher is closed while you play, so it can't time anything. Instead it notes which game it launched and when; when it opens again, the gap is that session's length. An honest approximation, and the alternative was no number at all.
Later I added PSP and PS1 support through Adrenaline bubbles, with tabs that build themselves from what's installed: All, Vita, PSP, PS1, Favourites and Played.
What broke
- The vpk the console rejected at 99%. Installation aborted with a bare
0x8010113D. It turns out every PNG undersce_sys/must be 8-bit indexed, and Pillow was writing 24-bit truecolour. The fix: quantise the art to 256 colours, and make CMake read each image header at configure time and stop the build if one doesn't comply. A three-hour bug turned into a one-line error message. - The GPU driver crashing on rescan. Freeing every texture mid-frame handed back memory the GPU could still be reading. Now those operations are queued and run between frames, after rendering finishes.
- Running out of vertex pool. Rounded pills, glow layers and a screen full of covers filled vita2d's default pool, an intermittent crash. A bigger pool fixed it.
- A loading screen with no exit. If the pending counter was ever wrong, you were stuck on the progress bar forever. Now it also enters once the loader has had nothing to do for a while.
- The animated loading screen I removed on purpose. Decoding a frame every 50 ms competed with icon decoding for the same thread and made both worse. A still image with a progress bar has neither problem.
Testing without a console
There's no debugger and every hardware iteration takes minutes. Three things made up for it: host tests with sanitizers (the SFO test corrupts and truncates files thousands of times), the PC preview for design, and a detailed log on the console that says, for every missing cover, why it failed and which candidate paths actually existed.
What I took from it
The platform's limits are the design. Half the interface came from "the Vita doesn't store this": the generated covers, the hero taken from the LiveArea, the play time measured by gaps. None of them were in the original plan, and they're the best part of the project.
And removing things is work too: the animated loading screen looked nice, and it went because it was fighting for the wrong thread.
The .vpk contains nothing from Sony and no game artwork: each user adds their own covers on their own console.

