Skip to content

Commit

Permalink
feat!: Recode SkullItemCreator to not use XSeries's XSkull class
Browse files Browse the repository at this point in the history
XSkull is having a couple of issues and just throwing exceptions for a couple of people.
No fix in sight etc. so let's get rid of it and have our own implementation.
  • Loading branch information
SpraxDev committed Sep 20, 2024
1 parent 6483e0d commit 656d3b9
Show file tree
Hide file tree
Showing 30 changed files with 561 additions and 28 deletions.
102 changes: 74 additions & 28 deletions Core/src/main/java/com/craftaro/core/utils/SkullItemCreator.java
Original file line number Diff line number Diff line change
@@ -1,58 +1,104 @@
package com.craftaro.core.utils;

import com.craftaro.core.nms.Nms;
import com.cryptomorin.xseries.profiles.builder.XSkull;
import com.cryptomorin.xseries.profiles.objects.ProfileInputType;
import com.cryptomorin.xseries.profiles.objects.Profileable;
import org.bukkit.Bukkit;
import com.craftaro.core.nms.entity.player.GameProfile;
import com.cryptomorin.xseries.XMaterial;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.UUID;

public final class SkullItemCreator {
private static final String STEVE_TEXTURE = "ewogICJ0aW1lc3RhbXAiIDogMTYyMTcxNTMxMjI5MCwKICAicHJvZmlsZUlkIiA6ICJiNTM5NTkyMjMwY2I0MmE0OWY5YTRlYmYxNmRlOTYwYiIsCiAgInByb2ZpbGVOYW1lIiA6ICJtYXJpYW5hZmFnIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzFhNGFmNzE4NDU1ZDRhYWI1MjhlN2E2MWY4NmZhMjVlNmEzNjlkMTc2OGRjYjEzZjdkZjMxOWE3MTNlYjgxMGIiCiAgICB9CiAgfQp9";
private static final String ALEX_TEXTURE = "rZvLQoZsgLYaoKqEuASopYAs7IAlZlsGkwagoM8ZX38cP9kalseZrWY5OHZVfoiftdQJ+lGOzkiFfyx6kNJDTZniLrnRa8sd3X6D65ZihT1sOm/RInCwxpS1K0zGCM2h9ErkWswfwaviIf7hJtrwk8/zL0bfzDk2IgX/IBvIZpVoYTfmQsVY9jgSwORrS9ObePGIfFgmThMoZnCYWQMVpS2+yTFA2wnw9hmisQK9UWBU+iBZv55bMmkMcyEuXw1w14DaEu+/M0UGD91LU4GmJLPA9T4GCuIV8GxOcraSVIajki1cMlOBQwIaibB2NE6KAwq1Zh6NnsNYucy6qFM+136lXfBchQ1Nx4FDRZQgt8VRqTMy/OQFpr2nTbWWbRU4gRFpKC3R0518DqUH0Qm612kPWniKku/QzUUBSe1PSVljBaZCyyRx0OB1a1/8MexboKRnPXuTDnmPa9UPfuH4VO0q+qYkjV2KUzP6e5vIP5aQ6USPrMie7MmAHFJzwAMIbLjgkTVx91GWtYqg/t7qBlvrdBRLIPPsy/DSOqa+2+4hABouVCPZrBMCMLzstPPQoqZAyiCqcKb2HqWSU0h9Bhx19yoIcbHCeI3zsQs8PqIBjUL4mO6VQT4lzHy0e3M61Xsdd8S1GtsakSetTvEtMdUwCEDfBA5PRRTLOVYTY+g=";

public static ItemStack byPlayer(Player player) {
if (Bukkit.getOnlineMode()) {
return XSkull.createItem().profile(new Profileable.PlayerProfileable(player)).apply();
}

String textureValue = Nms.getImplementations().getPlayer().getProfile(player).getTextureValue();
if (textureValue != null) {
return byTextureValue(textureValue);
}
public static ItemStack byProfile(GameProfile profile) {
ItemStack item = Objects.requireNonNull(XMaterial.PLAYER_HEAD.parseItem());
SkullMeta meta = (SkullMeta) Objects.requireNonNull(item.getItemMeta());
applyProfile(meta, profile);
item.setItemMeta(meta);
return item;
}

return createDefaultSkull(player.getUniqueId());
public static ItemStack byPlayer(Player player) {
return byProfile(Nms.getImplementations().getPlayer().getProfile(player));
}

public static ItemStack byUuid(UUID uuid) {
return XSkull.createItem().profile(new Profileable.UUIDProfileable(uuid)).apply();
public static ItemStack byTextureValue(String textureValue) {
return byProfile(Nms.getImplementations().getPlayer().createProfileByTextureValue(textureValue));
}

public static ItemStack byUsername(String username) {
return XSkull.createItem().profile(new Profileable.StringProfileable(username, ProfileInputType.USERNAME)).apply();
public static ItemStack byTextureUrl(String textureUrl) {
return byProfile(Nms.getImplementations().getPlayer().createProfileByUrl(textureUrl));
}

public static ItemStack byTextureValue(String textureValue) {
return XSkull.createItem().profile(new Profileable.StringProfileable(textureValue, ProfileInputType.BASE64)).apply();
public static ItemStack byTextureUrlHash(String textureUrlHash) {
return byTextureUrl("https://textures.minecraft.net/texture/" + textureUrlHash);
}

public static ItemStack byTextureUrl(String textureUrl) {
return XSkull.createItem().profile(new Profileable.StringProfileable(textureUrl, ProfileInputType.TEXTURE_URL)).apply();
public static ItemStack createSteve() {
return byTextureValue(STEVE_TEXTURE);
}

public static ItemStack byTextureHash(String textureHash) {
return XSkull.createItem().profile(new Profileable.StringProfileable(textureHash, ProfileInputType.TEXTURE_HASH)).apply();
public static ItemStack createAlex() {
return byTextureValue(ALEX_TEXTURE);
}

private static ItemStack createDefaultSkull(UUID uuid) {
String textureValue = STEVE_TEXTURE;
public static ItemStack createDefaultSkullForUuid(UUID uuid) {
if ((uuid.hashCode() & 1) != 0) {
textureValue = ALEX_TEXTURE;
return byTextureValue(ALEX_TEXTURE);
}
return byTextureValue(STEVE_TEXTURE);
}

private static Method skullMetaSetProfile = null;
private static Field skullMetaProfileField = null;
private static boolean setProfileUsesResolvable = false;

private static void applyProfile(SkullMeta skullMeta, GameProfile profile) {
if (skullMetaSetProfile == null && profile.getMojangResolvableGameProfile() != null) {
try {
skullMetaSetProfile = skullMeta.getClass().getDeclaredMethod("setProfile", profile.getMojangResolvableGameProfile().getClass());
skullMetaSetProfile.setAccessible(true);
setProfileUsesResolvable = true;
} catch (ReflectiveOperationException ignored) {
}
}
if (skullMetaSetProfile == null) {
try {
skullMetaSetProfile = skullMeta.getClass().getDeclaredMethod("setProfile", profile.getMojangGameProfile().getClass());
skullMetaSetProfile.setAccessible(true);
} catch (ReflectiveOperationException ignored) {
}
}

return XSkull.createItem().profile(new Profileable.StringProfileable(textureValue, ProfileInputType.BASE64)).apply();
if (skullMetaSetProfile != null) {
try {
skullMetaSetProfile.invoke(skullMeta, setProfileUsesResolvable ? profile.getMojangResolvableGameProfile() : profile.getMojangGameProfile());
} catch (IllegalAccessException | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
return;
}

if (skullMetaProfileField == null) {
try {
skullMetaProfileField = skullMeta.getClass().getDeclaredField("profile");
skullMetaProfileField.setAccessible(true);
} catch (ReflectiveOperationException ex) {
throw new RuntimeException("Unable to find compatible #setProfile method or profile field", ex);
}
}

try {
skullMetaProfileField.set(skullMeta, profile.getMojangGameProfile());
} catch (IllegalAccessException | IllegalArgumentException ex) {
throw new RuntimeException("Encountered an error while setting the profile field", ex);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,27 @@

import com.craftaro.core.nms.entity.player.GameProfile;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.UUID;

public interface NMSPlayer {
void sendPacket(Player p, Object packet);

GameProfile getProfile(Player p);

GameProfile createProfile(UUID id, String name, @Nullable String textureValue);

default GameProfile createProfileByUrl(String url) {
UUID id = UUID.nameUUIDFromBytes(("SongodaCore:" + url).getBytes(StandardCharsets.UTF_8));
String rawTextureValue = "{\"textures\":{\"SKIN\":{\"url\":\"" + url + "\"}}}";
return createProfile(id, "by_SongodaCore", Base64.getEncoder().encodeToString(rawTextureValue.getBytes()));
}

default GameProfile createProfileByTextureValue(String textureValue) {
UUID id = UUID.nameUUIDFromBytes(("SongodaCore:" + textureValue).getBytes(StandardCharsets.UTF_8));
return createProfile(id, "by_SongodaCore", textureValue);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

public class GameProfile {
private final Object mojangGameProfile;
private final Object mojangResolvableGameProfile;

private final UUID id;
private final String name;
Expand All @@ -16,13 +17,16 @@ public class GameProfile {

public GameProfile(
Object mojangGameProfile,
@Nullable Object mojangResolvableGameProfile,

UUID id,
String name,
@Nullable String textureValue,
@Nullable String textureSignature
) {
this.mojangGameProfile = Objects.requireNonNull(mojangGameProfile);
this.mojangResolvableGameProfile = mojangResolvableGameProfile;


this.id = Objects.requireNonNull(id);
this.name = Objects.requireNonNull(name);
Expand All @@ -34,6 +38,10 @@ public Object getMojangGameProfile() {
return this.mojangGameProfile;
}

public @Nullable Object getMojangResolvableGameProfile() {
return this.mojangResolvableGameProfile;
}

public @NotNull UUID getId() {
return this.id;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import net.minecraft.server.v1_10_R1.Packet;
import org.bukkit.craftbukkit.v1_10_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;

public class NMSPlayerImpl implements NMSPlayer {
@Override
Expand All @@ -16,7 +19,20 @@ public void sendPacket(Player p, Object packet) {
@Override
public GameProfile getProfile(Player p) {
com.mojang.authlib.GameProfile profile = ((CraftPlayer) p).getHandle().getProfile();
return wrapProfile(profile);
}

@Override
public GameProfile createProfile(UUID id, String name, @Nullable String textureValue) {
com.mojang.authlib.GameProfile profile = new com.mojang.authlib.GameProfile(id, name);
if (textureValue != null) {
profile.getProperties().put("textures", new Property("textures", textureValue));
}

return wrapProfile(profile);
}

private GameProfile wrapProfile(com.mojang.authlib.GameProfile profile) {
String textureValue = null;
String textureSignature = null;
for (Property property : profile.getProperties().get("textures")) {
Expand All @@ -28,6 +44,7 @@ public GameProfile getProfile(Player p) {

return new GameProfile(
profile,
null,

profile.getId(),
profile.getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import net.minecraft.server.v1_11_R1.Packet;
import org.bukkit.craftbukkit.v1_11_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;

public class NMSPlayerImpl implements NMSPlayer {
@Override
Expand All @@ -16,7 +19,20 @@ public void sendPacket(Player p, Object packet) {
@Override
public GameProfile getProfile(Player p) {
com.mojang.authlib.GameProfile profile = ((CraftPlayer) p).getHandle().getProfile();
return wrapProfile(profile);
}

@Override
public GameProfile createProfile(UUID id, String name, @Nullable String textureValue) {
com.mojang.authlib.GameProfile profile = new com.mojang.authlib.GameProfile(id, name);
if (textureValue != null) {
profile.getProperties().put("textures", new Property("textures", textureValue));
}

return wrapProfile(profile);
}

private GameProfile wrapProfile(com.mojang.authlib.GameProfile profile) {
String textureValue = null;
String textureSignature = null;
for (Property property : profile.getProperties().get("textures")) {
Expand All @@ -28,6 +44,7 @@ public GameProfile getProfile(Player p) {

return new GameProfile(
profile,
null,

profile.getId(),
profile.getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import net.minecraft.server.v1_12_R1.Packet;
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;

public class NMSPlayerImpl implements NMSPlayer {
@Override
Expand All @@ -16,7 +19,20 @@ public void sendPacket(Player p, Object packet) {
@Override
public GameProfile getProfile(Player p) {
com.mojang.authlib.GameProfile profile = ((CraftPlayer) p).getHandle().getProfile();
return wrapProfile(profile);
}

@Override
public GameProfile createProfile(UUID id, String name, @Nullable String textureValue) {
com.mojang.authlib.GameProfile profile = new com.mojang.authlib.GameProfile(id, name);
if (textureValue != null) {
profile.getProperties().put("textures", new Property("textures", textureValue));
}

return wrapProfile(profile);
}

private GameProfile wrapProfile(com.mojang.authlib.GameProfile profile) {
String textureValue = null;
String textureSignature = null;
for (Property property : profile.getProperties().get("textures")) {
Expand All @@ -28,6 +44,7 @@ public GameProfile getProfile(Player p) {

return new GameProfile(
profile,
null,

profile.getId(),
profile.getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import net.minecraft.server.v1_13_R1.Packet;
import org.bukkit.craftbukkit.v1_13_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;

public class NMSPlayerImpl implements NMSPlayer {
@Override
Expand All @@ -16,7 +19,20 @@ public void sendPacket(Player p, Object packet) {
@Override
public GameProfile getProfile(Player p) {
com.mojang.authlib.GameProfile profile = ((CraftPlayer) p).getHandle().getProfile();
return wrapProfile(profile);
}

@Override
public GameProfile createProfile(UUID id, String name, @Nullable String textureValue) {
com.mojang.authlib.GameProfile profile = new com.mojang.authlib.GameProfile(id, name);
if (textureValue != null) {
profile.getProperties().put("textures", new Property("textures", textureValue));
}

return wrapProfile(profile);
}

private GameProfile wrapProfile(com.mojang.authlib.GameProfile profile) {
String textureValue = null;
String textureSignature = null;
for (Property property : profile.getProperties().get("textures")) {
Expand All @@ -28,6 +44,7 @@ public GameProfile getProfile(Player p) {

return new GameProfile(
profile,
null,

profile.getId(),
profile.getName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
import net.minecraft.server.v1_13_R2.Packet;
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;

public class NMSPlayerImpl implements NMSPlayer {
@Override
Expand All @@ -16,7 +19,20 @@ public void sendPacket(Player p, Object packet) {
@Override
public GameProfile getProfile(Player p) {
com.mojang.authlib.GameProfile profile = ((CraftPlayer) p).getHandle().getProfile();
return wrapProfile(profile);
}

@Override
public GameProfile createProfile(UUID id, String name, @Nullable String textureValue) {
com.mojang.authlib.GameProfile profile = new com.mojang.authlib.GameProfile(id, name);
if (textureValue != null) {
profile.getProperties().put("textures", new Property("textures", textureValue));
}

return wrapProfile(profile);
}

private GameProfile wrapProfile(com.mojang.authlib.GameProfile profile) {
String textureValue = null;
String textureSignature = null;
for (Property property : profile.getProperties().get("textures")) {
Expand All @@ -28,6 +44,7 @@ public GameProfile getProfile(Player p) {

return new GameProfile(
profile,
null,

profile.getId(),
profile.getName(),
Expand Down
Loading

0 comments on commit 656d3b9

Please sign in to comment.