Memory, symbols, hooks
The low-level primitives every backend declares. Packs are built on these; most mods should never need them.
These are the ten primitives every backend declares, which is what makes a backend a backend. Packs live here. Most mods never touch any of it, and a mod that reaches for memory when a question would do is a mod that breaks on the next patch.
What you are attached to.
Name, base and size of every loaded module.
One by name.
x64, arm64, ppc64, jvm or clr. jvm or clr is the signal that pointer math is unavailable and the object APIs are what you want.
4 or 8, where the concept applies.
Reading the address space.
One span.
Many spans in one crossing. This is how a pack walks a chunked pool without paying per chunk.
Follow a pointer chain host-side. Four deep is one call, and it fails at whichever link was bad rather than faulting.
Mapped and readable, without reading it.
Separate question, separately denied, and frequently absent. A pack can answer read without write.
One span.
Many, one crossing.
Finding something when a symbol will not do. Slow: at init, never per frame.
IDA-style pattern with ?? wildcards.
Exactly one match, or a failure.
The only address source anything above this layer should use.
A name from the pack’s symbol table, resolved against this build.
Everything the pack declares, with its state. This is what axiom symbols prints.
Exported symbols of a module.
A mod or pack holding an address across a session is holding something that was only true for one build. Ask for the name every time; resolution is cached host-side.
Intercepting a game function.
Install a hook.
Before, After, Replace or AroundRemove it. Unloading does this for you.
From inside an Around hook.
Before still observes the callCalling the game's own code. The tenth primitive, the most dangerous thing in the system, and the only one the sandbox cannot protect you from.
Six of the seven game shapes can be read. The one that cannot is resolution that is computed rather than stored: a handle table behind a virtual dispatch, ids transformed per frame, managed resolution that needs GC cooperation. A pack can bind the concrete layout until the game swaps implementations, and then it must either reimplement the game’s code, which breaks every patch, or call it.
Register a call site. The signature comes from the pack’s symbols file and generates a typed stub, so calling convention and argument marshalling are the generator’s problem rather than yours.
Invoke it. Runs on the thread the backend designates, at a barrier, once.
What this backend certifies as safe.
Barriers, and the clock they fix
FrameBeginPrePresentTickBoundarySafepointframe used to mean a render frame on a client, a 50 ms logic tick
on a headless server, and a GC safepoint on a managed runtime. One word, three
clocks, and a mod saw 5 ms of staleness on one game and 50 ms on another.
staleness() now reports milliseconds, and clock()
names the barrier your frame hook rides.
What it cannot be made safe against
A wrong signature is undefined behavior inside the game’s own code. WebAssembly contains the
pack’s own memory; invoke by definition leaves it. A Tier W pack that calls a real function with
the wrong argument types corrupts or crashes the game exactly as a native one would.
invoke signature is flaggedA pack that reads its way to an answer cannot crash a game.
invoke exists because some games leave no alternative, not
because calling in is a reasonable default.