diff --git a/src/main/java/moe/yushi/authlibinjector/AuthlibInjector.java b/src/main/java/moe/yushi/authlibinjector/AuthlibInjector.java index b6bdafc..0dc2d90 100644 --- a/src/main/java/moe/yushi/authlibinjector/AuthlibInjector.java +++ b/src/main/java/moe/yushi/authlibinjector/AuthlibInjector.java @@ -55,6 +55,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; @@ -343,6 +344,7 @@ public final class AuthlibInjector { 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]))); 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 new file mode 100644 index 0000000..f07919d --- /dev/null +++ b/src/main/java/moe/yushi/authlibinjector/transform/support/MC52974_1710Workaround.java @@ -0,0 +1,131 @@ +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.logging.Level; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +import moe.yushi.authlibinjector.transform.TransformUnit; +import moe.yushi.authlibinjector.util.Logging; +import moe.yushi.authlibinjector.util.WeakIdentityHashMap; + +public class MC52974_1710Workaround implements TransformUnit { + + // Empty GameProfile -> Filled GameProfile? + private static final Map> markedGameProfiles = new WeakIdentityHashMap<>(); + + public static void markGameProfile(Object gp) { + synchronized (markedGameProfiles) { + markedGameProfiles.putIfAbsent(gp, Optional.empty()); + } + } + + public static Object accessGameProfile(Object gp, Object minecraftServer) { + synchronized (markedGameProfiles) { + Optional 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 { + Class classGameProfile = gp.getClass(); + Object gameProfile = classGameProfile.getConstructor(UUID.class, String.class) + .newInstance( + classGameProfile.getMethod("getId").invoke(gp), + classGameProfile.getMethod("getName").invoke(gp)); + + // MD: net/minecraft/server/MinecraftServer/av ()Lcom/mojang/authlib/minecraft/MinecraftSessionService; net/minecraft/server/MinecraftServer/func_147130_as ()Lcom/mojang/authlib/minecraft/MinecraftSessionService; + Object minecraftSessionService = minecraftServer.getClass().getMethod("av").invoke(minecraftServer); + Object filledGameProfile = minecraftSessionService.getClass().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; + } + + @Override + public Optional transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) { + if ("bbs".equals(className)) { + // CL: bbs net/minecraft/util/Session + return Optional.of(new ClassVisitor(ASM7, writer) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + if ("e".equals(name) && "()Lcom/mojang/authlib/GameProfile;".equals(descriptor)) { + // MD: bbs/e ()Lcom/mojang/authlib/GameProfile; net/minecraft/util/Session/func_148256_e ()Lcom/mojang/authlib/GameProfile; + 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); + } + } + }); + + } else if ("gb".equals(className)) { + // CL: gb net/minecraft/network/play/server/S0CPacketSpawnPlayer + return Optional.of(new ClassVisitor(ASM7, writer) { + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + if ("b".equals(name) && "(Let;)V".equals(descriptor)) { + // MD: gb/b (Let;)V net/minecraft/network/play/server/S0CPacketSpawnPlayer/func_148840_b (Lnet/minecraft/network/PacketBuffer;)V + // func_148840_b,writePacketData,0,Writes the raw packet data to the data stream. + 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); + // FD: gb/b net/minecraft/network/play/server/S0CPacketSpawnPlayer/field_148955_b + if (opcode == GETFIELD && "gb".equals(owner) && "b".equals(name) && "Lcom/mojang/authlib/GameProfile;".equals(descriptor)) { + modifiedCallback.run(); + // MD: net/minecraft/server/MinecraftServer/I ()Lnet/minecraft/server/MinecraftServer; net/minecraft/server/MinecraftServer/func_71276_C ()Lnet/minecraft/server/MinecraftServer; + // func_71276_C,getServer,0,Gets mcServer. + super.visitMethodInsn(INVOKESTATIC, "net/minecraft/server/MinecraftServer", "I", "()Lnet/minecraft/server/MinecraftServer;", false); + super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(MC52974_1710Workaround.class), "accessGameProfile", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false); + super.visitTypeInsn(CHECKCAST, "com/mojang/authlib/GameProfile"); + } + } + }; + } else { + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + } + }); + } + return Optional.empty(); + } + + @Override + public String toString() { + return "MC-52974 Workaround for 1.7.10"; + } +} diff --git a/src/main/java/moe/yushi/authlibinjector/util/WeakIdentityHashMap.java b/src/main/java/moe/yushi/authlibinjector/util/WeakIdentityHashMap.java new file mode 100644 index 0000000..719ec4d --- /dev/null +++ b/src/main/java/moe/yushi/authlibinjector/util/WeakIdentityHashMap.java @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2019 Haowei Wen 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 . + */ +/* + * This file was originally from + */ +/* + * 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. + * + * + * 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. + * + */ +@SuppressWarnings("unchecked") +public class WeakIdentityHashMap implements Map { + private final ReferenceQueue queue = new ReferenceQueue<>(); + private Map 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> entrySet() { + reap(); + Set> ret = new HashSet<>(); + for (Map.Entry ref : backingStore.entrySet()) { + final K key = ref.getKey().get(); + final V value = ref.getValue(); + Map.Entry entry = new Map.Entry() { + @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 keySet() { + reap(); + Set 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 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 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 { + 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; + } + } +}