如何在Mixin中定位i++语句并按需取消其执行
Minecraft Forge 字节码注入:取消特定条件下的
i++执行 问题场景
我在AnvilScreenHandler类的updateResult()方法中有如下逻辑:
public void updateResult() { ItemStack itemStack = this.input.getStack(0); int i = 0; int j = 0; int k = 0; /***/ for (int m = 0; l > 0 && m < itemStack3.getCount(); m++) { int n = itemStack2.getDamage() - l; itemStack2.setDamage(n); i++; l = Math.min(itemStack2.getDamage(), itemStack2.getMaxDamage() / 4); } /***/ }
需要实现:当itemStack2.getItem() instanceof MyInterface时,跳过循环内的i++执行,但无法准确定位这个自增语句。
尝试过的失败方案
@Inject定位失败
尝试在setDamage调用后注入逻辑,编译报错:@Inject(method = "updateResult", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;setDamage(I)V", shift = At.Shift.BY, by = 2)) private void resetRepair(CallbackInfo ci, @Local(ordinal = 1) ItemStack itemStack2, @Local(ordinal = 0) LocalRef<Integer> i) { if (itemStack2.getItem() instanceof MyInterface) i.set(0); }错误信息:
@Local does not match any or matched multiple local variables in the target method
@Redirect找不到目标
不清楚i++对应的字节码target,写法陷入困境:@Redirect(method = "updateResult", at = @At(value = "INVOKE", target = "WHAT IS THE TARGET OF i++"))@ModifyConstant/@ModifyVariable无效- 使用
@ModifyConstant(method = "updateResult", constant = @Constant(intValue = 1))无法定位目标行 - 使用
@ModifyVariable(method = "updateResult", at = @At("STORE"))无法定位,日志输出变量值为0或15,非预期的1
- 使用
正确解决方案
方案一:用@ModifyVariable修改自增增量
i++对应的字节码是IINC指令,直接针对这个指令修改增量值:
import net.minecraft.item.ItemStack; import net.minecraft.screen.AnvilScreenHandler; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.At; @Mixin(AnvilScreenHandler.class) public class AnvilScreenHandlerMixin { @ModifyVariable( method = "updateResult", at = @At(value = "INC", ordinal = 0), // ordinal需根据实际字节码确认,对应循环内的i++ argsOnly = true ) private int cancelIncrement(int increment, @Local ItemStack itemStack2) { // 满足条件时返回0(不增加),否则返回原增量1 return itemStack2.getItem() instanceof MyInterface ? 0 : increment; } }
注意:
ordinal的值需要通过查看updateResult的字节码确认,因为方法中可能存在多个IINC指令,要确保选中循环里的i++对应的那一个。
方案二:用@Redirect拦截IINC操作
直接拦截自增指令,根据条件决定是否执行:
import net.minecraft.item.ItemStack; import net.minecraft.screen.AnvilScreenHandler; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.At; @Mixin(AnvilScreenHandler.class) public class AnvilScreenHandlerMixin { @Redirect( method = "updateResult", at = @At(value = "IINC", ordinal = 0) ) private void redirectIncrement(AnvilScreenHandler instance, int index, int increment, @Local ItemStack itemStack2) { // 仅当不满足条件时执行自增 if (!(itemStack2.getItem() instanceof MyInterface)) { // 由于i是局部变量,方案一的ModifyVariable更简便,此例仅作演示 try { var field = AnvilScreenHandler.class.getDeclaredField("i"); // 注意:如果i是局部变量,此方法无效 field.setAccessible(true); field.setInt(instance, field.getInt(instance) + increment); } catch (Exception e) { e.printStackTrace(); } } } }
注意:如果
i是方法内的局部变量,反射字段的方式不可行,此时优先选择方案一。
原错误原因解析
- 之前
@Inject的LocalRef<Integer> i写法错误,局部变量不能用LocalRef包装,且shift = At.Shift.BY的偏移量计算不准确,导致无法正确捕获变量。 i++不是方法调用,因此不能用@Redirect的INVOKE类型,必须针对IINC字节码指令。
内容的提问来源于stack exchange,提问作者DanilMK
相关产品推荐
相关产品推荐

