Select a category to the left.



Light or dark? Choose how the site looks to you by clicking an image below.

Light Dark AMOLED


Preferred Language

All content on is originally in UK English. However, if content exists in your preferred language, it will display as that instead. Feel free to choose that below. This will require a page refresh to take effect.


"" details

Domain Name:
Site Version: 1.0.0
Last Updated: 2020/12/29
Action Replay DS (Part 2): Firmware Flashing
Sunday, July 10, 2022


Following Part 1, I felt like the research done was sufficient. And it really was. I was able to recover the data from my Action Replay DS by dumping the cartridge and taking a look at what's inside. I even wrote programs to aid in extracting the data. So what's next?

I just feel like we can do better than that. It didn't sit well with me that I was restricted by a means of not being able to test this with hardware, because I was missing some pieces of the puzzle. But I want this story to have a proper ending, so it's time to fix that cartridge and get it working again. Once it's working, testing it and getting accurate data for documentation and archival purposes sounds like a great next step. Also, I would like to figure out what actually went wrong with my cartridge which started this series of blog posts. So, let's jump in.

Analysing the situation

A white screen "brick" doesn't seem good. Even on their own site, they don't seem to think highly of the fact their product fails.

" seems your device is beyond repair..."

That screenshot is from this site [Archive]. Yeah, that's not a great start. But there are ways around this. And I don't mean ringing their call centre.

What Hardware/Software is available?

To recap on part 1, I have a DSi with CFW. That CFW doesn't mean much right now, because we already dumped the contents of the Action Replay cartridge to recover its contents. I also have a normal DS Lite and a New 3DS LL with CFW. The 3DS is useless because the Action Replay cartridge will not fit in there. The DS Lite may come in handy because it is the only device that the Action Replay cartridge can run off of without any problems. When slotted into the DSi, the contents can be dumped, but there are issues with the bottom touch screen when trying to use it.

Let's introduce the key. I have a flashcart and a clean working copy of the Action Replay DS as a ROM that I downloaded from this magical place called "The Internet". There exists a trick that will let you flash the physical contents of your Action Replay DS cartridge with a flashcart. That trick is to do the following:

  1. Insert Flashcart and run the working copy of Action Replay DS
  2. Take out Flashcart
  3. Insert physical Action Replay DS cartridge
  4. Connect it to the computer
  5. Update it via Action Replay Code Manager

It sounds intuitive enough. But one of the quirks of the last blog post is that the entire effort was done without a cable, as mine went missing. So without having the cable, we aren't able to have proper interaction between the cartridge and a PC. So I guess I should just write up some homebrew that wi-

So anyways, I bought the cable between the last blog post and this one. That's right. We're buying our way out of this problem. I bought this one from eBay here. So now that we have a cable, it's time to commence our plan.

Installing Action Replay Code Manager

The physical Action Replay DS disc ISO

I wouldn't make this its own section in this blog. But I wanted to because, for archival sake, I found the original Action Replay disc and dumped the ISO. I also scanned the disc. This isn't very special, as people have put their copies up on a while ago.

You can find the ISO here.

Installation, but things go wrong

And with that, we run the installation wizard and get the Code Manager installed. Behold:

What a blast from the past. So if I just simply plug in that cable and connect my PC to my Action Replay DS cartridge, it'll work, right? It would be nice if everything worked on the very first try with little effort, right? Hah.

"USB device not recognised"

Unfortunately, no. Of course it has to go wrong. The device is not seen as working or functioning properly. My first instinct in this would be to look at drivers to install. My second instinct would be to resort to virtualisation. Since I know this will work on a Windows XP installation, it would be practical to use USB passthrough to get those virtual machines to read it and do my work on there.

After digging a little bit in devmgmt.msc (Device Manager) and trying to install the drivers this way, it became pretty clear I wasn't going to get this thing working the easy way. So then I tried to use VMware and VirtualBox with a 32-bit Windows XP installation. That didn't work either. The USB device won't even show up in the USB device list for me to pass through to the virtual machine. I was tempted to try it on Linux, but I think I have a solution that is the least hassle and wastes the least amount of time, because I know it is guaranteed to work: Actual hardware.

Installation, but things go right

I have several computers in my studio. And a few of them just happen to be 32-bit. So I took one of them and made room for a 20 GB partition and put a copy of Windows XP on it. Then I ran the installation on there and hooked it up. And...

So the hardware was detected properly. One more time. So running the Code Manager should work, right? Right?

Jackpot. We're back in the mid-2000s. And the DS is connected.

Installing the firmware and fixing the cartridge once and for all

Finding the Firmware

Installing the Code Manager was only the first part. We need to get the firmware too. Unfortunately trying to download the firmware through means like the "Software Update" button doesn't work. Updating the Code Manager itself actually resulted in the software not being able to detect my DS at all. So that's a bad idea. So how the hell do I fix this thing?

After Googling a little bit more, I found out that I can skip all of the "Internet update" bullshit and install a specific firmware by taking a firmware file and dragging it to the top of the Code Manager window. Great! So I just have to find a firmware file online. A lot of the sources online try to get me to download 1.71. And that one doesn't work for me. The Cutting Room Floor has more information on why here. Simply put, mine is the old-type hardware. So 1.71 won't work on it. Instead, Datel backported 1.71 to work on their older Action Replay DS's and called it version 1.55. So that's the version to get firmware of.

It took a lot more effort than I would have liked to get it. Including searching the WayBack Machine. But I found it: Funny. Once I know the filename, suddenly it's really easy to Google up.

Installing the Firmware

Again, just take the firmware and drag it to the top of the Code Manager. The follow things happen.

Clicking "Yes" did the obvious job of installing the firmware.

I let it do its business and after a few seconds, it was done!

Before anyone asks, yes, there is a DSi in these pictures. No, Action Replay DS doesn't normally work on a DSi. But since mine is CFW'd, it bypasses that restriction. And even then, it still has issues. There is a reason an entirely separate product exists specifically for the DSi.

Behold a clean, working copy

With that done, it boots perfectly. I used my DSi to dump the contents of the cartridge once more so I can snag these perfect screenshots.


It just sits there happily and just works all of a sudden, just like it did over a decade ago. That's just so amazing. Even on the DSi with CFW. I am able to transfer codes to and from the cartridge with the Code Manager flawlessly.

Pictures aside, now that we have a working cartridge, it is possible to get more information for reverse engineering efforts. This is because it is possible for me to tweak values and see how they are written to the cartridge. So that's the next part. Back to 2022, it seems.

Cartridge Specifications

How much ROM space is actually used?

From Part 1, it's clear that the ROM is 16 MiB and the save is 256 KiB. Of course, the save is a very useless 256 KiB since it's just 262,144 01s. But how much of the ROM is used?

The ROM is separated into 16 chunks of 1 MiB each. One thing that bothered me while I was developing ards_game_ls was that duplicates of games were appearing. It bothered me enough to make the flag -d to allow duplicates, and then disable printing duplicates by default. So what exactly is happening here? A game that appeared at 0x00054000 also appeared at 0x00154000, 0x00254000, ..., 0x00F54000. It just kept reappearing. So then I look at the bytes and realise that 0x00100000 has the exact same ROM header as shown at 0x00000000.

So, are these chunks all the same? Does the cartridge just store 15 copies of the same 1 MiB chunk at the start?

ards_mem_eval - ARDS ROM Chunk Comparison Utility

Behold ards_mem_eval. Very simple and extremely specific tool. It just takes a ROM dump and will compare chunks. The exit status of the program can be used to determine if all 16 chunks are exactly the same or not. But a pretty table is also outputted by default.

UNIX Command
UNIX> ./ards_mem_eval [-hq] "ACTION_REPLAY_DS.nds"

As usual, comes with optional flags.

With this tool, we can find out the answer to the question of if 15 copies are stored on the cartridge.

Chunk comparison "chart".

Okay, so the first chunk is different from all of the rest. But the rest of them are all the same. This result is consistent on my original broken ARDS dump, as well as the fixed one, and also the working copy I downloaded from some random place on "The Internet". If you are curious what the 49 is and where it comes from, it's the result of memcmp. That function in C will compare bytes of 2 areas of memory. It returns 0 if the 2 areas of memory are the same. It returns a non-zero value when they are not the same. That number is the difference between the first byte it finds different between the two areas of memory.

So, what's different from the first chunk and all of the others? After digging around in a hex viewer, 0x00002000 through 0x00004800 are different. Other than that, all other regions are the exact same between all chunks.

What is the "firmware" file anyways?

This one might be the easiest thing to look into. Remember that ZIP file I linked earlier? Inside of it is a ARDS.firmware.1.55.bin file. It is exactly 221,704 bytes, or 216.5 KiB. This appears to not be consistent between versions. The 1.71 firmware is 218,120 bytes, or 213 KiB. More on that one later.

Initial Inspection and Header Reverse-Engineering

So what's inside the 1.55 firmware file? Well let's take a look.

First 64 bytes of "ARDS.firmware.1.55.bin".

This looks kind of familiar... Let's look at the first few bytes of the working ARDS NDS ROM dump?

First 64 bytes of ARDS NDS ROM, dumped after flashing with steps up above.

These are the exact same with the exception that the firmware file has a header, as well as a mysterious value afterwards. This totals to 8 bytes. The first set of 4 bytes is really easy. FIRM is just a way for the Action Replay Code Manager to tell that the file is a firmware file. The second set of 4 bytes is a little less obvious. There are some reasonable guesses, like size, checksum, version, and more. And size is clearly not the case. But rather than me try to figure this shit out the hard way, I am going to use a digital sledgehammer to figure it out the easy way.

IDA Pro in action. Decompiling and analysing header detection.

Yes. I'm just decompiling the Action Replay Code Manager. The segment up above tells me that there are more firmware headers than just FIRM that are accepted by the Code Manager. Specifically, it can take a CCAR, a AR2M, or a FIRM.

I didn't really look much into device types. But I am guessing that there are checks in place for making sure the correct device gets the correct firmware. Seeing as v2 is checked quite a lot, that's my hunch. I just don't care right now. What I care about is the second set of 4 bytes. One thing to pay attention to is line 40,

Pseudocode-C++ from IDA Pro
CFile::Read((CFile *)&fp, magic, 8u);

So magic has to be a type of 8 bytes. During the decompilation process, I told IDA it was defined as a char magic[8]. This helped it figure out the following:

Pseudocode-C++ from IDA Pro

  // Read in all bytes except 8 byte header
  firmware_size = CFile::GetLength((CFile *)&fp) - 8;
  firmware_bytes = operator new[](firmware_size);
  CFile::Read((CFile *)&fp, firmware_bytes, firmware_size);

  // Mystery routine on firmware data
  LOWORD(v1) = sub_4127B0(0xFFFF, firmware_size, firmware_bytes);
  v16 = v1;

  // Check sub_4127B0 result with 5th and 6th bytes
  if ( (_WORD)v1 == *(_WORD *)&magic[4] )

This screams out "Checksum" to me. So the Code Manager reads in all but the first 8 bytes into a buffer (firmware_bytes), and then runs sub_4127B0 on it. Then the results are compared to *(_WORD *)&magic[4]. This is a reinterpret_cast of the 5th and 6th bytes of magic into a uint16_t. In English, some computation is done on all bytes past the 8th in the file. A value is generated and compared to the 4A 3F seen in ARDS.firmware.1.55.bin up above. If the check passes, the firmware is valid.

So, what really is this sub_4127B0 function? Let's decompile it too.

Pseudocode-C++ from IDA Pro
__int16 __cdecl sub_4127B0(__int16 mask, int size, _BYTE *bytes)
	int v3; // esi
	_BYTE *v4; // ecx
	__int16 result; // ax

	v3 = size;
	if ( !size )
		return mask;
	v4 = bytes;
	result = mask;
		result = byte_4A47E0[(unsigned __int8)(result ^ *v4++)] ^ HIBYTE(result);
	while ( v3 );
	return result;

Okay, this is obviously a CRC algorithm with a table lookup. That table is byte_4A47E0, which is an array of 256 uint16_ts. You can find this table here. With this, it is possible to generate our own firmware files with a correct checksum. But rather than do that manually and explain this code, I'm just going to write something with this procedure to make sure I can re-compute the checksum correctly.

A checksum generated with the decompiled code from up above.

That program is ards_firm_checksum. And it correctly generates the values we see in the 5th and 6th bytes up above: 4A 3F. The byte flipping is because of how it's stored in memory, as opposed to how it's actually read. For more details on that, check this out.

Where do the rest of the bytes go?

So anyways, every byte after the first 8. I may have led you on to believe it's simply copied onto the ROM byte-for-byte. And you are almost correct. Remember how we used ards_mem_eval to evaluate which chunks are the same and which are different? The first one is different from all of the rest. All of the others are identical to each other. Hence 0x00100000 up to 0x00200000 being identical to the next chunk. The only differences between all of those and the first chunk is the regions between 0x00002000 and 0x00004800. It comes back to bite us.

So here's the rules. For every chunk other than the first one, the bytes of the firmware are byte-for-byte exact with the firmware file. This means that extracting a backup of your firmware is as simple as jumping to 0x00100000 and going until you hit a huge wall of FFs. To be precise, start at 0x0013FFFF and read backwards until you find a byte that isn't an FF. That's the end of your firmware.

What makes the region between 0x00002000 and 0x00004800 so special? Well, I am not sure. And I doubt it really matters. To prove that, I modified the ROM to force the first chunk to be an exact copy of every other chunk, as well as being identical to the firmware file. And? It boots just fine. So I am not sure what the quirk is.

Overwriting it with 1.71

So what if I overwrite the beginning of the NDS ROM with firmware from a different version? I also have a dump of 1.71 for the new-type hardware. So if I copy that into the first chunk only, what will happen?

Oh hey. That somewhat worked.

I do not recommend testing this on actual hardware. I'm doing it on emulation just out of curiosity.

Overwriting it with 1.02

Same procedure. Except this time, let's say I'm too lazy to search the Internet for firmware. And admittedly finding 1.02 seems to be a bit more difficult than 1.55 was. Excuses. Anyways, I want to try extracting a firmware from another dump. I downloaded a working copy of Action Replay DS that happened to have 1.02's firmware. Can we reconstruct the firmware file from that?

Well, I'm too lazy to search the Internet, but not lazy enough to not write a program. So behold ards_firm_extract.

It has a syntax like this:

UNIX Command
UNIX> ./ards_firm_extract "ARDS_IN.nds" > "FIRMWARE_OUT.bin"

So, if we adjust that to work on the ROM with 1.02's firmware...

1.02 extracted from some random ROM off of the Internet.

Injecting it into the ROM results in this:

Left = Initial Screen. Right = A second later.

A reminder that this is on an emulator. melonDS, specifically. I thought that was funny. It doesn't do this with DeSmuME.

Overwriting it with 1.00

One more time. This time, with a ROM with 1.00's firmware.

1.00 extracted from some random ROM off of the Internet.

Once again, here we go.

Left = Initial Screen. Right = A second later.

Very first version doesn't show the game's hash whenever a game is slotted in.

Overwriting it with a Japanese 1.21?

Suddenly the temptation to tinker is really getting me. There's a Japanese ROM on the internet. So let's rip the firmware out of that and put it into the USA ROM dump. I'm curious.

A Japanese version of 1.21 extracted from some random ROM off of the Internet.


Left = Opening screen. Middle = Main Screen. Right = A second later.

Okay, I've never seen this one before. My curiosity is getting the best of me. So now it's time to do something incredibly dangerous.

Flashing Japanese firmware to USA hardware

Now that we have something that can generate a firmware that is "valid" to the Action Replay Code Manager. We can kind of mix and match things that don't exist. Or shouldn't exist. A USA cartridge with Japanese firmware? I like the sound of that. So let's give it a shot.

Japanese v1.21 working on an English Action Replay DS Cartridge.

Huh. Okay, Cool. It works on actual hardware. Prior to all of this, I had a hunch that all the Code Manager is doing is copy the firmware over to the cartridge. And then it just overwrites itself with whatever is sent over. I had no idea that it would let literally anything go over and work. Datel really created a tinkerer's dream. And I'm pretty sure this part wasn't even intended.

There is some oddity here though. The text in that picture says 「ゲームカードに差し替えてください」 (Replace with a game card). The reason for this is because the ROM dump probably came from a Pro Action Replay that didn't have an extra NDS Cartridge Slot at the top of it. A look at v1.62, which came from a Pro Action Replay MAX, shows the text as 「ゲームカードを挿入してください」 (Insert a game card). The Pro Action Replay MAX did come with an extra NDS Cartridge Slot. So this actually makes sense. It's a nice attention to detail as opposed to "INSERT GAME CART" on the USA version. Oddly, no matter the version, all versions do support the extra NDS Cartridge Slot. I can slot in a game and it will recognise it without having to swap out the Action Replay DS Cartridge.

There is also a 1.50 Japanese release, which allows for editing codes without having to be connected to a PC. These kinds of things made me search around for a little bit and extract the firmware of a few ROM dumps that just happened to be online. Here's a table of some of them:


Version Model Link Checksum
1.00 Old EA90
1.02 Old A5B5
1.54 Old EC84
1.55 Old Official 3F4A
1.60 New Official 7CFB
1.71 New Official CDCC

Version Model Link Checksum
1.21 CDB6
1.50 F5C4
1.54 MAX 761C
1.62 MAX FED5
2.03 MAX2 Official A1E3
3.02 MAX3 Official 3F45
3.05 MAX3 Official D18E

I feel like I can build up a little collection of these.

By the way, you may have noticed that 7 of those entries above do not have links. This is intentional. They were extracted from ROMs that you can easily get off of the Internet. And not all of them have FIRM at the top. So they wouldn't be official firmware releases from Datel. I only posted those that are not ROM dumps and are official distributions from Datel's own website. If you want the others, download the ROMs and run ards_firm_extract, shown earlier in this post. I don't think Datel would go after me, but I want to be on the safe side anyways.

So, what went wrong with my cartridge?

Now that I have access to some firmware dumps, I figured it was time to figure out what the hell went wrong with my cartridge. Since it was literally just a white screen, my initial assumption is the firmware. That was a good guess, because while I was dumping USA v1.54, I noticed something very interesting. On initial inspection, it's nearly byte-for-byte the same as my original cartridge dump. So I decided to do a data comparison on it. The result was miraculous.

Assume the following UNIX command:

UNIX Command
UNIX> vimdiff <(hexdump -Cv "ARDS.firmware.broken.bin") <(hexdump -Cv "ARDS.firmware.1.54.bin")


Left = Non-working Firmware. Right = Working Firmware.

That's right. It was a single byte located at 0x00005555 (subtract 8 from the screenshot above to account for the header). It was 0x80 instead of 0xCD. And if I change the byte in the ROM file, it actually boots! So that tells me that the bricking of that entire cartridge was due to a single byte being written incorrectly. I'm guessing this was during an update since that's the only time this portion of the cartridge is written to. I don't remember. This was over a decade ago. The best guess I have is that I was updating to v1.54 and that byte was written wrong.

Oh, there was one more detail. Bytes between 0x00034400 and 0x00034FFF inclusively were set to 0xCD instead of 0xFF, which made the firmware dump of my broken cartridge slightly larger. But this had no impact on whether the cartridge booted or not. Probably because there was no need to jump to that spot in memory to execute code.

This discovery is kind of huge. But it also is pointless to me since we also are able to just overwrite the firmware via the CFW trick anyways. People without the same equipment are not able to do anything themselves to fix their cartridges. And if they do have the same equipment, they can just dump the cart to save their codes, flash another firmware, then copy the codes back to the cart. So the discovery is kind of a moot point. It's nice to know, I guess.

If you wanted to do the byte comparison yourself for some reason, here's ARDS.firmware.broken.bin, the firmware extracted from my broken ARDS. Its checksum is 8137. That single byte change and extra bytes at the end made the checksum entirely different from v1.54's EC84 due to the avalanche effect.

So, what's next?

I wasn't hoping to spend as much time as I did on firmware in this post. It kind of makes me think about lost media because there are versions out there that aren't dumped, or are not easily accessible. It would be nice to archive them. There's another version of the Japanese MAX2's 2.03 called max2_2.03b.bin that somehow the Wayback Machine didn't archive. And there's versions like USA 1.52 and probably some between that and 1.02 that exist.

As for what's next, we have working hardware now. The white screen "brick" is now gone. So it is possible to accurately document what is going on in the software. Part 1 had me guessing most of the time due to the lack of working hardware. But now we can give the software a true breakdown and confirm any suspicions listed in Part 1. So that'll be Part 3.

Clara Nguyễn
Hi! I am a Vietnamese/Italian mix with a Master's Degree in Computer Science from UTK. I have been programming since I was 6 and love to write apps and tools to make people's lives easier. I also love to do photography and media production. Nice to meet you!

Blog Links
Post Archive
Affiliates/Cool People
Nigoli's Blog
Raas's Blog