HMCL modpack import

This commit is contained in:
huangyuhui
2017-08-22 20:15:26 +08:00
parent 0cc7d621bc
commit d83291eac0
9 changed files with 186 additions and 21 deletions

View File

@@ -15,11 +15,12 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}. * 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 javafx.scene.image.Image
import org.jackhuang.hmcl.Main import org.jackhuang.hmcl.Main
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
import org.jackhuang.hmcl.setting.Settings
import org.jackhuang.hmcl.task.FileDownloadTask import org.jackhuang.hmcl.task.FileDownloadTask
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import org.jackhuang.hmcl.task.Task import org.jackhuang.hmcl.task.Task

View File

@@ -0,0 +1,73 @@
/*
* 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.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<Modpack>(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<Task>()
override val dependents = mutableListOf<Task>()
init {
check(!repository.hasVersion(name), { "Version $name already exists." })
val json = readTextFromZipFile(zipFile, "minecraft/pack.json")
var version = GSON.fromJson<Version>(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()
}
}

View File

@@ -0,0 +1,46 @@
/*
* 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.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.")
}

View File

@@ -36,7 +36,7 @@ import javafx.scene.paint.Color
import org.jackhuang.hmcl.auth.Account import org.jackhuang.hmcl.auth.Account
import org.jackhuang.hmcl.auth.OfflineAccount import org.jackhuang.hmcl.auth.OfflineAccount
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount 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.setting.Settings
import org.jackhuang.hmcl.task.Scheduler import org.jackhuang.hmcl.task.Scheduler
import java.util.concurrent.Callable import java.util.concurrent.Callable

View File

@@ -19,8 +19,11 @@ package org.jackhuang.hmcl.ui.download
import javafx.scene.Node import javafx.scene.Node
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider 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.CurseForgeModpackInstallTask
import org.jackhuang.hmcl.mod.CurseForgeModpackManifest import org.jackhuang.hmcl.mod.CurseForgeModpackManifest
import org.jackhuang.hmcl.mod.Modpack
import org.jackhuang.hmcl.setting.EnumGameDirectory import org.jackhuang.hmcl.setting.EnumGameDirectory
import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Profile
import org.jackhuang.hmcl.setting.Settings import org.jackhuang.hmcl.setting.Settings
@@ -62,11 +65,12 @@ class DownloadWizardProvider(): WizardProvider() {
return null return null
val selectedFile = settings[ModpackPage.MODPACK_FILE] as? File? ?: 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 val name = settings[ModpackPage.MODPACK_NAME] as? String? ?: return null
profile.repository.markVersionAsModpack(name) profile.repository.markVersionAsModpack(name)
return CurseForgeModpackInstallTask(profile.dependency, selectedFile, manifest, name) with task {
val finalizeTask = task {
profile.repository.refreshVersions() profile.repository.refreshVersions()
val vs = profile.specializeVersionSetting(name) val vs = profile.specializeVersionSetting(name)
profile.repository.undoMark(name) profile.repository.undoMark(name)
@@ -74,6 +78,12 @@ class DownloadWizardProvider(): WizardProvider() {
vs.gameDirType = EnumGameDirectory.VERSION_FOLDER 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<String, Any>): Any? { override fun finish(settings: MutableMap<String, Any>): Any? {

View File

@@ -25,6 +25,7 @@ import javafx.fxml.FXML
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.layout.StackPane import javafx.scene.layout.StackPane
import javafx.stage.FileChooser import javafx.stage.FileChooser
import org.jackhuang.hmcl.game.readModpackManifest
import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.i18n
import org.jackhuang.hmcl.mod.readCurseForgeModpackManifest import org.jackhuang.hmcl.mod.readCurseForgeModpackManifest
import org.jackhuang.hmcl.setting.Profile import org.jackhuang.hmcl.setting.Profile
@@ -52,6 +53,7 @@ class ModpackPage(private val controller: WizardController): StackPane(), Wizard
val chooser = FileChooser() val chooser = FileChooser()
chooser.title = i18n("modpack.choose") chooser.title = i18n("modpack.choose")
chooser.extensionFilters += FileChooser.ExtensionFilter("Zip", "*.zip")
val selectedFile = chooser.showOpenDialog(Controllers.stage) val selectedFile = chooser.showOpenDialog(Controllers.stage)
if (selectedFile == null) Platform.runLater { controller.onFinish() } if (selectedFile == null) Platform.runLater { controller.onFinish() }
else { else {
@@ -65,14 +67,12 @@ class ModpackPage(private val controller: WizardController): StackPane(), Wizard
} }
try { try {
val manifest = readCurseForgeModpackManifest(selectedFile) val manifest = readModpackManifest(selectedFile)
controller.settings[MODPACK_CURSEFORGE_MANIFEST] = manifest controller.settings[MODPACK_CURSEFORGE_MANIFEST] = manifest
lblName.text = manifest.name lblName.text = manifest.name
lblVersion.text = manifest.version lblVersion.text = manifest.version
lblAuthor.text = manifest.author lblAuthor.text = manifest.author
} catch (e: IOException) { } catch (e: Exception) {
// TODO
} catch (e: JsonParseException) {
// TODO // TODO
} }
} }

View File

@@ -30,7 +30,6 @@ import java.io.File
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
import java.util.logging.Level import java.util.logging.Level
import java.util.zip.ZipFile
class CurseForgeModpackManifest @JvmOverloads constructor( class CurseForgeModpackManifest @JvmOverloads constructor(
@SerializedName("manifestType") @SerializedName("manifestType")
@@ -54,6 +53,8 @@ class CurseForgeModpackManifest @JvmOverloads constructor(
check(manifestType == MINECRAFT_MODPACK, { "Only support Minecraft modpack" }) check(manifestType == MINECRAFT_MODPACK, { "Only support Minecraft modpack" })
} }
fun toModpack() = Modpack(name = name, author = author, version = version, description = "No description")
companion object { companion object {
val MINECRAFT_MODPACK = "minecraftModpack" val MINECRAFT_MODPACK = "minecraftModpack"
} }
@@ -101,14 +102,15 @@ class CurseForgeModpackManifestFile (
/** /**
* @param f the CurseForge modpack file. * @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. * @return the manifest.
*/ */
@Throws(IOException::class, JsonParseException::class)
fun readCurseForgeModpackManifest(f: File): CurseForgeModpackManifest { fun readCurseForgeModpackManifest(f: File): CurseForgeModpackManifest {
ZipFile(f).use { zipFile -> val json = readTextFromZipFile(f, "manifest.json")
val entry = zipFile.getEntry("manifest.json") ?: throw IOException("`manifest.json` not found. Not a valid CurseForge modpack.") return GSON.fromJson<CurseForgeModpackManifest>(json)
val json = zipFile.getInputStream(entry).readFullyAsString() ?: throw JsonParseException("`manifest.json` not found. Not a valid CurseForge modpack.")
return GSON.fromJson<CurseForgeModpackManifest>(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() { class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDependencyManager, private val zipFile: File, private val manifest: CurseForgeModpackManifest, private val name: String): Task() {
val repository = dependencyManager.repository val repository = dependencyManager.repository
init { init {
if (repository.hasVersion(name)) check(!repository.hasVersion(name), { "Version $name already exists." })
throw IllegalStateException("Version $name already exists.")
} }
val root = repository.getVersionRoot(name) val root = repository.getVersionRoot(name)

View File

@@ -17,8 +17,10 @@
*/ */
package org.jackhuang.hmcl.mod package org.jackhuang.hmcl.mod
import java.io.File data class Modpack @JvmOverloads constructor(
val name: String = "",
class Modpack(val file: File) { val author: String = "",
val version: String = "",
} val description: String = "",
val manifest: Any? = null
)

View File

@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.util
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
@@ -195,3 +196,34 @@ 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
}
}