I figured that it’d probably be good to show off some examples in this thread so that people don’t think this is purely an April Fools joke.
Here’s an updated version of the ubiquitous anti-huffman hack that comes with FEditor and the essential fixes EA installer. I’ll split it up a bit to comment on parts and post the whole text at the end, alright?
#include "THUMBLIB2.event"
// Temporary helpers
#define t_push_lr "t_push(False, False, False, False, False, False, False, False, True)"
#define t_pop_pc "t_pop(False, False, False, False, False, False, False, False, True)"
One of the big flaws with THUMBLIB2
is that there isn’t a good way to implement register lists, so opcodes like push and pop are big chunks of true/false. I don’t want to have to copy/paste this every time, so I just use these two macros.
#ifndef GetStringFromIndex
#define GetStringFromIndex (0x0800A280 | 1)
#endif
#ifndef bx_r2
#define bx_r2 (0x080D18C8 | 1)
#endif
#ifndef TextBuffer
#define TextBuffer 0x0202A6AC
#endif
#ifndef ARM_HuffmanTextDecomp
#define ARM_HuffmanTextDecomp 0x03003C0C
#endif
Next, some vanilla routine and data offsets that’ll be referenced. There are probably better ways to do this, but I’m out of practice. Notice the THUMB bit on the first two and the lack of it on the ARM routine.
PUSH
ORG 0x00002BA4
AntihuffmanPointerTester:
{
/* push {lr} */ t_push_lr
/* lsr r2, r0, #31 */ t_lsr(r2, r0, 31)
/* beq _AHCompr... */ t_beq(_AHCompressed)
/* bl Antihuff... */ t_bl(AntihuffmanUncompressed)
/* b _AHReturn */ t_b(_AHReturn)
_AHCompressed:
/* bl Antihuff... */ t_bl(AntihuffmanCompressed)
_AHReturn:
/* pop {pc} */ t_pop_pc
}
This is 1-to-1 from the normal AH code. I like to use the braces for scope shenanigans. I’ve included the normal assembly formatting as comments.
I would have liked both of these bl
s to instead be straight up branches and not subroutine calls, because there’s nothing to be done after calling them, but this routine is exactly small enough to fit here and using bx
s and a literal pool is too big.
If you’ve never seen how the anti-huffman patch works: pointers in the text table to text that is not compressed are flagged by having their uppermost bit set. This check shifts the pointer right until just the upper bit remains and then uses that to pick a routine. We’ll get to the actual routines in a moment.
ORG 0x0000A240
GetStringFromIndexExt:
{
/* ldr r1, offs_TextBuffer */ t_ldr(r1, offs_TextBuffer)
/* b GetStringFromIndex */ t_b(GetStringFromIndex)
ALIGN 4
offs_TextBuffer: WORD TextBuffer
}
The original AH hack actually starts editing this routine midway through, attempting to preserve some vanilla functionality that would save time if the requested string was already in the buffer. The problem is that the AH hack also destroys the original function’s literal pool, causing it to load garbage and making the attempt useless. I just gutted the vanilla stuff and the AH’s attempt at preserving it, which gives me plenty of extra space to fit the rest inline.
Also notice that the call to GetStringFromIndex
is a b
and not a bl
. GetStringFromIndex
pushes lr
and returns using it, so I don’t have to. It’s also really close by, so I can use b
instead of bx
. This little tail call optimization ends up saving me another instruction, which is nice.
AntihuffmanCompressed:
{
/* push {lr} */ t_push_lr
/* ldr r2, offs... */ t_ldr(r2, offs_ARM_HuffmanTextDecomp)
/* bl bx_r2 */ t_bl(bx_r2)
/* pop {pc} */ t_pop_pc
ALIGN 4
offs_ARM_HuffmanTextDecomp: WORD ARM_HuffmanTextDecomp
}
Small optimization here. The original AH grabs a pointer to the ARM routine indirectly, using another ldr
to fetch it from RAM. As far as I know, this pointer in RAM isn’t ever changed after booting the game, so I just call the function directly.
AntihuffmanUncompressed:
{
/* mov r3, #0x80 */ t_mvi(r3, 0x80)
/* lsl r3, r3, #24 */ t_lsl(r3, r3, 24)
/* sub r0, r0, r3 */ t_sbr(r0, r0, r3)
_AHLoop:
/* ldrb r2, [r0] */ t_ldbi(r2, r0, 0)
/* strb r2, [r1] */ t_stbi(r2, r1, 0)
/* add r1, #1 */ t_adi(r1, 1)
/* add r0, #1 */ t_adi(r0, 1)
/* cmp r2, #0 */ t_cpi(r2, 0)
/* bne _AHLoop */ t_bne(_AHLoop)
/* bx lr */ t_bx(lr)
}
The original AH hack couldn’t fit this inline and had to put it in freespace, jumping to it with a bx
. I saved enough space to fit it, so the bx
part is missing.
ASSERT (0x0000A280 - CURRENTOFFSET)
POP
#undef t_push_lr
#undef t_pop_pc
I use ASSERT
here just to make sure all of this fits inline. It’ll throw an error if the current offset is larger than 0xA280
. I also undefine the macros I made just for a bit of cleanliness, although it really isn’t necessary.
The whole thing
#include "THUMBLIB2.event"
// Temporary helpers
#define t_push_lr "t_push(False, False, False, False, False, False, False, False, True)"
#define t_pop_pc "t_pop(False, False, False, False, False, False, False, False, True)"
#ifndef GetStringFromIndex
#define GetStringFromIndex (0x0800A280 | 1)
#endif
#ifndef bx_r2
#define bx_r2 (0x080D18C8 | 1)
#endif
#ifndef TextBuffer
#define TextBuffer 0x0202A6AC
#endif
#ifndef ARM_HuffmanTextDecomp
#define ARM_HuffmanTextDecomp 0x03003C0C
#endif
PUSH
ORG 0x00002BA4
AntihuffmanPointerTester:
{
t_push_lr
t_lsr(r2, r0, 31)
t_beq(_AHCompressed)
t_bl(AntihuffmanUncompressed)
t_b(_AHReturn)
_AHCompressed:
t_bl(AntihuffmanCompressed)
_AHReturn:
t_pop_pc
}
ORG 0x0000A240
GetStringFromIndexExt:
{
t_ldr(r1, offs_TextBuffer)
t_b(GetStringFromIndex)
ALIGN 4
offs_TextBuffer: WORD TextBuffer
}
AntihuffmanCompressed:
{
t_push_lr
t_ldr(r2, offs_ARM_HuffmanTextDecomp)
t_bl(bx_r2)
t_pop_pc
ALIGN 4
offs_ARM_HuffmanTextDecomp: WORD ARM_HuffmanTextDecomp
}
AntihuffmanUncompressed:
{
t_mvi(r3, 0x80)
t_lsl(r3, r3, 24)
t_sbr(r0, r0, r3)
_AHLoop:
t_ldbi(r2, r0, 0)
t_stbi(r2, r1, 0)
t_adi(r1, 1)
t_adi(r0, 1)
t_cpi(r2, 0)
t_bne(_AHLoop)
t_bx(lr)
}
ASSERT (0x0000A280 - CURRENTOFFSET)
POP
#undef t_push_lr
#undef t_pop_pc