Extracting 20+ NES & Famicom ROMs from Animal Crossing

8 min read Original article ↗

Animal Crossing for the GameCube was a game far ahead of its time and one of my personal favorites growing up. One of the most beloved features was the addition of playable NES games as rare collectible furniture items.

This feature was implemented by including NES and Famicom Disk System emulators and the actual game ROMs on the Animal Crossing disk. The NES emulator included with Animal Crossing is frequently referenced as being one of the most accurate NES emulators available, but comes with the requirement of playing or emulating a GameCube to access the feature.

What if you wanted to play these ROMs on a different emulator without having to jump into your Animal Crossing town, is that possible?

What game ROMs are available?

There are 21 total NES and Famicom Disk System (FDS) ROMs across the first generation of Animal Crossing games. Different games are included depending on the edition of the game (Doubutsu no Mori, Doubutsu no Mori+, Animal Crossing, or Doubutsu no Mori e+) with Animal Crossing containing the most unique games at 19. Different ROMs are sometimes used depending on the game's region.

I've included a full list of games available along with two other data columns. The first being the "loose" game price from Pricecharting at the time of publication. Buying physical copies of all the games included in Animal Crossing costs just over $500 USD!

The second column is whether the game is available as a part of the "Nintendo Entertaminment System™ Nintendo Switch Online" subscription, here abbreviated as "NSO".

Game Price NSO?
Balloon Fight $30 Yes
Baseball $5 Yes
Clu Clu Land $40 Yes
Clu Clu Land D (1) $42 No
Donkey Kong $47 Yes
Donkey Kong 3 $18 Yes
Donkey Kong Jr. $151 Yes
Excitebike $11 Yes
Golf $7 Yes
Gomoku Narabe Renju (2) $3 No
Ice Climber $24 Yes
The Legend of Zelda $27 Yes
Mahjong (2) $8 No
Pinball $4 Yes
Punch-Out!! $19 Yes
Super Mario Bros. $15 Yes
Tennis $9 Yes
Wario's Woods $44 Yes
Total $504

NOTE (1): You will need to convert the QD file (.qd) for "Clu Clu Land: Welcome to New Clu Clu Land" into an FDS file (.fds) to play the ROM with an emulator. Here's a simple Python script that I found on GitHub that worked perfectly.

NOTE (2): If you happen to also own Animal Forest+ (but not Animal Forest e+) you can also obtain "Mahjong" and "Renju Gomoku Narabe" (ごもくならべ) Famicom games.

So if we've archived the Animal Crossing ROM from the physical disk using CleanRip or the FlippyDrive backup tool, what can we do to extract the NES and FDS ROM files?

When I went looking for answers, originally I tried tracking down the exact offsets within the Animal Crossing ROM using the Animal Crossing decompilation project as a reference. This turned out to be unnecessary, as I discovered that the ROM data was likely compressed.

Knowing that the ROM data was compressed, on a hunch I checked the ROM for all Yaz0-compressed blobs. Yaz0 is commonly used by Nintendo for compressing assets. Sure enough, the ROMs were all compressed using Yaz0 which uses a highly identifiable magic string of "Yaz0". This meant we could extract all ROM files by decompressing all Yaz0 blobs and then look for NES and FDS ROM headers.

Below is the entire script I used to archive the NES ROMs from the Animal Crossing ISO, including an implementation of the Yaz0 compression scheme:

Click to show animal-crossing-nes-roms.py source code
import sys
import mmap
import hashlib
import struct

