diff --git a/src/main/java/top/hendrixshen/tweakmyclient/compat/CompatMixinPlugin.java b/src/main/java/top/hendrixshen/tweakmyclient/compat/CompatMixinPlugin.java new file mode 100644 index 00000000..8bd4c3d8 --- /dev/null +++ b/src/main/java/top/hendrixshen/tweakmyclient/compat/CompatMixinPlugin.java @@ -0,0 +1,183 @@ +/* + * Copyright (c) Copyright 2020 - 2022 The Cat Town Craft and contributors. + * This source code is subject to the terms of the GNU Lesser General Public + * License, version 3. If a copy of the LGPL was not distributed with this + * file, You can obtain one at: https://www.gnu.org/licenses/lgpl-3.0.txt + */ +package top.hendrixshen.tweakmyclient.compat; + +import com.google.common.collect.Lists; +import net.fabricmc.tinyremapper.IMappingProvider.Member; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; +import org.spongepowered.asm.util.Annotations; +import top.hendrixshen.tweakmyclient.TweakMyClientMixinPlugin; +import top.hendrixshen.tweakmyclient.util.RemapUtil; +import top.hendrixshen.tweakmyclient.util.mixin.MixinType; +import top.hendrixshen.tweakmyclient.util.mixin.annotation.MagicAttack; +import top.hendrixshen.tweakmyclient.util.mixin.annotation.MagicInterruption; + +import java.util.ArrayList; +import java.util.List; + +public class CompatMixinPlugin extends TweakMyClientMixinPlugin { + @Override + public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { + super.postApply(targetClassName, targetClass, mixinClassName, mixinInfo); + + // Magical chanting. + ClassNode mixinClass = mixinInfo.getClassNode(0); + AnnotationNode magicInterruption = Annotations.getVisible(mixinClass, MagicInterruption.class); + + if (magicInterruption == null) { + return; + } + + for (MethodNode methodNode : mixinClass.methods) { + AnnotationNode magicAttack = Annotations.getVisible(methodNode, MagicAttack.class); + + if (magicAttack == null) { + continue; + } + + this.jikuTsuiho(targetClass, methodNode, magicInterruption, magicAttack); + } + } + + /** + * 時空追放. + *

Erase target. + * @param targetClass Injection target class. + * @param magicAttackMethodNode Spell casting MethodNode. + * @param magicInterruption Spell casting interruption AnnotationNode. + * @param magicAttack Magical attacking AnnotationNode. + */ + private void jikuTsuiho(ClassNode targetClass, MethodNode magicAttackMethodNode, AnnotationNode magicInterruption, AnnotationNode magicAttack) { + List> tekitaiClass = Annotations.getValue(magicInterruption, "value", true); + List tekitaiTarget = Annotations.getValue(magicInterruption, "targets", true); + + ArrayList tekitaiClasses = Lists.newArrayList(); + for (Class cls : tekitaiClass) { + String s = cls.getName(); + if (!tekitaiClasses.contains(s)) { + tekitaiClasses.add(s); + } + } + for (String s : tekitaiTarget) { + if (!tekitaiClasses.contains(s)) { + tekitaiClasses.add(s); + } + } + + MethodNode tekitaiMethod = this.findTekitaiMethod(targetClass, magicAttack, tekitaiClasses); + + if (tekitaiMethod == null) { + return; + } + + // Magic releasing. + + this.tsuihoTarget(targetClass, tekitaiMethod, magicAttackMethodNode, magicAttack); + + // Magic Harvesting. + targetClass.visibleAnnotations.removeIf(annotationNode -> annotationNode.desc.equals("Ltop/catowncraft/carpettctcaddition/util/mixin/annotation/MagicInterruption;")); + } + + /** + * Find the enemy's MethodNode. + * @param targetClass Injection target class. + * @param magicAttack Spell casting MethodNode. + * @param tekitaiTarget Enemy's class. + * @return Enemy's MethodNode. + */ + private MethodNode findTekitaiMethod(ClassNode targetClass, AnnotationNode magicAttack, ArrayList tekitaiTarget) { + List type = Annotations.getValue(magicAttack, "type", true, MixinType.class); + String name = Annotations.getValue(magicAttack, "name"); + int priority = Annotations.getValue(magicAttack, "priority", 1000); + for (MethodNode methodNode : targetClass.methods) { + if (!(methodNode.name.contains(type.get(0).getPrefix()) && methodNode.name.contains(name))) { + continue; + } + + AnnotationNode annotationNode = Annotations.getVisible(methodNode, MixinMerged.class); + String mixin = Annotations.getValue(annotationNode, "mixin"); + + if (!tekitaiTarget.contains(mixin)) { + continue; + } + + int targetPriority = Annotations.getValue(annotationNode, "priority"); + if (priority != targetPriority) { + continue; + } + + return methodNode; + } + return null; + } + + /** + * Erase target and infuse soul. + * @param targetClass Injection target ClassNode. + * @param tekitaiMethodNode Enemy's MethodNode. + * @param magicAttackMethodNode Spell casting MethodNode. + * @param magicAttack Magical attacking AnnotationNode. + */ + private void tsuihoTarget(ClassNode targetClass, MethodNode tekitaiMethodNode, MethodNode magicAttackMethodNode, AnnotationNode magicAttack) { + String method = Annotations.getValue(magicAttack, "method"); + String owner = Annotations.getValue(magicAttack, "owner"); + String desc = Annotations.getValue(magicAttack, "desc"); + Member remappedMethod = RemapUtil.mapMethod(owner, method, desc); + int ordinal = Annotations.getValue(magicAttack, "ordinal", -1); + boolean keep = Annotations.getValue(magicAttack, "keep", Boolean.FALSE); + + for (MethodNode methodNode : targetClass.methods) { + if (methodNode.name.equals(remappedMethod.name) && methodNode.desc.equals(remappedMethod.desc)) { + int offset = 0; + int found = 0; + int processed = 0; + + for(AbstractInsnNode abstractInsnNode : methodNode.instructions) { + if (!(abstractInsnNode instanceof MethodInsnNode)) { + continue; + } + + MethodInsnNode methodInsnNode = (MethodInsnNode) abstractInsnNode; + + if (!methodInsnNode.name.equals(tekitaiMethodNode.name) || + !methodInsnNode.desc.equals(tekitaiMethodNode.desc)) { + continue; + } + + if ((tekitaiMethodNode.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC && + methodInsnNode.getOpcode() != Opcodes.INVOKESTATIC) { + continue; + } + + if ((tekitaiMethodNode.access & Opcodes.ACC_STATIC) == 0 && + methodInsnNode.getOpcode() == Opcodes.INVOKESTATIC) { + continue; + } + + if (ordinal < 0 || ordinal == offset) { + MethodInsnNode invokeMethod = new MethodInsnNode(methodInsnNode.getOpcode(), methodInsnNode.owner, + magicAttackMethodNode.name, methodInsnNode.desc); + methodNode.instructions.insertBefore(methodInsnNode, invokeMethod); + methodNode.instructions.remove(methodInsnNode); + processed++; + } + + offset++; + found++; + + if (found == processed && !keep) { + targetClass.methods.remove(tekitaiMethodNode); + } + } + break; + } + } + } +} diff --git a/src/main/java/top/hendrixshen/tweakmyclient/compat/wurst/mixin/MixinLocalPlayer.java b/src/main/java/top/hendrixshen/tweakmyclient/compat/wurst/mixin/MixinLocalPlayer.java new file mode 100644 index 00000000..86e68a92 --- /dev/null +++ b/src/main/java/top/hendrixshen/tweakmyclient/compat/wurst/mixin/MixinLocalPlayer.java @@ -0,0 +1,42 @@ +package top.hendrixshen.tweakmyclient.compat.wurst.mixin; + +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +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.Redirect; +import top.hendrixshen.magiclib.dependency.annotation.Dependencies; +import top.hendrixshen.magiclib.dependency.annotation.Dependency; +import top.hendrixshen.tweakmyclient.config.Configs; +import top.hendrixshen.tweakmyclient.util.mixin.MixinType; +import top.hendrixshen.tweakmyclient.util.mixin.annotation.MagicAttack; +import top.hendrixshen.tweakmyclient.util.mixin.annotation.MagicInterruption; + +@MagicInterruption(targets = "net.wurstclient.mixin.ClientPlayerEntityMixin") +@Dependencies(and = @Dependency(value = "wurst")) +@Mixin(value = LocalPlayer.class, priority = 1100) +public abstract class MixinLocalPlayer extends LivingEntity { + @Shadow + private boolean startedUsingItem; + + protected MixinLocalPlayer(EntityType entityType, Level level) { + super(entityType, level); + } + + @MagicAttack( + type = MixinType.REDIRECT, + name = "wurstIsUsingItem", + owner = "class_1309", + method = "method_6007", + desc = "()V" + ) + private boolean tmc$getUsingItemState(LocalPlayer instance) { + if (Configs.disableSlowdown) { + return false; + } + return this.startedUsingItem; + } +} diff --git a/src/main/java/top/hendrixshen/tweakmyclient/mixin/disable/disableSlowdown/MixinLocalPlayer.java b/src/main/java/top/hendrixshen/tweakmyclient/mixin/disable/disableSlowdown/MixinLocalPlayer.java index 6e4fbce1..beffa2bc 100644 --- a/src/main/java/top/hendrixshen/tweakmyclient/mixin/disable/disableSlowdown/MixinLocalPlayer.java +++ b/src/main/java/top/hendrixshen/tweakmyclient/mixin/disable/disableSlowdown/MixinLocalPlayer.java @@ -8,8 +8,11 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; +import top.hendrixshen.magiclib.dependency.annotation.Dependencies; +import top.hendrixshen.magiclib.dependency.annotation.Dependency; import top.hendrixshen.tweakmyclient.config.Configs; +@Dependencies(not = @Dependency(value = "wurst")) @Mixin(LocalPlayer.class) public abstract class MixinLocalPlayer extends LivingEntity { @Shadow diff --git a/src/main/java/top/hendrixshen/tweakmyclient/util/RemapUtil.java b/src/main/java/top/hendrixshen/tweakmyclient/util/RemapUtil.java new file mode 100644 index 00000000..83460b0f --- /dev/null +++ b/src/main/java/top/hendrixshen/tweakmyclient/util/RemapUtil.java @@ -0,0 +1,41 @@ +package top.hendrixshen.tweakmyclient.util; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.MappingResolver; +import net.fabricmc.tinyremapper.IMappingProvider.Member; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RemapUtil { + private static final Pattern CLASS_FINDER = Pattern.compile("Lnet\\/minecraft\\/([^;]+);"); + private static final String INTERMEDIARY = "intermediary"; + private static final MappingResolver RESOLVER = FabricLoader.getInstance().getMappingResolver(); + + private static String fromIntermediaryDot(String className) { + return RESOLVER.mapClassName(INTERMEDIARY, String.format("net.minecraft.%s", className)); + } + + public static String getClassName(String className) { + return RemapUtil.fromIntermediaryDot(className).replace('.', '/'); + } + + public static String getMethodName(String owner, String methodName, String desc) { + return RemapUtil.RESOLVER.mapMethodName(INTERMEDIARY, "net.minecraft." + owner, methodName, desc); + } + + public static Member mapMethod(String owner, String name, String desc) { + return new Member(RemapUtil.getClassName(owner), RemapUtil.getMethodName(owner, name, desc), RemapUtil.mapMethodDescriptor(desc)); + } + + public static String mapMethodDescriptor(String desc) { + StringBuilder stringBuffer = new StringBuilder(); + + Matcher matcher = CLASS_FINDER.matcher(desc); + while (matcher.find()) { + matcher.appendReplacement(stringBuffer, Matcher.quoteReplacement('L' + RemapUtil.getClassName(matcher.group(1)) + ';')); + } + + return matcher.appendTail(stringBuffer).toString(); + } +} diff --git a/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/MixinType.java b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/MixinType.java new file mode 100644 index 00000000..d3bbd363 --- /dev/null +++ b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/MixinType.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) Copyright 2020 - 2022 The Cat Town Craft and contributors. + * This source code is subject to the terms of the GNU Lesser General Public + * License, version 3. If a copy of the LGPL was not distributed with this + * file, You can obtain one at: https://www.gnu.org/licenses/lgpl-3.0.txt + */ +package top.hendrixshen.tweakmyclient.util.mixin; + +public enum MixinType { + // ModifyVariable + MODIFY_ARG("modify"), + MODIFY_ARGS("args"), + MODIFY_CONSTANT("constant"), + MODIFY_VARIABLE("localvar"), + REDIRECT("redirect"); + + private final String prefix; + + MixinType(String prefix) { + this.prefix = prefix; + } + + public String getPrefix() { + return prefix; + } +} diff --git a/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicAttack.java b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicAttack.java new file mode 100644 index 00000000..6ed6c252 --- /dev/null +++ b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicAttack.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) Copyright 2020 - 2022 The Cat Town Craft and contributors. + * This source code is subject to the terms of the GNU Lesser General Public + * License, version 3. If a copy of the LGPL was not distributed with this + * file, You can obtain one at: https://www.gnu.org/licenses/lgpl-3.0.txt + */ +package top.hendrixshen.tweakmyclient.util.mixin.annotation; + +import top.hendrixshen.tweakmyclient.util.mixin.MixinType; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * You need magic to defeat magic. + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface MagicAttack { + /** + * The type of injector you want to erase the target. + * @return Injector Type. + */ + MixinType type(); + + /** + * Name of the method you want to erase the target. + * @return Target method name. + */ + String name(); + + /** + * From which owner you want to erase the target. Here you should use the intermediary name. + * @return Source method owner. + */ + String owner(); + + /** + * From which method you want to erase the target. Here you should use the intermediary name. + * @return Source method name. + */ + String method(); + + /** + * From which method you want to erase the target. Here you should use the intermediary name. + * @return Source method desc. + */ + String desc(); + + /** + * The point at which you want to erase the target. + * @return Offset(-1 means all). + */ + int ordinal() default -1; + + /** + * Priority of the method you want to erase the target. + * @return Priority. + */ + int priority() default 1000; + + /** + * Whether to retain the target original injection. + * @return Whether to keep. + */ + boolean keep() default false; +} diff --git a/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicInterruption.java b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicInterruption.java new file mode 100644 index 00000000..c78d74c3 --- /dev/null +++ b/src/main/java/top/hendrixshen/tweakmyclient/util/mixin/annotation/MagicInterruption.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) Copyright 2020 - 2022 The Cat Town Craft and contributors. + * This source code is subject to the terms of the GNU Lesser General Public + * License, version 3. If a copy of the LGPL was not distributed with this + * file, You can obtain one at: https://www.gnu.org/licenses/lgpl-3.0.txt + */ +package top.hendrixshen.tweakmyclient.util.mixin.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface MagicInterruption { + Class[] value() default {}; + + String[] targets() default {}; +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e5d35bad..2188686f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -29,7 +29,8 @@ ] }, "mixins": [ - "${mod_id}.mixins.json" + "${mod_id}.mixins.json", + "${mod_id}-compat-wurst.mixins.json" ], "depends": { "minecraft": "${minecraft_dependency}", diff --git a/src/main/resources/tweakmyclient-compat-wurst.mixins.json b/src/main/resources/tweakmyclient-compat-wurst.mixins.json new file mode 100644 index 00000000..178d36bd --- /dev/null +++ b/src/main/resources/tweakmyclient-compat-wurst.mixins.json @@ -0,0 +1,15 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "top.hendrixshen.tweakmyclient.compat.wurst.mixin", + "plugin": "top.hendrixshen.tweakmyclient.compat.CompatMixinPlugin", + "compatibilityLevel": "JAVA_8", + "mixins": [ + ], + "client": [ + "MixinLocalPlayer" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/resources/tweakmyclient.mixins.json b/src/main/resources/tweakmyclient.mixins.json index 0e310edd..997140dd 100644 --- a/src/main/resources/tweakmyclient.mixins.json +++ b/src/main/resources/tweakmyclient.mixins.json @@ -5,11 +5,10 @@ "plugin": "top.hendrixshen.tweakmyclient.TweakMyClientMixinPlugin", "compatibilityLevel": "JAVA_8", "mixins": [ - "accessor.PlayerTabOverlayAccessor", - "patch.litematicaSchematicWailaCompat.jade.MixinOverlayRenderer" ], "client": [ "accessor.MultiPlayerGameModeAccessor", + "accessor.PlayerTabOverlayAccessor", "disable.disableAttackEntity.MixinMinecraft", "disable.disableClientBlockEvents.MixinClientPacketListener", "disable.disableClientEntityRendering.MixinEntityRenderDispatcher", @@ -65,6 +64,7 @@ "patch.litematicaSchematicWailaCompat.wthit.MixinClientTickHandler", "patch.litematicaSchematicWailaCompat.MixinLocalPlayer", "patch.litematicaSchematicWailaCompat.hwyla.MixinOverlayRenderer", + "patch.litematicaSchematicWailaCompat.jade.MixinOverlayRenderer", "patch.litematicaSchematicWailaCompat.wthit.MixinTickHandler" ], "injectors": {