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
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<String, String>() // 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<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
// 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
}
}

View File

@ -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) {

View File

@ -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()