Skip to content

Commit 46ad2da

Browse files
committed
Fix several XSkull issues related to 1.21.9 support
Fixes #376
1 parent 7172abc commit 46ad2da

File tree

21 files changed

+364
-174
lines changed

21 files changed

+364
-174
lines changed

.github/ISSUE_TEMPLATE/bug-report.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ labels: 'bug'
66
assignees: CryptoMorin
77
---
88

9+
Example of a good bug report: https://github.com/CryptoMorin/XSeries/issues/376
10+
911
## Description
1012
A clear and concise description of what the bug is.
1113

commons/src/main/java/com/cryptomorin/xseries/profiles/gameprofile/MojangGameProfile.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@
2424

2525
import com.cryptomorin.xseries.AbstractReferencedClass;
2626
import com.google.common.collect.Iterables;
27+
import com.google.common.collect.Multimap;
2728
import com.mojang.authlib.GameProfile;
2829
import com.mojang.authlib.properties.Property;
2930
import com.mojang.authlib.properties.PropertyMap;
31+
import org.checkerframework.checker.nullness.qual.Nullable;
3032

3133
import java.util.UUID;
34+
import java.util.function.Consumer;
3235

3336
public abstract class MojangGameProfile extends AbstractReferencedClass<GameProfile> {
3437
protected final GameProfile object;
@@ -46,16 +49,13 @@ public final GameProfile object() {
4649

4750
public abstract PropertyMap properties();
4851

49-
public abstract void addProperty(Property property);
50-
51-
public abstract void addProperty(String name, String value);
52-
53-
public abstract void removeProperty(String name);
54-
55-
public abstract void setProperty(String name, String value);
52+
public final MojangGameProfile copy() {
53+
return copy(null);
54+
}
5655

57-
public abstract MojangGameProfile copy();
56+
public abstract MojangGameProfile copy(@Nullable Consumer<PropertyModifier> propertyModifier);
5857

58+
@Nullable
5959
public Property getProperty(String name) {
6060
return Iterables.getFirst(this.properties().get(name), null);
6161
}
Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,29 +23,28 @@
2323
package com.cryptomorin.xseries.profiles.gameprofile;
2424

2525
import com.google.common.collect.Multimap;
26-
import com.google.common.collect.MultimapBuilder;
2726
import com.mojang.authlib.properties.Property;
28-
import com.mojang.authlib.properties.PropertyMap;
2927

30-
import java.util.UUID;
31-
32-
public final class GameProfilerBuilder {
33-
private final String name;
34-
private final UUID id;
28+
public final class PropertyModifier {
3529
private final Multimap<String, Property> properties;
3630

37-
public GameProfilerBuilder(String name, UUID id) {
38-
this.name = name;
39-
this.id = id;
40-
this.properties = MultimapBuilder.hashKeys().arrayListValues().build();
31+
public PropertyModifier(Multimap<String, Property> properties) {this.properties = properties;}
32+
33+
public void setProperty(String name, String value) {
34+
properties.removeAll(name);
35+
properties.put(name, new Property(name, value));
36+
}
37+
38+
public void setProperty(String name, Property value) {
39+
properties.removeAll(name);
40+
properties.put(name, value);
4141
}
4242

43-
public GameProfilerBuilder addProperty(String name, String value) {
44-
this.properties.put(name, new Property(name, value));
45-
return this;
43+
public void removeProperty(String name) {
44+
properties.removeAll(name);
4645
}
4746

48-
public MojangGameProfile build() {
49-
return XGameProfile.create(id, name, new PropertyMap(properties));
47+
public Multimap<String, Property> getProperties() {
48+
return properties;
5049
}
5150
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2025 Crypto Morin
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
17+
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
18+
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
19+
* FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20+
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
*/
22+
23+
package com.cryptomorin.xseries.profiles.gameprofile.property;
24+
25+
import com.cryptomorin.xseries.AbstractReferencedClass;
26+
import com.mojang.authlib.properties.Property;
27+
import org.checkerframework.checker.nullness.qual.NonNull;
28+
import org.checkerframework.checker.nullness.qual.Nullable;
29+
30+
public abstract class MojangProperty extends AbstractReferencedClass<Property> {
31+
protected final Property object;
32+
33+
protected MojangProperty(Property object) {this.object = object;}
34+
35+
@Override
36+
public final Property object() {
37+
return object;
38+
}
39+
40+
41+
@NonNull
42+
public abstract String name();
43+
44+
@NonNull
45+
public abstract String value();
46+
47+
@Nullable
48+
public abstract String signature();
49+
50+
@NonNull
51+
public final Property copy() {
52+
return new Property(name(), value(), signature());
53+
}
54+
}

core/src/main/java/com/cryptomorin/xseries/profiles/PlayerProfiles.java

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@
2424

2525
import com.cryptomorin.xseries.profiles.gameprofile.MojangGameProfile;
2626
import com.cryptomorin.xseries.profiles.gameprofile.XGameProfile;
27+
import com.cryptomorin.xseries.profiles.gameprofile.property.XProperty;
2728
import com.cryptomorin.xseries.profiles.objects.transformer.ProfileTransformer;
2829
import com.cryptomorin.xseries.reflection.XReflection;
29-
import com.google.common.collect.ImmutableMultimap;
30-
import com.google.common.collect.ListMultimap;
31-
import com.google.common.collect.MultimapBuilder;
3230
import com.google.gson.Gson;
3331
import com.google.gson.GsonBuilder;
3432
import com.google.gson.JsonObject;
@@ -38,10 +36,10 @@
3836
import com.mojang.authlib.properties.PropertyMap;
3937
import org.bukkit.Bukkit;
4038
import org.jetbrains.annotations.ApiStatus;
39+
import org.jetbrains.annotations.Contract;
4140
import org.jetbrains.annotations.NotNull;
4241
import org.jetbrains.annotations.Nullable;
4342

44-
import java.lang.reflect.Field;
4543
import java.nio.charset.StandardCharsets;
4644
import java.util.Base64;
4745
import java.util.Objects;
@@ -63,7 +61,7 @@ public final class PlayerProfiles {
6361
* It's not needed to change it every time, but it should be changed
6462
* if the XSeries internals are changed.
6563
*/
66-
private static final Property XSERIES_GAMEPROFILE_SIGNATURE = new Property(XSERIES_SIG, XReflection.XSERIES_VERSION);
64+
private static final Property XSERIES_GAMEPROFILE_SIGNATURE = XProperty.create(XSERIES_SIG, XReflection.XSERIES_VERSION).object();
6765
private static final String TEXTURES_PROPERTY = "textures";
6866

6967
public static final GameProfile NIL = createGameProfile(PlayerUUIDs.IDENTITY_UUID, XSERIES_SIG).object();
@@ -159,8 +157,7 @@ public static boolean hasTextures(MojangGameProfile profile) {
159157
public static MojangGameProfile profileFromHashAndBase64(String hash, String base64) {
160158
java.util.UUID uuid = java.util.UUID.nameUUIDFromBytes(hash.getBytes(StandardCharsets.UTF_8));
161159
MojangGameProfile profile = PlayerProfiles.createNamelessGameProfile(uuid);
162-
PlayerProfiles.setTexturesProperty(profile, base64);
163-
return profile;
160+
return PlayerProfiles.setTexturesProperty(profile, base64);
164161
}
165162

166163
@SuppressWarnings("deprecation")
@@ -180,7 +177,7 @@ public static void removeTimestamp(MojangGameProfile profile) {
180177
* Tries to unwrap a {@link GameProfile} from a {@link net.minecraft.world.item.component.ResolvableProfile}.
181178
*/
182179
@Nullable
183-
public static GameProfile unwrapProfile(@Nullable Object profile) throws Throwable {
180+
public static GameProfile fromResolvableProfile(@Nullable Object profile) throws Throwable {
184181
if (profile == null) return null;
185182
if (!(profile instanceof GameProfile) && ProfilesCore.ResolvableProfile_gameProfile != null) {
186183
// Unwrap from ResolvableProfile
@@ -190,7 +187,7 @@ public static GameProfile unwrapProfile(@Nullable Object profile) throws Throwab
190187
}
191188

192189
@Nullable
193-
public static Object wrapProfile(@Nullable MojangGameProfile profile) throws Throwable {
190+
public static Object toResolvableProfile(@Nullable MojangGameProfile profile) throws Throwable {
194191
if (profile == null) return null;
195192
if (ProfilesCore.ResolvableProfile$bukkitSupports) {
196193
return ProfilesCore.ResolvableProfile$constructor.invoke(profile.object());
@@ -223,9 +220,9 @@ public static GameProfile sanitizeProfile(GameProfile gameProfile) {
223220
return clone.object();
224221
}
225222

226-
public static void setTexturesProperty(MojangGameProfile profile, String texture) {
227-
profile.removeProperty(TEXTURES_PROPERTY);
228-
profile.addProperty(TEXTURES_PROPERTY, texture);
223+
@Contract(pure = true)
224+
public static MojangGameProfile setTexturesProperty(MojangGameProfile profile, String texture) {
225+
return profile.copy(x -> x.setProperty(TEXTURES_PROPERTY, texture));
229226
}
230227

231228
/**
@@ -234,6 +231,7 @@ public static void setTexturesProperty(MojangGameProfile profile, String texture
234231
* @param str The string to encode.
235232
* @return The Base64 encoded string.
236233
*/
234+
@Contract(pure = true)
237235
public static String encodeBase64(String str) {
238236
return Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8));
239237
}
@@ -245,6 +243,7 @@ public static String encodeBase64(String str) {
245243
* @return the decoded Base64 string if it is a valid Base64 string, or null if not.
246244
*/
247245
@Nullable
246+
@Contract(pure = true)
248247
public static String decodeBase64(String base64) {
249248
Objects.requireNonNull(base64, "Cannot decode null string");
250249
try {
@@ -255,10 +254,12 @@ public static String decodeBase64(String base64) {
255254
}
256255
}
257256

257+
@Contract(pure = true)
258258
public static MojangGameProfile createGameProfile(UUID uuid, String username) {
259259
return signXSeries(XGameProfile.create(uuid, username));
260260
}
261261

262+
@Contract(pure = true)
262263
public static MojangGameProfile createGameProfile(UUID uuid, String username, PropertyMap properties) {
263264
return signXSeries(XGameProfile.create(uuid, username, properties));
264265
}
@@ -268,27 +269,15 @@ public static MojangGameProfile createGameProfile(UUID uuid, String username, Pr
268269
* purposes, specially since we're directly messing with the server's internal cache
269270
* it should be there in case something goes wrong.
270271
*/
272+
@Contract(pure = true)
271273
public static MojangGameProfile signXSeries(MojangGameProfile profile) {
272274
// Just as an indicator that this is not a vanilla-created profile.
273-
PropertyMap properties = profile.properties();
274275
// I don't think a single profile is being signed multiple times.
275276
// Even if it was, it might be helpful?
276-
// properties.asMap().remove(DEFAULT_PROFILE_NAME); // Remove previous versions if any.
277-
try {
278-
Field props = PropertyMap.class.getDeclaredField("properties");
279-
props.setAccessible(true);
280-
ListMultimap<String, Property> newProps = MultimapBuilder.hashKeys().arrayListValues().build();
281-
newProps.putAll(properties);
282-
newProps.put(XSERIES_SIG, XSERIES_GAMEPROFILE_SIGNATURE);
283-
props.set(properties, ImmutableMultimap.copyOf(newProps));
284-
285-
// properties.put(XSERIES_SIG, XSERIES_GAMEPROFILE_SIGNATURE);
286-
} catch (NoSuchFieldException | IllegalAccessException e) {
287-
throw new RuntimeException(e);
288-
}
289-
return profile;
277+
return profile.copy(x -> x.setProperty(XSERIES_SIG, XSERIES_GAMEPROFILE_SIGNATURE));
290278
}
291279

280+
@Contract(pure = true)
292281
public static MojangGameProfile createNamelessGameProfile(UUID id) {
293282
return createGameProfile(id, XSERIES_SIG);
294283
}

core/src/main/java/com/cryptomorin/xseries/profiles/ProfilesCore.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,9 @@ public final class ProfilesCore {
154154
// Added by Bukkit
155155
Object minecraftServer = MinecraftServer.method("public static MinecraftServer getServer()").reflect().invoke();
156156

157-
// Services is a record class.
157+
// Services is a record class, it existed even in 1.21.8 but we don't need to switch.
158158
MinecraftClassHandle Services = ns.ofMinecraft("package nms.server; public class Services");
159-
boolean usesServices = Services.exists();
159+
boolean usesServices = XReflection.supports(1, 21, 9) && Services.exists();
160160
Object services = null;
161161
if (usesServices) {
162162
services = MinecraftServer.method("public Services services()")

core/src/main/java/com/cryptomorin/xseries/profiles/builder/XSkull.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,13 @@ public static ProfileInstruction<ItemStack> createItem() {
9494
/**
9595
* Creates a {@link ProfileInstruction} for an {@link ItemStack}.
9696
*
97-
* @param stack The {@link ItemStack} to set the profile for.
97+
* @param item The {@link ItemStack} to set the profile for.
9898
* @return A {@link ProfileInstruction} that sets the profile for the given {@link ItemStack}.
9999
*/
100100
@NotNull
101101
@Contract(value = "_ -> new", pure = true)
102-
public static ProfileInstruction<ItemStack> of(@NotNull ItemStack stack) {
103-
return new ProfileInstruction<>(new ProfileContainer.ItemStackProfileContainer(stack));
102+
public static ProfileInstruction<ItemStack> of(@NotNull ItemStack item) {
103+
return new ProfileInstruction<>(new ProfileContainer.ItemStackProfileContainer(item));
104104
}
105105

106106
/**

core/src/main/java/com/cryptomorin/xseries/profiles/gameprofile/XGameProfile.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.cryptomorin.xseries.profiles.gameprofile;
22

3+
import com.cryptomorin.xseries.reflection.XReflection;
34
import com.mojang.authlib.GameProfile;
45
import com.mojang.authlib.properties.PropertyMap;
56
import org.checkerframework.checker.nullness.qual.Nullable;
@@ -11,20 +12,7 @@
1112
public class XGameProfile {
1213
private XGameProfile() {}
1314

14-
private static final boolean USE_RECORDS;
15-
16-
static {
17-
boolean useRecords;
18-
19-
try {
20-
Method isRecord = Class.class.getDeclaredMethod("isRecord");
21-
useRecords = (boolean) isRecord.invoke(GameProfile.class);
22-
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
23-
useRecords = false;
24-
}
25-
26-
USE_RECORDS = useRecords;
27-
}
15+
private static final boolean USE_RECORDS = XReflection.isRecord(GameProfile.class);
2816

2917
@Nullable
3018
public static MojangGameProfile of(GameProfile gameProfile) {
@@ -34,7 +22,14 @@ public static MojangGameProfile of(GameProfile gameProfile) {
3422
}
3523

3624
public static MojangGameProfile create(UUID id, String name, PropertyMap properties) {
37-
return of(new GameProfile(id, name, properties));
25+
if (USE_RECORDS) {
26+
return of(new GameProfile(id, name, properties));
27+
} else {
28+
GameProfile gameProfile = new GameProfile(id, name);
29+
MojangGameProfile converted = of(gameProfile);
30+
converted.properties().putAll(properties);
31+
return converted;
32+
}
3833
}
3934

4035
public static MojangGameProfile create(UUID id, String name) {

0 commit comments

Comments
 (0)