Mirror von
https://github.com/GeyserMC/Geyser.git
synchronisiert 2024-12-27 08:30:12 +01:00
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
Dieser Commit ist enthalten in:
Ursprung
4c1dae6714
Commit
5ae95433e5
@ -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.MinecraftProtocol;
|
||||||
import com.github.steveice10.mc.protocol.data.SubProtocol;
|
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.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.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.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.mc.protocol.packet.login.server.LoginSuccessPacket;
|
||||||
import com.github.steveice10.packetlib.Client;
|
import com.github.steveice10.packetlib.Client;
|
||||||
import com.github.steveice10.packetlib.event.session.*;
|
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.network.translators.world.block.BlockTranslator;
|
||||||
import org.geysermc.connector.utils.ChunkUtils;
|
import org.geysermc.connector.utils.ChunkUtils;
|
||||||
import org.geysermc.connector.utils.LocaleUtils;
|
import org.geysermc.connector.utils.LocaleUtils;
|
||||||
|
import org.geysermc.connector.utils.SkinUtils;
|
||||||
import org.geysermc.connector.utils.Toolbox;
|
import org.geysermc.connector.utils.Toolbox;
|
||||||
import org.geysermc.floodgate.util.BedrockData;
|
import org.geysermc.floodgate.util.BedrockData;
|
||||||
import org.geysermc.floodgate.util.EncryptionUtil;
|
import org.geysermc.floodgate.util.EncryptionUtil;
|
||||||
@ -338,6 +339,11 @@ public class GeyserSession implements CommandSender {
|
|||||||
GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
|
GameProfile profile = ((LoginSuccessPacket) event.getPacket()).getProfile();
|
||||||
playerEntity.setUsername(profile.getName());
|
playerEntity.setUsername(profile.getName());
|
||||||
playerEntity.setUuid(profile.getId());
|
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);
|
Registry.JAVA.translate(event.getPacket().getClass(), event.getPacket(), GeyserSession.this);
|
||||||
|
@ -24,6 +24,10 @@ public class BedrockClientData {
|
|||||||
private String skinId;
|
private String skinId;
|
||||||
@JsonProperty(value = "SkinData")
|
@JsonProperty(value = "SkinData")
|
||||||
private String skinData;
|
private String skinData;
|
||||||
|
@JsonProperty(value = "SkinImageHeight")
|
||||||
|
private int skinImageHeight;
|
||||||
|
@JsonProperty(value = "SkinImageWidth")
|
||||||
|
private int skinImageWidth;
|
||||||
@JsonProperty(value = "CapeId")
|
@JsonProperty(value = "CapeId")
|
||||||
private String capeId;
|
private String capeId;
|
||||||
@JsonProperty(value = "CapeData")
|
@JsonProperty(value = "CapeData")
|
||||||
|
@ -58,6 +58,9 @@ 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<>();
|
||||||
|
|
||||||
|
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 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
|
||||||
|
|
||||||
@ -164,6 +167,31 @@ public class SkinProvider {
|
|||||||
return CompletableFuture.completedFuture(officialCape);
|
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) {
|
private static Skin supplySkin(UUID uuid, String textureUrl) {
|
||||||
byte[] skin = EMPTY_SKIN.getSkinData();
|
byte[] skin = EMPTY_SKIN.getSkinData();
|
||||||
try {
|
try {
|
||||||
@ -285,6 +313,17 @@ public class SkinProvider {
|
|||||||
private boolean failed;
|
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'
|
* Sorted by 'priority'
|
||||||
*/
|
*/
|
||||||
|
@ -39,6 +39,7 @@ import org.geysermc.common.AuthType;
|
|||||||
import org.geysermc.connector.GeyserConnector;
|
import org.geysermc.connector.GeyserConnector;
|
||||||
import org.geysermc.connector.entity.PlayerEntity;
|
import org.geysermc.connector.entity.PlayerEntity;
|
||||||
import org.geysermc.connector.network.session.GeyserSession;
|
import org.geysermc.connector.network.session.GeyserSession;
|
||||||
|
import org.geysermc.connector.network.session.auth.BedrockClientData;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
@ -52,6 +53,8 @@ public class SkinUtils {
|
|||||||
GameProfileData data = GameProfileData.from(profile);
|
GameProfileData data = GameProfileData.from(profile);
|
||||||
SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl());
|
SkinProvider.Cape cape = SkinProvider.getCachedCape(data.getCapeUrl());
|
||||||
|
|
||||||
|
SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.getLegacy("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : ""));
|
||||||
|
|
||||||
return buildEntryManually(
|
return buildEntryManually(
|
||||||
profile.getId(),
|
profile.getId(),
|
||||||
profile.getName(),
|
profile.getName(),
|
||||||
@ -60,8 +63,8 @@ public class SkinUtils {
|
|||||||
SkinProvider.getCachedSkin(profile.getId()).getSkinData(),
|
SkinProvider.getCachedSkin(profile.getId()).getSkinData(),
|
||||||
cape.getCapeId(),
|
cape.getCapeId(),
|
||||||
cape.getCapeData(),
|
cape.getCapeData(),
|
||||||
getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")),
|
geometry.getGeometryName(),
|
||||||
""
|
geometry.getGeometryData()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,8 +77,8 @@ public class SkinUtils {
|
|||||||
SkinProvider.STEVE_SKIN,
|
SkinProvider.STEVE_SKIN,
|
||||||
SkinProvider.EMPTY_CAPE.getCapeId(),
|
SkinProvider.EMPTY_CAPE.getCapeId(),
|
||||||
SkinProvider.EMPTY_CAPE.getCapeData(),
|
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.Skin skin = skinAndCape.getSkin();
|
||||||
SkinProvider.Cape cape = skinAndCape.getCape();
|
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) {
|
if (cape.isFailed() && SkinProvider.ALLOW_THIRD_PARTY_CAPES) {
|
||||||
cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape(
|
cape = SkinProvider.getOrDefault(SkinProvider.requestUnofficialCape(
|
||||||
cape, entity.getUuid(),
|
cape, entity.getUuid(),
|
||||||
@ -158,6 +167,11 @@ public class SkinUtils {
|
|||||||
), SkinProvider.EMPTY_CAPE, SkinProvider.CapeProvider.VALUES.length * 3);
|
), 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()) {
|
if (entity.getLastSkinUpdate() < skin.getRequestedOn()) {
|
||||||
entity.setLastSkinUpdate(skin.getRequestedOn());
|
entity.setLastSkinUpdate(skin.getRequestedOn());
|
||||||
|
|
||||||
@ -170,8 +184,8 @@ public class SkinUtils {
|
|||||||
skin.getSkinData(),
|
skin.getSkinData(),
|
||||||
cape.getCapeId(),
|
cape.getCapeId(),
|
||||||
cape.getCapeData(),
|
cape.getCapeData(),
|
||||||
getLegacySkinGeometry("geometry.humanoid.custom" + (data.isAlex() ? "Slim" : "")),
|
geometry.getGeometryName(),
|
||||||
""
|
geometry.getGeometryData()
|
||||||
);
|
);
|
||||||
|
|
||||||
PlayerListPacket playerRemovePacket = new PlayerListPacket();
|
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
|
* Create a basic geometry json for the given name
|
||||||
*
|
*
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren