Action Replay Save Format?

slinga

Established Member
I'm interested in supporting reading\writing directly to the Action Replay save data in Save Game Copier. Any documentation on how the data is stored? And how to detect an Action Replay is present? Thanks in advance.
 

Jameson

Gear Supporter
You can detect AR by the bootcode header located at the start of CS0, i.e. 0x22000000. It is almost equivalent to IP.BIN on CD, and should include "ACTION REPLAY" and "DATEL ELECTRONICS" lines within first 256 bytes. Might differ for a new chinese ones.

As for export format, i have an "AR-like" setting in my tool, it was reversed from an existing file, but i don't remember where i got it from. Hope that helps.

1611576094296.png
 

slinga

Established Member
Thanks. I'm thinking maybe I should just add notes on how to dump the raw Action Replay cartridge and tell them to use ss-save-parser.
 

Jameson

Gear Supporter
Oh, did you mean getting saves off the raw cartridge image? I thought it was about AR export tool for PC via DB25 connector (i had one AR like this, but it was lost sadly). I don't know if the tool like this ever existed, but how did i got this file anyway?

For raw cartridge, ss-save-parser can import it, but not export, and the feature is experimental. If you need the code, look at ProbeActionReplay() and ImportActionReplay() functions in import.cpp

Another option is inserting raw image into emulator and copying everything manually to internal backup, it's less convenient, but more reliable i guess.
 

slinga

Established Member
I wanted to add support for copying\writing\deleting saves from the Action Replay same as internal memory works now within SGC. It looks like a pain so...
 

slinga

Established Member
@Jameson,

I had some time to review your code as well as reverse engineer the the Action Replay firmware. I understand the RLE01 algorithm and my results match what I see when I check via the Yabause debugger. I'm stumped on what format the uncompressed data is. In your code you setup a huge ram data field which I don't follow:

Code:
  //add header
    for (int i=0;i<4;i++)
        hugeram->replace(16*i,16,QByteArray("BackUpRam Format"));

Looking at the uncompressed data I can make out the save. The only issue I see is that there seems to be extraneous 0x00s in certain places. Do you know if these are unescaped at some point?

So any advice on how to go from this RLE format to parsing out saves would be appreciated.
 

Jameson

Gear Supporter
@slinga , the code you mentioned simply adds 4 "BackUpRam Format" lines to the beggining of the image. The replace() function is a member of QByteArray class, it replaces a part of array with something else without changing the array size.

Extra 0x00s should not be there after RLE decompressing. Are you sure you followed both RLE01 sequences thru? The first one is 0xKK 0xXX 0xYY and it transforrms into 0xXX copies of the 0xYY, and the second one is 0xKK 0x00, and it transformes to 0xKK. Maybe you missed the second one?
 

slinga

Established Member
The extra zeros are present in the emulator's memory as well. I found the decompressRLE01() function in the cartridge firmware. The src is 0x22020000 and the dest is 0x06020000. The dest in the emulator matches what I have unpacked manually including the extra 0x00s embedded within the save.

Can you explain what the "BackupRam Format" is? I don't see a size field. How do I know where the next save starts? It looks like there is a marker for 0x80 00 00 00 in front of the save files. Can saves be non-contiguous?

From Ghidra's decompiler (minimally cleaned up):
Code:
// 0x002897dc ARP_202C.BIN
int decompressRLE01(unsigned char *src, unsigned char *dest)
{
    unsigned int count = 0;
    int j;
    unsigned int i;
    unsigned int k;

    unsigned char rleKey;
    unsigned char val;

    /*
    i = 0;
    do {
        local_14[i] = src[i];
        i = i + 1;
    } while (i < 5);
    */
    rleKey = src[5];

    unsigned int compressedSize = *(unsigned int*)(unsigned char*)&src[6];
    compressedSize = ntohl(compressedSize);

    printf("size is %x\n", compressedSize);

    j = 0;
    i = 10;
    do {
        if (src[i] == rleKey) {
            count = (int)(char)src[i + 1] & 0xff;
            if (count == 0) {
                if(dest)
                {
                    dest[j] = rleKey;
                }
                i = i + 2;
                goto continue_loop;
            }
            val = src[i + 2];
            i = i + 3;
            k = 0;
            if (count != 0) {
                do {
                    if(dest)
                    {
                        dest[j] = val;
                    }
                    j = j + 1;
                    k = k + 1;
                } while (k < count);
            }
        }
        else {
            if(dest)
            {
                dest[j] = src[i];
            }
            i = i + 1;
            continue_loop:
            j = j + 1;
        }
        if (compressedSize <= i) {
            return j;
            }
    } while( 1 );
}
 

Jameson

Gear Supporter
"BackupRam Format" is a signature sequence that allows BIOS to detect if the media is formatted or not. If there are at least 4 copies of this sequence at the start, the media is considered as formatted. The number of these sequences determines the cluste size (4 = 64 bytes, 16=256 bytes, etc). Thus, the first cluster is always completely filled with these sequences. The second cluster is always unused. Following clusters can be save start clusters (start with 0x8x), save continue or unused (start with 0x0x). The save start cluster includes what i call SAT (analogue to FAT), a table that enumerates all save continue clusters for this save. If the table is big enough, it can extend into the next continue cluster.

So, saves can be very fragmented, and the only way to know if the cluster that starts with 0x00 is unused or continue is to parse the entire image.
 

slinga

Established Member
Thank you, I'm beginning to understand now. The additional 0x00s I'm seeing are most likely save continue headers.
 

slinga

Established Member
The save start cluster includes what i call SAT (analogue to FAT), a table that enumerates all save continue clusters for this save. If the table is big enough, it can extend into the next continue cluster.
Is the SAT guaranteed to be contiguous? Or do I need to look a the SAT while parsing the SAT?
 

Jameson

Gear Supporter
Is the SAT guaranteed to be contiguous? Or do I need to look a the SAT while parsing the SAT?
I can't remember the exact cases of SAT being non-contiguous, but i remember fragmented saves with the second cluster being away from the first one. They were relatively small, so SAT was probably completely in first cluster, but i think nothing prevents SAT from being fragmented. And even if the cluster chain that stores SAT is contiguous, the SAT itself is interrupted by save continue headers in every new cluster.

To be on a safe side, you should look at the SAT while parsing SAT. SimCity2000 saves use 461 blocks , using entire internal backup, they are a good research material for the case. There are even bigger ones, like well-known Tamagotchi park save or Sega Rally ghosts, they can only fit into backup cart.
 
From what I recall when writing my own extractor, the allocation table definitely spans multiple blocks and suffers from fragmentation if the save file is large enough. Here's a comment from my code:
//note: we can have max 111 addresses in the FIRST sector.
// so if a save file is bigger than 111 * 252 = 27972 bytes,
// the sector address table will need multiple sectors by itself!
This refers to extracting data from memory cards where a sector size is 256 bytes (252 = sector size - sector header size). On internal memory you have smaller sectors but I forgot the exact number (64 bytes?)
 

slinga

Established Member
This refers to extracting data from memory cards where a sector size is 256 bytes (252 = sector size - sector header size). On internal memory you have smaller sectors but I forgot the exact number (64 bytes?)

On the Action Replay, after uncompressing the RLE01 compression, the uncompressed data is stored in 64-byte sectors. 60 bytes of data storage after subtracting the 4-byte sector header\tag. The first two sectors are all 0x00 whereas they should normally save "BackupRAM Format". Other then that I believe the uncompressed data should be identical to the internal\cartridge backup memory.

I'm close to finishing (reading, not writing the save), will document my code.
 
Top