mirror of
https://github.com/unmojang/authlib-injector.git
synced 2025-10-03 08:11:04 -04:00
commit
3eee25593e
@ -26,18 +26,22 @@ import static moe.yushi.authlibinjector.util.IOUtils.removeNewLines;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.lang.instrument.ClassFileTransformer;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
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.DefaultURLRedirector;
|
||||||
import moe.yushi.authlibinjector.httpd.LegacySkinAPIFilter;
|
import moe.yushi.authlibinjector.httpd.LegacySkinAPIFilter;
|
||||||
@ -55,6 +59,7 @@ import moe.yushi.authlibinjector.transform.YggdrasilKeyTransformUnit;
|
|||||||
import moe.yushi.authlibinjector.transform.support.CitizensTransformer;
|
import moe.yushi.authlibinjector.transform.support.CitizensTransformer;
|
||||||
import moe.yushi.authlibinjector.transform.support.LaunchWrapperTransformer;
|
import moe.yushi.authlibinjector.transform.support.LaunchWrapperTransformer;
|
||||||
import moe.yushi.authlibinjector.transform.support.MC52974Workaround;
|
import moe.yushi.authlibinjector.transform.support.MC52974Workaround;
|
||||||
|
import moe.yushi.authlibinjector.transform.support.MC52974_1710Workaround;
|
||||||
import moe.yushi.authlibinjector.util.Logging;
|
import moe.yushi.authlibinjector.util.Logging;
|
||||||
import moe.yushi.authlibinjector.yggdrasil.CustomYggdrasilAPIProvider;
|
import moe.yushi.authlibinjector.yggdrasil.CustomYggdrasilAPIProvider;
|
||||||
import moe.yushi.authlibinjector.yggdrasil.MojangYggdrasilAPIProvider;
|
import moe.yushi.authlibinjector.yggdrasil.MojangYggdrasilAPIProvider;
|
||||||
@ -117,19 +122,33 @@ public final class AuthlibInjector {
|
|||||||
|
|
||||||
private 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 {
|
public static synchronized void bootstrap(Instrumentation instrumentation) throws InjectorInitializationException {
|
||||||
if (!booted.compareAndSet(false, true)) {
|
if (booted) {
|
||||||
Logging.LAUNCH.info("Already started, skipping");
|
Logging.LAUNCH.info("Already started, skipping");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
booted = true;
|
||||||
|
AuthlibInjector.instrumentation = instrumentation;
|
||||||
|
|
||||||
|
retransformSupported = instrumentation.isRetransformClassesSupported();
|
||||||
|
if (!retransformSupported) {
|
||||||
|
Logging.LAUNCH.warning("Retransform is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
Logging.LAUNCH.info("Version: " + getVersion());
|
Logging.LAUNCH.info("Version: " + getVersion());
|
||||||
|
|
||||||
Optional<YggdrasilConfiguration> optionalConfig = configure();
|
Optional<YggdrasilConfiguration> optionalConfig = configure();
|
||||||
if (optionalConfig.isPresent()) {
|
if (optionalConfig.isPresent()) {
|
||||||
transformerRegistry.accept(createTransformer(optionalConfig.get()));
|
classTransformer = createTransformer(optionalConfig.get());
|
||||||
|
instrumentation.addTransformer(classTransformer, retransformSupported);
|
||||||
|
|
||||||
|
MC52974Workaround.init();
|
||||||
|
MC52974_1710Workaround.init();
|
||||||
} else {
|
} else {
|
||||||
Logging.LAUNCH.severe("No config available");
|
Logging.LAUNCH.severe("No config available");
|
||||||
throw new InjectorInitializationException();
|
throw new InjectorInitializationException();
|
||||||
@ -315,11 +334,6 @@ public final class AuthlibInjector {
|
|||||||
filters.add(new QueryUUIDsFilter(mojangClient, customClient));
|
filters.add(new QueryUUIDsFilter(mojangClient, customClient));
|
||||||
filters.add(new QueryProfileFilter(mojangClient, customClient));
|
filters.add(new QueryProfileFilter(mojangClient, customClient));
|
||||||
|
|
||||||
MainArgumentsTransformer.getListeners().add(args -> {
|
|
||||||
MC52974Workaround.acceptMainArguments(args);
|
|
||||||
return args;
|
|
||||||
});
|
|
||||||
|
|
||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -342,7 +356,6 @@ public final class AuthlibInjector {
|
|||||||
transformer.units.add(new MainArgumentsTransformer());
|
transformer.units.add(new MainArgumentsTransformer());
|
||||||
transformer.units.add(new ConstantURLTransformUnit(urlProcessor));
|
transformer.units.add(new ConstantURLTransformUnit(urlProcessor));
|
||||||
transformer.units.add(new CitizensTransformer());
|
transformer.units.add(new CitizensTransformer());
|
||||||
transformer.units.add(new MC52974Workaround());
|
|
||||||
transformer.units.add(new LaunchWrapperTransformer());
|
transformer.units.add(new LaunchWrapperTransformer());
|
||||||
|
|
||||||
transformer.units.add(new SkinWhitelistTransformUnit(config.getSkinDomains().toArray(new String[0])));
|
transformer.units.add(new SkinWhitelistTransformUnit(config.getSkinDomains().toArray(new String[0])));
|
||||||
@ -357,4 +370,62 @@ public final class AuthlibInjector {
|
|||||||
return AuthlibInjector.class.getPackage().getImplementationVersion();
|
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(AuthlibInjector::canRetransformClass)
|
||||||
|
.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 void retransformAllClasses() {
|
||||||
|
if (!retransformSupported) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logging.TRANSFORM.info("Attempt to retransform all classes");
|
||||||
|
long t0 = System.currentTimeMillis();
|
||||||
|
|
||||||
|
Class<?>[] classes = Stream.of(instrumentation.getAllLoadedClasses())
|
||||||
|
.filter(AuthlibInjector::canRetransformClass)
|
||||||
|
.toArray(Class[]::new);
|
||||||
|
if (classes.length > 0) {
|
||||||
|
try {
|
||||||
|
instrumentation.retransformClasses(classes);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Logging.TRANSFORM.log(Level.WARNING, "Failed to retransform", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long t1 = System.currentTimeMillis();
|
||||||
|
Logging.TRANSFORM.info("Retransformed " + classes.length + " classes in " + (t1 - t0) + "ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean canRetransformClass(Class<?> clazz) {
|
||||||
|
if (!instrumentation.isModifiableClass(clazz)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String name = clazz.getName();
|
||||||
|
for (String prefix : nonTransformablePackages) {
|
||||||
|
if (name.startsWith(prefix)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClassTransformer getClassTransformer() {
|
||||||
|
return classTransformer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,22 +16,15 @@
|
|||||||
*/
|
*/
|
||||||
package moe.yushi.authlibinjector.javaagent;
|
package moe.yushi.authlibinjector.javaagent;
|
||||||
|
|
||||||
import static moe.yushi.authlibinjector.AuthlibInjector.PROP_API_ROOT;
|
|
||||||
import static moe.yushi.authlibinjector.AuthlibInjector.bootstrap;
|
|
||||||
import static moe.yushi.authlibinjector.AuthlibInjector.nonTransformablePackages;
|
|
||||||
import java.lang.instrument.Instrumentation;
|
import java.lang.instrument.Instrumentation;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import moe.yushi.authlibinjector.AuthlibInjector;
|
||||||
import moe.yushi.authlibinjector.InjectorInitializationException;
|
import moe.yushi.authlibinjector.InjectorInitializationException;
|
||||||
import moe.yushi.authlibinjector.util.Logging;
|
import moe.yushi.authlibinjector.util.Logging;
|
||||||
|
|
||||||
public class AuthlibInjectorPremain {
|
public class AuthlibInjectorPremain {
|
||||||
|
|
||||||
static {
|
|
||||||
Logging.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void premain(String arg, Instrumentation instrumentation) {
|
public static void premain(String arg, Instrumentation instrumentation) {
|
||||||
try {
|
try {
|
||||||
initInjector(arg, instrumentation, false);
|
initInjector(arg, instrumentation, false);
|
||||||
@ -55,63 +48,18 @@ public class AuthlibInjectorPremain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void initInjector(String arg, Instrumentation instrumentation, boolean needsRetransform) {
|
public static void initInjector(String arg, Instrumentation instrumentation, boolean retransform) {
|
||||||
setupConfig(arg);
|
setupConfig(arg);
|
||||||
|
AuthlibInjector.bootstrap(instrumentation);
|
||||||
|
|
||||||
boolean retransformSupported = instrumentation.isRetransformClassesSupported();
|
if (retransform) {
|
||||||
boolean retransformEnabled = retransformSupported && needsRetransform;
|
AuthlibInjector.retransformAllClasses();
|
||||||
bootstrap(x -> instrumentation.addTransformer(x, retransformEnabled));
|
|
||||||
|
|
||||||
if (needsRetransform) {
|
|
||||||
if (retransformSupported) {
|
|
||||||
Logging.TRANSFORM.info("Start retransforming");
|
|
||||||
doRetransform(instrumentation);
|
|
||||||
} else {
|
|
||||||
Logging.TRANSFORM.warning("Retransform is not supported");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setupConfig(String arg) {
|
private static void setupConfig(String arg) {
|
||||||
if (arg != null && !arg.isEmpty()) {
|
if (arg != null && !arg.isEmpty()) {
|
||||||
System.setProperty(PROP_API_ROOT, arg);
|
System.setProperty(AuthlibInjector.PROP_API_ROOT, arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void doRetransform(Instrumentation instrumentation) {
|
|
||||||
try {
|
|
||||||
long t0 = System.currentTimeMillis();
|
|
||||||
Class<?>[] classToRetransform = getClassesToRetransform(instrumentation);
|
|
||||||
if (classToRetransform.length > 0) {
|
|
||||||
instrumentation.retransformClasses(classToRetransform);
|
|
||||||
}
|
|
||||||
long t1 = System.currentTimeMillis();
|
|
||||||
Logging.TRANSFORM.info("Retransform finished in " + (t1 - t0) + "ms");
|
|
||||||
} catch (Throwable e) {
|
|
||||||
Logging.TRANSFORM.log(Level.SEVERE, "Failed to retransform", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?>[] getClassesToRetransform(Instrumentation instrumentation) {
|
|
||||||
Class<?>[] loadedClasses = instrumentation.getAllLoadedClasses();
|
|
||||||
Class<?>[] dest = new Class[loadedClasses.length];
|
|
||||||
int idx = 0;
|
|
||||||
for (Class<?> clazz : loadedClasses) {
|
|
||||||
if (instrumentation.isModifiableClass(clazz)) {
|
|
||||||
boolean toRetransform = true;
|
|
||||||
for (String nonTransformablePackage : nonTransformablePackages) {
|
|
||||||
if (clazz.getName().startsWith(nonTransformablePackage)) {
|
|
||||||
toRetransform = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (toRetransform) {
|
|
||||||
dest[idx++] = clazz;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Logging.TRANSFORM.fine("Loaded " + loadedClasses.length + " classes, " + idx + " to retransform");
|
|
||||||
return Arrays.copyOf(dest, idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,11 @@ import java.lang.instrument.IllegalClassFormatException;
|
|||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.objectweb.asm.ClassReader;
|
import org.objectweb.asm.ClassReader;
|
||||||
import org.objectweb.asm.ClassVisitor;
|
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);
|
private static final boolean PRINT_UNTRANSFORMED_CLASSES = Boolean.getBoolean(AuthlibInjector.PROP_PRINT_UNTRANSFORMED_CLASSES);
|
||||||
|
|
||||||
public List<TransformUnit> units = new ArrayList<>();
|
public final List<TransformUnit> units = new CopyOnWriteArrayList<>();
|
||||||
public List<ClassLoadingListener> listeners = new ArrayList<>();
|
public final List<ClassLoadingListener> listeners = new CopyOnWriteArrayList<>();
|
||||||
public Set<String> ignores = new HashSet<>();
|
public final Set<String> ignores = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||||
|
|
||||||
private static class TransformHandle {
|
private static class TransformHandle {
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@ -20,22 +21,6 @@ import moe.yushi.authlibinjector.util.Logging;
|
|||||||
|
|
||||||
public class MainArgumentsTransformer implements TransformUnit {
|
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
|
@Override
|
||||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||||
if ("net.minecraft.client.main.Main".equals(className)) {
|
if ("net.minecraft.client.main.Main".equals(className)) {
|
||||||
@ -68,4 +53,60 @@ public class MainArgumentsTransformer implements TransformUnit {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "Main Arguments Transformer";
|
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 java.util.Collections.unmodifiableSet;
|
||||||
import static org.objectweb.asm.Opcodes.ASM7;
|
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 static org.objectweb.asm.Opcodes.ISTORE;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -13,8 +11,9 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.objectweb.asm.ClassVisitor;
|
import org.objectweb.asm.ClassVisitor;
|
||||||
import org.objectweb.asm.MethodVisitor;
|
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.transform.TransformUnit;
|
||||||
import moe.yushi.authlibinjector.util.Logging;
|
import moe.yushi.authlibinjector.util.Logging;
|
||||||
|
|
||||||
@ -23,11 +22,10 @@ import moe.yushi.authlibinjector.util.Logging;
|
|||||||
*/
|
*/
|
||||||
public class MC52974Workaround implements TransformUnit {
|
public class MC52974Workaround implements TransformUnit {
|
||||||
|
|
||||||
private static boolean affected = false;
|
private MC52974Workaround() {
|
||||||
|
}
|
||||||
|
|
||||||
// ==== Detect affected versions ====
|
private static final Set<String> AFFECTED_VERSION_SERIES = unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||||
public static final Set<String> AFFECTED_VERSION_SERIES = unmodifiableSet(new HashSet<>(Arrays.asList(
|
|
||||||
"1.7.4", // MC 1.7.9 uses this
|
|
||||||
"1.7.10",
|
"1.7.10",
|
||||||
"1.8",
|
"1.8",
|
||||||
"1.9",
|
"1.9",
|
||||||
@ -35,42 +33,15 @@ public class MC52974Workaround implements TransformUnit {
|
|||||||
"1.11",
|
"1.11",
|
||||||
"1.12")));
|
"1.12")));
|
||||||
|
|
||||||
public static Optional<String> inferVersionSeries(String[] args) {
|
public static void init() {
|
||||||
boolean hit = false;
|
MainArgumentsTransformer.getVersionSeriesListeners().add(version -> {
|
||||||
for (String arg : args) {
|
if (AFFECTED_VERSION_SERIES.contains(version)) {
|
||||||
if (hit) {
|
Logging.TRANSFORM.info("Enable MC-52974 Workaround");
|
||||||
if (arg.startsWith("--")) {
|
AuthlibInjector.getClassTransformer().units.add(new MC52974Workaround());
|
||||||
// arg doesn't seem to be a value
|
AuthlibInjector.retransformClasses("com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService");
|
||||||
// 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 boolean overwriteRequireSecure(boolean requireSecure) {
|
|
||||||
if (affected) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return requireSecure;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||||
@ -84,8 +55,7 @@ public class MC52974Workaround implements TransformUnit {
|
|||||||
public void visitCode() {
|
public void visitCode() {
|
||||||
super.visitCode();
|
super.visitCode();
|
||||||
modifiedCallback.run();
|
modifiedCallback.run();
|
||||||
super.visitVarInsn(ILOAD, 2);
|
super.visitLdcInsn(1);
|
||||||
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(MC52974Workaround.class), "overwriteRequireSecure", "(Z)Z", false);
|
|
||||||
super.visitVarInsn(ISTORE, 2);
|
super.visitVarInsn(ISTORE, 2);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,176 @@
|
|||||||
|
package moe.yushi.authlibinjector.transform.support;
|
||||||
|
|
||||||
|
import static org.objectweb.asm.Opcodes.ARETURN;
|
||||||
|
import static org.objectweb.asm.Opcodes.ASM7;
|
||||||
|
import static org.objectweb.asm.Opcodes.CHECKCAST;
|
||||||
|
import static org.objectweb.asm.Opcodes.DUP;
|
||||||
|
import static org.objectweb.asm.Opcodes.GETFIELD;
|
||||||
|
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 SessionTransformer());
|
||||||
|
AuthlibInjector.getClassTransformer().units.add(new S0CPacketSpawnPlayerTransformer());
|
||||||
|
AuthlibInjector.retransformClasses("bbs", "net.minecraft.util.Session", "gb", "net.minecraft.network.play.server.S0CPacketSpawnPlayer");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty GameProfile -> Filled GameProfile?
|
||||||
|
private static final Map<Object, Optional<Object>> markedGameProfiles = new WeakIdentityHashMap<>();
|
||||||
|
|
||||||
|
public static void markGameProfile(Object gp) {
|
||||||
|
synchronized (markedGameProfiles) {
|
||||||
|
markedGameProfiles.putIfAbsent(gp, Optional.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object accessGameProfile(Object gp, Object minecraftServer, boolean isNotchName) {
|
||||||
|
synchronized (markedGameProfiles) {
|
||||||
|
Optional<Object> value = markedGameProfiles.get(gp);
|
||||||
|
if (value != null) {
|
||||||
|
if (value.isPresent()) {
|
||||||
|
return value.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// query it
|
||||||
|
if (minecraftServer != null) {
|
||||||
|
Logging.TRANSFORM.info("Filling properties for " + gp);
|
||||||
|
try {
|
||||||
|
ClassLoader cl = minecraftServer.getClass().getClassLoader();
|
||||||
|
|
||||||
|
Class<?> classGameProfile = cl.loadClass("com.mojang.authlib.GameProfile");
|
||||||
|
Object gameProfile = classGameProfile.getConstructor(UUID.class, String.class)
|
||||||
|
.newInstance(
|
||||||
|
classGameProfile.getMethod("getId").invoke(gp),
|
||||||
|
classGameProfile.getMethod("getName").invoke(gp));
|
||||||
|
|
||||||
|
Class<?> classMinecraftServer = cl.loadClass("net.minecraft.server.MinecraftServer");
|
||||||
|
Object minecraftSessionService = (isNotchName
|
||||||
|
? classMinecraftServer.getMethod("av")
|
||||||
|
: classMinecraftServer.getMethod("func_147130_as"))
|
||||||
|
.invoke(minecraftServer);
|
||||||
|
|
||||||
|
Object filledGameProfile = cl.loadClass("com.mojang.authlib.minecraft.MinecraftSessionService").getMethod("fillProfileProperties", classGameProfile, boolean.class)
|
||||||
|
.invoke(minecraftSessionService, gameProfile, true);
|
||||||
|
|
||||||
|
markedGameProfiles.put(gp, Optional.of(filledGameProfile));
|
||||||
|
return filledGameProfile;
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
Logging.TRANSFORM.log(Level.WARNING, "Failed to inject GameProfile properties", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SessionTransformer implements TransformUnit {
|
||||||
|
@Override
|
||||||
|
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||||
|
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) {
|
||||||
|
if (isNotchName
|
||||||
|
? "e".equals(name) && "()Lcom/mojang/authlib/GameProfile;".equals(descriptor)
|
||||||
|
: "func_148256_e".equals(name) && "()Lcom/mojang/authlib/GameProfile;".equals(descriptor)) {
|
||||||
|
|
||||||
|
return new MethodVisitor(ASM7, super.visitMethod(access, name, descriptor, signature, exceptions)) {
|
||||||
|
@Override
|
||||||
|
public void visitInsn(int opcode) {
|
||||||
|
if (opcode == ARETURN) {
|
||||||
|
modifiedCallback.run();
|
||||||
|
super.visitInsn(DUP);
|
||||||
|
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(MC52974_1710Workaround.class), "markGameProfile", "(Ljava/lang/Object;)V", false);
|
||||||
|
}
|
||||||
|
super.visitInsn(opcode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "1.7.10 MC-52974 Workaround (Session)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class S0CPacketSpawnPlayerTransformer implements TransformUnit {
|
||||||
|
@Override
|
||||||
|
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
|
||||||
|
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) {
|
||||||
|
if (isNotchName
|
||||||
|
? "b".equals(name) && "(Let;)V".equals(descriptor)
|
||||||
|
: "func_148840_b".equals(name) && "(Lnet/minecraft/network/PacketBuffer;)V".equals(descriptor)) {
|
||||||
|
|
||||||
|
return new MethodVisitor(ASM7, super.visitMethod(access, name, descriptor, signature, exceptions)) {
|
||||||
|
@Override
|
||||||
|
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
|
||||||
|
super.visitFieldInsn(opcode, owner, name, descriptor);
|
||||||
|
|
||||||
|
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();
|
||||||
|
if (isNotchName) {
|
||||||
|
super.visitMethodInsn(INVOKESTATIC, "net/minecraft/server/MinecraftServer", "I", "()Lnet/minecraft/server/MinecraftServer;", false);
|
||||||
|
} else {
|
||||||
|
super.visitMethodInsn(INVOKESTATIC, "net/minecraft/server/MinecraftServer", "func_71276_C", "()Lnet/minecraft/server/MinecraftServer;", false);
|
||||||
|
}
|
||||||
|
super.visitLdcInsn(isNotchName ? 1 : 0);
|
||||||
|
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(MC52974_1710Workaround.class), "accessGameProfile", "(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object;", false);
|
||||||
|
super.visitTypeInsn(CHECKCAST, "com/mojang/authlib/GameProfile");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "1.7.10 MC-52974 Workaround (S0CPacketSpawnPlayer)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> Optional<T> detectNotchName(String input, String ifNotchName, String ifDeobf, Function<Boolean, T> callback) {
|
||||||
|
if (ifNotchName.equals(input)) {
|
||||||
|
return Optional.of(callback.apply(true));
|
||||||
|
} else if (ifDeobf.equals(input)) {
|
||||||
|
return Optional.of(callback.apply(false));
|
||||||
|
} else {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -43,9 +43,8 @@ public final class Logging {
|
|||||||
|
|
||||||
private static Predicate<String> debugLoggerNamePredicate;
|
private static Predicate<String> debugLoggerNamePredicate;
|
||||||
|
|
||||||
public static void init() {
|
static {
|
||||||
debugLoggerNamePredicate = createDebugLoggerNamePredicate();
|
debugLoggerNamePredicate = createDebugLoggerNamePredicate();
|
||||||
|
|
||||||
initRootLogger();
|
initRootLogger();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> 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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* This file was originally from <https://github.com/apache/avro/blob/2bbb99602e9e925058ead86fc8ac4e27055b05d6/lang/java/avro/src/main/java/org/apache/avro/util/WeakIdentityHashMap.java>
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package moe.yushi.authlibinjector.util;
|
||||||
|
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a combination of WeakHashMap and IdentityHashMap.
|
||||||
|
* Useful for caches that need to key off of a == comparison
|
||||||
|
* instead of a .equals.
|
||||||
|
*
|
||||||
|
* <b>
|
||||||
|
* This class is not a general-purpose Map implementation! While
|
||||||
|
* this class implements the Map interface, it intentionally violates
|
||||||
|
* Map's general contract, which mandates the use of the equals method
|
||||||
|
* when comparing objects. This class is designed for use only in the
|
||||||
|
* rare cases wherein reference-equality semantics are required.
|
||||||
|
*
|
||||||
|
* Note that this implementation is not synchronized.
|
||||||
|
* </b>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public class WeakIdentityHashMap<K, V> implements Map<K, V> {
|
||||||
|
private final ReferenceQueue<K> queue = new ReferenceQueue<>();
|
||||||
|
private Map<IdentityWeakReference, V> backingStore = new HashMap<>();
|
||||||
|
|
||||||
|
public WeakIdentityHashMap() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
backingStore.clear();
|
||||||
|
reap();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
reap();
|
||||||
|
return backingStore.containsKey(new IdentityWeakReference(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unlikely-arg-type")
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(Object value) {
|
||||||
|
reap();
|
||||||
|
return backingStore.containsValue(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Map.Entry<K, V>> entrySet() {
|
||||||
|
reap();
|
||||||
|
Set<Map.Entry<K, V>> ret = new HashSet<>();
|
||||||
|
for (Map.Entry<IdentityWeakReference, V> ref : backingStore.entrySet()) {
|
||||||
|
final K key = ref.getKey().get();
|
||||||
|
final V value = ref.getValue();
|
||||||
|
Map.Entry<K, V> entry = new Map.Entry<K, V>() {
|
||||||
|
@Override
|
||||||
|
public K getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V setValue(V value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ret.add(entry);
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableSet(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<K> keySet() {
|
||||||
|
reap();
|
||||||
|
Set<K> ret = new HashSet<>();
|
||||||
|
for (IdentityWeakReference ref : backingStore.keySet()) {
|
||||||
|
ret.add(ref.get());
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableSet(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (!(o instanceof WeakIdentityHashMap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return backingStore.equals(((WeakIdentityHashMap<?, ?>) o).backingStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object key) {
|
||||||
|
reap();
|
||||||
|
return backingStore.get(new IdentityWeakReference(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(K key, V value) {
|
||||||
|
reap();
|
||||||
|
return backingStore.put(new IdentityWeakReference(key), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
reap();
|
||||||
|
return backingStore.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
reap();
|
||||||
|
return backingStore.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(Map<? extends K, ? extends V> t) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V remove(Object key) {
|
||||||
|
reap();
|
||||||
|
return backingStore.remove(new IdentityWeakReference(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
reap();
|
||||||
|
return backingStore.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<V> values() {
|
||||||
|
reap();
|
||||||
|
return backingStore.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void reap() {
|
||||||
|
Object zombie = queue.poll();
|
||||||
|
|
||||||
|
while (zombie != null) {
|
||||||
|
IdentityWeakReference victim = (IdentityWeakReference) zombie;
|
||||||
|
backingStore.remove(victim);
|
||||||
|
zombie = queue.poll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IdentityWeakReference extends WeakReference<K> {
|
||||||
|
int hash;
|
||||||
|
|
||||||
|
IdentityWeakReference(Object obj) {
|
||||||
|
super((K) obj, queue);
|
||||||
|
hash = System.identityHashCode(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof WeakIdentityHashMap.IdentityWeakReference)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
IdentityWeakReference ref = (IdentityWeakReference) o;
|
||||||
|
if (this.get() == ref.get()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
package moe.yushi.authlibinjector.test;
|
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 static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class MC52974WorkaroundTest {
|
public class VersionSeriesDetectTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testNone() {
|
public void testNone() {
|
Loading…
x
Reference in New Issue
Block a user