Add skin parser used to populate metadata based on the image data

This commit is contained in:
Matthew Messinger 2018-08-25 22:40:07 -04:00
parent 1442dc51a3
commit e38856aee7
13 changed files with 186 additions and 57 deletions

View file

@ -74,6 +74,9 @@ dependencies {
compile('org.apache.httpcomponents:httpmime:4.3.2') { compile('org.apache.httpcomponents:httpmime:4.3.2') {
transitive = false transitive = false
} }
compile('org.spongepowered:mixin:0.7.11-SNAPSHOT') {
transitive = false
}
} }
manifest { manifest {
@ -117,6 +120,7 @@ shadowJar {
dependencies { dependencies {
exclude dependency('deobf.com.mumfrey:liteloader:') exclude dependency('deobf.com.mumfrey:liteloader:')
exclude dependency('deobf.org.ow2.asm:') exclude dependency('deobf.org.ow2.asm:')
exclude dependency('org.spongepowered:mixin:')
exclude 'META-INF/**' exclude 'META-INF/**'
} }

View file

@ -24,6 +24,7 @@ import com.voxelmodpack.hdskins.skins.LegacySkinServer;
import com.voxelmodpack.hdskins.skins.ServerType; import com.voxelmodpack.hdskins.skins.ServerType;
import com.voxelmodpack.hdskins.skins.SkinServer; import com.voxelmodpack.hdskins.skins.SkinServer;
import com.voxelmodpack.hdskins.skins.ValhallaSkinServer; import com.voxelmodpack.hdskins.skins.ValhallaSkinServer;
import com.voxelmodpack.hdskins.util.ProfileTextureUtil;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetHandlerPlayClient; import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.client.network.NetworkPlayerInfo; import net.minecraft.client.network.NetworkPlayerInfo;
@ -79,6 +80,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
.build(CacheLoader.from(this::loadProfileData)); .build(CacheLoader.from(this::loadProfileData));
private List<ISkinModifier> skinModifiers = Lists.newArrayList(); private List<ISkinModifier> skinModifiers = Lists.newArrayList();
private List<ISkinParser> skinParsers = Lists.newArrayList();
private SkinResourceManager resources = new SkinResourceManager(); private SkinResourceManager resources = new SkinResourceManager();
// private ExecutorService executor = Executors.newCachedThreadPool(); // private ExecutorService executor = Executors.newCachedThreadPool();
@ -210,13 +212,8 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
FileUtils.deleteQuietly(new File(LiteLoader.getAssetsDirectory(), "hd")); FileUtils.deleteQuietly(new File(LiteLoader.getAssetsDirectory(), "hd"));
NetHandlerPlayClient connection = Minecraft.getMinecraft().getConnection();
if (connection != null) {
connection.getPlayerInfoMap().forEach(this::clearNetworkSkin);
}
skins.invalidateAll(); skins.invalidateAll();
reloadSkins();
clearListeners = clearListeners.stream() clearListeners = clearListeners.stream()
.filter(this::onSkinCacheCleared) .filter(this::onSkinCacheCleared)
@ -224,7 +221,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
} }
private void clearNetworkSkin(NetworkPlayerInfo player) { private void clearNetworkSkin(NetworkPlayerInfo player) {
((INetworkPlayerInfo) player).deleteTextures(); ((INetworkPlayerInfo) player).reloadTextures();
} }
private boolean onSkinCacheCleared(ISkinCacheClearListener callback) { private boolean onSkinCacheCleared(ISkinCacheClearListener callback) {
@ -240,6 +237,10 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
skinModifiers.add(modifier); skinModifiers.add(modifier);
} }
public void addSkinParser(ISkinParser parser) {
skinParsers.add(parser);
}
public ResourceLocation getConvertedSkin(ResourceLocation res) { public ResourceLocation getConvertedSkin(ResourceLocation res) {
ResourceLocation loc = resources.getConvertedResource(res); ResourceLocation loc = resources.getConvertedResource(res);
return loc == null ? res : loc; 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<String, String> 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 @Override
public void onResourceManagerReload(IResourceManager resourceManager) { public void onResourceManagerReload(IResourceManager resourceManager) {
this.resources.onResourceManagerReload(resourceManager); this.resources.onResourceManagerReload(resourceManager);

View file

@ -11,5 +11,7 @@ public interface INetworkPlayerInfo {
Optional<MinecraftProfileTexture> getProfileTexture(MinecraftProfileTexture.Type type); Optional<MinecraftProfileTexture> getProfileTexture(MinecraftProfileTexture.Type type);
void deleteTextures(); void reloadTextures();
void setSkinType(String type);
} }

View file

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

View file

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

View file

@ -1,5 +1,6 @@
package com.voxelmodpack.hdskins.mixin; package com.voxelmodpack.hdskins.mixin;
import com.google.common.util.concurrent.Runnables;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
@ -7,7 +8,6 @@ import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.INetworkPlayerInfo; import com.voxelmodpack.hdskins.INetworkPlayerInfo;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetworkPlayerInfo; import net.minecraft.client.network.NetworkPlayerInfo;
import net.minecraft.client.renderer.texture.TextureManager;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; 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.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream; import java.util.concurrent.CompletableFuture;
@Mixin(NetworkPlayerInfo.class) @Mixin(NetworkPlayerInfo.class)
public abstract class MixinPlayerInfo implements INetworkPlayerInfo { public abstract class MixinNetworkPlayerInfo implements INetworkPlayerInfo {
private Map<Type, ResourceLocation> customTextures = new HashMap<>(); private Map<Type, ResourceLocation> customTextures = new HashMap<>();
private Map<Type, MinecraftProfileTexture> customProfiles = new HashMap<>(); private Map<Type, MinecraftProfileTexture> customProfiles = new HashMap<>();
@Shadow @Final private GameProfile gameProfile; @Shadow @Final private GameProfile gameProfile;
@Shadow Map<Type, ResourceLocation> playerTextures;
@Shadow private boolean playerTexturesLoaded; @Shadow private boolean playerTexturesLoaded;
@Shadow private String skinType; @Shadow private String skinType;
@ -52,7 +51,6 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo {
@Redirect(method = "getSkinType()Ljava/lang/String;", @Redirect(method = "getSkinType()Ljava/lang/String;",
at = @At(value = "FIELD", target = "Lnet/minecraft/client/network/NetworkPlayerInfo;skinType:Ljava/lang/String;")) at = @At(value = "FIELD", target = "Lnet/minecraft/client/network/NetworkPlayerInfo;skinType:Ljava/lang/String;"))
private String getTextureModel(NetworkPlayerInfo self) { private String getTextureModel(NetworkPlayerInfo self) {
return getProfileTexture(Type.SKIN).map(profile -> { return getProfileTexture(Type.SKIN).map(profile -> {
String model = profile.getMetadata("model"); String model = profile.getMetadata("model");
@ -71,6 +69,11 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo {
HDSkinManager.INSTANCE.loadProfileTextures(this.gameProfile) HDSkinManager.INSTANCE.loadProfileTextures(this.gameProfile)
.thenAcceptAsync(m -> m.forEach((type, profile) -> { .thenAcceptAsync(m -> m.forEach((type, profile) -> {
HDSkinManager.INSTANCE.loadTexture(type, profile, (typeIn, location, profileTexture) -> { 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); customTextures.put(type, location);
customProfiles.put(type, profileTexture); customProfiles.put(type, profileTexture);
}); });
@ -88,16 +91,14 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo {
} }
@Override @Override
public void deleteTextures() { public void reloadTextures() {
TextureManager tm = Minecraft.getMinecraft().getTextureManager(); synchronized (this) {
Stream.concat(this.customTextures.values().stream(), this.playerTextures.values().stream())
.forEach(tm::deleteTexture);
this.customTextures.clear();
this.customProfiles.clear();
this.playerTextures.clear();
this.skinType = null;
this.playerTexturesLoaded = false; this.playerTexturesLoaded = false;
} }
}
@Override
public void setSkinType(String skinType) {
this.skinType = skinType;
}
} }

View file

@ -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<String, String> getMetadata(MinecraftProfileTexture texture) {
try {
return (Map<String, String>) FieldUtils.readField(metadata, texture);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to read metadata field", e);
}
}
public static void setMetadata(MinecraftProfileTexture texture, Map<String, String> meta) {
try {
FieldUtils.writeField(metadata, texture, meta);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to write metadata field", e);
}
}
}

View file

@ -7,7 +7,8 @@
"MixinMinecraft", "MixinMinecraft",
"MixinGuiMainMenu", "MixinGuiMainMenu",
"MixinImageBufferDownload", "MixinImageBufferDownload",
"MixinPlayerInfo", "MixinNetworkPlayerInfo",
"MixinNetworkPlayerInfo$1",
"MixinSkullRenderer" "MixinSkullRenderer"
] ]
} }

View file

@ -74,6 +74,7 @@ public class MineLittlePony {
// manager.setSkinUrl(SKIN_SERVER_URL); // manager.setSkinUrl(SKIN_SERVER_URL);
// manager.setGatewayURL(GATEWAY_URL); // manager.setGatewayURL(GATEWAY_URL);
manager.addSkinModifier(new PonySkinModifier()); manager.addSkinModifier(new PonySkinModifier());
manager.addSkinParser(new PonySkinParser());
// logger.info("Set MineLP skin server URL."); // logger.info("Set MineLP skin server URL.");
manager.addClearListener(ponyManager); manager.addClearListener(ponyManager);

View file

@ -6,6 +6,7 @@ import com.minelittlepony.settings.SensibleConfig;
import com.mumfrey.liteloader.modconfig.ConfigStrategy; import com.mumfrey.liteloader.modconfig.ConfigStrategy;
import com.mumfrey.liteloader.modconfig.Exposable; import com.mumfrey.liteloader.modconfig.Exposable;
import com.mumfrey.liteloader.modconfig.ExposableOptions; import com.mumfrey.liteloader.modconfig.ExposableOptions;
import com.voxelmodpack.hdskins.HDSkinManager;
/** /**
* Storage container for MineLP client settings. * Storage container for MineLP client settings.
@ -64,6 +65,7 @@ public class PonyConfig extends SensibleConfig implements Exposable {
*/ */
public void setPonyLevel(PonyLevel ponylevel) { public void setPonyLevel(PonyLevel ponylevel) {
this.ponylevel = ponylevel; this.ponylevel = ponylevel;
HDSkinManager.INSTANCE.reloadSkins();
} }
public float getGlobalScaleFactor() { public float getGlobalScaleFactor() {

View file

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

View file

@ -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<String> info) {
info.setReturnValue(MineLittlePony.getInstance().getManager()
.getPony((NetworkPlayerInfo) (Object) this)
.getRace(false)
.getModel()
.getId("slim".equals(info.getReturnValue())));
}
}

View file

@ -6,7 +6,6 @@
"compatibilityLevel": "JAVA_8", "compatibilityLevel": "JAVA_8",
"mixins": [ "mixins": [
"MixinThreadDownloadImageData", "MixinThreadDownloadImageData",
"MixinNetworkPlayerInfo",
"MixinRenderItem", "MixinRenderItem",
"MixinItemRenderer", "MixinItemRenderer",
"MixinRenderManager", "MixinRenderManager",