Assembly language (ASM) refers to program code that a computer processor can "understand" and interpret as commands to follow out. Encoded assembly language is sometimes called machine code. Different computer architectures have different assembly languages.
Computers don't understand anything but assembly language. So how can you code in other languages like C, C++, VB, etc.? Those code files have to be compiled into an executable file that the computer can understand. Ever wondered was was inside an EXE file? It's ASM. So basically, ASM controls everything every computer ever does. No big deal!
The PSX processor is built on the MIPS architecture, so MIPS assembly language is the one we're looking at. Specifically, the instruction set the PSX processor (MIPS R3000A) can understand. ASM Instructions
Processors operate on registers
, which are small areas of memory inside the processor itself that can be manipulated quickly. The PSX has 32 registers available for general use by the processor (and a few others used behind the scenes).
ASM instructions will typically either modify either registers or memory. I'll explain what various instructions do as we run into them. Here are a few examples:
add r2,r4,r5 # r2 = r4 + r5
or r3,r3,r5 # r3 = r3 | r5 (Bitwise OR)
addi r3,r3,4 # r3 = r3 + 4
lui r4,0x8019 # r4 = 0x80190000 (This instruction loads the 0x8019 into the upper half of the register)
lb r5,0x24cc(r4) # r5 = (Value at memory location 0x801924cc - The first digit is insignificant, so this is memory at 0x1924cc)
sb r5,0x08cc(r4) # Value at memory location 0x801908cc = r5
The registers are simply referred by number (so r2,r3,r4,r5,etc. are all registers). Those familiar with some of the memory locations FFT uses might recognize what's happening in the last example... Formulas!
Let's look at a formula, why don't we?
When we're looking at formulas, we have some memory addresses that tend to be used a lot:
0x80192d94 Caster unit (User of action)
0x80192d98 Target unit
It's also useful to know the offsets of data as they relate to the units, which can be found on the wiki here
. There are also some offsets to the action that change what it does. These are the properties I know about (and I find invaluable as a reference when formula hacking):
0x00: 1 if the action hit, 0 if it missed
0x04: HP Damage
0x06: HP Healing
0x08: MP Damage
0x0A: MP Healing
0x25: Effect flags - these are used for displaying the projected damage
0x01 = Status? (Status proc)
0x10 = MP Healing
0x20 = MP Damage
0x40 = HP Healing
0x80 = HP Damage
These values are what we'll manipulate in the formulas. Example formula : 44 (Difference)
So the Byblos has an ability, Difference, that deals damage equal to the MP of the target. Here's the formula, on which I've commented every line in a literal fashion:
lui r2,0x8019 # r2 = 0x80190000
lw r2,0x2d98(r2) # r2 = Memory value at 0x80192d98
lui r3,0x8019 # r3 = 0x80190000
lw r3,0x2d90(r3) # r3 = Memory value at 0x80192d90
lhu r4,0x002c(r2) # r4 = Memory at r2 + 0x002c
ori r2,r0,0x0080 # r2 = 0x80 (r2 = r0 | 0x80)
sb r2,0x0025(r3) # Memory at r3 + 0x0025 = r2
jr r31 # (Return, but run next command first)
sh r4,0x0004(r3) # Memory at r3 + 0x0004 = r4
So that's the technical idea of what's happening. But what significance does it have?
This is a very common pattern to load in a value from a memory address to a register. The first command loads the upper half of the memory address into a register, then a value is loaded in at that location + the lower half of the memory address as the offset. Note that r2's value is not ready until two commands after this one due to load delay.lw
syntax looks like this: lw (destination_register), (offset)(source_register), where you're loading memory into the destination register at (value of source register) + (offset).
What's the significance of loading this into r2?
The same pattern.
Here, r4 = Memory value at r2 + 0x002c. We know r2 is the target unit, so what is offset 0x2c for a unit? See the formula hacking
page for the unit offsets.
The first command is a way to set r2 = 0x80. Using a command of the pattern ori
(destination_register),r0,(value) is a way to set (destination_register) = (value), since a bitwise OR operation on zero and a value results in the value. You could also use addi
to do this (0 + value = value).
The second command says to save r2 to the memory location at r3 + 0x25. Why might we do that?
is a command that will jump to the address in a register; r31
is the return address. This is a return statement, where we return to the code that called this formula... but yet, we have a statement under it that needs to be run. This is because jump/branch statements are subject to branch delay, which means that the statement after them is run before the jump truly occurs.sh
is like sb
, the only difference being that we save out a 2 byte value instead of a 1 byte value.
lw r2,0x2d98(r2) # r2 = Target
lw r3,0x2d90(r3) # r3 = Action
lhu r4,0x002c(r2) # r4 = Target MP
sb r2,0x0025(r3) # Action type = HP Damage
sh r4,0x0004(r3) # Action HP Damage = Target MP
The comments here are more minimal, but it's a lot easier to understand what's going on here.
So... what if we changed it so that r4 loads in the target HP
? So, change:
to load in HP instead of MP. (look up the unit offset and replace it)
Putting that into MassHexASM we get a little-endian encoded value of 28004494
. That's at RAM address 0x186e64, which is in BATTLE.BIN at 0x11fe64 (subtract 0x67000 to get a BATTLE.BIN address from an appropriate RAM address). We can use FFTorgASM to patch the ISO using a patch like this:
And if I use FFTPatcher to change Ramza's Wish to use formula 0x44...
Next time we'll fiddle with formulas some more.