diff --git a/src/main/java/moe/yushi/authlibinjector/AuthlibInjector.java b/src/main/java/moe/yushi/authlibinjector/AuthlibInjector.java index 0dc2d90..545e2bd 100644 --- a/src/main/java/moe/yushi/authlibinjector/AuthlibInjector.java +++ b/src/main/java/moe/yushi/authlibinjector/AuthlibInjector.java @@ -26,18 +26,22 @@ import static moe.yushi.authlibinjector.util.IOUtils.removeNewLines; import java.io.IOException; import java.io.UncheckedIOException; -import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.Instrumentation; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.Base64; +import java.util.HashSet; import java.util.List; import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Set; import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.stream.Stream; import moe.yushi.authlibinjector.httpd.DefaultURLRedirector; import moe.yushi.authlibinjector.httpd.LegacySkinAPIFilter; @@ -118,19 +122,33 @@ public final class AuthlibInjector { private AuthlibInjector() {} - private static AtomicBoolean booted = new AtomicBoolean(false); + private static boolean booted = false; + private static Instrumentation instrumentation; + private static boolean retransformSupported; + private static ClassTransformer classTransformer; - public static void bootstrap(Consumer transformerRegistry) throws InjectorInitializationException { - if (!booted.compareAndSet(false, true)) { + public static synchronized void bootstrap(Instrumentation instrumentation) throws InjectorInitializationException { + if (booted) { Logging.LAUNCH.info("Already started, skipping"); return; } + booted = true; + AuthlibInjector.instrumentation = instrumentation; + + retransformSupported = instrumentation.isRetransformClassesSupported(); + if (!retransformSupported) { + Logging.LAUNCH.warning("Retransform is not supported"); + } Logging.LAUNCH.info("Version: " + getVersion()); Optional optionalConfig = configure(); if (optionalConfig.isPresent()) { - transformerRegistry.accept(createTransformer(optionalConfig.get())); + classTransformer = createTransformer(optionalConfig.get()); + instrumentation.addTransformer(classTransformer, retransformSupported); + + MC52974Workaround.init(); + MC52974_1710Workaround.init(); } else { Logging.LAUNCH.severe("No config available"); throw new InjectorInitializationException(); @@ -316,11 +334,6 @@ public final class AuthlibInjector { filters.add(new QueryUUIDsFilter(mojangClient, customClient)); filters.add(new QueryProfileFilter(mojangClient, customClient)); - MainArgumentsTransformer.getListeners().add(args -> { - MC52974Workaround.acceptMainArguments(args); - return args; - }); - return filters; } @@ -343,8 +356,6 @@ public final class AuthlibInjector { transformer.units.add(new MainArgumentsTransformer()); transformer.units.add(new ConstantURLTransformUnit(urlProcessor)); transformer.units.add(new CitizensTransformer()); - transformer.units.add(new MC52974Workaround()); - transformer.units.add(new MC52974_1710Workaround()); transformer.units.add(new LaunchWrapperTransformer()); transformer.units.add(new SkinWhitelistTransformUnit(config.getSkinDomains().toArray(new String[0]))); @@ -359,4 +370,26 @@ public final class AuthlibInjector { return AuthlibInjector.class.getPackage().getImplementationVersion(); } + public static void retransformClasses(String... classNames) { + if (!retransformSupported) { + return; + } + Set classNamesSet = new HashSet<>(Arrays.asList(classNames)); + Class[] classes = Stream.of(instrumentation.getAllLoadedClasses()) + .filter(clazz -> classNamesSet.contains(clazz.getName())) + .filter(instrumentation::isModifiableClass) + .toArray(Class[]::new); + if (classes.length > 0) { + Logging.TRANSFORM.info("Attempt to retransform classes: " + Arrays.toString(classes)); + try { + instrumentation.retransformClasses(classes); + } catch (Throwable e) { + Logging.TRANSFORM.log(Level.WARNING, "Failed to retransform", e); + } + } + } + + public static ClassTransformer getClassTransformer() { + return classTransformer; + } } diff --git a/src/main/java/moe/yushi/authlibinjector/javaagent/AuthlibInjectorPremain.java b/src/main/java/moe/yushi/authlibinjector/javaagent/AuthlibInjectorPremain.java index a9e0eee..7327b9c 100644 --- a/src/main/java/moe/yushi/authlibinjector/javaagent/AuthlibInjectorPremain.java +++ b/src/main/java/moe/yushi/authlibinjector/javaagent/AuthlibInjectorPremain.java @@ -60,7 +60,7 @@ public class AuthlibInjectorPremain { boolean retransformSupported = instrumentation.isRetransformClassesSupported(); boolean retransformEnabled = retransformSupported && needsRetransform; - bootstrap(x -> instrumentation.addTransformer(x, retransformEnabled)); + bootstrap(instrumentation); if (needsRetransform) { if (retransformSupported) { diff --git a/src/main/java/moe/yushi/authlibinjector/transform/ClassTransformer.java b/src/main/java/moe/yushi/authlibinjector/transform/ClassTransformer.java index 77dddd3..1cd6580 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/ClassTransformer.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/ClassTransformer.java @@ -23,10 +23,11 @@ import java.lang.instrument.IllegalClassFormatException; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Level; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -39,9 +40,9 @@ public class ClassTransformer implements ClassFileTransformer { private static final boolean PRINT_UNTRANSFORMED_CLASSES = Boolean.getBoolean(AuthlibInjector.PROP_PRINT_UNTRANSFORMED_CLASSES); - public List units = new ArrayList<>(); - public List listeners = new ArrayList<>(); - public Set ignores = new HashSet<>(); + public final List units = new CopyOnWriteArrayList<>(); + public final List listeners = new CopyOnWriteArrayList<>(); + public final Set ignores = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static class TransformHandle { diff --git a/src/main/java/moe/yushi/authlibinjector/transform/MainArgumentsTransformer.java b/src/main/java/moe/yushi/authlibinjector/transform/MainArgumentsTransformer.java index 42c62ed..ffbe95b 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/MainArgumentsTransformer.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/MainArgumentsTransformer.java @@ -9,6 +9,7 @@ import static org.objectweb.asm.Opcodes.INVOKESTATIC; import java.util.List; import java.util.Optional; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Stream; @@ -20,22 +21,6 @@ import moe.yushi.authlibinjector.util.Logging; public class MainArgumentsTransformer implements TransformUnit { - private static final List> LISTENERS = new CopyOnWriteArrayList<>(); - - public static String[] processMainArguments(String[] args) { - Logging.TRANSFORM.fine(() -> "Main arguments: " + Stream.of(args).collect(joining(" "))); - - String[] result = args; - for (Function listener : LISTENERS) { - result = listener.apply(result); - } - return result; - } - - public static List> getListeners() { - return LISTENERS; - } - @Override public Optional transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) { if ("net.minecraft.client.main.Main".equals(className)) { @@ -68,4 +53,60 @@ public class MainArgumentsTransformer implements TransformUnit { public String toString() { return "Main Arguments Transformer"; } + + // ==== Main arguments processing ==== + private static final List> ARGUMENTS_LISTENERS = new CopyOnWriteArrayList<>(); + + public static String[] processMainArguments(String[] args) { + Logging.TRANSFORM.fine(() -> "Main arguments: " + Stream.of(args).collect(joining(" "))); + + String[] result = args; + for (Function listener : ARGUMENTS_LISTENERS) { + result = listener.apply(result); + } + return result; + } + + public static List> getArgumentsListeners() { + return ARGUMENTS_LISTENERS; + } + // ==== + + // ==== Version series detection ==== + private static final List> VERSION_SERIES_LISTENERS = new CopyOnWriteArrayList<>(); + + public static Optional inferVersionSeries(String[] args) { + boolean hit = false; + for (String arg : args) { + if (hit) { + if (arg.startsWith("--")) { + // arg doesn't seem to be a value + // maybe the previous argument is a value, but we wrongly recognized it as an option + hit = false; + } else { + return Optional.of(arg); + } + } + + if ("--assetIndex".equals(arg)) { + hit = true; + } + } + return Optional.empty(); + } + + static { + getArgumentsListeners().add(args -> { + inferVersionSeries(args).ifPresent(versionSeries -> { + Logging.TRANSFORM.fine("Version series detected: " + versionSeries); + VERSION_SERIES_LISTENERS.forEach(listener -> listener.accept(versionSeries)); + }); + return args; + }); + } + + public static List> getVersionSeriesListeners() { + return VERSION_SERIES_LISTENERS; + } + // ==== } diff --git a/src/main/java/moe/yushi/authlibinjector/transform/support/MC52974Workaround.java b/src/main/java/moe/yushi/authlibinjector/transform/support/MC52974Workaround.java index d35c0b1..88e772b 100644 --- a/src/main/java/moe/yushi/authlibinjector/transform/support/MC52974Workaround.java +++ b/src/main/java/moe/yushi/authlibinjector/transform/support/MC52974Workaround.java @@ -2,8 +2,6 @@ package moe.yushi.authlibinjector.transform.support; import static java.util.Collections.unmodifiableSet; import static org.objectweb.asm.Opcodes.ASM7; -import static org.objectweb.asm.Opcodes.ILOAD; -import static org.objectweb.asm.Opcodes.INVOKESTATIC; import static org.objectweb.asm.Opcodes.ISTORE; import java.util.Arrays; @@ -13,8 +11,9 @@ import java.util.Set; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Type; +import moe.yushi.authlibinjector.AuthlibInjector; +import moe.yushi.authlibinjector.transform.MainArgumentsTransformer; import moe.yushi.authlibinjector.transform.TransformUnit; import moe.yushi.authlibinjector.util.Logging; @@ -23,11 +22,10 @@ import moe.yushi.authlibinjector.util.Logging; */ public class MC52974Workaround implements TransformUnit { - private static boolean affected = false; + private MC52974Workaround() { + } - // ==== Detect affected versions ==== - public static final Set AFFECTED_VERSION_SERIES = unmodifiableSet(new HashSet<>(Arrays.asList( - "1.7.4", // MC 1.7.9 uses this + private static final Set AFFECTED_VERSION_SERIES = unmodifiableSet(new HashSet<>(Arrays.asList( "1.7.10", "1.8", "1.9", @@ -35,42 +33,15 @@ public class MC52974Workaround implements TransformUnit { "1.11", "1.12"))); - public static Optional inferVersionSeries(String[] args) { - boolean hit = false; - for (String arg : args) { - if (hit) { - if (arg.startsWith("--")) { - // arg doesn't seem to be a value - // maybe the previous argument is a value, but we wrongly recognized it as an option - hit = false; - } else { - return Optional.of(arg); - } - } - - if ("--assetIndex".equals(arg)) { - hit = true; - } - } - return Optional.empty(); - } - - public static void acceptMainArguments(String[] args) { - inferVersionSeries(args).ifPresent(assetIndexName -> { - if (AFFECTED_VERSION_SERIES.contains(assetIndexName)) { - Logging.HTTPD.info("Current version series is " + assetIndexName + ", enable MC-52974 workaround."); - affected = true; + public static void init() { + MainArgumentsTransformer.getVersionSeriesListeners().add(version -> { + if (AFFECTED_VERSION_SERIES.contains(version)) { + Logging.TRANSFORM.info("Enable MC-52974 Workaround"); + AuthlibInjector.getClassTransformer().units.add(new MC52974Workaround()); + AuthlibInjector.retransformClasses("com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService"); } }); } - // ==== - - public static boolean overwriteRequireSecure(boolean requireSecure) { - if (affected) { - return true; - } - return requireSecure; - } @Override public Optional transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) { @@ -84,8 +55,7 @@ public class MC52974Workaround implements TransformUnit { public void visitCode() { super.visitCode(); modifiedCallback.run(); - super.visitVarInsn(ILOAD, 2); - super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(MC52974Workaround.class), "overwriteRequireSecure", "(Z)Z", false); + super.visitLdcInsn(1); super.visitVarInsn(ISTORE, 2); } }; 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 f07919d..9385e6f 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 @@ -16,11 +16,25 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; +import moe.yushi.authlibinjector.AuthlibInjector; +import moe.yushi.authlibinjector.transform.MainArgumentsTransformer; import moe.yushi.authlibinjector.transform.TransformUnit; import moe.yushi.authlibinjector.util.Logging; import moe.yushi.authlibinjector.util.WeakIdentityHashMap; public class MC52974_1710Workaround implements TransformUnit { + private MC52974_1710Workaround() { + } + + public static void init() { + MainArgumentsTransformer.getVersionSeriesListeners().add(version -> { + if ("1.7.10".equals(version)) { + Logging.TRANSFORM.info("Enable MC-52974 Workaround for 1.7.10"); + AuthlibInjector.getClassTransformer().units.add(new MC52974_1710Workaround()); + AuthlibInjector.retransformClasses("bbs", "gb"); + } + }); + } // Empty GameProfile -> Filled GameProfile? private static final Map> markedGameProfiles = new WeakIdentityHashMap<>(); diff --git a/src/test/java/moe/yushi/authlibinjector/test/MC52974WorkaroundTest.java b/src/test/java/moe/yushi/authlibinjector/test/VersionSeriesDetectTest.java similarity index 92% rename from src/test/java/moe/yushi/authlibinjector/test/MC52974WorkaroundTest.java rename to src/test/java/moe/yushi/authlibinjector/test/VersionSeriesDetectTest.java index a446f8b..ddcf999 100644 --- a/src/test/java/moe/yushi/authlibinjector/test/MC52974WorkaroundTest.java +++ b/src/test/java/moe/yushi/authlibinjector/test/VersionSeriesDetectTest.java @@ -1,13 +1,13 @@ package moe.yushi.authlibinjector.test; -import static moe.yushi.authlibinjector.transform.support.MC52974Workaround.inferVersionSeries; +import static moe.yushi.authlibinjector.transform.MainArgumentsTransformer.inferVersionSeries; import static org.junit.Assert.assertEquals; import java.util.Optional; import org.junit.Test; -public class MC52974WorkaroundTest { +public class VersionSeriesDetectTest { @Test public void testNone() {