FFHacktics
News
About

Downloads
Tools
Patches
Custom Sprites

Community
Forum
Contests
Mibbit (IRC)
FFH's Discord irc.ffhacktics.com #FFH

Resources
Wiki
Maps
Effects
Tutorials
Sprites
Item Palettes
Event Instructions


Affiliates
FFVI Hacking
Graphics Gale
Beginner tutorial to ASM hacking *in progress* (written by: Zodiac )

You may have heard the MIPS assembly language is hard to learn. Or perhaps it seemed hard to you. Well this is completely false. Learning MIPS assembly is easy, putting it in practice whoever might be hard for a lot of people who have an undeveloped logical mind.


HEXADECIMAL AND BINARY

Now let’s start with Hex.
Hex is a base 16 number system while Decimal is base 10.

While Decimal contains:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9

Hex has 6 more:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F

Conversion table
BinaryHexDec BinaryHexDecBinaryHexDecBinaryHexDec
000000000x000010000000x4064100000000x80128110000000xC0192
000000010x011010000010x4165100000010x81129110000010xC1193
000000100x022010000100x4266100000100x82130110000100xC2194
000000110x033010000110x4367100000110x83131110000110xC3195
000001000x044010001000x4468100001000x84132110001000xC4196
000001010x055010001010x4569100001010x85133110001010xC5197
000001100x066010001100x4670100001100x86134110001100xC6198
000001110x077010001110x4771100001110x87135110001110xC7199
000010000x088010010000x4872100010000x88136110010000xC8200
000010010x099010010010x4973100010010x89137110010010xC9201
000010100x0A10010010100x4A74100010100x8A138110010100xCA202
000010110x0B11010010110x4B75100010110x8B139110010110xCB203
000011000x0C12010011000x4C76100011000x8C140110011000xCC204
000011010x0D13010011010x4D77100011010x8D141110011010xCD205
000011100x0E14010011100x4E78100011100x8E142110011100xCE206
000011110x0F15010011110x4F79100011110x8F143110011110xCF207
000100000x1016010100000x5080100100000x90144110100000xD0208
000100010x1117010100010x5181100100010x91145110100010xD1209
000100100x1218010100100x5282100100100x92146110100100xD2210
000100110x1319010100110x5383100100110x93147110100110xD3211
000101000x1420010101000x5484100101000x94148110101000xD4212
000101010x1521010101010x5585100101010x95149110101010xD5213
000101100x1622010101100x5686100101100x96150110101100xD6214
000101110x1723010101110x5787100101110x97151110101110xD7215
000110000x1824010110000x5888100110000x98152110110000xD8216
000110010x1925010110010x5989100110010x99153110110010xD9217
000110100x1A26010110100x5A90100110100x9A154110110100xDA218
000110110x1B27010110110x5B91100110110x9B155110110110xDB219
000111000x1C28010111000x5C92100111000x9C156110111000xDC220
000111010x1D29010111010x5D93100111010x9D157110111010xDD221
000111100x1E30010111100x5E94100111100x9E158110111100xDE222
000111110x1F31010111110x5F95100111110x9F159110111110xDF223
001000000x2032011000000x6096101000000xA0160111000000xE0224
001000010x2133011000010x6197101000010xA1161111000010xE1225
001000100x2234011000100x6298101000100xA2162111000100xE2226
001000110x2335011000110x6399101000110xA3163111000110xE3227
001001000x2436011001000x64100101001000xA4164111001000xE4228
001001010x2537011001010x65101101001010xA5165111001010xE5229
001001100x2638011001100x66102101001100xA6166111001100xE6230
001001110x2739011001110x67103101001110xA7167111001110xE7231
001010000x2840011010000x68104101010000xA8168111010000xE8232
001010010x2941011010010x69105101010010xA9169111010010xE9233
001010100x2A42011010100x6A106101010100xAA170111010100xEA234
001010110x2B43011010110x6B107101010110xAB171111010110xEB235
001011000x2C44011011000x6C108101011000xAC172111011000xEC236
001011010x2D45011011010x6D109101011010xAD173111011010xED237
001011100x2E46011011100x6E110101011100xAE174111011100xEE238
001011110x2F47011011110x6F111101011110xAF175111011110xEF239
001100000x3048011100000x70112101100000xB0176111100000xF0240
001100010x3149011100010x71113101100010xB1177111100010xF1241
001100100x3250011100100x72114101100100xB2178111100100xF2242
001100110x3351011100110x73115101100110xB3179111100110xF3243
001101000x3452011101000x74116101101000xB4180111101000xF4244
001101010x3553011101010x75117101101010xB5181111101010xF5245
001101100x3654011101100x76118101101100xB6182111101100xF6246
001101110x3755011101110x77119101101110xB7183111101110xF7247
001110000x3856011110000x78120101110000xB8184111110000xF8248
001110010x3957011110010x79121101110010xB9185111110010xF9249
001110100x3A58011110100x7A122101110100xBA186111110100xFA250
001110110x3B59011110110x7B123101110110xBB187111110110xFB251
001111000x3C60011111000x7C124101111000xBC188111111000xFC252
001111010x3D61011111010x7D125101111010xBD189111111010xFD253
001111100x3E62011111100x7E126101111100xBE190111111100xFE254
001111110x3F63011111110x7F127101111110xBF191111111110xFF255


