MultiMC modpack support
This commit is contained in:
@@ -75,8 +75,7 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat
|
||||
var version = GSON.fromJson<Version>(json)!!
|
||||
version = version.copy(jar = null)
|
||||
dependents += dependency.gameBuilder().name(name).gameVersion(modpack.gameVersion!!).buildAsync()
|
||||
dependencies += dependency.checkGameCompletionAsync(version)
|
||||
dependencies += VersionJSONSaveTask(repository, version)
|
||||
dependencies += VersionJSONSaveTask(repository, version) // override the json created by buildAsync()
|
||||
}
|
||||
|
||||
private val run = repository.getRunDirectory(name)
|
||||
|
||||
@@ -17,31 +17,83 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.game
|
||||
|
||||
import org.jackhuang.hmcl.mod.InstanceConfiguration
|
||||
import org.jackhuang.hmcl.mod.Modpack
|
||||
import org.jackhuang.hmcl.mod.readCurseForgeModpackManifest
|
||||
import org.jackhuang.hmcl.util.readTextFromZipFileQuietly
|
||||
import org.jackhuang.hmcl.mod.readMMCModpackManifest
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory
|
||||
import org.jackhuang.hmcl.setting.Profile
|
||||
import org.jackhuang.hmcl.setting.VersionSetting
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.toStringOrEmpty
|
||||
import java.io.File
|
||||
|
||||
fun readModpackManifest(f: File): Modpack {
|
||||
try {
|
||||
val manifest = readCurseForgeModpackManifest(f)
|
||||
return Modpack(
|
||||
name = manifest.name,
|
||||
version = manifest.version,
|
||||
author = manifest.author,
|
||||
gameVersion = manifest.minecraft.gameVersion,
|
||||
description = readTextFromZipFileQuietly(f, "modlist.html") ?: "No description",
|
||||
manifest = manifest)
|
||||
return readCurseForgeModpackManifest(f)
|
||||
} catch (e: Exception) {
|
||||
// ignore it, not a CurseForge modpack.
|
||||
// ignore it, not a valid CurseForge modpack.
|
||||
}
|
||||
|
||||
try {
|
||||
val manifest = readHMCLModpackManifest(f)
|
||||
return manifest
|
||||
} catch (e: Exception) {
|
||||
// ignore it, not a HMCL modpack.
|
||||
// ignore it, not a valid HMCL modpack.
|
||||
}
|
||||
|
||||
try {
|
||||
val manifest = readMMCModpackManifest(f)
|
||||
return manifest
|
||||
} catch (e: Exception) {
|
||||
// ignore it, not a valid MMC modpack.
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Modpack file $f is not supported.")
|
||||
}
|
||||
|
||||
fun InstanceConfiguration.toVersionSetting(vs: VersionSetting) {
|
||||
vs.usesGlobal = false
|
||||
vs.gameDirType = EnumGameDirectory.VERSION_FOLDER
|
||||
|
||||
if (overrideJavaLocation) {
|
||||
vs.javaDir = javaPath.toStringOrEmpty()
|
||||
}
|
||||
|
||||
if (overrideMemory) {
|
||||
vs.permSize = permGen.toStringOrEmpty()
|
||||
if (maxMemory != null)
|
||||
vs.maxMemory = maxMemory!!
|
||||
vs.minMemory = minMemory
|
||||
}
|
||||
|
||||
if (overrideCommands) {
|
||||
vs.wrapper = wrapperCommand.orEmpty()
|
||||
vs.precalledCommand = preLaunchCommand.orEmpty()
|
||||
}
|
||||
|
||||
if (overrideJavaArgs) {
|
||||
vs.javaArgs = jvmArgs.orEmpty()
|
||||
}
|
||||
|
||||
if (overrideConsole) {
|
||||
vs.showLogs = showConsole
|
||||
}
|
||||
|
||||
if (overrideWindow) {
|
||||
vs.fullscreen = fullscreen
|
||||
if (width != null)
|
||||
vs.width = width!!
|
||||
if (height != null)
|
||||
vs.height = height!!
|
||||
}
|
||||
}
|
||||
|
||||
class MMCInstallVersionSettingTask(private val profile: Profile, val manifest: InstanceConfiguration, val name: String): Task() {
|
||||
override val scheduler = Scheduler.JAVAFX
|
||||
override fun execute() {
|
||||
val vs = profile.specializeVersionSetting(name)!!
|
||||
manifest.toVersionSetting(vs)
|
||||
}
|
||||
}
|
||||
@@ -68,12 +68,17 @@ class VersionSetting() {
|
||||
var permSize: String by permSizeProperty
|
||||
|
||||
/**
|
||||
* The maximum memory that JVM can allocate.
|
||||
* The size of JVM heap.
|
||||
* The maximum memory that JVM can allocate for heap.
|
||||
*/
|
||||
val maxMemoryProperty = ImmediateIntegerProperty(this, "maxMemory", OS.SUGGESTED_MEMORY)
|
||||
var maxMemory: Int by maxMemoryProperty
|
||||
|
||||
/**
|
||||
* The minimum memory that JVM can allocate for heap.
|
||||
*/
|
||||
val minMemoryProperty = ImmediateObjectProperty<Int?>(this, "minMemory", null)
|
||||
var minMemory: Int? by minMemoryProperty
|
||||
|
||||
/**
|
||||
* The command that will be executed before launching the Minecraft.
|
||||
* Operating system relevant.
|
||||
@@ -202,6 +207,7 @@ class VersionSetting() {
|
||||
wrapperProperty.addListener(listener)
|
||||
permSizeProperty.addListener(listener)
|
||||
maxMemoryProperty.addListener(listener)
|
||||
minMemoryProperty.addListener(listener)
|
||||
precalledCommandProperty.addListener(listener)
|
||||
javaArgsProperty.addListener(listener)
|
||||
minecraftArgsProperty.addListener(listener)
|
||||
@@ -227,6 +233,7 @@ class VersionSetting() {
|
||||
minecraftArgs = minecraftArgs,
|
||||
javaArgs = javaArgs,
|
||||
maxMemory = maxMemory,
|
||||
minMemory = minMemory,
|
||||
metaspace = permSize.toIntOrNull(),
|
||||
width = width,
|
||||
height = height,
|
||||
@@ -251,6 +258,7 @@ class VersionSetting() {
|
||||
addProperty("javaArgs", src.javaArgs)
|
||||
addProperty("minecraftArgs", src.minecraftArgs)
|
||||
addProperty("maxMemory", if (src.maxMemory <= 0) OS.SUGGESTED_MEMORY else src.maxMemory)
|
||||
addProperty("minMemory", src.minMemory)
|
||||
addProperty("permSize", src.permSize)
|
||||
addProperty("width", src.width)
|
||||
addProperty("height", src.height)
|
||||
@@ -282,6 +290,7 @@ class VersionSetting() {
|
||||
javaArgs = json["javaArgs"]?.asString ?: ""
|
||||
minecraftArgs = json["minecraftArgs"]?.asString ?: ""
|
||||
maxMemory = maxMemoryN
|
||||
minMemory = json["minMemory"]?.asInt
|
||||
permSize = json["permSize"]?.asString ?: ""
|
||||
width = parseJsonPrimitive(json["width"]?.asJsonPrimitive)
|
||||
height = parseJsonPrimitive(json["height"]?.asJsonPrimitive)
|
||||
|
||||
@@ -21,9 +21,8 @@ import javafx.scene.Node
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
|
||||
import org.jackhuang.hmcl.game.HMCLModpackInstallTask
|
||||
import org.jackhuang.hmcl.game.HMCLModpackManifest
|
||||
import org.jackhuang.hmcl.mod.CurseForgeModpackInstallTask
|
||||
import org.jackhuang.hmcl.mod.CurseForgeModpackManifest
|
||||
import org.jackhuang.hmcl.mod.Modpack
|
||||
import org.jackhuang.hmcl.game.MMCInstallVersionSettingTask
|
||||
import org.jackhuang.hmcl.mod.*
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory
|
||||
import org.jackhuang.hmcl.setting.Profile
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
@@ -82,6 +81,7 @@ class DownloadWizardProvider(): WizardProvider() {
|
||||
return when (modpack.manifest) {
|
||||
is CurseForgeModpackManifest -> CurseForgeModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as CurseForgeModpackManifest, name)
|
||||
is HMCLModpackManifest -> HMCLModpackInstallTask(profile, selectedFile, modpack, name)
|
||||
is InstanceConfiguration -> MMCModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as InstanceConfiguration, name) with MMCInstallVersionSettingTask(profile, modpack.manifest as InstanceConfiguration, name)
|
||||
else -> throw Error()
|
||||
} with finalizeTask
|
||||
}
|
||||
|
||||
@@ -105,10 +105,17 @@ class CurseForgeModpackManifestFile (
|
||||
* @return the manifest.
|
||||
*/
|
||||
@Throws(IOException::class, JsonParseException::class)
|
||||
fun readCurseForgeModpackManifest(f: File): CurseForgeModpackManifest {
|
||||
fun readCurseForgeModpackManifest(f: File): Modpack {
|
||||
val json = readTextFromZipFile(f, "manifest.json")
|
||||
return GSON.fromJson<CurseForgeModpackManifest>(json)
|
||||
val manifest = GSON.fromJson<CurseForgeModpackManifest>(json)
|
||||
?: throw JsonParseException("`manifest.json` not found. Not a valid CurseForge modpack.")
|
||||
return Modpack(
|
||||
name = manifest.name,
|
||||
version = manifest.version,
|
||||
author = manifest.author,
|
||||
gameVersion = manifest.minecraft.gameVersion,
|
||||
description = readTextFromZipFileQuietly(f, "modlist.html") ?: "No description",
|
||||
manifest = manifest)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.mod
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager
|
||||
import org.jackhuang.hmcl.download.game.VersionJSONSaveTask
|
||||
import org.jackhuang.hmcl.game.Library
|
||||
import org.jackhuang.hmcl.game.Version
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.util.*
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.util.*
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class InstancePatch @JvmOverloads constructor(
|
||||
val name: String = "",
|
||||
val version: String = "",
|
||||
@SerializedName("mcVersion")
|
||||
val gameVersion: String = "",
|
||||
val mainClass: String = "",
|
||||
val fileId: String = "",
|
||||
@SerializedName("+tweakers")
|
||||
val tweakers: List<String> = emptyList(),
|
||||
@SerializedName("+libraries")
|
||||
val libraries: List<Library> = emptyList()
|
||||
)
|
||||
|
||||
class InstanceConfiguration(contentStream: InputStream) {
|
||||
/**
|
||||
* The instance's name
|
||||
*/
|
||||
val name: String // name
|
||||
|
||||
/**
|
||||
* The game version of the instance
|
||||
*/
|
||||
val gameVersion: String // IntendedVersion
|
||||
|
||||
/**
|
||||
* The permanent generation size of JVM.
|
||||
*/
|
||||
val permGen: Int? // PermGen
|
||||
|
||||
/**
|
||||
* The command to launch JVM.
|
||||
*/
|
||||
val wrapperCommand: String? // WrapperCommand
|
||||
|
||||
/**
|
||||
* The command that will be executed before game launches.
|
||||
*/
|
||||
val preLaunchCommand: String? // PreLaunchCommand
|
||||
|
||||
/**
|
||||
* The command that will be executed after game exits.
|
||||
*/
|
||||
val postExitCommand: String? // PostExitCommand
|
||||
|
||||
/**
|
||||
* The description of the instance
|
||||
*/
|
||||
val notes: String? // notes
|
||||
|
||||
/**
|
||||
* JVM installation location
|
||||
*/
|
||||
val javaPath: String? // JavaPath
|
||||
|
||||
/**
|
||||
* The JVM's arguments
|
||||
*/
|
||||
val jvmArgs: String? // JvmArgs
|
||||
|
||||
/**
|
||||
* True if Minecraft will start in fullscreen mode.
|
||||
*/
|
||||
val fullscreen: Boolean // LaunchMaximized
|
||||
|
||||
/**
|
||||
* The initial width of the game window.
|
||||
*/
|
||||
val width: Int? // MinecraftWinWidth
|
||||
|
||||
/**
|
||||
* The initial height of the game window.
|
||||
*/
|
||||
val height: Int? // MinecraftWinHeight
|
||||
|
||||
/**
|
||||
* The maximum memory that JVM can allocate.
|
||||
*/
|
||||
val maxMemory: Int? // MaxMemAlloc
|
||||
|
||||
/**
|
||||
* The minimum memory that JVM can allocate.
|
||||
*/
|
||||
val minMemory: Int? // MinMemAlloc
|
||||
|
||||
/**
|
||||
* True if show the console window when game launches.
|
||||
*/
|
||||
val showConsole: Boolean // ShowConsole
|
||||
|
||||
/**
|
||||
* True if show the console window when game crashes.
|
||||
*/
|
||||
val showConsoleOnError: Boolean // ShowConsoleOnError
|
||||
|
||||
/**
|
||||
* True if closes the console window when game stops.
|
||||
*/
|
||||
val autoCloseConsole: Boolean // AutoCloseConsole
|
||||
|
||||
/**
|
||||
* True if [maxMemory], [minMemory], [permGen] will come info force.
|
||||
*/
|
||||
val overrideMemory: Boolean // OverrideMemory
|
||||
|
||||
/**
|
||||
* True if [javaPath] will come info force.
|
||||
*/
|
||||
val overrideJavaLocation: Boolean // OverrideJavaLocation
|
||||
|
||||
/**
|
||||
* True if [jvmArgs] will come info force.
|
||||
*/
|
||||
val overrideJavaArgs: Boolean // OverrideJavaArgs
|
||||
|
||||
/**
|
||||
* True if [showConsole], [showConsoleOnError], [autoCloseConsole] will come into force.
|
||||
*/
|
||||
val overrideConsole: Boolean // OverrideConsole
|
||||
|
||||
/**
|
||||
* True if [preLaunchCommand], [postExitCommand], [wrapperCommand] will come into force.
|
||||
*/
|
||||
val overrideCommands: Boolean // OverrideCommands
|
||||
|
||||
/**
|
||||
* True if [height], [width], [fullscreen] will come into force.
|
||||
*/
|
||||
val overrideWindow: Boolean // OverrideWindow
|
||||
|
||||
init {
|
||||
val p = Properties()
|
||||
p.load(contentStream)
|
||||
|
||||
autoCloseConsole = p.getProperty("AutoCloseConsole") == "true"
|
||||
gameVersion = p.getProperty("IntendedVersion")
|
||||
javaPath = p.getProperty("JavaPath")
|
||||
jvmArgs = p.getProperty("JvmArgs")
|
||||
fullscreen = p.getProperty("LaunchMaximized") == "true"
|
||||
maxMemory = p.getProperty("MaxMemAlloc")?.toIntOrNull()
|
||||
minMemory = p.getProperty("MinMemAlloc")?.toIntOrNull()
|
||||
height = p.getProperty("MinecraftWinHeight")?.toIntOrNull()
|
||||
width = p.getProperty("MinecraftWinWidth")?.toIntOrNull()
|
||||
overrideCommands = p.getProperty("OverrideCommands") == "true"
|
||||
overrideConsole = p.getProperty("OverrideConsole") == "true"
|
||||
overrideJavaArgs = p.getProperty("OverrideJavaArgs") == "true"
|
||||
overrideJavaLocation = p.getProperty("OverrideJavaLocation") == "true"
|
||||
overrideMemory = p.getProperty("OverrideMemory") == "true"
|
||||
overrideWindow = p.getProperty("OverrideWindow") == "true"
|
||||
permGen = p.getProperty("PermGen")?.toIntOrNull()
|
||||
postExitCommand = p.getProperty("PostExitCommand")
|
||||
preLaunchCommand = p.getProperty("PreLaunchCommand")
|
||||
showConsole = p.getProperty("ShowConsole") == "true"
|
||||
showConsoleOnError = p.getProperty("ShowConsoleOnError") == "true"
|
||||
wrapperCommand = p.getProperty("WrapperCommand")
|
||||
name = p.getProperty("name")
|
||||
notes = p.getProperty("notes")
|
||||
}
|
||||
}
|
||||
|
||||
fun readMMCModpackManifest(f: File): Modpack {
|
||||
ZipFile(f).use { zipFile ->
|
||||
val entry = zipFile.getEntry("instance.cfg") ?: throw IOException("`instance.cfg` not found, $f is not a valid MultiMC modpack.")
|
||||
val cfg = InstanceConfiguration(zipFile.getInputStream(entry))
|
||||
return Modpack(
|
||||
name = cfg.name,
|
||||
version = "",
|
||||
author = "",
|
||||
gameVersion = cfg.gameVersion,
|
||||
description = cfg.notes,
|
||||
manifest = cfg
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class MMCModpackInstallTask(private val dependencyManager: DefaultDependencyManager, private val zipFile: File, private val manifest: InstanceConfiguration, private val name: String): Task() {
|
||||
private val repository = dependencyManager.repository
|
||||
override val dependencies = mutableListOf<Task>()
|
||||
override val dependents = mutableListOf<Task>()
|
||||
|
||||
init {
|
||||
check(!repository.hasVersion(name), { "Version $name already exists." })
|
||||
dependents += dependencyManager.gameBuilder().name(name).gameVersion(manifest.gameVersion).buildAsync()
|
||||
}
|
||||
|
||||
private val run = repository.getRunDirectory(name)
|
||||
|
||||
override fun execute() {
|
||||
var version = repository.readVersionJson(name)!!
|
||||
unzipSubDirectory(zipFile, run, "minecraft/", false)
|
||||
|
||||
ZipFile(zipFile).use { zip ->
|
||||
for (entry in zip.entries()) {
|
||||
// ensure that this entry is in folder 'patches' and is a json file.
|
||||
if (!entry.isDirectory && entry.name.startsWith("patches/") && entry.name.endsWith(".json")) {
|
||||
val patch = GSON.fromJson<InstancePatch>(zip.getInputStream(entry).readFullyAsString())!!
|
||||
val args = StringBuilder(version.minecraftArguments)
|
||||
for (arg in patch.tweakers)
|
||||
args.append(" --tweakClass ").append(arg)
|
||||
version = version.copy(
|
||||
libraries = merge(version.libraries, patch.libraries),
|
||||
mainClass = patch.mainClass,
|
||||
minecraftArguments = args.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies += VersionJSONSaveTask(repository, version)
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,7 @@ fun String.asVersion(): String? {
|
||||
return builder.deleteCharAt(builder.length - 1).toString()
|
||||
}
|
||||
|
||||
fun Any?.toStringOrEmpty() = this?.toString().orEmpty()
|
||||
|
||||
fun parseParams(addBefore: String, objects: Collection<*>, addAfter: String): String {
|
||||
return parseParams(addBefore, objects.toTypedArray(), addAfter)
|
||||
|
||||
Reference in New Issue
Block a user