Improvements to MultiMC metadata updating

Reformatted and refactored names to fit Kotlin conventions
Closed manifest file reader properly
Fixed switching between loaders, improved intermediary logic
Improved error messages
This commit is contained in:
comp500 2022-06-29 03:26:50 +01:00
parent ab3de9a246
commit 02b01b90d7
3 changed files with 102 additions and 91 deletions

View File

@ -11,98 +11,109 @@ import java.io.File
import java.nio.file.Paths import java.nio.file.Paths
class LauncherUtils internal constructor(private val opts: UpdateManager.Options, val ui: IUserInterface) { class LauncherUtils internal constructor(private val opts: UpdateManager.Options, val ui: IUserInterface) {
enum class LauncherStatus { enum class LauncherStatus {
Succesful, SUCCESSFUL,
NoChanges, NO_CHANGES,
Cancelled, CANCELLED,
NotFound, // We'll use the NotFound as the neutral return type for now NOT_FOUND, // When there is no mmc-pack.json file found (i.e. MultiMC is not being used)
} }
fun handleMultiMC(pf: PackFile, gson: Gson): LauncherStatus { fun handleMultiMC(pf: PackFile, gson: Gson): LauncherStatus {
// MultiMC MC and loader version checker // MultiMC MC and loader version checker
val manifestPath = Paths.get(opts.multimcFolder, "mmc-pack.json").toString() val manifestPath = Paths.get(opts.multimcFolder, "mmc-pack.json").toString()
val manifestFile = File(manifestPath) val manifestFile = File(manifestPath)
if (!manifestFile.exists()) { if (!manifestFile.exists()) {
return LauncherStatus.NotFound return LauncherStatus.NOT_FOUND
} }
val multimcManifest = try { val multimcManifest = manifestFile.reader().use {
JsonParser.parseReader(manifestFile.reader()) try {
} catch (e: JsonIOException) { JsonParser.parseReader(it)
throw Exception("Cannot read the MultiMC pack file", e) } catch (e: JsonIOException) {
} catch (e: JsonSyntaxException) { throw Exception("Cannot read the MultiMC pack file", e)
throw Exception("Invalid MultiMC pack file", e) } catch (e: JsonSyntaxException) {
}.asJsonObject throw Exception("Invalid MultiMC pack file", e)
}.asJsonObject
}
Log.info("Loaded MultiMC config") Log.info("Loaded MultiMC config")
// We only support format 1, if it gets updated in the future we'll have to handle that // We only support format 1, if it gets updated in the future we'll have to handle that
// There's only version 1 for now tho, so that's good // There's only version 1 for now tho, so that's good
if (multimcManifest["formatVersion"].asInt != 1) { if (multimcManifest["formatVersion"]?.asInt != 1) {
throw Exception("Invalid MultiMC format version") throw Exception("Unsupported MultiMC format version ${multimcManifest["formatVersion"]}")
} }
var manifestModified = false var manifestModified = false
val modLoaders = hashMapOf("net.minecraft" to "minecraft", "net.minecraftforge" to "forge", "net.fabricmc.fabric-loader" to "fabric", "org.quiltmc.quilt-loader" to "quilt", "com.mumfrey.liteloader" to "liteloader") val modLoaders = hashMapOf(
val modLoadersClasses = modLoaders.entries.associate{(k,v)-> v to k} "net.minecraft" to "minecraft",
var modLoaderFound = false "net.minecraftforge" to "forge",
val modLoadersFound = HashMap<String, String>() // Key: modLoader, Value: Version "net.fabricmc.fabric-loader" to "fabric",
val components = multimcManifest["components"].asJsonArray "org.quiltmc.quilt-loader" to "quilt",
for (componentObj in components) { "com.mumfrey.liteloader" to "liteloader")
val component = componentObj.asJsonObject val modLoadersClasses = modLoaders.entries.associate{(k,v)-> v to k}
val loaderVersionsFound = HashMap<String, String?>()
val outdatedLoaders = mutableSetOf<String>()
val components = multimcManifest["components"]?.asJsonArray ?: throw Exception("Invalid mmc-pack.json: no components key")
components.removeAll {
val component = it.asJsonObject
val version = component["version"].asString val version = component["version"]?.asString
// If we find any of the modloaders we support, we save it and check the version // If we find any of the modloaders we support, we save it and check the version
if (modLoaders.containsKey(component["uid"].asString)) { if (modLoaders.containsKey(component["uid"]?.asString)) {
val modLoader = modLoaders.getValue(component["uid"].asString) val modLoader = modLoaders.getValue(component["uid"]!!.asString)
if (modLoader != "minecraft") loaderVersionsFound[modLoader] = version
modLoaderFound = true // Only set to true if modLoader isn't Minecraft if (version != pf.versions?.get(modLoader)) {
modLoadersFound[modLoader] = version outdatedLoaders.add(modLoader)
if (version != pf.versions?.get(modLoader)) { true // Delete component; cached metadata is invalid and will be re-added
manifestModified = true } else {
component.addProperty("version", pf.versions?.get(modLoader)) false // Already up to date; cached metadata is valid
} }
} } else { false } // Not a known loader / MC
} }
// If we can't find the mod loader in the MultiMC file, we add it for ((_, loader) in modLoaders
if (!modLoaderFound) { .filter {
// Using this filter and loop to handle multiple handlers (!loaderVersionsFound.containsKey(it.value) || outdatedLoaders.contains(it.value))
for ((_, loader) in modLoaders && pf.versions?.containsKey(it.value) == true }
.filter { it.value != "minecraft" && !modLoadersFound.containsKey(it.value) && pf.versions?.containsKey(it.value) == true } ) {
) { manifestModified = true
components.add(gson.toJsonTree(hashMapOf("uid" to modLoadersClasses.get(loader), "version" to pf.versions?.get(loader)))) components.add(gson.toJsonTree(
} hashMapOf("uid" to modLoadersClasses[loader], "version" to pf.versions?.get(loader)))
} )
}
// If mc version change detected, and fabric mappings are found, delete them, MultiMC will add and re-dl the correct one // If inconsistent Intermediary mappings version is found, delete it - MultiMC will add and re-dl the correct one
if (modLoadersFound["minecraft"] != pf.versions?.getValue("minecraft")) components.find { it.isJsonObject && it.asJsonObject["uid"]?.asString == "net.fabricmc.intermediary" }?.let {
components.find { it.asJsonObject["uid"].asString == "net.fabricmc.intermediary" }?.asJsonObject?.let { components.remove(it) } if (it.asJsonObject["version"]?.asString != pf.versions?.get("minecraft")) {
components.remove(it)
manifestModified = true
}
}
if (manifestModified) { if (manifestModified) {
// The manifest has been modified, so before saving it we'll ask the user // The manifest has been modified, so before saving it we'll ask the user
// if they wanna update it, continue without updating it, or exit // if they wanna update it, continue without updating it, or exit
val oldVers = modLoadersFound.map { Pair(it.key, it.value) } val oldVers = loaderVersionsFound.map { Pair(it.key, it.value) }
val newVers = pf.versions!!.map { Pair(it.key, it.value) } val newVers = pf.versions!!.map { Pair(it.key, it.value) }
when (ui.showUpdateConfirmationDialog(oldVers, newVers)) {
IUserInterface.UpdateConfirmationResult.CANCELLED -> {
return LauncherStatus.CANCELLED
}
IUserInterface.UpdateConfirmationResult.CONTINUE -> {
return LauncherStatus.SUCCESSFUL
}
else -> {}
}
when (ui.showUpdateConfirmationDialog(oldVers, newVers)) { manifestFile.writeText(gson.toJson(multimcManifest))
IUserInterface.UpdateConfirmationResult.CANCELLED -> { Log.info("Successfully updated mmc-pack.json based on version metadata")
return LauncherStatus.Cancelled
}
IUserInterface.UpdateConfirmationResult.CONTINUE -> {
return LauncherStatus.Succesful // Returning succesful as... Well, the user is telling us to continue
}
else -> {} // Compiler is giving warning about "non-exhaustive when", so i'll just add an empty one
}
manifestFile.writeText(gson.toJson(multimcManifest)) return LauncherStatus.SUCCESSFUL
Log.info("Updated modpack Minecrafts and/or the modloaders version") }
return LauncherStatus.Succesful return LauncherStatus.NO_CHANGES
} }
return LauncherStatus.NoChanges
}
} }

