diff --git a/src/main/java/moe/yushi/authlibinjector/transform/CallbackMetafactoryTransformer.java b/src/main/java/moe/yushi/authlibinjector/transform/CallbackMetafactoryTransformer.java deleted file mode 100644 index 6107155..0000000 --- a/src/main/java/moe/yushi/authlibinjector/transform/CallbackMetafactoryTransformer.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2021 Haowei Wen and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package moe.yushi.authlibinjector.transform; - -import static org.objectweb.asm.Opcodes.ACC_PRIVATE; -import static org.objectweb.asm.Opcodes.ACC_STATIC; -import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; -import static org.objectweb.asm.Opcodes.ALOAD; -import static org.objectweb.asm.Opcodes.ARETURN; -import static org.objectweb.asm.Opcodes.ASM9; -import static org.objectweb.asm.Opcodes.DUP; -import static org.objectweb.asm.Opcodes.INVOKESPECIAL; -import static org.objectweb.asm.Opcodes.INVOKESTATIC; -import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; -import static org.objectweb.asm.Opcodes.NEW; -import java.util.Optional; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; - -class CallbackMetafactoryTransformer implements TransformUnit { - - @Override - public Optional transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext context) { - return Optional.of(new ClassVisitor(ASM9, writer) { - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, access, name, signature, superName, interfaces); - - MethodVisitor mv = super.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, - CallbackSupport.METAFACTORY_NAME, - CallbackSupport.METAFACTORY_SIGNATURE, - null, null); - mv.visitCode(); - mv.visitTypeInsn(NEW, "java/lang/invoke/ConstantCallSite"); - mv.visitInsn(DUP); - mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false); - mv.visitVarInsn(ALOAD, 3); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false); - mv.visitVarInsn(ALOAD, 1); - mv.visitVarInsn(ALOAD, 2); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false); - mv.visitMethodInsn(INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false); - mv.visitInsn(ARETURN); - mv.visitMaxs(-1, -1); - mv.visitEnd(); - - context.markModified(); - } - }); - } - - @Override - public String toString() { - return "Callback Metafactory Transformer"; - } -} diff --git a/src/main/java/moe/yushi/authlibinjector/transform/CallbackSupport.java b/src/main/java/moe/yushi/authlibinjector/transform/CallbackSupport.java index 917ca34..f618378 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/CallbackSupport.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/CallbackSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Haowei Wen and contributors + * Copyright (C) 2022 Haowei Wen and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -16,17 +16,42 @@ */ package moe.yushi.authlibinjector.transform; +import static org.objectweb.asm.Opcodes.AASTORE; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_STATIC; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ANEWARRAY; +import static org.objectweb.asm.Opcodes.ARETURN; +import static org.objectweb.asm.Opcodes.DLOAD; +import static org.objectweb.asm.Opcodes.DRETURN; +import static org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.FLOAD; +import static org.objectweb.asm.Opcodes.FRETURN; +import static org.objectweb.asm.Opcodes.GETSTATIC; +import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; +import static org.objectweb.asm.Opcodes.ILOAD; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Opcodes.IRETURN; +import static org.objectweb.asm.Opcodes.LLOAD; +import static org.objectweb.asm.Opcodes.LRETURN; +import static org.objectweb.asm.Opcodes.NEW; +import static org.objectweb.asm.Opcodes.RETURN; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; -public final class CallbackSupport { +final class CallbackSupport { private CallbackSupport() { } - static final String METAFACTORY_NAME = "__authlibinjector_metafactory"; - static final String METAFACTORY_SIGNATURE = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;"; + private static final String METAFACTORY_NAME = "__authlibinjector_metafactory"; + private static final String METAFACTORY_SIGNATURE = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;"; private static Method findCallbackMethod(Class owner, String methodName) { for (Method method : owner.getDeclaredMethods()) { @@ -40,11 +65,123 @@ public final class CallbackSupport { throw new IllegalArgumentException("No such method: " + methodName); } - public static void invoke(TransformContext ctx, MethodVisitor mv, Class owner, String methodName) { - ctx.requireMinimumClassVersion(50); - ctx.upgradeClassVersion(51); - + static void callWithInvokeDynamic(MethodVisitor mv, Class owner, String methodName, TransformContext ctx) { String descriptor = Type.getMethodDescriptor(findCallbackMethod(owner, methodName)); - mv.visitInvokeDynamicInsn(methodName, descriptor, ctx.acquireCallbackMetafactory(), owner.getName()); + Handle callbackMetafactory = new Handle( + H_INVOKESTATIC, + ctx.getClassName().replace('.', '/'), + CallbackSupport.METAFACTORY_NAME, + CallbackSupport.METAFACTORY_SIGNATURE, + ctx.isInterface()); + mv.visitInvokeDynamicInsn(methodName, descriptor, callbackMetafactory, owner.getName()); + } + + static void callWithIntermediateMethod(MethodVisitor mv0, Class owner, String methodName, TransformContext ctx) { + Method callbackMethod = findCallbackMethod(owner, methodName); + String descriptor = Type.getMethodDescriptor(callbackMethod); + String intermediateMethod = "__authlibinjector_intermediate__" + owner.getName().replace('.', '_') + "__" + methodName; + mv0.visitMethodInsn(INVOKESTATIC, ctx.getClassName().replace('.', '/'), intermediateMethod, descriptor, ctx.isInterface()); + + ctx.addGeneratedMethod(intermediateMethod, cv -> { + int paramNum = callbackMethod.getParameterCount(); + Class[] paramTypes = callbackMethod.getParameterTypes(); + Class returnType = callbackMethod.getReturnType(); + + MethodVisitor mv = cv.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, intermediateMethod, descriptor, null, null); + mv.visitCode(); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "publicLookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false); + mv.visitLdcInsn(owner.getName()); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false); + mv.visitLdcInsn(methodName); + pushType(mv, returnType); + mv.visitLdcInsn(paramNum); + mv.visitTypeInsn(ANEWARRAY, "java/lang/Class"); + for (int i = 0; i < paramNum; i++) { + mv.visitInsn(DUP); + mv.visitLdcInsn(i); + pushType(mv, paramTypes[i]); + mv.visitInsn(AASTORE); + } + mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodType", "methodType", "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/invoke/MethodType;", false); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false); + for (int i = 0; i < paramNum; i++) { + Class type = paramTypes[i]; + if (type == boolean.class || type == byte.class || type == char.class || type == short.class || type == int.class) { + mv.visitVarInsn(ILOAD, i); + } else if (type == long.class) { + mv.visitVarInsn(LLOAD, i); + } else if (type == float.class) { + mv.visitVarInsn(FLOAD, i); + } else if (type == double.class) { + mv.visitVarInsn(DLOAD, i); + } else { + mv.visitVarInsn(ALOAD, i); + } + } + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", descriptor, false); + if (returnType == void.class) { + mv.visitInsn(RETURN); + } else if (returnType == boolean.class || returnType == byte.class || returnType == char.class || returnType == short.class || returnType == int.class) { + mv.visitInsn(IRETURN); + } else if (returnType == long.class) { + mv.visitInsn(LRETURN); + } else if (returnType == float.class) { + mv.visitInsn(FRETURN); + } else if (returnType == double.class) { + mv.visitInsn(DRETURN); + } else { + mv.visitInsn(ARETURN); + } + mv.visitMaxs(-1, -1); + mv.visitEnd(); + }); + } + + private static void pushType(MethodVisitor mv, Class type) { + if (type.isPrimitive()) { + if (type == boolean.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"); + } else if (type == byte.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;"); + } else if (type == char.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;"); + } else if (type == short.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;"); + } else if (type == int.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;"); + } else if (type == float.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;"); + } else if (type == long.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;"); + } else if (type == double.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;"); + } else if (type == void.class) { + mv.visitFieldInsn(GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;"); + } + } else { + mv.visitLdcInsn(Type.getType(type)); + } + } + + static void insertMetafactory(ClassVisitor visitor) { + MethodVisitor mv = visitor.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, + CallbackSupport.METAFACTORY_NAME, + CallbackSupport.METAFACTORY_SIGNATURE, + null, null); + mv.visitCode(); + mv.visitTypeInsn(NEW, "java/lang/invoke/ConstantCallSite"); + mv.visitInsn(DUP); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false); + mv.visitVarInsn(ALOAD, 3); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false); + mv.visitVarInsn(ALOAD, 1); + mv.visitVarInsn(ALOAD, 2); + mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "", "(Ljava/lang/invoke/MethodHandle;)V", false); + mv.visitInsn(ARETURN); + mv.visitMaxs(-1, -1); + mv.visitEnd(); } } diff --git a/src/main/java/moe/yushi/authlibinjector/transform/ClassTransformer.java b/src/main/java/moe/yushi/authlibinjector/transform/ClassTransformer.java index f35ccd5..28f3827 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/ClassTransformer.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/ClassTransformer.java @@ -22,20 +22,24 @@ import static moe.yushi.authlibinjector.util.Logging.Level.DEBUG; import static moe.yushi.authlibinjector.util.Logging.Level.INFO; import static moe.yushi.authlibinjector.util.Logging.Level.WARNING; import static org.objectweb.asm.Opcodes.ACC_INTERFACE; -import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; +import static org.objectweb.asm.Opcodes.ASM9; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; import moe.yushi.authlibinjector.Config; public class ClassTransformer implements ClassFileTransformer { @@ -50,8 +54,6 @@ public class ClassTransformer implements ClassFileTransformer { private class TransformContextImpl implements TransformContext { public boolean modifiedMark; - public int minVersionMark = -1; - public int upgradedVersionMark = -1; public boolean callbackMetafactoryRequested = false; @Override @@ -59,35 +61,40 @@ public class ClassTransformer implements ClassFileTransformer { modifiedMark = true; } - @Override - public void requireMinimumClassVersion(int version) { - if (this.minVersionMark < version) { - this.minVersionMark = version; - } - } - - @Override - public void upgradeClassVersion(int version) { - if (this.upgradedVersionMark < version) { - this.upgradedVersionMark = version; - } - } - - @Override - public Handle acquireCallbackMetafactory() { - this.callbackMetafactoryRequested = true; - return new Handle( - H_INVOKESTATIC, - className.replace('.', '/'), - CallbackSupport.METAFACTORY_NAME, - CallbackSupport.METAFACTORY_SIGNATURE, - TransformHandle.this.isInterface()); - } - @Override public List getStringConstants() { return TransformHandle.this.getStringConstants(); } + + @Override + public String getClassName() { + return className; + } + + @Override + public boolean isInterface() { + return TransformHandle.this.isInterface(); + } + + @Override + public void invokeCallback(MethodVisitor mv, Class owner, String methodName) { + boolean useInvokeDynamic = (getClassVersion() & 0xffff) >= 50; + + if (useInvokeDynamic) { + addCallbackMetafactory = true; + CallbackSupport.callWithInvokeDynamic(mv, owner, methodName, this); + } else { + CallbackSupport.callWithIntermediateMethod(mv, owner, methodName, this); + } + } + + @Override + public void addGeneratedMethod(String name, Consumer generator) { + if (generatedMethods == null) { + generatedMethods = new LinkedHashMap<>(); + } + generatedMethods.put(name, generator); + } } private final String className; @@ -97,9 +104,8 @@ public class ClassTransformer implements ClassFileTransformer { private List cachedConstants; private List appliedTransformers; - private int minVersion = -1; - private int upgradedVersion = -1; private boolean addCallbackMetafactory = false; + private Map> generatedMethods; public TransformHandle(ClassLoader classLoader, String className, byte[] classBuffer) { this.className = className; @@ -123,6 +129,11 @@ public class ClassTransformer implements ClassFileTransformer { return cachedConstants; } + private int getClassVersion() { + ClassReader reader = getClassReader(); + return reader.readInt(reader.getItem(1) - 7); + } + public void accept(TransformUnit... units) { long t0 = System.nanoTime(); @@ -168,43 +179,77 @@ public class ClassTransformer implements ClassFileTransformer { appliedTransformers = new ArrayList<>(); appliedTransformers.add(units[i]); - if (ctx.minVersionMark > this.minVersion) { - this.minVersion = ctx.minVersionMark; - } - if (ctx.upgradedVersionMark > this.upgradedVersion) { - this.upgradedVersion = ctx.upgradedVersionMark; - } this.addCallbackMetafactory |= ctx.callbackMetafactoryRequested; modified = true; } if (modified) { - classBuffer = writer.toByteArray(); - cachedClassReader = null; - cachedConstants = null; + updateClassBuffer(writer.toByteArray()); } } + private void injectCallbackMetafactory() { + log(DEBUG, "Adding callback metafactory"); + + int classVersion = getClassVersion(); + int majorVersion = classVersion & 0xffff; + + int newVersion; + if (majorVersion < 51) { + newVersion = 51; + log(DEBUG, "Upgrading class version from " + classVersion + " to " + newVersion); + } else { + newVersion = classVersion; + } + + ClassReader reader = getClassReader(); + ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS); + ClassVisitor visitor = new ClassVisitor(ASM9, writer) { + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(newVersion, access, name, signature, superName, interfaces); + CallbackSupport.insertMetafactory(this); + } + }; + reader.accept(visitor, 0); + updateClassBuffer(writer.toByteArray()); + } + + private void injectGeneratedMethods() { + ClassReader reader = getClassReader(); + ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS); + ClassVisitor visitor = new ClassVisitor(ASM9, writer) { + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + for (Entry> el : generatedMethods.entrySet()) { + log(DEBUG, "Adding generated method [" + el.getKey() + "]"); + el.getValue().accept(this); + } + } + }; + reader.accept(visitor, 0); + updateClassBuffer(writer.toByteArray()); + } + + private void updateClassBuffer(byte[] buf) { + classBuffer = buf; + cachedClassReader = null; + cachedConstants = null; + } + public Optional finish() { if (appliedTransformers == null || appliedTransformers.isEmpty()) { return Optional.empty(); - } else { - if (addCallbackMetafactory) { - accept(new CallbackMetafactoryTransformer()); - } - if (minVersion == -1 && upgradedVersion == -1) { - return Optional.of(classBuffer); - } else { - try { - accept(new ClassVersionTransformUnit(minVersion, upgradedVersion)); - return Optional.of(classBuffer); - } catch (ClassVersionException e) { - log(WARNING, "Skipping [" + className + "], " + e.getMessage()); - return Optional.empty(); - } - } } + if (addCallbackMetafactory) { + injectCallbackMetafactory(); + } + if (generatedMethods != null) { + injectGeneratedMethods(); + } + return Optional.of(classBuffer); } public List getAppliedTransformers() { diff --git a/src/main/java/moe/yushi/authlibinjector/transform/ClassVersionException.java b/src/main/java/moe/yushi/authlibinjector/transform/ClassVersionException.java deleted file mode 100644 index 85067d5..0000000 --- a/src/main/java/moe/yushi/authlibinjector/transform/ClassVersionException.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2020 Haowei Wen and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package moe.yushi.authlibinjector.transform; - -class ClassVersionException extends RuntimeException { - public ClassVersionException(String message) { - super(message); - } -} diff --git a/src/main/java/moe/yushi/authlibinjector/transform/ClassVersionTransformUnit.java b/src/main/java/moe/yushi/authlibinjector/transform/ClassVersionTransformUnit.java deleted file mode 100644 index f916ba8..0000000 --- a/src/main/java/moe/yushi/authlibinjector/transform/ClassVersionTransformUnit.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2021 Haowei Wen and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package moe.yushi.authlibinjector.transform; - -import static moe.yushi.authlibinjector.util.Logging.log; -import static moe.yushi.authlibinjector.util.Logging.Level.DEBUG; -import static org.objectweb.asm.Opcodes.ASM9; -import java.util.Optional; -import org.objectweb.asm.ClassVisitor; - -class ClassVersionTransformUnit implements TransformUnit { - - private final int minVersion; - private final int upgradedVersion; - - public ClassVersionTransformUnit(int minVersion, int upgradedVersion) { - this.minVersion = minVersion; - this.upgradedVersion = upgradedVersion; - } - - @Override - public Optional transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext context) { - return Optional.of(new ClassVisitor(ASM9, writer) { - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - int major = version & 0xffff; - - if (minVersion != -1 && major < minVersion) { - throw new ClassVersionException("class version (" + major + ") is lower than required(" + minVersion + ")"); - } - - if (upgradedVersion != -1 && major < upgradedVersion) { - log(DEBUG,"Upgrading class version from " + major + " to " + upgradedVersion); - version = upgradedVersion; - context.markModified(); - } - super.visit(version, access, name, signature, superName, interfaces); - } - }); - } - - @Override - public String toString() { - return "Class File Version Transformer"; - } -} diff --git a/src/main/java/moe/yushi/authlibinjector/transform/TransformContext.java b/src/main/java/moe/yushi/authlibinjector/transform/TransformContext.java index 525b031..92c9709 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/TransformContext.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/TransformContext.java @@ -17,17 +17,21 @@ package moe.yushi.authlibinjector.transform; import java.util.List; -import org.objectweb.asm.Handle; +import java.util.function.Consumer; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; public interface TransformContext { + String getClassName(); + + boolean isInterface(); + void markModified(); - void requireMinimumClassVersion(int version); - - void upgradeClassVersion(int version); - - Handle acquireCallbackMetafactory(); - List getStringConstants(); + + void invokeCallback(MethodVisitor mv, Class owner, String methodName); + + void addGeneratedMethod(String name, Consumer generator); } diff --git a/src/main/java/moe/yushi/authlibinjector/transform/support/AuthlibLogInterceptor.java b/src/main/java/moe/yushi/authlibinjector/transform/support/AuthlibLogInterceptor.java index 673b691..a09836f 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/support/AuthlibLogInterceptor.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/support/AuthlibLogInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Haowei Wen and contributors + * Copyright (C) 2022 Haowei Wen and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -37,7 +37,6 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import moe.yushi.authlibinjector.transform.CallbackMethod; -import moe.yushi.authlibinjector.transform.CallbackSupport; import moe.yushi.authlibinjector.transform.TransformContext; import moe.yushi.authlibinjector.transform.TransformUnit; @@ -232,7 +231,7 @@ public class AuthlibLogInterceptor implements TransformUnit { super.visitCode(); super.visitLdcInsn(Type.getType("L" + className.replace('.', '/') + ";")); super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false); - CallbackSupport.invoke(ctx, mv, AuthlibLogInterceptor.class, "onClassLoading"); + ctx.invokeCallback(mv, AuthlibLogInterceptor.class, "onClassLoading"); ctx.markModified(); } }; diff --git a/src/main/java/moe/yushi/authlibinjector/transform/support/ConcatenateURLTransformUnit.java b/src/main/java/moe/yushi/authlibinjector/transform/support/ConcatenateURLTransformUnit.java index 09070ef..d26ed01 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/support/ConcatenateURLTransformUnit.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/support/ConcatenateURLTransformUnit.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Haowei Wen and contributors + * Copyright (C) 2022 Haowei Wen and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -25,7 +25,6 @@ import java.util.Optional; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import moe.yushi.authlibinjector.transform.CallbackMethod; -import moe.yushi.authlibinjector.transform.CallbackSupport; import moe.yushi.authlibinjector.transform.TransformContext; import moe.yushi.authlibinjector.transform.TransformUnit; @@ -59,7 +58,7 @@ public class ConcatenateURLTransformUnit implements TransformUnit { mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); - CallbackSupport.invoke(ctx, mv, ConcatenateURLTransformUnit.class, "concatenateURL"); + ctx.invokeCallback(mv, ConcatenateURLTransformUnit.class, "concatenateURL"); mv.visitInsn(ARETURN); mv.visitMaxs(-1, -1); mv.visitEnd(); diff --git a/src/main/java/moe/yushi/authlibinjector/transform/support/MC52974_1710Workaround.java b/src/main/java/moe/yushi/authlibinjector/transform/support/MC52974_1710Workaround.java index 0b6dd92..dea9a01 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/support/MC52974_1710Workaround.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/support/MC52974_1710Workaround.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Haowei Wen and contributors + * Copyright (C) 2022 Haowei Wen and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -33,7 +33,6 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import moe.yushi.authlibinjector.AuthlibInjector; import moe.yushi.authlibinjector.transform.CallbackMethod; -import moe.yushi.authlibinjector.transform.CallbackSupport; import moe.yushi.authlibinjector.transform.TransformContext; import moe.yushi.authlibinjector.transform.TransformUnit; import moe.yushi.authlibinjector.util.WeakIdentityHashMap; @@ -120,7 +119,7 @@ public class MC52974_1710Workaround { if (opcode == ARETURN) { ctx.markModified(); super.visitInsn(DUP); - CallbackSupport.invoke(ctx, mv, MC52974_1710Workaround.class, "markGameProfile"); + ctx.invokeCallback(mv, MC52974_1710Workaround.class, "markGameProfile"); } super.visitInsn(opcode); } @@ -163,7 +162,7 @@ public class MC52974_1710Workaround { super.visitMethodInsn(INVOKESTATIC, "net/minecraft/server/MinecraftServer", "func_71276_C", "()Lnet/minecraft/server/MinecraftServer;", false); } super.visitLdcInsn(isNotchName ? 1 : 0); - CallbackSupport.invoke(ctx, mv, MC52974_1710Workaround.class, "accessGameProfile"); + ctx.invokeCallback(mv, MC52974_1710Workaround.class, "accessGameProfile"); super.visitTypeInsn(CHECKCAST, "com/mojang/authlib/GameProfile"); } } diff --git a/src/main/java/moe/yushi/authlibinjector/transform/support/MainArgumentsTransformer.java b/src/main/java/moe/yushi/authlibinjector/transform/support/MainArgumentsTransformer.java index 15dd9b2..72dc24f 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/support/MainArgumentsTransformer.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/support/MainArgumentsTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Haowei Wen and contributors + * Copyright (C) 2022 Haowei Wen and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -31,7 +31,6 @@ import java.util.stream.Stream; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import moe.yushi.authlibinjector.transform.CallbackMethod; -import moe.yushi.authlibinjector.transform.CallbackSupport; import moe.yushi.authlibinjector.transform.TransformContext; import moe.yushi.authlibinjector.transform.TransformUnit; @@ -51,7 +50,7 @@ public class MainArgumentsTransformer implements TransformUnit { ctx.markModified(); super.visitVarInsn(ALOAD, 0); - CallbackSupport.invoke(ctx, mv, MainArgumentsTransformer.class, "processMainArguments"); + ctx.invokeCallback(mv, MainArgumentsTransformer.class, "processMainArguments"); super.visitVarInsn(ASTORE, 0); } }; diff --git a/src/main/java/moe/yushi/authlibinjector/transform/support/SkinWhitelistTransformUnit.java b/src/main/java/moe/yushi/authlibinjector/transform/support/SkinWhitelistTransformUnit.java index 566a1c8..4de0009 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/support/SkinWhitelistTransformUnit.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/support/SkinWhitelistTransformUnit.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Haowei Wen and contributors + * Copyright (C) 2022 Haowei Wen and contributors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -27,7 +27,6 @@ import java.util.concurrent.CopyOnWriteArrayList; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import moe.yushi.authlibinjector.transform.CallbackMethod; -import moe.yushi.authlibinjector.transform.CallbackSupport; import moe.yushi.authlibinjector.transform.TransformContext; import moe.yushi.authlibinjector.transform.TransformUnit; @@ -64,6 +63,7 @@ public class SkinWhitelistTransformUnit implements TransformUnit { @CallbackMethod public static boolean isWhitelistedDomain(String url) { + System.out.println(url); String domain; try { domain = new URI(url).getHost(); @@ -103,7 +103,7 @@ public class SkinWhitelistTransformUnit implements TransformUnit { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); - CallbackSupport.invoke(ctx, mv, SkinWhitelistTransformUnit.class, "isWhitelistedDomain"); + ctx.invokeCallback(mv, SkinWhitelistTransformUnit.class, "isWhitelistedDomain"); mv.visitInsn(IRETURN); mv.visitMaxs(-1, -1); mv.visitEnd(); diff --git a/src/main/java/moe/yushi/authlibinjector/transform/support/YggdrasilKeyTransformUnit.java b/src/main/java/moe/yushi/authlibinjector/transform/support/YggdrasilKeyTransformUnit.java index 2625beb..86b376b 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/support/YggdrasilKeyTransformUnit.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/support/YggdrasilKeyTransformUnit.java @@ -16,10 +16,11 @@ */ package moe.yushi.authlibinjector.transform.support; +import static java.lang.invoke.MethodHandles.publicLookup; +import static java.lang.invoke.MethodType.methodType; import static moe.yushi.authlibinjector.util.Logging.Level.DEBUG; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.ASM9; -import static org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.IRETURN; import java.lang.invoke.MethodHandle; @@ -32,10 +33,8 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Handle; import org.objectweb.asm.MethodVisitor; import moe.yushi.authlibinjector.transform.CallbackMethod; -import moe.yushi.authlibinjector.transform.CallbackSupport; import moe.yushi.authlibinjector.transform.TransformContext; import moe.yushi.authlibinjector.transform.TransformUnit; import moe.yushi.authlibinjector.util.Logging; @@ -46,12 +45,14 @@ public class YggdrasilKeyTransformUnit implements TransformUnit { public static final List PUBLIC_KEYS = new CopyOnWriteArrayList<>(); @CallbackMethod - public static boolean verifyPropertySignature(Object property, PublicKey mojangKey, MethodHandle verifyAction) throws Throwable { - if ((boolean) verifyAction.invoke(property, mojangKey)) { + public static boolean verifyPropertySignature(Object property, PublicKey mojangKey) throws Throwable { + MethodHandle verifyAction = publicLookup().bind(property, "isSignatureValid", methodType(boolean.class, PublicKey.class)); + + if ((boolean) verifyAction.invokeExact(mojangKey)) { return true; } for (PublicKey customKey : PUBLIC_KEYS) { - if ((boolean) verifyAction.invoke(property, customKey)) { + if ((boolean) verifyAction.invokeExact(customKey)) { return true; } } @@ -101,8 +102,7 @@ public class YggdrasilKeyTransformUnit implements TransformUnit { && "isSignatureValid".equals(name) && "(Ljava/security/PublicKey;)Z".equals(descriptor)) { ctx.markModified(); - super.visitLdcInsn(new Handle(H_INVOKEVIRTUAL, owner, name, descriptor, isInterface)); - CallbackSupport.invoke(ctx, this, YggdrasilKeyTransformUnit.class, "verifyPropertySignature"); + ctx.invokeCallback(this, YggdrasilKeyTransformUnit.class, "verifyPropertySignature"); } else { super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } @@ -126,7 +126,7 @@ public class YggdrasilKeyTransformUnit implements TransformUnit { mv.visitMethodInsn(INVOKEVIRTUAL, "com/mojang/authlib/properties/Property", "getValue", "()Ljava/lang/String;", false); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "com/mojang/authlib/properties/Property", "getSignature", "()Ljava/lang/String;", false); - CallbackSupport.invoke(ctx, mv, YggdrasilKeyTransformUnit.class, "verifyPropertySignatureNew"); + ctx.invokeCallback(mv, YggdrasilKeyTransformUnit.class, "verifyPropertySignatureNew"); mv.visitInsn(IRETURN); mv.visitMaxs(-1, -1); mv.visitEnd();