diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPlayerInitializedTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPlayerInitializedTranslator.java index 435b192b..ba2e3c93 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPlayerInitializedTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/bedrock/BedrockPlayerInitializedTranslator.java @@ -15,7 +15,6 @@ public class BedrockPlayerInitializedTranslator extends PacketTranslator entity.sendPlayer(session)); } diff --git a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java index 4d00bdcb..660cac2e 100644 --- a/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java +++ b/connector/src/main/java/org/geysermc/connector/network/translators/java/entity/spawn/JavaSpawnPlayerTranslator.java @@ -51,8 +51,9 @@ public class JavaSpawnPlayerTranslator extends PacketTranslator entity.sendPlayer(session)); + if (session.getUpstream().isInitialized()) { + SkinUtils.requestAndHandleSkinAndCape(entity, session, skinAndCape -> entity.sendPlayer(session)); + } } } diff --git a/connector/src/main/java/org/geysermc/connector/utils/ProvidedSkin.java b/connector/src/main/java/org/geysermc/connector/utils/ProvidedSkin.java index 71bf2984..78cd8933 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/ProvidedSkin.java +++ b/connector/src/main/java/org/geysermc/connector/utils/ProvidedSkin.java @@ -3,7 +3,6 @@ package org.geysermc.connector.utils; import lombok.Getter; import javax.imageio.ImageIO; -import java.awt.Color; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -19,11 +18,11 @@ public class ProvidedSkin { try { for (int y = 0; y < image.getHeight(); y++) { for (int x = 0; x < image.getWidth(); x++) { - Color color = new Color(image.getRGB(x, y), true); - outputStream.write(color.getRed()); - outputStream.write(color.getGreen()); - outputStream.write(color.getBlue()); - outputStream.write(color.getAlpha()); + int rgba = image.getRGB(x, y); + outputStream.write((rgba >> 16) & 0xFF); // Red + outputStream.write((rgba >> 8) & 0xFF); // Green + outputStream.write(rgba & 0xFF); // Blue + outputStream.write((rgba >> 24) & 0xFF); // Alpha } } image.flush(); 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 2d2aceab..b2634e3b 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinProvider.java @@ -22,12 +22,12 @@ public class SkinProvider { public static final boolean ALLOW_THIRD_PARTY_CAPES = ((GeyserConnector)Geyser.getConnector()).getConfig().isAllowThirdPartyCapes(); private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14); - public static final Skin EMPTY_SKIN = new Skin(-1, ""); public static final byte[] STEVE_SKIN = new ProvidedSkin("bedrock/skin/skin_steve.png").getSkin(); + public static final Skin EMPTY_SKIN = new Skin(-1, "steve", STEVE_SKIN); private static Map cachedSkins = new ConcurrentHashMap<>(); private static Map> requestedSkins = new ConcurrentHashMap<>(); - public static final Cape EMPTY_CAPE = new Cape("", new byte[0], -1, true); + public static final Cape EMPTY_CAPE = new Cape("", "no-cape", new byte[0], -1, true); private static Map cachedCapes = new ConcurrentHashMap<>(); private static Map> requestedCapes = new ConcurrentHashMap<>(); @@ -149,8 +149,11 @@ public class SkinProvider { cape = requestImage(capeUrl, true); } catch (Exception ignored) {} // just ignore I guess + String[] urlSection = capeUrl.split("/"); // A real url is expected at this stage + return new Cape( capeUrl, + urlSection[urlSection.length - 1], // get the texture id and use it as cape id cape.length > 0 ? cape : EMPTY_CAPE.getCapeData(), System.currentTimeMillis(), cape.length == 0 @@ -209,13 +212,14 @@ public class SkinProvider { public static class Skin { private UUID skinOwner; private String textureUrl; - private byte[] skinData = STEVE_SKIN; + private byte[] skinData; private long requestedOn; private boolean updated; - private Skin(long requestedOn, String textureUrl) { + private Skin(long requestedOn, String textureUrl, byte[] skinData) { this.requestedOn = requestedOn; this.textureUrl = textureUrl; + this.skinData = skinData; } } @@ -223,6 +227,7 @@ public class SkinProvider { @Getter public static class Cape { private String textureUrl; + private String capeId; private byte[] capeData; private long requestedOn; private boolean failed; 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 988501b8..640ae450 100644 --- a/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java +++ b/connector/src/main/java/org/geysermc/connector/utils/SkinUtils.java @@ -20,6 +20,7 @@ import java.util.function.Consumer; public class SkinUtils { public static PlayerListPacket.Entry buildCachedEntry(GameProfile profile, long geyserId) { GameProfileData data = GameProfileData.from(profile); + SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl()); return buildEntryManually( profile.getId(), @@ -27,7 +28,8 @@ public class SkinUtils { geyserId, profile.getIdAsString(), SkinProvider.getCachedSkin(profile.getId()).getSkinData(), - SkinProvider.getCachedCape(data.getCapeUrl()).getCapeData(), + cape.getCapeId(), + cape.getCapeData(), getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")), "" ); @@ -40,6 +42,7 @@ public class SkinUtils { geyserId, profile.getIdAsString(), SkinProvider.STEVE_SKIN, + SkinProvider.EMPTY_CAPE.getCapeId(), SkinProvider.EMPTY_CAPE.getCapeData(), getLegacySkinGeometry("geometry.humanoid"), "" @@ -47,18 +50,13 @@ public class SkinUtils { } public static PlayerListPacket.Entry buildEntryManually(UUID uuid, String username, long geyserId, - String skinId, byte[] skinData, byte[] capeData, + String skinId, byte[] skinData, + String capeId, byte[] capeData, String geometryName, String geometryData) { - if (skinData == null || skinData.length == 0) { - skinData = SkinProvider.EMPTY_SKIN.getSkinData(); - } - - if (capeData == null || capeData.length == 0) { - capeData = SkinProvider.EMPTY_CAPE.getCapeData(); - } - - SerializedSkin serializedSkin = SerializedSkin.of(skinId, geometryName, ImageData.of(64, 64, skinData), - Collections.emptyList(), ImageData.of(64, 32, capeData), geometryData, "", true, false, false, "", ""); + SerializedSkin serializedSkin = SerializedSkin.of( + skinId, geometryName, ImageData.of(skinData), Collections.emptyList(), + ImageData.of(capeData), geometryData, "", true, false, false, capeId, uuid.toString() + ); PlayerListPacket.Entry entry = new PlayerListPacket.Entry(uuid); entry.setName(username); @@ -107,7 +105,7 @@ public class SkinUtils { public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, Consumer skinAndCapeConsumer) { Geyser.getGeneralThreadPool().execute(() -> { - SkinUtils.GameProfileData data = SkinUtils.GameProfileData.from(entity.getProfile()); + GameProfileData data = GameProfileData.from(entity.getProfile()); SkinProvider.requestSkinAndCape(entity.getUuid(), data.getSkinUrl(), data.getCapeUrl()) .whenCompleteAsync((skinAndCape, throwable) -> { @@ -126,12 +124,13 @@ public class SkinUtils { entity.setLastSkinUpdate(skin.getRequestedOn()); if (session.getUpstream().isInitialized()) { - PlayerListPacket.Entry updatedEntry = SkinUtils.buildEntryManually( + PlayerListPacket.Entry updatedEntry = buildEntryManually( entity.getUuid(), entity.getUsername(), entity.getGeyserId(), entity.getUuid().toString(), skin.getSkinData(), + cape.getCapeId(), cape.getCapeData(), getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")), ""