1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-19 14:59:27 +00:00

Fix skins not showing on 1.21.60

This commit is contained in:
onebeastchris
2025-02-20 17:01:39 +01:00
parent ebaaed7542
commit fd4c80598e
6 changed files with 70 additions and 40 deletions

View File

@@ -35,7 +35,6 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@@ -94,6 +93,7 @@ import org.cloudburstmc.protocol.bedrock.packet.ItemComponentPacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket; import org.cloudburstmc.protocol.bedrock.packet.LevelEventPacket;
import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet; import org.cloudburstmc.protocol.bedrock.packet.LevelSoundEvent2Packet;
import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayStatusPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetCommandsEnabledPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket; import org.cloudburstmc.protocol.bedrock.packet.SetTimePacket;
import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket; import org.cloudburstmc.protocol.bedrock.packet.StartGamePacket;
import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket; import org.cloudburstmc.protocol.bedrock.packet.SyncEntityPropertyPacket;
@@ -331,10 +331,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
private final Map<Vector3i, ItemFrameEntity> itemFrameCache = new Object2ObjectOpenHashMap<>(); private final Map<Vector3i, ItemFrameEntity> itemFrameCache = new Object2ObjectOpenHashMap<>();
/** /**
* A list of all players that have a player head on with a custom texture. * A map of all players (and their heads) that are wearing a player head with a custom texture.
* Our workaround for these players is to give them a custom skin and geometry to emulate wearing a custom skull. * Our workaround for these players is to give them a custom skin and geometry to emulate wearing a custom skull.
*/ */
private final Set<UUID> playerWithCustomHeads = new ObjectOpenHashSet<>(); private final Map<UUID, GameProfile> playerWithCustomHeads = new Object2ObjectOpenHashMap<>();
@Setter @Setter
private boolean droppingLecternBook; private boolean droppingLecternBook;
@@ -787,6 +787,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN); playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
upstream.sendPacket(playStatusPacket); upstream.sendPacket(playStatusPacket);
SetCommandsEnabledPacket setCommandsEnabledPacket = new SetCommandsEnabledPacket();
setCommandsEnabledPacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled());
upstream.sendPacket(setCommandsEnabledPacket);
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket(); UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(getPlayerEntity().getGeyserId()); attributesPacket.setRuntimeEntityId(getPlayerEntity().getGeyserId());
// Default move speed // Default move speed

View File

