mirror of
https://github.com/GearsProgress/Poke_Transporter_GB.git
synced 2026-10-23 16:03:34 -05:00
191 lines
6.4 KiB
ArmAsm
191 lines
6.4 KiB
ArmAsm
.syntax unified // one true syntax
|
|
.arch armv4t // all we need
|
|
|
|
.section .iwram, "awx", %progbits
|
|
.globl bps_patch
|
|
.type bps_patch, %function
|
|
.thumb
|
|
//
|
|
// Written by easyaspi314, ruined by risingPhil
|
|
//
|
|
// A compact UNCHECKED memory BPS patcher in ARMv4T Thumb assembly. This is designed to be as small as possible
|
|
// without explicitly being inefficient. Aggressive outlining is used.
|
|
// Notes:
|
|
// - The patch is assumed to be well-formed (source_size and target_size are ignored!)
|
|
// - out must point to a buffer large enough to hold the entire patched file
|
|
// - No CRC validation is performed
|
|
// - Uses 32-bit offsets, which combined with how offsets are encoded, means that only files up
|
|
// up to 1 GiB are supported.
|
|
// void bps_patch(
|
|
// const uint8_t *src, // r0
|
|
// uint8_t *out, // r1
|
|
// const uint8_t *patch, // r2
|
|
// size_t patch_size // r3
|
|
// )
|
|
//
|
|
// EDIT: This implementation was modified to deal with trimmed BPS patches we generated using bps-patch-trim.
|
|
//
|
|
// The bps-patch-trim tool eliminates:
|
|
// - magic bytes (BPS1)
|
|
// - header fields (source size, target size, metadata size)
|
|
// - CRC values (footer)
|
|
// You can find the original implementation that doesn't rely on that trimmed variant on commit 3ed33fa.
|
|
//
|
|
.thumb_func
|
|
bps_patch:
|
|
|
|
SRC .req r0 // Offset to output
|
|
OUT .req r1 // Pointer to the output data
|
|
PAT .req r2 // Pointer to the patch. We use direct indexing since patches are read forwards.
|
|
TPTR .req r3 // Temp pointer for copy loops
|
|
DATA .req r4 // Currently read varint, temp var
|
|
ACTLEN .req r5 // Temp var
|
|
TMP1 .req r6 // Temp var
|
|
TMP2 .req r7 // Temp offset for copy loops, temp var
|
|
|
|
hTGTOFF .req r12 // HREG: Pointer to the end of the patch (minus CRCs which are ignored)
|
|
|
|
.set sSRC_BASE, 0 // Stack: Offset to target
|
|
.set sOUT_BASE, 4 // Stack: Pointer to the source data
|
|
.set sPATEND, 8
|
|
adds r3, r3, PAT
|
|
push {r0, r1, r3, r4-r7, lr} // Save registers
|
|
mov hTGTOFF, OUT
|
|
|
|
.Lbps_patch.loop: // while (pat < patend)
|
|
ldr TMP1, [sp, #sPATEND]
|
|
cmp PAT, TMP1 // Check if at the end
|
|
bhs .Lcleanup
|
|
bl .Ldecode // Read action
|
|
lsrs ACTLEN, DATA, #2 // length = (data >> 2) + 1, the +1 is handled by returning from copy on carry.
|
|
lsls DATA, DATA, #31 // Test DATA & 0x3 using lsls shenanigans. C = bit 1, Z = not bit 0
|
|
bhi .Lbps_patch.target_copy // C == 1 & Z == 0 -> 0x3 -> target_copy
|
|
bcs .Lbps_patch.source_copy // C == 1 & (Z == 1) -> 0x2 -> source_copy
|
|
bne .Lbps_patch.target_read // (C == 0) & Z == 0 -> 0x1 -> target_read
|
|
// (C == 0) & (Z == 1) -> 0x0 -> source_read (fallthrough)
|
|
|
|
// The main structure is to set up temp registers and call either copy
|
|
// or copy_with_offset, then if necessary, write back the temp offset to the
|
|
// offset local variable.
|
|
|
|
// void sourceRead() {
|
|
// while(length--) {
|
|
// target[outputOffset] = source[outputOffset];
|
|
// outputOffset++;
|
|
// }
|
|
// }
|
|
.Lbps_patch.source_read: // case 0
|
|
ldr TMP2, [sp, #sSRC_BASE] // sourceRead is very rarely used.
|
|
ldr TMP1, [sp, #sOUT_BASE]
|
|
subs TMP1, OUT, TMP1
|
|
adds TPTR, TMP1, TMP2
|
|
bl .Lcopy // copy(SRC, OUTOFF)
|
|
// Don't write back to OUTOFF, the loop does that automatically.
|
|
b .Lbps_patch.loop
|
|
|
|
// void targetRead() {
|
|
// while(length--) {
|
|
// target[outputOffset++] = read();
|
|
// }
|
|
// }
|
|
.Lbps_patch.target_read: // case 1
|
|
movs TPTR, PAT // add to the pointer.
|
|
bl .Lcopy // copy(NULL, PAT)
|
|
movs PAT, TPTR // Update PAT pointer
|
|
b .Lbps_patch.loop
|
|
|
|
// void sourceCopy() {
|
|
// uint32 data = decode();
|
|
// sourceRelativeOffset += (data & 1 ? -1 : +1) * (data >> 1);
|
|
// while(length--) {
|
|
// target[outputOffset++] = source[sourceRelativeOffset++];
|
|
// }
|
|
// }
|
|
.Lbps_patch.source_copy: // case 2
|
|
bl .Ldecode // Decode length (avoids push lr)
|
|
movs TPTR, SRC // Set the pointers to &SRC[SRCOFF]
|
|
bl .Lcopy_with_offset
|
|
movs SRC, TPTR
|
|
b .Lbps_patch.loop
|
|
|
|
// void targetCopy() {
|
|
// uint32 data = decode();
|
|
// targetRelativeOffset += (data & 1 ? -1 : +1) * (data >> 1);
|
|
// while(length--) {
|
|
// target[outputOffset++] = target[targetRelativeOffset++];
|
|
// }
|
|
// }
|
|
.Lbps_patch.target_copy:
|
|
bl .Ldecode
|
|
mov TPTR, hTGTOFF
|
|
bl .Lcopy_with_offset
|
|
mov hTGTOFF, TPTR
|
|
b .Lbps_patch.loop
|
|
|
|
.Lcleanup:
|
|
.ifdef NO_INTERWORK
|
|
pop {r0, r1, r3, r4-r7, pc}
|
|
.else
|
|
pop {r0, r1, r3, r4-r7}
|
|
pop {r3}
|
|
bx r3
|
|
.endif
|
|
// SUBROUTINE
|
|
// Reads a signed varint offset, adds to TOFF, then copies memory
|
|
// DATA: Directly from decode() (prevents pushing lr)
|
|
// TOFF: Offset for indexing
|
|
// TPTR: Pointer for indexing
|
|
// ACTLEN: Length of data to copy - 1
|
|
// Modifies TOFF, OUTOFF, TMP, DATA, ACTLEN
|
|
.Lcopy_with_offset:
|
|
// offset += (data & 1 ? -1 : +1) * (data >> 1)
|
|
// equivalent:
|
|
lsrs DATA, DATA, #1 // data >>= 1; CF = data & 1;
|
|
bcc .Lcopy_with_offset.pos // if (CF)
|
|
rsbs DATA, DATA, #0 // data = -data;
|
|
.Lcopy_with_offset.pos:
|
|
adds TPTR, TPTR, DATA // offset += data;
|
|
// fallthrough
|
|
|
|
// SUBROUTINE
|
|
// Copies memory forwards like the loops in the reference code. Modifies the offset.
|
|
// TOFF: Offset for indexing
|
|
// TPTR: Pointer for indexing
|
|
// ACTLEN: Length of data to copy - 1
|
|
// Modifies TOFF, OUTOFF, TMP, ACTLEN (=-1)
|
|
.Lcopy:
|
|
.Lcopy.loop: // do {
|
|
ldrb TMP1, [TPTR] // tmp1 = *tmpptr;
|
|
adds TPTR, TPTR, #1 // ++tmpptr;
|
|
strb TMP1, [OUT] // *outptr = tmp1;
|
|
adds OUT, OUT, #1 // ++outptr;
|
|
subs ACTLEN, ACTLEN, #1 // actlen--;
|
|
bcs .Lcopy.loop // } while (actlen != -1);
|
|
bx lr // We use -1 because all offsets are +1. Note that carry is inverted for subs.
|
|
|
|
// SUBROUTINE
|
|
// Reads a varint from PAT, advances pointer
|
|
// Returns in DATA
|
|
// Modifies DATA, TOFF, TPTR, TMP (=0), PAT
|
|
// Compared to the ARM version, this one needs an extra register and
|
|
// far more instructions.
|
|
.Ldecode: // Slightly shorter translation of decode().
|
|
SHIFT .req TPTR
|
|
|
|
movs DATA, #0 // uint32 data = 0;
|
|
movs SHIFT, #0 // uint32 shift = 0;
|
|
.Ldecode.loop: // do {
|
|
ldrb TMP1, [PAT] // uint8 x = read();
|
|
adds PAT, PAT, #1
|
|
movs TMP2, #0x80 // uint8 tmp = x ^ 0x80
|
|
eors TMP2, TMP2, TMP1
|
|
lsls TMP2, TMP2, SHIFT // tmp <<= shift;
|
|
adds DATA, DATA, TMP2 // data += tmp;
|
|
adds SHIFT, SHIFT, #7 // shift += 7;
|
|
lsrs TMP1, TMP1, #8 // bool flag = x & 0x80; x = 0;
|
|
bcc .Ldecode.loop // } while (!flag);
|
|
bx lr
|
|
.size bps_patch, . - bps_patch
|
|
|
|
.section ".note.GNU-stack","" // Disable executable stack
|