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 092bf1b53..37f954836 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt @@ -22,12 +22,21 @@ 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 +import sun.misc.IOUtils +import jdk.nashorn.internal.objects.NativeFunction.call +import org.jackhuang.hmcl.task.TaskResult +import org.jackhuang.hmcl.util.* +import sun.plugin2.util.PojoUtil.toJson +import sun.tools.jar.resources.jar +import java.util.Collections.addAll +import java.util.ArrayList +import java.util.Arrays + + + + /** * Read the manifest in a HMCL modpack. @@ -70,4 +79,43 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat if (repository.getVersionJson(name) != json) json.delete() } +} + +val MODPACK_BLACK_LIST = listOf("usernamecache.json", "asm", "logs", "backups", "versions", "assets", "usercache.json", "libraries", "crash-reports", "launcher_profiles.json", "NVIDIA", "AMD", "TCNodeTracker", "screenshots", "natives", "native", "\$native", "pack.json", "launcher.jar", "minetweaker.log", "launcher.pack.lzma", "hmclmc.log") +val MODPACK_SUGGESTED_BLACK_LIST = listOf("fonts", "saves", "servers.dat", "options.txt", "optionsof.txt", "journeymap", "optionsshaders.txt", "mods/VoxelMods") + +class HMCLModpackExportTask @JvmOverloads constructor( + private val repository: DefaultGameRepository, + private val version: String, + private val blacklist: List, + private val modpack: Modpack, + private val output: File, + override val id: String = ID): TaskResult() { + override fun execute() { + val b = ArrayList(MODPACK_BLACK_LIST) + b.addAll(blacklist) + b.add(version + ".jar") + b.add(version + ".json") + LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker") + ZipEngine(output).use { zip -> + zip.putDirectory(repository.getRunDirectory(version)) a@ { x: String, y: Boolean -> + for (s in b) + if (y) { + if (x.startsWith(s + "/")) + return@a null + } else if (x == s) + return@a null + "minecraft/" + x + } + + val mv = repository.getVersion(version).resolve(repository) + val gameVersion = minecraftVersion(repository.getVersionJar(version)) ?: throw IllegalStateException("Cannot parse the version of $version") + zip.putTextFile(GSON.toJson(mv), "minecraft/pack.json") + zip.putTextFile(GSON.toJson(modpack.copy(gameVersion = gameVersion)), "modpack.json") + } + } + + companion object { + val ID = "zip_engine" + } } \ No newline at end of file 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 22536d9b8..1fcae50fe 100644 --- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/Modpack.kt @@ -21,6 +21,7 @@ data class Modpack @JvmOverloads constructor( val name: String = "", val author: String = "", val version: String = "", + val gameVersion: 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/ZipEngine.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ZipEngine.kt new file mode 100644 index 000000000..00fc06751 --- /dev/null +++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/ZipEngine.kt @@ -0,0 +1,126 @@ +/* + * 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.util + +import java.util.zip.ZipEntry +import java.util.HashSet +import java.io.* +import java.util.zip.ZipOutputStream + + +/** + * Non thread-safe + * + * @author huangyuhui + */ +class ZipEngine + @Throws(IOException::class) + constructor(f: File) : Closeable { + + val buf = ByteArray(1024) + val zos: ZipOutputStream = ZipOutputStream(BufferedOutputStream(f.outputStream())) + private val names = HashSet() + + @Throws(IOException::class) + override fun close() { + zos.closeEntry() + zos.close() + } + + /** + * 功能:把 sourceDir 目录下的所有文件进行 zip 格式的压缩,保存为指定 zip 文件 + * + * @param sourceDir 源文件夹 + * @param pathNameCallback callback(pathName, isDirectory) returns your + * modified pathName + * + * @throws java.io.IOException 压缩失败或无法读取 + */ + @Throws(IOException::class) + @JvmOverloads + fun putDirectory(sourceDir: File, pathNameCallback: ((String, Boolean) -> String?)? = null) { + putDirectoryImpl(sourceDir, if (sourceDir.isDirectory) sourceDir.path else sourceDir.parent, pathNameCallback) + } + + /** + * 将文件压缩成zip文件 + * + * @param source zip文件路径 + * @param basePath 待压缩文件根目录 + * @param zos zip文件的os + * @param pathNameCallback callback(pathName, isDirectory) returns your + * modified pathName, null if you dont want this file zipped + */ + @Throws(IOException::class) + private fun putDirectoryImpl(source: File, basePath: String, pathNameCallback: ((String, Boolean) -> String?)?) { + val files: Array? + if (source.isDirectory) + files = source.listFiles() + else + files = arrayOf(source) + if (files == null) + return + var pathName: String? //存相对路径(相对于待压缩的根目录) + for (file in files) + if (file.isDirectory) { + pathName = file.path.substring(basePath.length + 1) + "/" + pathName = pathName.replace('\\', '/') + if (pathNameCallback != null) + pathName = pathNameCallback(pathName, true) + if (pathName == null) + continue + put(ZipEntry(pathName)) + putDirectoryImpl(file, basePath, pathNameCallback) + } else { + if (".DS_Store" == file.name) // For Mac computers. + continue + pathName = file.path.substring(basePath.length + 1) + pathName = pathName.replace('\\', '/') + if (pathNameCallback != null) + pathName = pathNameCallback(pathName, false) + if (pathName == null) + continue + putFile(file, pathName) + } + } + + @Throws(IOException::class) + fun putFile(file: File, pathName: String) = + file.inputStream().use { putStream(it, pathName) } + + @Throws(IOException::class) + fun putStream(inputStream: InputStream, pathName: String) { + put(ZipEntry(pathName)) + inputStream.copyTo(zos, buf) + } + + @Throws(IOException::class) + fun putTextFile(text: String, pathName: String) = + putTextFile(text, "UTF-8", pathName) + + @Throws(IOException::class) + fun putTextFile(text: String, encoding: String, pathName: String) = + putStream(ByteArrayInputStream(text.toByteArray(charset(encoding))), pathName) + + @Throws(IOException::class) + fun put(entry: ZipEntry) { + if (names.add(entry.name)) + zos.putNextEntry(entry) + } + +}