@@ -109,6 +109,12 @@ public class FakeHeadProvider {
return; return;
} }
GameProfile current = session.getPlayerWithCustomHeads().get(entity.getUuid());
if (profile.equals(current)) {
// We already did this, no need to re-compute
return;
}
Map<TextureType, Texture> textures; Map<TextureType, Texture> textures;
try { try {
textures = profile.getTextures(false); textures = profile.getTextures(false);
@@ -118,7 +124,7 @@ public class FakeHeadProvider {
} }
if (textures == null || textures.isEmpty()) { if (textures == null || textures.isEmpty()) {
loadHead(session, entity, profile.getName()); loadHeadFromProfile(session, entity, profile);
return; return;
} }
@@ -133,16 +139,18 @@ public class FakeHeadProvider {
boolean isAlex = skinTexture.getModel() == TextureModel.SLIM; boolean isAlex = skinTexture.getModel() == TextureModel.SLIM;
loadHead(session, entity, new GameProfileData(skinTexture.getURL(), capeUrl, isAlex)); loadHeadFromProfile(session, entity, new GameProfileData(skinTexture.getURL(), capeUrl, isAlex), profile);
} }
public static void loadHead(GeyserSession session, PlayerEntity entity, String owner) { public static void loadHeadFromProfile(GeyserSession session, PlayerEntity entity, GameProfile profile) {
if (owner == null || owner.isEmpty()) { CompletableFuture<String> texturesFuture;
return; if (profile.getId() != null) {
texturesFuture = SkinProvider.requestTexturesFromUUID(profile.getId().toString());
} else {
texturesFuture = SkinProvider.requestTexturesFromUsername(profile.getName());
} }
CompletableFuture<String> completableFuture = SkinProvider.requestTexturesFromUsername(owner); texturesFuture.whenCompleteAsync((encodedJson, throwable) -> {
completableFuture.whenCompleteAsync((encodedJson, throwable) -> {
if (throwable != null) { if (throwable != null) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable); GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable);
return; return;
@@ -152,17 +160,17 @@ public class FakeHeadProvider {
if (gameProfileData == null) { if (gameProfileData == null) {
return; return;
} }
loadHead(session, entity, gameProfileData); loadHeadFromProfile(session, entity, gameProfileData, profile);
} catch (IOException e) { } catch (IOException e) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid(), e.getMessage())); GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid(), e.getMessage()));
} }
}); });
} }
public static void loadHead(GeyserSession session, PlayerEntity entity, SkinManager.GameProfileData gameProfileData) { public static void loadHeadFromProfile(GeyserSession session, PlayerEntity entity, SkinManager.GameProfileData gameProfileData, GameProfile profile) {
String fakeHeadSkinUrl = gameProfileData.skinUrl(); String fakeHeadSkinUrl = gameProfileData.skinUrl();
session.getPlayerWithCustomHeads().add(entity.getUuid()); session.getPlayerWithCustomHeads().put(entity.getUuid(), profile);
String texturesProperty = entity.getTexturesProperty(); String texturesProperty = entity.getTexturesProperty();
SkinProvider.getExecutorService().execute(() -> { SkinProvider.getExecutorService().execute(() -> {
try { try {
@@ -179,7 +187,7 @@ public class FakeHeadProvider {
return; return;
} }
if (!session.getPlayerWithCustomHeads().remove(entity.getUuid())) { if (session.getPlayerWithCustomHeads().remove(entity.getUuid()) == null) {
return; return;
} }

View File

@@ -47,7 +47,6 @@ import org.geysermc.geyser.text.GeyserLocale;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -102,7 +101,7 @@ public class SkinManager {
Skin skin, Skin skin,
Cape cape, Cape cape,
SkinGeometry geometry) { SkinGeometry geometry) {
SerializedSkin serializedSkin = getSkin(skin.textureUrl(), skin, cape, geometry); SerializedSkin serializedSkin = getSkin(session, skin.textureUrl(), skin, cape, geometry);
// This attempts to find the XUID of the player so profile images show up for Xbox accounts // This attempts to find the XUID of the player so profile images show up for Xbox accounts
String xuid = ""; String xuid = "";
@@ -138,7 +137,6 @@ public class SkinManager {
SkinGeometry geometry = skinData.geometry(); SkinGeometry geometry = skinData.geometry();
if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) { if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) {
// TODO is this special behavior needed?
PlayerListPacket.Entry updatedEntry = buildEntryManually( PlayerListPacket.Entry updatedEntry = buildEntryManually(
session, session,
entity.getUuid(), entity.getUuid(),
@@ -158,17 +156,24 @@ public class SkinManager {
packet.setUuid(entity.getUuid()); packet.setUuid(entity.getUuid());
packet.setOldSkinName(""); packet.setOldSkinName("");
packet.setNewSkinName(skin.textureUrl()); packet.setNewSkinName(skin.textureUrl());
packet.setSkin(getSkin(skin.textureUrl(), skin, cape, geometry)); packet.setSkin(getSkin(session, skin.textureUrl(), skin, cape, geometry));
packet.setTrustedSkin(true); packet.setTrustedSkin(true);
session.sendUpstreamPacket(packet); session.sendUpstreamPacket(packet);
} }
} }
private static SerializedSkin getSkin(String skinId, Skin skin, Cape cape, SkinGeometry geometry) { private static SerializedSkin getSkin(GeyserSession session, String skinId, Skin skin, Cape cape, SkinGeometry geometry) {
return SerializedSkin.of(skinId, "", geometry.geometryName(), return SerializedSkin.builder()
ImageData.of(skin.skinData()), Collections.emptyList(), .skinId(skinId)
ImageData.of(cape.capeData()), geometry.geometryData(), .skinResourcePatch(geometry.geometryName())
"", true, false, false, cape.capeId(), skinId); .skinData(ImageData.of(skin.skinData()))
.capeData(ImageData.of(cape.capeData()))
.geometryData(geometry.geometryData())
.premium(true)
.capeId(cape.capeId())
.fullSkinId(skinId)
.geometryDataEngineVersion(session.getClientData().getGameVersion())
.build();
} }
public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session,
@@ -334,4 +339,4 @@ public class SkinManager {
private static final String DEFAULT_FLOODGATE_STEVE = "https://textures.minecraft.net/texture/31f477eb1a7beee631c2ca64d06f8f68fa93a3386d04452ab27f43acdf1b60cb"; private static final String DEFAULT_FLOODGATE_STEVE = "https://textures.minecraft.net/texture/31f477eb1a7beee631c2ca64d06f8f68fa93a3386d04452ab27f43acdf1b60cb";
} }
} }

View File

@@ -35,20 +35,24 @@ import org.geysermc.geyser.entity.type.player.SkullPlayerEntity;
import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.text.GeyserLocale; import org.geysermc.geyser.text.GeyserLocale;
import java.util.Collections;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
public class SkullSkinManager extends SkinManager { public class SkullSkinManager extends SkinManager {
public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinData) { public static SerializedSkin buildSkullEntryManually(GeyserSession session, String skinId, byte[] skinData) {
// Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png
skinId = skinId + "_skull"; skinId = skinId + "_skull";
return SerializedSkin.of( return SerializedSkin.builder()
skinId, "", SkinProvider.SKULL_GEOMETRY.geometryName(), ImageData.of(skinData), Collections.emptyList(), .skinId(skinId)
ImageData.of(SkinProvider.EMPTY_CAPE.capeData()), SkinProvider.SKULL_GEOMETRY.geometryData(), .skinResourcePatch(SkinProvider.SKULL_GEOMETRY.geometryName())
"", true, false, false, SkinProvider.EMPTY_CAPE.capeId(), skinId .skinData(ImageData.of(skinData))
); .capeData(ImageData.of(SkinProvider.EMPTY_CAPE.capeData()))
.geometryData(SkinProvider.SKULL_GEOMETRY.geometryData())
.premium(true)
.capeId(SkinProvider.EMPTY_CAPE.capeId())
.fullSkinId(skinId)
.geometryDataEngineVersion(session.getClientData().getGameVersion())
.build();
} }
public static void requestAndHandleSkin(SkullPlayerEntity entity, GeyserSession session, public static void requestAndHandleSkin(SkullPlayerEntity entity, GeyserSession session,
@@ -59,7 +63,7 @@ public class SkullSkinManager extends SkinManager {
packet.setUuid(entity.getUuid()); packet.setUuid(entity.getUuid());
packet.setOldSkinName(""); packet.setOldSkinName("");
packet.setNewSkinName(skin.textureUrl()); packet.setNewSkinName(skin.textureUrl());
packet.setSkin(buildSkullEntryManually(skin.textureUrl(), skin.skinData())); packet.setSkin(buildSkullEntryManually(session, skin.textureUrl(), skin.skinData()));
packet.setTrustedSkin(true); packet.setTrustedSkin(true);
session.sendUpstreamPacket(packet); session.sendUpstreamPacket(packet);
} catch (Exception e) { } catch (Exception e) {

View File

@@ -76,7 +76,6 @@ import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.geyser.util.ItemUtils; import org.geysermc.geyser.util.ItemUtils;
import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType; import org.geysermc.mcprotocollib.protocol.data.game.inventory.ContainerType;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes; import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.data.game.item.component.DataComponentTypes;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay; import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.EmptySlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay; import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
@@ -258,14 +257,14 @@ public abstract class InventoryTranslator {
if (this instanceof PlayerInventoryTranslator) { if (this instanceof PlayerInventoryTranslator) {
if (destSlot == 5) { if (destSlot == 5) {
//only set the head if the destination is the head slot // only set the head if the destination is the head slot
GeyserItemStack javaItem = inventory.getItem(sourceSlot); GeyserItemStack javaItem = inventory.getItem(sourceSlot);
if (javaItem.asItem() == Items.PLAYER_HEAD if (javaItem.asItem() == Items.PLAYER_HEAD
&& javaItem.hasNonBaseComponents()) { && javaItem.hasNonBaseComponents()) {
FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE)); FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE));
} }
} else if (sourceSlot == 5) { } else if (sourceSlot == 5) {
//we are probably removing the head, so restore the original skin // we are probably removing the head, so restore the original skin
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity()); FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
} }
} }

View File

@@ -279,9 +279,22 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
return bundleResponse; return bundleResponse;
} }
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
int destSlot = bedrockSlotToJava(transferAction.getDestination());
if (destSlot == 5) {
// only set the head if the destination is the head slot
GeyserItemStack javaItem = inventory.getItem(sourceSlot);
if (javaItem.asItem() == Items.PLAYER_HEAD
&& javaItem.hasNonBaseComponents()) {
FakeHeadProvider.setHead(session, session.getPlayerEntity(), javaItem.getComponent(DataComponentTypes.PROFILE));
}
} else if (sourceSlot == 5) {
// we are probably removing the head, so restore the original skin
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
}
int transferAmount = transferAction.getCount(); int transferAmount = transferAction.getCount();
if (isCursor(transferAction.getDestination())) { if (isCursor(transferAction.getDestination())) {
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
GeyserItemStack sourceItem = inventory.getItem(sourceSlot); GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
if (playerInv.getCursor().isEmpty()) { if (playerInv.getCursor().isEmpty()) {
playerInv.setCursor(sourceItem.copy(0), session); playerInv.setCursor(sourceItem.copy(0), session);
@@ -294,7 +307,6 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
affectedSlots.add(sourceSlot); affectedSlots.add(sourceSlot);
} else if (isCursor(transferAction.getSource())) { } else if (isCursor(transferAction.getSource())) {
int destSlot = bedrockSlotToJava(transferAction.getDestination());
GeyserItemStack sourceItem = playerInv.getCursor(); GeyserItemStack sourceItem = playerInv.getCursor();
if (inventory.getItem(destSlot).isEmpty()) { if (inventory.getItem(destSlot).isEmpty()) {
inventory.setItem(destSlot, sourceItem.copy(0), session); inventory.setItem(destSlot, sourceItem.copy(0), session);
@@ -307,8 +319,6 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
affectedSlots.add(destSlot); affectedSlots.add(destSlot);
} else { } else {
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
int destSlot = bedrockSlotToJava(transferAction.getDestination());
GeyserItemStack sourceItem = inventory.getItem(sourceSlot); GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
if (inventory.getItem(destSlot).isEmpty()) { if (inventory.getItem(destSlot).isEmpty()) {
inventory.setItem(destSlot, sourceItem.copy(0), session); inventory.setItem(destSlot, sourceItem.copy(0), session);