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