Merge pull request #37 from yushijinhun/mc52974-1710

实现 #35
This commit is contained in:
Haowei Wen 2019-01-20 20:50:36 +08:00 committed by GitHub
commit 3eee25593e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 564 additions and 136 deletions

View file

@ -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;
@ -55,6 +59,7 @@ import moe.yushi.authlibinjector.transform.YggdrasilKeyTransformUnit;
import moe.yushi.authlibinjector.transform.support.CitizensTransformer;
import moe.yushi.authlibinjector.transform.support.LaunchWrapperTransformer;
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.yggdrasil.CustomYggdrasilAPIProvider;
import moe.yushi.authlibinjector.yggdrasil.MojangYggdrasilAPIProvider;
@ -117,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();
@ -315,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;
}
@ -342,7 +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 LaunchWrapperTransformer());
transformer.units.add(new SkinWhitelistTransformUnit(config.getSkinDomains().toArray(new String[0])));
@ -357,4 +370,62 @@ 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(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;
}
}

View file

@ -16,22 +16,15 @@
*/
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.util.Arrays;
import java.util.logging.Level;
import moe.yushi.authlibinjector.AuthlibInjector;
import moe.yushi.authlibinjector.InjectorInitializationException;
import moe.yushi.authlibinjector.util.Logging;
public class AuthlibInjectorPremain {
static {
Logging.init();
}
public static void premain(String arg, Instrumentation instrumentation) {
try {
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);
AuthlibInjector.bootstrap(instrumentation);
boolean retransformSupported = instrumentation.isRetransformClassesSupported();
boolean retransformEnabled = retransformSupported && needsRetransform;
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");
}
if (retransform) {
AuthlibInjector.retransformAllClasses();
}
}
private static void setupConfig(String arg) {
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);
}
}

View file

@ -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 {

View file

@ -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;
}
// ====
}

View file

@ -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);
}
};

View file

@ -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();
}
}
}

View file

@ -43,9 +43,8 @@ public final class Logging {
private static Predicate<String> debugLoggerNamePredicate;
public static void init() {
static {
debugLoggerNamePredicate = createDebugLoggerNamePredicate();
initRootLogger();
}

View file

@ -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;
}
}
}

View file

@ -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() {