Decompiling a Nintendo 64 game in 84 days
blog.chrislewis.au
blog.chrislewis.au
I'd also encourage people to check out the Legend of Dragoon recomp [1]. It's a real labor of love that has breathed new life into a largely abandoned game. You can play it fully vanilla, or with improved graphics, fixes to cumbersome design choices, bugfixes, etc.
None of the Switch emulators Nintendo took down had such issues, they all required supplying your own keys.
Nintendo takes them down anyway because they can, and because they have enough money that no individual can fight them on it. Their takedowns have no real legal standing, and there's no reason why they couldn't do the same for every other emulator, other than them simply not wanting to.
One thing to note is that all the famous legal cases establishing unauthorized emulation as legal revolved around PlayStation emulators. The PlayStation doesn't have anything that would be defined as a "technical measure" under the DMCA. The PlayStation copy protection involves stopping a user from playing a copied game on a real console, not stopping a user from making a copy of the game in the first place.
It inspired me to use AI to decompile my favorite games as well, mostly Mega Man Battle Network games from my childhood. I'm not going to make it in 84 days though. I insist on understanding the game and documenting it myself instead of just having the AI do it all for me on autopilot.
As slow as that is, it's already led to some awesome moments. AI built a literal synthesizer that played back music straight out of the ROM. Mapped out all of the music that way. It was incredible.
Have neen reverse engineering my laptop's BIOS as well. AI is fully in charge of that one, and it's nearly done. Hopefully soon I'll have drivers and perhaps even coreboot.
I'll also ask the AI to reverse engineer the LattePanda Mu BIOS later since I want to use the Mu on my handheld cyberdeck project. Nearly out of usage for the week, sadly. Wish Tibo would push his reset button yet again.
I remember one game had a crude speech synthesis and I discarded the game and persisted out the speech synthesis code independently as a separate program to play with arbitrary input.
I had hoped to someday make retro games for all major 80s and 90s consoles. I started with the Atari 2600 12 years ago[0], and planned to move to other 6502 family systems when I had time. I realized last year that AI could essentially do all of what I considered the hard parts and craft of retro game assembly, which felt like having the rug pulled out from me. Projects like this have really turned that around for me.
New tools just mean old things become easier and new things become the difficult (where I think the fun is), but solvable, frontier. This category of projects inspired me to pick back up an Ultima V decompilation project I started more than 10 years ago[1,2]. I'm looking forward to a world where all of my childhood games are cracked wide open for mods, ports, etc.
[0]: https://github.com/cleak/FlappyBoot [1]: https://github.com/cleak/u5-spec (spec for Ultima V binary) [2]: https://github.com/cleak/u5-engine (Rust port of Ultima V made from the spec; still lots of prose slop)
You become a machine with your workflow, once again limited only by your time/energy, tokens, and your discretion on how to spend it.
Indeed. But I think it's fair to say that just embracing LLMs is not enough.
LLMs still produce some of these, specially on C++ decomps.You can see it most often when calling the base class constructor/destructor. Here's an example: https://github.com/emoluvjd2/thps2-demo-decomp/blob/33d2c12b...
Disassembler output can relatively easily match original binary, I doubt anyone would consider it decompilation.
Let's say you add requirement of being higher abstraction language than assembly. You can translate the assembly code 1:1 with simple string substitions to C code which would provably replicate the behavior of program perfectly. I would consider that more of hard coded emulation, opposite of JIT like ahead of time code translation. Just because the intermediate langauge is higher level language than assembly doesn't automatically mean that the program text operates at higher level of abstraction than assembly.
I have seen plenty of decompilation projects where you start reading the code many functions contain nonsensical variable reuse (and name that match the register names), address calculations and memory casts and array calculations, arrays of function pointers, offsets within structures which strongly mirrors the assembly code.
The problem is that you can't draw a hard line how good do the variable/function names need to be so that it can be considered decompilation instead of hard coded emulation. It's hard to draw a line for how much of higher level language features you need to use to be considered decompilation. It's hard to define how logical the control flow needs to be. The line becomes blurrier by system programmers approaching it from the other side. Sometimes an extensively optimized code can contain a lot more manual memory mangling, index and offset calculations, manual unrolling than the language requires. So many C programs with homebrew systems of emulating virtual functions using structs of pointers. In such situations it's hard to tell if decompilation is incomplete or whether program was written that way from start.
Another possibility of junk decompilation (which perfectly compiles to byte identical copy of original executable) is program which depends on exact program layout in memory or undefined aspects of programming language. It would perfectly replicate original behavior on original hardware/compiler, but provide 0 insights in intended program behavior and fail miserably when attempting to modify the code or port it to different hardware/compiler. For example the "decompiled" code might intentionally peform out of bounds array access thus touching completely different variable or depend on fallthrough between functions lacking return statement.
Taking it to the extreme would be something like:
```
char data[] = {0x10, 0x12, 0xab, 0xcf, ...}; // bytes corresponding to majority of original program.
int main(){ ((void*())data)(); // cast the array to function pointer and execute it
} ```
It might behave exactly like original program, but that's junk decompilation.
You could add additional restriction that C program isn't allowed to exploit any undefined or machine specific behavior, but then there is very high chance that even original program wouldn't satisfy it.
Original program might also contain parts of it written in assembly for performance reasons or because that was only way to access certain hardware features. What does it mean for decompilation? Is decompiled code not allowed to use the same techniques. If decompiled code is allowed to have some pieces of assembly what stops it from using it for everything. There is no way of knowing precisely how much assembly the original program used.
Seems like easy money to just decompile a retro game, add some quality of life improvements and stick it on steam.
Are their hands tied by legal or what other factors are preventing this?
Yes.
Even if you're the owner of the game, the contracts for the voice actors, motion capture, models, etc. frequently define very narrow terms of usage. Defining the exact release platforms is common. Not to mention profit sharing if the actors or studios you're hiring are popular.
It's even worse for retro games. Many times the original rights holder went out of business, sold their rights, died, or something else that muddies the ownership chain.
Including the one my username is inspired by. It's why you see a lot of fan remakes. Another sad side effect of corporate owned IP is also a lot of times the individuals who did the bulk of the work on something can't be the ones to do a remake. They don't own their own work. A company owns it. That's what most of us sign up for to get a paycheck. But when it's something like film or games, the effect on artists or designers can be pretty depressing.
So yes, games are not unique in this way. I am hopeful in the optimistic future case where LLMs lead to lower barriers to entry, UBI, and so forth that copyright essentially disappears, or at the very least is severely reformed.
Many things should simply enter the public domain at a far more accelerated rate.
1) Rights can't be held by corporations, only individuals. So the rights to a piece of art or IP would always trace down to a list of names.
2) Everything enters the public domain after 20 years or the death of the entire list of ownership.
Isn't the current system setup around the idea that the company gets the rights, and in return the workers get a basically risk-free salary? Wouldn’t your proposal undo that, so then workers took on the risk of whether they got paid, based on whether the game ends up being profitable?
Then again Disney's old "vault" strategy would counter it, and Nintendo is already toying with it to sell through FOMO. How can it be this hard to make companies keep our digital culture available for sale? It's not even charity.
I don't disagree with the spirit of your argument. I'm just saying that my proposal would be a lot easier to put in practice than anything that hurts companies. The strength of the proposal is exactly that companies will mostly shrug while creating a very clear legal separation of what's "active copyright" and what's an "abandoned work".
By the way, this would also apply to the GPL (and other copyleft licenses).
Since I'm just pitching wild ideas on the Internet of course I have the freedom to make any suggestion I want, but I would like to live in a world where (a) no companies or rights owners are able to play bullshit games with gating access to art that can be reproduced at trivial cost and (b) we can consume all the art we want that's past its sell-by date. It's insane to me that we don't have free, legal databases of every work of art created before, say, 2006.
Nightdive studios has a few blog posts regarding this.
The problem apparently is that a lot of retro game IP changed hands when records were physical.
So Nightdive goes to Company A and says "Hey do you have the rights to <Game> or did you sell them"?
Company A says "You can pay us 50,000 dollars to check our records we are not sure."
Nightdive says "Ok, seeing as you dont know if you own the game, could you give us permission just in case you do"
Company A: "No, we would need to review the records. However if you released the game to market, we would check just to see if we can sue"
Nightdive has an identical conversation with Company B, who may have bought the rights from Company A but only have archival records.
Nightdive walks away from the project.
Worth a look if you want a bit of nostalgia.
https://github.com/perfect-dark-pc-port/perfect_dark
Ported it to OpenBSD for fun an evening about a year ago.
Historically there was a notion of "clean room" reimplementation.
These days it seems people translate the actual game code into a different representation of that same code and that makes it open source? Github is full of these.
Copyright for this new code is owned by the person doing decompilation. No one knows how similar to the original it is or not, just that it compiles to the same output.
Edit to clarify: by not exist, I mean it is not publicly available.
Also, If it is an unauthorized derivative work, as I understand it then it might not even qualify for copyright protection itself.
A reimplementation or a behavior-based clone is an entirely different legal world from decompilation.
In fact, the goal of most decomp not to produce the same binary, that’s just used as a validation.
True, but as a derivative work of the original source code they don't have rights to do anything with that code without infringing copyright.
On the one hand, it is fairly clear that producing source code with the explicit goal of reproducing a 1:1 binary is in no way transformative, so that's out. This would be a really hard argument to even attempt.
On the other hand, these projects are mostly free, intended for owners of the game to play the original game on a different platform or in a modified format, and not likely to have a negative effect on the original work's desirability or value. And, the reproductions aren't complete and alone usually produce limited to no value to a consumer (usually, they won't start without the original game files). These are the other important factors considered in fair use determinations and generally go the way of these being OK.
So, it's hard to say. With reverse engineering and copyright in the US in general, context is crucially important; something that would be completely illegal for one purpose (ie - decompiling and recompiling a competitor's software to distribute it without a license or use it internally without purchasing it would be obviously illegal) could be OK for another one.
Then a freshly written project would be completely different with no derivative elements at all; the only commonality between the two codebases is their functional elements. You can compile a decomp into the same binary, but that's only to prove functional equivalence. The intended mode would be with a modern compiler that completely rewrites the logic (it might even have to if it writes it for a completely different computer architecture, which describes every retro console game). Then neither the source nor the compiled artifact would match.
I strongly disagree with this notion from even a conceptual (much less legal) level; the point of compilation is not to erase the algorithms the programmer implemented, just to optimize and implement them.
> You can compile a decomp into the same binary, but that's only to prove functional equivalence.
This is like saying that a translated book is only "functionally" identical to the original; there's a lot of precedent in copyright law for this not being the case, and I don't think any argument revolving around the transformativeness of the compilation process would fly at all.
Programs as written are nothing like programs as compiled, certainly not as an expressive endeavor. Books don't have an analog. There's no point where we strip all conceptual meaning from the book and leave only the procedural algorithms the semantics demand. There's no point where we replace all the individual words with autovectorized versions, or where we automatically delete impossible sentences, or remove every layer of abstraction that the programmer put in there exactly for the ability to convey ideas.
Absolutely false. There are an infinite number of programs that will compile to the same machine code. Especially when an optimizer is involved. Discovering one of those is a creative process, transformative, and protected.
Using an LLM to do it for you? I wouldn’t touch that with a 10 foot pole. Seems too close to mechanical transformation to me.
These projects start off with the original assembly code and use it actively throughout all stages. This is about as far away from clean room as you get.
When run through a disassembler, in most cases, it’s probably the first time anyone has ever looked at the assembly (assuming PlayStation or newer). There are no comments or labels or symbols. Not what most people would consider “the original assembly”.
Their comment still stands; whether it's machine code or "original assembly code" that shipped on the disk it falls under "clean room" reversing and by extension implementation.
(I like these projects but I really don't think they could defend themselves legally if you company went after them)
But in essence it's not so complex. You only need the original compiler that was used to compile the game and tools like ghidra. Binutils for the architecture can help. From there you ask the agent to start building the project. Prepare to spend a ton of tokens though if you go the full AI route.
The agents seem to work a lot better if you keep tooling minimal and let it decide ad-hoc.
I’m really curious about trying this myself. Recently I’ve been handing off tasks for agents to complete on their own more and more. While their work is acceptable I’ve found them to not only take a long time to complete it, but they often add too much complexity and tests.
Trying to reconstruct the original build environment that Andy Onions (where are you?) used took some time. It was assembled with 16-bit MASM, but you can't easily run 16-bit exes under 64-bit Windows, so a bit of a kludge to set up DOSbox where the LLMs can push/pull in-and-out of it while being able to drive it enough to execute MASM and read the stderr and output files etc to get a feedback loop going.
Making up plausible function names and branch labels was pretty quick for LLMs.
there was official source code release for 6502 port, why not match names from that one?
What took me the most time was actually the beginning: building a mental model of what assembly patterns map into which Pascal patterns, and finding good areas to start writing code from. In hindsight, I should have probably started by generating inline assembly equivalents of each procedure/function. The ending was relatively smooth sailing in comparison, but it is of course much easier if the compiler you're targeting doesn't do much in the way of optimization passes, so it's not representative of decompilation work for more modern targets.
Relatedly, https://github.com/perfect-dark-pc-port/perfect_dark forked from the above describes itself as “a work-in-progress port of the Perfect Dark decompilation to modern platforms”. Further: “The game is in a mostly functional state, with both singleplayer and split-screen multiplayer modes fully working. There are minor graphics- and gameplay-related issues, and possibly occasional crashes.”
The 80s games were mostly coded in bedrooms, but after that it all moved to teams in offices and a lot of studios went boom-to-bust pretty quickly.
I think 80-ish days to recompile and old binary is a much better alternative to the total death of human arts and innovation, and the unending list of crimes against humanity that Marx's theory has led to every time it has been applied.
But to keep it a bit more videogame related, Marx always reminds me of this speech by senator Armstrong of Metal Gear Rising: https://www.youtube.com/watch?v=IMpuUUV2HeE . "We will use an absolute totalitarian state to abolish the state" - a lofty goal turned into complete lunacy.
Games of this generation had code that wasn't intended to be supported or managed after release (no patches for those consoles) and so the code tended to be fairly horrible.
There's a fair amount of folklore going around, but less direct sharing than would be ideal.
For me it was Goldeneye (of course) and Diddy Kong Racing.
Why snowboard kids and not the ocarina of time ????
i think you agree that there is. but it should change your language about this idea of "consumers" and "benefits" - like this is the difference between making code and making a product. nobody NEEDS retro games. in my opinion, they aren't even that good! they are completely and utterly an aesthetic experience. the process IS the product.
Not to mention, there's a lot of stuff beyond an "aesthetic experience". Videogames have historical value, and they have relevancy at the very least as a testament of the state of the industry at any given point.
Legend of Dragoon, for example, integrated QTE-like controls in its attacks, even as a turn-based game. FFVIII, released the same year, implemented interaction in a somewhat similar way. These two are very early predecessors to mechanics Clair Obscur: Expedition 33 much more recently used. That has value.
If anyone wants to establish random conditions as to why they consider a work to be worthy or not, they may have those. But then they should keep them to themselves, rather than pretending people shouldn't care about something because of the way it was done. It's like the classic situation of someone complaining about how some open source project doesn't do something exactly in the way they like. I have nothing but contempt and despise for that sort of people, and it's a whole order of magnitude worse here, where the "problem" stems from their political stance, which nobody with a pulse should give a crap about.
[right hand emoji] "their political stance"... "they should keep them to themselves"
Which does kind of suck. Yeah, it's not nearly as cool to say "I prompted a probabilistic pile of tensors and it did the hard work for me", and I think it majorly adjusts how "impressive" projects are. There's no point pretending like decompiling a game by hand and using GPT-6.7 to do it are remotely comparable.
The suck-y part is that if you do the decomp with GPT-6.7 and produce, say, a PC port of an early 2000s console game, a large number of people will refuse to touch it and treat it as if you have committed a cardinal sin. Maybe that's justified? I dunno. It definitely goes beyond people just saying it isn't as fun.
Meanwhile so many westerners see LLMs as "stealing IP", it becomes a bit paradoxical where the lines get drawn.
Vibe-decomped projects are also… a different result.
A matching hand-decompile is useful in itself, but it also serves as a proxy for how well you understand the project; how good are the function and variable names, are the structures good, do you understand the entire flow. There are plenty of LLM-decompiles out there that just match but still every variable name is “unk14”, where every flow is total spaghetti instead of going back to something closer to what a human would have written, or even tons of __asm__ statements. The match stopped being a high-quality proxy metric for the quality of the project as a whole. (There are also LLM-assisted decompiles that are high-quality, but then usually with significant human input. And of course, you can try to ask the agent to clean up the resulting mess after you're done matching, assuming you have any tokens left.)
Of course, if you just want the binary back and collect Internet points, you don't care about any of this. But decompilation projects are often made for either a) understanding the game better (for speedruns, TASes, or just general explanation), or b) modifying it. And for both, it is much nicer to have source that is closer to the original.
In my mind using an LLM to assist with decompiling is fine, as the output is provably correct. It either compiles to the original binary or it doesn’t.
Turning that into a playable PC game with controls that feel good, etc, is a different story. That requires human taste & judgement.
(Of course there will always be the annoying "never AI" virtue-signalers, but the same can be said about anything.)
- LoZelda Ocarina of time& majora’s mask
- starfox 64
- Mario party
- extreme G racing
- jet force Gemini
- golden eye
- conkers bad fur day
- Mario 64
- Tony hawk
Man… so much nostalgia
is there really no tools whatsoever that produce proof certificates for "these two functions do the same thing"?
Seems more like voodoo.
Emulators constantly run into games that don't run because of specific hardware nuances that may or may not have been intentionally used (Ex. relying on exact cycle counts, reading uninitialized memory, changing values while they're used, etc.).
This is something someone only familiar with writing code for general purpose devices or higher level languages can't imagine. But when you're building for one very specific hardware platform you can tailor your code to that platform in ways that simply aren't possible when building for portability.
I guess I'm assuming that sloppy and buggy software didn't much become popular.
Because it was sloppy and buggy.
But also, as long as the game works on the specific hardware it was designed for then that's all that mattered, which was my point. If it reads uninitialized memory or depends on exact cycle counts or some undocumented hardware behavior then that's _fine_ if it still works on the real hardware, plenty of that was even intentionally done to achieve things otherwise impossible on that hardware.
At the same time it makes it almost impossible to verify even trivial changes to a function. Once you start accounting for all the potential state differences the answer to "does this function work the same?" will basically always be 'no' unless the code is identical. The only way to eliminate various kinds of state from being a concern is to analyze where the function is _used_, rather than just the function itself, and that's an entirely different and much harder kind of challenge.
Why would I assume that sloppy and buggy software would be less popular?
I'll have to get back to you on that one.
This is also partly a preservation activity so (as best we can create it) identical code generating identical output is a big part of the point.