diff --git a/src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt b/src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt index 1e9eb05..fb0c43b 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt @@ -11,98 +11,109 @@ import java.io.File import java.nio.file.Paths class LauncherUtils internal constructor(private val opts: UpdateManager.Options, val ui: IUserInterface) { - enum class LauncherStatus { - Succesful, - NoChanges, - Cancelled, - NotFound, // We'll use the NotFound as the neutral return type for now - } + enum class LauncherStatus { + SUCCESSFUL, + NO_CHANGES, + CANCELLED, + 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 { - // MultiMC MC and loader version checker - val manifestPath = Paths.get(opts.multimcFolder, "mmc-pack.json").toString() - val manifestFile = File(manifestPath) + fun handleMultiMC(pf: PackFile, gson: Gson): LauncherStatus { + // MultiMC MC and loader version checker + val manifestPath = Paths.get(opts.multimcFolder, "mmc-pack.json").toString() + val manifestFile = File(manifestPath) - if (!manifestFile.exists()) { - return LauncherStatus.NotFound - } + if (!manifestFile.exists()) { + return LauncherStatus.NOT_FOUND + } - val multimcManifest = try { - JsonParser.parseReader(manifestFile.reader()) - } catch (e: JsonIOException) { - throw Exception("Cannot read the MultiMC pack file", e) - } catch (e: JsonSyntaxException) { - throw Exception("Invalid MultiMC pack file", e) - }.asJsonObject + val multimcManifest = manifestFile.reader().use { + try { + JsonParser.parseReader(it) + } catch (e: JsonIOException) { + throw Exception("Cannot read the MultiMC pack file", e) + } catch (e: JsonSyntaxException) { + 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 - // There's only version 1 for now tho, so that's good - if (multimcManifest["formatVersion"].asInt != 1) { - throw Exception("Invalid MultiMC format version") - } + // 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 + if (multimcManifest["formatVersion"]?.asInt != 1) { + throw Exception("Unsupported MultiMC format version ${multimcManifest["formatVersion"]}") + } - 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 modLoadersClasses = modLoaders.entries.associate{(k,v)-> v to k} - var modLoaderFound = false - val modLoadersFound = HashMap() // Key: modLoader, Value: Version - val components = multimcManifest["components"].asJsonArray - for (componentObj in components) { - val component = componentObj.asJsonObject + 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 modLoadersClasses = modLoaders.entries.associate{(k,v)-> v to k} + val loaderVersionsFound = HashMap() + val outdatedLoaders = mutableSetOf() + 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 - // If we find any of the modloaders we support, we save it and check the version - if (modLoaders.containsKey(component["uid"].asString)) { - val modLoader = modLoaders.getValue(component["uid"].asString) - if (modLoader != "minecraft") - modLoaderFound = true // Only set to true if modLoader isn't Minecraft - modLoadersFound[modLoader] = version - if (version != pf.versions?.get(modLoader)) { - manifestModified = true - component.addProperty("version", pf.versions?.get(modLoader)) - } - } - } + val version = component["version"]?.asString + // If we find any of the modloaders we support, we save it and check the version + if (modLoaders.containsKey(component["uid"]?.asString)) { + val modLoader = modLoaders.getValue(component["uid"]!!.asString) + loaderVersionsFound[modLoader] = version + if (version != pf.versions?.get(modLoader)) { + outdatedLoaders.add(modLoader) + true // Delete component; cached metadata is invalid and will be re-added + } else { + 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 - if (!modLoaderFound) { - // Using this filter and loop to handle multiple handlers - for ((_, loader) in modLoaders - .filter { it.value != "minecraft" && !modLoadersFound.containsKey(it.value) && pf.versions?.containsKey(it.value) == true } - ) { - components.add(gson.toJsonTree(hashMapOf("uid" to modLoadersClasses.get(loader), "version" to pf.versions?.get(loader)))) - } - } + for ((_, loader) in modLoaders + .filter { + (!loaderVersionsFound.containsKey(it.value) || outdatedLoaders.contains(it.value)) + && pf.versions?.containsKey(it.value) == true } + ) { + manifestModified = true + 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 (modLoadersFound["minecraft"] != pf.versions?.getValue("minecraft")) - components.find { it.asJsonObject["uid"].asString == "net.fabricmc.intermediary" }?.asJsonObject?.let { components.remove(it) } + // If inconsistent Intermediary mappings version is found, delete it - MultiMC will add and re-dl the correct one + components.find { it.isJsonObject && it.asJsonObject["uid"]?.asString == "net.fabricmc.intermediary" }?.let { + if (it.asJsonObject["version"]?.asString != pf.versions?.get("minecraft")) { + components.remove(it) + manifestModified = true + } + } - if (manifestModified) { - // 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 - val oldVers = modLoadersFound.map { Pair(it.key, it.value) } - val newVers = pf.versions!!.map { Pair(it.key, it.value) } + if (manifestModified) { + // 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 + val oldVers = loaderVersionsFound.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)) { - IUserInterface.UpdateConfirmationResult.CANCELLED -> { - 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)) + Log.info("Successfully updated mmc-pack.json based on version metadata") - manifestFile.writeText(gson.toJson(multimcManifest)) - Log.info("Updated modpack Minecrafts and/or the modloaders version") + return LauncherStatus.SUCCESSFUL + } - return LauncherStatus.Succesful - } - - return LauncherStatus.NoChanges - } + return LauncherStatus.NO_CHANGES + } } \ No newline at end of file diff --git a/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt b/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt index 425e538..4bf9812 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/UpdateManager.kt @@ -105,8 +105,9 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse ui.submitProgress(InstallProgress("Loading MultiMC pack file...")) try { when (lu.handleMultiMC(pf, gson)) { - LauncherUtils.LauncherStatus.Cancelled -> cancelled = true - LauncherUtils.LauncherStatus.NotFound -> Log.info("MultiMC not detected") + LauncherUtils.LauncherStatus.CANCELLED -> cancelled = true + LauncherUtils.LauncherStatus.NOT_FOUND -> Log.info("MultiMC not detected") + else -> {} } handleCancellation() } catch (e: Exception) { diff --git a/src/main/kotlin/link/infra/packwiz/installer/ui/gui/GUIHandler.kt b/src/main/kotlin/link/infra/packwiz/installer/ui/gui/GUIHandler.kt index 508e3cd..427d520 100644 --- a/src/main/kotlin/link/infra/packwiz/installer/ui/gui/GUIHandler.kt +++ b/src/main/kotlin/link/infra/packwiz/installer/ui/gui/GUIHandler.kt @@ -202,17 +202,16 @@ class GUIHandler : IUserInterface { val options = arrayOf("Cancel", "Continue anyways", "Update") - val result = JOptionPane.showOptionDialog(frmPackwizlauncher, - message, + val result = JOptionPane.showOptionDialog(frmPackwizlauncher, message, "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( - when (result) { - JOptionPane.CLOSED_OPTION, 0 -> IUserInterface.UpdateConfirmationResult.CANCELLED - 1 -> IUserInterface.UpdateConfirmationResult.CONTINUE - 2 -> IUserInterface.UpdateConfirmationResult.UPDATE - else -> IUserInterface.UpdateConfirmationResult.CANCELLED - } + when (result) { + JOptionPane.CLOSED_OPTION, 0 -> IUserInterface.UpdateConfirmationResult.CANCELLED + 1 -> IUserInterface.UpdateConfirmationResult.CONTINUE + 2 -> IUserInterface.UpdateConfirmationResult.UPDATE + else -> IUserInterface.UpdateConfirmationResult.CANCELLED + } ) } return future.get()