From 72324feaf34c7a84ef7b2b694cdfea167497f640 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Fri, 24 Aug 2018 21:38:29 -0400 Subject: [PATCH 1/5] Fix file lock issues with the skin cache --- .../voxelmodpack/hdskins/ThreadDownloadImageETag.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java b/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java index 65446d36..3f9ba045 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java @@ -193,7 +193,8 @@ public class ThreadDownloadImageETag extends SimpleTexture { Files.createDirectories(cacheFile.getParent()); Files.copy(resp.getInputStream(), cacheFile); - BufferedImage bufferedimage = ImageIO.read(Files.newInputStream(cacheFile)); + try (InputStream in = Files.newInputStream(cacheFile)) { + BufferedImage bufferedimage = ImageIO.read(in); // maybe write the etag to disk Header eTag = resp.getResponse().getFirstHeader(HttpHeaders.ETAG); @@ -201,10 +202,11 @@ public class ThreadDownloadImageETag extends SimpleTexture { Files.write(eTagFile, Collections.singleton(eTag.getValue())); } - if (imageBuffer != null) { - bufferedimage = imageBuffer.parseUserSkin(bufferedimage); + if (imageBuffer != null) { + bufferedimage = imageBuffer.parseUserSkin(bufferedimage); + } + setBufferedImage(bufferedimage); } - setBufferedImage(bufferedimage); } } catch (Exception exception) { LOGGER.error("Couldn\'t download http texture", exception); From 981cd002b35c0b16b752a2baae46b81907e14b1b Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Fri, 24 Aug 2018 21:55:45 -0400 Subject: [PATCH 2/5] Slightly rewrite texture loading so it is better adaptable. Also exposes metadata more --- .../voxelmodpack/hdskins/HDSkinManager.java | 169 ++++++++---------- .../hdskins/INetworkPlayerInfo.java | 15 ++ .../hdskins/mixin/MixinPlayerInfo.java | 103 +++++++---- .../hdskins/mixin/MixinSkullRenderer.java | 20 +-- .../java/com/minelittlepony/PonyManager.java | 4 - .../com/minelittlepony/ducks/IPlayerInfo.java | 17 -- .../mixin/MixinNetworkPlayerInfo.java | 41 ++--- .../render/player/RenderPonyPlayer.java | 7 +- 8 files changed, 183 insertions(+), 193 deletions(-) create mode 100644 src/hdskins/java/com/voxelmodpack/hdskins/INetworkPlayerInfo.java delete mode 100644 src/main/java/com/minelittlepony/ducks/IPlayerInfo.java diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java index eb86fa73..040b103a 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/HDSkinManager.java @@ -19,18 +19,19 @@ import com.mumfrey.liteloader.core.LiteLoader; import com.mumfrey.liteloader.util.log.LiteLoaderLogger; import com.voxelmodpack.hdskins.gui.GuiSkins; import com.voxelmodpack.hdskins.resource.SkinResourceManager; -import com.voxelmodpack.hdskins.skins.AsyncCacheLoader; import com.voxelmodpack.hdskins.skins.BethlehemSkinServer; 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 net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.client.network.NetworkPlayerInfo; +import net.minecraft.client.renderer.texture.ITextureObject; import net.minecraft.client.resources.DefaultPlayerSkin; import net.minecraft.client.resources.IResourceManager; import net.minecraft.client.resources.IResourceManagerReloadListener; -import net.minecraft.client.resources.SkinManager.SkinAvailableCallback; +import net.minecraft.client.resources.SkinManager; import net.minecraft.util.ResourceLocation; import org.apache.commons.io.FileUtils; import org.apache.http.impl.client.CloseableHttpClient; @@ -46,15 +47,17 @@ import java.lang.reflect.Modifier; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nullable; public final class HDSkinManager implements IResourceManagerReloadListener { @@ -64,24 +67,16 @@ public final class HDSkinManager implements IResourceManagerReloadListener { public static final ExecutorService skinDownloadExecutor = Executors.newFixedThreadPool(8); public static final CloseableHttpClient httpClient = HttpClients.createSystem(); - private static final ResourceLocation LOADING = new ResourceLocation("LOADING"); - public static final HDSkinManager INSTANCE = new HDSkinManager(); - private boolean enabled = true; - private List clearListeners = Lists.newArrayList(); private BiMap> skinServerTypes = HashBiMap.create(2); private List skinServers = Lists.newArrayList(); - private Map> skinCache = Maps.newHashMap(); - - private LoadingCache> skins = CacheBuilder.newBuilder() - .initialCapacity(20) - .maximumSize(100) - .expireAfterWrite(4, TimeUnit.HOURS) - .build(AsyncCacheLoader.create(CacheLoader.from(this::loadProfileData), Collections.emptyMap(), skinDownloadExecutor)); + private LoadingCache>> skins = CacheBuilder.newBuilder() + .expireAfterAccess(15, TimeUnit.SECONDS) + .build(CacheLoader.from(this::loadProfileData)); private List skinModifiers = Lists.newArrayList(); @@ -107,19 +102,30 @@ public final class HDSkinManager implements IResourceManagerReloadListener { return skinsGuiFunc.apply(ImmutableList.copyOf(this.skinServers)); } - public Optional getSkinLocation(GameProfile profile1, final Type type, boolean loadIfAbsent) { - if (!enabled) { - return Optional.empty(); - } + private CompletableFuture> loadProfileData(GameProfile profile) { - ResourceLocation skin = this.resources.getPlayerTexture(profile1, type); - if (skin != null) { - return Optional.of(skin); - } + return CompletableFuture.supplyAsync(() -> { + Map textureMap = Maps.newEnumMap(Type.class); + for (SkinServer server : skinServers) { + try { + server.loadProfileData(profile).getTextures().forEach(textureMap::putIfAbsent); + if (textureMap.size() == Type.values().length) { + break; + } + } catch (IOException e) { + logger.trace(e); + } + + } + return textureMap; + }, skinDownloadExecutor); + } + + public CompletableFuture> loadProfileTextures(GameProfile profile) { // try to recreate a broken gameprofile // happens when server sends a random profile with skin and displayname - Property textures = Iterables.getFirst(profile1.getProperties().get("textures"), null); + Property textures = Iterables.getFirst(profile.getProperties().get("textures"), null); if (textures != null) { String json = new String(Base64.getDecoder().decode(textures.getValue()), StandardCharsets.UTF_8); MinecraftTexturesPayload texturePayload = SkinServer.gson.fromJson(json, MinecraftTexturesPayload.class); @@ -129,86 +135,55 @@ public final class HDSkinManager implements IResourceManagerReloadListener { UUID uuid = texturePayload.getProfileId(); // uuid is required if (uuid != null) { - profile1 = new GameProfile(uuid, name); + profile = new GameProfile(uuid, name); } // probably uses this texture for a reason. Don't mess with it. if (!texturePayload.getTextures().isEmpty() && texturePayload.getProfileId() == null) { - return Optional.empty(); + return CompletableFuture.completedFuture(Collections.emptyMap()); } } } - final GameProfile profile = profile1; - - // cannot get texture without id! - if (profile.getId() == null) { - return Optional.empty(); - } - - if (!this.skinCache.containsKey(profile.getId())) { - this.skinCache.put(profile.getId(), Maps.newHashMap()); - } - - skin = this.skinCache.get(profile.getId()).get(type); - if (skin == null) { - if (loadIfAbsent && getProfileData(profile).containsKey(type)) { - skinCache.get(profile.getId()).put(type, LOADING); - loadTexture(profile, type, (t, loc, tex) -> skinCache.get(profile.getId()).put(t, loc)); - } - return Optional.empty(); - } - return skin == LOADING ? Optional.empty() : Optional.of(skin); + return skins.getUnchecked(profile); } - private void loadTexture(GameProfile profile, final Type type, final SkinAvailableCallback callback) { - if (profile.getId() == null) { - return; - } - + public ResourceLocation loadTexture(Type type, MinecraftProfileTexture texture, @Nullable SkinManager.SkinAvailableCallback callback) { String skinDir = type.toString().toLowerCase() + "s/"; - final MinecraftProfileTexture texture = getProfileData(profile).get(type); final ResourceLocation resource = new ResourceLocation("hdskins", skinDir + texture.getHash()); + ITextureObject texObj = Minecraft.getMinecraft().getTextureManager().getTexture(resource); - ISkinAvailableCallback buffs = new ImageBufferDownloadHD(type, () -> { - callback.skinAvailable(type, resource, texture); - }); - - // schedule texture loading on the main thread. - TextureLoader.loadTexture(resource, new ThreadDownloadImageETag( - new File(LiteLoader.getAssetsDirectory(), "hd/" + skinDir + texture.getHash().substring(0, 2) + "/" + texture.getHash()), - texture.getUrl(), - DefaultPlayerSkin.getDefaultSkinLegacy(), - buffs)); - } - - private Map loadProfileData(GameProfile profile) { - Map textures = Maps.newEnumMap(Type.class); - for (SkinServer server : skinServers) { - try { - server.loadProfileData(profile).getTextures().forEach(textures::putIfAbsent); - if (textures.size() == Type.values().length) { - break; - } - } catch (IOException e) { - logger.trace(e); + //noinspection ConstantConditions + if (texObj != null) { + if (callback != null) { + callback.skinAvailable(type, resource, texture); } - + } else { + // schedule texture loading on the main thread. + TextureLoader.loadTexture(resource, new ThreadDownloadImageETag( + new File(LiteLoader.getAssetsDirectory(), "hd/" + skinDir + texture.getHash().substring(0, 2) + "/" + texture.getHash()), + texture.getUrl(), + DefaultPlayerSkin.getDefaultSkinLegacy(), + new ImageBufferDownloadHD(type, () -> { + if (callback != null) { + callback.skinAvailable(type, resource, texture); + } + }))); } - return textures; + return resource; } - public Map getProfileData(GameProfile profile) { - boolean was = !skins.asMap().containsKey(profile); - Map textures = skins.getUnchecked(profile); - // This is the initial value. Refreshing will load it asynchronously. - if (was) { - skins.refresh(profile); + public Map getTextures(GameProfile profile) { + + Map map = new HashMap<>(); + for (Map.Entry e : loadProfileTextures(profile).getNow(Collections.emptyMap()).entrySet()) { + map.put(e.getKey(), loadTexture(e.getKey(), e.getValue(), null)); } - return textures; + return map; + } - public void addSkinServerType(Class type) { + private void addSkinServerType(Class type) { Preconditions.checkArgument(!type.isInterface(), "type cannot be an interface"); Preconditions.checkArgument(!Modifier.isAbstract(type.getModifiers()), "type cannot be abstract"); ServerType st = type.getAnnotation(ServerType.class); @@ -222,14 +197,10 @@ public final class HDSkinManager implements IResourceManagerReloadListener { return this.skinServerTypes.get(type); } - public void addSkinServer(SkinServer skinServer) { + void addSkinServer(SkinServer skinServer) { this.skinServers.add(skinServer); } - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - public void addClearListener(ISkinCacheClearListener listener) { clearListeners.add(listener); } @@ -237,18 +208,14 @@ public final class HDSkinManager implements IResourceManagerReloadListener { public void clearSkinCache() { LiteLoaderLogger.info("Clearing local player skin cache"); - try { - FileUtils.deleteDirectory(new File(LiteLoader.getAssetsDirectory(), "skins")); - FileUtils.deleteDirectory(new File(LiteLoader.getAssetsDirectory(), "hd")); - } catch (IOException e) { - e.printStackTrace(); + FileUtils.deleteQuietly(new File(LiteLoader.getAssetsDirectory(), "hd")); + + NetHandlerPlayClient connection = Minecraft.getMinecraft().getConnection(); + + if (connection != null) { + connection.getPlayerInfoMap().forEach(this::clearNetworkSkin); } - TextureManager textures = Minecraft.getMinecraft().getTextureManager(); - skinCache.values().stream() - .flatMap(m -> m.values().stream()) - .forEach(textures::deleteTexture); - skinCache.clear(); skins.invalidateAll(); clearListeners = clearListeners.stream() @@ -256,6 +223,10 @@ public final class HDSkinManager implements IResourceManagerReloadListener { .collect(Collectors.toList()); } + private void clearNetworkSkin(NetworkPlayerInfo player) { + ((INetworkPlayerInfo) player).deleteTextures(); + } + private boolean onSkinCacheCleared(ISkinCacheClearListener callback) { try { return callback.onSkinCacheCleared(); diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/INetworkPlayerInfo.java b/src/hdskins/java/com/voxelmodpack/hdskins/INetworkPlayerInfo.java new file mode 100644 index 00000000..d6eb8094 --- /dev/null +++ b/src/hdskins/java/com/voxelmodpack/hdskins/INetworkPlayerInfo.java @@ -0,0 +1,15 @@ +package com.voxelmodpack.hdskins; + +import com.mojang.authlib.minecraft.MinecraftProfileTexture; +import net.minecraft.util.ResourceLocation; + +import java.util.Optional; + +public interface INetworkPlayerInfo { + + Optional getResourceLocation(MinecraftProfileTexture.Type type); + + Optional getProfileTexture(MinecraftProfileTexture.Type type); + + void deleteTextures(); +} diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java index 432c383d..858c3433 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java @@ -4,65 +4,92 @@ import com.mojang.authlib.GameProfile; import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; 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; 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.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; @Mixin(NetworkPlayerInfo.class) -public abstract class MixinPlayerInfo { +public abstract class MixinPlayerInfo implements INetworkPlayerInfo { - @Shadow - public abstract GameProfile getGameProfile(); - @Inject( - method = "getLocationSkin", - cancellable = true, - at = @At("RETURN")) - private void getLocationSkin(CallbackInfoReturnable ci) { - getTextureLocation(ci, Type.SKIN); + private Map customTextures = new HashMap<>(); + private Map customProfiles = new HashMap<>(); + + @Shadow @Final private GameProfile gameProfile; + + @Shadow public abstract String getSkinType(); + + @SuppressWarnings("InvalidMemberReference") // mc-dev bug? + @Redirect( + method = { + "getLocationSkin", + "getLocationCape", + "getLocationElytra" + }, at = @At(value = "INVOKE", target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;")) + // synthetic + private Object getSkin(Map playerTextures, Object key) { + return getSkin(playerTextures, (Type) key); } - @Inject( - method = "getLocationCape", - cancellable = true, - at = @At("RETURN")) - private void getLocationCape(CallbackInfoReturnable ci) { - getTextureLocation(ci, Type.CAPE); + // with generics + private ResourceLocation getSkin(Map playerTextures, Type type) { + return getResourceLocation(type).orElseGet(() -> playerTextures.get(type)); } - @Inject( - method = "getLocationElytra", - cancellable = true, - at = @At("RETURN")) - private void getLocationElytra(CallbackInfoReturnable ci) { - getTextureLocation(ci, Type.ELYTRA); + @Inject(method = "getSkinType", at = @At("RETURN"), cancellable = true) + private void getTextureModel(CallbackInfoReturnable cir) { + getProfileTexture(Type.SKIN).ifPresent(profile -> { + String model = profile.getMetadata("model"); + cir.setReturnValue(model != null ? model : "default"); + }); } - private void getTextureLocation(CallbackInfoReturnable ci, Type type) { - Optional texture = HDSkinManager.INSTANCE.getSkinLocation(getGameProfile(), type, true); - texture.ifPresent(ci::setReturnValue); + @Inject(method = "loadPlayerTextures", + at = @At(value = "INVOKE", + target = "Lnet/minecraft/client/resources/SkinManager;loadProfileTextures(" + + "Lcom/mojang/authlib/GameProfile;" + + "Lnet/minecraft/client/resources/SkinManager$SkinAvailableCallback;" + + "Z)V", + shift = At.Shift.BEFORE)) + private void onLoadTexture(CallbackInfo ci) { + HDSkinManager.INSTANCE.loadProfileTextures(this.gameProfile) + .thenAcceptAsync(m -> m.forEach((type, profile) -> { + HDSkinManager.INSTANCE.loadTexture(type, profile, (typeIn, location, profileTexture) -> { + customTextures.put(type, location); + customProfiles.put(type, profileTexture); + }); + }), Minecraft.getMinecraft()::addScheduledTask); } - @Inject( - method = "getSkinType", - cancellable = true, - at = @At("RETURN")) - private void getSkinType(CallbackInfoReturnable ci) { - MinecraftProfileTexture skin = HDSkinManager.INSTANCE.getProfileData(getGameProfile()).get(Type.SKIN); - if (skin != null) { - String type = skin.getMetadata("model"); - if (type == null) - type = "default"; - String type1 = type; - Optional texture = HDSkinManager.INSTANCE.getSkinLocation(getGameProfile(), Type.SKIN, false); + @Override + public Optional getResourceLocation(Type type) { + return Optional.ofNullable(this.customTextures.get(type)); + } - texture.ifPresent((res) -> ci.setReturnValue(type1)); - } + @Override + public Optional getProfileTexture(Type type) { + return Optional.ofNullable(this.customProfiles.get(type)); + } + + @Override + public void deleteTextures() { + TextureManager tm = Minecraft.getMinecraft().getTextureManager(); + this.customTextures.values().forEach(tm::deleteTexture); + this.customTextures.clear(); + this.customProfiles.clear(); } } diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinSkullRenderer.java b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinSkullRenderer.java index dda9662c..52156508 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinSkullRenderer.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinSkullRenderer.java @@ -13,7 +13,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Redirect; import javax.annotation.Nullable; -import java.util.Optional; @Mixin(TileEntitySkullRenderer.class) public abstract class MixinSkullRenderer extends TileEntitySpecialRenderer { @@ -24,16 +23,15 @@ public abstract class MixinSkullRenderer extends TileEntitySpecialRenderer skin = HDSkinManager.INSTANCE.getSkinLocation(profile, Type.SKIN, true); - if (skin.isPresent()) - // rebind - bindTexture(skin.get()); - else - bindTexture(rl); - } else - bindTexture(rl); + ResourceLocation skin = HDSkinManager.INSTANCE.getTextures(profile).get(Type.SKIN); + if (skin != null) { + rl = skin; + } + } + bindTexture(rl); } } diff --git a/src/main/java/com/minelittlepony/PonyManager.java b/src/main/java/com/minelittlepony/PonyManager.java index ac31d982..1807f108 100644 --- a/src/main/java/com/minelittlepony/PonyManager.java +++ b/src/main/java/com/minelittlepony/PonyManager.java @@ -6,9 +6,7 @@ import com.google.gson.Gson; import com.google.gson.JsonParseException; import com.minelittlepony.pony.data.Pony; import com.minelittlepony.pony.data.PonyLevel; -import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.ISkinCacheClearListener; - import net.minecraft.client.Minecraft; import net.minecraft.client.entity.AbstractClientPlayer; import net.minecraft.client.network.NetworkPlayerInfo; @@ -83,8 +81,6 @@ public class PonyManager implements IResourceManagerReloadListener, ISkinCacheCl } public Pony getPony(NetworkPlayerInfo playerInfo) { - // force load HDSkins if they're not available - HDSkinManager.INSTANCE.getProfileData(playerInfo.getGameProfile()); ResourceLocation skin = playerInfo.getLocationSkin(); UUID uuid = playerInfo.getGameProfile().getId(); diff --git a/src/main/java/com/minelittlepony/ducks/IPlayerInfo.java b/src/main/java/com/minelittlepony/ducks/IPlayerInfo.java deleted file mode 100644 index bf7ef895..00000000 --- a/src/main/java/com/minelittlepony/ducks/IPlayerInfo.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.minelittlepony.ducks; - -import net.minecraft.client.network.NetworkPlayerInfo; - -public interface IPlayerInfo { - /** - * Returns true if the vanilla skin (the one returned by NetworkPlayerInfo.getSkinLocation) uses the ALEX model type. - */ - boolean usesSlimArms(); - - /** - * Quick cast back to the original type. - */ - default NetworkPlayerInfo unwrap() { - return (NetworkPlayerInfo)this; - } -} diff --git a/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java b/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java index a24eb269..2439f2e8 100644 --- a/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java +++ b/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java @@ -1,41 +1,42 @@ package com.minelittlepony.mixin; +import com.minelittlepony.MineLittlePony; +import com.minelittlepony.PonyManager; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; +import com.voxelmodpack.hdskins.INetworkPlayerInfo; +import net.minecraft.client.network.NetworkPlayerInfo; +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.CallbackInfoReturnable; -import com.minelittlepony.MineLittlePony; -import com.minelittlepony.PonyManager; -import com.minelittlepony.ducks.IPlayerInfo; -import com.mojang.authlib.minecraft.MinecraftProfileTexture; -import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; -import com.voxelmodpack.hdskins.HDSkinManager; +@Mixin(value = NetworkPlayerInfo.class, priority = 999) +public abstract class MixinNetworkPlayerInfo implements INetworkPlayerInfo { -import net.minecraft.client.network.NetworkPlayerInfo; + @Shadow private String skinType; -@Mixin(NetworkPlayerInfo.class) -public abstract class MixinNetworkPlayerInfo implements IPlayerInfo { - - @Shadow - private String skinType; + @Shadow @Final private GameProfile gameProfile; @Inject(method = "getSkinType()Ljava/lang/String;", at = @At("RETURN"), cancellable = true) private void getSkinType(CallbackInfoReturnable info) { - info.setReturnValue(MineLittlePony.getInstance().getManager().getPony(unwrap()).getRace(false).getModel().getId(usesSlimArms())); + info.setReturnValue(MineLittlePony.getInstance().getManager() + .getPony((NetworkPlayerInfo) (Object) this) + .getRace(false) + .getModel() + .getId(usesSlimArms())); } - @Override - public boolean usesSlimArms() { + private boolean usesSlimArms() { if (skinType == null) { - MinecraftProfileTexture skin = HDSkinManager.INSTANCE.getProfileData(unwrap().getGameProfile()).get(Type.SKIN); - if (skin != null) { - return "slim".equals(skin.getMetadata("model")); - } + return getProfileTexture(Type.SKIN) + .map(profile -> profile.getMetadata("model")) + .filter("slim"::equals) + .isPresent() || PonyManager.isSlimSkin(this.gameProfile.getId()); - return PonyManager.isSlimSkin(unwrap().getGameProfile().getId()); } return "slim".equals(skinType); diff --git a/src/main/java/com/minelittlepony/render/player/RenderPonyPlayer.java b/src/main/java/com/minelittlepony/render/player/RenderPonyPlayer.java index 70e89052..cbda740d 100644 --- a/src/main/java/com/minelittlepony/render/player/RenderPonyPlayer.java +++ b/src/main/java/com/minelittlepony/render/player/RenderPonyPlayer.java @@ -34,7 +34,6 @@ import net.minecraft.util.EnumHandSide; import net.minecraft.util.ResourceLocation; import java.util.Map; -import java.util.Optional; public class RenderPonyPlayer extends RenderPlayer implements IRenderPony { @@ -58,9 +57,9 @@ public class RenderPonyPlayer extends RenderPlayer implements IRenderPony skin = HDSkinManager.INSTANCE.getSkinLocation(profile, Type.SKIN, true); - if (skin.isPresent()) { - return skin.get(); + ResourceLocation skin = HDSkinManager.INSTANCE.getTextures(profile).get(Type.SKIN); + if (skin != null) { + return skin; } Minecraft minecraft = Minecraft.getMinecraft(); From c10c61b73b1ed40f9d74c0e5a30c4916bef51e23 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Fri, 24 Aug 2018 21:58:35 -0400 Subject: [PATCH 3/5] Don't know how this didn't get committed. --- .../hdskins/ThreadDownloadImageETag.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java b/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java index 3f9ba045..92377f01 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/ThreadDownloadImageETag.java @@ -26,7 +26,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.concurrent.atomic.AtomicInteger; - import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.imageio.ImageIO; @@ -104,7 +103,7 @@ public class ThreadDownloadImageETag extends SimpleTexture { } private void loadTexture() { - switch(checkLocalCache()) { + switch (checkLocalCache()) { case GONE: clearCache(); break; @@ -196,11 +195,11 @@ public class ThreadDownloadImageETag extends SimpleTexture { try (InputStream in = Files.newInputStream(cacheFile)) { BufferedImage bufferedimage = ImageIO.read(in); - // maybe write the etag to disk - Header eTag = resp.getResponse().getFirstHeader(HttpHeaders.ETAG); - if (eTag != null) { - Files.write(eTagFile, Collections.singleton(eTag.getValue())); - } + // maybe write the etag to disk + Header eTag = resp.getResponse().getFirstHeader(HttpHeaders.ETAG); + if (eTag != null) { + Files.write(eTagFile, Collections.singleton(eTag.getValue())); + } if (imageBuffer != null) { bufferedimage = imageBuffer.parseUserSkin(bufferedimage); From ca48785add488e3f934d42d89005537d2382d56c Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Fri, 24 Aug 2018 23:35:18 -0400 Subject: [PATCH 4/5] Clear all skins and actually re-load them when clearing the cache. --- .../hdskins/mixin/MixinPlayerInfo.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java index 858c3433..8a922e22 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java @@ -21,17 +21,18 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; @Mixin(NetworkPlayerInfo.class) public abstract class MixinPlayerInfo implements INetworkPlayerInfo { - private Map customTextures = new HashMap<>(); private Map customProfiles = new HashMap<>(); @Shadow @Final private GameProfile gameProfile; - - @Shadow public abstract String getSkinType(); + @Shadow Map playerTextures; + @Shadow private boolean playerTexturesLoaded; + @Shadow private String skinType; @SuppressWarnings("InvalidMemberReference") // mc-dev bug? @Redirect( @@ -88,8 +89,14 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo { @Override public void deleteTextures() { TextureManager tm = Minecraft.getMinecraft().getTextureManager(); - this.customTextures.values().forEach(tm::deleteTexture); + 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; } } From 8eb19c117fd73b928317cf38cee3ff7dd877fb06 Mon Sep 17 00:00:00 2001 From: Matthew Messinger Date: Sat, 25 Aug 2018 16:53:42 -0400 Subject: [PATCH 5/5] Quick fix for mixin conflict which caused ponies to be broken --- .../hdskins/mixin/MixinPlayerInfo.java | 13 +++++----- .../mixin/MixinNetworkPlayerInfo.java | 26 ++----------------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java index 8a922e22..9c80ff6a 100644 --- a/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java +++ b/src/hdskins/java/com/voxelmodpack/hdskins/mixin/MixinPlayerInfo.java @@ -16,7 +16,6 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.HashMap; import java.util.Map; @@ -51,12 +50,14 @@ public abstract class MixinPlayerInfo implements INetworkPlayerInfo { return getResourceLocation(type).orElseGet(() -> playerTextures.get(type)); } - @Inject(method = "getSkinType", at = @At("RETURN"), cancellable = true) - private void getTextureModel(CallbackInfoReturnable cir) { - getProfileTexture(Type.SKIN).ifPresent(profile -> { + @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"); - cir.setReturnValue(model != null ? model : "default"); - }); + return model != null ? model : "default"; + }).orElse(this.skinType); } @Inject(method = "loadPlayerTextures", diff --git a/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java b/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java index 2439f2e8..a8778bdf 100644 --- a/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java +++ b/src/main/java/com/minelittlepony/mixin/MixinNetworkPlayerInfo.java @@ -1,44 +1,22 @@ package com.minelittlepony.mixin; import com.minelittlepony.MineLittlePony; -import com.minelittlepony.PonyManager; -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; import com.voxelmodpack.hdskins.INetworkPlayerInfo; import net.minecraft.client.network.NetworkPlayerInfo; -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.CallbackInfoReturnable; -@Mixin(value = NetworkPlayerInfo.class, priority = 999) +@Mixin(NetworkPlayerInfo.class) public abstract class MixinNetworkPlayerInfo implements INetworkPlayerInfo { - @Shadow private String skinType; - - @Shadow @Final private GameProfile gameProfile; - @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(usesSlimArms())); - } - - private boolean usesSlimArms() { - if (skinType == null) { - - return getProfileTexture(Type.SKIN) - .map(profile -> profile.getMetadata("model")) - .filter("slim"::equals) - .isPresent() || PonyManager.isSlimSkin(this.gameProfile.getId()); - - } - - return "slim".equals(skinType); + .getId("slim".equals(info.getReturnValue()))); } }