From 2141c16d716f8549f47c6412523b0efd025cc8ce Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sun, 17 Mar 2024 16:27:42 +0000 Subject: [PATCH 01/15] Remove ears and unoffical skin sources --- .../geysermc/geyser/skin/SkinProvider.java | 201 +----------------- 1 file changed, 8 insertions(+), 193 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 12a1e8b2b..9145dac75 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -88,9 +88,6 @@ public class SkinProvider { */ private static final Predicate IS_NPC = uuid -> uuid.version() == 2; - private static final boolean ALLOW_THIRD_PARTY_EARS = GeyserImpl.getInstance().getConfig().isAllowThirdPartyEars(); - private static final String EARS_GEOMETRY; - private static final String EARS_GEOMETRY_SLIM; static final SkinGeometry SKULL_GEOMETRY; static final SkinGeometry WEARING_CUSTOM_SKULL; static final SkinGeometry WEARING_CUSTOM_SKULL_SLIM; @@ -116,12 +113,6 @@ public class SkinProvider { } EMPTY_SKIN = new Skin(-1, "geysermc:empty", outputStream.toByteArray()); - /* Load in the normal ears geometry */ - EARS_GEOMETRY = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.ears.json"), StandardCharsets.UTF_8); - - /* Load in the slim ears geometry */ - EARS_GEOMETRY_SLIM = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.earsSlim.json"), StandardCharsets.UTF_8); - /* Load in the custom skull geometry */ String skullData = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.customskull.json"), StandardCharsets.UTF_8); SKULL_GEOMETRY = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.customskull\"}}", skullData, false); @@ -260,41 +251,6 @@ public class SkinProvider { cape = getCachedBedrockCape(entity.getUuid()); } - if (cape.failed() && ALLOW_THIRD_PARTY_CAPES) { - cape = getOrDefault(requestUnofficialCape( - cape, entity.getUuid(), - entity.getUsername(), false - ), EMPTY_CAPE, CapeProvider.VALUES.length * 3); - } - - boolean isDeadmau5 = "deadmau5".equals(entity.getUsername()); - // Not a bedrock player check for ears - if (geometry.failed() && (ALLOW_THIRD_PARTY_EARS || isDeadmau5)) { - boolean isEars; - - // Its deadmau5, gotta support his skin :) - if (isDeadmau5) { - isEars = true; - } else { - // Get the ears texture for the player - skin = getOrDefault(requestUnofficialEars( - skin, entity.getUuid(), entity.getUsername(), false - ), skin, 3); - - isEars = skin.isEars(); - } - - // Does the skin have an ears texture - if (isEars) { - // Get the new geometry - geometry = SkinGeometry.getEars(data.isAlex()); - - // Store the skin and geometry for the ears - storeEarSkin(skin); - storeEarGeometry(entity.getUuid(), data.isAlex()); - } - } - return new SkinData(skin, cape, geometry); } catch (Exception e) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); @@ -377,67 +333,6 @@ public class SkinProvider { return future; } - private static CompletableFuture requestUnofficialCape(Cape officialCape, UUID playerId, - String username, boolean newThread) { - if (officialCape.failed() && ALLOW_THIRD_PARTY_CAPES) { - for (CapeProvider provider : CapeProvider.VALUES) { - if (provider.type != CapeUrlType.USERNAME && IS_NPC.test(playerId)) { - continue; - } - - Cape cape1 = getOrDefault( - requestCape(provider.getUrlFor(playerId, username), provider, newThread), - EMPTY_CAPE, 4 - ); - if (!cape1.failed()) { - return CompletableFuture.completedFuture(cape1); - } - } - } - return CompletableFuture.completedFuture(officialCape); - } - - private static CompletableFuture requestEars(String earsUrl, boolean newThread, Skin skin) { - if (earsUrl == null || earsUrl.isEmpty()) return CompletableFuture.completedFuture(skin); - - CompletableFuture future; - if (newThread) { - future = CompletableFuture.supplyAsync(() -> supplyEars(skin, earsUrl), getExecutorService()) - .whenCompleteAsync((outSkin, throwable) -> { }); - } else { - Skin ears = supplyEars(skin, earsUrl); // blocking - future = CompletableFuture.completedFuture(ears); - } - return future; - } - - /** - * Try and find an ear texture for a Java player - * - * @param officialSkin The current players skin - * @param playerId The players UUID - * @param username The players username - * @param newThread Should we start in a new thread - * @return The updated skin with ears - */ - private static CompletableFuture requestUnofficialEars(Skin officialSkin, UUID playerId, String username, boolean newThread) { - for (EarsProvider provider : EarsProvider.VALUES) { - if (provider.type != CapeUrlType.USERNAME && IS_NPC.test(playerId)) { - continue; - } - - Skin skin1 = getOrDefault( - requestEars(provider.getUrlFor(playerId, username), newThread, officialSkin), - officialSkin, 4 - ); - if (skin1.isEars()) { - return CompletableFuture.completedFuture(skin1); - } - } - - return CompletableFuture.completedFuture(officialSkin); - } - static void storeBedrockSkin(UUID playerID, String skinId, byte[] skinData) { Skin skin = new Skin(playerID, skinId, skinData, System.currentTimeMillis(), true, false); CACHED_BEDROCK_SKINS.put(skin.getTextureUrl(), skin); @@ -453,32 +348,13 @@ public class SkinProvider { cachedGeometry.put(playerID, geometry); } - /** - * Stores the adjusted skin with the ear texture to the cache - * - * @param skin The skin to cache - */ - public static void storeEarSkin(Skin skin) { - CACHED_JAVA_SKINS.put(skin.getTextureUrl(), skin); - } - - /** - * Stores the geometry for a Java player with ears - * - * @param playerID The UUID to cache it against - * @param isSlim If the player is using an slim base - */ - private static void storeEarGeometry(UUID playerID, boolean isSlim) { - cachedGeometry.put(playerID, SkinGeometry.getEars(isSlim)); - } - private static Skin supplySkin(UUID uuid, String textureUrl) { try { byte[] skin = requestImageData(textureUrl, null); - return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false, false); + return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false); } catch (Exception ignored) {} // just ignore I guess - return new Skin(uuid, "empty", EMPTY_SKIN.getSkinData(), System.currentTimeMillis(), false, false); + return new Skin(uuid, "empty", EMPTY_SKIN.getSkinData(), System.currentTimeMillis(), false); } private static Cape supplyCape(String capeUrl, CapeProvider provider) { @@ -532,7 +408,6 @@ public class SkinProvider { existingSkin.getTextureUrl(), data, System.currentTimeMillis(), - true, true ); } catch (Exception ignored) {} // just ignore I guess @@ -669,17 +544,12 @@ public class SkinProvider { } private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException { - BufferedImage image; - if (provider == CapeProvider.FIVEZIG) { - image = readFiveZigCape(imageUrl); - } else { - HttpURLConnection con = (HttpURLConnection) new URL(imageUrl).openConnection(); - con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); - con.setConnectTimeout(10000); - con.setReadTimeout(10000); + HttpURLConnection con = (HttpURLConnection) new URL(imageUrl).openConnection(); + con.setRequestProperty("User-Agent", "Geyser-" + GeyserImpl.getInstance().getPlatformType().toString() + "/" + GeyserImpl.VERSION); + con.setConnectTimeout(10000); + con.setReadTimeout(10000); - image = ImageIO.read(con.getInputStream()); - } + BufferedImage image = ImageIO.read(con.getInputStream()); if (image == null) { throw new IllegalArgumentException("Failed to read image from: %s (cape provider=%s)".formatted(imageUrl, provider)); @@ -687,16 +557,6 @@ public class SkinProvider { return image; } - private static @Nullable BufferedImage readFiveZigCape(String url) throws IOException { - JsonNode element = GeyserImpl.JSON_MAPPER.readTree(WebUtils.getBody(url)); - if (element != null && element.isObject()) { - JsonNode capeElement = element.get("d"); - if (capeElement == null || capeElement.isNull()) return null; - return ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(capeElement.textValue()))); - } - return null; - } - public static BufferedImage scale(BufferedImage bufferedImage, int newWidth, int newHeight) { BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = resized.createGraphics(); @@ -784,7 +644,6 @@ public class SkinProvider { private final byte[] skinData; private final long requestedOn; private boolean updated; - private boolean ears; Skin(long requestedOn, String textureUrl, byte[] skinData) { this.requestedOn = requestedOn; @@ -809,16 +668,6 @@ public class SkinProvider { private static SkinGeometry getLegacy(boolean isSlim) { return new SkinProvider.SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.custom" + (isSlim ? "Slim" : "") + "\"}}", "", true); } - - /** - * Generate basic geometry with ears - * - * @param isSlim Should it be the alex model - * @return The generated geometry for the ears model - */ - private static SkinGeometry getEars(boolean isSlim) { - return new SkinProvider.SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.ears" + (isSlim ? "Slim" : "") + "\"}}", (isSlim ? EARS_GEOMETRY_SLIM : EARS_GEOMETRY), false); - } } /* @@ -828,11 +677,7 @@ public class SkinProvider { @NoArgsConstructor @Getter public enum CapeProvider { - MINECRAFT, - OPTIFINE("https://optifine.net/capes/%s.png", CapeUrlType.USERNAME), - LABYMOD("https://dl.labymod.net/capes/%s", CapeUrlType.UUID_DASHED), - FIVEZIG("https://textures.5zigreborn.eu/profile/%s", CapeUrlType.UUID_DASHED), - MINECRAFTCAPES("https://api.minecraftcapes.net/profile/%s/cape", CapeUrlType.UUID); + MINECRAFT; public static final CapeProvider[] VALUES = Arrays.copyOfRange(values(), 1, 5); private String url; @@ -860,34 +705,4 @@ public class SkinProvider { UUID, UUID_DASHED } - - /* - * Sorted by 'priority' - */ - @AllArgsConstructor - @NoArgsConstructor - @Getter - public enum EarsProvider { - MINECRAFTCAPES("https://api.minecraftcapes.net/profile/%s/ears", CapeUrlType.UUID); - - public static final EarsProvider[] VALUES = values(); - private String url; - private CapeUrlType type; - - public String getUrlFor(String type) { - return String.format(url, type); - } - - public String getUrlFor(UUID uuid, String username) { - return getUrlFor(toRequestedType(type, uuid, username)); - } - - public static String toRequestedType(CapeUrlType type, UUID uuid, String username) { - return switch (type) { - case UUID -> uuid.toString().replace("-", ""); - case UUID_DASHED -> uuid.toString(); - default -> username; - }; - } - } } From 5d95ee0ba70c4c6acf84ebeb18d8fe25ee9077cf Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sun, 17 Mar 2024 17:00:49 +0000 Subject: [PATCH 02/15] Remove supplyEars --- .../geysermc/geyser/skin/SkinProvider.java | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 9145dac75..9c209d7b9 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -375,46 +375,6 @@ public class SkinProvider { ); } - /** - * Get the ears texture and place it on the skin from the given URL - * - * @param existingSkin The players current skin - * @param earsUrl The URL to get the ears texture from - * @return The updated skin with ears - */ - private static Skin supplyEars(Skin existingSkin, String earsUrl) { - try { - // Get the ears texture - BufferedImage ears = ImageIO.read(new URL(earsUrl)); - if (ears == null) throw new NullPointerException(); - - // Convert the skin data to a BufferedImage - int height = (existingSkin.getSkinData().length / 4 / 64); - BufferedImage skinImage = imageDataToBufferedImage(existingSkin.getSkinData(), 64, height); - - // Create a new image with the ears texture over it - BufferedImage newSkin = new BufferedImage(skinImage.getWidth(), skinImage.getHeight(), BufferedImage.TYPE_INT_ARGB); - Graphics2D g = (Graphics2D) newSkin.getGraphics(); - g.drawImage(skinImage, 0, 0, null); - g.drawImage(ears, 24, 0, null); - - // Turn the buffered image back into an array of bytes - byte[] data = bufferedImageToImageData(newSkin); - skinImage.flush(); - - // Create a new skin object with the new infomation - return new Skin( - existingSkin.getSkinOwner(), - existingSkin.getTextureUrl(), - data, - System.currentTimeMillis(), - true - ); - } catch (Exception ignored) {} // just ignore I guess - - return existingSkin; - } - @SuppressWarnings("ResultOfMethodCallIgnored") public static BufferedImage requestImage(String imageUrl, CapeProvider provider) throws IOException { BufferedImage image = null; From f490d849589819c35fdf849f09637516d53d4e60 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sun, 17 Mar 2024 17:50:39 +0000 Subject: [PATCH 03/15] Cleanup and add SkinApplyEvent --- .../api/event/lifecycle/SkinApplyEvent.java | 80 ++++++ .../org/geysermc/geyser/api/skin/Cape.java | 29 ++ .../org/geysermc/geyser/api/skin/Skin.java | 29 ++ .../geysermc/geyser/api/skin/SkinData.java | 32 +++ .../geyser/api/skin/SkinGeometry.java | 42 +++ .../geyser/skin/FakeHeadProvider.java | 30 ++- .../geysermc/geyser/skin/ProvidedSkins.java | 7 +- .../org/geysermc/geyser/skin/SkinManager.java | 38 +-- .../geysermc/geyser/skin/SkinProvider.java | 100 +++---- .../geyser/skin/SkullSkinManager.java | 12 +- .../bedrock/skin/geometry.humanoid.ears.json | 249 ------------------ .../skin/geometry.humanoid.earsSlim.json | 249 ------------------ 12 files changed, 304 insertions(+), 593 deletions(-) create mode 100644 api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/skin/Cape.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/skin/Skin.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java create mode 100644 api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java delete mode 100644 core/src/main/resources/bedrock/skin/geometry.humanoid.ears.json delete mode 100644 core/src/main/resources/bedrock/skin/geometry.humanoid.earsSlim.json diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java new file mode 100644 index 000000000..bd19e7d2a --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.event.lifecycle; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.geysermc.event.Event; +import org.geysermc.geyser.api.skin.Cape; +import org.geysermc.geyser.api.skin.Skin; +import org.geysermc.geyser.api.skin.SkinData; +import org.geysermc.geyser.api.skin.SkinGeometry; + +import java.util.UUID; + +public abstract class SkinApplyEvent implements Event { + + private final String username; + private final UUID uuid; + private final boolean slim; + private final boolean isBedrock; + private final SkinData skinData; + + public SkinApplyEvent(String username, UUID uuid, boolean slim, boolean isBedrock, SkinData skinData) { + this.username = username; + this.uuid = uuid; + this.slim = slim; + this.isBedrock = isBedrock; + this.skinData = skinData; + } + + public String username() { + return username; + } + + public UUID uuid() { + return uuid; + } + + public boolean slim() { + return slim; + } + + public boolean isBedrock() { + return isBedrock; + } + + public SkinData skinData() { + return skinData; + } + + public abstract void skin(@NonNull Skin newSkin); + public abstract void cape(@NonNull Cape newCape); + public abstract void geometry(@NonNull SkinGeometry newGeometry); + + public void geometry(@NonNull String geometryName, @NonNull String geometryData) { + geometry(new SkinGeometry("{\"geometry\" :{\"default\" :\"" + geometryName + "\"}}", geometryData)); + } +} diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java new file mode 100644 index 000000000..620729a75 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.skin; + +public record Cape(String textureUrl, String capeId, byte[] capeData, boolean failed) { +} diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java new file mode 100644 index 000000000..c7bd763d0 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.skin; + +public record Skin(String textureUrl, byte[] skinData) { +} diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java b/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java new file mode 100644 index 000000000..5f0060fee --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.skin; + +/** + * Represents a full package of skin, cape, and geometry. + */ +public record SkinData(Skin skin, Cape cape, SkinGeometry geometry) { +} diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java new file mode 100644 index 000000000..9c7cf9219 --- /dev/null +++ b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.api.skin; + +public record SkinGeometry(String geometryName, String geometryData) { + + public static SkinGeometry WIDE = getLegacy(false); + public static SkinGeometry SLIM = getLegacy(true); + + /** + * Generate generic geometry + * + * @param isSlim Should it be the alex model + * @return The generic geometry object + */ + private static SkinGeometry getLegacy(boolean isSlim) { + return new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.custom" + (isSlim ? "Slim" : "") + "\"}}", ""); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index c7bd235b2..6fb0b5499 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -36,6 +36,10 @@ import lombok.Getter; import lombok.Setter; import org.checkerframework.checker.nullness.qual.NonNull; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.skin.Cape; +import org.geysermc.geyser.api.skin.Skin; +import org.geysermc.geyser.api.skin.SkinData; +import org.geysermc.geyser.api.skin.SkinGeometry; import org.geysermc.geyser.entity.type.LivingEntity; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; @@ -53,27 +57,27 @@ import java.util.concurrent.TimeUnit; * Responsible for modifying a player's skin when wearing a player head */ public class FakeHeadProvider { - private static final LoadingCache MERGED_SKINS_LOADING_CACHE = CacheBuilder.newBuilder() + private static final LoadingCache MERGED_SKINS_LOADING_CACHE = CacheBuilder.newBuilder() .expireAfterAccess(1, TimeUnit.HOURS) .maximumSize(10000) .build(new CacheLoader<>() { @Override - public SkinProvider.SkinData load(@NonNull FakeHeadEntry fakeHeadEntry) throws Exception { - SkinProvider.SkinData skinData = SkinProvider.getOrDefault(SkinProvider.requestSkinData(fakeHeadEntry.getEntity()), null, 5); + public SkinData load(@NonNull FakeHeadEntry fakeHeadEntry) throws Exception { + SkinData skinData = SkinProvider.getOrDefault(SkinProvider.requestSkinData(fakeHeadEntry.getEntity()), null, 5); if (skinData == null) { throw new Exception("Couldn't load player's original skin"); } - SkinProvider.Skin skin = skinData.skin(); - SkinProvider.Cape cape = skinData.cape(); - SkinProvider.SkinGeometry geometry = skinData.geometry().geometryName().equals("{\"geometry\" :{\"default\" :\"geometry.humanoid.customSlim\"}}") + Skin skin = skinData.skin(); + Cape cape = skinData.cape(); + SkinGeometry geometry = skinData.geometry().geometryName().equals("{\"geometry\" :{\"default\" :\"geometry.humanoid.customSlim\"}}") ? SkinProvider.WEARING_CUSTOM_SKULL_SLIM : SkinProvider.WEARING_CUSTOM_SKULL; - SkinProvider.Skin headSkin = SkinProvider.getOrDefault( + Skin headSkin = SkinProvider.getOrDefault( SkinProvider.requestSkin(fakeHeadEntry.getEntity().getUuid(), fakeHeadEntry.getFakeHeadSkinUrl(), false), SkinProvider.EMPTY_SKIN, 5); - BufferedImage originalSkinImage = SkinProvider.imageDataToBufferedImage(skin.getSkinData(), 64, skin.getSkinData().length / 4 / 64); - BufferedImage headSkinImage = SkinProvider.imageDataToBufferedImage(headSkin.getSkinData(), 64, headSkin.getSkinData().length / 4 / 64); + BufferedImage originalSkinImage = SkinProvider.imageDataToBufferedImage(skin.skinData(), 64, skin.skinData().length / 4 / 64); + BufferedImage headSkinImage = SkinProvider.imageDataToBufferedImage(headSkin.skinData(), 64, headSkin.skinData().length / 4 / 64); Graphics2D graphics2D = originalSkinImage.createGraphics(); graphics2D.setComposite(AlphaComposite.Clear); @@ -84,14 +88,14 @@ public class FakeHeadProvider { // Make the skin key a combination of the current skin data and the new skin data // Don't tie it to a player - that player *can* change skins in-game - String skinKey = "customPlayerHead_" + fakeHeadEntry.getFakeHeadSkinUrl() + "_" + skin.getTextureUrl(); + String skinKey = "customPlayerHead_" + fakeHeadEntry.getFakeHeadSkinUrl() + "_" + skin.textureUrl(); byte[] targetSkinData = SkinProvider.bufferedImageToImageData(originalSkinImage); - SkinProvider.Skin mergedSkin = new SkinProvider.Skin(fakeHeadEntry.getEntity().getUuid(), skinKey, targetSkinData, System.currentTimeMillis(), false, false); + Skin mergedSkin = new Skin(skinKey, targetSkinData); // Avoiding memory leak fakeHeadEntry.setEntity(null); - return new SkinProvider.SkinData(mergedSkin, cape, geometry); + return new SkinData(mergedSkin, cape, geometry); } }); @@ -136,7 +140,7 @@ public class FakeHeadProvider { String texturesProperty = entity.getTexturesProperty(); SkinProvider.getExecutorService().execute(() -> { try { - SkinProvider.SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity)); + SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity)); SkinManager.sendSkinPacket(session, entity, mergedSkinData); } catch (ExecutionException e) { GeyserImpl.getInstance().getLogger().error("Couldn't merge skin of " + entity.getUsername() + " with head skin url " + fakeHeadSkinUrl, e); diff --git a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java index 58c8f0072..c2647044a 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java +++ b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java @@ -26,6 +26,7 @@ package org.geysermc.geyser.skin; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.skin.Skin; import org.geysermc.geyser.util.AssetUtils; import javax.imageio.ImageIO; @@ -67,7 +68,7 @@ public final class ProvidedSkins { } public static final class ProvidedSkin { - private SkinProvider.Skin data; + private Skin data; private final boolean slim; ProvidedSkin(String asset, boolean slim) { @@ -94,14 +95,14 @@ public final class ProvidedSkins { image.flush(); String identifier = "geysermc:" + assetName + "_" + (slim ? "slim" : "wide"); - this.data = new SkinProvider.Skin(-1, identifier, byteData); + this.data = new Skin(identifier, byteData); } catch (IOException e) { e.printStackTrace(); } })); } - public SkinProvider.Skin getData() { + public Skin getData() { // Fall back to the default skin if we can't load our skins, or it's not loaded yet. return Objects.requireNonNullElse(data, SkinProvider.EMPTY_SKIN); } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 33a51fe8f..3865e5573 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -35,6 +35,10 @@ import org.cloudburstmc.protocol.bedrock.data.skin.SerializedSkin; import org.cloudburstmc.protocol.bedrock.packet.PlayerListPacket; import org.cloudburstmc.protocol.bedrock.packet.PlayerSkinPacket; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.skin.Cape; +import org.geysermc.geyser.api.skin.Skin; +import org.geysermc.geyser.api.skin.SkinData; +import org.geysermc.geyser.api.skin.SkinGeometry; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.session.GeyserSession; @@ -56,21 +60,21 @@ public class SkinManager { public static PlayerListPacket.Entry buildCachedEntry(GeyserSession session, PlayerEntity playerEntity) { // First: see if we have the cached skin texture ID. GameProfileData data = GameProfileData.from(playerEntity); - SkinProvider.Skin skin = null; - SkinProvider.Cape cape = null; - SkinProvider.SkinGeometry geometry = SkinProvider.SkinGeometry.WIDE; + Skin skin = null; + Cape cape = null; + SkinGeometry geometry = SkinGeometry.WIDE; if (data != null) { // GameProfileData is not null = server provided us with textures data to work with. skin = SkinProvider.getCachedSkin(data.skinUrl()); cape = SkinProvider.getCachedCape(data.capeUrl()); - geometry = data.isAlex() ? SkinProvider.SkinGeometry.SLIM : SkinProvider.SkinGeometry.WIDE; + geometry = data.isAlex() ? SkinGeometry.SLIM : SkinGeometry.WIDE; } if (skin == null || cape == null) { // The server either didn't have a texture to send, or we didn't have the texture ID cached. // Let's see if this player is a Bedrock player, and if so, let's pull their skin. // Otherwise, grab the default player skin - SkinProvider.SkinData fallbackSkinData = SkinProvider.determineFallbackSkinData(playerEntity.getUuid()); + SkinData fallbackSkinData = SkinProvider.determineFallbackSkinData(playerEntity.getUuid()); if (skin == null) { skin = fallbackSkinData.skin(); geometry = fallbackSkinData.geometry(); @@ -95,10 +99,10 @@ public class SkinManager { * With all the information needed, build a Bedrock player entry with translated skin information. */ public static PlayerListPacket.Entry buildEntryManually(GeyserSession session, UUID uuid, String username, long geyserId, - SkinProvider.Skin skin, - SkinProvider.Cape cape, - SkinProvider.SkinGeometry geometry) { - SerializedSkin serializedSkin = getSkin(skin.getTextureUrl(), skin, cape, geometry); + Skin skin, + Cape cape, + SkinGeometry geometry) { + SerializedSkin serializedSkin = getSkin(skin.textureUrl(), skin, cape, geometry); // This attempts to find the XUID of the player so profile images show up for Xbox accounts String xuid = ""; @@ -128,10 +132,10 @@ public class SkinManager { return entry; } - public static void sendSkinPacket(GeyserSession session, PlayerEntity entity, SkinProvider.SkinData skinData) { - SkinProvider.Skin skin = skinData.skin(); - SkinProvider.Cape cape = skinData.cape(); - SkinProvider.SkinGeometry geometry = skinData.geometry(); + public static void sendSkinPacket(GeyserSession session, PlayerEntity entity, SkinData skinData) { + Skin skin = skinData.skin(); + Cape cape = skinData.cape(); + SkinGeometry geometry = skinData.geometry(); if (entity.getUuid().equals(session.getPlayerEntity().getUuid())) { // TODO is this special behavior needed? @@ -153,16 +157,16 @@ public class SkinManager { PlayerSkinPacket packet = new PlayerSkinPacket(); packet.setUuid(entity.getUuid()); packet.setOldSkinName(""); - packet.setNewSkinName(skin.getTextureUrl()); - packet.setSkin(getSkin(skin.getTextureUrl(), skin, cape, geometry)); + packet.setNewSkinName(skin.textureUrl()); + packet.setSkin(getSkin(skin.textureUrl(), skin, cape, geometry)); packet.setTrustedSkin(true); session.sendUpstreamPacket(packet); } } - private static SerializedSkin getSkin(String skinId, SkinProvider.Skin skin, SkinProvider.Cape cape, SkinProvider.SkinGeometry geometry) { + private static SerializedSkin getSkin(String skinId, Skin skin, Cape cape, SkinGeometry geometry) { return SerializedSkin.of(skinId, "", geometry.geometryName(), - ImageData.of(skin.getSkinData()), Collections.emptyList(), + ImageData.of(skin.skinData()), Collections.emptyList(), ImageData.of(cape.capeData()), geometry.geometryData(), "", true, false, false, cape.capeId(), skinId); } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 9c209d7b9..a6c6d6c12 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -35,7 +35,12 @@ import lombok.NoArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.event.lifecycle.SkinApplyEvent; import org.geysermc.geyser.api.network.AuthType; +import org.geysermc.geyser.api.skin.Cape; +import org.geysermc.geyser.api.skin.Skin; +import org.geysermc.geyser.api.skin.SkinData; +import org.geysermc.geyser.api.skin.SkinGeometry; import org.geysermc.geyser.entity.type.player.PlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -45,7 +50,6 @@ import org.geysermc.geyser.util.WebUtils; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -61,7 +65,7 @@ public class SkinProvider { private static ExecutorService EXECUTOR_SERVICE; static final Skin EMPTY_SKIN; - static final Cape EMPTY_CAPE = new Cape("", "no-cape", ByteArrays.EMPTY_ARRAY, -1, true); + static final Cape EMPTY_CAPE = new Cape("", "no-cape", ByteArrays.EMPTY_ARRAY, true); private static final Cache CACHED_JAVA_CAPES = CacheBuilder.newBuilder() .expireAfterAccess(1, TimeUnit.HOURS) @@ -111,17 +115,17 @@ public class SkinProvider { outputStream.write((rgba >> 24) & 0xFF); // Alpha } } - EMPTY_SKIN = new Skin(-1, "geysermc:empty", outputStream.toByteArray()); + EMPTY_SKIN = new Skin("geysermc:empty", outputStream.toByteArray()); /* Load in the custom skull geometry */ String skullData = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.customskull.json"), StandardCharsets.UTF_8); - SKULL_GEOMETRY = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.customskull\"}}", skullData, false); + SKULL_GEOMETRY = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.customskull\"}}", skullData); /* Load in the player head skull geometry */ String wearingCustomSkull = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkull.json"), StandardCharsets.UTF_8); - WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull, false); + WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull); String wearingCustomSkullSlim = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json"), StandardCharsets.UTF_8); - WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim, false); + WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim); } public static ExecutorService getExecutorService() { @@ -195,7 +199,7 @@ public class SkinProvider { // We don't have a skin for the player right now. Fall back to a default. ProvidedSkins.ProvidedSkin providedSkin = ProvidedSkins.getDefaultPlayerSkin(uuid); skin = providedSkin.getData(); - geometry = providedSkin.isSlim() ? SkinProvider.SkinGeometry.SLIM : SkinProvider.SkinGeometry.WIDE; + geometry = providedSkin.isSlim() ? SkinGeometry.SLIM : SkinGeometry.WIDE; } if (cape == null) { @@ -229,7 +233,7 @@ public class SkinProvider { return CACHED_JAVA_CAPES.getIfPresent(capeUrl); } - static CompletableFuture requestSkinData(PlayerEntity entity) { + static CompletableFuture requestSkinData(PlayerEntity entity) { SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity); if (data == null) { // This player likely does not have a textures property @@ -251,7 +255,31 @@ public class SkinProvider { cape = getCachedBedrockCape(entity.getUuid()); } - return new SkinData(skin, cape, geometry); + // TODO: Call event to allow extensions to modify the skin, cape and geo + // Pass in if the skin is from a Bedrock player or java + GeyserImpl geyser = GeyserImpl.getInstance(); + geyser.getLogger().debug("Processing skin, isBedrock: " + (geyser.connectionByUuid(entity.getUuid()) != null)); + boolean isBedrock = geyser.connectionByUuid(entity.getUuid()) != null; + final SkinData[] skinData = {new SkinData(skin, cape, geometry)}; + GeyserImpl.getInstance().eventBus().fire(new SkinApplyEvent(entity.getUsername(), entity.getUuid(), data.isAlex(), isBedrock, skinData[0]) { + + @Override + public void skin(@NonNull Skin newSkin) { + skinData[0] = new SkinData(newSkin, skinData[0].cape(), skinData[0].geometry()); + } + + @Override + public void cape(@NonNull Cape newCape) { + skinData[0] = new SkinData(skinData[0].skin(), newCape, skinData[0].geometry()); + } + + @Override + public void geometry(@NonNull SkinGeometry newGeometry) { + skinData[0] = new SkinData(skinData[0].skin(), skinData[0].cape(), newGeometry); + } + }); + + return skinData[0]; } catch (Exception e) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); } @@ -292,7 +320,6 @@ public class SkinProvider { if (newThread) { future = CompletableFuture.supplyAsync(() -> supplySkin(playerId, textureUrl), getExecutorService()) .whenCompleteAsync((skin, throwable) -> { - skin.updated = true; CACHED_JAVA_SKINS.put(textureUrl, skin); requestedSkins.remove(textureUrl); }); @@ -334,27 +361,27 @@ public class SkinProvider { } static void storeBedrockSkin(UUID playerID, String skinId, byte[] skinData) { - Skin skin = new Skin(playerID, skinId, skinData, System.currentTimeMillis(), true, false); - CACHED_BEDROCK_SKINS.put(skin.getTextureUrl(), skin); + Skin skin = new Skin(skinId, skinData); + CACHED_BEDROCK_SKINS.put(skin.textureUrl(), skin); } static void storeBedrockCape(String capeId, byte[] capeData) { - Cape cape = new Cape(capeId, capeId, capeData, System.currentTimeMillis(), false); + Cape cape = new Cape(capeId, capeId, capeData, false); CACHED_BEDROCK_CAPES.put(capeId, cape); } static void storeBedrockGeometry(UUID playerID, byte[] geometryName, byte[] geometryData) { - SkinGeometry geometry = new SkinGeometry(new String(geometryName), new String(geometryData), false); + SkinGeometry geometry = new SkinGeometry(new String(geometryName), new String(geometryData)); cachedGeometry.put(playerID, geometry); } private static Skin supplySkin(UUID uuid, String textureUrl) { try { byte[] skin = requestImageData(textureUrl, null); - return new Skin(uuid, textureUrl, skin, System.currentTimeMillis(), false); + return new Skin(textureUrl, skin); } catch (Exception ignored) {} // just ignore I guess - return new Skin(uuid, "empty", EMPTY_SKIN.getSkinData(), System.currentTimeMillis(), false); + return new Skin("empty", EMPTY_SKIN.skinData()); } private static Cape supplyCape(String capeUrl, CapeProvider provider) { @@ -370,7 +397,6 @@ public class SkinProvider { capeUrl, urlSection[urlSection.length - 1], // get the texture id and use it as cape id cape, - System.currentTimeMillis(), cape.length == 0 ); } @@ -590,46 +616,6 @@ public class SkinProvider { public record SkinAndCape(Skin skin, Cape cape) { } - /** - * Represents a full package of skin, cape, and geometry. - */ - public record SkinData(Skin skin, Cape cape, SkinGeometry geometry) { - } - - @AllArgsConstructor - @Getter - public static class Skin { - private UUID skinOwner; - private final String textureUrl; - private final byte[] skinData; - private final long requestedOn; - private boolean updated; - - Skin(long requestedOn, String textureUrl, byte[] skinData) { - this.requestedOn = requestedOn; - this.textureUrl = textureUrl; - this.skinData = skinData; - } - } - - public record Cape(String textureUrl, String capeId, byte[] capeData, long requestedOn, boolean failed) { - } - - public record SkinGeometry(String geometryName, String geometryData, boolean failed) { - public static SkinGeometry WIDE = getLegacy(false); - public static SkinGeometry SLIM = getLegacy(true); - - /** - * Generate generic geometry - * - * @param isSlim Should it be the alex model - * @return The generic geometry object - */ - private static SkinGeometry getLegacy(boolean isSlim) { - return new SkinProvider.SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.custom" + (isSlim ? "Slim" : "") + "\"}}", "", true); - } - } - /* * Sorted by 'priority' */ diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java index 18edc8079..744db1433 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java @@ -29,6 +29,8 @@ import org.cloudburstmc.protocol.bedrock.data.skin.ImageData; import org.cloudburstmc.protocol.bedrock.data.skin.SerializedSkin; import org.cloudburstmc.protocol.bedrock.packet.PlayerSkinPacket; import org.geysermc.geyser.GeyserImpl; +import org.geysermc.geyser.api.skin.Skin; +import org.geysermc.geyser.api.skin.SkinData; import org.geysermc.geyser.entity.type.player.SkullPlayerEntity; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.text.GeyserLocale; @@ -50,14 +52,14 @@ public class SkullSkinManager extends SkinManager { } public static void requestAndHandleSkin(SkullPlayerEntity entity, GeyserSession session, - Consumer skinConsumer) { - BiConsumer applySkin = (skin, throwable) -> { + Consumer skinConsumer) { + BiConsumer applySkin = (skin, throwable) -> { try { PlayerSkinPacket packet = new PlayerSkinPacket(); packet.setUuid(entity.getUuid()); packet.setOldSkinName(""); - packet.setNewSkinName(skin.getTextureUrl()); - packet.setSkin(buildSkullEntryManually(skin.getTextureUrl(), skin.getSkinData())); + packet.setNewSkinName(skin.textureUrl()); + packet.setSkin(buildSkullEntryManually(skin.textureUrl(), skin.skinData())); packet.setTrustedSkin(true); session.sendUpstreamPacket(packet); } catch (Exception e) { @@ -74,7 +76,7 @@ public class SkullSkinManager extends SkinManager { GeyserImpl.getInstance().getLogger().debug("Using fallback skin for skull at " + entity.getSkullPosition() + " with texture value: " + entity.getTexturesProperty() + " and UUID: " + entity.getSkullUUID()); // No texture available, fallback using the UUID - SkinProvider.SkinData fallback = SkinProvider.determineFallbackSkinData(entity.getSkullUUID()); + SkinData fallback = SkinProvider.determineFallbackSkinData(entity.getSkullUUID()); applySkin.accept(fallback.skin(), null); } else { SkinProvider.requestSkin(entity.getUuid(), data.skinUrl(), true) diff --git a/core/src/main/resources/bedrock/skin/geometry.humanoid.ears.json b/core/src/main/resources/bedrock/skin/geometry.humanoid.ears.json deleted file mode 100644 index 5571655b8..000000000 --- a/core/src/main/resources/bedrock/skin/geometry.humanoid.ears.json +++ /dev/null @@ -1,249 +0,0 @@ -{ - "format_version": "1.14.0", - "minecraft:geometry": [ - { - "bones": [ - { - "name" : "root", - "pivot" : [ 0.0, 0.0, 0.0 ] - }, - - { - "name" : "waist", - "parent" : "root", - "pivot" : [ 0.0, 12.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes" : [] - }, - - - { - "name": "body", - "parent" : "waist", - "pivot": [ 0.0, 24.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 12.0, -2.0 ], - "size": [ 8, 12, 4 ], - "uv": [ 16, 16 ] - } - ] - }, - - { - "name": "jacket", - "parent" : "body", - "pivot": [ 0.0, 24.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 12.0, -2.0 ], - "size": [ 8, 12, 4 ], - "uv": [ 16, 32 ], - "inflate": 0.25 - } - ] - }, - - - { - "name": "head", - "parent" : "body", - "pivot": [ 0.0, 24.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 24.0, -4.0 ], - "size": [ 8, 8, 8 ], - "uv": [ 0, 0 ] - } - ] - }, - - { - "name": "hat", - "parent" : "head", - "pivot": [ 0.0, 24.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 24.0, -4.0 ], - "size": [ 8, 8, 8 ], - "uv": [ 32, 0 ], - "inflate": 0.5 - } - ] - }, - - - { - "name": "leftArm", - "parent" : "body", - "pivot": [ 5.0, 22.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ 4.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 32, 48 ] - } - ] - }, - { - "name": "rightArm", - "parent" : "body", - "pivot": [ -5.0, 22.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -8.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 40, 16 ] - } - ] - }, - - { - "name": "leftSleeve", - "parent" : "leftArm", - "pivot": [ 5.0, 22.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ 4.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 48, 48 ], - "inflate": 0.25 - } - ] - }, - - { - "name": "rightSleeve", - "parent" : "rightArm", - "pivot": [ -5.0, 22.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -8.0, 12.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 40, 32 ], - "inflate": 0.25 - } - ] - }, - - - { - "name": "leftLeg", - "parent" : "root", - "pivot": [ 1.9, 12.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -0.1, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 16 ] - } - ] - }, - - { - "name": "rightLeg", - "parent" : "root", - "pivot": [ -1.9, 12.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -3.9, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 16 ] - } - ] - }, - - { - "name": "leftPants", - "parent" : "leftLeg", - "pivot": [1.9, 12.0, 0.0], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -0.1, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 48 ], - "inflate": 0.25 - } - ] - }, - - { - "name": "rightPants", - "parent" : "rightLeg", - "pivot": [ -1.9, 12.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -3.9, 0.0, -2.0] , - "size": [ 4, 12, 4 ], - "uv": [ 0, 32], - "inflate": 0.25 - } - ] - }, - - - { - "name" : "rightItem", - "parent" : "rightArm", - "pivot" : [ -6.0, 15.0, 1.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes" : [] - }, - - { - "name" : "leftItem", - "parent" : "leftArm", - "pivot" : [ 6.0, 15.0, 1.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes" : [] - }, - - - { - "name": "leftEar", - "parent" : "head", - "pivot": [ -1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ 3.0, 31.0, -0.5 ], - "size": [ 6, 6, 1 ], - "uv": [ 24, 0 ], - "inflate": 0.5 - } - ] - }, - - { - "name": "rightEar", - "parent" : "head", - "pivot": [ -1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -9.0, 31.0, -0.5 ], - "size": [ 6, 6, 1 ], - "uv": [ 24, 0 ], - "inflate": 0.5 - } - ] - } - ], - "description": { - "identifier": "geometry.humanoid.ears", - "texture_height": 64, - "texture_width": 64 - } - } - ] -} \ No newline at end of file diff --git a/core/src/main/resources/bedrock/skin/geometry.humanoid.earsSlim.json b/core/src/main/resources/bedrock/skin/geometry.humanoid.earsSlim.json deleted file mode 100644 index 70c44f854..000000000 --- a/core/src/main/resources/bedrock/skin/geometry.humanoid.earsSlim.json +++ /dev/null @@ -1,249 +0,0 @@ -{ - "format_version": "1.14.0", - "minecraft:geometry": [ - { - "bones": [ - { - "name" : "root", - "pivot" : [ 0.0, 0.0, 0.0 ] - }, - - { - "name" : "waist", - "parent" : "root", - "pivot" : [ 0.0, 12.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes" : [] - }, - - - { - "name": "body", - "parent" : "waist", - "pivot": [ 0.0, 24.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 12.0, -2.0 ], - "size": [ 8, 12, 4 ], - "uv": [ 16, 16 ] - } - ] - }, - - { - "name": "jacket", - "parent" : "body", - "pivot": [ 0.0, 24.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 12.0, -2.0 ], - "size": [ 8, 12, 4 ], - "uv": [ 16, 32 ], - "inflate": 0.25 - } - ] - }, - - - { - "name": "head", - "parent" : "body", - "pivot": [ 0.0, 24.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 24.0, -4.0 ], - "size": [ 8, 8, 8 ], - "uv": [ 0, 0 ] - } - ] - }, - - { - "name": "hat", - "parent" : "head", - "pivot": [ 0.0, 24.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -4.0, 24.0, -4.0 ], - "size": [ 8, 8, 8 ], - "uv": [ 32, 0 ], - "inflate": 0.5 - } - ] - }, - - - { - "name": "leftArm", - "parent" : "body", - "pivot": [ 5.0, 21.5, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ 4.0, 12, -2.0 ], - "size": [ 3, 12, 4 ], - "uv": [ 32, 48 ] - } - ] - }, - { - "name": "rightArm", - "parent" : "body", - "pivot": [ -5.0, 21.5, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -7.0, 12, -2.0 ], - "size": [ 3, 12, 4 ], - "uv": [ 40, 16 ] - } - ] - }, - - { - "name": "leftSleeve", - "parent" : "leftArm", - "pivot": [ 5.0, 21.5, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ 4.0, 11.5, -2.0 ], - "size": [ 3, 12, 4 ], - "uv": [ 48, 48 ], - "inflate": 0.25 - } - ] - }, - - { - "name": "rightSleeve", - "parent" : "rightArm", - "pivot": [ -5.0, 21.5, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -7.0, 11.5, -2.0 ], - "size": [ 3, 12, 4 ], - "uv": [ 40, 32 ], - "inflate": 0.25 - } - ] - }, - - - { - "name": "leftLeg", - "parent" : "root", - "pivot": [ 1.9, 12.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -0.1, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 16 ] - } - ] - }, - - { - "name": "rightLeg", - "parent" : "root", - "pivot": [ -1.9, 12.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -3.9, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 16 ] - } - ] - }, - - { - "name": "leftPants", - "parent" : "leftLeg", - "pivot": [1.9, 12.0, 0.0], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -0.1, 0.0, -2.0 ], - "size": [ 4, 12, 4 ], - "uv": [ 0, 48 ], - "inflate": 0.25 - } - ] - }, - - { - "name": "rightPants", - "parent" : "rightLeg", - "pivot": [ -1.9, 12.0, 0.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes": [ - { - "origin": [ -3.9, 0.0, -2.0] , - "size": [ 4, 12, 4 ], - "uv": [ 0, 32], - "inflate": 0.25 - } - ] - }, - - - { - "name" : "rightItem", - "parent" : "rightArm", - "pivot" : [ -6.0, 15.0, 1.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes" : [] - }, - - { - "name" : "leftItem", - "parent" : "leftArm", - "pivot" : [ 6.0, 15.0, 1.0 ], - "rotation" : [ 0.0, 0.0, 0.0 ], - "cubes" : [] - }, - - - { - "name": "leftEar", - "parent" : "head", - "pivot": [ -1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ 3.0, 31.0, -0.5 ], - "size": [ 6, 6, 1 ], - "uv": [ 24, 0 ], - "inflate": 0.5 - } - ] - }, - - { - "name": "rightEar", - "parent" : "head", - "pivot": [ -1.9, 12.0, 0.0 ], - "cubes": [ - { - "origin": [ -9.0, 31.0, -0.5 ], - "size": [ 6, 6, 1 ], - "uv": [ 24, 0 ], - "inflate": 0.5 - } - ] - } - ], - "description": { - "identifier": "geometry.humanoid.earsSlim", - "texture_height": 64, - "texture_width": 64 - } - } - ] -} \ No newline at end of file From eade61c4284d68cfa040ac1d6cb7dd5649e86fee Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sun, 17 Mar 2024 21:20:46 +0000 Subject: [PATCH 04/15] Add failed back to skin --- api/src/main/java/org/geysermc/geyser/api/skin/Cape.java | 3 +++ api/src/main/java/org/geysermc/geyser/api/skin/Skin.java | 5 ++++- .../main/java/org/geysermc/geyser/skin/ProvidedSkins.java | 2 +- .../main/java/org/geysermc/geyser/skin/SkinProvider.java | 6 +++--- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java index 620729a75..0f0e2e003 100644 --- a/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java +++ b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java @@ -26,4 +26,7 @@ package org.geysermc.geyser.api.skin; public record Cape(String textureUrl, String capeId, byte[] capeData, boolean failed) { + public Cape(String textureUrl, String capeId, byte[] capeData) { + this(textureUrl, capeId, capeData, false); + } } diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java index c7bd763d0..b8fdf3164 100644 --- a/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java +++ b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java @@ -25,5 +25,8 @@ package org.geysermc.geyser.api.skin; -public record Skin(String textureUrl, byte[] skinData) { +public record Skin(String textureUrl, byte[] skinData, boolean failed) { + public Skin(String textureUrl, byte[] skinData) { + this(textureUrl, skinData, false); + } } diff --git a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java index c2647044a..23d75a00b 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java +++ b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java @@ -95,7 +95,7 @@ public final class ProvidedSkins { image.flush(); String identifier = "geysermc:" + assetName + "_" + (slim ? "slim" : "wide"); - this.data = new Skin(identifier, byteData); + this.data = new Skin(identifier, byteData, true); } catch (IOException e) { e.printStackTrace(); } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index a6c6d6c12..5b34a222c 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -115,7 +115,7 @@ public class SkinProvider { outputStream.write((rgba >> 24) & 0xFF); // Alpha } } - EMPTY_SKIN = new Skin("geysermc:empty", outputStream.toByteArray()); + EMPTY_SKIN = new Skin("geysermc:empty", outputStream.toByteArray(), true); /* Load in the custom skull geometry */ String skullData = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.customskull.json"), StandardCharsets.UTF_8); @@ -366,7 +366,7 @@ public class SkinProvider { } static void storeBedrockCape(String capeId, byte[] capeData) { - Cape cape = new Cape(capeId, capeId, capeData, false); + Cape cape = new Cape(capeId, capeId, capeData); CACHED_BEDROCK_CAPES.put(capeId, cape); } @@ -381,7 +381,7 @@ public class SkinProvider { return new Skin(textureUrl, skin); } catch (Exception ignored) {} // just ignore I guess - return new Skin("empty", EMPTY_SKIN.skinData()); + return new Skin("empty", EMPTY_SKIN.skinData(), true); } private static Cape supplyCape(String capeUrl, CapeProvider provider) { From 9c8b6de9bb493dfe84f5d6d16401cb9a85a17483 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sun, 17 Mar 2024 22:18:21 +0000 Subject: [PATCH 05/15] Accept suggestion on SkinGeometry Co-authored-by: chris --- .../main/java/org/geysermc/geyser/api/skin/SkinGeometry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java index 9c7cf9219..b9b30a630 100644 --- a/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java +++ b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java @@ -33,7 +33,7 @@ public record SkinGeometry(String geometryName, String geometryData) { /** * Generate generic geometry * - * @param isSlim Should it be the alex model + * @param isSlim if true, it will be the slimmer alex model * @return The generic geometry object */ private static SkinGeometry getLegacy(boolean isSlim) { From 48ac798da892e49e5e99829a52117dbd511fb84e Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sun, 17 Mar 2024 22:30:00 +0000 Subject: [PATCH 06/15] Add javadoc and update copyright --- .idea/copyright/Geyser.xml | 2 +- .idea/copyright/profiles_settings.xml | 2 +- .../main/java/org/geysermc/geyser/api/skin/Cape.java | 10 +++++++++- .../main/java/org/geysermc/geyser/api/skin/Skin.java | 9 ++++++++- .../java/org/geysermc/geyser/api/skin/SkinData.java | 4 ++-- .../org/geysermc/geyser/api/skin/SkinGeometry.java | 8 +++++++- .../org/geysermc/geyser/skin/FakeHeadProvider.java | 2 +- .../java/org/geysermc/geyser/skin/ProvidedSkins.java | 2 +- .../java/org/geysermc/geyser/skin/SkinManager.java | 2 +- .../java/org/geysermc/geyser/skin/SkinProvider.java | 9 +++------ .../org/geysermc/geyser/skin/SkullSkinManager.java | 2 +- 11 files changed, 35 insertions(+), 17 deletions(-) diff --git a/.idea/copyright/Geyser.xml b/.idea/copyright/Geyser.xml index c6b553aaf..f49412802 100644 --- a/.idea/copyright/Geyser.xml +++ b/.idea/copyright/Geyser.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml index f2d5911c9..09299b893 100644 --- a/.idea/copyright/profiles_settings.xml +++ b/.idea/copyright/profiles_settings.xml @@ -1,5 +1,5 @@ - + diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java index 0f0e2e003..6b8b3c072 100644 --- a/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java +++ b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,6 +25,14 @@ package org.geysermc.geyser.api.skin; +/** + * Represents a cape. + * + * @param textureUrl The URL of the cape texture + * @param capeId The ID of the cape + * @param capeData The raw cape image data + * @param failed If the cape failed to load, this is for things like fallback capes + */ public record Cape(String textureUrl, String capeId, byte[] capeData, boolean failed) { public Cape(String textureUrl, String capeId, byte[] capeData) { this(textureUrl, capeId, capeData, false); diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java index b8fdf3164..408a24add 100644 --- a/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java +++ b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,6 +25,13 @@ package org.geysermc.geyser.api.skin; +/** + * Represents a skin. + * + * @param textureUrl The URL/ID of the skin texture + * @param skinData The raw skin image data + * @param failed If the skin failed to load, this is for things like fallback skins + */ public record Skin(String textureUrl, byte[] skinData, boolean failed) { public Skin(String textureUrl, byte[] skinData) { this(textureUrl, skinData, false); diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java b/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java index 5f0060fee..9de4a3534 100644 --- a/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java +++ b/api/src/main/java/org/geysermc/geyser/api/skin/SkinData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -26,7 +26,7 @@ package org.geysermc.geyser.api.skin; /** - * Represents a full package of skin, cape, and geometry. + * Represents a full package of {@link Skin}, {@link Cape}, and {@link SkinGeometry}. */ public record SkinData(Skin skin, Cape cape, SkinGeometry geometry) { } diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java index b9b30a630..5b40d2022 100644 --- a/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java +++ b/api/src/main/java/org/geysermc/geyser/api/skin/SkinGeometry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * Copyright (c) 2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -25,6 +25,12 @@ package org.geysermc.geyser.api.skin; +/** + * Represents geometry of a skin. + * + * @param geometryName The name of the geometry (JSON) + * @param geometryData The geometry data (JSON) + */ public record SkinGeometry(String geometryName, String geometryData) { public static SkinGeometry WIDE = getLegacy(false); diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index 6fb0b5499..29309574c 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java index 23d75a00b..ba74fbb42 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java +++ b/core/src/main/java/org/geysermc/geyser/skin/ProvidedSkins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 3865e5573..434635c66 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 5b34a222c..be87e12e2 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -255,11 +255,8 @@ public class SkinProvider { cape = getCachedBedrockCape(entity.getUuid()); } - // TODO: Call event to allow extensions to modify the skin, cape and geo - // Pass in if the skin is from a Bedrock player or java - GeyserImpl geyser = GeyserImpl.getInstance(); - geyser.getLogger().debug("Processing skin, isBedrock: " + (geyser.connectionByUuid(entity.getUuid()) != null)); - boolean isBedrock = geyser.connectionByUuid(entity.getUuid()) != null; + // Call event to allow extensions to modify the skin, cape and geo + boolean isBedrock = GeyserImpl.getInstance().connectionByUuid(entity.getUuid()) != null; final SkinData[] skinData = {new SkinData(skin, cape, geometry)}; GeyserImpl.getInstance().eventBus().fire(new SkinApplyEvent(entity.getUsername(), entity.getUuid(), data.isAlex(), isBedrock, skinData[0]) { diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java index 744db1433..e3f00d3b7 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkullSkinManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal From f1dfe1ae247e7d5a222cafb1416a2780234b691b Mon Sep 17 00:00:00 2001 From: rtm516 Date: Mon, 18 Mar 2024 09:00:28 +0000 Subject: [PATCH 07/15] Remove old config options --- .../geyser/api/event/lifecycle/SkinApplyEvent.java | 5 +++++ .../geyser/configuration/GeyserConfiguration.java | 8 ++------ .../configuration/GeyserJacksonConfiguration.java | 8 +------- .../java/org/geysermc/geyser/skin/SkinProvider.java | 3 +-- core/src/main/resources/config.yml | 10 +--------- 5 files changed, 10 insertions(+), 24 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java index bd19e7d2a..dff1600cc 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java @@ -34,6 +34,11 @@ import org.geysermc.geyser.api.skin.SkinGeometry; import java.util.UUID; +/** + * Called when a skin is applied to a player. + *

