Action Replay/GameShark Pro v3.2 Communications Protocol
by Russ K. (x87bliss[nospam]yahoo.com)
Document version 1.0 - 6/19/07

Contents:
1. Preface
2. What You Need
3. Protocol Foundation
    3a. Code Example
4. Actual Commands!
    4a. Reset Comms
    4b. Enter
    4c. Exit
    4d. Get Version
    4e. Read RAM
    4f. Write RAM
    4g. Add Code
    4h. Delete Code
    4i. Count Codes
5. Memory Card Commands!
    5a. Read MC FAT
    5b. Read File
    5c. Write File
    5d. Delete File
    5e. Format Memory Card
6. PSX Memory Map
7. An Easier Way to Manage the Active Codes!
8. Special Thanks

1. Preface

    As the title suggests, this document is for the Action Replay/GameShark Pro v3.2 - hereafter referred to as GS32. Some of these commands will work with v3.0 and v3.1; I have not tested them but I will try to note which ones work on all versions. For instance, the commands to read/write the memory cards I could only get to work on my GS32; they did not work on my GS30. I didn't do much testing at all with the GS30 because sadly it is crippled - I thought it would be a good idea to delete every cheat code, and now it freezes when you go to the list (probably a buffer overflow due to a null set). If anyone knows how to fix this without a mod-chip, please feel free to send an e-mail! Also, please bear with me - this is my first protocol document ever. My code examples are not checked for compilation, and are only meant as a rough guide; they are C/C++ but you can probably convert them into whatever language you know by just following what's going on. If you have anything you'd like to add, like commands in the stock Action Replay that I missed, or error corrections, please feel free to send me an e-mail with a subject starting with GS32. This is made partially from an formerly existing partial document that was in Japanese. I do not read Japanese, and the document itself was incomplete. I make mention of it in the Special Thanks section. I can not guarantee that my document is correct, nor does it cover everything; but I try my best. :-)

2. What You Need

3. Protocol Foundation

    Actual data is sent to and from the GS32 4-bits at a time in 8-bit packets. The least significant 4-bits contain the data. To send a byte of actual data, like the character 'G' (0x47), you would first send 4, then 7. Likewise, in receiving the character 'G', you would first get 4, then 7. If you are confused, I will provide some code example a little further to hopefully help clear it up some. All communication is done in exchanges, first you send an 8-bit packet, then you can receive an 8-bit packet. If you only intend to receive data, you send a packet of 0 (the 4 most significant bits of the packet must still be valid - not 0).
    All this talk about the 8-bit packets and you probably have no idea what I'm talking about. I will now attempt to describe the format of these packets. In an outgoing packet the 4 most significant bits are always 0001 - followed by the 4 data bits - pretty easy. Incoming packets are even easier! The first 4 bits are the data bits - with the most significant bit inversed (XORed).
    All data is sent through the computers base I/O address of the parallel port (0x378 on most), and received through the base address + 1 (0x379). You can only send to the GS32 when you receive a byte and the 4th bit (0x08) is not set. If it is set, you can send 0x00 (just zero, no packet) once to tell the GS32 to clear the flag; then keep checking for the bit to clear. The GS32 will set that 4th bit when it wants to send a byte. If you are expecting a byte, only process it if this bit is set. This can be done over and over until the bit is set; or if you program a timeout in case of a failed connection. The exchanges must take place as follows; wait until GS32 is ready, send a byte of data (the outgoing packet), wait until GS32 is ready, recieve a byte of data (the incoming packet), and finally send 0x00 (just zero, no packet).

3a. Code Example:
short portno; // to store the base port address, global in this case - preferably part of a class
char inp(short port) {
    char value;
    __asm {
        mov dx, port
        in al, dx
        mov value, al
    };
    return value;
}

char outp(short port, char value) {
    __asm {
        mov dx, port
        mov al, value
        out dx, al
    };
}

void InitPort(short port) { // This only needs to be called once; it's to get the communications ready on the PC end
    portno = port;
    outp(portno + 2, 0);
    outp(portno, 0);
}

short Send(char packet) { // Use this to send an outgoing packet - you should also include a timeout to limit how many times it checks if GS32 is ready before it quits and returns an error value
    if (inp(portno + 1) & 0x08 != 0) { // The 4-bit flag is set; GS32 is not ready to receive data
        outp(portno, 0); // send just 0 to tell GS32 I wanna send!!
        while ((inp(CommsPort + 1) & 0x08) != 0); //wait for GS32 to say OK!
    }
    outp(portno, packet);
    return 0; // 0 for success, you can return -1 above if your loop times out
}

short Recieve() {
    char packet;
    while (inp(portno + 1) & 0x08 == 0); // wait for the 4-bit flag to set. There is nothing we can do to encourage it, just wait for it :) -- also it is a good idea to limit the amount of times it checks
    packet = inp(portno +1);
    return packet; // You can return -1 if your loop above times out; since the return value is short, and the packet is a char you will be able to tell the difference between a -1 packet and a -1 error
}

short ExchangeByte(char data) { // This function will do the exchange mentioned above. It will send 'data' (in two packets), then receive a byte (two packets) which it will return
    char recieved = 0; // used to recieve packets
    char ret = 0; // used to translate the packets into a byte to return

    char packet = (data >> 4) & 0x0f; // Copy the 4 most significant bits of data to the 4 least significant bits of packet; & 0x0f is redundant to make sure we only have 4-bits
    packet |= 0x10; // Set the 5th bit to finish making the packet
    Send(packet); // you can check if -1 returns here (i.e. Send timed out)
    recieved = Recieve(); // again you can check for a timeout
    ret = (recieved ^ 0x80) & 0xF0; // Swap the most significant bit and keep the 4 most significant bits (we don't need to move them since this is the first packet -- you'll see below we do
    outp(portno, 0); // Now just send 0, to let GS32 know we got the packet

    //Now we do it agian for the 4 least significant bits of data
    packet = (data) & 0x0f; // Now we need to send the 4 least significant bits
    packet |= 0x10;
    Send(packet);
    recieved = Recieve();
    ret |= ((recieved ^ 0x80) >> 4) & 0x0F; // Now we swap the bit of the packet; move the results to the right 4 bits (to make the actual data in position for the least significant bits); and OR it into ret

    return ret; // now return what we got -- or -1 if we got a time out error
}

short SendCommand(char cmd) { // This is used to actually summon each command; its a header that all commands except Enter have in common. You'll learn more of this in the next section
    char rec = 0;
    rec = ExchangeByte('G');
    if (rec != 'g') return -1; // You can also modify this function to try a couple of times before returning.
    rec = ExchangeByte('T');
    if (rec != 't') return -1; // Again, if you modified it to try a couple of times, don't send 'T' unless you got 'g' before. If you got 'g' but not 't', start again at sending 'G'.
    return ExchangeByte(cmd);
}

4. Actual Commands!

    Wow! I didn't lose you yet! ...I hope. Now that we know how to communicate with GS32, we need to learn just what it can do! All commands require you to Enter. When you Exit, it will return a status of whether the PSX is at the GameShark Menu or actually in game. If a command requires that you are in game, you Enter, Exit and check to see that it says it's in game, then Enter again, then do the command, then Exit. When you Enter it pauses execution on the PSX. This is useful to keep memory from being changed as you're reading it. It is also inevitable; you can't just read memory while the game is running. This prevents you from effectively synchronizing the PSX RAM with a hex-edit view; you can either allow the game to continue execution, or read it's memory. You can set up an interval where you let the game play a little bit, then read a little bit. This will cause stuttering if you read too much too often. It is not a problem at all for dumping; since you don't need the game to run during dumping. Writing is also the same - you can't write RAM while the game is running. For simplicity the following commands will not include much if any error checking. You can add it easily if you want; I will mention any important return values.
    There are commands that read and write the code list (the ones that are saved for each game) that I did not include. I have not figured them out right just yet. Hopefully soon I will!

4a. Reset Comms
    You need to reset the communications if you keep getting errors. It is also a good way to check that you can send data to the GS32 correctly. Go to the GameShark menu and hold Select+Square until a "Comms" screen appears. Now you can use the Enter command and Exit; this should return the GameShark to the menu. You can also use the Check Version command in between that Enter and Exit to test for correct data transfer. The purpose for this whole procedure it to synchronize the data transfer.

4b. Enter
   
As described above Enter is used to pause execution of code and allow GS32 to do commands. There are two slightly different ways of doing this; the GS30/31 way and the GS32 way. They are close enough that you can actually do both in one function. GS32 Enter is basically GS30 Enter 2x; you'll see below. Enter is the only command where we will have to do one packet at a time and it's not preceded by a call to SendCommand. All the other commands we can use the ExchangeByte function to do one actual data byte at a time.
 
1 Send Packet for 0x03 (i.e. the byte 0x13) This can be done directly; you don't need to wait for any bits to be set/clear
2 rec = Receive() This will tell us if AR3 got the message
3 outp(port, 0) Send 0 saying, we recieved!
4 rec ^= 0x80
rec = (rec >> 4) & 0x0F
Translate the recieved packet to the 4 dat bytes
5 check rec 7 = good
6 = GS32, goto step 1 and then we should get 7 next time
else just try again

4c. Exit
    This command is to resume execution of the PSX game. You don't need to call this after each other command - i.e. if you need to Read, Write, then Read again you can Enter, Read, Write, Read, then Exit.
1 SendCommand(0x65) Send the Exit command
2 rec = ExchangeByte(0) 1 if GS32 is at the menu
2 if PSX is in game

4d. Get Version
    This command gets the version information of the GS32. This command must be done at the GS32 menu.
1 SendCommand(0x66) Send the command
2 rec = ExchangeByte(0) repeat 3x to get 3 bytes of numerical version info
3 rec = ExchangeByte(0) This byte will return the length of a version string not including null terminator
4 rec = ExchangeByte(0) repeat for each character of the string; you will then have to add the null terminator

4e. Read RAM
    Finally, some actually useful commands! This must be done in game.
1 SendCommand(0x01) Send the command
2 ExchangeByte(address) Ok, here we send the address we want to read. The address is 4 bytes. We have to send it one byte at a time Big-Endian (Most Significant Byte first, LSB last)
See Contents for memory map info.
3 ExchangeByte(0) Do this 2x
4 ExchangeByte(size) size is the size of RAM you want to read. It's a 2 byte value, again sent in Big-Endian format. I don't know what the limit is on how much you can read at once, but you can always send the command multiple times for large amounts of data.
5 rec = ExchangeByte(0)
sum += rec
Do this for each byte of size (a loop)
At the same time keep a sum of the bytes received. Do this with unsigned chars (so that no subtraction occurs)
6 ExchangeByte(0) Do this 8 times
7 rec = ExchangeByte(0) This will get the sum of the bytes as reported by GS32
8 compare rec and sum They should be equal; if not you'll have to read that section of RAM again.

4f. Write RAM
    Very similar to Read RAM. This must be done in game.
1 SendCommand(0x02) Send the command
2 ExchangeByte(address) Ok, here we send the address we want to write. The address is 4 bytes. We have to send it one byte at a time Big-Endian (Most Significant Byte first, LSB last)
See Contents for memory map info.
3 ExchangeByte(0) Do this 2x
4 ExchangeByte(size) size is the size of RAM you want to write. It's a 2 byte value, again sent in Big-Endian format. I don't know what the limit is on how much you can write at once, but you can always send the command multiple times for large amounts of data.
5 ExchangeByte(data[i])
sum += data[i]
Do this for each byte of size (a loop)
At the same time keep a sum of the bytes sent. Do this with unsigned chars (so that no subtraction occurs)
6 ExchangeByte(0) Do this 8 times
7 rec = ExchangeByte(0) This will get the sum of the bytes as reported by GS32
8 compare rec and sum They should be equal; if not you'll have to write that section of RAM again.

