Bedrock to Bedrock legacy skin support (#276)

* Added legacy skin support for bedrock to bedrock clients

* Added bedrock to bedrock cape handling

* Added bedrock geometry support

* Bedrock skins now work in all auth modes

* Tonne of debug info

* Added fix to prevent customised skins from being loaded

* Added skin size to bedrock client data

* Cleaned debugging code

* Made bedrock cape take priority over third party

* Cut the customised skin image in half to hopefully get it to map

* Removed hacky conversion attempt

* Fixed bedrock skin caching on load and 1.14.60 support

* Cleaned up debug messages

* Added linked player ignore
This commit is contained in:
rtm516 2020-05-06 22:50:01 +01:00 committed by GitHub
parent 4c1dae6714
commit 5ae95433e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 98 additions and 8 deletions

View file

@ -31,10 +31,10 @@ import com.github.steveice10.mc.auth.exception.request.RequestException;
import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.SubProtocol;
import com.github.steveice10.mc.protocol.data.game.entity.player.GameMode;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
import com.github.steveice10.mc.protocol.data.game.world.block.BlockState;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket;
import com.github.steveice10.mc.protocol.packet.handshake.client.HandshakePacket;
import com.github.steveice10.mc.protocol.packet.ingame.client.world.ClientTeleportConfirmPacket;
import com.github.steveice10.mc.protocol.packet.ingame.server.ServerRespawnPacket;
import com.github.steveice10.mc.protocol.packet.login.server.LoginSuccessPacket;
import com.github.steveice10.packetlib.Client;
import com.github.steveice10.packetlib.event.session.*;
@ -68,6 +68,7 @@ import org.geysermc.connector.network.translators.Registry;
import org.geysermc.connector.network.translators.world.block.BlockTranslator;
import org.geysermc.connector.utils.ChunkUtils;
import org.geysermc.connector.utils.LocaleUtils;
import org.geysermc.connector.utils.SkinUtils;
import org.geysermc.connector.utils.Toolbox;
import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.EncryptionUtil;
@ -338,6 +339,11 @@ public class GeyserSession implements CommandSender {
GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
playerEntity.setUsername(profile.getName());
playerEntity.setUuid(profile.getId());
// Check if they are not using a linked account
if (!playerEntity.getUuid().toString().startsWith("00000000-0000-0000")) {
SkinUtils.handleBedrockSkin(playerEntity, clientData);
}
}
Registry.JAVA.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);

View file

