mirror of
https://github.com/unmojang/authlib-injector.git
synced 2025-09-24 03:41:41 -04:00
Merge branch 'evan-goode/fetch-missing-textures' into develop
This commit is contained in:
commit
6d66308929
@ -61,6 +61,7 @@ import moe.yushi.authlibinjector.transform.support.BungeeCordProfileKeyTransform
|
||||
import moe.yushi.authlibinjector.transform.support.CitizensTransformer;
|
||||
import moe.yushi.authlibinjector.transform.support.ConcatenateURLTransformUnit;
|
||||
import moe.yushi.authlibinjector.transform.support.ConstantURLTransformUnit;
|
||||
import moe.yushi.authlibinjector.transform.support.FetchMissingTexturesByPlayerName;
|
||||
import moe.yushi.authlibinjector.transform.support.MC52974Workaround;
|
||||
import moe.yushi.authlibinjector.transform.support.MC52974_1710Workaround;
|
||||
import moe.yushi.authlibinjector.transform.support.MainArgumentsTransformer;
|
||||
@ -270,7 +271,7 @@ public final class AuthlibInjector {
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
private static List<URLFilter> createFilters(APIMetadata config) {
|
||||
private static List<URLFilter> createFilters(APIMetadata config, YggdrasilClient customClient, YggdrasilClient mojangClient) {
|
||||
if (Config.httpdDisabled) {
|
||||
log(INFO, "Disabled local HTTP server");
|
||||
return emptyList();
|
||||
@ -278,9 +279,6 @@ public final class AuthlibInjector {
|
||||
|
||||
List<URLFilter> filters = new ArrayList<>();
|
||||
|
||||
YggdrasilClient customClient = new YggdrasilClient(new CustomYggdrasilAPIProvider(config));
|
||||
YggdrasilClient mojangClient = new YggdrasilClient(new MojangYggdrasilAPIProvider(), Config.mojangProxy);
|
||||
|
||||
boolean legacySkinPolyfillDefault = !Boolean.TRUE.equals(config.getMeta().get("feature.legacy_skin_api"));
|
||||
if (Config.legacySkinPolyfill.isEnabled(legacySkinPolyfillDefault)) {
|
||||
filters.add(new LegacySkinAPIFilter(customClient));
|
||||
@ -312,7 +310,10 @@ public final class AuthlibInjector {
|
||||
}
|
||||
|
||||
private static ClassTransformer createTransformer(APIMetadata config) {
|
||||
URLProcessor urlProcessor = new URLProcessor(createFilters(config), new DefaultURLRedirector(config));
|
||||
YggdrasilClient customClient = new YggdrasilClient(new CustomYggdrasilAPIProvider(config));
|
||||
YggdrasilClient mojangClient = new YggdrasilClient(new MojangYggdrasilAPIProvider(), Config.mojangProxy);
|
||||
|
||||
URLProcessor urlProcessor = new URLProcessor(createFilters(config, customClient, mojangClient), new DefaultURLRedirector(config));
|
||||
|
||||
ClassTransformer transformer = new ClassTransformer();
|
||||
transformer.setIgnores(Config.ignoredPackages);
|
||||
@ -330,6 +331,9 @@ public final class AuthlibInjector {
|
||||
transformer.units.add(new CitizensTransformer());
|
||||
transformer.units.add(new ConcatenateURLTransformUnit());
|
||||
|
||||
FetchMissingTexturesByPlayerName.setYggdrasilClient(customClient);
|
||||
transformer.units.add(new FetchMissingTexturesByPlayerName());
|
||||
|
||||
boolean usernameCheckDefault = Boolean.TRUE.equals(config.getMeta().get("feature.username_check"));
|
||||
if (Config.usernameCheck.isEnabled(usernameCheckDefault)) {
|
||||
log(INFO, "Username check is enforced");
|
||||
|
@ -137,7 +137,7 @@ public class ClassTransformer implements ClassFileTransformer {
|
||||
public void accept(TransformUnit... units) {
|
||||
long t0 = System.nanoTime();
|
||||
|
||||
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
||||
|
||||
TransformContextImpl[] ctxs = new TransformContextImpl[units.length];
|
||||
ClassVisitor chain = writer;
|
||||
@ -204,7 +204,7 @@ public class ClassTransformer implements ClassFileTransformer {
|
||||
}
|
||||
|
||||
ClassReader reader = getClassReader();
|
||||
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
|
||||
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
||||
ClassVisitor visitor = new ClassVisitor(ASM9, writer) {
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
@ -218,7 +218,7 @@ public class ClassTransformer implements ClassFileTransformer {
|
||||
|
||||
private void injectGeneratedMethods() {
|
||||
ClassReader reader = getClassReader();
|
||||
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
|
||||
ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
|
||||
ClassVisitor visitor = new ClassVisitor(ASM9, writer) {
|
||||
@Override
|
||||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||
|
@ -0,0 +1,216 @@
|
||||
package moe.yushi.authlibinjector.transform.support;
|
||||
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
|
||||
import static org.objectweb.asm.Opcodes.*;
|
||||
|
||||
import static moe.yushi.authlibinjector.util.Logging.Level.ERROR;
|
||||
import static moe.yushi.authlibinjector.util.Logging.Level.INFO;
|
||||
import static moe.yushi.authlibinjector.util.Logging.log;
|
||||
|
||||
import moe.yushi.authlibinjector.transform.CallbackMethod;
|
||||
import moe.yushi.authlibinjector.transform.TransformContext;
|
||||
import moe.yushi.authlibinjector.transform.TransformUnit;
|
||||
import moe.yushi.authlibinjector.yggdrasil.GameProfile.PropertyValue;
|
||||
import moe.yushi.authlibinjector.yggdrasil.GameProfile;
|
||||
import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient;
|
||||
|
||||
public class FetchMissingTexturesByPlayerName implements TransformUnit {
|
||||
private static volatile YggdrasilClient yggdrasilClient;
|
||||
public static void setYggdrasilClient(YggdrasilClient yggdrasilClient) {
|
||||
FetchMissingTexturesByPlayerName.yggdrasilClient = yggdrasilClient;
|
||||
}
|
||||
|
||||
private static final ConcurrentHashMap<String, Optional<UUID>> nameToUUIDCache = new ConcurrentHashMap<>();
|
||||
private static final ConcurrentHashMap<UUID, Optional<PropertyValue>> uuidToTexturesCache = new ConcurrentHashMap<>();
|
||||
|
||||
public static Object getMissingTexturesProperty(Object profile) {
|
||||
// (com.mojang.authlib.GameProfile) -> com.mojang.authlib.properties.Property
|
||||
// Fetches missing textures for a GameProfile by player name via the yggdrasilClient.
|
||||
try {
|
||||
// If the GameProfile already has textures, return null.
|
||||
Class<?> gameProfileClass = profile.getClass();
|
||||
Method getProperties = gameProfileClass.getMethod("getProperties");
|
||||
Object propertiesMap = getProperties.invoke(profile);
|
||||
Method containsKey = propertiesMap.getClass().getMethod("containsKey", Object.class);
|
||||
boolean hasTextures = (boolean) containsKey.invoke(propertiesMap, "textures");
|
||||
if (hasTextures) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Method getName = gameProfileClass.getMethod("getName");
|
||||
String name = (String) getName.invoke(profile);
|
||||
Optional<UUID> maybeUUID = nameToUUIDCache.computeIfAbsent(name, n -> {
|
||||
try {
|
||||
return yggdrasilClient.queryUUID(n);
|
||||
} catch (UncheckedIOException e) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (maybeUUID == null || !maybeUUID.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
UUID uuid = maybeUUID.get();
|
||||
|
||||
Optional<PropertyValue> maybeTextures = uuidToTexturesCache.computeIfAbsent(uuid, u -> {
|
||||
Optional<GameProfile> maybeFullProfile;
|
||||
try {
|
||||
maybeFullProfile = yggdrasilClient.queryProfile(u, true);
|
||||
} catch (UncheckedIOException e) {
|
||||
return null;
|
||||
}
|
||||
return maybeFullProfile.map(fullProfile -> {
|
||||
return fullProfile.properties.get("textures");
|
||||
});
|
||||
});
|
||||
if (maybeTextures == null || !maybeTextures.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
PropertyValue textures = maybeTextures.get();
|
||||
|
||||
log(INFO, "Successfully fetched missing textures for player " + name);
|
||||
Class<?> propertyClass = profile.getClass()
|
||||
.getClassLoader()
|
||||
.loadClass("com.mojang.authlib.properties.Property");
|
||||
Constructor<?> propertyConstructor = propertyClass.getConstructor(String.class, String.class, String.class);
|
||||
return propertyConstructor.newInstance("textures", textures.value, textures.signature);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@CallbackMethod
|
||||
public static Object getTextures(Object instance, Object profile, boolean requireSecure) {
|
||||
try {
|
||||
Object property = getMissingTexturesProperty(profile);
|
||||
if (property != null) {
|
||||
// Fill in the existing GameProfile with the missing textures
|
||||
Class<?> gameProfileClass = profile.getClass();
|
||||
|
||||
Method getProperties = gameProfileClass.getMethod("getProperties");
|
||||
Object propertiesMap = getProperties.invoke(profile);
|
||||
|
||||
Class<?> propertiesMapClass = propertiesMap.getClass();
|
||||
Method removeAll = propertiesMapClass.getMethod("removeAll", Object.class);
|
||||
removeAll.invoke(propertiesMap, "textures");
|
||||
Method put = propertiesMapClass.getMethod("put", Object.class, Object.class);
|
||||
put.invoke(propertiesMap, "textures", property);
|
||||
}
|
||||
|
||||
Method m = instance.getClass().getDeclaredMethod("getTextures$original", profile.getClass(), boolean.class);
|
||||
m.setAccessible(true);
|
||||
return m.invoke(instance, profile, requireSecure);
|
||||
} catch (Throwable e) {
|
||||
log(ERROR, "Error fetching missing textures:");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@CallbackMethod
|
||||
public static Object getPackedTextures(Object instance, Object profile) {
|
||||
try {
|
||||
Object property = getMissingTexturesProperty(profile);
|
||||
if (property != null) {
|
||||
return property;
|
||||
}
|
||||
|
||||
Method m = instance.getClass().getDeclaredMethod("getPackedTextures$original", profile.getClass());
|
||||
m.setAccessible(true);
|
||||
return m.invoke(instance, profile);
|
||||
} catch (Throwable e) {
|
||||
log(ERROR, "Error fetching missing textures:");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, TransformContext ctx) {
|
||||
if ("com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService".equals(className)) {
|
||||
return Optional.of(new ClassVisitor(ASM9, writer) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String desc,
|
||||
String signature, String[] exceptions) {
|
||||
if ("getPackedTextures".equals(name) &&
|
||||
"(Lcom/mojang/authlib/GameProfile;)Lcom/mojang/authlib/properties/Property;".equals(desc)) {
|
||||
|
||||
ctx.markModified();
|
||||
|
||||
MethodVisitor originalMethodVisitor = super.visitMethod(access, name + "$original", desc, signature, exceptions);
|
||||
|
||||
MethodVisitor hookedMethodVisitor = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
if (hookedMethodVisitor != null) {
|
||||
hookedMethodVisitor.visitCode();
|
||||
|
||||
// Load `this`
|
||||
hookedMethodVisitor.visitVarInsn(ALOAD, 0);
|
||||
// Load `profile`
|
||||
hookedMethodVisitor.visitVarInsn(ALOAD, 1);
|
||||
hookedMethodVisitor.visitMethodInsn(INVOKESTATIC,
|
||||
"moe/yushi/authlibinjector/transform/support/FetchMissingTexturesByPlayerName",
|
||||
"getPackedTextures",
|
||||
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
|
||||
false);
|
||||
|
||||
hookedMethodVisitor.visitTypeInsn(CHECKCAST, "com/mojang/authlib/properties/Property");
|
||||
hookedMethodVisitor.visitInsn(ARETURN);
|
||||
hookedMethodVisitor.visitMaxs(2, 2);
|
||||
hookedMethodVisitor.visitEnd();
|
||||
}
|
||||
|
||||
return originalMethodVisitor;
|
||||
} else if ("getTextures".equals(name) &&
|
||||
"(Lcom/mojang/authlib/GameProfile;Z)Ljava/util/Map;".equals(desc)) {
|
||||
ctx.markModified();
|
||||
|
||||
MethodVisitor originalMethodVisitor = super.visitMethod(access, name + "$original", desc, signature, exceptions);
|
||||
MethodVisitor hookedMethodVisitor = super.visitMethod(access, name, desc, signature, exceptions);
|
||||
|
||||
if (hookedMethodVisitor != null) {
|
||||
hookedMethodVisitor.visitCode();
|
||||
|
||||
// Load `this`
|
||||
hookedMethodVisitor.visitVarInsn(ALOAD, 0);
|
||||
// Load `profile`
|
||||
hookedMethodVisitor.visitVarInsn(ALOAD, 1);
|
||||
// Load `requireSecure`
|
||||
hookedMethodVisitor.visitVarInsn(ILOAD, 2);
|
||||
hookedMethodVisitor.visitMethodInsn(INVOKESTATIC,
|
||||
"moe/yushi/authlibinjector/transform/support/FetchMissingTexturesByPlayerName",
|
||||
"getTextures",
|
||||
"(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object;",
|
||||
false);
|
||||
|
||||
hookedMethodVisitor.visitTypeInsn(CHECKCAST, "java/util/Map");
|
||||
hookedMethodVisitor.visitInsn(ARETURN);
|
||||
hookedMethodVisitor.visitMaxs(3, 3);
|
||||
hookedMethodVisitor.visitEnd();
|
||||
}
|
||||
|
||||
return originalMethodVisitor;
|
||||
}
|
||||
|
||||
return super.visitMethod(access, name, desc, signature, exceptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FetchMissingTexturesByPlayerName";
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user