You can be sure a number is in hex when it is preceded by 0x, or $ which are commonly used in programming communities:
0xFF, 0x024E, $A2

The most commonly used format in hex is a byte.
A byte is composed of 2 nibbles. Those nibbles are simply a character from 0 to F.
Those nibbles are composed of bits. While bits and Boolean are stored the exact same way, we use the term Boolean for a value that is either TRUE (1) or FALSE (0) and a bit is simply 1 or 0.

Value types
Range of valuesTypeEquivalent
0-1Boolean/Bit-
0x0-0xFNibble4 bits
0x00-0xFFByte8 bits
0x0000-0xFFFFHalf-Word2 bytes
0x00000000-0xFFFFFFFFWord4 bytes
0x0000000000000000-0xFFFFFFFFFFFFFFFFDouble-Word2 words


There are others, but that’s not important for PSX hacking.

An important note about the Little Endian (the data format the PSX uses) is that the bytes are reversed. What this means is that the last byte in a value becomes the first. Note that this strictly depends of the value type; hence how the value is read/written. Note that the following examples show the stored values in hex display without 0x or $, which is because those characters would not only be confusing but would also take a lot of space. Whoever in person to person written conversations, 0x or $ should ALWAYS be used to avoid confusion with a decimal value.

Hex storage
Hexadecimal ValueIs stored as
0x7878
0x011F1F 01
0x8005026363 02 05 80
0x00000002846A050707 05 6A 84 02 00 00 00


Usually the game also reads/write bits this way.

The binary 10000000 represent 0x80 in hex. This “1” will be the first bit to be loaded. In order it loads the value 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02 and finally 0x01.

Booleans/Flags
0FALSE
1TRUE


Let’s do a little hex conversion practice to help you understand better:

Example
0x02A7 to decimal (hex is base 16)
$00 × 163 = 0 × 4096 = 0
$02 × 162 = 2 × 256 = 512
$0A × 161 = 10 × 16 = 160
$07 × 160 = 7 × 1 = 7
0 + 512 + 160 + 7 = 679
0x02A7 = 679


STATIC VS DYNAMIC

A static value doesn’t change, it is meant to be only loaded. All opcodes (which you will see later) are static. You COULD make some opcodes dynamic but this is seriously asking for trouble unless you are a high level hacker, perfectly sure of what you’re doing and know it will use less space (if that is even possible) than conventional coding.
Examples of static data: the WP of a weapon, the HPM of a job, the coordinates of a move-find item, etc.

A dynamic value is meant to be changed; therefore a new value will overwrite the previous one.
Examples of dynamic data:
HP of a unit, Max HP of a unit (base max HP calculated, different units, level up, etc.), total gil amount, etc.

A memory editor will help you finding dynamic data only. If you search for static data you’re better off using a simple hex editor. Using the memory editor you scan a first time inputting what you know of the value, change the value in game, and make another search with the known modifications to the value.
To search for static data, you can either guess what the data is for, or debug using breakpoints on the data.


SIGNED AND UNSIGNED

A signed variable can have half of its values positive, and the other half negative. How do we know if a value is signed or not? This all depends on how the value is read/written by opcodes.
Let me show you can example:
0x7F (maximum positive value of a signed byte) = 127
Nothing changed here. However:
0xFF = -1
Now I’ve got you confused. You see, while 0x7F is the highest positive value, the value right after which is 0x80 is the lowest negative value.
0x7F = +127
0x80 = -128

An unsigned byte (which is a normal byte) can have values from 0-255 (x00-xFF).

Let’s to it with 0x9B
255+1 =256
0x9B – 256 = 155 – 256 = -101
a signed 0x9B = -101, an unsigned 0x9B = 155
Remember that the value will only be negative if it exceeds half of its maximum value.

Of course, the same applies to other variables, except bits.

Sign table
ValueUnsignedSigned
0x70112+112
0x9F159-97
0x03E7999+999
0xFFB265458-78
0x000000FE254+254
0x801968042149148676-2145818620



OPCODES

Opcodes are the instructions executed by the console’s processor. Since we started with hexadecimal and binary, we’ll look into the bitwise operands first.
Opcodes will read/write registers depending on what it is supposed to do. Mainly we will work with conditional jumps (branches), jumps, load/write value (from/to memory), and everything else that alters a register’s value.


Many instructions have an immediate form, such as addiu (Add Immediate Unsigned) instead of addu (Add Unsigned). An immediate is a half-word value that is within the instruction itself and is static.
Some instructions have the variable form, such as SLLV (Shift Left Logical Variable) which is simply the register version of the SSL (which uses a 5 bit immediate).
A register is the type of data used by almost all instructions. They are words (so 4 bytes).
There are 32 main registers that are directly used by opcodes: r0, r1, r2, ... r31. r0 will always be 0x00000000 so you can use it to compare if a value is true or not safely.

Let’s get on to bitwise operands:

AND
(And)
AND returns TRUE to rd if both bits in rs and rt are TRUE.
Else it returns FALSE.
AND rd, rs, rt [rd = rs AND rt]
AND rd, 0x00678460, 0x8005CE40
rs 00000000011001111000010001100000 0x00678460
rt 10000000000001011100111001000000 0x8005CE40
----------------------------------------------
rd 00000000000001011000010001000000 0x00058440
ANDI rt, rs, imm. [rt = rs AND imm.]
ANDI rd, 0x0000006B, 0x02C6
rs 00000000000000000000000001101011 0x0000006B
im ················0000001011000110 ····0x02C6
----------------------------------------------
rd 00000000000000000000000001000010 0x00000042

OR
(Or)
OR returns TRUE to rd if at least one of the bits in rs and rt are TRUE.
Else it returns FALSE.
OR rd, rs, rt [rd = rs OR rt]
OR rd, 0x00057A46, 0x0C82
rs 00000000000001010111101001000110 0x00057A46
rt 00000000000000000000110010000010 0x00000C82
----------------------------------------------
rd 00000000000001010111111011000110 0x00057EC6
ORI rd, rs, imm. [rd = rs OR imm.]
ORI rd, 0x0000006B, 0x000002C6
rs 0000000000000000000000001101011 0x0000006B
im ················000001011000110 ····0x02C6
---------------------------------------------
rd 0000000000000000000001011101111 0x000002EF

SLL
(Shift Left Logical)
SLL shifts rt's bits to the left by sa bits and stores the result in rd.
Excessive bits will be destroyed. New bits are always FALSE.
sa can hold a value from 0x00 to 0x1F inclusively.
SLL rd, rt, sa [rd = rt << sa]
SLL rd, 0x000008E4, 0x02
rt 00000000000000000000100011100100 0x000008E4
sa ···························00010 ······0x02
----------------------------------------------
rd 00000000000000000010001110010000 0x00002390
SLLV rd, rt, sa [rd = rt << rs]
SLL rd, 0x0000008A, 0x00000004
rt 00000000000000000001010110001010 0x0000158A
rs 00000000000000000000000000000100 0x00000004
----------------------------------------------
rd 00000000000000010101100010100000 0x000158A0

SRL
(Shift Right Logical)
SRL shifts rt's bits to the right by sa bits and stores the result in rd.
Excessive bits will be destroyed. New bits are always FALSE.
sa can hold a value from 0x00 to 0x1F inclusively.
SRL rd, rt, sa [rd = rt >> sa]
SRL rd, 0x000012C7, 0x07
rt 00000000000000000001001011000111 0x000012C7
sa ···························00111 ······0x07
----------------------------------------------
rd 00000000000000000000000000100101 0x00000025
SRLV rd, rt, rs [rd = rt >> rs]
SRLV rd, 0x000000C3, 0x00000001
rt 00000000000000000000000011000011 0x000000C3
rs 00000000000000000000000000000000 0x00000001
----------------------------------------------
rd 00000000000000000000000001100001 0x00000061


A few mathematic functions can be applied to registers. Many of them have a signed/unsigned version so make sure you choose the right one depending on your needs.
some of them also uses the hi(high) and lo(low) registers. Basically you cannot use those registers directly as they are not part of the 32 first registers.
However you can still move a value/from to those registers in your hacks to store temporarly a value, which can be extremely useful in some cases if you want to avoid loading/writing to the game's memory.

