Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Zelda: A Link to the Past (SNES) re-implemented in C (github.com/snesrev)
211 points by adamdegas on Jan 29, 2023 | hide | past | favorite | 42 comments


> Then the RAM state is compared after each frame, to verify that the C implementation is correct.

So not simply recreated, it's a 100% accurate replica of the original game. Crazy.

What, by the way, was the original SNES "OS" written in?


It seems less crazy when you look at the code. It seems like someone has gone through each function in a disassembly of the original and written the directly-corresponding C. Pick any module, for example, ending.c's Module18_GanonEmerges(), and you'll see lots of magic constants and direct SNES references like Intro_Clear1kbBlocksOfWRAM(). This is basically a C-ified disassembly of the original combined with half a SNES emulator.


Sounds like something ChatGPT could have done in a nearly fully automated fashion now.

EDIT: While downvoted, it is likely true. ChatGPT + Github Copilot are amazing at translating code between languages, especially from minimized code or inferring TypeScript typings from JavaScript.


Extraordinary claims require evidence. If this can be automated, give it a whirl and write a blog about it. I'm sure a LOT of people would love to see this.

Also, keep in mind that translating between source code in two languages is fairly easy, while translating machine code to a compilable language is very hard. It requires knowledge of the machine itself which may or may not exist. Maybe ChatGPT knows enough about SNES to do this? Probably not.

Setting a register to some magic constant and then pushing it to some unknown address will do SOMETHING in the computer but you don't know what or why. It's honestly really messy.


I just quickly grabbed the assembly for Zelda1 here:

https://www.romhacking.net/forum/index.php?topic=32277.0

And then GPTChat gave me this:

  #include <stdint.h>

  #define SONG_TABLE_LEN 32
  #define SONG_HEADER_LEN 8
  #define SONG_HEADER_ITEM_TAKEN_LEN 7
  #define SONG_HEADER_END_LEVEL_LEN 7
  #define SONG_HEADER_OVERWORLD_LEN 8
  #define SONG_HEADER_UNDERWORLD_LEN 8
  #define SONG_HEADER_LAST_LEVEL_LEN 8
  #define SONG_HEADER_GANON_LEN 8
  #define SONG_HEADER_ENDING_LEN 8

  const uint8_t SongTable[SONG_TABLE_LEN] = {
    0x7D, 0xB5, 0x6E, 0x67, 0x7D, 0xAD, 0x64, 0x64,
    0x75, 0x7D, 0x85, 0x95, 0x7D, 0x8D, 0x95, 0x9D,
    0xA5, 0xBD, 0xC5, 0xCD, 0xD5, 0xDD, 0xD5, 0xE5,
    0xED, 0x24, 0x2C, 0x34, 0x3C, 0x44, 0x34, 0x4C,
    0x54, 0x5C, 0x44, 0xF5
  };

  ...

  void DriveAudio(void) {
  unsigned char i;
  unsigned char Square0Note;
  unsigned char Square1Note;
  unsigned char TriangleNote;
  unsigned char NoiseNote;
  unsigned char Envelope;
  unsigned int NoteLength;

  Square0Note = SongScript[SongHeader[SongSelect][4]];
  Square1Note = Square0Note;
  TriangleNote = SongScript[SongHeader[SongSelect][3]];
  NoiseNote = SongScript[SongHeader[SongSelect][5]];
  Envelope = SongHeader[SongSelect][6];

  NoteLength = SongLength[SongScript[SongHeader[SongSelect][0]] + (Envelope & 0x07)];

  // Play square wave on channel 1
  Square1Note = Square0Note;
  if (Square1Note & 0x80) Square1Note = 0;
  Square1Note &= 0x3F;
  Square1Note |= (Envelope & 0x10) | 0x40;
  Square1Note &= 0x7F;
  for (i = 0; i < Square1Note; ++i) {
  // Output audio waveform data
  }

  // Play triangle wave on channel 2
  if (TriangleNote & 0x80) TriangleNote = 0;
  TriangleNote &= 0x7F;
  for (i = 0; i < TriangleNote; ++i) {
  // Output audio waveform data
  }

  // Play noise on channel 4
  if (NoiseNote & 0x80) NoiseNote = 0;
  NoiseNote &= 0x3F;
  NoiseNote |= (Envelope & 0x20) | 0x40;
  for (i = 0; i < NoiseNote; ++i) {
  // Output audio waveform data
  }

  // Wait for note length to complete
  for (i = 0; i < NoteLength; ++i) {
  // Wait
  }
  }
The places where it can not fill in the details is because there are subroutines that are called whose body I didn't include in the prompt. Basically if you take a recursive approach where you feed ChatGPT incrementally the subroutines and whatnot, it can reconstruct it.


I don't think that looks right. It looks like C code but mostly is just empty loops and missing references.

  for (i = 0; i < Square1Note; ++i) {
  // Output audio waveform data
  }


As I said, it is calling subroutines in the code snippet I asked it to translate. I didn't include all of the subroutines. Thus it has stubs for those. To do this properly, you need to basically create a graph of all the subroutines and then ask it to translate the leaf nodes first and slowly work your way up to the root nodes, or something like that. ChatGPT has length limitations for each prompt so you need to have a strategy to overcome those.


That's pretty cool. I wonder if it would work well paired with FernFlower to decompile and de-obfuscate Java byte code. I often find myself stuck with a JAR where the source was never pushed to GIT and have to re-create it from decompiled source.


How did you get it to do that? I asked it to translate Z_00.asm to C and it said: "I'm sorry, I cannot translate a disassembly of code written in assembly language to C."


"Can you covert the following assembly code for the NES into C code?"

[cut and past a bunch of code that fit within the ChatGPT buffer]

And it worked.


SongTable won’t compile (on any modern compiler) because the number of initializers (36) doesn’t match the declaration (32)


Hilarious. Yeah, it definitely inferred the wrong thing there. I checked the original code, there is no length define at all. Thus it just inferred the wrong thing.


The SNES has no "OS", just the game code on the cartridge running directly on the bare metal. At the time the game would almost certainly have been written in assembly language, though some C began to sneak in during the 16 bit era, but Zelda seems a bit early for that.


No OS. These games were hand written in assembly direct to the metal.

The source code of the SNES Doom port was open sourced if you wanna take a look: https://github.com/RandalLinden/DOOM-FX


There is not such a thing as a SNES “OS”. The game cart would overtake the whole hardware.


This even happened in the PC world. My friend had a game on a 5.25” floppy where the floppy was unreadable from DOS. You literally had to boot from the floppy, and you’d boot directly into the game.


Mine came on cassette tapes. It look 1-2 hours to boot the game.


What do they do to ensure that the RAM layout is exactly the same as the SNES?

For example, how do they ensure that the stack layout matches the SNES, the right mix of variables in registers vs in memory, and so on?


When you re-write a SNES assembly game in C, you are only rewriting the code of the game itself. To make it run, you also have to provide an implementation of the CPU, Video chips, RAM layout, etc, of the SNES as a software library for the C code to call. So even if the game is no longer "emulated" because nothing is emulating the assembly instructions at runtime, it is still "simulated" in the sense that you are providing library calls that mimic what the original hardware did when the code ran.

What they are saying is that they can run their own C code along side the original assembly inside their own virtual SNES and compare the memory and register state after each operation. They aren't saying their code checks against a physical SNES. They are saying the virtual system state after running their C code in their virtual SNES matches the state after the original ASM code runs in their virtual SNES.

None of this is to minimize the work. It is a crazy difficult/obsessive project to pull off.


Ah see now. They're virtualizing the snes hardware, not cross-compiling.


Yes they decompiled the original cartridge, rewrote all of the assembly instructions in C, and then wrote an SNES emulator to run those instructions natively.


I would like to give a shout-out to another awesome open source 2D Zelda engine called Solarus: https://solarus-games.org/ The community is very active and supportive, they also made many indie games with this engine.


If anyone is interested the same was achieved with Ocarina Of Time almost 2 years ago. A fully matching decompiled C version from the assembly

https://arstechnica.com/gaming/2021/11/reverse-engineering-t...


That's different though. OoT was decompiled while this is a reimplementation (since there never was a C source for ALttP to begin with).


OP's reimplementation cites this disassembly of the Japanese version: https://github.com/spannerisms/jpdasm

Which cites and is modeled on MathOnNapkins's disassembly of the US version: https://www.zeldix.net/t143-disassembly-zelda-docs, https://www.romhacking.net/forum/index.php?topic=13592.0


I'm a little confused what precisely is this?

Oot seems to be clear it's decompiled the ROM and can deterministically make a ROM that runs on the hardware and emulator.

I thought it was a modern reimplementation but it seems to use parts of a SNES emulator.

And seems to run standalone but with parts of SNES hardware there too... Perhaps that made reusing assets easier than converting them. And translating graphics easier to.


If you like this sort of thing, a similar exact C reimplementation of the original Super Mario Bros. exists here: https://github.com/MitchellSternke/SuperMarioBros-C


What a coincidence! Yesterday night I tried retro game emulator on my old phone. I randomly choose SNES 9x ex+. Zelda: A Link to the Past was the first game I tried. I have never played any Zelda game, excited to play it.


So does it reimplement all the original glitches, too?

Asking for a speedrunning friend.


Who's going to check how much effort it is to run something like Emscripten on this to port it to Wasm for a browser-based version? :)


Are there any screens or videos? I’m especially interested in seeing the features not present in the original, like shaders.


had to ask in Discord and search but here you go: https://www.youtube.com/watch?v=zynoNRslRyo - to me it looks the same. Maybe the new features weren't in yet or maybe I just don't know what I'm looking for


The screen transition and text scrolling is not as laggy as on the SNES.


I wonder if the creator is wary of presenting screenshots if they are identical to the original? That may draw the attention of the infamously IP-aggressive Nintendo.


At least in the US, Sony v. Bleem established screenshots used for comparative purposes to be fair use.


Just because something is legal doesn’t mean Nintendo won’t sue you for it


Bleem is a throwback for sure


love how they supported compiling with tcc!


Should have ported to 64bits RISC-V assembly.

If RISC-V is a success, write RISC-V assembly once, run everywhere.


Yeah if......or hey why not java?


For risc-v you only need an assembler... for java you need... OMFG!





Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: