Fix capes (#436)

* fix capes

* remove gson

* clean up

* clean up

* formatting code

* Made the changes fit a bit better into the already existing code

* Throw the nullptr before the download complete message,
making debugging skins less confusing

Co-authored-by: Tim203 <mctim203@gmail.com>
This commit is contained in:
ForceUpdate1 2020-04-29 18:04:45 +02:00 committed by GitHub
parent 7a9fff1a64
commit 82433f8078
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 51 additions and 20 deletions

View file

@ -101,6 +101,8 @@ public class PlayerEntity extends LivingEntity {
} }
public void sendPlayer(GeyserSession session) { public void sendPlayer(GeyserSession session) {
if(session.getEntityCache().getPlayerEntity(uuid) == null)
return;
if (getLastSkinUpdate() == -1) { if (getLastSkinUpdate() == -1) {
if (playerList) { if (playerList) {
PlayerListPacket playerList = new PlayerListPacket(); PlayerListPacket playerList = new PlayerListPacket();

View file

@ -25,16 +25,22 @@
package org.geysermc.connector.utils; package org.geysermc.connector.utils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import org.geysermc.connector.GeyserConnector; import org.geysermc.connector.GeyserConnector;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import java.awt.*; import java.awt.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.*; import java.util.concurrent.*;
@ -52,6 +58,7 @@ public class SkinProvider {
private static Map<String, Cape> cachedCapes = new ConcurrentHashMap<>(); private static Map<String, Cape> cachedCapes = new ConcurrentHashMap<>();
private static Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>(); private static Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final int CACHE_INTERVAL = 8 * 60 * 1000; // 8 minutes private static final int CACHE_INTERVAL = 8 * 60 * 1000; // 8 minutes
public static boolean hasSkinCached(UUID uuid) { public static boolean hasSkinCached(UUID uuid) {
@ -74,9 +81,10 @@ public class SkinProvider {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null;
SkinAndCape skinAndCape = new SkinAndCape( SkinAndCape skinAndCape = new SkinAndCape(
getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), 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); GeyserConnector.getInstance().getLogger().debug("Took " + (System.currentTimeMillis() - time) + "ms for " + playerId);
@ -112,11 +120,11 @@ public class SkinProvider {
return future; return future;
} }
public static CompletableFuture<Cape> requestCape(String capeUrl, boolean newThread) { public static CompletableFuture<Cape> requestCape(String capeUrl, CapeProvider provider, boolean newThread) {
if (capeUrl == null || capeUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_CAPE); if (capeUrl == null || capeUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_CAPE);
if (requestedCapes.containsKey(capeUrl)) return requestedCapes.get(capeUrl); // already requested 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(); boolean validCache = (System.currentTimeMillis() - CACHE_INTERVAL) < cachedCapes.getOrDefault(capeUrl, EMPTY_CAPE).getRequestedOn();
if ((cachedCapes.containsKey(capeUrl) && officialCape) || validCache) { if ((cachedCapes.containsKey(capeUrl) && officialCape) || validCache) {
@ -126,14 +134,14 @@ public class SkinProvider {
CompletableFuture<Cape> future; CompletableFuture<Cape> future;
if (newThread) { if (newThread) {
future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl), EXECUTOR_SERVICE) future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl, provider), EXECUTOR_SERVICE)
.whenCompleteAsync((cape, throwable) -> { .whenCompleteAsync((cape, throwable) -> {
cachedCapes.put(capeUrl, cape); cachedCapes.put(capeUrl, cape);
requestedCapes.remove(capeUrl); requestedCapes.remove(capeUrl);
}); });
requestedCapes.put(capeUrl, future); requestedCapes.put(capeUrl, future);
} else { } else {
Cape cape = supplyCape(capeUrl); // blocking Cape cape = supplyCape(capeUrl, provider); // blocking
future = CompletableFuture.completedFuture(cape); future = CompletableFuture.completedFuture(cape);
cachedCapes.put(capeUrl, cape); cachedCapes.put(capeUrl, cape);
} }
@ -143,9 +151,9 @@ public class SkinProvider {
public static CompletableFuture<Cape> requestUnofficialCape(Cape officialCape, UUID playerId, public static CompletableFuture<Cape> requestUnofficialCape(Cape officialCape, UUID playerId,
String username, boolean newThread) { String username, boolean newThread) {
if (officialCape.isFailed() && ALLOW_THIRD_PARTY_CAPES) { if (officialCape.isFailed() && ALLOW_THIRD_PARTY_CAPES) {
for (UnofficalCape cape : UnofficalCape.VALUES) { for (CapeProvider provider : CapeProvider.VALUES) {
Cape cape1 = getOrDefault( Cape cape1 = getOrDefault(
requestCape(cape.getUrlFor(playerId, username), newThread), requestCape(provider.getUrlFor(playerId, username), provider, newThread),
EMPTY_CAPE, 4 EMPTY_CAPE, 4
); );
if (!cape1.isFailed()) { if (!cape1.isFailed()) {
@ -159,15 +167,15 @@ public class SkinProvider {
private static Skin supplySkin(UUID uuid, String textureUrl) { private static Skin supplySkin(UUID uuid, String textureUrl) {
byte[] skin = EMPTY_SKIN.getSkinData(); byte[] skin = EMPTY_SKIN.getSkinData();
try { try {
skin = requestImage(textureUrl, false); skin = requestImage(textureUrl, null);
} catch (Exception ignored) {} // just ignore I guess } catch (Exception ignored) {} // just ignore I guess
return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false); 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]; byte[] cape = new byte[0];
try { try {
cape = requestImage(capeUrl, true); cape = requestImage(capeUrl, provider);
} catch (Exception ignored) {} // just ignore I guess } catch (Exception ignored) {} // just ignore I guess
String[] urlSection = capeUrl.split("/"); // A real url is expected at this stage 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 { private static byte[] requestImage(String imageUrl, CapeProvider provider) throws Exception {
BufferedImage image = ImageIO.read(new URL(imageUrl)); BufferedImage image = downloadImage(imageUrl, provider);
GeyserConnector.getInstance().getLogger().debug("Downloaded " + imageUrl); 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; image = image.getWidth() > 64 ? scale(image) : image;
BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_RGB); BufferedImage newImage = new BufferedImage(64, 32, BufferedImage.TYPE_INT_RGB);
Graphics g = newImage.createGraphics(); 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); BufferedImage resized = new BufferedImage(bufferedImage.getWidth() / 2, bufferedImage.getHeight() / 2, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = resized.createGraphics(); Graphics2D g2 = resized.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
@ -262,14 +289,16 @@ public class SkinProvider {
* Sorted by 'priority' * Sorted by 'priority'
*/ */
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor
@Getter @Getter
public enum UnofficalCape { public enum CapeProvider {
MINECRAFT,
OPTIFINE("http://s.optifine.net/capes/%s.png", CapeUrlType.USERNAME), OPTIFINE("http://s.optifine.net/capes/%s.png", CapeUrlType.USERNAME),
LABYMOD("http://capes.labymod.net/capes/%s.png", CapeUrlType.UUID_DASHED), LABYMOD("https://www.labymod.net/page/php/getCapeTexture.php?uuid=%s", CapeUrlType.UUID_DASHED),
FIVEZIG("http://textures.5zig.net/2/%s", CapeUrlType.UUID), FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED),
MINECRAFTCAPES("https://www.minecraftcapes.co.uk/getCape/%s", CapeUrlType.UUID); 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 String url;
private CapeUrlType type; private CapeUrlType type;

View file

@ -155,7 +155,7 @@ public class SkinUtils {
cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape( cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape(
cape, entity.getUuid(), cape, entity.getUuid(),
entity.getUsername(), false entity.getUsername(), false
), SkinProvider.EMPTY_CAPE, SkinProvider.UnofficalCape.VALUES.length * 3); ), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3);
} }
if (entity.getLastSkinUpdate() < skin.getRequestedOn()) { if (entity.getLastSkinUpdate() < skin.getRequestedOn()) {