diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt index 6ec9d82ad..695eda89c 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt @@ -75,8 +75,7 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat var version = GSON.fromJson(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) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt index 8ed72d12b..3aa35fffc 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt @@ -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) + } } \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt index a526ae1a8..2550e410e 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/VersionSetting.kt @@ -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(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) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt index f9091a213..3a541aa0a 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/DownloadWizardProvider.kt @@ -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 } diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt index c63a544ab..c6b0a10cc 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt @@ -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(json) + val manifest = GSON.fromJson(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) } /** diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt new file mode 100644 index 000000000..fddb44acb --- /dev/null +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt @@ -0,0 +1,242 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2017 huangyuhui + * + * 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 = emptyList(), + @SerializedName("+libraries") + val libraries: List = 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() + override val dependents = mutableListOf() + + 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(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) + } +} \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt index 47ea28928..7ef82d122 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Lang.kt @@ -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)