4g. Add Code
    Use this command to add an active code. GS32 keeps two sets of active codes; the ones turned on from the Main Menu, and the ones turned on using the built-in code finder. This command adds the code to the list from the built-in code finder. If you need to have two adjacent codes (i.e. D0012345 0000, 80012345 0001) send them in that order. Look in the contents for An Easier Way to Manage the Active Codes. Also note you can only have a maximum of 40 active codes in this list. This must be done in game.
1 SendCommand(0x69) Send the command
2 ExchangeByte(address) Send the address part of the code here, 1 byte at a time. Again, Big-Endian.
3 ExchangeByte(0) Do this 2x
4 ExchangeByte(value) Send the value part of the code here, 1 byte at a time. Also Big-Endian. 2 bytes.

4h. Delete Code
    This command will delete an active code from the built-in code finder's list. This must be done in game.
1 SendCommand(0x6B) Send the command
2 ExchangeByte(address) Send the address part of the code here, 1 byte at a time. Again, Big-Endian.

4i. Count Codes
    This returns the number of active codes in the built-in code finder's list. This must be done in game.
1 SendCommand(0x6A) Send the command
2 ret = ExchangeByte(0) ret = the number of active codes

5. Memory Card Commands!

    These commands are awesome enough, and took me enough time to figure out, that they deserve their own section! They all must be done from the GameShark Menu off of a Cold Boot. If you are playing a game and you go back to the GameShark by pressing Reset, it will not be able to see the Memory Cards. I don't know why that is, but trust me it's annoying. As some of you may know the PSX Memory Card has a 16th (really 1st) block that stores a basic sort of FAT. You don't have to do any direct interaction with this block. :-D However, you do have to tell GS32 to read this block before you can do any of the memory card commands, and once after any command that would modify it (all except reading a file).
    These commands only seem to work on the GS32, NOT GS30 and probably not GS31. Also, unfortunately these commands do NOT work on the V-Mem of GS32. You can go into the V-Mem manager, and press Select; this will reset the PSX so you can use it's Memory Card manager to copy files from the V-Mem to a physical Memory Card. Then Power Off then On the PSX and then you can use these commands. They probably will not work if you have Memory Cards attached to a multi-tap either.
    There are a couple of steps in the various Memory Card commands below where I suggest you keep trying without a timeout. This is because they will succeed when GS32 is done reading/writing the Memory Card; which takes a few seconds just like in the games. Realistically if you can calibrate a timeout for about 15 seconds, it should be pretty alright. With all of these commands, you must first use Enter, then just use the command; you should not have to use Exit. Once the last byte is exchanged for the command GS32 auto-exits. If GS32 remains frozen, you can try to use the Exit command. Programmatically, after you think everything is sent and done, you can try to ExchangeByte(0) with a timeout and see if you get anything back. You should timeout, otherwise it means GS32 still wants to send/recieve data. You can just keep exchanging 0 until you do timeout, then give an error message like "Possible Loss of Data - try again!"

5a. Read MC FAT
   
This is the command that makes GS32 read the Memory Card's FAT. This is the one you have to call before you do any commands, and once after each command that would modify it. If you fail to call this command first, and once after each time you write to the card, you WILL get corrupted data. GS32 is not smart enough to do this by itself - small drawback to the possibilities of using you HDD as a huge PSX Memory Card.
1 SendCommand(0x74) Send the command
2 ExchangeByte(cardnum) cardnum = 0 for Memory Card 1, or 1 for Memory Card 2. Probably won't work with a multi-tap; I wouldn't try it.
3 ret = ExchangeByte(0) This you should circumvent any timeouts you put into send and receive, or send it over and over until it doesn't timeout. The reason is GS32 won't respond until it is done reading the Memory Card.
ret = 0 good; 0xFF means no Memory Card
4 ExchangeByte(0x01) Always send 0x01.
5 ExchangeByte(0) Again, no timeouts! This will finally return the first byte of the file list when GS32 is ready. Then resend 0 over and over for each byte of the file list until it's done. Below I describe the protocol of the file list
  File List Protocol  
