Patch

internal object Patch

Runtime method patching: weaves a ByteBuddy Advice into a method named at runtime. Used only by execute_code scripts, via Patches.

onEnter inlines PatchBridge.fireEnter(slot, key, this, args) at the method's head; onExit inlines PatchBridge.fireExit(slot, key, this, args, returned, thrown) at every exit, including the exceptional one. The bridge indexes SEQ's slot to reach the registered game-side callback.

intercept and modify weave the writable blueprints instead — see Phase for what separates them, and PatchInterceptAdvice / PatchModifyAdvice for the bytecode each produces.

How a target is chosen

PatchTargets does the picking; this is why it picks that way. When the class is already loaded — the normal case — the target is resolved against the LIVE class: its hierarchy supplies the owners, and the mojmap name the script wrote is translated FORWARD over them (Signatures.runtimeNamesOf). Reflection seeing ground truth buys three things:

  • An override declared on a MOD class resolves. Its own name is in no mapping row, but climbing to the MC class it was inherited from reaches the row that does name it — which a single forward lookup on the script's class would miss, and which a reverse demap of the runtime name cannot do at all on an obf runtime, where one name serves methods jar-wide (see Mappings.reverseMethod).

  • The weave matcher is built from the resolved method's exact (name, descriptor), so there is no second signature-comparison implementation that could disagree with the one used to pick the target.

  • "No such method" and "ambiguous" are found BEFORE anything is installed, so they throw as clean script errors listing the real candidates, instead of silently weaving nothing.

When the class is NOT loaded yet the patch still installs and waits — mod classes load late — but nothing can be verified at that point. See PatchHandle.targets for how that is reported.

Bridge injection lives in Instrumentations — see its class doc for the load-order constraint.

Types

Link copied to clipboard

InterceptHandler plus a read-only return observer, woven as the inner advice so a skipped body takes it with it. Fires are counted at the head only — one per call, as for every other phase.

Link copied to clipboard
internal sealed class CountingHandler : Handler

Game-side handler: counts fires and forwards to the user callback. Implements the bootstrap Handler, whose phase methods all default to no-ops, so each subclass overrides only the ones its patch was woven for.

Link copied to clipboard
enum End : Enum<Patch.End>

The two points a patch is observable at.

Link copied to clipboard
Link copied to clipboard
Link copied to clipboard

Head-side decision. Open only so AroundHandler can add the return observer to it.

Link copied to clipboard
Link copied to clipboard
enum Phase(val slug: String, val at: Patch.End, val affects: Set<Patch.End>) : Enum<Patch.Phase>

Which advice blueprint is woven. at is where the decision is made, affects which ends a write of it can be seen at; conflictsWith derives from those two and nothing else. Named slug and not tag, which on Patches means the caller's coexistence discriminator.

Link copied to clipboard
private object WarmTarget

Warm target: never called, only woven against. Non-void so the writable blueprints build a real return assigner.

Link copied to clipboard
private class Weave(val matcher: ElementMatcher<MethodDescription>, val params: List<String>?, val methodName: String, val slot: Int, val key: String, val phase: Patch.Phase)

Everything the per-type transform body needs that isn't the type itself. methodName is the mojmap name the script asked for, carried rather than looked back up: it is exact by construction, and on an obf runtime it is the only way the reported signature can name the method at all.

Link copied to clipboard
internal class WeaveListener(val cls: String) : AgentBuilder.Listener.Adapter

Holds the throwable ByteBuddy hands a listener, for PatchHandle.weaveError. Filtered to the patched type: one install's redefinition pass walks every loaded class.

Properties

Link copied to clipboard
private val BOOTSTRAP: Any

Key for the bootstrap loader in install's woven map: that loader is null, which ConcurrentHashMap refuses.

Link copied to clipboard
private val IGNORED: ElementMatcher.Junction<TypeDescription>

ByteBuddy's default ignore minus two clauses. Its classloader one (isNull() or PlatformClassLoader) drops every bootstrap/platform target as onIgnored, which reaches PatchHandle as pending, unreadable against a class that never loaded. isSynthetic() reads modifiers, and the description this matches against resolves its NAME lazily and nothing else — so it would parse the class file of everything the name clauses below miss, i.e. every class the game loads, once per installed patch. Both matchers select one method on one FQN, so nothing synthetic reaches them anyway. The rest is verbatim: weaving ByteBuddy or the reflection accessors recurses into the weave, and PatchBridge — bootstrap-resident, so reachable only now — joins it for that same reason, being what every woven advice calls.

