From d83291eac085d9661a29ffe02a7fc9845344d2c7 Mon Sep 17 00:00:00 2001 From: huangyuhui Date: Tue, 22 Aug 2017 20:15:26 +0800 Subject: [PATCH] HMCL modpack import --- .../hmcl/{setting => game}/AccountHelper.kt | 3 +- .../org/jackhuang/hmcl/game/HMCLModpack.kt | 73 +++++++++++++++++++ .../org/jackhuang/hmcl/game/ModpackHelper.kt | 46 ++++++++++++ .../org/jackhuang/hmcl/ui/AccountItem.kt | 2 +- .../ui/download/DownloadWizardProvider.kt | 14 +++- .../jackhuang/hmcl/ui/download/ModpackPage.kt | 8 +- .../jackhuang/hmcl/mod/CurseForgeModpack.kt | 17 +++-- .../kotlin/org/jackhuang/hmcl/mod/Modpack.kt | 12 +-- .../hmcl/util/{ZipUtils.kt => Compressors.kt} | 32 ++++++++ 9 files changed, 186 insertions(+), 21 deletions(-) rename HMCL/src/main/kotlin/org/jackhuang/hmcl/{setting => game}/AccountHelper.kt (97%) create mode 100644 HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt create mode 100644 HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt rename HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/{ZipUtils.kt => Compressors.kt} (88%) diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/AccountHelper.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/AccountHelper.kt similarity index 97% rename from HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/AccountHelper.kt rename to HMCL/src/main/kotlin/org/jackhuang/hmcl/game/AccountHelper.kt index aa0af1e65..c7a2f0e8d 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/AccountHelper.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/AccountHelper.kt @@ -15,11 +15,12 @@ * 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.setting +package org.jackhuang.hmcl.game import javafx.scene.image.Image import org.jackhuang.hmcl.Main import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount +import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.FileDownloadTask import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Task diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt new file mode 100644 index 000000000..092bf1b53 --- /dev/null +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt @@ -0,0 +1,73 @@ +/* + * 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.game + +import com.google.gson.JsonParseException +import org.jackhuang.hmcl.download.game.VersionJSONSaveTask +import org.jackhuang.hmcl.mod.Modpack +import org.jackhuang.hmcl.setting.Profile +import org.jackhuang.hmcl.task.Task +import org.jackhuang.hmcl.util.GSON +import org.jackhuang.hmcl.util.fromJson +import org.jackhuang.hmcl.util.readTextFromZipFile +import org.jackhuang.hmcl.util.unzipSubDirectory +import java.io.File +import java.io.IOException + +/** + * Read the manifest in a HMCL modpack. + * + * @param f a HMCL modpack file. + * @throws IOException if the file is not a valid zip file. + * @throws JsonParseException if the manifest.json is missing or malformed. + * @return the manifest of HMCL modpack. + */ +@Throws(IOException::class, JsonParseException::class) +fun readHMCLModpackManifest(f: File): Modpack { + val json = readTextFromZipFile(f, "modpack.json") + return GSON.fromJson(json)?.copy(manifest = HMCLModpackManifest) ?: throw JsonParseException("`modpack.json` not found. Not a valid CurseForge modpack.") +} + +object HMCLModpackManifest + +class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, private val name: String): Task() { + private val dependency = profile.dependency + private val repository = profile.repository + override val dependencies = mutableListOf() + override val dependents = mutableListOf() + + init { + check(!repository.hasVersion(name), { "Version $name already exists." }) + val json = readTextFromZipFile(zipFile, "minecraft/pack.json") + var version = GSON.fromJson(json)!! + val jar = version.jar!! + version = version.copy(jar = null) + dependents += dependency.gameBuilder().name(name).gameVersion(jar).buildAsync() + dependencies += dependency.checkGameCompletionAsync(version) + dependencies += VersionJSONSaveTask(dependency, version) + } + + private val run = repository.getRunDirectory(name) + + override fun execute() { + unzipSubDirectory(zipFile, run, "minecraft/", false) + val json = run.resolve("pack.json") + if (repository.getVersionJson(name) != json) + json.delete() + } +} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt new file mode 100644 index 000000000..16a23014f --- /dev/null +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/ModpackHelper.kt @@ -0,0 +1,46 @@ +/* + * 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.game + +import org.jackhuang.hmcl.mod.Modpack +import org.jackhuang.hmcl.mod.readCurseForgeModpackManifest +import org.jackhuang.hmcl.util.readTextFromZipFileQuietly +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, + description = readTextFromZipFileQuietly(f, "modlist.html") ?: "No description", + manifest = manifest) + } catch (e: Exception) { + // ignore it, not a CurseForge modpack. + } + + try { + val manifest = readHMCLModpackManifest(f) + return manifest + } catch (e: Exception) { + // ignore it, not a HMCL modpack. + } + + throw IllegalArgumentException("Modpack file $f is not supported.") +} \ No newline at end of file diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt index 60a1e3dc9..ba4da40f9 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/AccountItem.kt @@ -36,7 +36,7 @@ import javafx.scene.paint.Color import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.auth.OfflineAccount import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount -import org.jackhuang.hmcl.setting.AccountHelper +import org.jackhuang.hmcl.game.AccountHelper import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.task.Scheduler import java.util.concurrent.Callable 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 281fea703..c41d53cfc 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 @@ -19,8 +19,11 @@ package org.jackhuang.hmcl.ui.download 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.setting.EnumGameDirectory import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Settings @@ -62,11 +65,12 @@ class DownloadWizardProvider(): WizardProvider() { return null val selectedFile = settings[ModpackPage.MODPACK_FILE] as? File? ?: return null - val manifest = settings[ModpackPage.MODPACK_CURSEFORGE_MANIFEST] as? CurseForgeModpackManifest? ?: return null + val modpack = settings[ModpackPage.MODPACK_CURSEFORGE_MANIFEST] as? Modpack? ?: return null val name = settings[ModpackPage.MODPACK_NAME] as? String? ?: return null profile.repository.markVersionAsModpack(name) - return CurseForgeModpackInstallTask(profile.dependency, selectedFile, manifest, name) with task { + + val finalizeTask = task { profile.repository.refreshVersions() val vs = profile.specializeVersionSetting(name) profile.repository.undoMark(name) @@ -74,6 +78,12 @@ class DownloadWizardProvider(): WizardProvider() { vs.gameDirType = EnumGameDirectory.VERSION_FOLDER } } + + return when (modpack.manifest) { + is CurseForgeModpackManifest -> CurseForgeModpackInstallTask(profile.dependency, selectedFile, modpack.manifest as CurseForgeModpackManifest, name) + is HMCLModpackManifest -> HMCLModpackInstallTask(profile, selectedFile, name) + else -> throw Error() + } with finalizeTask } override fun finish(settings: MutableMap): Any? { diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/ModpackPage.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/ModpackPage.kt index 0bb31e6e9..d74dcc8f0 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/ModpackPage.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/download/ModpackPage.kt @@ -25,6 +25,7 @@ import javafx.fxml.FXML import javafx.scene.control.Label import javafx.scene.layout.StackPane import javafx.stage.FileChooser +import org.jackhuang.hmcl.game.readModpackManifest import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.mod.readCurseForgeModpackManifest import org.jackhuang.hmcl.setting.Profile @@ -52,6 +53,7 @@ class ModpackPage(private val controller: WizardController): StackPane(), Wizard val chooser = FileChooser() chooser.title = i18n("modpack.choose") + chooser.extensionFilters += FileChooser.ExtensionFilter("Zip", "*.zip") val selectedFile = chooser.showOpenDialog(Controllers.stage) if (selectedFile == null) Platform.runLater { controller.onFinish() } else { @@ -65,14 +67,12 @@ class ModpackPage(private val controller: WizardController): StackPane(), Wizard } try { - val manifest = readCurseForgeModpackManifest(selectedFile) + val manifest = readModpackManifest(selectedFile) controller.settings[MODPACK_CURSEFORGE_MANIFEST] = manifest lblName.text = manifest.name lblVersion.text = manifest.version lblAuthor.text = manifest.author - } catch (e: IOException) { - // TODO - } catch (e: JsonParseException) { + } catch (e: Exception) { // TODO } } 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 09fc796f6..9609ea1dd 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt @@ -30,7 +30,6 @@ import java.io.File import java.io.IOException import java.net.URL import java.util.logging.Level -import java.util.zip.ZipFile class CurseForgeModpackManifest @JvmOverloads constructor( @SerializedName("manifestType") @@ -54,6 +53,8 @@ class CurseForgeModpackManifest @JvmOverloads constructor( check(manifestType == MINECRAFT_MODPACK, { "Only support Minecraft modpack" }) } + fun toModpack() = Modpack(name = name, author = author, version = version, description = "No description") + companion object { val MINECRAFT_MODPACK = "minecraftModpack" } @@ -101,14 +102,15 @@ class CurseForgeModpackManifestFile ( /** * @param f the CurseForge modpack file. + * @throws IOException if the file is not a valid zip file. + * @throws JsonParseException if the manifest.json is missing or malformed. * @return the manifest. */ +@Throws(IOException::class, JsonParseException::class) fun readCurseForgeModpackManifest(f: File): CurseForgeModpackManifest { - ZipFile(f).use { zipFile -> - val entry = zipFile.getEntry("manifest.json") ?: throw IOException("`manifest.json` not found. Not a valid CurseForge modpack.") - val json = zipFile.getInputStream(entry).readFullyAsString() - return GSON.fromJson(json) ?: throw JsonParseException("`manifest.json` not found. Not a valid CurseForge modpack.") - } + val json = readTextFromZipFile(f, "manifest.json") + return GSON.fromJson(json) + ?: throw JsonParseException("`manifest.json` not found. Not a valid CurseForge modpack.") } /** @@ -123,8 +125,7 @@ fun readCurseForgeModpackManifest(f: File): CurseForgeModpackManifest { class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDependencyManager, private val zipFile: File, private val manifest: CurseForgeModpackManifest, private val name: String): Task() { val repository = dependencyManager.repository init { - if (repository.hasVersion(name)) - throw IllegalStateException("Version $name already exists.") + check(!repository.hasVersion(name), { "Version $name already exists." }) } val root = repository.getVersionRoot(name) diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt index f28badc84..22536d9b8 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt @@ -17,8 +17,10 @@ */ package org.jackhuang.hmcl.mod -import java.io.File - -class Modpack(val file: File) { - -} \ No newline at end of file +data class Modpack @JvmOverloads constructor( + val name: String = "", + val author: String = "", + val version: String = "", + val description: String = "", + val manifest: Any? = null +) \ No newline at end of file diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ZipUtils.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt similarity index 88% rename from HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ZipUtils.kt rename to HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt index 4a848b6ab..c12508635 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ZipUtils.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt @@ -20,6 +20,7 @@ package org.jackhuang.hmcl.util import java.io.File import java.io.IOException import java.util.zip.ZipEntry +import java.util.zip.ZipFile import java.util.zip.ZipInputStream import java.util.zip.ZipOutputStream @@ -194,4 +195,35 @@ fun unzipSubDirectory(zip: File, dest: File, subDirectory: String, ignoreExisten } } } +} + +/** + * Read the text content of a file in zip. + * + * @param f the zip file + * @param location the location of the text in zip file, something like A/B/C/D.txt + * @throws IOException if the file is not a valid zip file. + * @return the content of given file. + */ +@Throws(IOException::class) +fun readTextFromZipFile(f: File, location: String): String { + ZipFile(f).use { zipFile -> + val entry = zipFile.getEntry(location) ?: throw IOException("`$location` not found.") + return zipFile.getInputStream(entry).readFullyAsString() + } +} + +/** + * Read the text content of a file in zip. + * + * @param f the zip file + * @param location the location of the text in zip file, something like A/B/C/D.txt + * @return the content of given file. + */ +fun readTextFromZipFileQuietly(f: File, location: String): String? { + try { + return readTextFromZipFile(f, location) + } catch (e: IOException) { + return null + } } \ No newline at end of file