# MD5 hashes from https://datomatic.no-intro.org
# Headerless, as header is changed from non-AC releases.
known_roms = {
    "0033972cb952bbbc4f04217decdaf3a7": "Mahjong (Japan) (Rev 2) (Animal Crossing).nes",
    "0dd95c3047bb0336823c39fefb7639c3": "Donkey Kong (World) (Rev 1) (Animal Crossing).nes",
    "1ca706896a8d4f2a2b5480d941130a4a": "Donkey Kong Jr. Math (USA, Europe).nes",
    "1de41e13a2e691a8cc13b757a46ae3b8": "Clu Clu Land (World) (Animal Crossing).nes",
    "27b4479df4228d48986698ffb94e9f6b": "Punch-Out!! (USA).nes",
    "28c4a5b81feb4033acee9d67852d8ffc": "Gomoku Narabe Renju (Japan) (Animal Crossing).nes",
    "2bf3976d15ec25a756846465a16b064c": "Excitebike (Japan, USA) (En) (Animal Crossing).nes",
    "44d401f92e1da528ca4a9d7083acc9d2": "Clu Clu Land (Japan) (En) (GameCube, Virtual Console).qd",
    "5f37d85ba0f296bd471cd674d63cb640": "Legend of Zelda, The (USA) (Rev 1) (Animal Crossing).nes",
    "8e3630186e35d477231bf8fd50e54cdd": "Super Mario Bros. (World).nes",
    "70c309cb6b9ead20c06d554cf48b3993": "Balloon Fight (USA).nes",
    "108fea367dc5ba9a691b3500fc1464b4": "Baseball (USA, Europe) (Animal Crossing).nes",
    "6631ceac1aaef8efb063a34da86bacb1": "Donkey Kong Jr. (World) (Rev 1) (Animal Crossing).nes",
    "a2b5bddb4c7a5a39c8fac13e64494c9a": "Donkey Kong 3 (World).nes",
    "bec7fa447c1c8e13a87bd4a5685ce563": "Wario's Woods (USA).nes",
    "bfab5f738adb919f1ba389f5c38deb67": "Pinball (Japan, USA) (En) (Animal Crossing).nes",
    "c9c94df2ebb19bd6d717b2cfbf977574": "Ice Climber (USA, Europe, Asia) (En).nes",
    "c432b613606c41bafa4a09470d75e75f": "Soccer (Japan, USA) (En).nes",
    "cbb2c477a37b28517e330d1c562049f8": "Tennis (Japan, USA) (En) (Animal Crossing).nes",
    "d67ee6a0a7af959c417ce894470a49cb": "Mario Bros. (World) (Animal Crossing).nes",
    "f0d94f25db202c935cd8f1cdde10a0aa": "Golf (USA, Asia) (En).nes",
    # Multiboot ROMs
    "594b8e60e9406a570c9990e9bbc4340f": "Clu Clu Land (USA, Europe) (Animal Crossing).mb",
    "aa6bdfc4fce58b19d1a8a9f2f11042d9": "Donkey Kong (USA, Europe) (Animal Crossing).mb",
    "ee8a23328607687b50a6706c7fdfc2e1": "Donkey Kong Jr. (USA, Europe) (Animal Crossing).mb",
    "d3d227a1ca88b0629ef333d544686c41": "Excitebike (USA, Europe) (Animal Crossing).mb",
    "684e46cb0c672e06664ae742825ae89c": "Mario Bros. (USA, Europe) (Animal Crossing).mb",
    "c1c6eb0d42591c46f2e4dc68145e4c81": "Pinball (USA, Europe) (Animal Crossing).mb",
    "34217e69f45e52d1550a8b241ce27404": "Super Mario Bros. (USA, Europe) (Animal Crossing).mb",
    "a81a5d9b9268da64ea8426bdc6a987ba": "Soccer (USA, Europe) (Animal Crossing).mb",
    "146bdf7f70335a2ad67b59ef9e07bfaf": "Tennis (USA, Europe) (Animal Crossing).mb",
    "62c26ddf7579b5179b2a67073bc7e4a4": "Balloon Fight (USA, Europe) (Animal Crossing).mb",
    "b0c8f4dfe47c3649760748ad5c96a649": "Baseball (USA, Europe) (Animal Crossing).mb",
    "a7b95f64a01e7cc18968b1c501741414": "Donkey Kong 3 (USA, Europe) (Animal Crossing).mb",
    "f92aeb4bc274cb08c2eabe9dd3aadcb4": "Golf (USA, Europe) (Animal Crossing).mb",
    "f7adee0901bb73b6f1c1fbeb36b4ab4c": "Ice Climber (USA, Europe) (Animal Crossing).mb",
    "1c8dcf20e4ce979cb9962c835c39a5c9": "Donkey Kong Jr. Math (USA, Europe) (Animal Crossing).mb",
}