@ -24,6 +24,10 @@ public class BedrockClientData {
private String skinId;
@JsonProperty(value = "SkinData")
private String skinData;
@JsonProperty(value = "SkinImageHeight")
private int skinImageHeight;
@JsonProperty(value = "SkinImageWidth")
private int skinImageWidth;
@JsonProperty(value = "CapeId")
private String capeId;
@JsonProperty(value = "CapeData")

View file

@ -58,6 +58,9 @@ public class SkinProvider {
private static Map<String, Cape> cachedCapes = new ConcurrentHashMap<>();
private static Map<String, CompletableFuture<Cape>> requestedCapes = new ConcurrentHashMap<>();
public static final SkinGeometry EMPTY_GEOMETRY = SkinProvider.SkinGeometry.getLegacy("geometry.humanoid");
private static Map<UUID, SkinGeometry> cachedGeometry = new ConcurrentHashMap<>();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final int CACHE_INTERVAL = 8 * 60 * 1000; // 8 minutes
@ -164,6 +167,31 @@ public class SkinProvider {
return CompletableFuture.completedFuture(officialCape);
}
public static CompletableFuture<Cape> requestBedrockCape(UUID playerID, boolean newThread) {
Cape bedrockCape = cachedCapes.getOrDefault(playerID.toString() + ".Bedrock", EMPTY_CAPE);
return CompletableFuture.completedFuture(bedrockCape);
}
public static CompletableFuture<SkinGeometry> requestBedrockGeometry(SkinGeometry currentGeometry, UUID playerID, boolean newThread) {
SkinGeometry bedrockGeometry = cachedGeometry.getOrDefault(playerID, currentGeometry);
return CompletableFuture.completedFuture(bedrockGeometry);
}
public static void storeBedrockSkin(UUID playerID, String skinID, byte[] skinData) {
Skin skin = new Skin(playerID, skinID, skinData, System.currentTimeMillis(), true);
cachedSkins.put(playerID, skin);
}
public static void storeBedrockCape(UUID playerID, byte[] capeData) {
Cape cape = new Cape(playerID.toString() + ".Bedrock", playerID.toString(), capeData, System.currentTimeMillis(), false);
cachedCapes.put(playerID.toString() + ".Bedrock", cape);
}
public static void storeBedrockGeometry(UUID playerID, byte[] geometryName, byte[] geometryData) {
SkinGeometry geometry = new SkinGeometry(new String(geometryName), new String(geometryData));
cachedGeometry.put(playerID, geometry);
}
private static Skin supplySkin(UUID uuid, String textureUrl) {
byte[] skin = EMPTY_SKIN.getSkinData();
try {
@ -285,6 +313,17 @@ public class SkinProvider {
private boolean failed;
}
@AllArgsConstructor
@Getter
public static class SkinGeometry {
private String geometryName;
private String geometryData;
public static SkinGeometry getLegacy(String name) {
return new SkinProvider.SkinGeometry("{\"geometry\" :{\"default\" :\"" + name + "\"}}", "");
}
}
/*
* Sorted by 'priority'
*/

View file

@ -39,6 +39,7 @@ import org.geysermc.common.AuthType;
import org.geysermc.connector.GeyserConnector;
import org.geysermc.connector.entity.PlayerEntity;
import org.geysermc.connector.network.session.GeyserSession;
import org.geysermc.connector.network.session.auth.BedrockClientData;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@ -52,6 +53,8 @@ public class SkinUtils {
GameProfileData data = GameProfileData.from(profile);
SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl());
SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : ""));
return buildEntryManually(
profile.getId(),
profile.getName(),
@ -60,8 +63,8 @@ public class SkinUtils {
SkinProvider.getCachedSkin(profile.getId()).getSkinData(),
cape.getCapeId(),
cape.getCapeData(),
getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")),
""
geometry.getGeometryName(),
geometry.getGeometryData()
);
}
@ -74,8 +77,8 @@ public class SkinUtils {
SkinProvider.STEVE_SKIN,
SkinProvider.EMPTY_CAPE.getCapeId(),
SkinProvider.EMPTY_CAPE.getCapeData(),
getLegacySkinGeometry("geometry.humanoid"),
""
SkinProvider.EMPTY_GEOMETRY.getGeometryName(),
SkinProvider.EMPTY_GEOMETRY.getGeometryData()
);
}
@ -151,6 +154,12 @@ public class SkinUtils {
SkinProvider.Skin skin = skinAndCape.getSkin();
SkinProvider.Cape cape = skinAndCape.getCape();
if (cape.isFailed()) {
cape = SkinProvider.getOrDefault(SkinProvider.requestBedrockCape(
entity.getUuid(), false
), SkinProvider.EMPTY_CAPE, 3);
}
if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) {
cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape(
cape, entity.getUuid(),
@ -158,6 +167,11 @@ public class SkinUtils {
), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3);
}
SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : ""));
geometry = SkinProvider.getOrDefault(SkinProvider.requestBedrockGeometry(
geometry, entity.getUuid(), false
), geometry, 3);
if (entity.getLastSkinUpdate() < skin.getRequestedOn()) {
entity.setLastSkinUpdate(skin.getRequestedOn());
@ -170,8 +184,8 @@ public class SkinUtils {
skin.getSkinData(),
cape.getCapeId(),
cape.getCapeData(),
getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")),
""
geometry.getGeometryName(),
geometry.getGeometryData()
);
PlayerListPacket playerRemovePacket = new PlayerListPacket();
@ -194,6 +208,33 @@ public class SkinUtils {
});
}
public static void handleBedrockSkin(PlayerEntity playerEntity, BedrockClientData clientData) {
GameProfileData data = GameProfileData.from(playerEntity.getProfile());
GeyserConnector.getInstance().getLogger().info("Registering bedrock skin for " + playerEntity.getUsername() + " (" + playerEntity.getUuid() + ")");
try {
byte[] skinBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getSkinData().getBytes("UTF-8"));
byte[] capeBytes = clientData.getCapeData();
byte[] geometryNameBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getGeometryName().getBytes("UTF-8"));
byte[] geometryBytes = com.github.steveice10.mc.auth.util.Base64.decode(clientData.getGeometryData().getBytes("UTF-8"));
if (skinBytes.length <= (128 * 128 * 4)) {
SkinProvider.storeBedrockSkin(playerEntity.getUuid(), data.getSkinUrl(), skinBytes);
} else {
GeyserConnector.getInstance().getLogger().info("Unable to load bedrock skin for '" + playerEntity.getUsername() + "' as they are using a customised skin");
GeyserConnector.getInstance().getLogger().debug("The size of '" + playerEntity.getUsername() + "' skin is: " + clientData.getSkinImageWidth() + "x" + clientData.getSkinImageHeight());
}
SkinProvider.storeBedrockGeometry(playerEntity.getUuid(), geometryNameBytes, geometryBytes);
if (!clientData.getCapeId().equals("")) {
SkinProvider.storeBedrockCape(playerEntity.getUuid(), capeBytes);
}
} catch (Exception e) {
throw new AssertionError("Failed to cache skin for bedrock user (" + playerEntity.getUsername() + "): ", e);
}
}
/**
* Create a basic geometry json for the given name
*