-
Notifications
You must be signed in to change notification settings - Fork 74
Register Mismatch Decompilation Tricks
This page contains some examples of some unintuitive or tricky compiler tricks that may be SotN specific to get registers to match.
The developers of SotN tended to use foo > bar in if statements.
That tends to get compiled to look as foo >= bar+1
This can cause the "wrong" registers to be used. Take for example the asm
slti at,v1,0x29
bnez at,3d0
nop Directly, this translates to
if (foo >= 0x29) {
However, that will compile to
slti v1,v1,0x29
bnez v1,3d0
nopWhich uses the register v1 instead of at.
The "correct" translation is
if (foo> 0x28)
This is a harder one to find in PSX vs PSP.
Take the following assembly in PSX and PSP
| PSX | PSP |
|---|---|
lui v1,%hi(foo)
lw v1,%lo(foo)(v1)
nop
bgez v1,1c4 ~>
move v0,v1
addiu v0,v1,0x7f
sra a0,v0,0x7
sll v0,a0,0x7
subu a0,v1,v0 |
lui v0,%hi(foo)
lw v1,%lo(foo)(v0)
li v0,0x80
andi s2,v1,0x7f
bgez v1,284
nop
beqz s2,284
nop
addiu s2,s2,-0x80 |
It sure looks like this is some sort of branching statement and you may try to write something like this
bar = foo & 0x7F;
if (bar < 0 && bar != 0) {
bar -= 128;
}However, that compiles to (PSP)
lui v0,%hi(foo)
lw v0,%lo(foo)(v0)
andi s1,v0,0x7f
bgez s1,284 ~>
nop
beqz s1,284 ~>
nop
addiu s1,s1,-0x80Close, but not quite there. The "tell" on this situation is that we are loading 0x80 into v0 on psp, but not doing anything with it.
This may be a compiler bug (it may also be a sort of comment to help debug), but we can leverage it to know that this is actually a modulo. The correct translation is
index = foo % 0x80;There is a weird instruction that gets executed sometimes where the source and destination for a MOVE is the same. This gets compiled out of the code on PSX, so you won't see it there, but it can cause a lot of register mismatches in PSP.
jal Random
nop
move v1,v0
andi v1,v1,0x3
move v1,v1 #This is the odd move command
seh v0,v1
sh v0,0xa(s0)You may try to match that with something like this:
foo = Random() & 3;But that will lead to the asm being
jal Random
nop
move v1,v0
andi v1,v1,0x3
seh v1,v1
sh v1,0xa(s0)and missing that odd move.
MOVE is not actually a mips instruction. It's a psudo-instruction that the assembler uses as it's clearer to what is happening than the actual translation.
move v1,v0
#Is actually
addu v1, 0, v0So what we are missing here is an addition. The compiler is smart enough to get rid of a +0, but if we take a non zero value and subtract it off again, we will get back the missing move
foo = (Random() & 3) +1 -1;And that will match the assembly. In a lot of cases, this also resolves a lot of a0/v0 mismatches as well.
Likely, one of those values is some sort of #DEFINE and the other is an offset, but we may never actually know.
Sometimes function calls can have register mismatches immediately before them that seem impossible to resolve. For example:
628: li v0,0x415 # expected v1 register
62c: sh v0,0x36(s0) # expected v1 register
630: move a0,s3
634: move a1,s0
638: jal func_psp_0925EBA0In cases like this it can be worthwhile checking if the function being called would reside within the same file as the one being decompiled, and if so, making the function being called static:
static void func_psp_0925EBA0(Primitive* a, Primitive* b) {}This is hard to verify in decomp.me because functions produce additional assembly, but an empty statically declared function like this at the top of the scratch may resolve mismatches at the point where the function is called. Example
Certain calls to the scratchpad expect use of the $at register. This can be achieved by treating the scratchpad as an array reference, for example:
7b0: sll v1,s2,0x4
7b4: lui at,0x1 # expects $at
7b8: ori at,at,0x80 # expects $at
7bc: addu v0,v1,at
7c0: sw v0,0x34(sp)Expected code is
foo = &((long*)(SP(0x80)))[some_var];long* can be replaced with the type of foo you are expecting. Example