1 byte moredata This is a sort of boolean, 0 or 1 value. If it is 1 then there is data for another file, continue reading. If it is 0, there is no more data, stop reading. If this is the first byte you recieve and it's 0, that means the card is empty!
2 char filename[20] Always read 20 bytes for the filename. If the filename is less than 20 bytes, it will be null terminated - still keep reading the rest. If it is 20 bytes, it will occupy the whole array. You may want to use a 21 byte array here, so you can add a null terminator at filename[20]
3 byte null This is not used, and may always be zero. You may even be able to use it as filename[20] as a null terminator. But I'm not sure that it's always zero.
4 2byte filesize This gets the filesize in bytes (not blocks). Each block is 8192 bytes; so this should be a multiple of that. This value is Little-Endian.
5 byte null Again this is not used, and may always be zero. It is possible that it is actually Big-Endian end of the previous value. Hard to tell since the LSB of filesize is always zero in multiples of 8192 (0x2000). Doesn't matter.
6 that's it This repeats for each file on the card. You stop once moredata = 0.

5b. Read File
    This command reads a file from the Memory Card and gives you its raw data.
1 SendCommand(0x73) Send the command
2 ExchangeByte(filename[20]) Send the filename one byte at a time for 20 bytes. If the filename is < 20 bytes, continue sending 0 until you have sent 20 bytes.
3 ExchangeByte(cardnum) cardnum = 0 for Memory Card 1, or 1 for Memory Card 2. Probably won't work with a multi-tap; I wouldn't try it.
4 ExchangeByte(0)  
5 ExchangeByte(filesize) filesize = size in bytes of the requested save file. Should be a multiple of 8192; the size of a block. Little-Endian.
6 ExchangeByte(0) Again, it is possible that it is actually Big-Endian end of the previous value. Hard to tell since the LSB of filesize is always zero in multiples of 8192 (0x2000). Doesn't matter.
7 data = ExchangeByte(0) No timeouts here either. It'll return a byte when it's done reading the Memory Card, then it will return the first byte of the file data. Keep exchanging 0 until you get the whole file. This is the raw data of the save; no header. If you save this to your computer, you should insert a header to at least save filename.

5c. Write File
    This command writes the raw data of a save to the Memory Card. The filename is important that you get the original name right. That is why I recommend you make a header for when you Read File, to store the original filename. You can also learn other save file formats (like Dex-Drive, etc...) and read the data from them. This command will overwrite or create the file if it doesn't exist. I have not tried to overfill a memory card; so I don't know where you will get the error or what the return will be.
1 SendCommand(0x78) Send the command
2 ExchangeByte(filename[20]) Same as Read File
3 ExchangeByte(cardnum) Again, same as Read File
4 ExchangeByte(0)  
5 ExchangeByte(filesize) filesize = size in bytes of the requested save file. Should be a multiple of 8192; the size of a block. Little-Endian.
6 ExchangeByte(0)  
7 ExchangeByte(data) Send the raw data 1 byte at a time. Do this until you've sent filesize bytes.
8 ExchangeByte(0) No timeouts again! This is just waiting for GS32 to finish actually writing the file. It will return when it's done.

5d. Delete File
    This command deletes a file from the Memory Card. I have not tried deleting a file that does not exist, so I don't know what the return value will be; or if it will just delete the first file it finds (I doubt it).
1 SendCommand(0x79) Send the command
2 ExchangeByte(filename[20]) Same again, send the 20 bytes of the filename. (See Read File for more info)
3 ExchangeByte(cardnum) Again, same as Read File
4 rec = ExchangeByte(0) No timeouts again1 This is just waiting for GS32 to come back and say "I deleted it!" or maybe "I couldn't find it!" I don't know what rec will be - never played with it.