View File

@ -105,8 +105,9 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
ui.submitProgress(InstallProgress("Loading MultiMC pack file...")) ui.submitProgress(InstallProgress("Loading MultiMC pack file..."))
try { try {
when (lu.handleMultiMC(pf, gson)) { when (lu.handleMultiMC(pf, gson)) {
LauncherUtils.LauncherStatus.Cancelled -> cancelled = true LauncherUtils.LauncherStatus.CANCELLED -> cancelled = true
LauncherUtils.LauncherStatus.NotFound -> Log.info("MultiMC not detected") LauncherUtils.LauncherStatus.NOT_FOUND -> Log.info("MultiMC not detected")
else -> {}
} }
handleCancellation() handleCancellation()
} catch (e: Exception) { } catch (e: Exception) {

View File

@ -202,17 +202,16 @@ class GUIHandler : IUserInterface {
val options = arrayOf("Cancel", "Continue anyways", "Update") val options = arrayOf("Cancel", "Continue anyways", "Update")
val result = JOptionPane.showOptionDialog(frmPackwizlauncher, val result = JOptionPane.showOptionDialog(frmPackwizlauncher, message,
message,
"Updating MultiMC versions", "Updating MultiMC versions",
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]) JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[2])
future.complete( future.complete(
when (result) { when (result) {
JOptionPane.CLOSED_OPTION, 0 -> IUserInterface.UpdateConfirmationResult.CANCELLED JOptionPane.CLOSED_OPTION, 0 -> IUserInterface.UpdateConfirmationResult.CANCELLED
1 -> IUserInterface.UpdateConfirmationResult.CONTINUE 1 -> IUserInterface.UpdateConfirmationResult.CONTINUE
2 -> IUserInterface.UpdateConfirmationResult.UPDATE 2 -> IUserInterface.UpdateConfirmationResult.UPDATE
else -> IUserInterface.UpdateConfirmationResult.CANCELLED else -> IUserInterface.UpdateConfirmationResult.CANCELLED
} }
) )
} }
return future.get() return future.get()