MixinProbe

object MixinProbe

What Mixin merges into its targets, read off Mixin's own model — the prepared configs and the MixinInfos under them — rather than by applying anything to a class.

Game-loader side: the ClassNode Mixin hands back belongs to the host's ASM, and the masking loader has its own copy of that class, so only Decls crosses the seam.

Why so little is carried

Mixin renames a merged member exactly when nothing outside could name it: an injector handler becomes handler$<uid>$…, a non-public @Unique becomes md<session>$…, and a static @Accessor lands on the target as …_$md$<session>$<n> — where session is a fresh UUID per launch, so a name recorded today is wrong tomorrow. The converse is what this rests on: what Mixin does not rename is exactly what source is allowed to write, because renaming it would break the callers.

So the overlay carries two things:

  • the INTERFACES a mixin adds. Their members are already declared on the interface's own class file, which is an ordinary classpath entry, so the target needs the implements entry and nothing more — and an interface name is never renamed.

  • PUBLIC merged members, for a mixin that adds them with no interface to reach them through.

and nothing else. Handlers are modifications, not additions. Static @Accessor/@Invoker are called as Iface.foo(), so the interface alone already compiles. Anything below public is out of a snippet's reach regardless — it is neither in the package nor a subclass.

Every touch is reflective and none of it is API. Any failure degrades to "no overlay", which costs a snippet a cast.

Types

Link copied to clipboard
class Decls(val interfaces: List<String>, val methods: List<MixinProbe.Member>, val fields: List<MixinProbe.Member>)

A class's declarations. Declared here, in a package the masking loader delegates, so both sides see one type and nothing has to be flattened to cross.

Link copied to clipboard
class Member(val name: String, val desc: String, val access: Int)

One declared member.

Properties

Link copied to clipboard

Target class name (dotted, as Mixin keys it) -> what mixins add to it. Derived once: by the time anything asks, every config has long been prepared.

Link copied to clipboard
private val CALLBACK: List<String>
Link copied to clipboard
private val CORE_NOT_ADDED: Set<String>
Link copied to clipboard
const val ENABLED: String

Kill switch for the declaration overlay.

Functions

Link copied to clipboard
Link copied to clipboard
private fun adds(access: Int, desc: String?, notAdded: Set<String>, vararg annotations: List<AnnotationNode>?): Boolean

Whether a mixin's own member is an ADDITION the target then carries under this name.

Link copied to clipboard
Link copied to clipboard
private fun collect(processor: Any): Map<String, MixinProbe.Decls>
Link copied to clipboard
private fun contribution(node: ClassNode, ifaces: Collection<String>, renamed: Set<String>, notAdded: Set<String>): MixinProbe.Decls

What ONE mixin adds to whichever target it was listed under. ifaces is Mixin's own answer; the members are its own declarations that survive adds — none for an accessor mixin, whose @Accessor/@Invoker are reached through ifaces instead.

Link copied to clipboard

name's own declarations, read straight off its class file. No Mixin involved: this is asked about an added INTERFACE, which is an ordinary classpath class. Null when it cannot be read.

Link copied to clipboard
Link copied to clipboard
private fun disqualifying(): Set<String>

Annotation descriptors that mark a member as something other than a plain addition.

Link copied to clipboard
private fun field(owner: Any, name: String): Any
Link copied to clipboard
private fun snapshot(processor: Any): Map<String, List<Any>>

target -> the mixins listed under it, copied out from under the processor's monitor.

Link copied to clipboard

The targets with something to graft — not every registered target, since one that adds nothing would only claim entries the overlay then copies unchanged.