From e38856aee730edfbd6e90e13c4f70a5c889bcfb8 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Sat, 25 Aug 2018 22:40:07 -0400 Subject: [PATCH] Add skin parser used to populate metadata based on the image data --- build.gradle | 4 ++ .../voxelmodpack/hdskins/HDSkinManager.java | 39 +++++++++++++++---- .../hdskins/INetworkPlayerInfo.java | 4 +- .../com/voxelmodpack/hdskins/ISkinParser.java | 23 +++++++++++ .../mixin/MixinNetworkPlayerInfo$1.java | 39 +++++++++++++++++++ ...rInfo.java => MixinNetworkPlayerInfo.java} | 31 ++++++++------- .../hdskins/util/ProfileTextureUtil.java | 29 ++++++++++++++ src/hdskins/resources/hdskins.mixin.json | 23 +++++------ .../com/minelittlepony/MineLittlePony.java | 1 + .../java/com/minelittlepony/PonyConfig.java | 2 + .../com/minelittlepony/PonySkinParser.java | 25 ++++++++++++ .../mixin/MixinNetworkPlayerInfo.java | 22 ----------- src/main/resources/minelp.mixin.json | 1 - 13 files changed, 186 insertions(+), 57 deletions(-) create mode 100644 src/hdskins/java/com/voxelmodpack/hdskins/ISkinParser.java create mode 100644 src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinNetworkPlayerInfo$1.java rename src/hdskins/java/com/voxelmodpack/hdskins/mixin/{MixinPlayerInfo.java => MixinNetworkPlayerInfo.java} (82%) create mode 100644 src/hdskins/java/com/voxelmodpack/hdskins/util/ProfileTextureUtil.java create mode 100644 src/main/java/com/minelittlepony/PonySkinParser.java delete mode 100644 src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java diff --git a/build.gradle b/build.gradle index 219fc354..9e32c8bd 100644 --- a/build.gradle +++ b/build.gradle @@ -74,6 +74,9 @@ dependencies { compile('org.apache.httpcomponents:httpmime:4.3.2') { transitive = false } + compile('org.spongepowered:mixin:0.7.11-SNAPSHOT') { + transitive = false + } } manifest { @@ -117,6 +120,7 @@ shadowJar { dependencies { exclude dependency('deobf.com.mumfrey:liteloader:') exclude dependency('deobf.org.ow2.asm:') + exclude dependency('org.spongepowered:mixin:') exclude 'META-INF/**' } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java index 040b103a..14b898dd 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java @@ -24,6 +24,7 @@ import com.voxelmodpack.hdskins.skins.LegacySkinServer; import com.voxelmodpack.hdskins.skins.ServerType; import com.voxelmodpack.hdskins.skins.SkinServer; import com.voxelmodpack.hdskins.skins.ValhallaSkinServer; +import com.voxelmodpack.hdskins.util.ProfileTextureUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.client.network.NetworkPlayerInfo; @@ -79,6 +80,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener { .build(CacheLoader.from(this::loadProfileData)); private List skinModifiers = Lists.newArrayList(); + private List skinParsers = Lists.newArrayList(); private SkinResourceManager resources = new SkinResourceManager(); // private ExecutorService executor = Executors.newCachedThreadPool(); @@ -210,13 +212,8 @@ public final class HDSkinManager implements IResourceManagerReloadListener { FileUtils.deleteQuietly(new File(LiteLoader.getAssetsDirectory(), "hd")); - NetHandlerPlayClient connection = Minecraft.getMinecraft().getConnection(); - - if (connection != null) { - connection.getPlayerInfoMap().forEach(this::clearNetworkSkin); - } - skins.invalidateAll(); + reloadSkins(); clearListeners = clearListeners.stream() .filter(this::onSkinCacheCleared) @@ -224,7 +221,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener { } private void clearNetworkSkin(NetworkPlayerInfo player) { - ((INetworkPlayerInfo) player).deleteTextures(); + ((INetworkPlayerInfo) player).reloadTextures(); } private boolean onSkinCacheCleared(ISkinCacheClearListener callback) { @@ -240,6 +237,10 @@ public final class HDSkinManager implements IResourceManagerReloadListener { skinModifiers.add(modifier); } + public void addSkinParser(ISkinParser parser) { + skinParsers.add(parser); + } + public ResourceLocation getConvertedSkin(ResourceLocation res) { ResourceLocation loc = resources.getConvertedResource(res); return loc == null ? res : loc; @@ -251,6 +252,30 @@ public final class HDSkinManager implements IResourceManagerReloadListener { } } + public void reloadSkins() { + + NetHandlerPlayClient playClient = Minecraft.getMinecraft().getConnection(); + if (playClient != null) { + playClient.getPlayerInfoMap().forEach(this::clearNetworkSkin); + } + } + + public void parseSkin(Type type, ResourceLocation resource, MinecraftProfileTexture texture) { + // grab the metadata object via reflection. Object is live. + Map metadata = ProfileTextureUtil.getMetadata(texture); + boolean wasNull = metadata == null; + if (wasNull) { + metadata = new HashMap<>(); + } + for (ISkinParser parser : skinParsers) { + parser.parse(type, resource, metadata); + } + if (wasNull && !metadata.isEmpty()) { + ProfileTextureUtil.setMetadata(texture, metadata); + } + } + + @Override public void onResourceManagerReload(IResourceManager resourceManager) { this.resources.onResourceManagerReload(resourceManager); diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/INetworkPlayerInfo.java b/src/hdskins/java/com/voxelmodpack/hdskins/INetworkPlayerInfo.java index d6eb8094..c653ce57 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/INetworkPlayerInfo.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/INetworkPlayerInfo.java @@ -11,5 +11,7 @@ public interface INetworkPlayerInfo { Optional getProfileTexture(MinecraftProfileTexture.Type type); - void deleteTextures(); + void reloadTextures(); + + void setSkinType(String type); } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/ISkinParser.java b/src/hdskins/java/com/voxelmodpack/hdskins/ISkinParser.java new file mode 100644 index 00000000..8998cc52 --- /dev/null +++ b/src/hdskins/java/com/voxelmodpack/hdskins/ISkinParser.java @@ -0,0 +1,23 @@ +package com.voxelmodpack.hdskins; + +import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; +import net.minecraft.util.ResourceLocation; + +import java.util.Map; + +/** + * SkinParser is used to parse metadata (e.g. trigger pixels) from a texture. + */ +@FunctionalInterface +public interface ISkinParser { + + /** + * Parses the texture for metadata. Any discovered data should be put into + * the metadata Map parameter. + * + * @param type The texture type + * @param resource The texture location + * @param metadata The metadata previously parsed + */ + void parse(Type type, ResourceLocation resource, Map metadata); +} diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinNetworkPlayerInfo$1.java b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinNetworkPlayerInfo$1.java new file mode 100644 index 00000000..0cc19b76 --- /dev/null +++ b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinNetworkPlayerInfo$1.java @@ -0,0 +1,39 @@ +package com.voxelmodpack.hdskins.mixin; + +import com.google.common.util.concurrent.Runnables; +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import com.voxelmodpack.hdskins.HDSkinManager; +import com.voxelmodpack.hdskins.INetworkPlayerInfo; +import net.minecraft.client.Minecraft; +import net.minecraft.client.network.NetworkPlayerInfo; +import net.minecraft.client.resources.SkinManager; +import net.minecraft.util.ResourceLocation; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.concurrent.CompletableFuture; + +@Mixin(targets = "net.minecraft.client.network.NetworkPlayerInfo$1") +public abstract class MixinNetworkPlayerInfo$1 implements SkinManager.SkinAvailableCallback { + + @Shadow(aliases = {"this$0", "field_177224_a", "a"}) @Final private NetworkPlayerInfo player; + + @Inject(method = "skinAvailable", at = @At(value = "HEAD")) + private void skinAvailable(MinecraftProfileTexture.Type typeIn, ResourceLocation location, MinecraftProfileTexture profileTexture, + CallbackInfo ci) { + CompletableFuture.runAsync(Runnables.doNothing()) + .thenAcceptAsync((v) -> { + // schedule parsing next tick, texture may not be uploaded at this point + HDSkinManager.INSTANCE.parseSkin(typeIn, location, profileTexture); + + // re-set the skin-type because vanilla has already set it + String model = profileTexture.getMetadata("model"); + ((INetworkPlayerInfo) player).setSkinType(model != null ? model : "default"); + + }, Minecraft.getMinecraft()::addScheduledTask); + } +} diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinNetworkPlayerInfo.java similarity index 82% rename from src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java rename to src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinNetworkPlayerInfo.java index 9c80ff6a..119a111a 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinNetworkPlayerInfo.java @@ -1,5 +1,6 @@ package com.voxelmodpack.hdskins.mixin; +import com.google.common.util.concurrent.Runnables; import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; @@ -7,7 +8,6 @@ import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.INetworkPlayerInfo; import net.minecraft.client.Minecraft; import net.minecraft.client.network.NetworkPlayerInfo; -import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.util.ResourceLocation; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -20,16 +20,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.stream.Stream; +import java.util.concurrent.CompletableFuture; @Mixin(NetworkPlayerInfo.class) -public abstract class MixinPlayerInfo implements INetworkPlayerInfo { +public abstract class MixinNetworkPlayerInfo implements INetworkPlayerInfo { private Map customTextures = new HashMap<>(); private Map customProfiles = new HashMap<>(); @Shadow @Final private GameProfile gameProfile; - @Shadow Map playerTextures; @Shadow private boolean playerTexturesLoaded; @Shadow private String skinType; @@ -52,7 +51,6 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo { @Redirect(method = "getSkinType()Ljava/lang/String;", at = @At(value = "FIELD", target = "Lnet/minecraft/client/network/NetworkPlayerInfo;skinType:Ljava/lang/String;")) - private String getTextureModel(NetworkPlayerInfo self) { return getProfileTexture(Type.SKIN).map(profile -> { String model = profile.getMetadata("model"); @@ -71,6 +69,11 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo { HDSkinManager.INSTANCE.loadProfileTextures(this.gameProfile) .thenAcceptAsync(m -> m.forEach((type, profile) -> { HDSkinManager.INSTANCE.loadTexture(type, profile, (typeIn, location, profileTexture) -> { + CompletableFuture.runAsync(Runnables.doNothing()) + // schedule parsing next tick + .thenAcceptAsync((v) -> { + HDSkinManager.INSTANCE.parseSkin(typeIn, location, profileTexture); + }, Minecraft.getMinecraft()::addScheduledTask); customTextures.put(type, location); customProfiles.put(type, profileTexture); }); @@ -88,16 +91,14 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo { } @Override - public void deleteTextures() { - TextureManager tm = Minecraft.getMinecraft().getTextureManager(); - Stream.concat(this.customTextures.values().stream(), this.playerTextures.values().stream()) - .forEach(tm::deleteTexture); - this.customTextures.clear(); - this.customProfiles.clear(); - this.playerTextures.clear(); + public void reloadTextures() { + synchronized (this) { + this.playerTexturesLoaded = false; + } + } - this.skinType = null; - - this.playerTexturesLoaded = false; + @Override + public void setSkinType(String skinType) { + this.skinType = skinType; } } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/util/ProfileTextureUtil.java b/src/hdskins/java/com/voxelmodpack/hdskins/util/ProfileTextureUtil.java new file mode 100644 index 00000000..42293459 --- /dev/null +++ b/src/hdskins/java/com/voxelmodpack/hdskins/util/ProfileTextureUtil.java @@ -0,0 +1,29 @@ +package com.voxelmodpack.hdskins.util; + +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import org.apache.commons.lang3.reflect.FieldUtils; + +import java.lang.reflect.Field; +import java.util.Map; + +public class ProfileTextureUtil { + + private static Field metadata = FieldUtils.getDeclaredField(MinecraftProfileTexture.class, "metadata", true); + + @SuppressWarnings("unchecked") + public static Map getMetadata(MinecraftProfileTexture texture) { + try { + return (Map) FieldUtils.readField(metadata, texture); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to read metadata field", e); + } + } + + public static void setMetadata(MinecraftProfileTexture texture, Map meta) { + try { + FieldUtils.writeField(metadata, texture, meta); + } catch (IllegalAccessException e) { + throw new RuntimeException("Unable to write metadata field", e); + } + } +} diff --git a/src/hdskins/resources/hdskins.mixin.json b/src/hdskins/resources/hdskins.mixin.json index 130a9936..3b14e566 100644 --- a/src/hdskins/resources/hdskins.mixin.json +++ b/src/hdskins/resources/hdskins.mixin.json @@ -1,13 +1,14 @@ { - "required": true, - "minVersion": "0.7", - "package": "com.voxelmodpack.hdskins.mixin", - "refmap": "hdskins.mixin.refmap.json", - "mixins": [ - "MixinMinecraft", - "MixinGuiMainMenu", - "MixinImageBufferDownload", - "MixinPlayerInfo", - "MixinSkullRenderer" - ] + "required": true, + "minVersion": "0.7", + "package": "com.voxelmodpack.hdskins.mixin", + "refmap": "hdskins.mixin.refmap.json", + "mixins": [ + "MixinMinecraft", + "MixinGuiMainMenu", + "MixinImageBufferDownload", + "MixinNetworkPlayerInfo", + "MixinNetworkPlayerInfo$1", + "MixinSkullRenderer" + ] } diff --git a/src/main/java/com/minelittlepony/MineLittlePony.java b/src/main/java/com/minelittlepony/MineLittlePony.java index 3cd4e78a..76a04d12 100644 --- a/src/main/java/com/minelittlepony/MineLittlePony.java +++ b/src/main/java/com/minelittlepony/MineLittlePony.java @@ -74,6 +74,7 @@ public class MineLittlePony { // manager.setSkinUrl(SKIN_SERVER_URL); // manager.setGatewayURL(GATEWAY_URL); manager.addSkinModifier(new PonySkinModifier()); + manager.addSkinParser(new PonySkinParser()); // logger.info("Set MineLP skin server URL."); manager.addClearListener(ponyManager); diff --git a/src/main/java/com/minelittlepony/PonyConfig.java b/src/main/java/com/minelittlepony/PonyConfig.java index 1a918629..4e3c4d7a 100644 --- a/src/main/java/com/minelittlepony/PonyConfig.java +++ b/src/main/java/com/minelittlepony/PonyConfig.java @@ -6,6 +6,7 @@ import com.minelittlepony.settings.SensibleConfig; import com.mumfrey.liteloader.modconfig.ConfigStrategy; import com.mumfrey.liteloader.modconfig.Exposable; import com.mumfrey.liteloader.modconfig.ExposableOptions; +import com.voxelmodpack.hdskins.HDSkinManager; /** * Storage container for MineLP client settings. @@ -64,6 +65,7 @@ public class PonyConfig extends SensibleConfig implements Exposable { */ public void setPonyLevel(PonyLevel ponylevel) { this.ponylevel = ponylevel; + HDSkinManager.INSTANCE.reloadSkins(); } public float getGlobalScaleFactor() { diff --git a/src/main/java/com/minelittlepony/PonySkinParser.java b/src/main/java/com/minelittlepony/PonySkinParser.java new file mode 100644 index 00000000..85116387 --- /dev/null +++ b/src/main/java/com/minelittlepony/PonySkinParser.java @@ -0,0 +1,25 @@ +package com.minelittlepony; + +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import com.voxelmodpack.hdskins.ISkinParser; +import net.minecraft.util.ResourceLocation; + +import java.util.Map; + +public class PonySkinParser implements ISkinParser { + + @Override + public void parse(MinecraftProfileTexture.Type type, ResourceLocation resource, Map metadata) { + if (type == MinecraftProfileTexture.Type.SKIN) { + boolean slim = "slim".equals(metadata.get("model")); + // TODO use proper model metadata system + + metadata.put("model", MineLittlePony.getInstance().getManager() + .getPony(resource, slim) + .getRace(false) + .getModel() + .getId(slim)); + + } + } +} diff --git a/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java b/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java deleted file mode 100644 index a8778bdf..00000000 --- a/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.minelittlepony.mixin; - -import com.minelittlepony.MineLittlePony; -import com.voxelmodpack.hdskins.INetworkPlayerInfo; -import net.minecraft.client.network.NetworkPlayerInfo; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; - -@Mixin(NetworkPlayerInfo.class) -public abstract class MixinNetworkPlayerInfo implements INetworkPlayerInfo { - - @Inject(method = "getSkinType()Ljava/lang/String;", at = @At("RETURN"), cancellable = true) - private void getSkinType(CallbackInfoReturnable info) { - info.setReturnValue(MineLittlePony.getInstance().getManager() - .getPony((NetworkPlayerInfo) (Object) this) - .getRace(false) - .getModel() - .getId("slim".equals(info.getReturnValue()))); - } -} diff --git a/src/main/resources/minelp.mixin.json b/src/main/resources/minelp.mixin.json index a7c3f82d..b9626380 100644 --- a/src/main/resources/minelp.mixin.json +++ b/src/main/resources/minelp.mixin.json @@ -6,7 +6,6 @@ "compatibilityLevel": "JAVA_8", "mixins": [ "MixinThreadDownloadImageData", - "MixinNetworkPlayerInfo", "MixinRenderItem", "MixinItemRenderer", "MixinRenderManager",