Skip to content

Instantly share code, notes, and snippets.

@Mumfrey
Created July 11, 2023 19:41
Show Gist options
  • Save Mumfrey/000438f56f23b91c3276d4deba4a412e to your computer and use it in GitHub Desktop.
Save Mumfrey/000438f56f23b91c3276d4deba4a412e to your computer and use it in GitHub Desktop.
Mixin Examples from 2015
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered.org <http://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.mod.mixin;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.item.ItemStack;
import net.minecraft.world.World;
import org.spongepowered.asm.mixin.Implements;
import org.spongepowered.asm.mixin.Interface;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.exampleinterfaces.IEntityPlayerConflict;
/**
* Mixin demonstrating how "soft implementation" works
*/
@Mixin(EntityPlayer.class)
// This is the soft implementation, the @Implements annotation can actually take multiple @Interface annotations so it is still possible for a mixin
// to implement multiple conflicting interfaces, should such a situation ever arise. The original implements clause is commented out, removing the
// comment will cause a compiler error
@Implements(@Interface(iface = IEntityPlayerConflict.class, prefix = "entityPlayer$"))
public abstract class MixinEntityPlayerExample extends EntityLivingBase { // implements IEntityPlayerConflict {
/**
* ctor, not used
*
* @param worldIn The world to spawn the player in
*/
public MixinEntityPlayerExample(World worldIn) {
super(worldIn);
}
// ===============================================================================================================================================
// Methods below demonstrate use of the soft implementation declared on this mixin class.
// ===============================================================================================================================================
/**
* Conflicting method, now magically safe to implement because the prefix makes it compile
*
* @return The player's health
*/
public double entityPlayer$getHealth() {
return this.getHealth();
}
/**
* This non-conflicting method is also prefixed, this is recommended for soft implementations because there is no {@link Override} annotation and
* thus if the method in the underlying interface changes, there is no compile-time error which indicates this. By using the prefix even on
* non-conflicting methods, the transformer can verify that the method exists in the target interface at application time.
*
* @return The number 0
*/
public int entityPlayer$thisMethodDoesNotConflict() {
return 0;
}
/**
* This method doesn't conflict, but is not tagged with the prefix. Whilst this is totally legal, it's a bad idea because there is then no way
* to detect errors when the underlying interface changes, see the notes on {@link #entityPlayer$thisMethodDoesNotConflict}
*
* @return The number 0
*/
public int norDoesThisOne() {
return 0;
}
// ===============================================================================================================================================
// Declaring the implementation of a conflicting method
// ===============================================================================================================================================
/**
* We need this shadow field because it is referenced in the base implementation of isUsingItem()
*/
@Shadow private ItemStack itemInUse;
/**
* This method does something custom, we want to inject a call to this method into isUsingItem
*/
private void doSomethingCustom() {
// System.err.println("I like big butts and I can not lie");
}
/**
* <p>This comes first in the file for a reason, but you should read the javadoc for {@link #isUsingItem} first, then come back and read this...
* Go on! Do it!</p>
*
* <p>Okay, so you understand why we have the method below, it injects our custom code in the target class's method by overwriting the method
* body with the new code. However in order to preserve that functionality across the obfuscation boundary we need to tag it with
* {@link Overwrite}.</p>
*
* <p>The magic happens here. Because this method is <b>not</b> tagged with {@link Overwrite}, it will <b>not</b> be obfuscated at build time,
* this means it still implements the interface. At dev time, the method below (because it appears <b>after</b> this one) will be injected and
* will <em>overwrite <b>this</b> method</em>. This is exactly what we want to happen, because otherwise this method (at dev time) would actually
* end up just calling itself recursively!</p>
*
* <p>However, post-obfuscation, this method magically becomes an accessor for the (now renamed) isUsingItem() in the target class, and thus
* allows <em>both</em> the custom code to be injected into the original method (by the declaration below) <em>and</em> the interface to be
* implemented all at once.</p>
*
* <p>See the example below for where custom code is <b>not</b> required in the accessor</p>.
*
* @return Whether the player is using the item
*/
public boolean entityPlayer$isUsingItem() {
return this.isUsingItem();
}
/**
* <p>It should be pretty obvious that because this method exists in target class {@link EntityPlayer} <em>and</em> in the interface
* {@link IEntityPlayerConflict} that we don't <em>actually</em> need an implementation here at dev time, because the underlying method in the
* target class already implicitly 'implements' the method in the interface. We only need to {@link Overwrite} it if we need to include some
* custom functionality as shown here. However of course the problems start when we traverse the obfuscation boundary, since the method ends up
* actually named "func_71039_bw" and thus no longer implements the interface!</p>
*
* <p>We need the {@link Overwrite} annotation in order to have this method renamed, but we don't want to break the interface. So how do we do
* that? See {@link #entityPlayer$isUsingItem} above for how.</p>
*
* @return Whether the player is using the item
*/
@Overwrite
public boolean isUsingItem() {
// Custom thing
this.doSomethingCustom();
// Essentially the base implementation of isUsingItem
return this.itemInUse != null;
}
// ===============================================================================================================================================
// But what if we DON'T want to inject custom code into the target method? We just want essentially an accessor?
// ===============================================================================================================================================
/**
* <p>Then things become laughably simple. In a nutshell, we just copy-paste the target method into our mixin!</p>
*
* <p><em>What? It's that simple?</em></p>
*
* <p><b>Yes!</b> And it's because of the nature of {@link Overwrite}, notice how our new champion <em>is not</em> annotated with
* {@link Overwrite}. The {@link Overwrite} annotation is used to indicate to the annotation processor that the method should be obfuscated
* <em>as if it existed in the target class</em>, by omitting the annotation this method with simply replace the target method at dev time, and
* exist in tandem with it in production.</p>
*
* <p><em>So what's the catch?</em><p>
*
* <p>Firstly, unless we're talking about reasonably simple accessor methods like this one, copy-pasting an implementation of an entire method
* could be pretty overkill and result in a lot of {@link Shadow} fields being required. Secondly, it won't work if the implementation in the
* target class is actually an override of a method in a parent class. For the former scenario, a design rethink may be required, or use of other
* bytecode engineering techniques. For the latter, see below.</p>
* /
public boolean isUsingItem() {
// The base implementation of isUsingItem
return this.itemInUse != null;
}
//
===============================================================================================================================================
// So what do we do if the method is an override?
//
===============================================================================================================================================
/**
* <p>Naturally, if the method in the target class in an override, we run into the same scenario as using {@link Overwrite}, namely that the
* methodwill be included in the obfuscation AST (by its relationship to the parent class) and will thus get obfuscated even though we don't want
* it to.</p>
*
* <p>The solution this time is to use only soft implementation, this replicates the behaviour above where the method will be simply replaced at
* dev time, and co-exist at production time, but <em>won't</em> be obfuscated by virtue of the fact that the prefix decouples the method from its
* superclass counterpart.</p>
* /
public boolean entityPlayer$isUsingItem() {
// The base implementation of isUsingItem
return this.itemInUse != null;
}
*/
}
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered.org <http://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.mod.mixin;
import net.minecraft.tileentity.MobSpawnerBaseLogic;
import net.minecraft.util.EnumParticleTypes;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
* Mixin which demonstrates the use of the {@link ModifyArg} and {@link Redirect} annotations.
*/
@Mixin(MobSpawnerBaseLogic.class)
public abstract class MixinMobSpawnerBaseLogic {
/**
* <p>If you pay a brief visit to {@link MobSpawnerBaseLogic#updateSpawner} you'll notice the following calls in the method body:</p>
*
* <blockquote><pre>
* this.getSpawnerWorld().spawnParticle(EnumParticleTypes.SMOKE_NORMAL, d0, d1, d2, 0.0D, 0.0D, 0.0D, new int[0]);
* this.getSpawnerWorld().spawnParticle(EnumParticleTypes.FLAME, d0, d1, d2, 0.0D, 0.0D, 0.0D, new int[0]);</pre>
* </blockquote>
*
* <p>The purpose of the {@link ModifyArg} annotation is to modify <b>exactly one<b> argument from a method invokation. Specifically by having
* the annotated callback method <em>receive</em> and then <em>return</em> the value in question. This allows the method call to be "proxied"
* in a limited way, modifying a single argument.</p>
*
* <p>Two variations of this hook are available:</p>
* <ul>
* <li>The single-argument hook simply accepts <b>only</b> the argument in question. If there is only a single argument of that type then no
* further information is required and the hook will receive and then return the modified value. In our example this would be leveraged by
* a method with the signature <code>private EnumParticleTypes onSpawnParticle(EnumParticleTypes pt)</code> because there is only a single
* argument with the <em>EnumParticleTypes</em> type in the method signature. For methods with multiple args of the same type, the <em>index
* </em> property must be specified to identify the target argument.</li>
* <li>The multi-argument hook accepts <b>all</b> the original arguments to the method (as in this example) but can only modify the argument
* specified by the <em>return type</em> of the hook method. If multiple args of the same type exist, then the <em>index</em> property must
* likewise be specified.</li>
* </ul>
*
* <p>This hook does not interrupt the normal execution of the method, it only allows a single parameter to be modified.</p>
*
* @param x The x coordinate
* @param y The y coordinate
* @param z The z coordinate
* @param a a
* @param b b
* @param c c
* @param params params
*/
@ModifyArg(method = "updateSpawner()V", at =
@At(value = "INVOKE", target = "Lnet/minecraft/world/World;spawnParticle(Lnet/minecraft/util/EnumParticleTypes;DDDDDD[I)V"))
private EnumParticleTypes onSpawnParticle(EnumParticleTypes pt, double x, double y, double z, double a, double b, double c, int... params) {
if (pt == EnumParticleTypes.SMOKE_NORMAL) {
return EnumParticleTypes.SPELL;
} else if (pt == EnumParticleTypes.FLAME) {
return EnumParticleTypes.HEART;
}
return pt;
}
/**
* <p>{@link Redirect} annotations allow a method call to be proxied or even completely suppressed by redirecting the original method call to the
* annotated method.</p>
*
* <p>In this example, the {@link MobSpawnerBaseLogic#resetTimer} method is hooked and redirected to this handler. The signature of the hook
* method must match the redirected method precisely with the addition of a new first argument which must match the type of the invocation's
* target, in this case {@link MobSpawnerBaseLogic}. This first variable accepts the reference that the method was going to be invoked upon prior
* to being redirected.</p>
*
* <p>The benefit with {@link Redirect} versus ordinary method call injections, is that the call to the method can be conditionally suppressed if
* required, and also allows a more sophisticated version of {@link ModifyArg} to be enacted since all parameters are available to the hook method
* and can be altered as required.</p>
*
* <p>For <em>static</em> methods the handler must also be <em>static</em>, and the first argument can be omitted.</p>
*
* @param this$0 this$0
*/
@Redirect(method = "updateSpawner()V", at = @At(value = "INVOKE", target = "Lnet/minecraft/tileentity/MobSpawnerBaseLogic;resetTimer()V"))
private void onResetTimer(MobSpawnerBaseLogic this$0) {
// Do some stuff before calling the method
System.err.println("The timer for " + this + " was reset!");
// We could execute some logic here if required
boolean someCondition = true;
// Call the original method, but only if someCondition
if (someCondition) {
this.resetTimer();
}
}
@Shadow
private void resetTimer() {
}
;
}
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered.org <http://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.mod.mixin;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.BlockPos;
import net.minecraft.world.EnumSkyBlock;
import net.minecraft.world.World;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.exampleinterfaces.IWorld;
/**
* <p>This is an example Mixin class. This code will be merged into the class(es) specified in the {@link Mixin} annotation at load time and any
* interfaces implemented here will be monkey-patched onto the target class as well. Mixin classes <b>must</b> have the same superclass as their
* target class, thus calls to super.whatever() and any methods in the parent class will be correct.</p>
*
* <p>In order to provide support for referring to fields and methods in the target class which are not accessible, "shadow" methods and fields
* may be created in order to allow mixin code to reference them. These shadow methods and fields must be annotated with the {@link Shadow}
* annotation in order for the obfuscator to locate and appropriately transform them when doing a production build. In general, shadow methods will
* be declared as <i>abstract</i> but this is not a requirement.</p>
*
* <p>See below for examples.</p>
*/
@Mixin(World.class)
public abstract class MixinWorldExample implements IWorld {
/**
* A regular field, this will be merged into the target class and so will its initialiser. Note that the initialiser will run because the field
* is static. Non-static field initialisers <b>will be ignored</b> so if you add non-static fields be sure to initialise them somewhere else!
*/
@SuppressWarnings("unused")
private static final Logger logger = LogManager.getLogger("sponge");
/**
* A shadow field, describing a field in the World class which we want to be able to reference in code
*/
@Shadow
private int ambientTickCountdown;
/**
* A shadow method, describing a method in the World class which we would like to invoke. The parameter names are not important, only the types
* so we can change them to prevent checkstyle from bitching
*/
@Shadow
abstract int func_175638_a(BlockPos pos, EnumSkyBlock block);
/**
* Another shadow method
*/
@Shadow
abstract void notifyNeighborsOfStateChange(BlockPos pos, Block block);
// ===============================================================================================================================================
// Methods below demonstrate how to deal with name overlaps when a shadow method is named the same as an interface method
// ===============================================================================================================================================
/**
* This shadow method demonstrates use of the "prefix" option in the {@link Shadow} annotation. Since it is not possible to have two methods in a
* a class which differ only on return type, this can create problems when a shadow method overlaps with a method in an interface being
* implemented by a mixin. Luckily, the JVM itself actually supports such overlaps, and thus we can work around the problem by renaming the
* overlapping methods at runtime. Using the "prefix" option allows this behaviour to be leveraged. For more details see {@link Shadow#prefix}.
*
* @param pos The position
* @return The blockstate
*
*/
@Shadow(prefix = "shadow$")
abstract IBlockState shadow$getBlockState(BlockPos pos);
// ===============================================================================================================================================
// Methods below implement the IWorld interface which is applied to the target class at load time
// ===============================================================================================================================================
/* (non-Javadoc)
* @see org.spongepowered.mixin.interfaces.IWorld#getAmbientTickCountdown()
*/
@Override
public int getAmbientTickCountdown() {
// reference the shadow field, at runtime this will reference the "real" field in the target class
return this.ambientTickCountdown;
}
/* (non-Javadoc)
* @see org.spongepowered.mixin.interfaces.IWorld#exampleMethodToComputeLightValue(int, int, int, net.minecraft.world.EnumSkyBlock)
*/
@Override
public int exampleMethodToComputeLightValue(int x, int y, int z, EnumSkyBlock block) {
// invoke the shadow method, at runtime this will invoke the "real" method in the target class
return this.func_175638_a(new BlockPos(x, y, z), block);
}
/**
* This method implements getBlock from the {@link IWorld} interface. However since the method signature overlaps with the "getBlock" method
* above, it is necessary to use the {@link Shadow#prefix} functionality in the {@link Shadow} annotation to prevent a name clash at compile
* time.
*
* @see org.spongepowered.exampleinterfaces.IWorld#getBlock(int, int, int)
*
* @param x The x coordinate
* @param y The y coordinate
* @param z The z coordinate
*
* @return The block
*/
@Override
public Object getBlock(int x, int y, int z) {
// Invoke the original "getBlock" method in World and return its value
return this.shadow$getBlockState(new BlockPos(x, y, z)).getBlock();
}
// ===============================================================================================================================================
// Methods below actually overwrite methods in the target class
// ===============================================================================================================================================
/**
* <b>Overwrites</b> the <em>NotifyBlockChange</em> method in the target class
*
* @param pos The block location
* @param block The block
*/
@Overwrite
public void func_175722_b(BlockPos pos, Block block) {
// Uncomment this for spam! (and to check that this is working) notice that we can happily refer to the static field in this class since the
// mixin transformer will update references to injected fields at load time. Thus qualifiers for private fields should always use the mixin
// class itself.
// MixinWorld.logger.info("Spam! Block was changed at {}, {}, {} in {}", x, y, z, this);
// This method is called in the original notifyBlockChange method
this.notifyNeighborsOfStateChange(pos, block);
}
// ===============================================================================================================================================
// Methods below show things which are NOT ALLOWED in a mixin. Uncomment any of them to experience full derp mode.
// ===============================================================================================================================================
// /**
// * This <b>Non-static</b> field has an initialiser. Note that since instance initialisers are not (currently) injected, primitive types
// will get
// * their default value (eg. zero in this case) and reference types will be <em>null</em>!
// */
// private int someValue = 3;
//
// /**
// * Public, package-private, or protected static members <b>cannot<b> be injected into a target class, there is no point in doing so since
// there
// * is no feasible way to invoke the injected method anyway. If you need to inject static methods, make them private.
// */
// public static void illegalPublicStaticMethod()
// {
// // derp
// }
//
// /**
// * Attempting to {@link Shadow} or {@link Overwrite} a method which doesn't exist is a detectable error condition. The same applies to
// shadow
// * fields.
// */
// @Shadow public abstract void methodWhichDoesntExist();
}
/**
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered.org <http://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.mod.mixin;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;
import net.minecraft.world.WorldProvider;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.fml.relauncher.SideOnly;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
/**
* This mixin demonstrates how injectors work. Injection methods are merged into the target class just like regular mixin methods, the key difference
* being that they then inject a call to themselves into another method, designated the "target method". The actual position of the injected callback
* is determined by the values specified in the "at" parameter of the {@link Inject} annotation. A full explanation of the various {@link At} options
* is beyond the scope of this example but a number of common examples are provided below.
*/
@Mixin(WorldProvider.class)
public abstract class MixinWorldProviderExample {
/**
* <p>This method demonstrates injecting into a constructor in the target class. The method name "<init>" is the bytecode name for a constructor
* and so unlike Java it is necessary to refer to the constructor using this name instead of the class name</p>
*
* <p>Injected callback method signatures must match the target method signature with the distinction of always returning void and taking a
* single additional parameter which will either be a {@link CallbackInfo} or a {@link CallbackInfoReturnable} depending on the method's return
* type. Methods which return void will require a {@link CallbackInfo} and methods which return a value will require
* {@link CallbackInfoReturnable}.</p>
*
* <p>For a constructor, the only allowed value for "at" is RETURN, this is because injecting into a partly-initialised object can have unforseen
* side-effects and for this reason injections are restricted to after the object is guaranteed to be fully initialised.</p>
*
* <p>It is recommended that injected methods always begin with "on", this helps them be distinguished in stack traces and makes their purpose
* immediately clear when reading the mixin code.</p>
*/
@Inject(method = "<init>", at = @At("RETURN"))
private void onConstructed(CallbackInfo ci) {
// Uncomment for science
// System.err.println("Oh look, I'm being called from the constructor!");
}
/**
* <p>This method demonstrates injecting into a method with a return value. Notice that we take the original method, change the return type to
* <b>void</b> and add a {@link CallbackInfoReturnable} with the original return type ({@link Vec3}) as the type parameter.</p>
*
* <p>This method also demonstrates a more precise syntax for identifying the target method. This is useful if there are several methods in the
* target class with the same name. We simply append the bytecode descriptor of the target method to the method name. For more details on this
* syntax see the javadoc in {@link org.spongepowered.asm.mixin.injection.struct.MemberInfo}.</p>
*
* <p>The {@link At} specified HEAD will inject the callback at the top of the method (before all other code).</p>
*
* @param celestialAngle The celestial angle
* @param partialTicks The partial ticks
* @param cir The callback
*/
@Inject(method = "getFogColor(FF)Lnet/minecraft/util/Vec3;", at = @At("HEAD"))
@SideOnly(Side.CLIENT)
public void onGetFogColor(float celestialAngle, float partialTicks, CallbackInfoReturnable<Vec3> cir) {
// Uncomment for science
// System.err.println("The fog colour! It are being gotten!");
}
/**
* <p>This method demonstrates the use of the <em>cancellable</em> argument for an injection. Specifying that an injection is <em>cancellable</em>
* allows us to supply our own return values and short-circuit the target method's normal logic.</p>
*
* <p>Choosing the appropriate {@link At} is very important when dealing with cancellable callbacks. For example you may with to be able to
* "short-circuit" a method by injecting at the HEAD and cancelling it if you don't want the method to be executed. However if you want the method
* to execute but be able to change the result, then injecting at RETURN makes more sense. Injecting at RETURN also allows you to see the value
* the method was going to return and optionally change it. The {@link CallbackInfoReturnable#getReturnValue} method can be used to get the return
* value from the stack, but <b>only</b> when using the RETURN injection point.</p>
*
* <p>It should be noted that it's perfectly possible to specify <em>cancellable</em> when injecting into a method which returns void, but with
* the key difference being that it's not possible to fetch the return value (because there isn't one) or set a return value (because there isn't
* one!) but it is still perfectly possible to short-circuit a method in this way.</p>
*
* @param x The x coordinate
* @param z The z coordinate
* @param cir The callback
*/
@Inject(method = "canCoordinateBeSpawn", at = @At("RETURN"), cancellable = true)
public void onCanCoordinateBeSpawn(int x, int z, CallbackInfoReturnable<Boolean> cir) {
int coordinateWeDontLike = 666;
if (x == coordinateWeDontLike || z == coordinateWeDontLike) {
if (cir.getReturnValue()) {
System.err.println("WorldProvider " + this + " thought that " + x + ", " + z + " was a good spot for spawn. But I disagree!");
}
// Note that calling setReturnValue in a non-cancellable callback will throw a CancellationException!
cir.setReturnValue(false);
}
}
/**
* <p>What's this? A parameterised {@link At}? Surely not!</p>
*
* <p>{@link org.spongepowered.asm.mixin.injection.points.MethodHead HEAD} and
* {@link org.spongepowered.asm.mixin.injection.points.BeforeReturn RETURN} are only two of the available values for {@link At} types and are the
* most straightforward to understand. HEAD only ever makes a single injection (at the head of the method) and RETURN injects before <em>every
* RETURN opcode</em> in a method. Other injection types are available however:</p>
*
* <dl>
* <dt>{@link org.spongepowered.asm.mixin.injection.points.BeforeInvoke INVOKE}</dt>
* <dd>searches for method invocations matching its parameters and injects immediately prior to any matching invocations</dd>
* <dt>{@link org.spongepowered.asm.mixin.injection.points.BeforeFieldAccess FIELD}</dt>
* <dd>searches for field accesses (get or set) matching its parameters and injects immediately prior to any matching access</dd>
* <dt>{@link org.spongepowered.asm.mixin.injection.points.BeforeNew NEW}</dt>
* <dd>searches for object instantiation (<b>new</b> keywords) matching its parameters and injects prior to the NEW opcode</dd>
* <dt>{@link org.spongepowered.asm.mixin.injection.points.BeforeStringInvoke INVOKE_STRING}</dt>
* <dd>is a specialised version of INVOKE which searches for a method invocation of a method which accepts a single String argument and also
* matches the specified string literal. This is very useful for finding calls to Profiler::startSection() with a particular argument.</dd>
* <dt>{@link org.spongepowered.asm.mixin.injection.points.JumpInsnPoint JUMP}</dt>
* <dd>searches for specific JUMP opcodes</dd>
* <dt><em>Fully-qualified class name</em></dt>
* <dd>Allows you to specify a custom class which extends {@link org.spongepowered.asm.mixin.injection.InjectionPoint} to implement any custom
* logic you wish</dd>
* </dl>
*
* <p>The specific arguments accepted by each type of invokation are described in each class's javadoc. This example shows a simple use of the
* INVOKE type.</p>
*
* <p>This is what the code in the target method looks like:</p>
* <blockquote><pre>
* this.worldObj = worldIn;
* this.terrainType = worldIn.getWorldInfo().getTerrainType();
* this.generatorSettings = worldIn.getWorldInfo().getGeneratorOptions();
* // we want to inject a callback to our method here, immediately prior to calling registerWorldChunkManager
* this.registerWorldChunkManager();
* this.generateLightBrightnessTable();
* </pre></blockquote>
* <p>Having identified the target method, we simply supply the method name as the <em>target</em> argument to the {@link At} annotation. Note
* that
* unlike the <em>method</em> parameter (which <b>must</b> refer to a method in the target class) the <em>target</em> parameter for the {@link At}
* <b>must</b> be a <em>fully-qualified</em> member reference (include both the owner and signature) because the obfuscation processor requires
* this information in order to look up the target member in the obfuscation tables.</p>
*
* @param worldIn The world to register
* @param ci The callback on register
*/
@Inject(method = "registerWorld", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/WorldProvider;registerWorldChunkManager()V"))
public void onRegisterWorld(World worldIn, CallbackInfo ci) {
// I will be called immediately before the call to registerWorldChunkManager. Just like magic.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment