Appearance
aowlspt_detour.h
Source: abi/aowlspt_detour.h — 1886 lines, 46 file-scope functions.
What this header owns
Reproduced verbatim from the header's own banner comment — these notes are frequently the only written record of why the subsystem is shaped the way it is.
text
aowlspt_detour.h — x64 inline hooks, for `AowlHostApi.patch`.
Harmony patches a managed method by rewriting IL. There is no IL in an
IL2CPP build, so the equivalent here is a code detour: overwrite the first
instructions of the compiled function with a jump to our own, and keep the
instructions we overwrote in a trampoline so the original can still run.
Two notes before the mechanism, because both are easy to get wrong in ways
that only show up as a crash somewhere else.
**Why not just swap `MethodInfo.methodPointer`.** It is a field, and writing
it is trivial. But only reflective dispatch reads it — IL2CPP compiles a
direct call site into a direct `call`, so a pointer swap intercepts
`il2cpp_runtime_invoke` and nothing the game itself does. That is worse than
useless: it would appear to work in a test and never fire in a raid. So the
code gets patched and `methodPointer` is left alone.
**Why there is a length decoder below.** The jump needs `AOWL_JMP_SIZE`
bytes, and x64 instructions are variable length, so the bytes being
overwritten have to be decoded to find a whole number of them. Copying a
partial instruction into the trampoline produces garbage that then executes.
The decoder refuses anything it does not recognise rather than guessing a
length. A refused patch is an error a mod author can read. A guessed length
is a corrupted game.
"Conservative" is not the same as "small", though, and it was small for a
while in a way that made it useless. It covered the one-byte map and a
handful of two-byte opcodes, which is the prologue of a function that does
integer work -- and almost nothing in a Unity game does integer work. An
ordinary method opens `pxor`/`cvtsi2ss`/`mulss`/`movss`, all of it in the
two-byte map, and every one of those was refused. So the two-byte map is
covered properly: the mandatory prefixes, the three-byte escapes, and the
opcodes that carry an immediate, which is the one fact a length decoder
cannot get from the ModRM byte.
RIP-relative instructions encode a displacement from their own address, so
copying one into a trampoline silently changes where it points. They are
also extremely common in a prologue — `mov eax, [rip+off]` is how any access
to a global compiles — so refusing them would refuse most real functions.
They are relocated instead: the displacement is adjusted by the distance the
instruction moved, and the patch is refused if the result no longer fits in
32 bits.
Relative branches (`call rel32`, `jmp rel8/32`, `jcc`, `loop`) are still
refused. They could be rewritten too, but they are rare this early in a
function, and a refusal a mod author can read beats a rewrite nobody has
checked.
**Why the process stops while the bytes move.** Fourteen bytes is not an
atomic store, so a thread entering the function mid-write executes half of
one instruction and half of another. Every other thread in the process is
therefore suspended across the write and checked for standing in the bytes;
see the long note above `aowl_hook_arm`, which is mostly about what may and
may not be called while they are stopped.
The refusals are kept apart from each other on purpose. "I do not know this
instruction", "I know it and it cannot move", and "the function is shorter
than the jump" are three different problems -- the first is a gap in this
file, the second is a fact about the target that will never change, and the
third is neither -- and a single message listing all three tells the reader
only that something went wrong. See `aowl_hook_error_text`.Constants
AOWLSPT_DETOUR_HAOWL_INSN_OKAOWL_INSN_RELATIVEAOWL_INSN_SHORTAOWL_INSN_UNKNOWNAOWL_JMP_SIZEAOWL_MAX_HOOKSAOWL_MAX_STOLENAOWL_PARK_ACCESSAOWL_PARK_ENUM_NONEAOWL_PARK_ENUM_NTAOWL_PARK_ENUM_TOOLHELPAOWL_PARK_MAXAOWL_PARK_RETRIESAOWL_REGS_BYTESAOWL_STUB_SIZEAOWL_TRAMP_BLOCKAOWL_TRAMP_BLOCKSAOWL_TRAMP_SITEAOWL_TRAMP_SLOTAOWL_TRAMP_STUBAOWL_TRAMP_TRAMP
Types
struct AowlHookstruct AowlInsnstruct AowlParkstruct AowlSitestruct AowlTrampBlock
Functions
| Signature | Line |
|---|---|
AowlInsn aowl_insn(const uint8_t* p) | 128 |
int32_t aowl_insn_len(const uint8_t* p) | 385 |
int32_t aowl_stolen_len_why(const uint8_t* p, int32_t need, int32_t* why) | 402 |
int32_t aowl_stolen_len(const uint8_t* p, int32_t need) | 426 |
int32_t aowl_copy_relocated(uint8_t* dst, const uint8_t* src, int32_t bytes) | 435 |
void aowl_write_jmp_abs(uint8_t* at, void* dest) | 463 |
void aowl_write_stub(uint8_t* at, void* site) | 516 |
void aowl_alloc_near(void* anchor, size_t size) | 533 |
int aowl_tramp_block_reaches(const uint8_t* base, const void* anchor) | 675 |
uint8_t aowl_tramp_alloc(void* anchor) | 683 |
int32_t aowl_tramp_count(void) | 716 |
int32_t aowl_tramp_blocks(void) | 717 |
void aowl_hook_new(void) | 719 |
void aowl_hook_free(void* p) | 720 |
void aowl_hook_trampoline(void* p) | 721 |
int32_t aowl_hook_installed(void* p) | 724 |
int32_t aowl_hook_stolen(void* p) | 727 |
char aowl_hook_error_text(int32_t rc) | 750 |
int32_t aowl_why_to_rc(int32_t why) | 773 |
int32_t aowl_hook_prepare_ex(void* handle, void* target, void* detour, int32_t wantStub) | 801 |
int32_t aowl_hook_prepare(void* handle, void* target, void* detour) | 856 |
void aowl_hook_set_park(int32_t on) | 1003 |
int32_t aowl_hook_park_enabled(void) | 1004 |
int32_t aowl_hook_park_retries(void) | 1011 |
int32_t aowl_hook_park_giveups(void) | 1012 |
int32_t aowl_hook_park_enum(void) | 1020 |
char aowl_hook_park_enum_text(void) | 1021 |
void aowl_park_resume(AowlPark* p) | 1029 |
void aowl_park_close(AowlPark* p) | 1039 |
AowlNtGetNextThread aowl_nt_get_next_thread(void) | 1069 |
int32_t aowl_park_collect_nt(AowlPark* p) | 1083 |
int32_t aowl_park_collect_toolhelp(AowlPark* p) | 1141 |
int32_t aowl_park_begin(AowlPark* p, void* target, int32_t stolen) | 1176 |
int32_t aowl_hook_arm(void* handle) | 1222 |
int32_t aowl_hook_install(void* handle, void* target, void* detour) | 1258 |
int32_t aowl_hook_remove(void* handle) | 1264 |
int32_t aowl_hook_stale_fires(void) | 1494 |
void aowl_hook_set_postfix(int32_t slot, int32_t on) | 1560 |
int32_t aowl_hook_is_postfix(int32_t slot) | 1570 |
int32_t aowl_hook_capacity(void) | 1704 |
int32_t aowl_hook_used(void) | 1745 |
int32_t aowl_hook_free_count(void) | 1746 |
int32_t aowl_hook_claim(void) | 1754 |
void aowl_hook_release(int32_t slot) | 1790 |
int32_t aowl_hook_attach_at(void* handle, void* target, int32_t slot) | 1825 |
int32_t aowl_hook_attach(void* handle, void* target) | 1874 |

