DOOM on the Nikon Z 6II camera
For a little over a year, I’ve owned a Nikon Z 6II. I love using it because (ignoring its size) it gets out of the way and reliably delivers pictures I love. It’s also the only piece of tech I like taking on holidays. This piqued my interest because it’s the kind of engineering I admire, and over the past months, I’ve been reverse engineering this camera.
Past weekend this process culminated in the obligatory DOOM port (which, as far as I’m aware, is the first port with public code for any Nikon camera). Now, while interesting as a showcase of how to run custom code as a T-Kernel task (the Z 6II runs T-Kernel, a refined Japanese real-time OS), playing DOOM on my Z 6II is not my main aspiration as a hobby photographer, nor is it what took most of my engineering effort. That effort went into something else: a QEMU emulator of the Nikon Z 6II and its two Expeed 6 SoCs.
qemu-expeed
Inspired by qemu-eos and the Magic Lantern project, which develops firmware extensions for Canon DSLR and mirrorless cameras, I went for an emulator-based approach. This emulator allows to run a real camera’s machine code (binary) on my computer. Contrary to a real camera, the emulator allows full and instantaneous introspection, that is, I can read RAM and the state of all the hardware peripherals, which greatly eases development of firmware extensions.
This process was not straightforward, however, because Nikon (understandably) has kept their Expeed 6 SoC documentation under close wraps, also not publishing things like which OS it runs or which CPU cores it uses. Every bit had to be reconstructed from the camera’s firmware (what hardware peripheral would make sense for these register writes?) and sparse public sources. It’s basically a big puzzle. One of the breakthough moments was the realisation that the Nikon Expeed 6 has similar peripherals to the GoPro GP1 and GP2, and GoPro open sourcing their kernel patches allowed me to speed run the register identification of several peripherals. (Note to avoid possible confusion: the Expeed 6 still differs significantly from those two GoPro SoCs.) Furthermore, agentic LLMs were of help, especially for bug hunting, coding various tools as well as writing over 16k lines of QEMU C code. That said, though they sped up reaching the goal of running DOOM, my outsourcing to the LLM meant I now lack the low-level understanding I usually have, and I also ended up with ≈16k of slop (or at least questionable) code. I’ll be writing more about this in the next blog post.
Today I’m open-sourcing qemu-expeed, which boots the Z 6II firmware with a not-too-high number of hacks. Features currently supported include photo live view, menu navigation, image playback, still image capture, auto exposure and several more. I’ve also emulated part of Nikon’s “MIPU” (Magical* Image Processing Unit), so the live view picture you see is one that actually went through the camera’s emulated RAW development pipeline rather than just a JPEG pasted into the display scanout memory.
Here are a few screenshots so you get an idea of the current state:
What’s next
Over the coming months, I’ll be sharing several engineering deep dives on the Z 6II. Planned topics include how the Expeed 6 is put together, the imaging pipeline and the inter-Expeed “DualEngine” communication that makes the Z 6II such a unique camera. Out of courtesy to Nikon, there are also several sensitive topics I won’t cover, such as the Z 6II’s autofocus and autoexposure algorithms as well as what exactly makes their Picture Controls look so great.
Finally, if you’d appreciate receiving an email when these blog posts get published, please send any message to qemu-expeed@arnebaeyens.com. (Of course you can also subscribe to RSS.)
*I’ve no idea what the M in MIPU actually stands for. But for the complexity this block has, Magical sounds pretty appropriate.
Enjoy Reading This Article?
Here are some more articles you might like to read next: