Introduction
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.
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:
- Insert Flashcart and run the working copy of Action Replay DS
- Take out Flashcart
- Insert physical Action Replay DS cartridge
- Connect it to the computer
- 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 archive.org 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.
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: NDS-ARDS_firm_1_55.zip. 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 01
s. 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> ./ards_mem_eval [-hq] "ACTION_REPLAY_DS.nds"
As usual, comes with optional flags.
-
-h
- Prints help prompt in the terminal and does nothing else. -
-q
- Quiet (and quick) mode. Does a quick check of the chunks in sequence by comparing one with the previous one. O(n) checks instead of O(n2). Will immediately exit the moment a difference in a chunk is found. Doesn't print anything. Check the exit status withecho $?
. A status of0
means all chunks are the same. A status of1
means there's differences. Any other status means an error occurred.
With this tool, we can find out the answer to the question of if 15 copies are stored on the cartridge.
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
NDS-ARDS_firm_1_55.zip
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.
This looks kind of familiar... Let's look at the first few bytes of the working ARDS NDS ROM dump?
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.
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,
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:
LABEL_13:
// 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.
__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;
do
{
result = byte_4A47E0[(unsigned __int8)(result ^ *v4++)] ^ HIBYTE(result);
--v3;
}
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_t
s.
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.
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
FF
s. 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?
|
|
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> ./ards_firm_extract "ARDS_IN.nds" > "FIRMWARE_OUT.bin"
So, if we adjust that to work on the ROM with 1.02's firmware...
Injecting it into the ROM results in this:
|
|
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.
Once again, here we go.
|
|
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.
Patch.
|
|
|
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.
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:
|
|
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> vimdiff <(hexdump -Cv "ARDS.firmware.broken.bin") <(hexdump -Cv "ARDS.firmware.1.54.bin")
Thus,
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.