From 3b4359f7eb074d36d9474a37ed70bbba2cebb2aa Mon Sep 17 00:00:00 2001 From: huangyuhui Date: Fri, 25 Aug 2017 13:22:10 +0800 Subject: [PATCH] Fixed logging spaces and tabs missing, unclickable version item, removing 'logging' element when installing forge and liteloader --- .../org/jackhuang/hmcl/game/HMCLModpack.kt | 11 +-- .../org/jackhuang/hmcl/setting/Settings.kt | 5 +- .../kotlin/org/jackhuang/hmcl/ui/LogWindow.kt | 11 ++- .../jackhuang/hmcl/ui/download/ModpackPage.kt | 2 +- HMCL/src/main/resources/assets/fxml/main.fxml | 3 +- .../resources/assets/fxml/version-item.fxml | 8 +- .../resources/assets/log-window-content.html | 2 +- .../hmcl/download/BMCLAPIDownloadProvider.kt | 12 +-- .../hmcl/download/MojangDownloadProvider.kt | 2 - .../hmcl/download/forge/ForgeInstallTask.kt | 2 +- .../liteloader/LiteLoaderInstallTask.kt | 3 +- .../kotlin/org/jackhuang/hmcl/game/Library.kt | 2 +- .../jackhuang/hmcl/launch/DefaultLauncher.kt | 32 ++++-- .../jackhuang/hmcl/mod/CurseForgeModpack.kt | 6 +- .../org/jackhuang/hmcl/mod/MultiMCModpack.kt | 16 +-- .../org/jackhuang/hmcl/util/Compressors.kt | 97 ++++++------------- 16 files changed, 96 insertions(+), 118 deletions(-) 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 695eda89c..e9ad6662b 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/game/HMCLModpack.kt @@ -51,9 +51,9 @@ import com.sun.javafx.scene.layout.region.BorderStyleConverter.HIDDEN */ @Throws(IOException::class, JsonParseException::class) fun readHMCLModpackManifest(f: File): Modpack { - val manifestJson = readTextFromZipFile(f, "modpack.json") + val manifestJson = f.readTextZipEntry("modpack.json") val manifest = GSON.fromJson(manifestJson) ?: throw JsonParseException("`modpack.json` not found. $f is not a valid HMCL modpack.") - val gameJson = readTextFromZipFile(f, "minecraft/pack.json") + val gameJson = f.readTextZipEntry("minecraft/pack.json") val game = GSON.fromJson(gameJson) ?: throw JsonParseException("`minecraft/pack.json` not found. $f iot a valid HMCL modpack.") return if (game.jar == null) if (manifest.gameVersion.isNullOrBlank()) throw JsonParseException("Cannot recognize the game version of modpack $f.") @@ -71,7 +71,7 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat init { check(!repository.hasVersion(name), { "Version $name already exists." }) - val json = readTextFromZipFile(zipFile, "minecraft/pack.json") + val json = zipFile.readTextZipEntry("minecraft/pack.json") var version = GSON.fromJson(json)!! version = version.copy(jar = null) dependents += dependency.gameBuilder().name(name).gameVersion(modpack.gameVersion!!).buildAsync() @@ -81,10 +81,7 @@ class HMCLModpackInstallTask(profile: Profile, private val zipFile: File, privat 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() + zipFile.uncompressTo(run, "minecraft/", callback = { it != "minecraft/pack.json" }, ignoreExistentFile = false) } } diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt index 89f02c5ae..2fe05239f 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/setting/Settings.kt @@ -171,7 +171,10 @@ object Settings { else { System.setProperty("http.proxyHost", proxyHost) System.setProperty("http.proxyPort", proxyPort) - proxy = Proxy(proxyType, InetSocketAddress(host, port)) + if (proxyType == Proxy.Type.DIRECT) + proxy = Proxy.NO_PROXY + else + proxy = Proxy(proxyType, InetSocketAddress(host, port)) val user = proxyUser val pass = proxyPass diff --git a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LogWindow.kt b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LogWindow.kt index cd994b7f6..fe4e6f787 100644 --- a/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LogWindow.kt +++ b/HMCL/src/main/kotlin/org/jackhuang/hmcl/ui/LogWindow.kt @@ -30,13 +30,11 @@ import javafx.scene.layout.StackPane import javafx.scene.web.WebEngine import javafx.scene.web.WebView import javafx.stage.Stage +import netscape.javascript.JSObject import org.jackhuang.hmcl.game.LauncherHelper import org.jackhuang.hmcl.i18n import org.jackhuang.hmcl.setting.Settings -import org.jackhuang.hmcl.util.Log4jLevel -import org.jackhuang.hmcl.util.inc -import org.jackhuang.hmcl.util.onChange -import org.jackhuang.hmcl.util.readFullyAsString +import org.jackhuang.hmcl.util.* import org.w3c.dom.Document import org.w3c.dom.Node import java.util.concurrent.Callable @@ -59,7 +57,10 @@ class LogWindow : Stage() { fun logLine(line: String, level: Log4jLevel) { impl.body.appendChild(impl.engine.document.createElement("div").apply { - textContent = line + // a
 element to prevent multiple spaces and tabs being removed.
+            appendChild(impl.engine.document.createElement("pre").apply {
+                textContent = line
+            })
         })
         impl.engine.executeScript("checkNewLog(\"${level.name.toLowerCase()}\");scrollToBottom();")
 
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 130a8a3ba..5df12b668 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
@@ -72,7 +72,7 @@ class ModpackPage(private val controller: WizardController): StackPane(), Wizard
                 lblName.text = manifest!!.name
                 lblVersion.text = manifest!!.version
                 lblAuthor.text = manifest!!.author
-                txtModpackName.text = manifest!!.name + "-" + manifest!!.version
+                txtModpackName.text = manifest!!.name + (if (manifest!!.version.isNullOrBlank()) "" else ("-" + manifest!!.version))
             } catch (e: Exception) {
                 // TODO
                 txtModpackName.text = i18n("modpack.task.install.error")
diff --git a/HMCL/src/main/resources/assets/fxml/main.fxml b/HMCL/src/main/resources/assets/fxml/main.fxml
index b76221ec5..65dea1695 100644
--- a/HMCL/src/main/resources/assets/fxml/main.fxml
+++ b/HMCL/src/main/resources/assets/fxml/main.fxml
@@ -5,14 +5,13 @@
 
 
     
         
         
     
     
-        
         
             
diff --git a/HMCL/src/main/resources/assets/fxml/version-item.fxml b/HMCL/src/main/resources/assets/fxml/version-item.fxml
index 397b3da32..bb95ebcd6 100644
--- a/HMCL/src/main/resources/assets/fxml/version-item.fxml
+++ b/HMCL/src/main/resources/assets/fxml/version-item.fxml
@@ -10,9 +10,9 @@
 
 
-    
-        
+         type="StackPane" pickOnBounds="false">
+    
+        
             
                 
                     
@@ -26,7 +26,7 @@
                 
             
         
-        
+        
             
                 
                     
diff --git a/HMCL/src/main/resources/assets/log-window-content.html b/HMCL/src/main/resources/assets/log-window-content.html
index 5ed798c1c..fdc6d6c0a 100644
--- a/HMCL/src/main/resources/assets/log-window-content.html
+++ b/HMCL/src/main/resources/assets/log-window-content.html
@@ -9,7 +9,7 @@
             overflow-x: hidden;
         }
 
-        div {
+        div > pre {
             font: ${FONT};
             margin: 0px;
             padding: 2px;
diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.kt
index 1e159a38e..f6d385cd4 100644
--- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.kt
+++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/BMCLAPIDownloadProvider.kt
@@ -43,10 +43,10 @@ object BMCLAPIDownloadProvider : DownloadProvider() {
     }
 
     override fun injectURL(baseURL: String): String = baseURL
-            .replace("https://launchermeta.mojang.com", "http://bmclapi2.bangbang93.com")
-            .replace("https://launcher.mojang.com", "http://bmclapi2.bangbang93.com")
-            .replace("https://libraries.minecraft.net", "http://bmclapi2.bangbang93.com/libraries")
-            .replace("http://files.minecraftforge.net/maven", "http://bmclapi2.bangbang93.com/maven")
-            .replace("http://dl.liteloader.com/versions/versions.json", "http://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
-            .replace("http://dl.liteloader.com/versions", "http://bmclapi2.bangbang93.com/maven")
+            .replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com")
+            .replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com")
+            .replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
+            .replace("http://files.minecraftforge.net/maven", "https://bmclapi2.bangbang93.com/maven")
+            .replace("http://dl.liteloader.com/versions/versions.json", "httsp://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
+            .replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven")
 }
\ No newline at end of file
diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/MojangDownloadProvider.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/MojangDownloadProvider.kt
index 481d1e42f..8b1497fff 100644
--- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/MojangDownloadProvider.kt
+++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/MojangDownloadProvider.kt
@@ -46,8 +46,6 @@ object MojangDownloadProvider : DownloadProvider() {
     override fun injectURL(baseURL: String): String {
         if (baseURL.endsWith("net/minecraftforge/forge/json"))
             return baseURL
-        else if (Locale.getDefault() == Locale.CHINA)
-            return baseURL.replace("http://files.minecraftforge.net/maven", "http://maven.aliyun.com/nexus/content/groups/public");
         else
             return baseURL.replace("http://files.minecraftforge.net/maven", "http://ftb.cursecdn.com/FTB2/maven")
     }
diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeInstallTask.kt
index 6c34d0daf..e7eddf766 100644
--- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeInstallTask.kt
+++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/forge/ForgeInstallTask.kt
@@ -73,7 +73,7 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
             val versionProvider = SimpleVersionProvider()
             versionProvider.addVersion(version)
 
-            result = installProfile.versionInfo!!.copy(inheritsFrom = version.id).resolve(versionProvider).copy(id = version.id)
+            result = installProfile.versionInfo!!.copy(inheritsFrom = version.id).resolve(versionProvider).copy(id = version.id, logging = emptyMap())
             dependencies += GameLibrariesTask(dependencyManager, installProfile.versionInfo)
         }
 
diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.kt
index f91467080..6e5ebd6ee 100644
--- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.kt
+++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/download/liteloader/LiteLoaderInstallTask.kt
@@ -69,7 +69,8 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana
         result = version.copy(
                 mainClass = "net.minecraft.launchwrapper.Launch",
                 minecraftArguments = version.minecraftArguments + " --tweakClass " + remote.tag.tweakClass,
-                libraries = merge(tempVersion.libraries, version.libraries)
+                libraries = merge(tempVersion.libraries, version.libraries),
+                logging = emptyMap()
         )
         dependencies += GameLibrariesTask(dependencyManager, tempVersion)
     }
diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Library.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Library.kt
index 0b42adcfd..15a5188a5 100644
--- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Library.kt
+++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/game/Library.kt
@@ -73,7 +73,7 @@ open class Library(
 
     companion object LibrarySerializer : JsonDeserializer, JsonSerializer {
         fun fromName(name: String, url: String? = null, downloads: LibrariesDownloadInfo? = null, extract: ExtractRules? = null, natives: Map? = null, rules: List? = null): Library {
-            val arr = name.split(":".toRegex(), 3)
+            val arr = name.split(":".toRegex(), 4)
             if (arr.size != 3 && arr.size != 4)
                 throw IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version.")
             return Library(
diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt
index b0e9d42d2..1ed031fb7 100644
--- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt
+++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/launch/DefaultLauncher.kt
@@ -214,11 +214,11 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
     open fun decompressNatives() {
         version.libraries.filter { it.isNative }.forEach { library ->
             @Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
-            unzip(zip = repository.getLibraryFile(version, library),
+            repository.getLibraryFile(version, library).uncompressTo(
                     dest = native,
                     callback = if (library.extract == null) null
                     else library.extract!!::shouldExtract,
-                    ignoreExistsFile = false)
+                    ignoreExistentFile = false)
         }
     }
 
@@ -230,10 +230,13 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
         decompressNatives()
 
         if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) {
-            val process = Runtime.getRuntime().exec(options.precalledCommand)
-            ignoreException {
-                if (process.isAlive)
-                    process.waitFor()
+            try {
+                val process = Runtime.getRuntime().exec(options.precalledCommand)
+                    if (process.isAlive)
+                        process.waitFor()
+            } catch (e: IOException) {
+                // TODO: alert precalledCommand is wrong.
+                // rethrow InterruptedException
             }
         }
 
@@ -287,6 +290,14 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
     }
 
     private fun startMonitors(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
+        val enablesLoggingInfo = version.logging?.containsKey(DownloadType.CLIENT) ?: false
+        if (enablesLoggingInfo)
+            startMonitorsWithLoggingInfo(managedProcess, processListener, isDaemon)
+        else
+            startMonitorsWithoutLoggingInfo(managedProcess, processListener, isDaemon)
+    }
+
+    private fun startMonitorsWithLoggingInfo(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
         processListener.setProcess(managedProcess)
         val logHandler = Log4jHandler { line, level -> processListener.onLog(line, level); managedProcess.lines += line }.apply { start() }
         managedProcess.relatedThreads += logHandler
@@ -297,6 +308,15 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
         managedProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(managedProcess, listOf(stdout, stderr), { exitCode, exitType -> logHandler.onStopped(); processListener.onExit(exitCode, exitType) })::run)
     }
 
+    private fun startMonitorsWithoutLoggingInfo(managedProcess: ManagedProcess, processListener: ProcessListener, isDaemon: Boolean = true) {
+        processListener.setProcess(managedProcess)
+        val stdout = thread(name = "stdout-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.inputStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.guessLevel(it) ?: Log4jLevel.INFO); managedProcess.lines += it })::run)
+        managedProcess.relatedThreads += stdout
+        val stderr = thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(managedProcess.process.errorStream, { processListener.onLog(it + OS.LINE_SEPARATOR, Log4jLevel.ERROR); managedProcess.lines += it })::run)
+        managedProcess.relatedThreads += stderr
+        managedProcess.relatedThreads += thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(managedProcess, listOf(stdout, stderr), { exitCode, exitType -> processListener.onExit(exitCode, exitType) })::run)
+    }
+
     companion object {
         const val LAUNCH_ASYNC_ID = "process"
     }
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 c6b0a10cc..da6121bc5 100644
--- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt
+++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/CurseForgeModpack.kt
@@ -106,7 +106,7 @@ class CurseForgeModpackManifestFile (
  */
 @Throws(IOException::class, JsonParseException::class)
 fun readCurseForgeModpackManifest(f: File): Modpack {
-    val json = readTextFromZipFile(f, "manifest.json")
+    val json = f.readTextZipEntry("manifest.json")
     val manifest = GSON.fromJson(json)
             ?: throw JsonParseException("`manifest.json` not found. Not a valid CurseForge modpack.")
     return Modpack(
@@ -114,7 +114,7 @@ fun readCurseForgeModpackManifest(f: File): Modpack {
             version = manifest.version,
             author = manifest.author,
             gameVersion = manifest.minecraft.gameVersion,
-            description = readTextFromZipFileQuietly(f, "modlist.html") ?: "No description",
+            description = f.readTextZipEntryQuietly("modlist.html") ?: "No description",
             manifest = manifest)
 }
 
@@ -147,7 +147,7 @@ class CurseForgeModpackInstallTask(private val dependencyManager: DefaultDepende
     }
 
     override fun execute() {
-        unzipSubDirectory(zipFile, run, manifest.overrides)
+        zipFile.uncompressTo(run, subDirectory = manifest.overrides)
 
         var finished = 0
         for (f in manifest.files) {
diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt
index 599888e69..780e7b1cf 100644
--- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt
+++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/mod/MultiMCModpack.kt
@@ -42,7 +42,7 @@ class InstancePatch @JvmOverloads constructor(
         val libraries: List = emptyList()
 )
 
-class InstanceConfiguration(contentStream: InputStream) {
+class InstanceConfiguration(defaultName: String, contentStream: InputStream) {
     /**
      * The instance's name
      */
@@ -183,15 +183,17 @@ class InstanceConfiguration(contentStream: InputStream) {
         showConsole = p.getProperty("ShowConsole") == "true"
         showConsoleOnError = p.getProperty("ShowConsoleOnError") == "true"
         wrapperCommand = p.getProperty("WrapperCommand")
-        name = p.getProperty("name")
-        notes = p.getProperty("notes")
+        name = defaultName
+        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))
+        val firstEntry = zipFile.entries.nextElement()
+        val name = firstEntry.name.substringBefore("/")
+        val entry = zipFile.getEntry("$name/instance.cfg") ?: throw IOException("`instance.cfg` not found, $f is not a valid MultiMC modpack.")
+        val cfg = InstanceConfiguration(name, zipFile.getInputStream(entry))
         return Modpack(
                 name = cfg.name,
                 version = "",
@@ -217,12 +219,12 @@ class MMCModpackInstallTask(private val dependencyManager: DefaultDependencyMana
 
     override fun execute() {
         var version = repository.readVersionJson(name)!!
-        unzipSubDirectory(zipFile, run, "minecraft/", false)
+        zipFile.uncompressTo(run, subDirectory = "${manifest.name}/minecraft/", ignoreExistentFile = false, allowStoredEntriesWithDataDescriptor = true)
 
         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")) {
+                if (!entry.isDirectory && entry.name.startsWith("${manifest.name}/patches/") && entry.name.endsWith(".json")) {
                     val patch = GSON.fromJson(zip.getInputStream(entry).readFullyAsString())!!
                     val args = StringBuilder(version.minecraftArguments)
                     for (arg in patch.tweakers)
diff --git a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt
index 88b70cf62..88b80ca5f 100644
--- a/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt
+++ b/HMCLCore/src/main/kotlin/org/jackhuang/hmcl/util/Compressors.kt
@@ -34,21 +34,22 @@ import java.io.IOException
  */
 @JvmOverloads
 @Throws(IOException::class)
-fun zip(src: File, destZip: File, pathNameCallback: ((String, Boolean) -> String?)? = null) {
+fun File.zipTo(destZip: File, pathNameCallback: ((String, Boolean) -> String?)? = null) {
     ZipArchiveOutputStream(destZip.outputStream()).use { zos ->
         val basePath: String
-        if (src.isDirectory)
-            basePath = src.path
+        if (this.isDirectory)
+            basePath = this.path
         else
-        //直接压缩单个文件时,取父目录
-            basePath = src.parent
-        zipFile(src, basePath, zos, pathNameCallback)
+            //直接压缩单个文件时,取父目录
+            basePath = this.parent
+        zipFile(this, basePath, zos, pathNameCallback)
         zos.closeArchiveEntry()
     }
 }
 
 /**
  * Zip file.
+ *
  * @param src source directory to be compressed.
  * @param basePath the file directory to be compressed, if [src] is a file, this is the parent directory of [src]
  * @param zos the [ZipOutputStream] of dest zip file.
@@ -92,69 +93,21 @@ private fun zipFile(src: File,
 
 /**
  * Decompress the given zip file to a directory.
- * @param zip the source zip file.
- * @param dest the dest directory.
- * @param callback will be called for every entry in the zip file, returns false if you dont want this file unzipped.
- * @throws IOException
- */
-@JvmOverloads
-@Throws(IOException::class)
-fun unzip(zip: File, dest: File, callback: ((String) -> Boolean)? = null, ignoreExistsFile: Boolean = true) {
-    val buf = ByteArray(1024)
-    dest.mkdirs()
-    ZipArchiveInputStream(zip.inputStream()).use { zipFile ->
-        if (zip.exists()) {
-            val strPath = dest.absolutePath
-            while (true) {
-                val zipEnt = zipFile.nextEntry ?: break
-                var strtemp: String
-                var gbkPath = zipEnt.name
-                if (callback != null)
-                    if (!callback.invoke(gbkPath))
-                        continue
-                if (zipEnt.isDirectory) {
-                    strtemp = strPath + File.separator + gbkPath
-                    val dir = File(strtemp)
-                    dir.mkdirs()
-                } else {
-                    //读写文件
-                    gbkPath = zipEnt.name
-                    strtemp = strPath + File.separator + gbkPath
-                    //建目录
-                    val strsubdir = gbkPath
-                    for (i in 0 until strsubdir.length)
-                        if (strsubdir.substring(i, i + 1).equals("/", ignoreCase = true)) {
-                            val temp = strPath + File.separator + strsubdir.substring(0, i)
-                            val subdir = File(temp)
-                            if (!subdir.exists())
-                                subdir.mkdir()
-                        }
-                    if (ignoreExistsFile && File(strtemp).exists())
-                        continue
-                    File(strtemp).outputStream().use({ fos ->
-                        zipFile.copyTo(fos, buf)
-                    })
-                }
-            }
-        }
-    }
-}
-
-/**
- * Decompress the subdirectory of given zip file.
- * @param zip the source zip file.
+ *
  * @param dest the dest directory.
  * @param subDirectory the subdirectory of the zip file to be decompressed.
+ * @param callback will be called for every entry in the zip file, returns false if you dont want this file being uncompressed.
  * @param ignoreExistentFile true if skip all existent files.
+ * @param allowStoredEntriesWithDataDescriptor whether the zip stream will try to read STORED entries that use a data descriptor
  * @throws IOException
  */
 @JvmOverloads
 @Throws(IOException::class)
-fun unzipSubDirectory(zip: File, dest: File, subDirectory: String, ignoreExistentFile: Boolean = true) {
+fun File.uncompressTo(dest: File, subDirectory: String = "", callback: ((String) -> Boolean)? = null, ignoreExistentFile: Boolean = true, allowStoredEntriesWithDataDescriptor: Boolean = false) {
     val buf = ByteArray(1024)
     dest.mkdirs()
-    ZipArchiveInputStream(zip.inputStream()).use { zipFile ->
-        if (zip.exists()) {
+    ZipArchiveInputStream(this.inputStream(), null, true, allowStoredEntriesWithDataDescriptor).use { zipFile ->
+        if (this.exists()) {
             val strPath = dest.absolutePath
             while (true) {
                 val zipEnt = zipFile.nextEntry ?: break
@@ -166,13 +119,18 @@ fun unzipSubDirectory(zip: File, dest: File, subDirectory: String, ignoreExisten
                 gbkPath = gbkPath.substring(subDirectory.length)
                 if (gbkPath.startsWith("/") || gbkPath.startsWith("\\")) gbkPath = gbkPath.substring(1)
                 strtemp = strPath + File.separator + gbkPath
+
+                if (callback != null)
+                    if (!callback.invoke(gbkPath))
+                        continue
+
                 if (zipEnt.isDirectory) {
                     val dir = File(strtemp)
                     dir.mkdirs()
                 } else {
                     //建目录
                     val strsubdir = gbkPath
-                    for (i in 0..strsubdir.length - 1)
+                    for (i in 0 until strsubdir.length)
                         if (strsubdir.substring(i, i + 1).equals("/", ignoreCase = true)) {
                             val temp = strPath + File.separator + strsubdir.substring(0, i)
                             val subdir = File(temp)
@@ -194,14 +152,14 @@ 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
+ * @param name 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.")
+fun File.readTextZipEntry(name: String): String {
+    ZipFile(this).use { zipFile ->
+        val entry = zipFile.getEntry(name) ?: throw IOException("`$name` not found.")
         return zipFile.getInputStream(entry).readFullyAsString()
     }
 }
@@ -213,10 +171,9 @@ fun readTextFromZipFile(f: File, location: String): String {
  * @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? {
+fun File.readTextZipEntryQuietly(location: String) =
     try {
-        return readTextFromZipFile(f, location)
+        readTextZipEntry(location)
     } catch (e: IOException) {
-        return null
-    }
-}
\ No newline at end of file
+        null
+    }
\ No newline at end of file