Skip to content

Commit a11b403

Browse files
committed
Fix GameProfile properties in 1.21.9+
Fixes #3564
1 parent aa4fab1 commit a11b403

File tree

4 files changed

+101
-21
lines changed

4 files changed

+101
-21
lines changed

src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,10 @@ public static Class<?> getGameProfileClass() {
553553
return getClass("com.mojang.authlib.GameProfile");
554554
}
555555

556+
public static Class<?> getGameProfilePropertyMapClass() {
557+
return getClass("com.mojang.authlib.properties.PropertyMap");
558+
}
559+
556560
/**
557561
* Retrieve the entity (NMS) class.
558562
*
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.comphenix.protocol.wrappers;
2+
3+
import com.google.common.collect.HashMultimap;
4+
import com.google.common.collect.ImmutableMultimap;
5+
import com.google.common.collect.Multimap;
6+
import com.mojang.authlib.properties.Property;
7+
import com.mojang.authlib.properties.PropertyMap;
8+
9+
/**
10+
* Mutable version of PropertyMap
11+
* Credit to PaperMC: https://github.com/PaperMC/Paper/blob/main/paper-server/src/main/java/io/papermc/paper/profile/MutablePropertyMap.java
12+
*/
13+
public class MutablePropertyMap extends PropertyMap {
14+
private final Multimap<String, Property> properties = HashMultimap.create();
15+
16+
public MutablePropertyMap() {
17+
super(ImmutableMultimap.of());
18+
}
19+
20+
public MutablePropertyMap(final Multimap<String, Property> properties) {
21+
this();
22+
this.putAll(properties);
23+
}
24+
25+
@Override
26+
protected Multimap<String, Property> delegate() {
27+
return this.properties;
28+
}
29+
}

src/main/java/com/comphenix/protocol/wrappers/WrappedGameProfile.java

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class WrappedGameProfile extends AbstractWrapper {
3535
GAME_PROFILE, String.class, String.class);
3636
private static final ConstructorAccessor CREATE_UUID_STRING = Accessors.getConstructorAccessorOrNull(
3737
GAME_PROFILE, UUID.class, String.class);
38+
private static final ConstructorAccessor CREATE_UUID_STRING_PROPERTIES = Accessors.getConstructorAccessorOrNull(
39+
GAME_PROFILE, UUID.class, String.class, MinecraftReflection.getGameProfilePropertyMapClass());
3840

3941
private static final FieldAccessor GET_UUID_STRING = Accessors.getFieldAccessorOrNull(
4042
GAME_PROFILE, "id", String.class);
@@ -127,6 +129,51 @@ public WrappedGameProfile(String id, String name) {
127129
this(parseUUID(id), name);
128130
}
129131

132+
private static Object createHandle(UUID uuid, String name, Multimap<String, WrappedSignedProperty> properties) {
133+
if (CREATE_STRING_STRING != null) {
134+
return CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name);
135+
}
136+
137+
if (CREATE_UUID_STRING == null) {
138+
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
139+
}
140+
141+
if (!MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
142+
return CREATE_UUID_STRING.invoke(uuid, name);
143+
}
144+
145+
// 1.20.2+ requires all fields to have a value: null uuid -> UUID(0,0), null name -> empty name
146+
// it's not allowed to pass null for both, so we need to pre-check that
147+
if (uuid == null && (name == null || name.isEmpty())) {
148+
throw new IllegalArgumentException("Name and ID cannot both be blank");
149+
}
150+
151+
// 1.21.9+ made PropertyMap's underlying map immutable, so we need to override it with a mutable map
152+
if (MinecraftVersion.v1_21_9.atOrAbove()) {
153+
return CREATE_UUID_STRING_PROPERTIES.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name,
154+
convertPropertyMap(properties));
155+
}
156+
157+
return CREATE_UUID_STRING.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name);
158+
}
159+
160+
private static Object convertPropertyMap(Multimap<String, WrappedSignedProperty> properties) {
161+
com.comphenix.protocol.wrappers.MutablePropertyMap map =
162+
new com.comphenix.protocol.wrappers.MutablePropertyMap();
163+
164+
if (properties == null || properties.isEmpty()) {
165+
return map;
166+
}
167+
168+
for (String key : properties.keySet()) {
169+
for (WrappedSignedProperty property : properties.get(key)) {
170+
map.put(key, (com.mojang.authlib.properties.Property) property.getHandle());
171+
}
172+
}
173+
174+
return map;
175+
}
176+
130177
/**
131178
* Construct a new game profile with the given properties.
132179
* <p>
@@ -137,24 +184,12 @@ public WrappedGameProfile(String id, String name) {
137184
*/
138185
public WrappedGameProfile(UUID uuid, String name) {
139186
super(GAME_PROFILE);
187+
setHandle(createHandle(uuid, name, null));
188+
}
140189

