Well. That was the idea, anyway. SPOILERS: It didn’t work.
Vladimir Costescu has upped the ante and bought a day of my time this month, requesting:
It would be cool to read about you tinkering with a Raspberry Pi or similar cheap device and trying to get it to do cool stuff (where “cool stuff” is left up to your discretion).
Well it just so happens that I already have a Raspberry Pi. I got it at PyCon US, I think three years ago, when they gave every single attendee a Pi for free. I thought it was super duper cool and I spent a whole afternoon tinkering in their Raspberry Pi lab and then I came home and put it in a drawer forever because I had no idea what to use it for.
At first I thought it would be cool to rig something that would download a random wad from idgames (like vectorpoem‘s WADINFO.TXT) and just launch it and let you play it. A teeny tiny portable Doom box.
Then I realized you’d still need a mouse and keyboard (well, at least a keyboard) to actually play, which is a little bit more cumbersome and detracts from the portability a bit.
But I remembered hearing about a Linux-only project that had managed to interface with the Wii U GamePad. Run ZDoom on a light wireless controller with gyros and everything? That sounds awesome.
So off I went.
I plugged in my Pi, and soon discovered it had been so long that I’d forgotten it doesn’t actually have any on-board storage. I couldn’t find my original SD card, though I did find an old one I’d used in a 3DS. It’s possible I looked at the Pi card, saw it was a Linux distro, assumed I’d used to install Linux on a toaster, and used it as my new 3DS card instead. Oops.
I grabbed a Pi boot image, but it was over 4GB, and the old 3DS card was only 2GB. So I bought a new card off Amazon and waited two days.
Then I found out my trusty ancient multi card reader USB dongle doesn’t actually support SDHC, so I had to dust off my System76 laptop,
scp -C the image over, and write it with the built-in card reader.
Success! The Pi turned on.
Failure! The GamePad communicates via 802.11n, and I don’t have a recent WiFi dongle on hand. Back to Amazon, and another two days. This time I bought a case as well, so I wouldn’t just have a plain bare circuitboard sitting around in a house full of cats.
WiFi dongle arrived. Case also arrived. There were some minor issues.
Apparently the Raspberry Pi 2 Model B is different from the Raspberry Pi Model B Revision 2, which is what I have. What a great naming scheme.
I still wanted a case, but returning this one wasn’t worth it, so I just bought another. Total cost:
- 16GB SD card: $7.49
- Wrong Raspberry Pi case: $8.90
- Right Raspberry Pi case: $9.99
- WiFi dongle: $23.69
- Total: $51.89
Given that I’m only being paid $60 to do this in the first place, I’m sure glad I already had the Pi.
You can see we’re already off to a great start. Onwards!
Also, the project is called libdrc. I don’t know why. You’d think libwiiupad would be a better name. I can’t believe there aren’t already fifty different projects named libdrc.
If you’re not familiar with hacking on Nintendo products, they seem to have a habit of using lots of well-known standards, then slightly fucking them up to spite you. It was thus no surprise to learn that I would need to compile a modified
mac80211 module, the Linux wireless stack.
Well, that’s easy enough according to Some Blog. Just clone a repo and build it:
And here, we hit our first roadblock. You see, Rasbian (the modified Debian used as the standard Raspberry Pi distro) doesn’t ship with kernel headers. And they’re not in apt. This seems really fucking weird for a tiny hacker computer intended for hacking on.
This is a problem that strangely few people seem to encounter, and I had to cobble together instructions sprinkled in various places and spanning several years: the Raspberry Pi forums from 2012; StackOverflow last month; and someone else’s blog, fairly recently but inexplicably insisting that you build the entire kernel. On a Raspberry Pi. Yeah no.
First you need to get the git commit hash of the
firmware the Pi is running. These are listed in the bootloader changelog.
That’s probably not totally reliable since the changelog might be typed by hand, but whatever.
You can then use that to get the commit hash for the Linux kernel you’re running:
Now, uh, I don’t really want to
git clone the entire Linux kernel. You’d think
git clone --depth=1 might help here, and you’d be wrong, because you can only
git clone starting from a branch or tag — not an arbitrary commit. Alas.
Turns out, though, that you can just ask GitHub to give us a tarball.
Alas! The Pi disk image only comes with half a gig of free space, and the Linux kernel source won’t fit in that. My card is 16GB, though, and expanding a live filesystem via command line is way less harrowing than ye olden days of
df -h confirms I now have 11GB free. Cool beans.
tar -zxf rpi-linux.tar.gz, wait a minute or two, and I have a
At this point I’ve long since forgotten what the hell I was even doing. Right, right, wireless module.
So now I have to “prepare the kernel for module builds”. Disclaimer: I’ve built a few Linux kernels in my time, but it is not a simple topic and I have no idea what I’m doing. These shell commands I found on the Internet seem totally reasonable though.
1 2 3 4 5 6 7 8 9
Lastly I need
Module.symvers, which, clearly, is about symbolic versions, of modules, I guess. I think it lists module dependencies because I forgot to get it and the build complained it didn’t know anything about module dependencies. You can get it from the
Finally, we can—
At this point I briefly wonder why we need a modified wireless stack, so I check out the repo. It has only a single patch, and a libdrc changelog containing only:
* Exports the Wi-Fi Time Synchronization Function to userland.
Okay, sure, that sounds important.
The provided repo is actually super ancient (very early 2014) and no longer builds against the current kernel. It’s a good thing that there’s only a single patch, and it only adds a new function — it was super duper easy to just reapply the patch against my shiny new kernel sources.
1 2 3 4
Aaaand finally build it.
Success! Now I just have to unload the stock module and the various drivers using it, in the right order, then load the new module and drivers.
1 2 3
STEP 1 COMPLETE. It is now 8:30pm. I started this around noon. All I’m even doing is following someone else’s blog post. This is ridiculous.
The idea is to tell the console that you want to sync a new pad (by pressing the sync button twice), and it’ll show you a code of four symbols on the screen which you then enter on the pad. These symbols are actually used to construct a WPS pin for the console’s access point. Also, the console always uses the same four symbols. So we want to get the pin, use that to connect to the console, get the WPS key out of the handshake, and then use that key to pretend we’re the console and have the pad connect to us.
Apparently there’s a way to do this by pretending your Linux machine is a Wii U seeking for a GamePad to pair with, which sounds easier than this nonsense, but it’s undocumented. Or, rather, documented as “TODO: document this”.
I killed off
wpa_supplicant, which was already running on the Pi and likely to try interfering with my subsequent shenanigans. No NetworkManager or anything fancier to worry about.
I’m basically just following the libdrc docs now. Grabbed the
hostap fork. Cded to
CONFIG_TENDONIN=y. (I only realized later what
TENDONIN is, after seeing it written as “tendoNin”. It’s a light jab at the modification Nintendo made to the WPS handshake: it rotates part of the response three bytes to the left.)
libssl-dev, which Some Blog failed to mention. Tut, tut. I’d also like to take this moment to stress how very silly and irritating it is that Debian packages header files separately.
You can read the directions if you like. The goal here was to get the Wii U to show up in a scan:
Success. My Wii U said its code was ♥ ♦ ♦ ♥, which meant the pin was 12215678, which I supplied with
wps_pin, which completely didn’t work at all and only gave me
Some brief head-scratching led me to realize that “FAIL” means “you need a BSSID and you gave me an ESSID”. Can you believe anyone would get those things confused or fail to understand that error message? Ha, ha.
Okay so the BSSID is the thing that looks like a MAC address, but isn’t, because whoever designed this hates all that is good and pure in the world.
wps_pin succeeded this time, and some things happened, and now I had the PSK, which I assume stands for “pre-shared key” because it looks a lot like a key:
Please don’t hack my Wii U. Or, please do hack it and then tell me how on Earth you managed it.
Now we can pretend to be the console. I had to build a modified
hostapd from the same repo, for the same reason.
I tried to run it and got this error:
Apparently you have to set
CONFIG_IEEE80211N=y in the build config, which makes a lot of sense given that the pad uses 802.11n to communicate. I have no idea why nobody mentioned this.
I hit some more minor stumbling blocks; that
WiiU182a7b894e6182a7b894e69_STA1 mess above turns out not to be the SSID, but rather the ESSID. Or… maybe it’s the other way around. I don’t know. Or care! But that one is wrong, and the one that’s actually right is in
get_psk.conf and looks like
I also had to compile a teeny-tiny DHCP server called
netboot and give my wireless interface a static IP, and finally…
With both of those running, I could turn on the pad and see it successfully handshake with the Pi. Woohoo!
Yes, naturally, Nintendo is using their own variant of this as well. Please stop telling me the Pi has h.264 hardware decoding.
This was pretty painless, if a bit slow. Then I built libdrc itself, and all was well, and I ran a demo and just got “Illegal instruction”.
Hmm. Let’s back up.
x264 has a bunch of ARM-specific assembly that relies on some floating-point features the Pi doesn’t have. The advice is to just configure it with
So I did that. And set it about compiling again.
It took an awful long time.
We’ll come back to this.
This was pretty easy, actually. It took three or four hours, and I did have to fix one minor bug that I hope I’ll remember to upstream, but it built. And worked! Almost.
I did have to manually set
vid_forcesurface to true in
zdoom.ini, which… does… something… that makes a segfault not happen. But then it worked!
Thanks, by the way, to the brilliant individual who recently did all the actual work to make ZDoom work on a Pi.
I had a brief read of the libdrc docs, and it sounded like all I had to do to draw to the screen was pass a big ol’ array of pixel data to a function. That sounded like something that should be pretty easy with ZDoom (which renders entirely in software), and hopefully the compile cycle would be pretty short with master already built.
Before we worry about that, let’s see how x264 is coming along.
This is really weird. I left it overnight and it still didn’t finish. It gets to
encoder/analyse.c, takes a moment, spits out a bunch of warnings, and then busy-loops forever.
strace revealed weird
lsof didn’t show any activity. It wasn’t swapping, and it was still very gradually using more memory, but it didn’t seem to be doing anything.
Debugging GCC is not in my skillset, and Twitter wasn’t sure what the problem was either. It took a good few hours to bumble upon something that actually fixed it: patching
1 2 3 4 5 6
I ran it again, without
--disable-asm, and tried
make. It finished in five or ten minutes. Christ.
At some point I’d rebooted to free more RAM and modestly overclock the Pi, to see if that would pacify x264 (it did not). So I had to juggle modules again, kill
wpa_supplicant again, set my IP again.
hostapd again, aaand it refused to start.
Come on. Nothing changed!
Long story short I was in the “00” regulatory domain, and I had to install
iw and set myself as in the US domain. I don’t know why this only happened after a reboot. Maybe because the stick was plugged in when it booted? I don’t know. Computers.
Right! Here we go! Let’s pair the pad and run
demos/3dtest/3dtest from the libdrc repo!
Ha, ha! No problem; I don’t actually need OpenGL at all. Let’s try the
simpleaudio demo, which shouldn’t need GL either.
And this is it, dear reader. This is where my story ends. I wrote a trivial app of my own against libdrc that did nothing but stream a solid color to the pad, and that too segfaulted. I ran it under
gdb hit an assertion error trying to get the call stack. I know it died somewhere in my distro-provided
libswscale, but I don’t know where or why or who called it. I’ve already sunk two days into this, and I am not prepared to debug
I give up.
Here’s all I have to show for my effort:
ZDoom, running on a Raspberry Pi, at 14 fps. I suppose it’s some consolation that it wouldn’t have been playable anyway?
It’s kind of a shame, since this is the part that took the least effort by far, and someone else really did all the work. But then, the hard parts were all just following straightforward instructions. I don’t know what I’ve learned here. No one loves ARM, maybe.
It was a fun way to spend a weekend (it’s the weekend, right?)! But I still don’t know what to do with my Raspberry Pi, and I suggest you just run ZDoom on a full-size computer. Or a toaster.