+ * Won't be called when a fake player is spawned for a player skull. + */ public abstract class SkinApplyEvent implements Event { private final String username; diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index d12ab79e9..d3c8a2a00 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -44,7 +44,7 @@ public interface GeyserConfiguration { void setAutoconfiguredRemote(boolean autoconfiguredRemote); // Modify this when you introduce breaking changes into the config - int CURRENT_CONFIG_VERSION = 4; + int CURRENT_CONFIG_VERSION = 5; IBedrockConfiguration getBedrock(); @@ -72,10 +72,6 @@ public interface GeyserConfiguration { boolean isDebugMode(); - boolean isAllowThirdPartyCapes(); - - boolean isAllowThirdPartyEars(); - String getShowCooldown(); boolean isShowCoordinates(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index b3b7e8cd4..10ce4420d 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -91,9 +91,6 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("debug-mode") private boolean debugMode = false; - @JsonProperty("allow-third-party-capes") - private boolean allowThirdPartyCapes = true; - @JsonProperty("show-cooldown") private String showCooldown = "title"; @@ -107,9 +104,6 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("emote-offhand-workaround") private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED; - @JsonProperty("allow-third-party-ears") - private boolean allowThirdPartyEars = false; - @JsonProperty("default-locale") private String defaultLocale = null; // is null by default so system language takes priority diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index be87e12e2..991aea96d 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -61,7 +61,6 @@ import java.util.concurrent.*; import java.util.function.Predicate; public class SkinProvider { - private static final boolean ALLOW_THIRD_PARTY_CAPES = GeyserImpl.getInstance().getConfig().isAllowThirdPartyCapes(); private static ExecutorService EXECUTOR_SERVICE; static final Skin EMPTY_SKIN; @@ -130,7 +129,7 @@ public class SkinProvider { public static ExecutorService getExecutorService() { if (EXECUTOR_SERVICE == null) { - EXECUTOR_SERVICE = Executors.newFixedThreadPool(ALLOW_THIRD_PARTY_CAPES ? 21 : 14); + EXECUTOR_SERVICE = Executors.newFixedThreadPool(14); } return EXECUTOR_SERVICE; } diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 0617b316c..50fea68cd 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -113,14 +113,6 @@ max-players: 100 # If debug messages should be sent through console debug-mode: false -# Allow third party capes to be visible. Currently allowing: -# OptiFine capes, LabyMod capes, 5Zig capes and MinecraftCapes -allow-third-party-capes: false - -# Allow third party deadmau5 ears to be visible. Currently allowing: -# MinecraftCapes -allow-third-party-ears: false - # Allow a fake cooldown indicator to be sent. Bedrock players otherwise do not see a cooldown as they still use 1.8 combat. # Please note: if the cooldown is enabled, some users may see a black box during the cooldown sequence, like below: # https://cdn.discordapp.com/attachments/613170125696270357/957075682230419466/Screenshot_from_2022-03-25_20-35-08.png @@ -228,4 +220,4 @@ use-direct-connection: true # This requires use-direct-connection to be true. disable-compression: true -config-version: 4 +config-version: 5 From 2c8e4fefff424e7c1afa5a22d7deb6a5f6a6267f Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 19 Mar 2024 20:27:30 +0000 Subject: [PATCH 08/15] Make SkinApplyEvent a ConnectionEvent --- .../SessionSkinApplyEvent.java} | 10 ++++++---- .../org/geysermc/geyser/skin/FakeHeadProvider.java | 7 ++++--- .../java/org/geysermc/geyser/skin/SkinManager.java | 2 +- .../java/org/geysermc/geyser/skin/SkinProvider.java | 7 +++---- 4 files changed, 14 insertions(+), 12 deletions(-) rename api/src/main/java/org/geysermc/geyser/api/event/{lifecycle/SkinApplyEvent.java => bedrock/SessionSkinApplyEvent.java} (86%) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java similarity index 86% rename from api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java rename to api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java index dff1600cc..1fa11be59 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/lifecycle/SkinApplyEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java @@ -23,10 +23,11 @@ * @link https://github.com/GeyserMC/Geyser */ -package org.geysermc.geyser.api.event.lifecycle; +package org.geysermc.geyser.api.event.bedrock; import org.checkerframework.checker.nullness.qual.NonNull; -import org.geysermc.event.Event; +import org.geysermc.geyser.api.connection.GeyserConnection; +import org.geysermc.geyser.api.event.connection.ConnectionEvent; import org.geysermc.geyser.api.skin.Cape; import org.geysermc.geyser.api.skin.Skin; import org.geysermc.geyser.api.skin.SkinData; @@ -39,7 +40,7 @@ import java.util.UUID; *

