mirror of
				https://github.com/packwiz/packwiz-installer.git
				synced 2025-10-31 19:04:32 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master'
This commit is contained in:
		| @@ -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") | ||||
|   | ||||
							
								
								
									
										108
									
								
								src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/main/kotlin/link/infra/packwiz/installer/LauncherUtils.kt
									
									
									
									
									
										Normal 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 | ||||
|     } | ||||
| } | ||||
| @@ -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)") | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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 | ||||
|  | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user