GCN ActionReplay code format (Unfinished)

Table of Contents

Note: this only covers unencrypted AR codes. Also, GameCube cached RAM is from 0x80000000 to 0x81800000; all addresses within only bother with the bottom 25 bits. In effect, you can view the RAM as extending from 0x0000000 to 0x1800000 for the purposes of AR codes.

This article is intended as either a replacement or supplement for the Dolphin documentation on ActionReplay codes for the GameCube as I find it a little difficult to understand on a read-through. My intention is to create an assembly language and assembler for such codes once I have finished creating this documentation.

Layout

A single ActionReplay code1 is a 64-bit number, with the following layout:

Opcode_Subtype :  2
Opcode_Type    :  3
Data_Size      :  2
Address        : 25
Value          : 32

Data_Size

The Data_Size value represents the actual size of the data being acted on (measured in bytes) as so: size = 1 << Data_Size, or equivalently size = 2^Data_Size where ^ represents exponentation. For all of these codes, 3 is not a valid Data_Size (though it is for Type 0, Subtype 3, where it has a special meaning which isn’t covered here). Further, all addresses must be aligned, that is a multiple of size.

Opcodes

The Opcodes seem inspired by MIPS, at least indirectly through PowerPC which the GameCube’s Gecko processor is based on. I say this because they’re split into two parts (in the Dolphin documentation, referred to as Type and Subtype): the overall opcode, and the specific function covered by that opcode.

Broadly, the types are as follows:

  • 0. Write
  • 1. Equality test
  • 2. Inequality test
  • 3. Signed less than test
  • 4. Signed greater than test
  • 5. Unsigned less than test
  • 6. Unsigned greater than test
  • 7. Bitmask test

Write codes

I’m not covering Subtype 3 here as, to be frank, I don’t really get it. As far as I can tell, it’s used for master codes and not a lot else

The Action Replay code are written in Big-Endian format, and automatically translated to the endianness of the target system. Basically, you can write values as you would on paper (in hexadecimal), rather than having to maybe reverse the bytes.

All write codes have Opcode_Type 0.

0: Immediate write/fill

These codes fill an area of memory with a value. To better describe this, we split up the Value into bytes as in the Dolphin documentation:

Value = Y1 Y2 Y3 Y4

For a byte, Y4 is written (Y1 Y2 Y3) + 1 times, starting at Address.
For a short, Y3Y4 is written (Y1 Y2) + 1 times, starting at Address.
For a long, Y1Y2Y3Y4 is written 1 time, starting at Address.

1: Indirect write

These codes write to a pointer with some offset, like how arrays work in C. As above, we look at the individual bytes of the Value:

Value = Y1 Y2 Y3 Y4

Their behaviours are easiest expressed in C. Note that array indices are done as elements, not bytes.

sometype* array = *Address;

// sometype is 8-bit (byte)
array[Y1Y2Y3] = Y4

// sometype is 16-bit (short)
array[Y1Y2] = Y3Y4

// sometype is 32-bit (long)
array[0] = Y1Y2Y3Y4;

// Equivalently for long
*array = Y1Y2Y3Y4;

2: Addition

Addition, as the name implies, adds some value to an address, rather than overwriting it. There is an oddity in that the value to add is always 32-bit, regardless of the data size being used. However, overflow triggers standard wraparound, so the upper bytes are effectively unused for bytes and shorts.

*Address += Y1Y2Y3Y4

3: ????

I don’t currently understand Subtype 3, so this section is left blank for now. Try the Dolphin documentation if you need master codes, etc, or to read from uncached memory (0xC… instead of 0x8…)

Conditional codes

All the conditional codes work in effectively the same way, so they end up being lumped in together! The basic format is the same: they compare against some given Value, then do… something depending on the result. We’ll get to what that something is, but for now, here are the conditions you can check, corresponding to values of Opcode_Type:

Condition Opcode_Type
Equal 1
Not Equal 2
Less than (signed)2 3
More than (signed)2 4
Less than (unsigned) 5
More than (unsigned) 6
Mask != 0 7

Of these, I feel that only Mask needs explaining, or at least a better concise name. Mask means to AND the value at the given address with Value, effectively masking the bits we don’t care about. For example, we could check if a number is negative by masking it with 0x1000 0000, though perhaps “Less than (signed)” would be more understandable.

Now for the Opcode_Subtypes, and the actions they cause:

Action Opcode_Subtype
Skip next line 0
Skip next two lines 1
Skip rest of code 2
Wait until true 3

Yeah, conditions aren’t the easiest to work with conceptually while designing a code; nesting them or including an “else” is even harder. So lucky you, there’s an “if then else” example later on.

“Type Z” Codes

“Type Z” codes, as they are known in the Dolphin documentation, are special cases that don’t match the pattern of the other codes. The first 4 bytes are always zero.

Execution modes

There are three known execution mode commands:

  • End of Execution – return control to the game, then resume executing the code from the beginning. Code 00000000 00000000
  • Unlock – execute codes normally, where it interleaves code execution with the game’s code. Code 00000000 40000000
  • Lock – execute codes atomically, not giving control back to the game at all. Code 00000000 60000000

Fill with Increment

This is the only known two-line code. Its structure looks somewhat like this:

(zeros=0)       : 32
(code_type=8)   :  4
(zero=0)        :  1
size            :  2
address         : 25
----
value           : 32
value_increment : 8
write_count     : 8
addr_increment  : 16
  • size and address are the same as in the rest of the document
  • I’d hope that value is self-explanatory
  • value_increment is an 8-bit signed number. Each step, this is added to the value.
  • addr_increment is a 16-bit signed number. Each step, (2^size * addr_increment) is added to the address. In other words, addr_increment is measured by size of the value being written rather than bytes.
  • Contrary to the Dolphin documentation, the value increment appears to have no effect on the sign of the address increment.

Worked Example

I’m going to give an example here: “if *0x030bb4 == 28 then write 1 to 0x030bb8 else write 2 to 0x030bb0“.

I want to work with 4-byte integers, so I need to remember to use Data_Size 2. I’ve decided to implement it like this:

Opcode_Subtype Opcode_Type Data_Size
If *0x030bb4 == 28 Then Skip Two Skip Two (0b01) Equal (0b001) 32-bit (0b10)
Write 2 to 0x030bb0 Immediate (0b02) Write (0b000) 32-bit (0b10)
End Execution N/A N/A N/A
Write 1 to 0x030bb8 Immediate (0b02) Write (0b000) 32-bit (0b10)

End Execution is a Type Z code, which don’t have the standard byte pattern, and so is skipped here. Between Opcode_Subtype, Opcode_Type and Data_Size, we have the first 7 bits; the last bit of the first byte is the top bit (bit 25) of the address, here 0, giving us the first byte of each line:

0100 1100   =   4C
0000 0100   =   04
(End Execution skipped)
0000 0100   =   04

Now just append the other 24 bits of the address to get the first word:

4C030BB4
04030BB0
(End Execution skipped)
04030BB8

As for the rest, it’s just the value filling the rest of the word:

4C030BB4 0000001C
04030BB0 00000002
00000000 00000000
04030BB8 00000001

Et voilà! A simple-ish Action Replay code!


  1. Excluding the so-called “Type Z Codes”, where the first 32-bits are all zero. Their structure is covered separately
  2. The 1-byte size acts as if it were unsigned. This may be an action replay bug. 
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s