Drawing for drawing’s sake
Decompiling Sonic Adventure without writing code
Today I’m releasing sonic-adventure, the first complete matching decompilation of a retail Sega Dreamcast game: 247 binaries, 5.8 million bytes of code, rebuilt from source and matched byte for byte against the original disc.
Decompilations of this size have taken years for teams of volunteers to complete. Ocarina of Time took dozens of people roughly two years. This took one person hundreds of hours and hundreds of billions of tokens. A few more years of AI progress, and it’ll be child’s play. Today it’s still at the edge of what the best models can do. Today it’s still cool.
Opus, Fable, and Astra wrote every single line of code. I can’t write the Dreamcast’s SH-4 assembly. I could learn. I want to. I also want to get back to my daily Japanese lessons. And eventually learn Spanish.
Four weeks ago I left my job at a16z. For the first time in five years, I had time off from work. This is what I chose to do with it.
Build stuff
I’ve always loved to tinker. At 13 I ran a pay-to-win Ragnarok Online server and took donations from classmates for in-game rares. Despite running a LAMP stack on our family PC in elementary school, I didn’t study CS in undergrad. My past life was in finance. For most people, software engineering is a job, and a good one. For me, getting to do this as a career was the promised land.
The advice to anyone learning to code was always the same: build stuff. No destination. Make something. Anything. Once I became an engineer, I found Irshad Karim’s 50% rule on Drawabox: “At least half of your time drawing must be spent on doing it for its own sake. Play. Exploration. Pushing boundaries.” The other half goes to deliberate study. I love this advice. I’m very protective of my eng time. My nights used to be spent optimizing my local k8s cluster to handle 10k requests per second (with nothing to serve), or turning Netflix subtitles into Anki cards. Coding for the sake of coding.
The thing is, coding isn’t really coding anymore. We can build things faster than we can think of them. Writing an NES emulator used to be an ambition of mine. Now, Opus can do it in an afternoon. Irshad asks one question to students who say they don’t know what to draw: “What would you draw if you had all the skills in the world?”
An obsession
I’m a lifelong Sega fan who follows every retro scene closely, from emulators to unofficial remasters. For years I admired other people’s work and wrote full-stack code for a living. I never got to participate. I didn’t have the skills. So I started a grad program. In ML. Because, you know, you’re supposed to study ML. I still didn’t understand how emulators worked. At the start of this year I resolved to practice C++ for an hour every night, one LeetCode problem at a time. I’m going to learn low-level programming.
I started reading through the Sonic Advance 2 decomp, the work of some very good C programmers. Hm, no PSP port. What a shame. I looked over at the original PSP I’d bought to mess with. At work the models had just made a step change; we were watching Cursor diff files for hours a day. Surely Cursor couldn’t make a port, right? I asked anyway. It chewed, and chewed, and chewed. Finally, boom. An EBOOT.PBP. Does it work? Sure it does. I was playing Sonic Advance 2 on my PSP. I was dumbfounded. With LLMs, I could draw anything.
I went full tilt. I traded the IDE for Claude Code and pulled out my credit card. The obsession set in. If I wasn’t at my computer, nothing was being built. I stopped sleeping. I thought back to the Ocarina of Time decomp. Had anyone done this for a Dreamcast game? Not really, no. There was a Tokyo Bus Guide decomp, but it wasn’t byte-matched and didn’t decompile Sega’s SDK. The Dreamcast was begging to be worked on.
I gave the agents the mission: decompile Sonic Adventure 2. I blew through my usage limits and bought credits. At API rates. Rookie. $5,000 later I wasn’t even close. We had decompiled some of the main executable, but the repo was all over the place. I’d wake up in the night and run to my computer, only to find the agents off course and sputtering. They would invent new hacks to fake a match. They couldn’t stay honest, and I couldn’t keep them on task. After months of fighting cheating agents, I gave up.
The toolkit
I failed my first attempt to decompile a game because I didn’t know what I was doing. What the agents needed, I decided, was a todo list and an enforcer. Box each agent into one task, and have something else check that it was done correctly. I spent the summer trying to turn that into a product. Then Fable and Astra came out, and models could stay on task on their own. Nobody needed the enforcer anymore.
Meanwhile, the agents and I had gone a level deeper, into the compiler. The Dreamcast’s C compiler, SHC, is a set of 1998 Windows binaries. They were slow and would crash constantly. On top of that, the agents had been guessing: change a C spelling, compile, diff, repeat. They were bad with debuggers, even after we moved everything to Windows to use one.
Tired of fighting PowerShell and Windows, I set out to create a fork of wibo that could debug these binaries on Linux. This was much easier than I had expected, and I should have done this much, much sooner. Now the agents could run experiments on Linux. When a match failed, we could watch SHC make the decision that broke it. Months later the debugger resolved our hardest matches. In Sonic’s player code, a literal pool (the constants the compiler parks beside the code) kept landing in the wrong place; listing one case label first moved it to where Sega’s was, and the last of 68 functions finally matched.
We also needed a better tool for finding functions in a Dreamcast binary. The standard one is Ghidra, and its SH-4 support is thin. Its pattern file is 17 lines and untouched since 2019. We didn’t need all of the Ghidra machinery. We knew these binaries would always have Katana SDK code. So we made our own carver with the few Dreamcast games whose source code is available. Specializing was the goal, so I wasn’t sure how well it would generalize. With the decomp done, I can finally test it on Sonic Adventure. Ghidra finds 57% of function starts, the carver 93%.
The rematch
At the start of this month, I took a leap. I found myself at a crossroads at work. My previous team had been wound down, and I had started toying with the idea of working full time on a product. With the blessing of some terrific mentors, I parted ways with a16z. Time to build. Hm, that’s funny, suddenly everyone is talking about decompilation. For months, I’d been talking about game decomps to anyone who would listen. Anyone working on Dreamcast? A few. Anyone byte-matching a whole game? Not yet. I had to go for it.
This time we started with the original Sonic Adventure, a Dreamcast launch title. I figured the first game would be easier. It wasn’t. Sega built it with at least three different SHC versions. In the first campaign I had to contain each agent’s scope to a single function, because they would change the checker to make code match. This doesn’t work. In a decomp you only have guesses of functions, and agents need to be able to propose changes to the work they are assigned or modify their own tools. Steve Yegge makes the case for fences, not sandboxes: govern agents with laws instead of containing them with code.
With this advice in hand and my newly created Dreamcast toolkit, I created an orchestration system with Yegge’s Beads. I made some simple laws: the conductor merges all code, every experiment gets recorded, and no one patches the assembly. The thing with laws is they’re flexible. We eventually needed assembly patches for 14 files, but that was my call to make.
Inside those fences I built a mini Dreamcast research lab. A conductor to assign work. An SDK team. A tiger team for the hardest problems. A taste agent for reviewing style. When this works well, it feels like running an actual team. This is what engineering is now. I make decisions when I have time, and when I don’t, I put someone else in charge. This, of course, has a cost. When they make wrong decisions, you end up with debt that you have to fix. Most of this job is playing goalie: What is this? Do we need it? No? Then delete it. Is there a simpler way to do this? Can we please do it that way?
The most complex file in the game is njModel.c, the core of Sega’s 3D renderer. It’s 12k lines of C that light, texture, clip, and draw models. One brave Astra instance had ground at it for over 100 hours. We weren’t anywhere close. Only 16 of its 87 functions matched. The rest were off by thousands of bytes. So I called a retro. New P0 for the tiger team: recover the renderer. If we don’t get this done, we can’t finish the game. Read every note, every session, and come back with an approach. No compiling. This is strictly a research and design team. It worked. They proposed a series of experiments that eventually led to our solution: inline assembly blocks with comments allow us to manipulate the literal pools. Functions started matching one after another. I didn’t trust it. ChatGPT Pro, what’s going on here? Are they cheating? Nope. This looks real.
After weeks of very little progress on the SDK, the levee broke. We had matched the largest file in the game, and it was all downhill from here.
Drawing for drawing’s sake
Even before AI, the most senior engineers spent less time coding and more time thinking about problems. Making calls. Unblocking people. Now that’s all that’s left. And that’s okay. I’m still having fun.
I guess this is what drawing for drawing’s sake looks like for me now. I finally get to make something the retro community can use. Maybe I’ll actually learn SH-4 someday. But today, I can release nearly a million lines of C for one of my favorite games and share it with the world. And that is a truly awesome thing.
The code is at github.com/reshaped/sonic-adventure.