Skip to content
Jurre Groenendijk edited this page Jun 5, 2026 · 7 revisions

General matching tips that are broadly applicable:

Repeated code is usually a for loop

e.g.

do_stuff(arg)
if(bla){
    do_thing2[i]
}
i++
do_stuff(arg)
if(bla){
    do_thing2[i]
}
i++
do_stuff(arg)
if(bla){
    do_thing2[i]
}

is probably

for(i=0;i<3;i++){
    do_stuff(arg)
    if(bla){
        do_thing2[i]
    }
}

Avoid pointer math

Even if it matches, stuff like ((u8*) obj) + offset should likely be rewritten into obj->xOffset where xOffset is a struct member at offset and ((u8*) obj) + offset + offset2 * index should likely be rewritten into obj->xOffset[index] where xOffset is a struct member and an array of elements of size offset2

Jobj.h

anytime you see "jobj.h" it's an inline from jobj.h e.g. instead of

            digit_jobj = state->jobjs[i];
            if (digit_jobj == NULL) {
                __assert("jobj.h", 993, "jobj");
            }
            state->translation_x[i] = digit_jobj->translate.x;

you should look for the value of 993 in the jobj.h file, find that that corresponds to

static inline f32 HSD_JObjGetTranslationX(HSD_JObj* jobj)
{
    HSD_ASSERT(993, jobj);
    return jobj->translate.x;
}

and change your code to

          state->translation_x[i] = HSD_JobjGetTranslationX(digit_jobj)

__assert vs HSD_ASSERT

Anytime you see __assert, it should be one ofthese macros:

#define HSD_ASSERT(line, cond)                                                \
    ((cond) ? ((void) 0) : __assert(__FILE__, line, #cond))
#define HSD_ASSERTMSG(line, cond, msg)                                        \
    ((cond) ? ((void) 0) : __assert(__FILE__, line, msg))
#define HSD_ASSERTREPORT(line, cond, ...)                                     \
    ((cond) ? (void) 0                                                        \
            : (OSReport(__VA_ARGS__), __assert(__FILE__, line, #cond)))

e.g. instead of

if(!new){
    __assert("aobj.c", 489, "new");
}

it should be

HSD_ASSERT(489, new);

Same goes with HSD_ASSERTMSG and HSD_ASSERTREPORT

Matching Tricks

In this section I invite anyone to put tricks they have found work during getting the last few % of a function.

Stack padding with an inline

If you need 8 fewer bytes in your stack size and your function contains an inline such as GET_JOBJ, you can get those 8 bytes by manually inlining

Example:

gobj->hsd_obj instead of GET_JOBJ



Switching the register order of arithmetic operations

If you have an arithmetic operation such as a * (b - c), and you want to swap the register order of the *, you will have to store b - c in a variable. This will take up stack space, but is required to swap the register order.

Example:

tmp = b - c
result = tmp * a


Two branches in a row

This can be two things:

  • If it is after a comparison with a number, it is likely a switch statement
  • If it is after a comparison with another variable (and is of the format bgt b or something like that), it is likely a ternary. Try using the MIN, MAX or CLAMP defines, but if these don't work, create your own ternary

Examples:

Switch Statement:

Instead of:

if (lbl_80479A98.x4 < 3) {
    if (lbl_80479A98.x4 < 0) {
    } else {
        lbl_80479A98.x18 = 1;
    }
}

It is

switch (lbl_80479A98.x4) {
    case 0:
    case 1:
    case 2:
        lbl_80479A98.x18 = 1;
}

Ternary:

Instead of:

var_r4 = lbl_80479A98.xC;
temp_r0 = lbl_80479A98.x8 - lbl_80479A98.x10;
if (temp_r0 < var_r4) {
} else {
var_r4 = temp_r0;
}
lbl_80479A98.x8 = var_r4;

It is

lbl_80479A98.x8 =
    ((lbl_80479A98.x8 - lbl_80479A98.x10) < (lbl_80479A98.xC))
       ? (lbl_80479A98.xC)
       : (lbl_80479A98.x8 - lbl_80479A98.x10);

Struct assignment

When copying multiple contiguous fields, MWCC uses int stores/loads, regardless of the types of the fields being copied. So these expressions produce different assembly:

// uses stw/lwz
Vec3 v = foo;

// uses stfs/lfs
Vec3 v;
v.x = foo.x;
v.y = foo.y;
v.z = foo.z;

Loop unrolling

MWCC only unrolls loops if the counter is exactly type int -- it will not work for s32, even though these should be equivalent.

matching swapped fcmpu 0.0f

if(v != 0.0f)

 1bb8:    lfs     f1,grI1_804DB5C8@sda21(0) // 0.0f
 1bbc:    lfs     f2,0xf0(r31) // variable, v
 1bc0:    fcmpu   cr0,f2,f1 // cmp both

if(v)

 1bb8:    lfs     f1,grI1_804DB5C8@sda21(0) // 0.0f
 1bbc:    lfs     f2,0xf0(r31) // variable, v
 1bc0:    fcmpu   cr0,f1,f2 // cmp both

Clone this wiki locally