Introduction
In 2007, I got a hold of an Action Replay DS cartridge. As a kid, I used it not only to cheat, but to experiment and play around with games I had physical copies of. Don't worry. I didn't cheat online. Aside from them wiping out my saves, it was a pretty fun time. The way the cartridge worked was cool. It had an additional NDS cartridge slot on top. You would put your game in there. Apply the cheats in the interface that boots up, and then boot the game. The cheats will be applied and you can play the game normally.
Unfortunately, a few years after owning one, I managed to make that cartridge unusable. I have no idea how it actually happened. When I boot it, I am presented with a white screen and nothing else. On top of that, I don't have the cable that would let me connect the cartridge to my PC. So I am kind of out of luck, right? Well, not really. Let's look into it.
Just a disclaimer, this blog post was typed progressively. I type as I figure out more things. So it won't just be a "this is this" and "that is that". So it might come across as disorganised. My apologies. But I do think it might be useful to some people to see how I come to some of the conclusions I do. And you see the journey I went across. It's a fun challenge to me. So know what you're getting into by reading this. My goals are to figure out how to salvage whatever data I could off the cartridge, and then figure out why the Action Replay DS froze in the first place.
Getting information off of the cartridge
I do not have the USB cable that came with the Action Replay DS set. So I can't just connect the cartridge to a PC and try to figure out what's going on. However, I doubt it's going to work even if I did have the cable. Instead, I'll just force it to work. Conveniently, we have a modern solution, a DSi with CFW. I also have a New 3DS LL with CFW, but the cartridge will not fit. Oh well. While the Action Replay DS doesn't officially work with the DSi, the CFW on it will force it to work.
In either case, we can use CFW to run GodMode9i to dump the contents of the cartridge, as well as the "save". From there, we can potentially recover the data from the cartridge.
|
|
|
The ROM is 16 MB. The save is 256 KB. The metadata is stored
in a txt
file. This is that text file:
Title String : DAR4NDS v150
Product Code : DSAR52
Revision : 0
Cart ID : 00000FC2
Platform : DS
Save Type : SPI
Save chip ID : 0x010101
Timestamp : 2022-06-20 14:28:34
GM9i Version : v3.2.1
Whenever I bought this, it was version 1.0. I did update it a few times. I guess that the update will overwrite the contents of the cart directly. We still have no idea how exactly the data is stored on the cart. My initial assumption was that the codes would be stored in the save file, while the cartridge and firmware would be dumped and present in the ROM. That would make sense, right?
A look at the save file
Well, this is underwhelming. The DAR4NDS v150_DSAR52_00.sav
file contains a bunch of 01
s. Seriously. It's just 262,144 of
them. Gee, that would compress really well with bz2. 😏
I was hoping the save would contain the codes. Though 256 KB doesn't seem like a lot, now that I think about it more. I remember the cart coming with codes for a lot of games. And I'm sure it would have exceeded 256 KB. But whatever. It must be stored in the ROM dump, somehow.
A look at the NDS file
And now we look at the actual NDS file. At first, it looks like a pretty normal NDS file. But after scrolling down, it's pretty obvious that the code data is present in this dump, somehow. Check this out:
So, the code names are present there, as well as a game name. It seems like the data is stored in the NDS file after all. Not too sure why. But whatever. This means that, if we can reverse-engineer and figure out how the codes are stored on this dump, we can make a full backup of the codes that were on there. We can also use this to try to figure out what the hell happened to make the cartridge soft-brick.
Regarding those names up above, they resemble
C-Strings.
In other words, it's bytes that resemble each character, followed by a
00
, which is the null terminator. This is how strings
of text are stored usually in C. You can clearly see it up above. The
strings go on until a 00
is hit. Some seem to have two. My
guess at this moment is that they are string pairs. The first is the
code name. The second is like a code note, which may be
blank. I may be misremembering and it might just allow for 2 lines of text
per code. But it doesn't really matter. Either way, it's 2 strings per
code.
Before I actually proceed to figuring out where everything is, I need to make it clear, all memory addresses and codes found are for my own copy of Action Replay DS. Since the codes are stored on the actual NDS file itself, this means every cart has a unique dump, with codes located in unique spots. A specific memory address for me definitely won't be the same for you.
Finding the actual codes
I have a hunch that codes are not stored as strings, but actual bytes for compact measure. We have names up above, as well as this magical thing called "The Internet". So we can just grab the codes online and search them in the hex dump.
Some googling led me to this ancient post from 2008 (Archive). How convenient. It happens to have a lot of the codes for the strings above. Thanks mariobowser64! You probably have no idea your post would be useful 14 years later.
Have 240 Stars
Load the file and you will have the stars
02094BB4 FFFFFFFF 02094BB8 FFFFFFFF 02094BBC FFFFFFFF 12094BC0 FFFFFFFF 22094BC1 FFFFFFFF
22094BC2 FFFFFFFF 22094BC3 FFFFFFFF 22094BC4 FFFFFFFF 22094BC5 FFFFFFFF 22094BC6 FFFFFFFF
22094BC7 FFFFFFFF 22094BC8 FFFFFFFF 22094BC9 FFFFFFFF 22094BCA FFFFFFFF 22094BCB FFFFFFFF
22094BCC FFFFFFFF 22094BCD FFFFFFFF 22094BCE FFFFFFFF 22094BCF FFFFFFFF 22094BD0 FFFFFFFF
22094BD1 FFFFFFFF 22094BD2 FFFFFFFF 22094BD3 FFFFFFFF 22094BD4 FFFFFFFF 22094BD5 FFFFFFFF
22094BD6 FFFFFFFF 22094BD7 FFFFFFFF 22094BD8 FFFFFFFF 22094BD9 FFFFFFFF 22094BDA FFFFFFFF
22094BDB FFFFFFFF 22094BDC FFFFFFFF 22094BDD FFFFFFFF 22094BDE FFFFFFFF 22094BDF FFFFFFFF
22094BE2 FFFFFFFF
I guess I'll keep it compact like that for site readability. Anyways, let's
look for that code in memory. It's stored from 0x00054EF4
until 0x00055013
.
Score. If you don't see it, reverse the code values. The Nintendo DS is a
Little Endian system. It makes sense that they are reversed. So the
first part of the first code, 02094BB4
shows up at the top
left of the highlighted region as B4 4B 09 02
.
It also looks like the surrounding binary is other codes. The
50 35 09 62
at the bottom is the start of "Skip Text" code
from that website above. So we know how the codes are stored.
Surrounding Data: Code Header
There's a few other bytes in that diagram above that have some significance. For one, there is a sequence of 4 bytes that comes before the beginning of "Skip Text", which I just talked about. We don't really know what those bytes are for, other than that they are not part of the actual Action Replay code. So they must be a header of some kind.
Go back up to 0x00054EF0
. There's 4 bytes there too.
Specifically, 01 00 24 00
. My hunch is that this is a pair of
two 16-bit integers. I'd say the first 2 bytes are some kind of
flag. I would say the second 2 bytes is the length of the code. So
let's make some sense of it.
A single line of an Action Replay code is in the format
XXXXXXXX YYYYYYYY
. If you count the number of "lines" in the
"Have 240 Stars", you'll come up with 36 lines. In hex, this is
24
. Hmm. Convenient. So I propose the following C-like
structs:
typedef struct AR_LINE {
uint32_t memory_location; // Left side of AR code
uint32_t value; // Right side of AR code
} ar_line_t;
typedef struct AR_CODE_T {
uint16_t flag;
uint16_t num_lines; // A line is 8 bytes, or "XXXXXXXX YYYYYYYY"
ar_line_t *line;
} ar_code_t;
Scared of C or programming in general? Here's a fancy diagram to show what I mean.
If my hunch is correct, and the 01 00
is a flag, then it would
mean that the numerical value 1
will represent a Cheat
Code. The Action Replay DS lets people organise codes with folders as
well. So I will guess that there is a flag for a folder as well. It can't
be complicated.
Deconstructing Folders
Wonderful. So we have codes working. That Super Mario 64 cheats listing also features folders. Scrolling just a little bit down in the hex dump will reveal a mysterious new flag. What could it be?
If it wasn't clear, the arrow is pointing to address
0x000550B8
, being 02 00 03 00
. If we treat this
as an ar_code_t
as defined in our structs earlier, this means
the flag
is 2
. If 1
is a Cheat
Code, what is 2
? My hunch is obviously Folders.
I want the name of that entity. Fortunately, we have all codes (and supposedly folders) text all available to us. So jumping back to that,
There's a lot of bookkeeping to do here. So we will construct a table.
Code Name | AR_CODE_T Location | Name Location |
---|---|---|
240 Stars | 0x00054EF0 | 0x00055D33 |
Skip Text | 0x00055014 | 0x00055D6C |
Character/Arrow Replacement | 0x00055038 | 0x00055D97 |
Multiple Jumping | 0x0005505C | 0x00055DD7 |
Wireframe Character | 0x00055080 | 0x00055DF9 |
No Music | 0x000550A4 | 0x00055E0E |
Item Quantity Mods | 0x000550B8 | 0x00055E2B |
We got what we wanted. It's "Item Quantity Mods". And it has no
note. So, if we assume that it's a folder, then we can assume that
second bit means it has 3 codes inside? Recall. 02 00 03 00
.
If my assumption is correct, this is a folder that has 3 items in it.
Our random Internet friend mariobowser64 comes to the rescue again.
Item Quantity Mods
-
Control Coin Amount
Hold X and press up or down
520973EC FFFFFFFF 02097454 FFFFFFFF D0000000 FFFFFFFF 42097454 FFFFFFFF 02097454 00000000
D0000000 00000000 927FFFA8 FBFF0000 923FDF08 FF0F00F0 A4000130 FF0F00F0 D9000000 02097454
94000130 FFEF0000 D4000000 00000001 D0000000 00000000 94000130 FFDF0000 D4000000 FFFFFFFF
D0000000 00000000 D6000000 02097454 D2000000 00000000 D3000000 04000130 F23FDF08 00000002
D2000000 00000000
-
Control Red Coin Amount
Hold X and press up or down
520973EC FFFFFFFF 02097408 FFFFFFFF D0000000 FFFFFFFF 42097408 FFFFFFFF 02097408 00000000
D0000000 00000000 927FFFA8 FBFF0000 923FDF08 FF0F00F0 A4000130 FF0F00F0 D9000000 02097408
94000130 FFEF0000 D4000000 00000001 D0000000 00000000 94000130 FFDF0000 D4000000 FFFFFFFF
D0000000 00000000 D6000000 02097408 D2000000 00000000 D3000000 04000130 F23FDF08 00000002
D2000000 00000000
-
Control Silver/Multi Star Amount
Hold X and press up or down
520973EC FFFFFFFF 0209740C FFFFFFFF D0000000 FFFFFFFF 4209740C FFFFFFFF 0209740C 00000000
D0000000 00000000 927FFFA8 FBFF0000 923FDF08 FF0F00F0 A4000130 FF0F00F0 D9000000 0209740C
94000130 FFEF0000 D4000000 00000001 D0000000 00000000 94000130 FFDF0000 D4000000 FFFFFFFF
D0000000 00000000 D6000000 0209740C D2000000 00000000 D3000000 04000130 F23FDF08 00000002
D2000000 00000000
If you look at the name of the very next code in the hex dump above, it
will be "Control Silver/Multi Star Amount". And if you look at the values
after 02 00 03 00
, which is 01 00 15 00
, it
reveals a code of 21 lines (0x15
is 21). Those codes posted
above match up, and match the hex dump. We have folders figured out.
With all of this in mind, we can now start to construct an enum for the flag. This is just so things will be more readable whenever I turn all of this information into code. I propose the following enum:
typedef enum AR_FLAG_T {
AR_FLAG_TERMINATE = 0,
AR_FLAG_CODE = 1,
AR_FLAG_FOLDER1 = 2,
AR_FLAG_FOLDER2 = 6
} ar_flag_t;
Later on in the digging up of codes, I also discovered that 6
is a folder. Considering I am not able to test this on my bricked
cartridge, I have no way of telling if there is some special condition for
it yet. My hunch is that the Action Replay DS keeps track of what folder is
open and closed. This conclusion is only because I saw some videos on
YouTube of other people demonstrating their Action Replay DS's and booting
them up. Some folders were open, and some weren't. But I can be wrong.
We'll see. I also have no idea if folders can be nested or not. But
whenever I implement a code extractor later on in this post, it won't
really matter whether they can be extracted or not.
Furthermore, I discovered that we should stop reading data for a game if
an ar_code_t
flag of 0
is hit. It indicates we
hit the end of a game's code list. Hence AR_FLAG_TERMINATE
.
Reverse-Engineering Game Code Structure
With code and folder structure figured out. We can broaded the scope a bit. So what about how things are stored per game?
From above, the codes are stored as bytes. Then the text for the codes is
stored after it. This is done on a per-game basis. We have only looked into
some codes for Super Mario 64 DS. But what about what shows up before the
codes? Well, it's game information. And this starts at
0x00054000
. Here's a hex dump of that:
So the first 32 bytes from 0x00054000
are not bytes for a
code. Instead, they are for game information. A header. From the plaintext
view, we can see that 41 53 4D 45
is ASME
. Hmm,
doesn't that look familiar?
Yes. It's part of the code on the actual cartridge. Specifically, it's the
Game Code
. There are a few other parts that I can recognise.
49 37 A6 AE
at 0x0005401C
is a checksum. This one
is the result of a CRC32 run on the first 512 bytes of the NDS ROM, then
run through a
bitwise NOT
operator. For you C programmers, that's ~
. When you find
Game IDs online, this is where the final 8 digits come from. Hence
ASME-AEA63749
for version 1, or ASME-F486F859
for
version 2 of SM64DS.
Also, looking a bit up at 0x00054000
, this looks like a length
of some kind. 0x18CA
is 6346. And ends up being the number
of bytes from the header up until the text. The next 4 bytes is an
integer 0x1730
, which is 5936. Seeing as the size of the text
chunk is 0x835
(2101 bytes), it can't be that. Instead, it is
the number of bytes from the header until the end of the code
binary. There are bytes between the binary code and the text. This can
also serve as an "offset" to jump to that segment.
The 5th and 6th byte look like a 16-bit integer that is simply the number of codes present.
I also speculate one of these has to be a time field. A weird hunch that
has quite a coincidence behind it. The 4 bytes prior to ASME
,
when reinterpretted into a DOS date, gives us
2007/11/07 @ 18:44:00
. This "coincidence" is consistent
among a lot of games. Now, this isn't a release date. This is more like a
date that the code list was generated on. I'm skeptical, because why a DOS
timestamp? I was expecting a UNIX timestamp. Well, the software is for
Windows.
Anyways, let's assume this is true for now. Thus, Microsoft has
documentation
on this. So we can use that to decipher things without libraries or an
actual Action Replay DS. Another supporting factor is that the timestamps
I have sampled from my dump is that the "seconds" field is always
00
, which is how Action Replay DS XML files have their
<date>
tags organised. They do not specify a "seconds".
There are only two exceptions. If there is no date specified, which I
tended to leave out as a kid, these 4 bytes will be
00 00 00 00
. The other exception is where it shows
SCRO
, which is 53 43 52 4F
. I have no idea what
that is. But it decodes to 2013/10/19 @ 9:58:36
. That
doesn't sound quite right... But remember, I can't test this.
As for the other fields, I can do a
proof by
exhaustion on some of them. For the first 4 bytes, it is always
01 00 1C 00
. So I will treat it as a
Magic
Number. I want to say it's 2 uint16_t
s and each have their
own meaning. Maybe 01
is a "game" identifier, and
1C
(which is 28) could be something else. But it's consistent
enough for me to not care. Another is bytes 6 and 7. They will always be
20 00
(which is 32). Again, it's consistent enough for me to
not care about it.
With all of this in mind, I propose the following struct:
typedef struct AR_GAME_INFO_T {
// Bytes Description
uint32_t magic; // 00 - 03. Always "01 00 1C 00".
uint16_t num_codes; // 04 - 05. Number of codes present
uint16_t nx20; // 06 - 07. Always "20 00".
uint32_t offset_text; // 08 - 11. Bytes from game_info_t to text - 1
uint32_t offset_strlen; // 12 - 15. Bytes from game_info_t to strlen
uint16_t wDosDate; // 16 - 17. DOS date (?)
uint16_t wDosTime; // 18 - 19. DOS time (?)
char ID[4]; // 20 - 23. 4 characters that appear on cartridge
uint32_t idk; // 24 - 27
uint32_t N_CRC32; // 28 - 31. ~CRC32(first 512 bytes of ROM)
} ar_game_info_t;
Writing Code: A game-to-codelist "decompiler"
ards_game_to_xml
- A code "decompiler" into XML format
We now know enough to write up some code to "decompile" segments of the ROM dump into a code list. I'll stay classy. So we'll do this in C. The goal is to start small. We'll write something that will take a memory address and try to dump a single game with the information that was figured out above. This is why I was creating structs. Because then we can just chuck it at C and it will just work... I hope.
So, with this, I present to you
ards_game_to_xml
.
Mouthful. It takes the Action Replay DS rom dump, as well as a hex address
that points to the top of the header up above. In other words, look for a
01 00 1C 00
. That's the address you put in. It will spit
out a valid XML file with the codes from the game straight from the ROM
dump.
It has a syntax like this:
UNIX> ./ards_game_to_xml "ACTION_REPLAY_DS.NDS" HEX_ADDRESS > file.xml
And if we adjust that to where Super Mario 64 DS's header is, at
0x00054000
, we get...
UNIX> ./ards_game_to_xml "DAR4NDS v150_DSAR52_00.nds" 54000 > sm64ds.xml
Which results in the following file: sm64ds.xml. I would embed it here, but it's quite big.
Sweet! It works. And I have successfully recovered codes for that one game.
By searching in a hex editor for the magic number 01 00 1C 00
,
I consistently get results that let me extract more codes from more games.
So the code works consistently. I'm satisfied. In an effort to make it a
little more user friendly, I let it take more than one address. And it will
export the Action Replay codes of multiple games to the same XML file.
UNIX> ./ards_game_to_xml "DAR4NDS v150_DSAR52_00.nds" 54000 5A900 5BF00 5C500 5C800 5DE00 62C00 > codelist.xml
ards_game_ls
- A listing utility for game addresses
To aid in extracting addresses, another utility was written. This one is
for getting addresses where games are stored, and listing their names, if
possible. With this, I present to you
ards_game_ls
.
A bit less of a mouthful. It will take an Action Replay DS rom dump and
scan it from 0x00054000
throughout 0x000FFFFF
for
the magic number discussed earlier. To prevent stumbling upon finding that
magic number by coincidence in an Action Replay code, it will skip over
ones it finds in games.
Since we are going for data recovery and salvaging whatever we need to, I
will throw out my theory that there is a "game list" somewhere, probably at
0x00044000
. The program's point is to go through every single
byte and get whatever it can. Brute force. Thus, the syntax is as follows:
UNIX> ./ards_game_ls [-dehnw] "ACTION_REPLAY_DS.NDS"
There's some optional flags:
-
-d
- Allows duplicate games to be shown. By default, duplicates are not shown. -
-e
- For printing errors. By default, it only prints out games where the code section looks correct. With this flag, it will print out errors tostderr
. -
-h
- Prints help prompt in the terminal and does nothing else. -
-n
- Skips name reading. By default, theards_game_ls
will read a game header, validate the code section, and then run 2 × n C-Style string reads. This will skip that final step and try to read the next game through those string sections. -
-w
- Prints warnings tostderr
.
This program will help out with figuring out what's going on, and what went wrong with the Action Replay DS.
"Just give me something that dumps all codes into an XML file..."
Ok. This.
UNIX> ./ards_game_ls "ARDS.nds" | sed 's/^0x[0]\+\([0-9a-f]\+\) - .*/\1/' | xargs ./ards_game_to_xml "ARDS.nds" > "codelist.xml"
So, what caused the white screen brick?
At the current moment, trying to figure out the issue is not really
feasible. Maybe once I reverse-engineer the firmware in the next part of
this blog series, I'll be able to figure out what the culprit is. I was
hoping to use ards_game_ls
to do error-checking. It went like
this:
While it would make sense to take these error messages seriously, it's also random-access where a lookup table is located somewhere to jump to the correct spot. So if you delete a game from the Action Replay DS cartridge, the memory isn't erased. Yet it can be overwritten. So these errors mean nothing.
In short, I have no idea what broke just yet. For now, we got the codes recovered. The next blog post will be about firmware reverse-engineering as well as flashing to permanently fix the cartridge. So that will be the time to figure out what went wrong.
So, what's next?
Mission accomplished. I recovered my cheats from my bricked Action Replay cartridge and figured out, to some degree, what was wrong with it. So, what's next?
From here on, I will try to get my own cartridge to boot by flashing a working firmware onto it. Whether that requires more hardware or I just write some homebrew to do it will be discussed later on. That will have to make for a second part. Until then, I hope you enjoyed the read. It was a fun and simple challenge.
The programs I showed off earlier can be downloaded at its GitHub repo. You can find that here: ards_tools.