5e. Format Memory Card
        This is a command that you may want to use caution with! Not that I ever had anything go wrong, but whenever I format anything it usually ends with a slap on the forehead "Doh! I forgot to backup that file!"
1 SendCommand(0x7A) Send the command
2 ExchangeByte(cardnum) Same as Read File
3 ExchangeByte(0) Again, do this with no timeout. GS32 will return a value when it's done. And it'll say "Hey! I done formatted da memry curd!" Or something else. Didn't test for possible return values.

6. PSX Memory Map

    This is a basic chart to show you what memory you can read/write.
0x00000000 to
0x0000FFFF
Kernel (Physical) Don't know if you can read and write the kernel, never tried. If you can, you have to do it at the 0x80000000 copy.
0x00010000 to
0x001FFFFF
Game Memory (Physical) This is where most of your reading and writing takes place. I don't know if you can do it at this physical address though. You should do it at the 0x8001000 copy.
0x1F000000 to
0x1F05FFFF
GS32 Memory (Physical) You can read and write this at the 0x9F000000 copy. I don't know how careful you have to be. I don't know if there's anything you can write that'll permanently damage things.
0x1F800000 to
0x1F8003FF
Scratch Pad (Physical) I am pretty sure you can read and write this at the 0x9F800000 copy.
0x80000000 + Physical AddressCached Copy This is where you can do the reading and writing of the above addresses. By adding 0x80000000 to it, you are accessing a cached copy instead of the direct physical address.
0xA0000000 + Physical AddressUncached Copy I never did any reading or writing here. It may be possible. I don't know what the real difference would be.
0xBFC00000 to
0xBFC7FFFF
Bios You may be able to read this; definitely shouldn't try to write it - I doubt it's even possible. I've never tried playing with this at all. I think this is what people copy to use in emulators.

7. An Easier Way to Manage the Active Codes

    As I mentioned in the section of commands for adding, deleting, and counting the active codes, there is an easier way! You can even read and write the RAM of GS32 itself! GS32's RAM is addressed at 0x1F000000 in the PSX; however, we can't access it directly. You can access it at the cached copy 0x9F000000 - and it's just as if you accessed it directly. You want to be careful in here that you don't modify something you shouldn't! I think it's just RAM so a reset should fix any problems if you do mess up. Below is a table of important addresses! Note they may be the same for Action Replay, but I found these on GameShark - you may want to double check by reading them, before you write to them!
0x9F040004 (GS30)
?? (GS31)
0x9F040004 (GS32)
1Byte - CodesOff 0 when Codes are On
1 when Codes are Off
This affects all codes; the ones activated at the menu, and the ones activated in the code-finder.
0x9F04015C (GS30)
?? (GS31)
0x9F040160 (GS32)
4Byte - NumCodes (Little Endian)
4Byte - 0x00000000
-- Repeat Below for Each Code --
4Byte - Address (Little Endian)
2Byte - Value (Little Endian)
2Byte - 0x0000
The names here are pretty obvious. You can have up to 40 codes here, I believe. This is the list of codes in the code-finder. If you need a D0 then an 80 code, or similar. Do it in order D0 first, then 80 next; left-to-right
0x9F040C38 (GS30)
?? (GS31)
?? (GS32)
4Byte - NumCodes (Little Endian)
-- Repeat Below for Each Code --
4Byte - Address (Little Endian)
2Byte - Value (Little Endian)
2Byte - 0x0000
Similar to above, except it doesn't have 0x00000000 after NumCodes. This is the list of codes activated from the menu (not code-finder). I did not play with this much, so I didn't bother finding the address in GS32. I don't know how many codes you can put here. To find the address for GS32 or GS31 just turn on some codes, dump from 0x9F000000 to 9F05FFFF. And search for the code list in that dump.

8. Special Thanks