24
24
import com .google .common .collect .Lists ;
25
25
import com .mojang .authlib .GameProfile ;
26
26
import com .mojang .authlib .properties .Property ;
27
+ import java .lang .invoke .MethodType ;
27
28
import org .bukkit .Bukkit ;
28
29
import org .bukkit .OfflinePlayer ;
29
30
import org .bukkit .block .Block ;
55
56
* <ul>
56
57
* <li>https://minecraft-heads.com/</li>
57
58
* </ul>
59
+ * <p>
60
+ * The basic premise behind this API is that the final skull data is contained in a {@link GameProfile}
61
+ * either by ID, name or encoded textures URL property.
58
62
*
59
63
* @author Crypto Morin
60
- * @version 3.1.1
64
+ * @version 4.0.3
61
65
* @see XMaterial
62
66
*/
63
67
public class XSkull {
64
68
protected static final MethodHandle
65
69
CRAFT_META_SKULL_PROFILE_GETTER , CRAFT_META_SKULL_PROFILE_SETTER ,
66
- CRAFT_META_SKULL_BLOCK_SETTER ;
70
+ CRAFT_META_SKULL_BLOCK_SETTER , PROPERTY_GETVALUE ;
67
71
68
72
/**
69
73
* Some people use this without quotes surrounding the keys, not sure what that'd work.
@@ -84,6 +88,17 @@ public class XSkull {
84
88
*/
85
89
private static final Pattern MOJANG_SHA256_APPROX = Pattern .compile ("[0-9a-z]{60,70}" );
86
90
91
+ /**
92
+ * In v1.20.2 there were some changes to the mojang API.
93
+ */
94
+ private static final boolean NULLABILITY_RECORD_UPDATE = ReflectionUtils .VERSION .equals ("v1_20_R2" );
95
+
96
+ /**
97
+ * Does using a random UUID have any advantage?
98
+ */
99
+ private static final UUID GAME_PROFILE_EMPTY_UUID = NULLABILITY_RECORD_UPDATE ? new UUID (0 , 0 ) : null ;
100
+ private static final String GAME_PROFILE_EMPTY_NAME = NULLABILITY_RECORD_UPDATE ? "" : null ;
101
+
87
102
/**
88
103
* The value after this URL is probably an SHA-252 value that Mojang uses to unique identify player skins.
89
104
* <br>
@@ -94,7 +109,7 @@ public class XSkull {
94
109
95
110
static {
96
111
MethodHandles .Lookup lookup = MethodHandles .lookup ();
97
- MethodHandle profileSetter = null , profileGetter = null , blockSetter = null ;
112
+ MethodHandle profileSetter = null , profileGetter = null , blockSetter = null , propGetval = null ;
98
113
99
114
try {
100
115
Class <?> CraftMetaSkull = ReflectionUtils .getCraftClass ("inventory.CraftMetaSkull" );
@@ -110,7 +125,7 @@ public class XSkull {
110
125
} catch (NoSuchMethodException e ) {
111
126
profileSetter = lookup .unreflectSetter (profile );
112
127
}
113
- } catch (NoSuchFieldException | IllegalAccessException e ) {
128
+ } catch (Throwable e ) {
114
129
e .printStackTrace ();
115
130
}
116
131
@@ -124,6 +139,16 @@ public class XSkull {
124
139
e .printStackTrace ();
125
140
}
126
141
142
+ if (!NULLABILITY_RECORD_UPDATE ) {
143
+ try {
144
+ //noinspection JavaLangInvokeHandleSignature
145
+ propGetval = lookup .findVirtual (Property .class , "getValue" , MethodType .methodType (String .class ));
146
+ } catch (Throwable ex ) {
147
+ ex .printStackTrace ();
148
+ }
149
+ }
150
+
151
+ PROPERTY_GETVALUE = propGetval ;
127
152
CRAFT_META_SKULL_PROFILE_SETTER = profileSetter ;
128
153
CRAFT_META_SKULL_PROFILE_GETTER = profileGetter ;
129
154
CRAFT_META_SKULL_BLOCK_SETTER = blockSetter ;
@@ -192,7 +217,9 @@ protected static SkullMeta setSkullBase64(@NotNull SkullMeta head, @NotNull Stri
192
217
193
218
@ NotNull
194
219
public static GameProfile profileFromBase64 (String value ) {
195
- GameProfile profile = new GameProfile (UUID .randomUUID (), null );
220
+ // Use an empty string instead of null for the name parameter because it's now null-checked since 1.20.2.
221
+ // It doesn't seem to affect functionality.
222
+ GameProfile profile = new GameProfile (GAME_PROFILE_EMPTY_UUID , GAME_PROFILE_EMPTY_NAME );
196
223
profile .getProperties ().put ("textures" , new Property ("textures" , value ));
197
224
return profile ;
198
225
}
@@ -206,8 +233,8 @@ public static GameProfile profileFromPlayer(OfflinePlayer player) {
206
233
public static GameProfile detectProfileFromString (String identifier ) {
207
234
// @formatter:off sometimes programming is just art that a machine can't understand :)
208
235
switch (detectSkullValueType (identifier )) {
209
- case UUID : return new GameProfile (UUID .fromString ( identifier ), null );
210
- case NAME : return new GameProfile (null , identifier );
236
+ case UUID : return new GameProfile (UUID .fromString ( identifier ), GAME_PROFILE_EMPTY_NAME );
237
+ case NAME : return new GameProfile (GAME_PROFILE_EMPTY_UUID , identifier );
211
238
case BASE64 : return profileFromBase64 ( identifier );
212
239
case TEXTURE_URL : return profileFromBase64 (encodeTexturesURL ( identifier ));
213
240
case TEXTURE_HASH : return profileFromBase64 (encodeTexturesURL (TEXTURES + identifier ));
@@ -284,14 +311,32 @@ public static ItemBuilder.SkullTexture getSkinValue(@NotNull ItemMeta skull) {
284
311
}
285
312
if (profile != null && !profile .getProperties ().get ("textures" ).isEmpty ()) {
286
313
for (Property property : profile .getProperties ().get ("textures" )) {
287
- if (!property .getValue ().isEmpty ()) {
288
- return new ItemBuilder .SkullTexture (property .getValue (), profile .getId ());
314
+ String value = getPropertyValue (property );
315
+ if (!value .isEmpty ()) {
316
+ return new ItemBuilder .SkullTexture (value , profile .getId ());
289
317
}
290
318
}
291
319
}
292
320
return null ;
293
321
}
294
322
323
+ /**
324
+ * They changed {@link Property} to a Java record in 1.20.2
325
+ *
326
+ * @since 4.0.1
327
+ */
328
+ private static String getPropertyValue (Property property ) {
329
+ if (NULLABILITY_RECORD_UPDATE ) {
330
+ return property .value ();
331
+ } else {
332
+ try {
333
+ return (String ) PROPERTY_GETVALUE .invoke (property );
334
+ } catch (Throwable e ) {
335
+ throw new RuntimeException (e );
336
+ }
337
+ }
338
+ }
339
+
295
340
/**
296
341
* https://help.minecraft.net/hc/en-us/articles/360034636712
297
342
*
0 commit comments