Unlocking Famicom Disk System ROM Injection in Animal Crossing's NES Emulator
Introduction
Last week I detailed how to obtain Super Mario Bros. and The Legend of Zelda in Animal Crossing through e-Reader dot codes for the first time. That process, along the warm reception to my work, inspired me to continue researching Animal Crossing’s NES emulator. For those unfamiliar, Animal Crossing does have a built-in NES emulator that is used to play actual NES games through special furniture items. After further interest in this topic, I learned that it was possible to load any NES ROM file you want onto this built-in emulator. That is, you can actually load your own NES games into Animal Crossing that normally aren’t present on the disk, such as Mega Man, Super Mario Bros. 3, etc.
Now this is not a new discovery, as James Chambers went over this exact topic in his blog post from over five years ago. However, from this, it should also be possible to get Famicom Disk System ROMs injected into the game from the memory card using a similar technique.
Discovery
To summarize Chambers’ post, a “blank” NES exists in Animal Crossing that actually scans the GameCube’s memory card for ROM files when interacted with. This discovery is huge because it allows for practically any NES ROM to be loaded onto the GameCube’s memory card and played in-game. This includes ROM hacks and custom patches. The process is more complex than simply putting a ROM onto the memory card, but luckily Cuyler created a wonderful program to easily inject ROMs in a way that Animal Crossing can read and load them. However, this program only worked for .nes
or .bin
ROMs and it was not possible to load Famicom Disk System ROMs from the memory card.
Now, this would typically make sense since the Famicom Disk System is a Japanese-exclusive peripheral for the Famicom and no NES emulator can properly emulate Disk System games without an extra BIOS. Yet, the Animal Crossing emulator actually does properly emulate the Famicom Disk System. See, the game “Clu Clu Land D” is actually a Famicom Disk System game that was never released in North America and this game is present in the game files under the name 10_cluclu_1.qd.szs
. So how does Animal Crossing handle this game if it requires a special BIOS to load? The answer is what you would expect; the Famicom Disk System BIOS also exists on Animal Crossing’s disk and is loaded by the emulator specifically when playing Clu Clu Land D.
With this knowledge, it should theoretically be possible to load Famicom Disk System ROMs in a similar manner to NES ROMs. So, it was time to dive into figuring it out.
Process
I started with Cuyler’s program which took ROM files as an input and output a working .gci
GameCube save file for use in Animal Crossing. The latest version from 2019 actually attempted to handle Famicom Disk System ROMs as Cuyler added boolean flags to check if the ROM was designed for the NES or for the Disk System. This is done by looking at the header of each ROM; all NES ROMs begin with the ASCII NES
and all Famicom Disk System ROMs begin with the header *NINTENDO-HVC*
. Below is a sample of how Cuyler checked this in his code.
1
2
3
4
5
internal bool IsNESImage(byte[] Data)
=> Encoding.ASCII.GetString(Data, 0, 3) == "NES";
internal bool IsFDSImage(byte[] data)
=> Encoding.ASCII.GetString(data, 1, 14) == "*NINTENDO-HVC*";
This code is correct and is set in place because Famicom Disk System games require custom NES tagging when injected into Animal Crossing. The game actually looks for special tags that define metadata when loading ROMs into the emulator. In the case of NES ROMs, the tag BBR
is used to back up the entire battery system in RAM. However, for Famicom Disk System ROMs, the tag QDS
is used which references the Disk System’s origins using Mitsumi’s Quick Disk format. Having this QDS
tag tells the emulator to load a custom Disk System BIOS that is has in its files, which allows Disk System games to run. To learn more about these tags, you can see this section of my post documenting Animal Crossing’s emulator. Cuyler attempted to handled these tags in his code following the developer’s default implementation, but noted that the default implementation didn’t make sense logically.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
if (IsROM)
{
Tags.Add(new KeyValuePair<string, byte[]>("BBR", new byte[4] { 0x00, 0x00, 0x20, 0x00 }));
}
else if (IsFDS)
{
var sections = (originalROMSize / 0x8000) + 1;
var offset = 0;
for (var i = 0; i < sections; i++)
{
if (i == sections - 1)
{
var size = originalROMSize - offset;
if (size > 0)
Tags.Add(new KeyValuePair<string, byte[]>("QDS", new byte[5] { (byte)(offset >> 16), (byte)(offset >> 8), (byte)offset, (byte)(size >> 8), (byte)size }));
}
else
Tags.Add(new KeyValuePair<string, byte[]>("QDS", new byte[5] { (byte)(offset >> 16), (byte)(offset >> 8), (byte)offset, 0x80, 0x00 }));
offset += 0x8000;
offset &= 0xFFFFFF;
}
// NOTE: The default implementation inside e+ doesn't even work. FamicomSaveDataHeader->saveDataSize is a ushort, so it's limited to 0xFFFF bytes.
// The default implementation in tags_table_external_disksystem_default does the following:
// OFS 0000
// QDS 000000 8000
// QDS 008000 8000
// QDS 010000 8000
// QDS 018000 8000
// This is strange, because it would mean the max save size is 0x20000 bytes, which is 0x10001 bytes more than FamicomSaveDataHeader->saveDataSize can hold.
}
Indeed, trying to load Disk System ROMs in any format resulted in several invalid reads and writes in Dolphin Emulator, with some cases even crashing the game altogether. I was interested in why this was the case - after all, Animal Crossing already runs Clu Clu Land D flawlessly. After some tinkering, I decided to look into Clu Clu Land D’s included ROM file and luckily the developers left a lot of debugging messages when loading it.
1
2
3
4
5
6
7
8
9
10
N[OSREPORT]: nesinfo_tag_process1 開始 mode = 1
N[OSREPORT]: タグ=GID 長さ=3
N[OSREPORT]: CLU CLU LAND D
N[OSREPORT]: タグ=GNM 長さ=14
N[OSREPORT]: 09
N[OSREPORT]: タグ=GNO 長さ=1
N[OSREPORT]: タグ=OFS 長さ=2
N[OSREPORT]: ディスクセーブエリアをロード
N[OSREPORT]: 00 a1 70 00 50
N[OSREPORT]: タグ=QDS 長さ=5
Translating these into English is simple enough, and results in this:
1
2
3
4
5
6
7
8
9
10
N[OSREPORT]: Starting nesinfo_tag_process1 mode = 1
N[OSREPORT]: Tag=GID Length=3
N[OSREPORT]: CLU CLU LAND D
N[OSREPORT]: Tag=GNM Length=14
N[OSREPORT]: 09
N[OSREPORT]: Tag=GNO Length=1
N[OSREPORT]: Tag=OFS Length=2
N[OSREPORT]: Loading disk save area
N[OSREPORT]: 00 a1 70 00 50
N[OSREPORT]: Tag=QDS Length=5
I noted that Clu Clu Land D had a QDS tag value of 0x00a1700050
which is drastically different from the default implementation described in Cuyler’s code.
After trying pretty much everything else, I finally just rewrote portions of Cuyler’s code to instead just write the exact bytes Clu Clu Land D does as a single QDS tag:
1
2
3
4
else if (IsFDS)
{
Tags.Add(new KeyValuePair<string, byte[]>("QDS", new byte[5] { 0x00, 0xa1, 0x70, 0x00, 0x50 }));
}
Testing
After rebuilding with the new code, I tried a few games and they actually worked! I loaded up Yume Kōjō: Doki Doki Panic and Zelda no Densetsu - The Hyrule Fantasy for the first time in Animal Crossing.
However, it’s important to note that through this process I actually figured out the Animal Crossing’s emulator only supports .qd
files. This means the standard .fds
files do not run, and thus need to be converted to the Quick Disk format before being injected. So, I had to find and modify custom Python code to convert .fds
to .qd
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def convert_to_qd(disk, null):
def insert_crc(start, end, null):
if not null:
crc = fds_crc(disk[start:end])
disk.insert(end + 0, crc & 0xFF)
disk.insert(end + 1, crc >> 0x8)
else:
disk.insert(end + 0, 0)
disk.insert(end + 1, 0)
if disk[0] != 0x01:
return bytearray()
insert_crc(0, 0x38, null)
pos = 0x3A
insert_crc(pos, pos + 2, null)
pos = 0x3E
try:
while disk[pos] == 3:
print(disk[pos + 0x3: pos + 0xB].decode('latin_1'))
filesize, = struct.unpack('<H', disk[pos + 0xD: pos + 0xF])
insert_crc(pos, pos + 0x10, null)
pos += 0x10 + 2
insert_crc(pos, pos + 1 + filesize, null)
pos += 1 + filesize + 2
except IndexError:
pass
if len(disk) > 0x10000:
disk = disk[:0x10000]
else:
disk = disk.ljust(0x10000, b"/0")
return disk
This is taken from einstein95’s qd2fds tool, found here.
Conclusion
With this patch to Cuyler’s ACNESCreator, the full potential of Animal Crossing’s NES emulator has been unlocked. You can now most, if not all, .nes
, .bin
, or .qd
ROMs through memory card injection!
Acknowledgements
I’d like to thank James Chambers and Cuyler for discovering this in 2018, making the tools, and helping me troubleshoot the Disk System Process.