Created
July 11, 2023 19:41
-
-
Save Mumfrey/000438f56f23b91c3276d4deba4a412e to your computer and use it in GitHub Desktop.
Mixin Examples from 2015
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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 contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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