def main():
    animal_crossing_iso = sys.argv[1]
    with open(animal_crossing_iso, mode="r+b") as f, mmap.mmap(f.fileno(), 0) as fm:
        offset = -1
        # Find all 'Yaz0' headers.
        while -1 != (offset := fm.find(b"Yaz0", offset + 1)):
            try:
                data = yaz0(fm[offset : offset + 0x80000])
            except Exception:
                continue

            # NES ROM
            if data.startswith(b"NES\x1a"):
                # Calculate the MD5 without the NES header.
                rom_md5 = hashlib.md5(data[16:]).hexdigest()
            # Famicom Disk System ROM
            elif data.startswith(b"\x01*NINTENDO-HVC*\x01"):
                rom_md5 = hashlib.md5(data).hexdigest()
            # GBA Joyboot ROM
            elif data[0xAC:0xB3] == b"AGBJ01\x96":
                rom_md5 = hashlib.md5(data).hexdigest()
            else:
                continue

            if rom_md5 not in known_roms:
                print(f"Unknown ROM MD5: {rom_md5}")
                continue

            filename = known_roms[rom_md5]
            print(f"Extracted ROM: {filename}")
            with open(filename, mode="wb") as rom_fp:
                rom_fp.truncate()
                rom_fp.write(data)


def yaz0(data: bytes) -> bytes:
    # Implementation of Yaz0 following
    # this reference: http://www.amnoid.de/gc/yaz0.txt
    (buf_length,) = struct.unpack(">xxxxLxxxxxxxx", data[:16])
    data = data[16:]
    src = dst = 0
    buf = bytearray(buf_length)
    while dst < buf_length and src < len(data):
        bit_header = data[src]
        src += 1
        for _ in range(8):
            if not (dst < buf_length and src < len(data)):
                break
            if bit_header & 0x80:
                buf[dst] = data[src]
                dst += 1
                src += 1
            else:
                byte1, byte2 = struct.unpack(">BB", data[src : src + 2])
                assert byte1 >= 0 and byte2 >= 0
                src += 2
                copy_src = dst - ((byte1 & 0x0F) << 8 | byte2) - 1
                num_bytes = byte1 >> 4
                if num_bytes == 0:
                    num_bytes = data[src] + 0x12
                    src += 1
                else:
                    num_bytes += 2
                for i in range(num_bytes):
                    buf[dst + i] = buf[copy_src + i]
                dst += num_bytes
            bit_header <<= 1
    return bytes(buf)


if __name__ == "__main__":
    main()

The source code above is also available as a Gist, licensed MIT. After running this script with an Animal Crossing ISO you'll find all the NES and Famicom Disk System ROMs in your current directory:

# Running the script with Python!
$ python animal-crossing-nes-roms.py ./AnimalCrossing\ \(v1.00\).iso

# All of the ROMs are extracted in the CWD.
Extracted ROM: 'Clu Clu Land (World) (Animal Crossing).nes'
Extracted ROM: 'Balloon Fight (USA).nes'
...
Extracted ROM: 'Super Mario Bros. (World).nes'
Extracted ROM: 'Legend of Zelda, The (USA) (Rev 1) (Animal Crossing).nes'

This script can be improved by adding more MD5 checksums of ROMs from different regions like Japan and the EU. Currently I've only tested using the North American copy of Animal Crossing.

These ROMs can now be used just like any other ROM with NES emulators. If you're playing on desktop and want to try your ROMs out you can use Pinky, a WASM-based NES emulator. If you want to play on mobile I recommend using the Delta emulator.

It feels quite magical to be playing NES games from a childhood copy of Animal Crossing on a modern iPhone. Wario's Woods was always my favorite, so I look forward to playing the game more often and keeping that nostalgic connection alive.

If you're looking for more ways to find legal ROMs of games, WULFF DEN published a video recently linking to a list of other "ROM extraction" methods like this one.


Playing Wario's Woods on the Delta emulator

What if I only have a Game Boy Advance?

You're in luck! Animal Crossing also had a feature called "Advance Play" which meant most NES games could be saved to your GBA via a mode called "Multiboot" or "Joyboot". Animal Crossing used separate ROMs that were compiled for the GBA CPU instead of the NES CPU.

These ROMs are detected by looking for the string AGBJ01\x96 at offset 0xAC, corresponding to the Game Boy Advance ROM header that was chosen for these ROMs.

NOTE: To run these on a GBA emulator or with a GBA flash cartridge you need to convert the .mb files into .gba files. This script worked for converting the files to .gba.

Wow, you made it to the end!