Skip to content

Decompiling Your First Function

Rozelette edited this page Apr 19, 2020 · 1 revision

Let's take the following function from z_actor and convert it to C.

glabel Actor_SetChestFlag
/* 016500 0x800B5C90 8C8E1E68 */ lw	$t6, 0X1E68($a0)
/* 016501 0x800B5C94 240F0001 */ li	$t7, 1
/* 016502 0x800B5C98 00AFC004 */ sllv	$t8, $t7, $a1
/* 016503 0x800B5C9C 01D8C825 */ or	$t9, $t6, $t8
/* 016504 0x800B5CA0 AC991E68 */ sw	$t9, 0X1E68($a0)
/* 016505 0x800B5CA4 03E00008 */ jr	$ra
/* 016506 0x800B5CA8 00000000 */ nop

Let's take a look at the first line.

/* 016500 0x800B5C90 8C8E1E68 */ lw	$t6, 0X1E68($a0)

There are two things that we can take away from this line.

First, $a0 is in use. $a0 contains the first argument to the function, and thus we know that this function has at least one argument.

Second, we know that $a0 is a pointer because it is loading something relative to the address it points to and storing it in $t6. Since the lw or "load word" instruction is being used, we know its loading a 32-bit integer.

void Actor_SetChestFlag(u32 a0) {
    u32 t6 = *(u32*)(a0 + 0x1E68);

}

Let's take a look at the next few lines:

/* 016501 0x800B5C94 240F0001 */ li	$t7, 1
/* 016502 0x800B5C98 00AFC004 */ sllv	$t8, $t7, $a1
/* 016503 0x800B5C9C 01D8C825 */ or	$t9, $t6, $t8

An integer value 1 is being loaded into register $t7. This value is then shifted to the left using $a1 to determine how many bits it should be shifted, with the result being saved in $t8. Finally, this result is Binary OR'd with the contents of $t6. This roughly translates to:

void Actor_SetChestFlag(u32 a0, u32 a1) {
    u32 t6 = *(u32*)(a0 + 0x1E68);
    u32 t7 = 1;
    u32 t8 = t7 << a1;
    u32 t9 = t6 | t8;

}

Let's take a look at the final three lines:

/* 016504 0x800B5CA0 AC991E68 */ sw	$t9, 0X1E68($a0)
/* 016505 0x800B5CA4 03E00008 */ jr	$ra
/* 016506 0x800B5CA8 00000000 */ nop

The result of these calculations are saved back into $a0 + 0x1E68, then the function returns. The nop after the jr is necessary because the instruction in the binary after a jump is executed first due to the use of a delay slot.

This translates to:

void Actor_SetChestFlag(u32 a0, u32 a1) {
    u32 t6 = *(u32*)(a0 + 0x1E68);
    u32 t7 = 1;
    u32 t8 = t7 << a1;
    u32 t9 = t6 | t8;

    *(u32*)(a0 + 0x1E68) = t9;
}

This can be simplified down to:

void Actor_SetChestFlag(u32 a0, u32 a1) {
    *(u32*)(a0 + 0x1E68) |= (1 << a1);
}

Be sure to comment out the #pragma GLOBAL_ASM line with your function. Otherwise, you'll get an error about duplicate functions.

Clone this wiki locally