forked from GeyserMC/Geyser
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:
parent
4c1dae6714
commit
5ae95433e5
4 changed files with 98 additions and 8 deletions
|
@ -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);
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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'
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue