I acquired one of these in a deal Google had running. After trying it out, I decided it wasn’t really for me, so I had an unused Stadia controller lying around….
The design is interesting; in order to reduce latency, it connects directly to Google via WiFi by itself, rather than locally to the Chromecast. But anyway..
Looking at the controller itself showed no obvious screws, but there did appear to be a plastic blanking plate:
I heated it up gently with my PCB rework air gun (to soften the glue), and managed to prise it off. This revealed a screw that I removed.
Next up (after some research) the thing its held together with loads of plastic clips. Removing these is tricky without damaging the finish. I managed it, but not without a little plastic damage. I attacked one of the “lobes” with a spudger and managed to gradually lever it open. In case its useful to others, here’s where the tabs are located:
Once open, the innards are really well constructed, and also easily disassembled with some screws. Here’s a pic of the total disassembly:
Interesting things to note:
- The vibration motors are weighted differently on either side! The left hand one is three times heavier than the right hand one: weird.
- The trigger controls are achieved with a hall effect sensor on the mainboard and small permanent magnets on the triggers: as you move the magnet closer, the magnetic field gets stronger.
- Yeah, it has a microphone, its that small rectangular PCB on the table just below the white plastic case.
OK, physical access gained, lets have a look at the chips onboard…
I desoldered the microprocessor and the flash chip using my PCB rework gun, and cleaned up the area with some soldering braid.
The main processor is an ARM based NXP i.MX RT 1061. (The big square one at an angle above)
The flash chip is a Zetta 25LQ128 DWIG 128Mb SPI NOR Flash. (The smaller square one at an angle above)
Dumping the ROM
Since I had the flash chip desoldered, I attached some wires, hooked it into a breadboard and tried to dump it. First of all, I tried hooking it up to a Raspberry Pi Pico and wrote some SPI code:
This didn’t work very well – I was getting something, but the values changed randomly. I think the problem was the voltage shifter device I had to use: the flash chip runs at 1.8v, but the Pico runs at 3.3v so you need to convert it. However, I saw people complaining that the convertor wasn’t really fast enough for this sort of access.
Hmm annoying. Then, I remembered I had a Pickit 2 in the tech cupboard. This is a fairly cheap USB device, originally for programing PIC microcontrollers. However, it is extremely flexible, and can be used for a variety of things, including dumping SPI flash chips (you can also just set it to run at 1.8v with no extra level conversion required). I hooked it up…
Then its a matter of just running the command and waiting…
flashrom -p pickit2_spi -r dump.bin
Investigating the ROM Dump
I’m not going to include the ROM dump itself since I would imagine its code is copyright to Google, but I can do a quick spot of analysis of it.
Looking at location 0x0:
OK, “FCFB”, that looks kinda interesting. A spot of research shows this is a “FlexSPI NOR Config header”, as described here and here.
According to those docs, the next header is at 0x1000 in the file:
The above tells us the start of the ARM code is at 0x60801000 – or 0x8010000 in this dump (the runtime memory address will be based at 0x60000000, so we need to subtract that). So dumping that gives us:
From experience, that looks like ARM code (in fact chunks of it look like 16 bit ARM Thumb instructions). But I’m not going to go any further, I’ve got other more interesting projects I want to do.
For fun, I tried dumping the strings out of the dump, here are some examples of what’s in there:
This is a google internal device, sending bug report now. /usr/local/google/home/mosaic-role/workspace/team/gotham-cr/manifest/default.xml/gotham-1.3/gotham/source/yeti/yeti_http_client.cc /usr/local/google/home/mosaic-role/workspace/team/gotham-cr/manifest/default.xml/gotham-1.3/gotham/source/yeti/yeti_input.cc Google, Inc. /usr/local/google/home/mosaic-role/workspace/team/gotham-cr/manifest/default.xml/gotham-1.3/gotham/source/yeti/yeti_runnable.cc dev-cloudcast-pa.sandbox.googleapis.com staging-cloudcast-pa.sandbox.googleapis.com cloudcast-pa.googleapis.com test-cloudcast-pa.sandbox.googleapis.com Google LLC external/com_google_absl/absl/base/internal/throw_delegate.cc external/com_google_absl/absl/strings/internal/escaping.cc external/com_google_absl/absl/time/internal/get_current_time_posix.inc external/com_google_absl/absl/synchronization/internal/mutex_nonprod.cc
Hey, is that a debug connector over there?
I also tried “Beeping out” the test pads on the device to see if I could figure out how you could access the flash chip without removing it. While doing that, I realised there’s an unpopulated port, which could very well be a debug port (highlighted in red in this image):
I tried attacking that by soldering wirewrap wires onto test pads and using the “Beep” continuity tester on my multimeter to figure out what is attached to what:
BigClive style, I printed out a picture of the board and scribbled on it, but it was kinda inconclusive. I wanted to move on to another projects, but I include it here in case someone finds it useful:
3 thoughts on “Taking a Google Stadia Controller Apart and Dumping its ROM”
Respect for your dedication and skills, this is pretty cool to read! Thanks! 🙂
Did you ever find a way to dump the rom without removing the flash and mcu? Or do you have a dump of the file you can send I’m interested in enabling the classic bluetooth function.
Nice read, thanks for sharing. I was wondering if anyone had attempted this as it seems it’s great hardware but sadly only usable over wire for different purposes than the nearly dead stadia service xD