diff --git a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java index d0ac2aeb..1766ae5b 100644 --- a/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java +++ b/connector/src/main/java/org/geysermc/connector/entity/PlayerEntity.java @@ -101,6 +101,8 @@ public class PlayerEntity extends LivingEntity { } public void sendPlayer(GeyserSession session) { + if(session.getEntityCache().getPlayerEntity(uuid) == null) + return; if (getLastSkinUpdate() == -1) { if (playerList) { PlayerListPacket playerList = new PlayerListPacket(); diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java index ade03c54..7c2b7fc0 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -25,16 +25,22 @@ package org.geysermc.connector.utils; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AllArgsConstructor; import lombok.Getter; - +import lombok.NoArgsConstructor; import org.geysermc.connector.GeyserConnector; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.net.URL; +import java.util.Arrays; +import java.util.Base64; import java.util.Map; import java.util.UUID; import java.util.concurrent.*; @@ -52,6 +58,7 @@ public class SkinProvider { private static Map cachedCapes = new ConcurrentHashMap<>(); private static Map> requestedCapes = new ConcurrentHashMap<>(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final int CACHE_INTERVAL = 8 * 60 * 1000; // 8 minutes public static boolean hasSkinCached(UUID uuid) { @@ -74,9 +81,10 @@ public class SkinProvider { return CompletableFuture.supplyAsync(() -> { long time = System.currentTimeMillis(); + CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null; SkinAndCape skinAndCape = new SkinAndCape( getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), - getOrDefault(requestCape(capeUrl, false), EMPTY_CAPE, 5) + getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5) ); GeyserConnector.getInstance().getLogger().debug("Took " + (System.currentTimeMillis() - time) + "ms for " + playerId); @@ -112,11 +120,11 @@ public class SkinProvider { return future; } - public static CompletableFuture requestCape(String capeUrl, boolean newThread) { + public static CompletableFuture requestCape(String capeUrl, CapeProvider provider, boolean newThread) { if (capeUrl == null || capeUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_CAPE); if (requestedCapes.containsKey(capeUrl)) return requestedCapes.get(capeUrl); // already requested - boolean officialCape = capeUrl.startsWith("https://textures.minecraft.net"); + boolean officialCape = provider == CapeProvider.MINECRAFT; boolean validCache = (System.currentTimeMillis() - CACHE_INTERVAL) < cachedCapes.getOrDefault(capeUrl, EMPTY_CAPE).getRequestedOn(); if ((cachedCapes.containsKey(capeUrl) && officialCape) || validCache) { @@ -126,14 +134,14 @@ public class SkinProvider { CompletableFuture future; if (newThread) { - future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl), EXECUTOR_SERVICE) + future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl, provider), EXECUTOR_SERVICE) .whenCompleteAsync((cape, throwable) -> { cachedCapes.put(capeUrl, cape); requestedCapes.remove(capeUrl); }); requestedCapes.put(capeUrl, future); } else { - Cape cape = supplyCape(capeUrl); // blocking + Cape cape = supplyCape(capeUrl, provider); // blocking future = CompletableFuture.completedFuture(cape); cachedCapes.put(capeUrl, cape); } @@ -143,9 +151,9 @@ public class SkinProvider { public static CompletableFuture requestUnofficialCape(Cape officialCape, UUID playerId, String username, boolean newThread) { if (officialCape.isFailed() && ALLOW_THIRD_PARTY_CAPES) { - for (UnofficalCape cape : UnofficalCape.VALUES) { + for (CapeProvider provider : CapeProvider.VALUES) { Cape cape1 = getOrDefault( - requestCape(cape.getUrlFor(playerId, username), newThread), + requestCape(provider.getUrlFor(playerId, username), provider, newThread), EMPTY_CAPE, 4 ); if (!cape1.isFailed()) { @@ -159,15 +167,15 @@ public class SkinProvider { private static Skin supplySkin(UUID uuid, String textureUrl) { byte[] skin = EMPTY_SKIN.getSkinData(); try { - skin = requestImage(textureUrl, false); + skin = requestImage(textureUrl, null); } catch (Exception ignored) {} // just ignore I guess return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false); } - private static Cape supplyCape(String capeUrl) { + private static Cape supplyCape(String capeUrl, CapeProvider provider) { byte[] cape = new byte[0]; try { - cape = requestImage(capeUrl, true); + cape = requestImage(capeUrl, provider); } catch (Exception ignored) {} // just ignore I guess String[] urlSection = capeUrl.split("/"); // A real url is expected at this stage @@ -181,11 +189,12 @@ public class SkinProvider { ); } - private static byte[] requestImage(String imageUrl, boolean cape) throws Exception { - BufferedImage image = ImageIO.read(new URL(imageUrl)); + private static byte[] requestImage(String imageUrl, CapeProvider provider) throws Exception { + BufferedImage image = downloadImage(imageUrl, provider); GeyserConnector.getInstance().getLogger().debug("Downloaded " + imageUrl); - if (cape) { + // if the requested image is an cape + if (provider != null) { image = image.getWidth() > 64 ? scale(image) : image; BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_RGB); Graphics g = newImage.createGraphics(); @@ -209,7 +218,25 @@ public class SkinProvider { } } - private static BufferedImage scale (BufferedImage bufferedImage) { + private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException { + if (provider == CapeProvider.FIVEZIG) + return readFiveZigCape(imageUrl); + BufferedImage image = ImageIO.read(new URL(imageUrl)); + if (image == null) throw new NullPointerException(); + return image; + } + + private static BufferedImage readFiveZigCape(String url) throws IOException { + JsonNode element = OBJECT_MAPPER.readTree(WebUtils.getBody(url)); + if (element != null && element.isObject()) { + JsonNode capeElement = element.get("d"); + if (capeElement == null || capeElement.isNull()) return null; + return ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(capeElement.textValue()))); + } + return null; + } + + private static BufferedImage scale(BufferedImage bufferedImage) { BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_RGB); Graphics2D g2 = resized.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); @@ -262,14 +289,16 @@ public class SkinProvider { * Sorted by 'priority' */ @AllArgsConstructor + @NoArgsConstructor @Getter - public enum UnofficalCape { + public enum CapeProvider { + MINECRAFT, OPTIFINE("http://s.optifine.net/capes/%s.png", CapeUrlType.USERNAME), - LABYMOD("http://capes.labymod.net/capes/%s.png", CapeUrlType.UUID_DASHED), - FIVEZIG("http://textures.5zig.net/2/%s", CapeUrlType.UUID), + LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED), + FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED), MINECRAFTCAPES("https://www.minecraftcapes.co.uk/getCape/%s", CapeUrlType.UUID); - public static final UnofficalCape[] VALUES = values(); + public static final CapeProvider[] VALUES = Arrays.copyOfRange(values(), 1, 5); private String url; private CapeUrlType type; diff --git a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java index 012bd73a..9ce025e7 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -155,7 +155,7 @@ public class SkinUtils { cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( cape, entity.getUuid(), entity.getUsername(), false - ), SkinProvider.EMPTY_CAPE, SkinProvider.UnofficalCape.VALUES.length * 3); + ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3); } if (entity.getLastSkinUpdate() < skin.getRequestedOn()) {