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
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. :-)
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);
}
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 |
1 | SendCommand(0x65) | Send the Exit command |
2 | rec = ExchangeByte(0) | 1 if GS32 is at the menu 2 if PSX is in game |
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 |
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. |
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. |
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. |
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. |
1 | SendCommand(0x6A) | Send the command |
2 | ret = ExchangeByte(0) | ret = the number of active codes |
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. |
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. |
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. |
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. |
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. |
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 Address | Cached 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 Address | Uncached 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. |
Hanimar - I found some tools with source at his page that got me started with the GS32 protocol. Enter, Exit, Read RAM, Write RAM, and Get Version are mostly from there. http://www.geocities.com/SiliconValley/Station/8269/
THUMP! INDIES SOFTWARE - At this page I found (in japanese - with some ok English) the Add Code, Delete Code and Count Codes commands. http://www.thump.nu/PAR3ANA.HTM(dead)
Hitmen - They host the site where this is at! (Hopefully that's where you're reading this from.) http://hitmen.c02.at/
Joshua Walker - His PSX Documentation Project is where I got the Memory Map information from. Check his document for a better more detailed version.
Datel - I guess I can thank them for making GS32. It would be nice if they released an official protocol document though.