forked from MirrorHub/authlib-injector
Implement class file version upgrading
This commit is contained in:
parent
9eb3b0d738
commit
a94528bb68
11 changed files with 127 additions and 47 deletions
|
@ -212,7 +212,7 @@ public class AuthlibLogInterceptor implements TransformUnit {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
if (className.startsWith("com.mojang.authlib.")) {
|
||||
synchronized (interceptedClassloaders) {
|
||||
if (interceptedClassloaders.contains(classLoader)) {
|
||||
|
@ -228,11 +228,11 @@ public class AuthlibLogInterceptor implements TransformUnit {
|
|||
@Override
|
||||
public void visitCode() {
|
||||
super.visitCode();
|
||||
CallbackInvocation callback = CallbackInvocation.push(mv, AuthlibLogInterceptor.class, "onClassLoading");
|
||||
CallbackInvocation callback = CallbackInvocation.push(ctx, mv, AuthlibLogInterceptor.class, "onClassLoading");
|
||||
super.visitLdcInsn(Type.getType("L" + className.replace('.', '/') + ";"));
|
||||
super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false);
|
||||
callback.invoke();
|
||||
modifiedCallback.run();
|
||||
ctx.markModified();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package moe.yushi.authlibinjector.transform;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.ACONST_NULL;
|
||||
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
||||
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
|
||||
|
||||
|
@ -26,6 +25,8 @@ import java.lang.reflect.Modifier;
|
|||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import moe.yushi.authlibinjector.transform.TransformUnit.TransformContext;
|
||||
|
||||
public class CallbackInvocation {
|
||||
|
||||
private static Method findCallbackMethod(Class<?> owner, String methodName) {
|
||||
|
@ -40,7 +41,10 @@ public class CallbackInvocation {
|
|||
throw new IllegalArgumentException("No such method: " + methodName);
|
||||
}
|
||||
|
||||
public static CallbackInvocation push(MethodVisitor mv, Class<?> owner, String methodName) {
|
||||
public static CallbackInvocation push(TransformContext ctx, MethodVisitor mv, Class<?> owner, String methodName) {
|
||||
ctx.requireMinimumClassVersion(50);
|
||||
ctx.upgradeClassVersion(51);
|
||||
|
||||
String descriptor = Type.getMethodDescriptor(findCallbackMethod(owner, methodName));
|
||||
|
||||
mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "publicLookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false);
|
||||
|
@ -48,10 +52,7 @@ public class CallbackInvocation {
|
|||
mv.visitLdcInsn(owner.getName());
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false);
|
||||
mv.visitLdcInsn(methodName);
|
||||
// we cannot use CONSTANT_MethodType_info here because the class file version may be lower than 51
|
||||
mv.visitLdcInsn(descriptor);
|
||||
mv.visitInsn(ACONST_NULL);
|
||||
mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodType", "fromMethodDescriptorString", "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/invoke/MethodType;", false);
|
||||
mv.visitLdcInsn(Type.getMethodType(descriptor));
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
|
||||
|
||||
return new CallbackInvocation(mv, descriptor);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package moe.yushi.authlibinjector.transform;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.objectweb.asm.Opcodes.ASM7;
|
||||
|
||||
import java.lang.instrument.ClassFileTransformer;
|
||||
import java.lang.instrument.IllegalClassFormatException;
|
||||
|
@ -34,6 +35,7 @@ import org.objectweb.asm.ClassVisitor;
|
|||
import org.objectweb.asm.ClassWriter;
|
||||
|
||||
import moe.yushi.authlibinjector.AuthlibInjector;
|
||||
import moe.yushi.authlibinjector.transform.TransformUnit.TransformContext;
|
||||
import moe.yushi.authlibinjector.util.Logging;
|
||||
|
||||
public class ClassTransformer implements ClassFileTransformer {
|
||||
|
@ -46,12 +48,71 @@ public class ClassTransformer implements ClassFileTransformer {
|
|||
|
||||
private static class TransformHandle {
|
||||
|
||||
private static class TransformContextImpl implements TransformContext {
|
||||
public boolean modifiedMark;
|
||||
public int minVersionMark = -1;
|
||||
public int upgradedVersionMark = -1;
|
||||
|
||||
@Override
|
||||
public void markModified() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClassVersionException extends RuntimeException {
|
||||
public ClassVersionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassVersionTransformUnit implements TransformUnit {
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext context) {
|
||||
return Optional.of(new ClassVisitor(ASM7, 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) {
|
||||
Logging.TRANSFORM.fine("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";
|
||||
}
|
||||
}
|
||||
|
||||
private List<TransformUnit> appliedTransformers;
|
||||
private boolean currentModified;
|
||||
private String className;
|
||||
private byte[] classBuffer;
|
||||
private ClassWriter pooledClassWriter;
|
||||
private ClassLoader classLoader;
|
||||
private int minVersion = -1;
|
||||
private int upgradedVersion = -1;
|
||||
|
||||
public TransformHandle(ClassLoader classLoader, String className, byte[] classBuffer) {
|
||||
this.className = className;
|
||||
|
@ -60,37 +121,45 @@ public class ClassTransformer implements ClassFileTransformer {
|
|||
}
|
||||
|
||||
public void accept(TransformUnit unit) {
|
||||
ClassWriter writer;
|
||||
if (pooledClassWriter == null) {
|
||||
writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
} else {
|
||||
writer = pooledClassWriter;
|
||||
pooledClassWriter = null;
|
||||
}
|
||||
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
TransformContextImpl ctx = new TransformContextImpl();
|
||||
|
||||
Optional<ClassVisitor> optionalVisitor = unit.transform(classLoader, className, writer, () -> currentModified = true);
|
||||
Optional<ClassVisitor> optionalVisitor = unit.transform(classLoader, className, writer, ctx);
|
||||
if (optionalVisitor.isPresent()) {
|
||||
currentModified = false;
|
||||
ClassReader reader = new ClassReader(classBuffer);
|
||||
reader.accept(optionalVisitor.get(), 0);
|
||||
if (currentModified) {
|
||||
if (ctx.modifiedMark) {
|
||||
Logging.TRANSFORM.info("Transformed [" + className + "] with [" + unit + "]");
|
||||
if (appliedTransformers == null) {
|
||||
appliedTransformers = new ArrayList<>();
|
||||
}
|
||||
appliedTransformers.add(unit);
|
||||
classBuffer = writer.toByteArray();
|
||||
if (ctx.minVersionMark > this.minVersion) {
|
||||
this.minVersion = ctx.minVersionMark;
|
||||
}
|
||||
if (ctx.upgradedVersionMark > this.upgradedVersion) {
|
||||
this.upgradedVersion = ctx.upgradedVersionMark;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pooledClassWriter = writer;
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<byte[]> getTransformResult() {
|
||||
public Optional<byte[]> finish() {
|
||||
if (appliedTransformers == null || appliedTransformers.isEmpty()) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
if (minVersion == -1 && upgradedVersion == -1) {
|
||||
return Optional.of(classBuffer);
|
||||
} else {
|
||||
try {
|
||||
accept(new ClassVersionTransformUnit());
|
||||
return Optional.of(classBuffer);
|
||||
} catch (ClassVersionException e) {
|
||||
Logging.TRANSFORM.warning("Skipping [" + className + "], " + e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,7 +188,7 @@ public class ClassTransformer implements ClassFileTransformer {
|
|||
units.forEach(handle::accept);
|
||||
listeners.forEach(it -> it.onClassLoading(loader, className, handle.getFinalResult(), handle.getAppliedTransformers()));
|
||||
|
||||
Optional<byte[]> transformResult = handle.getTransformResult();
|
||||
Optional<byte[]> transformResult = handle.finish();
|
||||
if (PRINT_UNTRANSFORMED_CLASSES && !transformResult.isPresent()) {
|
||||
Logging.TRANSFORM.fine("No transformation is applied to [" + className + "]");
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ import org.objectweb.asm.MethodVisitor;
|
|||
public abstract class LdcTransformUnit implements TransformUnit {
|
||||
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
return Optional.of(new ClassVisitor(ASM7, writer) {
|
||||
|
||||
@Override
|
||||
|
@ -36,7 +36,7 @@ public abstract class LdcTransformUnit implements TransformUnit {
|
|||
if (cst instanceof String) {
|
||||
Optional<String> transformed = transformLdc((String) cst);
|
||||
if (transformed.isPresent() && !transformed.get().equals(cst)) {
|
||||
modifiedCallback.run();
|
||||
ctx.markModified();
|
||||
super.visitLdcInsn(transformed.get());
|
||||
} else {
|
||||
super.visitLdcInsn(cst);
|
||||
|
|
|
@ -36,7 +36,7 @@ import moe.yushi.authlibinjector.util.Logging;
|
|||
public class MainArgumentsTransformer implements TransformUnit {
|
||||
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
if ("net.minecraft.client.main.Main".equals(className)) {
|
||||
return Optional.of(new ClassVisitor(ASM7, writer) {
|
||||
@Override
|
||||
|
@ -46,9 +46,9 @@ public class MainArgumentsTransformer implements TransformUnit {
|
|||
@Override
|
||||
public void visitCode() {
|
||||
super.visitCode();
|
||||
modifiedCallback.run();
|
||||
ctx.markModified();
|
||||
|
||||
CallbackInvocation callback = CallbackInvocation.push(mv, MainArgumentsTransformer.class, "processMainArguments");
|
||||
CallbackInvocation callback = CallbackInvocation.push(ctx, mv, MainArgumentsTransformer.class, "processMainArguments");
|
||||
super.visitVarInsn(ALOAD, 0);
|
||||
callback.invoke();
|
||||
super.visitVarInsn(ASTORE, 0);
|
||||
|
|
|
@ -37,7 +37,7 @@ public class SkinWhitelistTransformUnit implements TransformUnit {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
if ("com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService".equals(className)) {
|
||||
return Optional.of(new ClassVisitor(ASM7, writer) {
|
||||
|
||||
|
@ -59,7 +59,7 @@ public class SkinWhitelistTransformUnit implements TransformUnit {
|
|||
} else if ((status == 5 || status == 9) && opcode == AASTORE) {
|
||||
status++;
|
||||
if (status == 10) {
|
||||
modifiedCallback.run();
|
||||
ctx.markModified();
|
||||
super.visitIntInsn(SIPUSH, skinWhitelist.length + 2);
|
||||
super.visitTypeInsn(ANEWARRAY, "java/lang/String");
|
||||
super.visitInsn(DUP);
|
||||
|
|
|
@ -21,6 +21,16 @@ import org.objectweb.asm.ClassVisitor;
|
|||
|
||||
public interface TransformUnit {
|
||||
|
||||
Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback);
|
||||
public interface TransformContext {
|
||||
|
||||
void markModified();
|
||||
|
||||
void requireMinimumClassVersion(int version);
|
||||
|
||||
void upgradeClassVersion(int version);
|
||||
|
||||
}
|
||||
|
||||
Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext context);
|
||||
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ public class YggdrasilKeyTransformUnit implements TransformUnit {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
if ("com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService".equals(className)) {
|
||||
return Optional.of(new ClassVisitor(ASM7, writer) {
|
||||
|
||||
|
@ -76,7 +76,7 @@ public class YggdrasilKeyTransformUnit implements TransformUnit {
|
|||
mv.visitInsn(IRETURN);
|
||||
mv.visitLabel(l0);
|
||||
mv.visitFrame(F_SAME, 0, null, 0, null);
|
||||
CallbackInvocation.push(mv, YggdrasilKeyTransformUnit.class, "getPublicKeys").invoke();
|
||||
CallbackInvocation.push(ctx, mv, YggdrasilKeyTransformUnit.class, "getPublicKeys").invoke();
|
||||
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true);
|
||||
mv.visitVarInsn(ASTORE, 2);
|
||||
Label l1 = new Label();
|
||||
|
@ -117,7 +117,7 @@ public class YggdrasilKeyTransformUnit implements TransformUnit {
|
|||
&& "com/mojang/authlib/properties/Property".equals(owner)
|
||||
&& "isSignatureValid".equals(name)
|
||||
&& "(Ljava/security/PublicKey;)Z".equals(descriptor)) {
|
||||
modifiedCallback.run();
|
||||
ctx.markModified();
|
||||
super.visitMethodInsn(INVOKESTATIC,
|
||||
"com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService",
|
||||
"authlib_injector_isSignatureValid",
|
||||
|
|
|
@ -42,7 +42,7 @@ import moe.yushi.authlibinjector.transform.TransformUnit;
|
|||
public class CitizensTransformer implements TransformUnit {
|
||||
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
if ("net.citizensnpcs.Settings$Setting".equals(className)) {
|
||||
return Optional.of(new ClassVisitor(ASM7, writer) {
|
||||
@Override
|
||||
|
@ -62,7 +62,7 @@ public class CitizensTransformer implements TransformUnit {
|
|||
super.visitInsn(RETURN);
|
||||
super.visitLabel(lbl);
|
||||
super.visitFrame(F_SAME, 0, null, 0, null);
|
||||
modifiedCallback.run();
|
||||
ctx.markModified();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ public class MC52974Workaround implements TransformUnit {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
if ("com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService".equals(className)) {
|
||||
return Optional.of(new ClassVisitor(ASM7, writer) {
|
||||
@Override
|
||||
|
@ -70,7 +70,7 @@ public class MC52974Workaround implements TransformUnit {
|
|||
@Override
|
||||
public void visitCode() {
|
||||
super.visitCode();
|
||||
modifiedCallback.run();
|
||||
ctx.markModified();
|
||||
super.visitLdcInsn(1);
|
||||
super.visitVarInsn(ISTORE, 2);
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ public class MC52974_1710Workaround {
|
|||
|
||||
private static class SessionTransformer implements TransformUnit {
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
return detectNotchName(className, "bbs", "net.minecraft.util.Session", isNotchName -> new ClassVisitor(ASM7, writer) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
|
@ -121,9 +121,9 @@ public class MC52974_1710Workaround {
|
|||
@Override
|
||||
public void visitInsn(int opcode) {
|
||||
if (opcode == ARETURN) {
|
||||
modifiedCallback.run();
|
||||
ctx.markModified();
|
||||
super.visitInsn(DUP);
|
||||
CallbackInvocation callback = CallbackInvocation.push(mv, MC52974_1710Workaround.class, "markGameProfile");
|
||||
CallbackInvocation callback = CallbackInvocation.push(ctx, mv, MC52974_1710Workaround.class, "markGameProfile");
|
||||
super.visitInsn(SWAP);
|
||||
callback.invoke();
|
||||
super.visitTypeInsn(CHECKCAST, "com/mojang/authlib/GameProfile");
|
||||
|
@ -146,7 +146,7 @@ public class MC52974_1710Workaround {
|
|||
|
||||
private static class S0CPacketSpawnPlayerTransformer implements TransformUnit {
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
return detectNotchName(className, "gb", "net.minecraft.network.play.server.S0CPacketSpawnPlayer", isNotchName -> new ClassVisitor(ASM7, writer) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
|
||||
|
@ -160,8 +160,8 @@ public class MC52974_1710Workaround {
|
|||
if (opcode == GETFIELD && (isNotchName
|
||||
? "gb".equals(owner) && "b".equals(name) && "Lcom/mojang/authlib/GameProfile;".equals(descriptor)
|
||||
: "net/minecraft/network/play/server/S0CPacketSpawnPlayer".equals(owner) && "field_148955_b".equals(name) && "Lcom/mojang/authlib/GameProfile;".equals(descriptor))) {
|
||||
modifiedCallback.run();
|
||||
CallbackInvocation callback = CallbackInvocation.push(mv, MC52974_1710Workaround.class, "accessGameProfile");
|
||||
ctx.markModified();
|
||||
CallbackInvocation callback = CallbackInvocation.push(ctx, mv, MC52974_1710Workaround.class, "accessGameProfile");
|
||||
super.visitInsn(SWAP);
|
||||
super.visitFieldInsn(opcode, owner, name, descriptor);
|
||||
if (isNotchName) {
|
||||
|
|
Loading…
Reference in a new issue