Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Bootstrap/Core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ dependencies {
implementation 'org.ow2.asm:asm:9.9.1'
implementation 'org.ow2.asm:asm-commons:9.9.1'
implementation 'org.ow2.asm:asm-tree:9.9.1'
testImplementation 'junit:junit:4.13.2'
testRuntimeOnly 'com.google.code.gson:gson:2.14.0'
testRuntimeOnly 'org.apache.logging.log4j:log4j-api:2.26.0'
testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.26.0'
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ abstract class PatchSupport extends TargetedClassTransformer implements Opcodes
protected static final String MINECRAFT_PROFILE_TEXTURE = "com/mojang/authlib/minecraft/MinecraftProfileTexture";
protected static final String MINECRAFT_PROFILE_TEXTURE_TYPE = "com/mojang/authlib/minecraft/MinecraftProfileTexture$Type";
protected static final String MINECRAFT_SESSION_SERVICE = "com/mojang/authlib/minecraft/MinecraftSessionService";
protected static final String SESSION_SERVICE = "com/mojang/authlib/minecraft/SessionService";
protected static final String MINECRAFT_PROFILE_TEXTURES = "com/mojang/authlib/minecraft/MinecraftProfileTextures";
protected static final String GAME_PROFILE = "com/mojang/authlib/GameProfile";
protected static final String PROPERTY = "com/mojang/authlib/properties/Property";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ private boolean replaceGetTexturesWithFakeUserProfile(MethodNode methodNode) {
continue;
}

methodNode.instructions.set(instruction, new MethodInsnNode(INVOKESTATIC, FAKE_SKIN_MANAGER, "getUserProfile", "(" + objectDesc(MINECRAFT_SESSION_SERVICE) + objectDesc(GAME_PROFILE) + "Z)" + objectDesc(MAP), false));
methodNode.instructions.set(instruction, new MethodInsnNode(INVOKESTATIC, FAKE_SKIN_MANAGER, "getUserProfile", "(" + objectDesc(OBJECT) + objectDesc(GAME_PROFILE) + "Z)" + objectDesc(MAP), false));
modified = true;
}
return modified;
Expand Down Expand Up @@ -489,15 +489,15 @@ private boolean replaceGetTexturesWithFakeSkinCache(MethodNode methodNode) {
continue;
}

methodNode.instructions.set(instruction, new MethodInsnNode(INVOKESTATIC, FAKE_SKIN_MANAGER, "loadSkinFromCache", "(" + objectDesc(MINECRAFT_SESSION_SERVICE) + objectDesc(GAME_PROFILE) + "Z)" + objectDesc(MAP), false));
methodNode.instructions.set(instruction, new MethodInsnNode(INVOKESTATIC, FAKE_SKIN_MANAGER, "loadSkinFromCache", "(" + objectDesc(OBJECT) + objectDesc(GAME_PROFILE) + "Z)" + objectDesc(MAP), false));
modified = true;
}
return modified;
}

