3DS homebrew chronicles pt. I
Discussing about the development of 3DS homebrew in zig.
I’ve been learning lately about the 3DS system and its architecture. As I like reinventing the wheel every month in some way, I’ve been cooking some homebrew code written purely in zig.
Yes you’ve heard it, purely. From _start
all the way to exiting the program with only zig as the build system, compiler, linker and language
I. Everyone starts something knowing nothing about it
I began this journey knowing nothing about the 3DS architecture (internally at least) with a knowledge about ARM approaching 0, so I searched and read, a lot.
If you do some simple research (a.k.a: 1 google search or 1 chatgpt query for the new generations), you’ll find that some amazing people already documented and made libraries and utilities which are REALLY useful to check out when you know nothing (or almost nothing).
I wanted to start and iterate as fast as possible, a.k.a: executing the binary as fast as possible so I began to yoink the complete linkerscript that is already used by the community for 3dsx (the defacto executable format for homebrew).
Aaaand, I began to COOK my guy/gal. Just write some lines of code to export a naked _start
function and there you have, a glorious black screen with a total of 0 FPS (I’ve been using azahar to make my life easier when I started).
II. The port and the service
Want to exit? Well, calling ExitProcess() looks easy but it doesn’t exit the process (at least in the emulator) so I had to run before I learned to walk, we had to somehow call APT:PrepareToCloseApplication
and APT:CloseApplication
…
You could ask: So?
And I say that now I had to: connect to srv:
, get the available APT:X
port handle from it, get its lock and now start cooking some commands for it. That leaves us with at least 5 syscalls that we need to implement and some proper error handling (we do want to get feedback if we screw something, don’t we?)
Code was written and the app was closed, now we go to the next logical step if you like dopamine.
III. Blitting pixels
The next logical step is obviously getting some kind of visuals working as seeing a black screen is pretty boring. So I started looking at gsp::Gpu
, the port used for interacting with the gsp services and gpu specifically.
Now, after reading a lil bit about the GSP Shared Memory (interrupts and framebuffer presentation mainly), implementing it and painfully debugging everything, we got some movement.
IV. A new library was born
After all this mess, I tought “bro, if I write like 200 loc more this could be used as a library or even a toolchain to write homebrew” and so I did. Now homebrew can be written very easily with just a few lines of code to compile (Sorry but I don’t like makefiles, they’re useful when you don’t have anything else but we have zig
).
So I started implementing really mandatory things like jumping to the home menu, handling events and notifications, and input scanning. And after these things zitrus was born…
Now compiling 3DS homebrew is as easy as downloading zig and running zig build
.
V. The credits
As I already say in zitrus, all of this wouldn’t have been possible without the amazing work of the 3ds homebrew scene, even if this is a separate toolchain, devkitPRO’s tools are a must due to how robust they are. Luma3DS is also a must if you want to debug your homebrew in the real deal, a physical 3DS instead of an emulator. It also seems someone else in the interweb tried to use also zig for writing 3DS homebrew but it depends on existing C code.
So, till next time!
This is my very first blog post, thanks for reading!
Written in June 03, 2025 19:00 UTC