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

@ -12,10 +12,10 @@ 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 {
@ -24,85 +24,96 @@ class LauncherUtils internal constructor(private val opts: UpdateManager.Options
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 {
JsonParser.parseReader(it)
} catch (e: JsonIOException) { } catch (e: JsonIOException) {
throw Exception("Cannot read the MultiMC pack file", e) throw Exception("Cannot read the MultiMC pack file", e)
} catch (e: JsonSyntaxException) { } catch (e: JsonSyntaxException) {
throw Exception("Invalid MultiMC pack file", e) throw Exception("Invalid MultiMC pack file", e)
}.asJsonObject }.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(
"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 modLoadersClasses = modLoaders.entries.associate{(k,v)-> v to k}
var modLoaderFound = false val loaderVersionsFound = HashMap<String, String?>()
val modLoadersFound = HashMap<String, String>() // Key: modLoader, Value: Version val outdatedLoaders = mutableSetOf<String>()
val components = multimcManifest["components"].asJsonArray val components = multimcManifest["components"]?.asJsonArray ?: throw Exception("Invalid mmc-pack.json: no components key")
for (componentObj in components) { components.removeAll {
val component = componentObj.asJsonObject 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
modLoadersFound[modLoader] = version
if (version != pf.versions?.get(modLoader)) { if (version != pf.versions?.get(modLoader)) {
manifestModified = true outdatedLoaders.add(modLoader)
component.addProperty("version", pf.versions?.get(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 for ((_, loader) in modLoaders
.filter { it.value != "minecraft" && !modLoadersFound.containsKey(it.value) && pf.versions?.containsKey(it.value) == true } .filter {
(!loaderVersionsFound.containsKey(it.value) || outdatedLoaders.contains(it.value))
&& pf.versions?.containsKey(it.value) == true }
) { ) {
components.add(gson.toJsonTree(hashMapOf("uid" to modLoadersClasses.get(loader), "version" to pf.versions?.get(loader)))) 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 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)) { when (ui.showUpdateConfirmationDialog(oldVers, newVers)) {
IUserInterface.UpdateConfirmationResult.CANCELLED -> { IUserInterface.UpdateConfirmationResult.CANCELLED -> {
return LauncherStatus.Cancelled return LauncherStatus.CANCELLED
} }
IUserInterface.UpdateConfirmationResult.CONTINUE -> { IUserInterface.UpdateConfirmationResult.CONTINUE -> {
return LauncherStatus.Succesful // Returning succesful as... Well, the user is telling us to continue return LauncherStatus.SUCCESSFUL
} }
else -> {} // Compiler is giving warning about "non-exhaustive when", so i'll just add an empty one else -> {}
} }
manifestFile.writeText(gson.toJson(multimcManifest)) manifestFile.writeText(gson.toJson(multimcManifest))
Log.info("Updated modpack Minecrafts and/or the modloaders version") Log.info("Successfully updated mmc-pack.json based on version metadata")
return LauncherStatus.Succesful return LauncherStatus.SUCCESSFUL
} }
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...")) 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,10 +202,9 @@ 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