private boolean replaceUnpackTexturesWithFakeSkinCache(ClassTransformationContext context, MethodNode methodNode) {
boolean modified = false;
String desc = context.remapMethodDescriptor("(" + objectDesc(MINECRAFT_SESSION_SERVICE) + objectDesc(PROPERTY) + objectDesc(SKIN_MANAGER_CACHE_KEY) + ")" + objectDesc(OBJECT));
String desc = context.remapMethodDescriptor("(" + objectDesc(OBJECT) + objectDesc(PROPERTY) + objectDesc(SKIN_MANAGER_CACHE_KEY) + ")" + objectDesc(OBJECT));
String returnType = context.remapClassName(MINECRAFT_PROFILE_TEXTURES);

for (AbstractInsnNode instruction : methodNode.instructions.toArray()) {
Expand All @@ -506,7 +506,7 @@ private boolean replaceUnpackTexturesWithFakeSkinCache(ClassTransformationContex
}

MethodInsnNode methodInsnNode = (MethodInsnNode) instruction;
if (!MINECRAFT_SESSION_SERVICE.equals(methodInsnNode.owner) || !"unpackTextures".equals(methodInsnNode.name) || !("(" + objectDesc(PROPERTY) + ")" + objectDesc(MINECRAFT_PROFILE_TEXTURES)).equals(methodInsnNode.desc)) {
if ((!MINECRAFT_SESSION_SERVICE.equals(methodInsnNode.owner) && !SESSION_SERVICE.equals(methodInsnNode.owner)) || !"unpackTextures".equals(methodInsnNode.name) || !("(" + objectDesc(PROPERTY) + ")" + objectDesc(MINECRAFT_PROFILE_TEXTURES)).equals(methodInsnNode.desc)) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package customskinloader.bootstrap.transformer.patch;

import customskinloader.bootstrap.mapping.Mappings;
import customskinloader.bootstrap.transformer.ClassTransformationContext;
import org.junit.Test;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public final class SkinManagerPatchTest implements Opcodes {
private static final String CACHE_KEY = "net/minecraft/client/resources/SkinManager$CacheKey";
private static final String SERVICES = "net/minecraft/server/Services";
private static final String PROFILE_TEXTURES = "com/mojang/authlib/minecraft/MinecraftProfileTextures";
private static final String SESSION_SERVICE = "com/mojang/authlib/minecraft/SessionService";
private static final String PROPERTY = "com/mojang/authlib/properties/Property";

@Test
public void transformsMinecraft26_3SessionServiceInterfaceInvocation() {
ClassNode classNode = new ClassNode();
classNode.name = "net/minecraft/client/resources/SkinManager$1";
classNode.methods.add(createLoadMethod());
classNode.methods.add(createMinecraft26_3Lambda());

ClassTransformationContext context = ClassTransformationContext.create(classNode.name, classNode, Mappings.EMPTY);
assertTrue(new SkinManagerPatch().transform(context));

MethodNode lambda = classNode.methods.get(1);
assertFalse(containsInvocation(lambda, SESSION_SERVICE, "unpackTextures"));
assertTrue(containsInvocation(lambda, "customskinloader/fake/FakeSkinManager", "loadSkinFromCache"));
}

private static MethodNode createLoadMethod() {
MethodNode method = new MethodNode(ACC_PUBLIC, "load", "(L" + CACHE_KEY + ";)Ljava/util/concurrent/CompletableFuture;", null, null);
method.instructions.add(new MethodInsnNode(INVOKESTATIC, "java/util/concurrent/CompletableFuture", "supplyAsync", "(Ljava/util/function/Supplier;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", false));
method.instructions.add(new MethodInsnNode(INVOKEVIRTUAL, "java/util/concurrent/CompletableFuture", "thenComposeAsync", "(Ljava/util/function/Function;Ljava/util/concurrent/Executor;)Ljava/util/concurrent/CompletableFuture;", false));
method.instructions.add(new InsnNode(ARETURN));
return method;
}

private static MethodNode createMinecraft26_3Lambda() {
MethodNode method = new MethodNode(ACC_PRIVATE | ACC_STATIC, "lambda$load$0", "(L" + CACHE_KEY + ";L" + SERVICES + ";)L" + PROFILE_TEXTURES + ";", null, null);
method.instructions.add(new MethodInsnNode(INVOKEINTERFACE, SESSION_SERVICE, "unpackTextures", "(L" + PROPERTY + ";)L" + PROFILE_TEXTURES + ";", true));
method.instructions.add(new InsnNode(ARETURN));
return method;
}

private static boolean containsInvocation(MethodNode method, String owner, String name) {
for (AbstractInsnNode instruction : method.instructions.toArray()) {
if (instruction instanceof MethodInsnNode) {
MethodInsnNode invocation = (MethodInsnNode) instruction;
if (owner.equals(invocation.owner) && name.equals(invocation.name)) {
return true;
}
}
}
return false;
}
}
4 changes: 4 additions & 0 deletions Bootstrap/Core/src/test/resources/version.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"protocol_version": 777,
"world_version": 0
}
23 changes: 13 additions & 10 deletions Common/src/main/java/customskinloader/fake/FakeSkinManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import com.mojang.authlib.SignatureState;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTextures;
import com.mojang.authlib.minecraft.MinecraftSessionService;
import com.mojang.authlib.properties.Property;
import com.mojang.blaze3d.platform.NativeImage;
import customskinloader.CustomSkinLoader;
Expand All @@ -27,18 +26,18 @@
public class FakeSkinManager {
/**
* 1.20.1-
* Invoked from {@link SkinManager(TextureManager, File, MinecraftSessionService)}
* Invoked from SkinManager(TextureManager, File, MinecraftSessionService)
*/
public static void setSkinCacheDir(File skinCacheDirectory) {
HttpTextureUtil.defaultCacheDir = skinCacheDirectory;
}

/**
* 23w31a ~ 23w45a
* Invoked from {@link SkinManager(TextureManager, Path, MinecraftSessionService, Executor)}
* Invoked from SkinManager(TextureManager, Path, MinecraftSessionService, Executor)
*
* 23w46a ~ 25w33a
* Invoked from {@link SkinManager(Path, MinecraftSessionService, Executor)}
* Invoked from SkinManager(Path, MinecraftSessionService, Executor)
*
* 25w34a+
* Invoked from {@link SkinManager(Path, Services, Executor)}
Expand Down Expand Up @@ -67,7 +66,7 @@ public static Executor loadProfileTextures(Executor executor) {
* 1.20.1-
* Invoked from {@link SkinManager#lambda$registerSkins$4(GameProfile, boolean, SkinManager$SkinTextureCallback)}
*/
public static Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getUserProfile(MinecraftSessionService sessionService, GameProfile profile, boolean requireSecure) {
public static Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getUserProfile(Object sessionService, GameProfile profile, boolean requireSecure) {
return ModelManager0.fromUserProfile(CustomSkinLoader.loadProfile(profile));
}

Expand All @@ -81,24 +80,28 @@ public static Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> loadSki

/**
* 23w31a ~ 23w41a
* Invoked from {@link SkinManager$1#lambda$load$0(MinecraftSessionService, GameProfile)}
* Invoked from SkinManager$1.lambda$load$0(MinecraftSessionService, GameProfile)
*/
public static Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> loadSkinFromCache(MinecraftSessionService sessionService, GameProfile profile, boolean requireSecure) {
public static Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> loadSkinFromCache(Object sessionService, GameProfile profile, boolean requireSecure) {
return getUserProfile(sessionService, profile, requireSecure);
}

/**
* 23w42a ~ 25w33a
* Invoked from {@link SkinManager$1#lambda$load$0(SkinManager$CacheKey, MinecraftSessionService)}
* Invoked from SkinManager$1.lambda$load$0(SkinManager$CacheKey, MinecraftSessionService)
*
* 25w34a+
* Invoked from {@link SkinManager$1#lambda$load$0(SkinManager$CacheKey, Services)}
*/
public static Object loadSkinFromCache(MinecraftSessionService sessionService, Property property, SkinManager$CacheKey cacheKey) {
public static Object loadSkinFromCache(Object sessionService, Property property, SkinManager$CacheKey cacheKey) {
if (cacheKey instanceof FakeCacheKey) {
return FakeCacheKey.createMinecraftProfileTextures(loadSkinFromCache(sessionService, ((FakeCacheKey) cacheKey).profile(), false));
}
return sessionService.unpackTextures(property);
try {
return sessionService.getClass().getMethod("unpackTextures", Property.class).invoke(sessionService, property);
} catch (ReflectiveOperationException exception) {
throw new RuntimeException(exception);
}
}

public static class BaseBuffer implements FakeHttpTextureProcessor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import customskinloader.CustomSkinLoader;
import customskinloader.config.SkinSiteProfile;
Expand Down Expand Up @@ -101,10 +100,14 @@ public static Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getText
if (result == null) {
return Maps.newHashMap();
}
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures = TextureUtil.AuthlibField.MINECRAFT_TEXTURES_PAYLOAD_TEXTURES.get(result);
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures = result.textures;
if (textures == null) {
return Maps.newHashMap();
}
return textures;
}

private static final class MinecraftTexturesPayload {
private Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures;
}
}
30 changes: 17 additions & 13 deletions Common/src/main/java/customskinloader/loader/MojangAPILoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.authlib.yggdrasil.response.MinecraftProfilePropertiesResponse;
import customskinloader.CustomSkinLoader;
import customskinloader.config.SkinSiteProfile;
import customskinloader.plugin.ICustomSkinLoaderPlugin;
Expand Down Expand Up @@ -81,12 +80,12 @@ public String getSessionRoot() {
@Override
public UserProfile loadProfile(SkinSiteProfile ssp, GameProfile gameProfile) {
String username = TextureUtil.AuthlibField.GAME_PROFILE_NAME.get(gameProfile);
MinecraftProfilePropertiesResponse newProfile = loadGameProfileCached(ssp.apiRoot, username);
MojangProfile newProfile = loadGameProfileCached(ssp.apiRoot, username);
if (newProfile == null) {
CustomSkinLoader.logger.info("Profile not found.(" + username + "'s profile not found.)");
return null;
}
PropertyMap propertyMap = TextureUtil.AuthlibField.MINECRAFT_PROFILE_PROPERTIES_RESPONSE_PROPERTIES.get(fillProfile(ssp.sessionRoot, newProfile));
PropertyMap propertyMap = fillProfile(ssp.sessionRoot, newProfile).properties;
if (propertyMap != null) {
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = GameProfileLoader.getTextures(propertyMap);
if (!map.isEmpty()) {
Expand All @@ -97,26 +96,26 @@ public UserProfile loadProfile(SkinSiteProfile ssp, GameProfile gameProfile) {
return null;
}

private static final Map<String, MinecraftProfilePropertiesResponse> gameProfileCache = new ConcurrentHashMap<>();
private static final Map<String, MojangProfile> gameProfileCache = new ConcurrentHashMap<>();

public static MinecraftProfilePropertiesResponse loadGameProfileCached(String apiRoot, String username) {
public static MojangProfile loadGameProfileCached(String apiRoot, String username) {
return gameProfileCache.computeIfAbsent(apiRoot + " " + username, ignored -> loadGameProfile(apiRoot, username));
}

//Username -> UUID
public static MinecraftProfilePropertiesResponse loadGameProfile(String apiRoot, String username) {
public static MojangProfile loadGameProfile(String apiRoot, String username) {
//Doc (https://minecraft.wiki/w/Mojang_API#Query_player_UUIDs_in_batch)
HttpRequestUtil.HttpResponce responce = HttpRequestUtil.makeHttpRequest(new HttpRequestUtil.HttpRequest(apiRoot + "profiles/minecraft").setCacheTime(600).setPayload(GameProfileLoader.GSON.toJson(Collections.singletonList(username))));
if (StringUtils.isEmpty(responce.content)) {
return null;
}

MinecraftProfilePropertiesResponse[] profiles = GameProfileLoader.GSON.fromJson(responce.content, MinecraftProfilePropertiesResponse[].class);
MojangProfile[] profiles = GameProfileLoader.GSON.fromJson(responce.content, MojangProfile[].class);
if (profiles.length == 0) {
return null;
}

UUID id = TextureUtil.AuthlibField.MINECRAFT_PROFILE_PROPERTIES_RESPONSE_ID.get(profiles[0]);
UUID id = profiles[0].id;
if (id == null) {
return null;
}
Expand All @@ -131,22 +130,27 @@ public static MinecraftProfilePropertiesResponse loadGameProfile(String apiRoot,
* @return UUID in Mojang API style string. Returns {@code null} if username not found in Mojang API.
*/
public static String getMojangUuidByUsername(String username, boolean standard) {
MinecraftProfilePropertiesResponse profile = loadGameProfileCached(getMojangApiRoot(), username);
MojangProfile profile = loadGameProfileCached(getMojangApiRoot(), username);
if (profile == null) {
return null;
}
UUID id = TextureUtil.AuthlibField.MINECRAFT_PROFILE_PROPERTIES_RESPONSE_ID.get(profile);
UUID id = profile.id;
return standard ? id.toString() : TextureUtil.fromUUID(id);
}

//UUID -> Profile
public static MinecraftProfilePropertiesResponse fillProfile(String sessionRoot, MinecraftProfilePropertiesResponse profile) {
public static MojangProfile fillProfile(String sessionRoot, MojangProfile profile) {
//Doc (https://minecraft.wiki/w/Mojang_API#Query_player's_skin_and_cape)
HttpRequestUtil.HttpResponce responce = HttpRequestUtil.makeHttpRequest(new HttpRequestUtil.HttpRequest(sessionRoot + "session/minecraft/profile/" + TextureUtil.fromUUID(TextureUtil.AuthlibField.MINECRAFT_PROFILE_PROPERTIES_RESPONSE_ID.get(profile))).setCacheTime(90));
HttpRequestUtil.HttpResponce responce = HttpRequestUtil.makeHttpRequest(new HttpRequestUtil.HttpRequest(sessionRoot + "session/minecraft/profile/" + TextureUtil.fromUUID(profile.id)).setCacheTime(90));
if (StringUtils.isEmpty(responce.content)) {
return profile;
}
return GameProfileLoader.GSON.fromJson(responce.content, MinecraftProfilePropertiesResponse.class);
return GameProfileLoader.GSON.fromJson(responce.content, MojangProfile.class);
}

public static final class MojangProfile {
private UUID id;
private PropertyMap properties;
}

@Override
Expand Down
7 changes: 1 addition & 6 deletions Common/src/main/java/customskinloader/utils/TextureUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.yggdrasil.response.MinecraftProfilePropertiesResponse;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import customskinloader.CustomSkinLoader;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
Expand Down Expand Up @@ -43,10 +41,7 @@ public enum AuthlibField {
GAME_PROFILE_ID(GameProfile.class, "id"),
GAME_PROFILE_NAME(GameProfile.class, "name"),
GAME_PROFILE_PROPERTIES(GameProfile.class, "properties"),
PROPERTY_VALUE(Property.class, "value"),
MINECRAFT_PROFILE_PROPERTIES_RESPONSE_ID(MinecraftProfilePropertiesResponse.class, "id"),
MINECRAFT_PROFILE_PROPERTIES_RESPONSE_PROPERTIES(MinecraftProfilePropertiesResponse.class, "properties"),
MINECRAFT_TEXTURES_PAYLOAD_TEXTURES(MinecraftTexturesPayload.class, "textures");
PROPERTY_VALUE(Property.class, "value");

private final Field field;

Expand Down
2 changes: 1 addition & 1 deletion build.info.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4",
"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6",
"1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5", "1.21.6", "1.21.7", "1.21.8", "1.21.9", "1.21.10", "1.21.11",
"26.1", "26.1.1", "26.1.2", "26.2"
"26.1", "26.1.1", "26.1.2", "26.2", "26.3"
],
"java": [
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 25
Expand Down
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
build_type=beta

minecraft_version=26.2
fabric_v1_version=0.19.3
minecraft_version=26.3
fabric_v1_version=0.19.5
forge_v1_version=14.23.5.2864
forge_v2_version=36.2.42
neoforge_v1_version=20.6.13-beta
Expand Down