Merge remote-tracking branch 'origin/master'

This commit is contained in:
comp500 2022-06-28 18:04:40 +01:00
commit ab3de9a246
6 changed files with 192 additions and 4 deletions

View File

@ -23,7 +23,7 @@ val r8 by configurations.creating
dependencies {
implementation("commons-cli:commons-cli:1.5.0")
implementation("com.moandjiezana.toml:toml4j:0.7.2")
implementation("com.google.code.gson:gson:2.8.9")
implementation("com.google.code.gson:gson:2.9.0")
implementation("com.squareup.okio:okio:3.0.0")
implementation(kotlin("stdlib-jdk8"))
implementation("com.squareup.okhttp3:okhttp:4.9.3")

View File

@ -0,0 +1,108 @@
package link.infra.packwiz.installer
import com.google.gson.Gson
import com.google.gson.JsonIOException
import com.google.gson.JsonParser
import com.google.gson.JsonSyntaxException
import link.infra.packwiz.installer.metadata.PackFile
import link.infra.packwiz.installer.ui.IUserInterface
import link.infra.packwiz.installer.util.Log
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
}
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
}
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
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")
}
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
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))
}
}
}
// 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))))
}
}
// 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 (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) }
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("Updated modpack Minecrafts and/or the modloaders version")
return LauncherStatus.Succesful
}
return LauncherStatus.NoChanges
}
}

View File

@ -71,6 +71,7 @@ class Main(args: Array<String>) {
downloadURI = SpaceSafeURI(unparsedArgs[0]),
side = cmd.getOptionValue("side")?.let((Side)::from),
packFolder = cmd.getOptionValue("pack-folder"),
multimcFolder = cmd.getOptionValue("multimc-folder"),
manifestFile = cmd.getOptionValue("meta-file")
)
} catch (e: URISyntaxException) {
@ -94,6 +95,7 @@ class Main(args: Array<String>) {
options.addOption("s", "side", true, "Side to install mods from (client/server, defaults to client)")
options.addOption(null, "title", true, "Title of the installer window")
options.addOption(null, "pack-folder", true, "Folder to install the pack to (defaults to the JAR directory)")
options.addOption(null, "multimc-folder", true, "The MultiMC pack folder (defaults to the parent of the pack directory)")
options.addOption(null, "meta-file", true, "JSON file to store pack metadata, relative to the pack folder (defaults to packwiz.json)")
}

View File

@ -45,12 +45,13 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
val downloadURI: SpaceSafeURI,
val manifestFile: String,
val packFolder: String,
val multimcFolder: String,
val side: Side
) {
// Horrible workaround for default params not working cleanly with nullable values
companion object {
fun construct(downloadURI: SpaceSafeURI, manifestFile: String?, packFolder: String?, side: Side?) =
Options(downloadURI, manifestFile ?: "packwiz.json", packFolder ?: ".", side ?: Side.CLIENT)
fun construct(downloadURI: SpaceSafeURI, manifestFile: String?, packFolder: String?, multimcFolder: String?, side: Side?) =
Options(downloadURI, manifestFile ?: "packwiz.json", packFolder ?: ".", multimcFolder ?: "..", side ?: Side.CLIENT)
}
}
@ -97,6 +98,26 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
handleCancellation()
}
// Launcher checks
val lu = LauncherUtils(opts, ui)
// MultiMC MC and loader version checker
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")
}
handleCancellation()
} catch (e: Exception) {
ui.showErrorAndExit(e.message!!, e)
}
if (ui.cancelButtonPressed) {
showCancellationDialog()
handleCancellation()
}
ui.submitProgress(InstallProgress("Checking local files..."))
// Invalidation checking must be done here, as it must happen before pack/index hashes are checked
@ -163,7 +184,6 @@ class UpdateManager internal constructor(private val opts: Options, val ui: IUse
handleCancellation()
// TODO: update MMC params, java args etc
// If there were errors, don't write the manifest/index hashes, to ensure they are rechecked later
if (errorsOccurred) {

View File

@ -23,6 +23,8 @@ interface IUserInterface {
fun showCancellationDialog(): CancellationResult = CancellationResult.QUIT
fun showUpdateConfirmationDialog(oldVersions: List<Pair<String, String?>>, newVersions: List<Pair<String, String?>>): UpdateConfirmationResult = UpdateConfirmationResult.CANCELLED
fun awaitOptionalButton(showCancel: Boolean)
enum class ExceptionListResult {
@ -33,6 +35,10 @@ interface IUserInterface {
QUIT, CONTINUE
}
enum class UpdateConfirmationResult {
CANCELLED, CONTINUE, UPDATE
}
var optionsButtonPressed: Boolean
var cancelButtonPressed: Boolean

View File

@ -166,6 +166,58 @@ class GUIHandler : IUserInterface {
return future.get()
}
override fun showUpdateConfirmationDialog(oldVersions: List<Pair<String, String?>>, newVersions: List<Pair<String, String?>>): IUserInterface.UpdateConfirmationResult {
assert(newVersions.isNotEmpty())
val future = CompletableFuture<IUserInterface.UpdateConfirmationResult>()
EventQueue.invokeLater {
val oldVersIndex = oldVersions.map { it.first to it.second }.toMap()
val newVersIndex = newVersions.map { it.first to it.second }.toMap()
val message = StringBuilder()
message.append("<html>" +
"This modpack uses newer versions of the following:<br>" +
"<ul>")
for (oldVer in oldVersions) {
val correspondingNewVer = newVersIndex[oldVer.first]
message.append("<li>")
message.append(oldVer.first.replaceFirstChar { it.uppercase() })
message.append(": <font color=${if (oldVer.second != correspondingNewVer) "#ff0000" else "#000000"}>")
message.append(oldVer.second ?: "Not found")
message.append("</font></li>")
}
message.append("</ul>")
message.append("New versions:" +
"<ul>")
for (newVer in newVersions) {
val correspondingOldVer = oldVersIndex[newVer.first]
message.append("<li>")
message.append(newVer.first.replaceFirstChar { it.uppercase() })
message.append(": <font color=${if (newVer.second != correspondingOldVer) "#00ff00" else "#000000"}>")
message.append(newVer.second ?: "Not found")
message.append("</font></li>")
}
message.append("</ul><br>" +
"Would you like to update the versions, launch without updating, or cancel the launch?")
val options = arrayOf("Cancel", "Continue anyways", "Update")
val result = JOptionPane.showOptionDialog(frmPackwizlauncher,
message,
"Updating MultiMC versions",
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0])
future.complete(
when (result) {
JOptionPane.CLOSED_OPTION, 0 -> IUserInterface.UpdateConfirmationResult.CANCELLED
1 -> IUserInterface.UpdateConfirmationResult.CONTINUE
2 -> IUserInterface.UpdateConfirmationResult.UPDATE
else -> IUserInterface.UpdateConfirmationResult.CANCELLED
}
)
}
return future.get()
}
override fun awaitOptionalButton(showCancel: Boolean) {
EventQueue.invokeAndWait {
frmPackwizlauncher.showOk(!showCancel)