MultiMC modpack support

This commit is contained in:
huangyuhui
2017-08-24 23:53:27 +08:00
parent 0fa601ad5e
commit f532df20e6
7 changed files with 330 additions and 20 deletions

View File

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

View File

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

View File

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

View File

@@ -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
}

View File

@@ -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)
}
/**

View File

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

View File

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