Here are some mathematical functions:

ADD
(Add)
ADD will add the value, positive or negative, of rs to rt
and store the result in rd.
ADD rd, rs, st [rd = rs + rt]
ADD rd, 0x8005A48C, 0x000002B0
rs 10000000000001011010010010001100 0x8005A48C
rt 00000000000000000000001010110000 0x000002B0
----------------------------------------------
rd 10000000000001011010011100111100 0x8005A73C
ADDU rd, rs, st [rd = rs + rt]
ADDU rd, 0x00000034, 0x80067F80
rs 00000000000000000000001000110100 0x00000234
rt 10000000000001100111111110000000 0x80067F80
----------------------------------------------
rd 10000000000001101000000110110100 0x800681B4
ADDI rd, rs, imm. [rd = rs + imm.]
ADDI rd, 0x0000187E, 0xFFE2
rs 00000000000000000001100001111110 0x0000187E
im ················1111111111100010 ····0xFFE2
----------------------------------------------
rd 00000000000000000001100001100000 0x00001860
ADDIU rd, rs, imm. [rd = rs + imm.]
ADDIU rd, 0x0074FF80, 0xC800
rs 00000000011101001111111110000000 0x0074FF80
im ················0000000011001000 ····0xC800
----------------------------------------------
rd 00000000011101010000000001001000 0x00750048

SUB
(Subtract)
SUB will subtract from rs the value of rt and store the result in rd.
SUB rd, rs, st [rd = rs - rt]
SUB rd, 0x00000000, 0x00000000
rs 00000000000000000000000000000000 0x00000000
rt 00000000000000000000000000000000 0x00000000
----------------------------------------------
rd 00000000000000000000000000000000 0x00000000
SUBU rd, rs, st [rd = rs - rt]
SUBU rd, 0x00000000, 0x00000000
rs 00000000000000000000000000000000 0x00000000
rt 00000000000000000000000000000000 0x00000000
----------------------------------------------
rd 00000000000000000000000000000000 0x00000000




SRL
SRL Shifts the register’s bits to the right by a given amount. Excessive bits will be destroyed.
Example of a SRL with a 0x0003 value
Rs 0010000000000001010000100000010 0x1000A102
---------------------------------------------
rd 0000010000000000001010000100000 0x02001420


xor

math:
add/i/u
sub/u
mult
div
mthi
mtlo
mfhi
mflo

load/store:
lui
lb
lh
lw
sb
sh
sw

jump/branches:
J
JAL
JR
B




??? rd, 0x00000000, 0x0000
rs 00000000000000000000000000000000 0x00000000
rt 00000000000000000000000000000000 0x00000000
----------------------------------------------
rd 00000000000000000000000000000000 0x00000000

??? rd, 0x00000000, 0x0000
rs 00000000000000000000000000000000 0x00000000
rt ················0000000000000000 ····0x0000
----------------------------------------------
rd 00000000000000000000000000000000 0x00000000





ASM Hacking & Debugging with pSX

In this example we're going to edit the formula 0x53, which is originally:
Dmg_(Y%) Hit_(MA+X%)
and transform it into
Dmg_(PA*Y) Hit_(MA+X%)

Because of lazyness and the lack of zodiac compatibility already present in the formula, the outcome of this tutorial will be perfect damage.
This means the damage cannot be increased or lowered in any way. This is only an example.

First let's set a dummy ability for testing. The skill of choice, is as always, Cure. Make a new fresh PSX patch.



Now that you have made your changes, go to the gameshark tab and note the addresses, which means the first part of the address. The other half is unimportant for now.



Open the debugger and the memory window. Now head to your first address. To do that, press ctrl+g when the memory address is active, and type 0x5FC05.



Now enter the values manually. Remember, a gameshark code starting with "3" affects ONE byte, while a gameshark code starting with "8" affects TWO bytes. DON'T FORGET TO FLIP YOUR BYTES!!



Just checking if you did it right:



http://www.ffhacktics.com/wiki/Quick_References

Concerning unit stats
OK, a bit ugly, but you'll get the idea.
# Player unit 1 : 0x801924CC : Malak
# Player unit 2 : 0x8019268C : Rafa
# Player unit 3 : 0x8019284C : Meliadoul
# Player unit 4 : 0x80192A0C : Hyudra
# Player unit 5 : 0x80192BCC : Orlandu


It's always better to know your unit IDs before entering the battle, it saves searching time.

Too bad this won't be of any use in this tutorial, but it's very good to know.



Now take your unholy cure spell and prepare to target someone, make a quicksave here.



Time to add a breakpoint. To do this, right click inside the breakpoint window.



It will be Cure's Y value. It will be read and stored before the formula uses it. You can use FFTPatcher to get the memory address again if you forgot it.



If you look carefully, the byte is loaded and stored again. It was coded this way to always have the X/Y value and other stuff used in a formula at a static location. We simply have to check its next destination, which is 0x801938FA.
This will be our new breakpoint. So edit it.




I made a square to delimitate the formula routine. It's pretty simple, like anything, it starts after another routine ends (after the jr r31) and ends with one.



00186624: 3c028019 lui r2,0x8019
00186628: 8c422d98 lw r2,0x2d98(r2)
0018662c: 00000000 nop
>> r2 = Target's stat offset (see http://www.ffhacktics.com/wiki/Formula_Hacking)

00186630: 9443002a lhu r3,0x002a(r2)
>> r3 = Target's Max HP (see http://www.ffhacktics.com/wiki/Formula_Hacking)
00186634: 3c028019 lui r2,0x8019
00186638: 904238fa lbu r2,0x38fa(r2)
0018663c: 00000000 nop
>> r2 = Current ability's "Y"

00186640: 00620018 mult r3,r2
00186644: 3c0351eb lui r3,0x51eb
00186648: 00001012 mflo r2
0018664c: 3463851f ori r3,r3,0x851f
00186650: 24420063 addiu r2,r2,0x0063
00186654: 00430018 mult r2,r3
>> Some complex stuff to calculate % and round up

00186658: 3c038019 lui r3,0x8019
0018665c: 8c632d90 lw r3,0x2d90(r3)
00186660: 00000000 nop
00186664: 90620025 lbu r2,0x0025(r3)
00186668: 27bdfff8 addiu r29,r29,0xfff8
0018666c: 34420080 ori r2,r2,0x0080
00186670: a0620025 sb r2,0x0025(r3)
>> store r2 = Some value for later use?

00186674: 00001010 mfhi r2
>> r2 = calculated % HP damage

00186678: 00021143 sra r2,r2,0x05
0018667c: a4620004 sh r2,0x0004(r3)
>> r2 = r2 divided by 32

00186680: 27bd0008 addiu r29,r29,0x0008
>> no idea

00186684: 03e00008 jr r31
00186688: 00000000 nop
>> Jump return



now let's code some asm to convert Y% damage to PA*Y:

.org 0x80186640
lui r3, 0x8019
lw r3, 0x2D94
lbu r3, 0x0036(r3) (see http://www.ffhacktics.com/wiki/Formula_Hacking)
>> r3 = Caster's PA

mult r2, r3
>> Caster's PA * Y



Now you need to compile this code. Personally I use Renegade64 because I have no idea what other program can compile MIPS r3000.



And you need to flip the bytes because Renegade compiled that way... yes, it's a lot of trouble but we don't have much other choice. If you're good with excel you can save some time, unless you really don't have much opcodes like this one.
3C038019
8C632D94
90630036
00430018
becomes
1980033C
942D638C
36006390
18004300


We also need to change "mfhi r2" to "mflo r2"
We should also fill everything else that affects r2 with nop (0x00000000).

Our memory hack should look like this:

0x80186640
1980033C
942D638C
36006390
18004300
00000000
00000000

0x80186674
12100000
00000000


Now to test it, the best way is to use winhex and use your savestate right before taking the action.
However, very often the addresses in quicksaves are 0x02B0 higher than memory addresses.
Meaning you will have to paste your data at:
0x80186640 > 1868F0
and
0x00186678 > 186928

Note that winhex doesn't support "0x" in front of hex. Now load your savestate, make sure you are in hex mode. If you aren't, click the address column.
Press alt+g and enter 1868F0. OK.
Now copy/write the new code. To write, press ctrl+b. It's like paste but it will simply overwrite the present data instead of moving it onwards.



Load your savestate.



Test.



Victoly!

Now since this is only a formula hack, converting the addresses to BATTLE.BIN is easy. Just subtract 0x67000.

Here's the final Hack:

BATTLE.BIN

0x0011F640
1980033C
942D638C
36006390
18004300
00000000
00000000

0x0011F674
12100000
00000000

Final Fantasy Hacktics 2.0 ~ The Final Fantasy Tactics Hacking community
©2007-2014 Xifanie Boisvert
All materials are property of their respective owners.