Launcher incorrectly serializes version data
The bug
The Java launcher incorrectly serializes version data of the new format. This prevents playing versions which use the new version file format in offline mode.
How to reproduce
- Start a Minecraft version higher than or equal to 17w43b
- Look in the Launcher Log tab of your launcher
→ The following error was logged[19:16:51 INFO]: Refreshing local version list... [19:16:51 ERROR]: Couldn't load local version .minecraft\versions\17w43b\17w43b.json java.lang.NullPointerException at net.minecraft.launcher.updater.Argument$Serializer.deserialize(Argument.java:56) ~[launcher.jar:1.6.84-j] at net.minecraft.launcher.updater.Argument$Serializer.deserialize(Argument.java:46) ~[launcher.jar:1.6.84-j] at com.google.gson.TreeTypeAdapter.read(TreeTypeAdapter.java:58) ~[launcher.jar:1.6.84-j] at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40) ~[launcher.jar:1.6.84-j] at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:81) ~[launcher.jar:1.6.84-j] at com.google.gson.internal.bind.CollectionTypeAdapterFactory$Adapter.read(CollectionTypeAdapterFactory.java:60) ~[launcher.jar:1.6.84-j] at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:40) ~[launcher.jar:1.6.84-j] at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:187) ~[launcher.jar:1.6.84-j] at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145) ~[launcher.jar:1.6.84-j] at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:93) ~[launcher.jar:1.6.84-j] at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:172) ~[launcher.jar:1.6.84-j] at com.google.gson.Gson.fromJson(Gson.java:803) ~[launcher.jar:1.6.84-j] at com.google.gson.Gson.fromJson(Gson.java:768) ~[launcher.jar:1.6.84-j] at com.google.gson.Gson.fromJson(Gson.java:717) ~[launcher.jar:1.6.84-j] at com.google.gson.Gson.fromJson(Gson.java:689) ~[launcher.jar:1.6.84-j] at net.minecraft.launcher.updater.LocalVersionList.refreshVersions(LocalVersionList.java:46) [launcher.jar:1.6.84-j] at net.minecraft.launcher.updater.MinecraftVersionManager.refreshVersions(MinecraftVersionManager.java:60) [launcher.jar:1.6.84-j] at net.minecraft.launcher.Launcher$3.run(Launcher.java:184) [launcher.jar:1.6.84-j] at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) [?:1.8.0_151] at java.util.concurrent.FutureTask.run(Unknown Source) [?:1.8.0_151] at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) [?:1.8.0_151] at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) [?:1.8.0_151] at java.lang.Thread.run(Unknown Source) [?:1.8.0_151]
Code analysis
The problem is caused by the class net.minecraft.launcher.updater.Argument. This class is missing a proper serializer and therefore when called by net.minecraft.launcher.updater.VersionList.serializeVersion(CompleteVersion) all fields are serialized. This causes two problems:
- Deserializing requires a key value, but the field is called values
- For rules the key rules is expected, but the field is called compatibilityRules
The following is rough implementation added to the class net.minecraft.launcher.updater.Argument.Serializer after having it implement com.google.gson.JsonSerializer<Argument> as well. Please verify that this works as expected and does not miss any corner case before implementing it.
@Override public JsonElement serialize(Argument argument, Type type, JsonSerializationContext serializationContext) { if (argument.values.length == 0) { throw new IllegalStateException("Don't know how to serialize Argument with no values"); } else if (argument.values.length == 1 && (argument.compatibilityRules == null || argument.compatibilityRules.isEmpty())) { return new JsonPrimitive(argument.values[0]); } else { JsonObject argumentObject = new JsonObject(); if (argument.values.length == 1) { argumentObject.addProperty("value", argument.values[0]); } else { argumentObject.add("value", serializationContext.serialize(argument.values)); } if (!(argument.compatibilityRules == null || argument.compatibilityRules.isEmpty())) { argumentObject.add("rules", serializationContext.serialize(argument.compatibilityRules, new TypeToken<ArrayList<CompatibilityRule>>(){}.getType())); } return argumentObject; } }
Affected versions
Currently tracked here since selectable launcher versions here in Mojira are not always up to date
- 1.6.84-j
2017-11-07, 08:56 PM
2020-07-14, 03:55 PM
2020-07-14, 03:55 PM
5
5
-