forked from MirrorHub/authlib-injector
Support retransform
This commit is contained in:
parent
f2a447d58d
commit
bee0998925
7 changed files with 137 additions and 78 deletions
|
@ -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<ClassFileTransformer> 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<YggdrasilConfiguration> 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<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<TransformUnit> units = new ArrayList<>();
|
||||
public List<ClassLoadingListener> listeners = new ArrayList<>();
|
||||
public Set<String> ignores = new HashSet<>();
|
||||
public final List<TransformUnit> units = new CopyOnWriteArrayList<>();
|
||||
public final List<ClassLoadingListener> listeners = new CopyOnWriteArrayList<>();
|
||||
public final Set<String> ignores = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
private static class TransformHandle {
|
||||
|
||||
|
|
|
@ -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<Function<String[], String[]>> LISTENERS = new CopyOnWriteArrayList<>();
|
||||
|
||||
public static String[] processMainArguments(String[] args) {
|
||||
Logging.TRANSFORM.fine(() -> "Main arguments: " + Stream.of(args).collect(joining(" ")));
|
||||
|
||||
String[] result = args;
|
||||
for (Function<String[], String[]> listener : LISTENERS) {
|
||||
result = listener.apply(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Function<String[], String[]>> getListeners() {
|
||||
return LISTENERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClassVisitor> 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<Function<String[], String[]>> 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<String[], String[]> listener : ARGUMENTS_LISTENERS) {
|
||||
result = listener.apply(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Function<String[], String[]>> getArgumentsListeners() {
|
||||
return ARGUMENTS_LISTENERS;
|
||||
}
|
||||
// ====
|
||||
|
||||
// ==== Version series detection ====
|
||||
private static final List<Consumer<String>> VERSION_SERIES_LISTENERS = new CopyOnWriteArrayList<>();
|
||||
|
||||
public static Optional<String> 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<Consumer<String>> getVersionSeriesListeners() {
|
||||
return VERSION_SERIES_LISTENERS;
|
||||
}
|
||||
// ====
|
||||
}
|
||||
|
|
|
@ -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<String> AFFECTED_VERSION_SERIES = unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
"1.7.4", // MC 1.7.9 uses this
|
||||
private static final Set<String> 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<String> 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<ClassVisitor> 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);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<Object, Optional<Object>> markedGameProfiles = new WeakIdentityHashMap<>();
|
||||
|
|
|
@ -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() {
|
Loading…
Reference in a new issue