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.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import lombok.AccessLevel;
import lombok.Getter;
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.LevelSoundEvent2Packet;
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.StartGamePacket;
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<>();
/**
* 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.
*/
private final Set<UUID> playerWithCustomHeads = new ObjectOpenHashSet<>();
private final Map<UUID, GameProfile> playerWithCustomHeads = new Object2ObjectOpenHashMap<>();
@Setter
private boolean droppingLecternBook;
@@ -787,6 +787,10 @@ public class GeyserSession implements GeyserConnection, GeyserCommandSource {
playStatusPacket.setStatus(PlayStatusPacket.Status.PLAYER_SPAWN);
upstream.sendPacket(playStatusPacket);
SetCommandsEnabledPacket setCommandsEnabledPacket = new SetCommandsEnabledPacket();
setCommandsEnabledPacket.setCommandsEnabled(!geyser.getConfig().isXboxAchievementsEnabled());
upstream.sendPacket(setCommandsEnabledPacket);
UpdateAttributesPacket attributesPacket = new UpdateAttributesPacket();
attributesPacket.setRuntimeEntityId(getPlayerEntity().getGeyserId());
// Default move speed

View File

@@ -109,6 +109,12 @@ public class FakeHeadProvider {
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;
try {
textures = profile.getTextures(false);
@@ -118,7 +124,7 @@ public class FakeHeadProvider {
}
if (textures == null || textures.isEmpty()) {
loadHead(session, entity, profile.getName());
loadHeadFromProfile(session, entity, profile);
return;
}
@@ -133,16 +139,18 @@ public class FakeHeadProvider {
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) {
if (owner == null || owner.isEmpty()) {
return;
public static void loadHeadFromProfile(GeyserSession session, PlayerEntity entity, GameProfile profile) {
CompletableFuture<String> texturesFuture;
if (profile.getId() != null) {
texturesFuture = SkinProvider.requestTexturesFromUUID(profile.getId().toString());
} else {
texturesFuture = SkinProvider.requestTexturesFromUsername(profile.getName());
}
CompletableFuture<String> completableFuture = SkinProvider.requestTexturesFromUsername(owner);
completableFuture.whenCompleteAsync((encodedJson, throwable) -> {
texturesFuture.whenCompleteAsync((encodedJson, throwable) -> {
if (throwable != null) {
GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable);
return;
@@ -152,17 +160,17 @@ public class FakeHeadProvider {
if (gameProfileData == null) {
return;
}
loadHead(session, entity, gameProfileData);
loadHeadFromProfile(session, entity, gameProfileData, profile);
} catch (IOException e) {
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();
session.getPlayerWithCustomHeads().add(entity.getUuid());
session.getPlayerWithCustomHeads().put(entity.getUuid(), profile);
String texturesProperty = entity.getTexturesProperty();
SkinProvider.getExecutorService().execute(() -> {
try {
@@ -179,7 +187,7 @@ public class FakeHeadProvider {
return;
}
if (!session.getPlayerWithCustomHeads().remove(entity.getUuid())) {
if (session.getPlayerWithCustomHeads().remove(entity.getUuid()) == null) {
return;
}

View File

@@ -47,7 +47,6 @@ import org.geysermc.geyser.text.GeyserLocale;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
@@ -102,7 +101,7 @@ public class SkinManager {
Skin skin,
Cape cape,
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
String xuid = "";
@@ -138,7 +137,6 @@ public class SkinManager {
SkinGeometry geometry = skinData.geometry();
if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) {
// TODO is this special behavior needed?
PlayerListPacket.Entry updatedEntry = buildEntryManually(
session,
entity.getUuid(),
@@ -158,17 +156,24 @@ public class SkinManager {
packet.setUuid(entity.getUuid());
packet.setOldSkinName("");
packet.setNewSkinName(skin.textureUrl());
packet.setSkin(getSkin(skin.textureUrl(), skin, cape, geometry));
packet.setSkin(getSkin(session, skin.textureUrl(), skin, cape, geometry));
packet.setTrustedSkin(true);
session.sendUpstreamPacket(packet);
}
}
private static SerializedSkin getSkin(String skinId, Skin skin, Cape cape, SkinGeometry geometry) {
return SerializedSkin.of(skinId, "", geometry.geometryName(),
ImageData.of(skin.skinData()), Collections.emptyList(),
ImageData.of(cape.capeData()), geometry.geometryData(),
"", true, false, false, cape.capeId(), skinId);
private static SerializedSkin getSkin(GeyserSession session, String skinId, Skin skin, Cape cape, SkinGeometry geometry) {
return SerializedSkin.builder()
.skinId(skinId)
.skinResourcePatch(geometry.geometryName())
.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,
@@ -334,4 +339,4 @@ public class SkinManager {
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.text.GeyserLocale;
import java.util.Collections;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class SkullSkinManager extends SkinManager {
public static SerializedSkin buildSkullEntryManually(String skinId, byte[] skinData) {
// Prevents https://cdn.discordapp.com/attachments/613194828359925800/779458146191147008/unknown.png
public static SerializedSkin buildSkullEntryManually(GeyserSession session, String skinId, byte[] skinData) {
skinId = skinId + "_skull";
return SerializedSkin.of(
skinId, "", SkinProvider.SKULL_GEOMETRY.geometryName(), ImageData.of(skinData), Collections.emptyList(),
ImageData.of(SkinProvider.EMPTY_CAPE.capeData()), SkinProvider.SKULL_GEOMETRY.geometryData(),
"", true, false, false, SkinProvider.EMPTY_CAPE.capeId(), skinId
);
return SerializedSkin.builder()
.skinId(skinId)
.skinResourcePatch(SkinProvider.SKULL_GEOMETRY.geometryName())
.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,
@@ -59,7 +63,7 @@ public class SkullSkinManager extends SkinManager {
packet.setUuid(entity.getUuid());
packet.setOldSkinName("");
packet.setNewSkinName(skin.textureUrl());
packet.setSkin(buildSkullEntryManually(skin.textureUrl(), skin.skinData()));
packet.setSkin(buildSkullEntryManually(session, skin.textureUrl(), skin.skinData()));
packet.setTrustedSkin(true);
session.sendUpstreamPacket(packet);
} catch (Exception e) {

View File

@@ -76,7 +76,6 @@ import org.geysermc.geyser.util.InventoryUtils;
import org.geysermc.geyser.util.ItemUtils;
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.recipe.display.slot.EmptySlotDisplay;
import org.geysermc.mcprotocollib.protocol.data.game.recipe.display.slot.SlotDisplay;
@@ -258,14 +257,14 @@ public abstract class InventoryTranslator {
if (this instanceof PlayerInventoryTranslator) {
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);
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
// we are probably removing the head, so restore the original skin
FakeHeadProvider.restoreOriginalSkin(session, session.getPlayerEntity());
}
}

View File

@@ -279,9 +279,22 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
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();
if (isCursor(transferAction.getDestination())) {
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
if (playerInv.getCursor().isEmpty()) {
playerInv.setCursor(sourceItem.copy(0), session);
@@ -294,7 +307,6 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
affectedSlots.add(sourceSlot);
} else if (isCursor(transferAction.getSource())) {
int destSlot = bedrockSlotToJava(transferAction.getDestination());
GeyserItemStack sourceItem = playerInv.getCursor();
if (inventory.getItem(destSlot).isEmpty()) {
inventory.setItem(destSlot, sourceItem.copy(0), session);
@@ -307,8 +319,6 @@ public class PlayerInventoryTranslator extends InventoryTranslator {
affectedSlots.add(destSlot);
} else {
int sourceSlot = bedrockSlotToJava(transferAction.getSource());
int destSlot = bedrockSlotToJava(transferAction.getDestination());
GeyserItemStack sourceItem = inventory.getItem(sourceSlot);
if (inventory.getItem(destSlot).isEmpty()) {
inventory.setItem(destSlot, sourceItem.copy(0), session);