Link copied to clipboard
private val SEQ: AtomicInteger

Minted per INSTALL, and it is BOTH the key's suffix and the bridge's registry slot — one number, so the two can never come to name different patches. Freshness, not collision avoidance: an id kept across a re-install answers null / -1 instead of the replacement's state, and a Probe channel named after it starts empty. It equally strands advice Patches.remove could not unweave, whose slot stays burned and reads null. Starts at 1, which is what leaves 0 free for warm's never-registered blueprints.

Functions

Link copied to clipboard
private fun adviceFor(phase: Patch.Phase, slot: Int, key: String, matcher: ElementMatcher<MethodDescription>, rt: TypeDescription?): AsmVisitorWrapper?

The visitor for phase, or null when there is nothing left to weave.

Link copied to clipboard
fun around(className: String, methodName: String, params: List<String>?, tag: String?, cb: PatchInterceptCallback, onReturn: PatchExitCallback): PatchHandle

intercept plus onReturn, which observes what the body produced. It does not fire when cb skipped the body — the advice carrying it is woven INSIDE the one that skips, so the jump clears it too.

Link copied to clipboard
internal fun discoveryOf(loaded: List<Class<*>>): AgentBuilder.RedefinitionStrategy.DiscoveryStrategy

Which types the install's redefinition pass walks: the default walks getAllLoadedClasses() and matches every entry, so a pending install — which has no Class to name yet — still catches its target the moment it loads. Unweaving does not come through here; Unweave re-resolves the name against the live classes instead, so it sees whatever a pending patch eventually wove.

Link copied to clipboard
private fun install(className: String, methodName: String, params: List<String>?, tag: String?, gh: Patch.CountingHandler): PatchHandle
Link copied to clipboard
fun intercept(className: String, methodName: String, params: List<String>?, tag: String?, cb: PatchInterceptCallback): PatchHandle

As onEnter, but cb may rewrite the arguments or skip the body — see PatchInterceptCallback.

Link copied to clipboard
private fun keyFor(className: String, methodName: String, params: List<String>?, tag: String?, phase: Patch.Phase, seq: Int): String

The install call echoed back, deliberately NOT the resolved methods: the caller reads their own spelling with no namespace to undo, and the id cannot outgrow what they typed. What was woven is PatchHandle.targets.

Link copied to clipboard
fun modify(className: String, methodName: String, params: List<String>?, tag: String?, cb: PatchModifyCallback): PatchHandle

As onExit, but cb may replace the return value — see PatchModifyCallback.

Link copied to clipboard
fun onEnter(className: String, methodName: String, params: List<String>?, tag: String?, cb: PatchEnterCallback): PatchHandle

Patch the method(s) named methodName on className to fire cb on entry. params selects a single overload — see Patches.onEnter for its spelling — or null to weave every overload of that name. Callers pass mojmap class/method names. Returns a handle exposing the fire count, the woven targets, and the id Patches.remove takes to restore the original bytecode.

Link copied to clipboard
fun onExit(className: String, methodName: String, params: List<String>?, tag: String?, cb: PatchExitCallback): PatchHandle

As onEnter, but woven at every exit — normal return and thrown exception alike.

Link copied to clipboard
internal fun warm()

Load and parse everything a first install would, WITHOUT a retransform — that is a stop-the-world pause proportional to the number of classes the JVM has loaded, and it warms nothing this does not.

Link copied to clipboard
private fun weave(builder: DynamicType.Builder<*>, td: TypeDescription, loader: ClassLoader?, w: Patch.Weave, woven: MutableMap<Any, List<Woven>>): DynamicType.Builder<*>

The per-type transform body. It re-derives the matching methods from the type being transformed, so the PENDING path — which had nothing to check at install time — still enforces the one-signature rule here. It cannot throw usefully at this point: a ClassFileTransformer throwable is swallowed by the JVM and the class loads untransformed, and the caller returned long ago. So an ambiguous late resolution refuses to weave and says so, and PatchHandle.targets reports the empty result.