INTRODUCTION

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

 

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.

 

Range of values

Type

Equivalent

0-1

Boolean/Bit

-

0x0-0xF

Nibble

4 bit

0x00-0xFF

Byte

2 nibbles

0x0000-0xFFFF

Half-Word

2 bytes

0x00000000-0xFFFFFFFF

Word

4 bytes

0x0000000000000000 -0xFFFFFFFFFFFFFFFF

Double-Word

8 bytes

 

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.

 

Examples:

Value

Is stored as

0x78

78

0x011F

1F 01

0x80050263

63 02 05 80

0x00000002846A0507

07 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.

 

0 = FALSE

1 = TRUE

 

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

0x02A7 to decimal (hex is base 16)

$00 x 163 = 0 x 4096 = 0

$02 x 162 = 2 x 256 = 512

$0A x 161 = 10 x 16 = 160

$07 x 160 = 7 x 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.

 

Value

Unsigned

Signed

0x5B4D

23373

+23373

0xA9B5

43445

-22091

0x7999

31129

+31129

0xFE

254

-2

 

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.

 

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 rd, rs, rt         rd = rs + rt     

AND returns TRUE to rd if both bits in rs and rt are TRUE.

 

Example:

Rs 0000000000000000000000001101011 0x0000006B

Rt 0000000000000000000001011000110 0x000002C6

---------------------------------------------

rd 0000000000000000000000001000010 0x00000042

 

AND is mostly used to verify that a bit is enabled. For example AND is used to detect if a unit is immortal flagged or not.

 

 

OR rd, rs, rt                           rd = rs OR rt

OR returns TRUE to rd if any bits are TRUE.

 

Example:

Rs 0000000000000000000000001101011 0x0000006B

Rt 0000000000000000000001011000110 0x000002C6

---------------------------------------------

rd 0000000000000000000001011101111 0x000002EF

 

 

SLL

SLL Shifts the register’s bits to the left by a given amount. Excessive bits will be destroyed.

Example of a SLL with a 0x0005 value

 

Rs 0010000000000001010000100000010 0x1000A102

---------------------------------------------

rd 0000000000101000010000001000000 0x00142040

 

 

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