141-
if (CREATE_STRING_STRING != null) {
142-
setHandle(CREATE_STRING_STRING.invoke(uuid != null ? uuid.toString() : null, name));
143-
} else if (CREATE_UUID_STRING != null) {
144-
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
145-
// 1.20.2+ requires all fields to have a value: null uuid -> UUID(0,0), null name -> empty name
146-
// it's not allowed to pass null for both, so we need to pre-check that
147-
if (uuid == null && (name == null || name.isEmpty())) {
148-
throw new IllegalArgumentException("Name and ID cannot both be blank");
149-
}
150-
151-
setHandle(CREATE_UUID_STRING.invoke(uuid == null ? MinecraftGenerator.SYS_UUID : uuid, name == null ? "" : name));
152-
} else {
153-
setHandle(CREATE_UUID_STRING.invoke(uuid, name));
154-
}
155-
} else {
156-
throw new IllegalArgumentException("Unsupported GameProfile constructor.");
157-
}
190+
public WrappedGameProfile(UUID uuid, String name, Multimap<String, WrappedSignedProperty> properties) {
191+
super(GAME_PROFILE);
192+
setHandle(createHandle(uuid, name, properties));
158193
}
159194

160195
/**
@@ -167,7 +202,12 @@ public static WrappedGameProfile fromHandle(Object handle) {
167202
if (handle == null)
168203
return null;
169204

170-
return new WrappedGameProfile(handle);
205+
WrappedGameProfile delegate = new WrappedGameProfile(handle);
206+
if (MinecraftVersion.v1_21_9.atOrAbove()) {
207+
return new WrappedGameProfile(delegate.getUUID(), delegate.getName(), delegate.getProperties());
208+
} else {
209+
return delegate;
210+
}
171211
}
172212

173213
/**
@@ -279,7 +319,15 @@ public Multimap<String, WrappedSignedProperty> getProperties() {
279319

280320
if (result == null) {
281321
Multimap properties = (Multimap) GET_PROPERTIES.invoke(handle);
282-
result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(GuavaWrappers.getBukkitMultimap(properties)) {
322+
323+
Multimap inner;
324+
if (MinecraftVersion.v1_21_9.atOrAbove()) {
325+
inner = properties;
326+
} else {
327+
inner = GuavaWrappers.getBukkitMultimap(properties);
328+
}
329+
330+
result = new ConvertedMultimap<String, Object, WrappedSignedProperty>(inner) {
283331
@Override
284332
protected Object toInner(WrappedSignedProperty outer) {
285333
return outer.handle;

src/test/java/com/comphenix/protocol/wrappers/WrappedGameProfileTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ void testGetProperties() {
6767
assertEquals(property.getSignature(), signature);
6868
}
6969

70-
// @Test
71-
// TODO: backing map was changed to be immutable
70+
@Test
7271
void testAddProperties() {
7372
String name = "test";
7473
String value = "test";

0 commit comments

Comments
 (0)