Skip to content

Commit

Permalink
Compat WurstClient
Browse files Browse the repository at this point in the history
  • Loading branch information
Hendrix-Shen committed Aug 16, 2022
1 parent 319660f commit 8650769
Show file tree
Hide file tree
Showing 10 changed files with 403 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -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);
}
}

/**
* 時空追放.
* <p>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<Class<?>> tekitaiClass = Annotations.getValue(magicInterruption, "value", true);
List<String> tekitaiTarget = Annotations.getValue(magicInterruption, "targets", true);

ArrayList<String> 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<String> tekitaiTarget) {
List<MixinType> 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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<? extends LivingEntity> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions src/main/java/top/hendrixshen/tweakmyclient/util/RemapUtil.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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 {};
}
Loading

0 comments on commit 8650769

Please sign in to comment.