* Won't be called when a fake player is spawned for a player skull. */ -public abstract class SkinApplyEvent implements Event { +public abstract class SessionSkinApplyEvent extends ConnectionEvent { private final String username; private final UUID uuid; @@ -47,7 +48,8 @@ public abstract class SkinApplyEvent implements Event { private final boolean isBedrock; private final SkinData skinData; - public SkinApplyEvent(String username, UUID uuid, boolean slim, boolean isBedrock, SkinData skinData) { + public SessionSkinApplyEvent(@NonNull GeyserConnection connection, String username, UUID uuid, boolean slim, boolean isBedrock, SkinData skinData) { + super(connection); this.username = username; this.uuid = uuid; this.slim = slim; diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index 29309574c..8be49b1ee 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -63,7 +63,7 @@ public class FakeHeadProvider { .build(new CacheLoader<>() { @Override public SkinData load(@NonNull FakeHeadEntry fakeHeadEntry) throws Exception { - SkinData skinData = SkinProvider.getOrDefault(SkinProvider.requestSkinData(fakeHeadEntry.getEntity()), null, 5); + SkinData skinData = SkinProvider.getOrDefault(SkinProvider.requestSkinData(fakeHeadEntry.getEntity(), fakeHeadEntry.getSession()), null, 5); if (skinData == null) { throw new Exception("Couldn't load player's original skin"); @@ -140,7 +140,7 @@ public class FakeHeadProvider { String texturesProperty = entity.getTexturesProperty(); SkinProvider.getExecutorService().execute(() -> { try { - SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity)); + SkinData mergedSkinData = MERGED_SKINS_LOADING_CACHE.get(new FakeHeadEntry(texturesProperty, fakeHeadSkinUrl, entity, session)); SkinManager.sendSkinPacket(session, entity, mergedSkinData); } catch (ExecutionException e) { GeyserImpl.getInstance().getLogger().error("Couldn't merge skin of " + entity.getUsername() + " with head skin url " + fakeHeadSkinUrl, e); @@ -157,7 +157,7 @@ public class FakeHeadProvider { return; } - SkinProvider.requestSkinData(entity).whenCompleteAsync((skinData, throwable) -> { + SkinProvider.requestSkinData(entity, session).whenCompleteAsync((skinData, throwable) -> { if (throwable != null) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), throwable); return; @@ -174,6 +174,7 @@ public class FakeHeadProvider { private final String texturesProperty; private final String fakeHeadSkinUrl; private PlayerEntity entity; + private GeyserSession session; @Override public boolean equals(Object o) { diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java index 434635c66..49bbf9e99 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinManager.java @@ -173,7 +173,7 @@ public class SkinManager { public static void requestAndHandleSkinAndCape(PlayerEntity entity, GeyserSession session, Consumer skinAndCapeConsumer) { - SkinProvider.requestSkinData(entity).whenCompleteAsync((skinData, throwable) -> { + SkinProvider.requestSkinData(entity, session).whenCompleteAsync((skinData, throwable) -> { if (skinData == null) { if (skinAndCapeConsumer != null) { skinAndCapeConsumer.accept(null); diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 991aea96d..5f365b035 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -35,7 +35,7 @@ import lombok.NoArgsConstructor; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.geyser.GeyserImpl; -import org.geysermc.geyser.api.event.lifecycle.SkinApplyEvent; +import org.geysermc.geyser.api.event.bedrock.SessionSkinApplyEvent; import org.geysermc.geyser.api.network.AuthType; import org.geysermc.geyser.api.skin.Cape; import org.geysermc.geyser.api.skin.Skin; @@ -232,7 +232,7 @@ public class SkinProvider { return CACHED_JAVA_CAPES.getIfPresent(capeUrl); } - static CompletableFuture requestSkinData(PlayerEntity entity) { + static CompletableFuture requestSkinData(PlayerEntity entity, GeyserSession session) { SkinManager.GameProfileData data = SkinManager.GameProfileData.from(entity); if (data == null) { // This player likely does not have a textures property @@ -257,8 +257,7 @@ public class SkinProvider { // Call event to allow extensions to modify the skin, cape and geo boolean isBedrock = GeyserImpl.getInstance().connectionByUuid(entity.getUuid()) != null; final SkinData[] skinData = {new SkinData(skin, cape, geometry)}; - GeyserImpl.getInstance().eventBus().fire(new SkinApplyEvent(entity.getUsername(), entity.getUuid(), data.isAlex(), isBedrock, skinData[0]) { - + GeyserImpl.getInstance().eventBus().fire(new SessionSkinApplyEvent(session, entity.getUsername(), entity.getUuid(), data.isAlex(), isBedrock, skinData[0]) { @Override public void skin(@NonNull Skin newSkin) { skinData[0] = new SkinData(newSkin, skinData[0].cape(), skinData[0].geometry()); From a394d6623559d0237bc711baa9947d3efe367967 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Tue, 19 Mar 2024 20:34:09 +0000 Subject: [PATCH 09/15] Add warning about third-party config options --- .../geyser/configuration/GeyserConfiguration.java | 8 +++++++- .../geyser/configuration/GeyserJacksonConfiguration.java | 8 +++++++- .../main/java/org/geysermc/geyser/skin/SkinProvider.java | 9 +++++++++ core/src/main/resources/config.yml | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java index d3c8a2a00..88bb98171 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserConfiguration.java @@ -44,7 +44,7 @@ public interface GeyserConfiguration { void setAutoconfiguredRemote(boolean autoconfiguredRemote); // Modify this when you introduce breaking changes into the config - int CURRENT_CONFIG_VERSION = 5; + int CURRENT_CONFIG_VERSION = 4; IBedrockConfiguration getBedrock(); @@ -72,6 +72,12 @@ public interface GeyserConfiguration { boolean isDebugMode(); + @Deprecated + boolean isAllowThirdPartyCapes(); + + @Deprecated + boolean isAllowThirdPartyEars(); + String getShowCooldown(); boolean isShowCoordinates(); diff --git a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java index 10ce4420d..b3b7e8cd4 100644 --- a/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java +++ b/core/src/main/java/org/geysermc/geyser/configuration/GeyserJacksonConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -91,6 +91,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("debug-mode") private boolean debugMode = false; + @JsonProperty("allow-third-party-capes") + private boolean allowThirdPartyCapes = true; + @JsonProperty("show-cooldown") private String showCooldown = "title"; @@ -104,6 +107,9 @@ public abstract class GeyserJacksonConfiguration implements GeyserConfiguration @JsonProperty("emote-offhand-workaround") private EmoteOffhandWorkaroundOption emoteOffhandWorkaround = EmoteOffhandWorkaroundOption.DISABLED; + @JsonProperty("allow-third-party-ears") + private boolean allowThirdPartyEars = false; + @JsonProperty("default-locale") private String defaultLocale = null; // is null by default so system language takes priority diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 5f365b035..7a6168135 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -125,6 +125,15 @@ public class SkinProvider { WEARING_CUSTOM_SKULL = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkull\"}}", wearingCustomSkull); String wearingCustomSkullSlim = new String(FileUtils.readAllBytes("bedrock/skin/geometry.humanoid.wearingCustomSkullSlim.json"), StandardCharsets.UTF_8); WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim); + + GeyserImpl geyser = GeyserImpl.getInstance(); + if (geyser.getConfig().isAllowThirdPartyEars()) { + geyser.getLogger().warning("Third-party ears have been removed from Geyser, if you still want this functionality please use the extension: https://github.com/GeyserMC/EarsExtension"); + } + if (geyser.getConfig().isAllowThirdPartyCapes()) { + // TODO Update warning once an extension is available + geyser.getLogger().warning("Third-party capes have been removed from Geyser, there is currently no replacement for this functionality"); + } } public static ExecutorService getExecutorService() { diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 50fea68cd..ac2267a10 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -220,4 +220,4 @@ use-direct-connection: true # This requires use-direct-connection to be true. disable-compression: true -config-version: 5 +config-version: 4 From 2d25a89e4e86bfa055fb45be1c16181aa998490b Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 25 Apr 2024 16:22:39 +0100 Subject: [PATCH 10/15] Update warning message --- .../main/java/org/geysermc/geyser/skin/SkinProvider.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 78b450db0..bf9ca9246 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -127,12 +127,8 @@ public class SkinProvider { WEARING_CUSTOM_SKULL_SLIM = new SkinGeometry("{\"geometry\" :{\"default\" :\"geometry.humanoid.wearingCustomSkullSlim\"}}", wearingCustomSkullSlim); GeyserImpl geyser = GeyserImpl.getInstance(); - if (geyser.getConfig().isAllowThirdPartyEars()) { - geyser.getLogger().warning("Third-party ears have been removed from Geyser, if you still want this functionality please use the extension: https://github.com/GeyserMC/EarsExtension"); - } - if (geyser.getConfig().isAllowThirdPartyCapes()) { - // TODO Update warning once an extension is available - geyser.getLogger().warning("Third-party capes have been removed from Geyser, there is currently no replacement for this functionality"); + if (geyser.getConfig().isAllowThirdPartyEars() || geyser.getConfig().isAllowThirdPartyCapes()) { + geyser.getLogger().warning("Third-party ears/capes have been removed from Geyser, if you still wish to have this functionality please use the extension: https://github.com/GeyserMC/ThirdPartyCosmetics"); } } From 9c1d252057b0f49b6ab0f47f70d16e58c143da93 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 25 Apr 2024 16:30:32 +0100 Subject: [PATCH 11/15] Add javadoc to event --- .../event/bedrock/SessionSkinApplyEvent.java | 60 +++++++++++++++++-- .../org/geysermc/geyser/api/skin/Cape.java | 2 +- .../org/geysermc/geyser/api/skin/Skin.java | 2 +- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java index 1fa11be59..7487a027a 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java @@ -45,42 +45,92 @@ public abstract class SessionSkinApplyEvent extends ConnectionEvent { private final String username; private final UUID uuid; private final boolean slim; - private final boolean isBedrock; + private final boolean bedrock; private final SkinData skinData; - public SessionSkinApplyEvent(@NonNull GeyserConnection connection, String username, UUID uuid, boolean slim, boolean isBedrock, SkinData skinData) { + public SessionSkinApplyEvent(@NonNull GeyserConnection connection, String username, UUID uuid, boolean slim, boolean bedrock, SkinData skinData) { super(connection); this.username = username; this.uuid = uuid; this.slim = slim; - this.isBedrock = isBedrock; + this.bedrock = bedrock; this.skinData = skinData; } + /** + * The username of the player. + * + * @return the username of the player + */ public String username() { return username; } + /** + * The UUID of the player. + * + * @return the UUID of the player + */ public UUID uuid() { return uuid; } + /** + * If the player is using a slim model. + * + * @return if the player is using a slim model + */ public boolean slim() { return slim; } - public boolean isBedrock() { - return isBedrock; + /** + * If the player is using a Bedrock skin. + * + * @return if the player is using a Bedrock skin + */ + public boolean bedrock() { + return bedrock; } + /** + * The skin data of the player. + * + * @return the skin data of the player + */ public SkinData skinData() { return skinData; } + /** + * Change the skin of the player. + * + * @param newSkin the new skin + */ public abstract void skin(@NonNull Skin newSkin); + + /** + * Change the cape of the player. + * + * @param newCape the new cape + */ public abstract void cape(@NonNull Cape newCape); + + /** + * Change the geometry of the player. + * + * @param newGeometry the new geometry + */ public abstract void geometry(@NonNull SkinGeometry newGeometry); + /** + * Change the geometry of the player. + *

+ * Constructs a generic {@link SkinGeometry} object with the given data. + * + * @param geometryName the name of the geometry + * @param geometryData the data of the geometry + */ public void geometry(@NonNull String geometryName, @NonNull String geometryData) { geometry(new SkinGeometry("{\"geometry\" :{\"default\" :\"" + geometryName + "\"}}", geometryData)); } diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java index 6b8b3c072..1e7341ae4 100644 --- a/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java +++ b/api/src/main/java/org/geysermc/geyser/api/skin/Cape.java @@ -30,7 +30,7 @@ package org.geysermc.geyser.api.skin; * * @param textureUrl The URL of the cape texture * @param capeId The ID of the cape - * @param capeData The raw cape image data + * @param capeData The raw cape image data in ARGB format * @param failed If the cape failed to load, this is for things like fallback capes */ public record Cape(String textureUrl, String capeId, byte[] capeData, boolean failed) { diff --git a/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java index 408a24add..9b39ddfe8 100644 --- a/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java +++ b/api/src/main/java/org/geysermc/geyser/api/skin/Skin.java @@ -29,7 +29,7 @@ package org.geysermc.geyser.api.skin; * Represents a skin. * * @param textureUrl The URL/ID of the skin texture - * @param skinData The raw skin image data + * @param skinData The raw skin image data in ARGB * @param failed If the skin failed to load, this is for things like fallback skins */ public record Skin(String textureUrl, byte[] skinData, boolean failed) { From 19d95a3193a7c3aba35b25ebbc7341676b6312a7 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Thu, 25 Apr 2024 16:34:03 +0100 Subject: [PATCH 12/15] Fix javadoc --- .../geyser/api/event/bedrock/SessionSkinApplyEvent.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java index 7487a027a..ca2f1a803 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java @@ -85,9 +85,9 @@ public abstract class SessionSkinApplyEvent extends ConnectionEvent { } /** - * If the player is using a Bedrock skin. + * If the player is a Bedrock player. * - * @return if the player is using a Bedrock skin + * @return if the player is a Bedrock player */ public boolean bedrock() { return bedrock; From d2a9ee53e84bf0d4e1682ed62105c6fb2595c991 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Fri, 26 Apr 2024 22:29:01 +0100 Subject: [PATCH 13/15] Ajust for review and bump version to 2.2.4 --- .idea/copyright/Geyser.xml | 1 + .../geyser/pack/SkullResourcePackManager.java | 4 +- .../geyser/skin/FakeHeadProvider.java | 1 + .../geysermc/geyser/skin/SkinProvider.java | 65 ++++--------------- gradle.properties | 2 +- 5 files changed, 19 insertions(+), 54 deletions(-) diff --git a/.idea/copyright/Geyser.xml b/.idea/copyright/Geyser.xml index f49412802..758c31cbd 100644 --- a/.idea/copyright/Geyser.xml +++ b/.idea/copyright/Geyser.xml @@ -1,5 +1,6 @@ + diff --git a/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java b/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java index f6c5140d8..12785a4a7 100644 --- a/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java +++ b/core/src/main/java/org/geysermc/geyser/pack/SkullResourcePackManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019-2022 GeyserMC. http://geysermc.org + * Copyright (c) 2019-2024 GeyserMC. http://geysermc.org * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -116,7 +116,7 @@ public class SkullResourcePackManager { return; } - BufferedImage image = SkinProvider.requestImage(skinUrl, null); + BufferedImage image = SkinProvider.requestImage(skinUrl, false); // Resize skins to 48x16 to save on space and memory BufferedImage skullTexture = new BufferedImage(48, 16, image.getType()); // Reorder skin parts to fit into the space diff --git a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java index 8be49b1ee..4c7247b10 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/FakeHeadProvider.java @@ -94,6 +94,7 @@ public class FakeHeadProvider { // Avoiding memory leak fakeHeadEntry.setEntity(null); + fakeHeadEntry.setSession(null); return new SkinData(mergedSkin, cape, geometry); } diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index bf9ca9246..ecab11e3e 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -292,10 +292,9 @@ public class SkinProvider { return CompletableFuture.supplyAsync(() -> { long time = System.currentTimeMillis(); - CapeProvider provider = capeUrl != null ? CapeProvider.MINECRAFT : null; SkinAndCape skinAndCape = new SkinAndCape( getOrDefault(requestSkin(playerId, skinUrl, false), EMPTY_SKIN, 5), - getOrDefault(requestCape(capeUrl, provider, false), EMPTY_CAPE, 5) + getOrDefault(requestCape(capeUrl, false), EMPTY_CAPE, 5) ); GeyserImpl.getInstance().getLogger().debug("Took " + (System.currentTimeMillis() - time) + "ms for " + playerId); @@ -332,7 +331,7 @@ public class SkinProvider { return future; } - private static CompletableFuture requestCape(String capeUrl, CapeProvider provider, boolean newThread) { + private static CompletableFuture requestCape(String capeUrl, boolean newThread) { if (capeUrl == null || capeUrl.isEmpty()) return CompletableFuture.completedFuture(EMPTY_CAPE); CompletableFuture requestedCape = requestedCapes.get(capeUrl); if (requestedCape != null) { @@ -346,14 +345,14 @@ public class SkinProvider { CompletableFuture future; if (newThread) { - future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl, provider), getExecutorService()) + future = CompletableFuture.supplyAsync(() -> supplyCape(capeUrl), getExecutorService()) .whenCompleteAsync((cape, throwable) -> { CACHED_JAVA_CAPES.put(capeUrl, cape); requestedCapes.remove(capeUrl); }); requestedCapes.put(capeUrl, future); } else { - Cape cape = supplyCape(capeUrl, provider); // blocking + Cape cape = supplyCape(capeUrl); // blocking future = CompletableFuture.completedFuture(cape); CACHED_JAVA_CAPES.put(capeUrl, cape); } @@ -377,17 +376,17 @@ public class SkinProvider { private static Skin supplySkin(UUID uuid, String textureUrl) { try { - byte[] skin = requestImageData(textureUrl, null); + byte[] skin = requestImageData(textureUrl, false); return new Skin(textureUrl, skin); } catch (Exception ignored) {} // just ignore I guess return new Skin("empty", EMPTY_SKIN.skinData(), true); } - private static Cape supplyCape(String capeUrl, CapeProvider provider) { + private static Cape supplyCape(String capeUrl) { byte[] cape = EMPTY_CAPE.capeData(); try { - cape = requestImageData(capeUrl, provider); + cape = requestImageData(capeUrl, true); } catch (Exception ignored) { } // just ignore I guess @@ -402,7 +401,7 @@ public class SkinProvider { } @SuppressWarnings("ResultOfMethodCallIgnored") - public static BufferedImage requestImage(String imageUrl, CapeProvider provider) throws IOException { + public static BufferedImage requestImage(String imageUrl, boolean isCape) throws IOException { BufferedImage image = null; // First see if we have a cached file. We also update the modification stamp so we know when the file was last used @@ -417,7 +416,7 @@ public class SkinProvider { // If no image we download it if (image == null) { - image = downloadImage(imageUrl, provider); + image = downloadImage(imageUrl); GeyserImpl.getInstance().getLogger().debug("Downloaded " + imageUrl); // Write to cache if we are allowed @@ -433,7 +432,7 @@ public class SkinProvider { } // if the requested image is a cape - if (provider != null) { + if (isCape) { if (image.getWidth() > 64 || image.getHeight() > 32) { // Prevent weirdly-scaled capes from being cut off BufferedImage newImage = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB); @@ -465,8 +464,8 @@ public class SkinProvider { return image; } - private static byte[] requestImageData(String imageUrl, CapeProvider provider) throws Exception { - BufferedImage image = requestImage(imageUrl, provider); + private static byte[] requestImageData(String imageUrl, boolean isCape) throws Exception { + BufferedImage image = requestImage(imageUrl, isCape); byte[] data = bufferedImageToImageData(image); image.flush(); return data; @@ -529,7 +528,7 @@ public class SkinProvider { }); } - private static BufferedImage downloadImage(String imageUrl, CapeProvider provider) throws IOException { + private static BufferedImage downloadImage(String imageUrl) throws IOException { HttpURLConnection con = (HttpURLConnection) new URL(imageUrl).openConnection(); con.setRequestProperty("User-Agent", WebUtils.getUserAgent()); con.setConnectTimeout(10000); @@ -538,7 +537,7 @@ public class SkinProvider { BufferedImage image = ImageIO.read(con.getInputStream()); if (image == null) { - throw new IllegalArgumentException("Failed to read image from: %s (cape provider=%s)".formatted(imageUrl, provider)); + throw new IllegalArgumentException("Failed to read image from: %s".formatted(imageUrl)); } return image; } @@ -615,40 +614,4 @@ public class SkinProvider { public record SkinAndCape(Skin skin, Cape cape) { } - - /* - * Sorted by 'priority' - */ - @AllArgsConstructor - @NoArgsConstructor - @Getter - public enum CapeProvider { - MINECRAFT; - - public static final CapeProvider[] VALUES = Arrays.copyOfRange(values(), 1, 5); - private String url; - private CapeUrlType type; - - public String getUrlFor(String type) { - return String.format(url, type); - } - - public String getUrlFor(UUID uuid, String username) { - return getUrlFor(toRequestedType(type, uuid, username)); - } - - public static String toRequestedType(CapeUrlType type, UUID uuid, String username) { - return switch (type) { - case UUID -> uuid.toString().replace("-", ""); - case UUID_DASHED -> uuid.toString(); - default -> username; - }; - } - } - - public enum CapeUrlType { - USERNAME, - UUID, - UUID_DASHED - } } diff --git a/gradle.properties b/gradle.properties index a7c0bf93d..962cae5c1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ org.gradle.vfs.watch=false group=org.geysermc id=geyser -version=2.2.3-SNAPSHOT +version=2.2.4-SNAPSHOT description=Allows for players from Minecraft: Bedrock Edition to join Minecraft: Java Edition servers. From 92e4439075ea812d84820cf5bd52c3e4ca23b503 Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 27 Apr 2024 00:00:41 +0100 Subject: [PATCH 14/15] Get rid of array and preserve original skin data --- .../geysermc/geyser/skin/SkinProvider.java | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index ecab11e3e..4be5f09a4 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -261,25 +261,26 @@ public class SkinProvider { // Call event to allow extensions to modify the skin, cape and geo boolean isBedrock = GeyserImpl.getInstance().connectionByUuid(entity.getUuid()) != null; - final SkinData[] skinData = {new SkinData(skin, cape, geometry)}; - GeyserImpl.getInstance().eventBus().fire(new SessionSkinApplyEvent(session, entity.getUsername(), entity.getUuid(), data.isAlex(), isBedrock, skinData[0]) { + SkinData skinData = new SkinData(skin, cape, geometry); + final EventSkinData eventSkinData = new EventSkinData(skinData); + GeyserImpl.getInstance().eventBus().fire(new SessionSkinApplyEvent(session, entity.getUsername(), entity.getUuid(), data.isAlex(), isBedrock, skinData) { @Override public void skin(@NonNull Skin newSkin) { - skinData[0] = new SkinData(newSkin, skinData[0].cape(), skinData[0].geometry()); + eventSkinData.skinData(new SkinData(newSkin, skinData.cape(), skinData.geometry())); } @Override public void cape(@NonNull Cape newCape) { - skinData[0] = new SkinData(skinData[0].skin(), newCape, skinData[0].geometry()); + eventSkinData.skinData(new SkinData(skinData.skin(), newCape, skinData.geometry())); } @Override public void geometry(@NonNull SkinGeometry newGeometry) { - skinData[0] = new SkinData(skinData[0].skin(), skinData[0].cape(), newGeometry); + eventSkinData.skinData(new SkinData(skinData.skin(), skinData.cape(), newGeometry)); } }); - return skinData[0]; + return eventSkinData.skinData(); } catch (Exception e) { GeyserImpl.getInstance().getLogger().error(GeyserLocale.getLocaleStringLog("geyser.skin.fail", entity.getUuid()), e); } @@ -614,4 +615,20 @@ public class SkinProvider { public record SkinAndCape(Skin skin, Cape cape) { } + + public static class EventSkinData { + private SkinData skinData; + + public EventSkinData(SkinData skinData) { + this.skinData = skinData; + } + + public SkinData skinData() { + return skinData; + } + + public void skinData(SkinData skinData) { + this.skinData = skinData; + } + } } From 46f19dd6f7b38dbdc8b3e954916bbb33e8d1c85e Mon Sep 17 00:00:00 2001 From: rtm516 Date: Sat, 27 Apr 2024 00:07:49 +0100 Subject: [PATCH 15/15] Add originalSkin method to event --- .../event/bedrock/SessionSkinApplyEvent.java | 17 ++++++++++++----- .../org/geysermc/geyser/skin/SkinProvider.java | 5 +++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java index ca2f1a803..5167625ea 100644 --- a/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java +++ b/api/src/main/java/org/geysermc/geyser/api/event/bedrock/SessionSkinApplyEvent.java @@ -46,7 +46,7 @@ public abstract class SessionSkinApplyEvent extends ConnectionEvent { private final UUID uuid; private final boolean slim; private final boolean bedrock; - private final SkinData skinData; + private final SkinData originalSkinData; public SessionSkinApplyEvent(@NonNull GeyserConnection connection, String username, UUID uuid, boolean slim, boolean bedrock, SkinData skinData) { super(connection); @@ -54,7 +54,7 @@ public abstract class SessionSkinApplyEvent extends ConnectionEvent { this.uuid = uuid; this.slim = slim; this.bedrock = bedrock; - this.skinData = skinData; + this.originalSkinData = skinData; } /** @@ -93,14 +93,21 @@ public abstract class SessionSkinApplyEvent extends ConnectionEvent { return bedrock; } + /** + * The original skin data of the player. + * + * @return the original skin data of the player + */ + public SkinData originalSkin() { + return originalSkinData; + } + /** * The skin data of the player. * * @return the skin data of the player */ - public SkinData skinData() { - return skinData; - } + public abstract SkinData skinData(); /** * Change the skin of the player. diff --git a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java index 4be5f09a4..587779c43 100644 --- a/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java +++ b/core/src/main/java/org/geysermc/geyser/skin/SkinProvider.java @@ -264,6 +264,11 @@ public class SkinProvider { SkinData skinData = new SkinData(skin, cape, geometry); final EventSkinData eventSkinData = new EventSkinData(skinData); GeyserImpl.getInstance().eventBus().fire(new SessionSkinApplyEvent(session, entity.getUsername(), entity.getUuid(), data.isAlex(), isBedrock, skinData) { + @Override + public SkinData skinData() { + return eventSkinData.skinData(); + } + @Override public void skin(@NonNull Skin newSkin) { eventSkinData.skinData(new SkinData(newSkin, skinData.cape(), skinData.geometry()));