From 7de07319d9c93b1b3589829f66cb61f694095089 Mon Sep 17 00:00:00 2001 From: zkitefly Date: Wed, 7 Feb 2024 22:21:48 +0800 Subject: [PATCH 01/33] Update CacheRepository.java (#2756) * Update CacheRepository.java https://github.com/FCL-Team/FoldCraftLauncher/commit/e96dd55395ed8107e70d8446b9a3ac14f0a17416 * Update CacheRepository.java * Update CacheRepository.java * Update CacheRepository.java * Update CacheRepository.java * update --------- Co-authored-by: Glavo --- .../src/main/java/org/jackhuang/hmcl/util/CacheRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java index 133ea1f32f..3b05444563 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/CacheRepository.java @@ -287,7 +287,7 @@ private final Map joinETagIndexes(Collection... inde } public void saveETagIndex() throws IOException { - try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.READ, StandardOpenOption.WRITE)) { + try (FileChannel channel = FileChannel.open(indexFile, StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE)) { FileLock lock = channel.lock(); try { ETagIndex indexOnDisk = JsonUtils.fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), ETagIndex.class); From 20fe5430ddc5e9303079d2e2267be246bb6c96e5 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 7 Feb 2024 22:37:56 +0800 Subject: [PATCH 02/33] =?UTF-8?q?Close=20#2757:=20=E6=9B=B4=E6=96=B0=20Mul?= =?UTF-8?q?tiFileItem=20(#2758)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jackhuang/hmcl/ui/construct/MultiFileItem.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java index b60ef78c52..cd6506fb15 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/MultiFileItem.java @@ -40,7 +40,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -public class MultiFileItem extends VBox { +public final class MultiFileItem extends VBox { private final ObjectProperty selectedData = new SimpleObjectProperty<>(this, "selectedData"); private final ObjectProperty fallbackData = new SimpleObjectProperty<>(this, "fallbackData"); @@ -176,8 +176,8 @@ protected Node createItem(ToggleGroup group) { } } - public static class StringOption extends Option { - private JFXTextField customField = new JFXTextField(); + public static final class StringOption extends Option { + private final JFXTextField customField = new JFXTextField(); public StringOption(String title, T data) { super(title, data); @@ -196,7 +196,7 @@ public void setValue(String value) { } public StringOption bindBidirectional(Property property) { - customField.textProperty().bindBidirectional(property); + FXUtils.bindString(customField, property); return this; } @@ -230,8 +230,8 @@ protected Node createItem(ToggleGroup group) { } } - public static class FileOption extends Option { - private FileSelector selector = new FileSelector(); + public static final class FileOption extends Option { + private final FileSelector selector = new FileSelector(); public FileOption(String title, T data) { super(title, data); From fb6538f042953020d40cdf3d9dee121328971f12 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 8 Feb 2024 09:36:37 +0800 Subject: [PATCH 03/33] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Linux=20RISC-V=2064?= =?UTF-8?q?=20=E6=94=AF=E6=8C=81=20(#2760)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/resources/assets/natives.json | 154 ++++++++++++++++++++ PLATFORM.md | 2 +- PLATFORM_cn.md | 2 +- 3 files changed, 156 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/resources/assets/natives.json b/HMCL/src/main/resources/assets/natives.json index 87059afa72..89ee0fe762 100644 --- a/HMCL/src/main/resources/assets/natives.json +++ b/HMCL/src/main/resources/assets/natives.json @@ -2346,6 +2346,160 @@ "org.lwjgl:lwjgl-glfw:3.3.1:natives-linux": null, "org.lwjgl:lwjgl-stb:3.3.1:natives-linux": null, "org.lwjgl:lwjgl-tinyfd:3.3.1:natives-linux": null, + "org.lwjgl:lwjgl:3.3.2": { + "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4.jar", + "sha1": "7202012cf0cadb9ffad4874494920fd8bbd93413", + "size": 792204 + } + } + }, + "org.lwjgl:lwjgl:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl:3.3.4-SNAPSHOT:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-freebsd.jar", + "sha1": "2d38355b453edfe2daee1a567bcdb82c0485edcf", + "size": 95872 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.2": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4.jar", + "sha1": "41256f2c098806304fd224613d3d01b02725470e", + "size": 46421 + } + } + }, + "org.lwjgl:lwjgl-jemalloc:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-jemalloc:3.3.4-SNAPSHOT:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-freebsd.jar", + "sha1": "bdba1662b621228679c7aed87945d1f28c590d5b", + "size": 155867 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.2": { + "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4.jar", + "sha1": "89d8868c2d688b55e3e923345e4a146c6d034229", + "size": 113094 + } + } + }, + "org.lwjgl:lwjgl-openal:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-openal:3.3.4-SNAPSHOT:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-freebsd.jar", + "sha1": "0fc6495e6752727b629cf03a105c9d56087d4edd", + "size": 597512 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.2": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4.jar", + "sha1": "81d0a7fd96bf5eb6257fddf6b77e338d8918bf32", + "size": 931744 + } + } + }, + "org.lwjgl:lwjgl-opengl:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-opengl:3.3.4-SNAPSHOT:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-freebsd.jar", + "sha1": "d3e8ec997cef8bc66819c7e0ad7a54182f7aff2a", + "size": 81034 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.2": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4.jar", + "sha1": "e6dba9ab8532cb6aac273adf26496ce689999943", + "size": 146829 + } + } + }, + "org.lwjgl:lwjgl-glfw:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-glfw:3.3.4-SNAPSHOT:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-freebsd.jar", + "sha1": "4881a965c7679b4984c0d8a5890f07ea85c653c4", + "size": 101847 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.2": { + "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3.jar", + "sha1": "033fe42d1b37e35afd8b6e2653abc77deadb0730", + "size": 143099 + } + } + }, + "org.lwjgl:lwjgl-stb:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-stb:3.3.4-SNAPSHOT:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-freebsd.jar", + "sha1": "0aad82a857ebb9a3a212dc2c386761d57e11a8f2", + "size": 225735 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.2": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3.jar", + "sha1": "8b7c94a57f56a5b38b23c02c1cada77dccba9930", + "size": 15917 + } + } + }, + "org.lwjgl:lwjgl-tinyfd:3.3.2:natives-linux": { + "name": "org.lwjgl:lwjgl-tinyfd:3.3.4-SNAPSHOT:natives-freebsd", + "downloads": { + "artifact": { + "path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", + "url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-freebsd.jar", + "sha1": "124d0e48ae1584f09e5701588ae8ad139be26003", + "size": 39077 + } + } + }, "net.java.jinput:jinput-platform:2.0.5:natives": null, "com.mojang:text2speech:1.10.3:natives": null, "com.mojang:text2speech:1.11.3:natives": null, diff --git a/PLATFORM.md b/PLATFORM.md index 4db26c2a72..af68e418cc 100644 --- a/PLATFORM.md +++ b/PLATFORM.md @@ -9,7 +9,7 @@ English | [简体中文](PLATFORM_cn.md) | ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.20.4) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (use Rosetta 2) | ❔ | | ARM32 | /️ | 👌 (Minecraft 1.8~1.20.1) | / | / | | MIPS64el | / | 👌 (Minecraft 1.8~1.20.1) | / | / | -| RISC-V 64 | / | 👌 (Minecraft 1.13~1.20.1) | / | / | +| RISC-V 64 | / | 👌 (Minecraft 1.13~1.20.4) | / | / | | LoongArch64 | / | 👌 (Minecraft 1.6~1.20.1) | / | / | | PowerPC-64 (Little-Endian) | / | ❔ | / | / | | S390x | / | ❔ | / | / | diff --git a/PLATFORM_cn.md b/PLATFORM_cn.md index ddce6f88fb..8150c26ea2 100644 --- a/PLATFORM_cn.md +++ b/PLATFORM_cn.md @@ -9,7 +9,7 @@ | ARM64 | 👌 (Minecraft 1.8~1.18.2)
✅ (Minecraft 1.19+) | 👌 (Minecraft 1.8~1.20.4) | 👌 (Minecraft 1.6~1.18.2)
✅ (Minecraft 1.19+)
✅ (使用 Rosetta 2) | ❔ | | ARM32 | /️ | 👌 (Minecraft 1.6~1.20.1) | / | / | | MIPS64el | / | 👌 (Minecraft 1.6~1.20.1) | / | / | -| RISC-V 64 | / | 👌 (Minecraft 1.13~1.20.1) | / | / | +| RISC-V 64 | / | 👌 (Minecraft 1.13~1.20.4) | / | / | | LoongArch64 | / | 👌 (Minecraft 1.6~1.20.1) | / | / | | PowerPC-64 (小端序) | / | ❔ | / | / | | S390x | / | ❔ | / | / | From c1acd0b0b5e58f157008f4efbcb885ac2b524718 Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 8 Feb 2024 19:19:01 +0800 Subject: [PATCH 04/33] =?UTF-8?q?=E5=88=A0=E9=99=A4=20RemoteResourceManage?= =?UTF-8?q?r=20(#2761)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 删除 RemoteResourceManager * update --- HMCL/build.gradle.kts | 66 ------ .../java/org/jackhuang/hmcl/Launcher.java | 14 +- .../java/org/jackhuang/hmcl/Metadata.java | 4 +- .../org/jackhuang/hmcl/ui/CrashWindow.java | 2 +- .../org/jackhuang/hmcl/ui/UpgradeDialog.java | 2 +- .../hmcl/ui/account/CreateAccountPane.java | 2 +- .../org/jackhuang/hmcl/ui/main/MainPage.java | 6 +- .../org/jackhuang/hmcl/ui/main/RootPage.java | 2 +- .../jackhuang/hmcl/ui/main/SettingsPage.java | 8 +- .../hmcl/ui/versions/ModTranslations.java | 36 ++-- .../{hmcl => }/ExecutableHeaderHelper.java | 2 +- .../upgrade/{hmcl => }/HMCLDownloadTask.java | 2 +- .../upgrade/{hmcl => }/IntegrityChecker.java | 2 +- .../upgrade/{hmcl => }/RemoteVersion.java | 2 +- .../upgrade/{hmcl => }/UpdateChannel.java | 2 +- .../upgrade/{hmcl => }/UpdateChecker.java | 2 +- .../upgrade/{hmcl => }/UpdateHandler.java | 2 +- .../resource/RemoteResourceManager.java | 204 ------------------ .../jackhuang/hmcl/util/CrashReporter.java | 4 +- README.md | 4 +- README_cn.md | 4 +- data-json/dynamic-remote-resources-raw.json | 24 --- data-json/dynamic-remote-resources.json | 1 - 23 files changed, 38 insertions(+), 359 deletions(-) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{hmcl => }/ExecutableHeaderHelper.java (99%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{hmcl => }/HMCLDownloadTask.java (98%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{hmcl => }/IntegrityChecker.java (99%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{hmcl => }/RemoteVersion.java (98%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{hmcl => }/UpdateChannel.java (96%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{hmcl => }/UpdateChecker.java (99%) rename HMCL/src/main/java/org/jackhuang/hmcl/upgrade/{hmcl => }/UpdateHandler.java (99%) delete mode 100644 HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java delete mode 100644 data-json/dynamic-remote-resources-raw.json delete mode 100644 data-json/dynamic-remote-resources.json diff --git a/HMCL/build.gradle.kts b/HMCL/build.gradle.kts index d5547601d3..a5c749b7ff 100644 --- a/HMCL/build.gradle.kts +++ b/HMCL/build.gradle.kts @@ -1,6 +1,3 @@ -import com.google.gson.Gson -import com.google.gson.JsonElement -import com.google.gson.JsonObject import java.net.URI import java.nio.file.FileSystems import java.nio.file.Files @@ -10,16 +7,6 @@ import java.security.Signature import java.security.spec.PKCS8EncodedKeySpec import java.util.zip.ZipFile -buildscript { - repositories { - mavenCentral() - } - - dependencies { - classpath("com.google.code.gson:gson:2.10.1") - } -} - plugins { id("com.github.johnrengelman.shadow") version "7.1.2" } @@ -94,59 +81,6 @@ fun attachSignature(jar: File) { } } -tasks.getByName("compileJava") { - dependsOn(tasks.create("computeDynamicResources") { - this@create.inputs.file(rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources-raw.json")) - this@create.outputs.file(rootProject.rootDir.toPath().resolve("data-json/dynamic-remote-resources.json")) - - doLast { - Gson().also { gsonInstance -> - rootProject.rootDir.resolve("data-json/dynamic-remote-resources-raw.json").bufferedReader().use { br -> - (gsonInstance.fromJson(br, JsonElement::class.java) as JsonObject) - }.also { data -> - data.asMap().forEach { (namespace, namespaceData) -> - (namespaceData as JsonObject).asMap().forEach { (name, nameData) -> - (nameData as JsonObject).asMap().forEach { (version, versionData) -> - require(versionData is JsonObject) - val localPath = - (versionData.get("local_path") as com.google.gson.JsonPrimitive).asString - val sha1 = (versionData.get("sha1") as com.google.gson.JsonPrimitive).asString - - val currentSha1 = digest( - "SHA-1", - rootProject.rootDir.resolve(localPath).readBytes() - ).joinToString(separator = "") { "%02x".format(it) } - - if (!sha1.equals(currentSha1, ignoreCase = true)) { - throw IllegalStateException("Mismatched SHA-1 in $.${namespace}.${name}.${version} of dynamic remote resources detected. Require ${currentSha1}, but found $sha1") - } - } - } - } - - rootProject.rootDir.resolve("data-json/dynamic-remote-resources.json").also { zippedPath -> - gsonInstance.toJson(data).also { expectedData -> - if (zippedPath.exists()) { - zippedPath.readText().also { rawData -> - if (rawData != expectedData) { - if (System.getenv("GITHUB_SHA") == null) { - zippedPath.writeText(expectedData) - } else { - throw IllegalStateException("Mismatched zipped dynamic-remote-resources json file!") - } - } - } - } else { - zippedPath.writeText(expectedData) - } - } - } - } - } - } - }) -} - val java11 = sourceSets.create("java11") { java { srcDir("src/main/java11") diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java index 489d779991..bf967fc5a8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Launcher.java @@ -33,12 +33,10 @@ import org.jackhuang.hmcl.task.AsyncTaskExecutor; import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateHandler; -import org.jackhuang.hmcl.upgrade.resource.RemoteResourceManager; +import org.jackhuang.hmcl.upgrade.UpdateChecker; +import org.jackhuang.hmcl.upgrade.UpdateHandler; import org.jackhuang.hmcl.util.CrashReporter; import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.JarUtils; import org.jackhuang.hmcl.util.platform.Architecture; @@ -123,10 +121,6 @@ public void start(Stage primaryStage) { UpdateChecker.init(); - RemoteResourceManager.init(); - - RemoteResourceManager.register(); - primaryStage.show(); }); } catch (Throwable e) { @@ -293,10 +287,6 @@ public static void main(String[] args) { if (OperatingSystem.CURRENT_OS.isLinuxOrBSD()) LOG.info("XDG Session Type: " + System.getenv("XDG_SESSION_TYPE")); - if (System.getProperty("hmcl.update_source.override") != null) { - Logging.LOG.log(Level.WARNING, "'hmcl.update_source.override' is deprecated! Please use 'hmcl.hmcl_update_source.override' instead"); - } - launch(Launcher.class, args); } catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter. CRASH_REPORTER.uncaughtException(Thread.currentThread(), e); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 0a599934f9..9d2e0c383b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -37,9 +37,7 @@ private Metadata() {} public static final String TITLE = NAME + " " + VERSION; public static final String FULL_TITLE = FULL_NAME + " v" + VERSION; - // hmcl.update_source.override is deprecated. If it is used, a warning message will be printed in org.jackhuang.hmcl.Launcher.main . - public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.hmcl_update_source.override", System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link")); - public static final String RESOURCE_UPDATE_URL = System.getProperty("hmcl.resource_update_source.override", "https://hmcl.huangyuhui.net/api/dynamic_remote_resource/update_link"); + public static final String HMCL_UPDATE_URL = System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link"); public static final String CONTACT_URL = "https://docs.hmcl.net/help.html"; public static final String HELP_URL = "https://docs.hmcl.net"; public static final String CHANGELOG_URL = "https://docs.hmcl.net/changelog/"; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java index 6761ad9866..cb8936947e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/CrashWindow.java @@ -28,7 +28,7 @@ import javafx.stage.Stage; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.countly.CrashReport; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; +import org.jackhuang.hmcl.upgrade.UpdateChecker; import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java index 733e393da9..b43133a1cf 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/UpgradeDialog.java @@ -25,7 +25,7 @@ import javafx.scene.web.WebView; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.ui.construct.DialogCloseEvent; -import org.jackhuang.hmcl.upgrade.hmcl.RemoteVersion; +import org.jackhuang.hmcl.upgrade.RemoteVersion; import java.util.logging.Level; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java index dfa578cc2e..06c6763a0e 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/account/CreateAccountPane.java @@ -58,7 +58,7 @@ import org.jackhuang.hmcl.ui.SVG; import org.jackhuang.hmcl.ui.WeakListenerHolder; import org.jackhuang.hmcl.ui.construct.*; -import org.jackhuang.hmcl.upgrade.hmcl.IntegrityChecker; +import org.jackhuang.hmcl.upgrade.IntegrityChecker; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter; import org.jackhuang.hmcl.util.javafx.BindingMapping; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java index f613d8fb30..6f1cf7f176 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/MainPage.java @@ -53,9 +53,9 @@ import org.jackhuang.hmcl.ui.decorator.DecoratorPage; import org.jackhuang.hmcl.ui.versions.GameItem; import org.jackhuang.hmcl.ui.versions.Versions; -import org.jackhuang.hmcl.upgrade.hmcl.RemoteVersion; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateHandler; +import org.jackhuang.hmcl.upgrade.RemoteVersion; +import org.jackhuang.hmcl.upgrade.UpdateChecker; +import org.jackhuang.hmcl.upgrade.UpdateHandler; import org.jackhuang.hmcl.util.javafx.BindingMapping; import org.jackhuang.hmcl.util.javafx.MappedObservableList; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index d6dd1c5fb0..36d5af536f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -43,7 +43,7 @@ import org.jackhuang.hmcl.ui.nbt.NBTHelper; import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem; import org.jackhuang.hmcl.ui.versions.Versions; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; +import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.io.CompressingUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java index a76070c1c6..b534709448 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SettingsPage.java @@ -27,10 +27,10 @@ import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType; -import org.jackhuang.hmcl.upgrade.hmcl.RemoteVersion; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateChannel; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateHandler; +import org.jackhuang.hmcl.upgrade.RemoteVersion; +import org.jackhuang.hmcl.upgrade.UpdateChannel; +import org.jackhuang.hmcl.upgrade.UpdateChecker; +import org.jackhuang.hmcl.upgrade.UpdateHandler; import org.jackhuang.hmcl.util.Logging; import org.jackhuang.hmcl.util.i18n.Locales; import org.jackhuang.hmcl.util.io.FileUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java index 7d7a22cb4d..8dc68a1f61 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModTranslations.java @@ -18,13 +18,11 @@ package org.jackhuang.hmcl.ui.versions; import org.jackhuang.hmcl.mod.RemoteModRepository; -import org.jackhuang.hmcl.upgrade.resource.RemoteResourceManager; import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.IOUtils; import org.jetbrains.annotations.Nullable; -import java.io.InputStream; import java.util.*; import java.util.logging.Level; import java.util.stream.Collectors; @@ -38,19 +36,19 @@ * @see mcmod.cn */ public enum ModTranslations { - MOD("/assets/mod_data.txt", "translation", "mod_data", "1") { + MOD("/assets/mod_data.txt") { @Override public String getMcmodUrl(Mod mod) { return String.format("https://www.mcmod.cn/class/%s.html", mod.getMcmod()); } }, - MODPACK("/assets/modpack_data.txt", "translation", "modpack_data", "1") { + MODPACK("/assets/modpack_data.txt") { @Override public String getMcmodUrl(Mod mod) { return String.format("https://www.mcmod.cn/modpack/%s.html", mod.getMcmod()); } }, - EMPTY("", "", "", "") { + EMPTY("") { @Override public String getMcmodUrl(Mod mod) { return ""; @@ -68,18 +66,15 @@ public static ModTranslations getTranslationsByRepositoryType(RemoteModRepositor } } - private final String defaultResourceName; - private final RemoteResourceManager.RemoteResourceKey remoteResourceKey; + private final String resourceName; private List mods; private Map modIdMap; // mod id -> mod private Map curseForgeMap; // curseforge id -> mod private List> keywords; private int maxKeywordLength = -1; - ModTranslations(String defaultResourceName, String namespace, String name, String version) { - this.defaultResourceName = defaultResourceName; - - remoteResourceKey = RemoteResourceManager.get(namespace, name, version, () -> ModTranslations.class.getResourceAsStream(defaultResourceName)); + ModTranslations(String resourceName) { + this.resourceName = resourceName; } @Nullable @@ -120,24 +115,19 @@ public List searchMod(String query) { .collect(Collectors.toList()); } - private boolean loaded() { + private boolean loadFromResource() { if (mods != null) return true; - if (StringUtils.isBlank(defaultResourceName)) { + if (StringUtils.isBlank(resourceName)) { mods = Collections.emptyList(); return true; } try { - InputStream inputStream = remoteResourceKey.getResource(); - if (inputStream == null) { - return false; - } - - String modData = IOUtils.readFullyAsString(inputStream); + String modData = IOUtils.readFullyAsString(ModTranslations.class.getResourceAsStream(resourceName)); mods = Arrays.stream(modData.split("\n")).filter(line -> !line.startsWith("#")).map(Mod::new).collect(Collectors.toList()); return true; } catch (Exception e) { - LOG.log(Level.WARNING, "Failed to load " + defaultResourceName, e); + LOG.log(Level.WARNING, "Failed to load " + resourceName, e); return false; } } @@ -148,7 +138,7 @@ private boolean loadCurseForgeMap() { } if (mods == null) { - if (!loaded()) return false; + if (!loadFromResource()) return false; } curseForgeMap = new HashMap<>(); @@ -166,7 +156,7 @@ private boolean loadModIdMap() { } if (mods == null) { - if (!loaded()) return false; + if (!loadFromResource()) return false; } modIdMap = new HashMap<>(); @@ -186,7 +176,7 @@ private boolean loadKeywords() { } if (mods == null) { - if (!loaded()) return false; + if (!loadFromResource()) return false; } keywords = new ArrayList<>(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/ExecutableHeaderHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/ExecutableHeaderHelper.java similarity index 99% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/ExecutableHeaderHelper.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/ExecutableHeaderHelper.java index e5c9ccb5f5..7c47f586d8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/ExecutableHeaderHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/ExecutableHeaderHelper.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade.hmcl; +package org.jackhuang.hmcl.upgrade; import java.io.IOException; import java.io.InputStream; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/HMCLDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java similarity index 98% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/HMCLDownloadTask.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java index 0ae8358a52..8b6fdc06c3 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/HMCLDownloadTask.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/HMCLDownloadTask.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade.hmcl; +package org.jackhuang.hmcl.upgrade; import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.util.Pack200Utils; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/IntegrityChecker.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java similarity index 99% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/IntegrityChecker.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java index 8833f94c0d..d03600e2e6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/IntegrityChecker.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/IntegrityChecker.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade.hmcl; +package org.jackhuang.hmcl.upgrade; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.util.DigestUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/RemoteVersion.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java similarity index 98% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/RemoteVersion.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java index 776881aca9..c3ac2caaf7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/RemoteVersion.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/RemoteVersion.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade.hmcl; +package org.jackhuang.hmcl.upgrade; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChannel.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChannel.java similarity index 96% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChannel.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChannel.java index f56d645dd3..998a3da7d2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChannel.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChannel.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade.hmcl; +package org.jackhuang.hmcl.upgrade; import org.jackhuang.hmcl.Metadata; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChecker.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java similarity index 99% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChecker.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java index d26c10a0b3..f91b4cc793 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateChecker.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade.hmcl; +package org.jackhuang.hmcl.upgrade; import javafx.application.Platform; import javafx.beans.binding.Bindings; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateHandler.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java similarity index 99% rename from HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateHandler.java rename to HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java index f027fff1ed..eb2d179a84 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/hmcl/UpdateHandler.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateHandler.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.jackhuang.hmcl.upgrade.hmcl; +package org.jackhuang.hmcl.upgrade; import com.google.gson.Gson; import com.google.gson.JsonParseException; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java deleted file mode 100644 index b9c4a31dba..0000000000 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/resource/RemoteResourceManager.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * 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 . - */ -package org.jackhuang.hmcl.upgrade.resource; - -import com.google.gson.annotations.SerializedName; -import com.google.gson.reflect.TypeToken; -import org.jackhuang.hmcl.Metadata; -import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.task.Schedulers; -import org.jackhuang.hmcl.task.Task; -import org.jackhuang.hmcl.ui.versions.ModTranslations; -import org.jackhuang.hmcl.upgrade.hmcl.IntegrityChecker; -import org.jackhuang.hmcl.util.DigestUtils; -import org.jackhuang.hmcl.util.function.ExceptionalSupplier; -import org.jackhuang.hmcl.util.io.HttpRequest; -import org.jackhuang.hmcl.util.io.IOUtils; -import org.jackhuang.hmcl.util.io.NetworkUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; - -public final class RemoteResourceManager { - private RemoteResourceManager() { - } - - private static final class RemoteResource { - @SerializedName("sha1") - private final String sha1; - - @SerializedName("urls") - private final String[] urls; - - private transient byte[] data = null; - - private RemoteResource(String sha1, String[] urls) { - this.sha1 = sha1; - this.urls = urls; - } - - public void download(Path path, Runnable callback) { - if (data != null) { - return; - } - - new FileDownloadTask(Arrays.stream(urls).map(NetworkUtils::toURL).collect(Collectors.toList()), path.toFile(), new FileDownloadTask.IntegrityCheck("SHA-1", sha1)) - .whenComplete(Schedulers.defaultScheduler(), (result, exception) -> { - if (exception != null) { - data = Files.readAllBytes(path); - callback.run(); - } - }).start(); - } - } - - public static final class RemoteResourceKey { - private final String namespace; - private final String name; - private final String version; - private final Path cachePath; - private final ExceptionalSupplier localResourceSupplier; - private String localResourceSha1 = null; - - public RemoteResourceKey(String namespace, String name, String version, ExceptionalSupplier localResourceSupplier) { - this.namespace = namespace; - this.name = name; - this.version = version; - this.localResourceSupplier = localResourceSupplier; - - this.cachePath = Metadata.HMCL_DIRECTORY.resolve("remoteResources").resolve(namespace).resolve(name).resolve(version).resolve(String.format("%s-%s-%s.resource", namespace, name, version)); - } - - private InputStream getLocalResource() throws IOException { - if (Files.isReadable(cachePath)) { - return Files.newInputStream(cachePath); - } - return localResourceSupplier.get(); - } - - private String getLocalResourceSha1() throws IOException { - if (localResourceSha1 == null) { - localResourceSha1 = DigestUtils.digestToString("SHA-1", IOUtils.readFullyAsByteArray(getLocalResource())); - } - - return localResourceSha1; - } - - @Nullable - private RemoteResource getRemoteResource() { - return Optional.ofNullable(remoteResources.get(namespace)).map(map -> map.get(name)).map(map -> map.get(version)).orElse(null); - } - - @Nullable - public InputStream getResource() throws IOException { - RemoteResource remoteResource = getRemoteResource(); - - if (remoteResource == null) { - return getLocalResource(); - } - - if (remoteResource.sha1.equals(getLocalResourceSha1())) { - return getLocalResource(); - } - - if (remoteResource.data == null) { - return null; - } - - return new ByteArrayInputStream(remoteResource.data); - } - - public void downloadRemoteResourceIfNecessary() throws IOException { - RemoteResource remoteResource = getRemoteResource(); - - if (remoteResource == null) { - return; - } - - if (remoteResource.sha1.equals(getLocalResourceSha1())) { - return; - } - - remoteResource.download(cachePath, () -> localResourceSha1 = null); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - RemoteResourceKey that = (RemoteResourceKey) o; - - if (!namespace.equals(that.namespace)) return false; - if (!name.equals(that.name)) return false; - return version.equals(that.version); - } - - @Override - public int hashCode() { - int result = namespace.hashCode(); - result = 31 * result + name.hashCode(); - result = 31 * result + version.hashCode(); - return result; - } - } - - private static final Map>> remoteResources = new ConcurrentHashMap<>(); - - private static final Map keys = new ConcurrentHashMap<>(); - - public static void init() { - Task.>>>supplyAsync(() -> - IntegrityChecker.isSelfVerified() ? HttpRequest.GET(Metadata.RESOURCE_UPDATE_URL).getJson( - new TypeToken>>>() { - }.getType() - ) : null - ).whenComplete(Schedulers.defaultScheduler(), (result, exception) -> { - if (exception == null && result != null) { - remoteResources.clear(); - remoteResources.putAll(result); - - for (RemoteResourceKey key : keys.values()) { - key.downloadRemoteResourceIfNecessary(); - } - } - }).start(); - } - - public static void register() { - ModTranslations.values(); - } - - public static RemoteResourceKey get(@NotNull String namespace, @NotNull String name, @NotNull String version, ExceptionalSupplier defaultSupplier) { - String stringKey = String.format("%s:%s:%s", namespace, name, version); - RemoteResourceKey key = keys.containsKey(stringKey) ? keys.get(stringKey) : new RemoteResourceKey(namespace, name, version, defaultSupplier); - Task.runAsync(key::downloadRemoteResourceIfNecessary).start(); - keys.put(stringKey, key); - return key; - } -} diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java index 5e5e28fe55..f1d00548f5 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/CrashReporter.java @@ -23,8 +23,8 @@ import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.countly.CrashReport; import org.jackhuang.hmcl.ui.CrashWindow; -import org.jackhuang.hmcl.upgrade.hmcl.IntegrityChecker; -import org.jackhuang.hmcl.upgrade.hmcl.UpdateChecker; +import org.jackhuang.hmcl.upgrade.IntegrityChecker; +import org.jackhuang.hmcl.upgrade.UpdateChecker; import org.jackhuang.hmcl.util.io.NetworkUtils; import java.io.IOException; diff --git a/README.md b/README.md index 55690a1336..d8e2df63f6 100644 --- a/README.md +++ b/README.md @@ -66,9 +66,7 @@ Make sure you have Java installed with JavaFX 8 at least. Liberica Full JDK 8 or | `-Dhmcl.bmclapi.override=` | Override API Root of BMCLAPI download provider, defaults to `https://bmclapi2.bangbang93.com`. e.g. `https://download.mcbbs.net`. | | `-Dhmcl.font.override=` | Override font family. | | `-Dhmcl.version.override=` | Override the version number. | -| ~~`-Dhmcl.update_source.override=`~~ | Override the update source for HMCL itself. (Deprecated, please use `hmcl.hmcl_update_source.override` instead.) | -| `-Dhmcl.hmcl_update_source.override=` | Override the update source for HMCL itself. | -| `-Dhmcl.resource_update_source.override=` | Override the update source for dynamic remote resources. | +| `-Dhmcl.update_source.override=` | Override the update source for HMCL itself. | | `-Dhmcl.authlibinjector.location=` | Use specified authlib-injector (instead of downloading one). | | `-Dhmcl.openjfx.repo=` | Add custom Maven repository for download OpenJFX. | | `-Dhmcl.native.encoding=` | Override the native encoding. | diff --git a/README_cn.md b/README_cn.md index 0afaffa94b..3b7bbef9da 100644 --- a/README_cn.md +++ b/README_cn.md @@ -64,9 +64,7 @@ HMCL 有着强大的跨平台能力. 它不仅支持 Windows、Linux、macOS 等 | `-Dhmcl.bmclapi.override=` | 覆盖 BMCLAPI 的 API Root, 默认值为 `https://bmclapi2.bangbang93.com`. 例如 `https://download.mcbbs.net`. | | `-Dhmcl.font.override=` | 覆盖字族. | | `-Dhmcl.version.override=` | 覆盖版本号. | -| ~~`-Dhmcl.update_source.override=`~~ | 覆盖 HMCL 更新源(已弃用,请使用 `hmcl.hmcl_update_source.override`). | -| `-Dhmcl.hmcl_update_source.override=` | 覆盖 HMCL 更新源. | -| `-Dhmcl.resource_update_source.override=` | 覆盖动态远程资源更新源. | +| `-Dhmcl.update_source.override=` | 覆盖 HMCL 更新源. | | `-Dhmcl.authlibinjector.location=` | 使用指定的 authlib-injector (而非下载一个). | | `-Dhmcl.openjfx.repo=` | 添加用于下载 OpenJFX 的自定义 Maven 仓库 | | `-Dhmcl.native.encoding=` | 覆盖原生编码. | diff --git a/data-json/dynamic-remote-resources-raw.json b/data-json/dynamic-remote-resources-raw.json deleted file mode 100644 index a765752068..0000000000 --- a/data-json/dynamic-remote-resources-raw.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "translation": { - "mod_data": { - "1": { - "urls": [ - "https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt", - "https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt" - ], - "local_path": "HMCL/src/main/resources/assets/mod_data.txt", - "sha1": "0ae36a65a00b00176358bd6b0d3c8787b3668c23" - } - }, - "modpack_data": { - "1": { - "urls": [ - "https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt", - "https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt" - ], - "local_path": "HMCL/src/main/resources/assets/modpack_data.txt", - "sha1": "b0e771db170835e1154da4c21b7417a688836162" - } - } - } -} \ No newline at end of file diff --git a/data-json/dynamic-remote-resources.json b/data-json/dynamic-remote-resources.json deleted file mode 100644 index b14477adee..0000000000 --- a/data-json/dynamic-remote-resources.json +++ /dev/null @@ -1 +0,0 @@ -{"translation":{"mod_data":{"1":{"urls":["https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt","https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/raw/javafx/HMCL/src/main/resources/assets/mod_data.txt"],"local_path":"HMCL/src/main/resources/assets/mod_data.txt","sha1":"0ae36a65a00b00176358bd6b0d3c8787b3668c23"}},"modpack_data":{"1":{"urls":["https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt","https://rgp.zkitefly.repl.co/https://github.com/huanghongxun/HMCL/blob/javafx/HMCL/src/main/resources/assets/modpack_data.txt"],"local_path":"HMCL/src/main/resources/assets/modpack_data.txt","sha1":"b0e771db170835e1154da4c21b7417a688836162"}}}} \ No newline at end of file From 59cc5b9114153def6668317631f6aa85a20676ff Mon Sep 17 00:00:00 2001 From: Glavo Date: Thu, 8 Feb 2024 19:27:28 +0800 Subject: [PATCH 05/33] =?UTF-8?q?Close=20#2766:=20=E4=B8=8D=E5=BA=94?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=20Quilt=20=E4=B8=8E=20Fabric=20API=20?= =?UTF-8?q?=E4=B8=8D=E5=85=BC=E5=AE=B9=20(#2768)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Close #2766 * update --- HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index 3d72555b2e..9c3da9be6f 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -170,7 +170,7 @@ private void mutualIncompatible(InstallerItem... items) { public InstallerItemGroup(String gameVersion) { mutualIncompatible(forge, fabric, quilt, neoForge, liteLoader); addIncompatibles(optiFine, fabric, quilt, neoForge); - addIncompatibles(fabricApi, forge, quilt, quiltApi, neoForge, liteLoader, optiFine); + addIncompatibles(fabricApi, forge, quiltApi, neoForge, liteLoader, optiFine); addIncompatibles(quiltApi, forge, fabric, fabricApi, neoForge, liteLoader, optiFine); InvalidationListener listener = o -> { From b7ffb763dc6a22a892434ecb11b7cd86dc2b5fd9 Mon Sep 17 00:00:00 2001 From: Yukinoshita Coldshine <31348408+coldshineb@users.noreply.github.com> Date: Sun, 11 Feb 2024 19:44:48 -0500 Subject: [PATCH 06/33] Delete .github directory --- .github/ISSUE_TEMPLATE/bug-report.yml | 55 ---------- .github/ISSUE_TEMPLATE/config.yml | 14 --- .github/ISSUE_TEMPLATE/feature.yml | 22 ---- .github/workflows/check-style.yml | 21 ---- .github/workflows/check-translations.yml | 19 ---- .github/workflows/check-update.yml | 131 ----------------------- .github/workflows/gitee.yml | 21 ---- .github/workflows/gradle.yml | 30 ------ 8 files changed, 313 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature.yml delete mode 100644 .github/workflows/check-style.yml delete mode 100644 .github/workflows/check-translations.yml delete mode 100644 .github/workflows/check-update.yml delete mode 100644 .github/workflows/gitee.yml delete mode 100644 .github/workflows/gradle.yml diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml deleted file mode 100644 index e9f530d068..0000000000 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Bug 反馈 | Bug Feedback -description: - 反馈一个 HMCL 错误。| Bug Feedback. -title: "[Bug] " -labels: bug -body: - - type: markdown - attributes: - value: | - 提交前请确认: - * 该问题确实是 **HMCL 的错误**,而**不是 Minecraft 非正常退出**,如果你的 Minecraft 非正常退出,请前往 [KOOK 频道](https://kook.top/Kx7n3t) 或 [Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。 - * 你的启动器版本是**最新的快照版本**,可以点击 [此处](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) 下载最新快照版本。 - - 如果你的问题并不属于上述两类,你可以选取另一种 Issue 类型,或者直接前往 [KOOK 频道](https://kook.top/Kx7n3t) 或 [Discord 频道](https://discord.gg/jVvC7HfM6U) 中获取帮助。 - 如果你希望在 QQ 群中反馈问题,请 [赞助 HMCL](https://hmcl.huangyuhui.net/api/redirect/sponsor) 后申请加入群聊,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。 - - Before submitting, please confirm: - * The issue is indeed **an error of HMCL**, not **Minecraft abnormal exit**. If your Minecraft exits abnormally, please go to the [KOOK channel](https://kook.top/Kx7n3t) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help. - * Your launcher version is **the latest snapshot version**. You can click [here](https://github.com/burningtnt/HMCL-Snapshot-Update/raw/master/datas/HMCL-dev.jar) to download the latest snapshot version. - - If your issue does not fall into the above two categories, you can choose another type of issue or directly go to the [KOOK channel](https://kook.top/Kx7n3t) or [Discord channel](https://discord.gg/jVvC7HfM6U) for help. - If you want to report a problem in the QQ group, please [sponsor HMCL](https://hmcl.huangyuhui.net/api/redirect/sponsor) and apply to join the group chat. Please also fill in the information form below and send it directly in the group to speed up our communication. - - type: input - id: platform - attributes: - label: 平台 | Platform - description: 请输入您遇到 BUG 的平台。 Please enter the platform on which you encountered the bug. - placeholder: e.g. Windows 11 - validations: - required: true - - type: textarea - id: bug-report - attributes: - label: 问题描述 | Problem Description - description: 请尽可能地详细描述你所遇到的问题,并描述如何重新触发这个问题。 Please describe the problem you met in as much detail as possible. Besides, please describe how to trigger this problem again. - placeholder: | - 1. 点击 HMCL 上的某个按钮 | Click a button named ... - 2. 向下翻页 | Scroll down - 3. ... - validations: - required: true - - type: textarea - id: hmcl-crash-report-or-logs - attributes: - label: 启动器崩溃报告 / 启动器日志文件 | Launcher Crash Report / Launcher Log File - description: | - 如果您的启动器崩溃了,请将崩溃报告填入(或将文件拖入)下方。 - 如果您的启动器没有崩溃,请在遇到问题后不要退出启动器,在启动器的【设置】>【通用】>【调试】一栏中点击“导出启动器日志”,并将导出的日志拖到下方的输入栏中。 - **请注意:启动器崩溃报告或日志文件是诊断问题的重要依据,请务必上传!** - - If your launcher crashes, please fill it in (or drag the file in) below. - If your launcher doesn't crash, please DO NOT EXIT your launcher, click "Export Launcher Log" in the [Settings] > [General] > [Debug] column of the launcher, and drag the exported log to the input field below. - **Attention: The crash report or the log file is the key to solve the problem. Please update it!** - validations: - required: true \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index d36d0b824e..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,14 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: KOOK 频道 | KOOK Channel - url: https://kook.top/Kx7n3t - about: Hello Minecraft! Launcher 的官方 KOOK 频道。| The official KOOK channel of Hello Minecraft! Laucher. - - name: Discord 频道 | Discord Channel - url: https://discord.gg/jVvC7HfM6U - about: Hello Minecraft! Launcher 的官方 Discord 频道。| The official Discord channel of Hello Minecraft! Launcher. - - name: 赞助通道 | Sponsorship channel - url: https://afdian.net/@huanghongxun - about: 前往爱发电赞助 HMCL,赞助后可以申请加入官方 QQ 群反馈问题。| Sponsor Hello Minecraft! Launcher on afdian.net and you can apply to join the official QQ group. - - name: 其他反馈 | Others - url: https://github.com/HMCL-dev/HMCL/discussions/new/choose - about: 通过 Discussions 反馈其他问题。| Report other problems in Discussions. diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml deleted file mode 100644 index 5bab343941..0000000000 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: 新功能 | Feature Request -description: 为 HMCL 提出新功能 | A new feature for HMCL -title: "[Feature] " -labels: enhancement -body: -- type: markdown - attributes: - value: 请确认 Issues 列表无重复的项目。| Please make sure that no duplicated issues has already been delivered. -- type: textarea - id: description - attributes: - label: 描述 | Description - description: 请详细描述你想加入的新功能。| Please describe the new feature detaily. - validations: - required: true -- type: textarea - id: reason - attributes: - label: 原因 | Reason - description: 请描述该功能带来的好处及原因。| Please describe why you want to add the feature into HMCL. - validations: - required: true diff --git a/.github/workflows/check-style.yml b/.github/workflows/check-style.yml deleted file mode 100644 index 5c2b4bca29..0000000000 --- a/.github/workflows/check-style.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Check Style - -on: [push, pull_request] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '11' - java-package: 'jdk+fx' - - name: Check style main - run: ./gradlew checkstyleMain - - name: Check style test - run: ./gradlew checkstyleTest diff --git a/.github/workflows/check-translations.yml b/.github/workflows/check-translations.yml deleted file mode 100644 index 879bcdc0d0..0000000000 --- a/.github/workflows/check-translations.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Check Translations - -on: [push, pull_request] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '11' - java-package: 'jdk+fx' - - name: Check style test - run: ./gradlew checkTranslations diff --git a/.github/workflows/check-update.yml b/.github/workflows/check-update.yml deleted file mode 100644 index 6f600bedd7..0000000000 --- a/.github/workflows/check-update.yml +++ /dev/null @@ -1,131 +0,0 @@ -name: Check Update - -on: - workflow_dispatch: - schedule: - - cron: '30 * * * *' - -permissions: - contents: write - -jobs: - dev-check-update: - if: ${{ github.repository_owner == 'HMCL-dev' }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Fetch tags - run: git fetch --all --tags - - name: Install tools - run: sudo apt-get install -y jq - - name: Fetch last version - run: | - wget -O ci.json https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/api/json - - export HMCL_EXE_FILE_NAME=`cat ci.json | jq -M -r '.artifacts[] | select(.fileName | endswith(".exe")) | .fileName'` - if [ -z `echo $HMCL_EXE_FILE_NAME | grep -E "^HMCL-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?\.exe\$"` ]; then exit 1; fi - - export HMCL_VERSION=`echo "${HMCL_EXE_FILE_NAME%.exe}" | tail -c +6` - export HMCL_COMMIT_SHA=`cat ci.json | jq -M -r '.actions[] | select(._class == "hudson.plugins.git.util.BuildData") | .lastBuiltRevision.SHA1'` - - if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi - - echo "HMCL_VERSION=$HMCL_VERSION" >> $GITHUB_ENV - echo "HMCL_COMMIT_SHA=$HMCL_COMMIT_SHA" >> $GITHUB_ENV - echo "HMCL_TAG_NAME=v$HMCL_VERSION" >> $GITHUB_ENV - - name: Check for existing tags - run: if [ -z "$(git tag -l "$HMCL_TAG_NAME")" ]; then echo "continue=true" >> $GITHUB_ENV; fi - - name: Download artifacts - if: ${{ env.continue == 'true' }} - run: | - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.exe" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.exe.sha1" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha1" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh.sha1" - env: - DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL/lastSuccessfulBuild/artifact/HMCL/build/libs - - name: Generate release note - if: ${{ env.continue == 'true' }} - run: | - echo "HMCL v$HMCL_VERSION" >> RELEASE_NOTE - echo "" >> RELEASE_NOTE - echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/dev.html" >> RELEASE_NOTE - echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE - - name: Create release - if: ${{ env.continue == 'true' }} - uses: softprops/action-gh-release@v1 - with: - body_path: RELEASE_NOTE - files: | - HMCL-${{ env.HMCL_VERSION }}.exe - HMCL-${{ env.HMCL_VERSION }}.exe.sha1 - HMCL-${{ env.HMCL_VERSION }}.jar - HMCL-${{ env.HMCL_VERSION }}.jar.sha1 - HMCL-${{ env.HMCL_VERSION }}.sh - HMCL-${{ env.HMCL_VERSION }}.sh.sha1 - target_commitish: ${{ env.HMCL_COMMIT_SHA }} - name: ${{ env.HMCL_TAG_NAME }} - tag_name: ${{ env.HMCL_TAG_NAME }} - stable-check-update: - if: ${{ github.repository_owner == 'HMCL-dev' }} - needs: dev-check-update - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Fetch tags - run: git fetch --all --tags - - name: Install tools - run: sudo apt-get install -y jq - - name: Fetch last version - run: | - wget -O ci.json https://ci.huangyuhui.net/job/HMCL-stable/lastSuccessfulBuild/api/json - - export HMCL_EXE_FILE_NAME=`cat ci.json | jq -M -r '.artifacts[] | select(.fileName | endswith(".exe")) | .fileName'` - if [ -z `echo $HMCL_EXE_FILE_NAME | grep -E "^HMCL-[0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?\.exe\$"` ]; then exit 1; fi - - export HMCL_VERSION=`echo "${HMCL_EXE_FILE_NAME%.exe}" | tail -c +6` - export HMCL_COMMIT_SHA=`cat ci.json | jq -M -r '.actions[] | select(._class == "hudson.plugins.git.util.BuildData") | .lastBuiltRevision.SHA1'` - - if [ "${#HMCL_COMMIT_SHA}" != 40 ]; then exit 1; fi - - echo "HMCL_VERSION=$HMCL_VERSION" >> $GITHUB_ENV - echo "HMCL_COMMIT_SHA=$HMCL_COMMIT_SHA" >> $GITHUB_ENV - echo "HMCL_TAG_NAME=release-$HMCL_VERSION" >> $GITHUB_ENV - - name: Check for existing tags - run: if ! git tag -l | grep -q "$HMCL_TAG_NAME"; then echo "continue=true" >> $GITHUB_ENV; fi - - name: Download artifacts - if: ${{ env.continue == 'true' }} - run: | - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.exe" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.exe.sha1" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.jar.sha1" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh" - wget "$DOWNLOAD_BASE_URL/HMCL-$HMCL_VERSION.sh.sha1" - env: - DOWNLOAD_BASE_URL: https://ci.huangyuhui.net/job/HMCL-stable/lastSuccessfulBuild/artifact/HMCL/build/libs - - name: Generate release note - if: ${{ env.continue == 'true' }} - run: | - echo " === **Stable Version** === " >> RELEASE_NOTE - echo "HMCL v$HMCL_VERSION" >> RELEASE_NOTE - echo "" >> RELEASE_NOTE - echo "The full changelogs can be found on the website: https://docs.hmcl.net/changelog/stable.html" >> RELEASE_NOTE - echo "Notice: changelogs are written in Chinese." >> RELEASE_NOTE - - name: Create release - if: ${{ env.continue == 'true' }} - uses: softprops/action-gh-release@v1 - with: - body_path: RELEASE_NOTE - files: | - HMCL-${{ env.HMCL_VERSION }}.exe - HMCL-${{ env.HMCL_VERSION }}.exe.sha1 - HMCL-${{ env.HMCL_VERSION }}.jar - HMCL-${{ env.HMCL_VERSION }}.jar.sha1 - HMCL-${{ env.HMCL_VERSION }}.sh - HMCL-${{ env.HMCL_VERSION }}.sh.sha1 - target_commitish: ${{ env.HMCL_COMMIT_SHA }} - name: ${{ env.HMCL_TAG_NAME }} - tag_name: ${{ env.HMCL_TAG_NAME }} diff --git a/.github/workflows/gitee.yml b/.github/workflows/gitee.yml deleted file mode 100644 index 4ec85b097c..0000000000 --- a/.github/workflows/gitee.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Sync to Gitee - -on: - push - -jobs: - run: - if: ${{ github.repository_owner == 'HMCL-dev' }} - runs-on: ubuntu-latest - steps: - - name: Mirror GitHub to Gitee - uses: Yikun/hub-mirror-action@v1.3 - with: - src: github/HMCL-dev - dst: gitee/huanghongxun - static_list: 'HMCL' - force_update: true - debug: true - dst_key: ${{ secrets.GITEE_SYNC_BOT_PRIVATE_KEY }} - dst_token: ${{ secrets.GITEE_SYNC_BOT_TOKEN }} - cache_path: /github/workspace/hub-mirror-cache diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml deleted file mode 100644 index 99f059d62a..0000000000 --- a/.github/workflows/gradle.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Java CI - -on: [push, pull_request] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Set up JDK 11 - uses: actions/setup-java@v3 - with: - distribution: 'zulu' - java-version: '11' - java-package: 'jdk+fx' - - name: Build with Gradle - run: ./gradlew build - env: - MICROSOFT_AUTH_ID: ${{ secrets.MICROSOFT_AUTH_ID }} - MICROSOFT_AUTH_SECRET: ${{ secrets.MICROSOFT_AUTH_SECRET }} - CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} - - name: Get short SHA - run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV - - name: Upload Artifacts - uses: actions/upload-artifact@v3 - with: - name: HMCL-${{ env.SHORT_SHA }} - path: HMCL/build/libs From 8db8693132ea0c0ef49179b8abbe5652aa4cc81d Mon Sep 17 00:00:00 2001 From: zkitefly Date: Mon, 12 Feb 2024 23:31:29 +0800 Subject: [PATCH 07/33] updata crash report analysis (#2785) * updata OUT_OF_MEMORY * Update CrashReportAnalyzer.java --------- Co-authored-by: Glavo --- .../java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java | 2 +- .../org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java | 7 +++++++ HMCLCore/src/test/resources/logs/out_of_memory2.txt | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 HMCLCore/src/test/resources/logs/out_of_memory2.txt diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java index 1829d8f9c0..662b517fae 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java @@ -47,7 +47,7 @@ public enum Rule { OPENGL_NOT_SUPPORTED(Pattern.compile("The driver does not appear to support OpenGL")), GRAPHICS_DRIVER(Pattern.compile("(Pixel format not accelerated|GLX: Failed to create context: GLXBadFBConfig|Couldn't set pixel format|net\\.minecraftforge\\.fml.client\\.SplashProgress|org\\.lwjgl\\.LWJGLException|EXCEPTION_ACCESS_VIOLATION(.|\\n|\\r)+# C {2}\\[(ig|atio|nvoglv))")), // Out of memory - OUT_OF_MEMORY(Pattern.compile("(java\\.lang\\.OutOfMemoryError|The system is out of physical RAM or swap space|Out of Memory Error)")), + OUT_OF_MEMORY(Pattern.compile("(java\\.lang\\.OutOfMemoryError|The system is out of physical RAM or swap space|Out of Memory Error|Error occurred during initialization of VM\\RToo small maximum heap)")), // Memory exceeded MEMORY_EXCEEDED(Pattern.compile("There is insufficient memory for the Java Runtime Environment to continue")), // Too high resolution diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java index 9500ba1fa1..41b3477594 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java @@ -324,6 +324,13 @@ public void outOfMemoryJVM() throws IOException { CrashReportAnalyzer.Rule.OUT_OF_MEMORY); } + @Test + public void outOfMemoryJVM1() throws IOException { + CrashReportAnalyzer.Result result = findResultByRule( + CrashReportAnalyzer.anaylze(loadLog("/logs/out_of_memory2.txt")), + CrashReportAnalyzer.Rule.OUT_OF_MEMORY); + } + @Test public void memoryExceeded() throws IOException { CrashReportAnalyzer.Result result = findResultByRule( diff --git a/HMCLCore/src/test/resources/logs/out_of_memory2.txt b/HMCLCore/src/test/resources/logs/out_of_memory2.txt new file mode 100644 index 0000000000..2ac79e4a6f --- /dev/null +++ b/HMCLCore/src/test/resources/logs/out_of_memory2.txt @@ -0,0 +1,3 @@ +Error occurred during initialization of VM +Too small maximum heap +[HMCL ProcessListener] Minecraft exit with code 1(0x1). From 9a76b1ff2747d233ff95c3711468c65850c12b97 Mon Sep 17 00:00:00 2001 From: Glavo Date: Tue, 13 Feb 2024 00:35:45 +0800 Subject: [PATCH 08/33] Bump Gradle to 8.6 (#2786) --- gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 14 +++++++------- javafx.gradle.kts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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 literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e0930b..a80b22ce5c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 0adc8e1a53..1aa94a4269 100755 --- a/gradlew +++ b/gradlew @@ -145,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -153,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -202,11 +202,11 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/javafx.gradle.kts b/javafx.gradle.kts index cf386562bb..8fccd05337 100644 --- a/javafx.gradle.kts +++ b/javafx.gradle.kts @@ -30,7 +30,7 @@ data class Platform( val jfxModules = listOf("base", "graphics", "controls", "media", "web") val jfxMirrorRepos = listOf("https://mirrors.cloud.tencent.com/nexus/repository/maven-public") -val jfxDependenciesFile = project("HMCL").buildDir.resolve("openjfx-dependencies.json") +val jfxDependenciesFile = project("HMCL").layout.buildDirectory.file("openjfx-dependencies.json").get().asFile val jfxPlatforms = listOf( Platform("windows-x86", "win-x86"), Platform("windows-x86_64", "win"), From 94890e2924171b47b0f48d991f2b8f9831a3852e Mon Sep 17 00:00:00 2001 From: Glavo Date: Tue, 13 Feb 2024 00:39:39 +0800 Subject: [PATCH 09/33] Update javafx.gradle.kts (#2787) --- javafx.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javafx.gradle.kts b/javafx.gradle.kts index 8fccd05337..734639eeb3 100644 --- a/javafx.gradle.kts +++ b/javafx.gradle.kts @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath("com.google.code.gson:gson:2.8.1") + classpath("com.google.code.gson:gson:2.10.1") } } From 82fc1c067dc595f689f61c8ee992ade7aafcca73 Mon Sep 17 00:00:00 2001 From: Glavo Date: Tue, 13 Feb 2024 10:08:26 +0800 Subject: [PATCH 10/33] =?UTF-8?q?Fix=20#2153:=20=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E7=A6=BB=E7=BA=BF=E8=B4=A6=E6=88=B7=E5=A4=B4?= =?UTF-8?q?=E5=83=8F=20(#2788)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update * update * update * update * fix checkstyle * update --- .../jackhuang/hmcl/game/TexturesLoader.java | 100 ++++++++++++------ 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java index 7c472cc820..3e40976cb1 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/TexturesLoader.java @@ -17,7 +17,10 @@ */ package org.jackhuang.hmcl.game; +import javafx.beans.InvalidationListener; +import javafx.beans.WeakInvalidationListener; import javafx.beans.binding.ObjectBinding; +import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.scene.canvas.Canvas; @@ -29,9 +32,13 @@ import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.ServerResponseMalformedException; import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount; +import org.jackhuang.hmcl.auth.offline.OfflineAccount; +import org.jackhuang.hmcl.auth.offline.Skin; import org.jackhuang.hmcl.auth.yggdrasil.*; import org.jackhuang.hmcl.task.FileDownloadTask; -import org.jackhuang.hmcl.util.ResourceNotFoundError; +import org.jackhuang.hmcl.task.Schedulers; +import org.jackhuang.hmcl.ui.FXUtils; +import org.jackhuang.hmcl.util.Holder; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.javafx.BindingMapping; @@ -141,16 +148,7 @@ public static LoadedTexture loadTexture(Texture texture) throws Throwable { } private static void loadDefaultSkin(String path, TextureModel model) { - Image skin; - try { - skin = new Image(path); - if (skin.isError()) - throw skin.getException(); - } catch (Throwable e) { - throw new ResourceNotFoundError("Cannot load default skin from " + path, e); - } - - DEFAULT_SKINS.put(model, new LoadedTexture(skin, singletonMap("model", model.modelName))); + DEFAULT_SKINS.put(model, new LoadedTexture(FXUtils.newBuiltinImage(path), singletonMap("model", model.modelName))); } public static LoadedTexture getDefaultSkin(TextureModel model) { @@ -188,27 +186,61 @@ public static ObjectBinding skinBinding(YggdrasilService service, }, uuidFallback); } - public static ObjectBinding skinBinding(Account account) { + public static ObservableValue skinBinding(Account account) { LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(account.getUUID())); - return BindingMapping.of(account.getTextures()) - .map(textures -> textures - .flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN))) - .filter(it -> StringUtils.isNotBlank(it.getUrl()))) - .asyncMap(it -> { - if (it.isPresent()) { - Texture texture = it.get(); - return CompletableFuture.supplyAsync(() -> { - try { - return loadTexture(texture); - } catch (Throwable e) { - LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e); - return uuidFallback; + if (account instanceof OfflineAccount) { + OfflineAccount offlineAccount = (OfflineAccount) account; + + SimpleObjectProperty binding = new SimpleObjectProperty<>(); + InvalidationListener listener = o -> { + Skin skin = offlineAccount.getSkin(); + String username = offlineAccount.getUsername(); + + binding.set(uuidFallback); + if (skin != null) { + skin.load(username).setExecutor(POOL).whenComplete(Schedulers.javafx(), (result, exception) -> { + if (exception != null) { + LOG.log(Level.WARNING, "Failed to load texture", exception); + } else if (result != null && result.getSkin() != null && result.getSkin().getImage() != null) { + Map metadata; + if (result.getModel() != null) { + metadata = singletonMap("model", result.getModel().modelName); + } else { + metadata = emptyMap(); } - }, POOL); - } else { + + binding.set(new LoadedTexture(result.getSkin().getImage(), metadata)); + } + }).start(); + } + }; + + listener.invalidated(offlineAccount); + + binding.addListener(new Holder<>(listener)); + offlineAccount.addListener(new WeakInvalidationListener(listener)); + + return binding; + } else { + return BindingMapping.of(account.getTextures()) + .asyncMap(textures -> { + if (textures.isPresent()) { + Texture texture = textures.get().get(TextureType.SKIN); + if (texture != null && StringUtils.isNotBlank(texture.getUrl())) { + return CompletableFuture.supplyAsync(() -> { + try { + return loadTexture(texture); + } catch (Throwable e) { + LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e); + return uuidFallback; + } + }, POOL); + } + } + return CompletableFuture.completedFuture(uuidFallback); - } - }, uuidFallback); + }, uuidFallback); + } } // ==== @@ -277,9 +309,9 @@ private static final class SkinBindingChangeListener implements ChangeListener hole = new WeakHashMap<>(); final WeakReference canvasRef; - final ObjectBinding binding; + final ObservableValue binding; - SkinBindingChangeListener(Canvas canvas, ObjectBinding binding) { + SkinBindingChangeListener(Canvas canvas, ObservableValue binding) { this.canvasRef = new WeakReference<>(canvas); this.binding = binding; } @@ -293,14 +325,14 @@ public void changed(ObservableValue observable, } } - public static void fxAvatarBinding(Canvas canvas, ObjectBinding skinBinding) { + public static void fxAvatarBinding(Canvas canvas, ObservableValue skinBinding) { synchronized (SkinBindingChangeListener.hole) { SkinBindingChangeListener oldListener = SkinBindingChangeListener.hole.remove(canvas); if (oldListener != null) oldListener.binding.removeListener(oldListener); SkinBindingChangeListener listener = new SkinBindingChangeListener(canvas, skinBinding); - listener.changed(skinBinding, null, skinBinding.get()); + listener.changed(skinBinding, null, skinBinding.getValue()); skinBinding.addListener(listener); SkinBindingChangeListener.hole.put(canvas, listener); @@ -312,7 +344,7 @@ public static void bindAvatar(Canvas canvas, YggdrasilService service, UUID uuid } public static void bindAvatar(Canvas canvas, Account account) { - if (account instanceof YggdrasilAccount || account instanceof MicrosoftAccount) + if (account instanceof YggdrasilAccount || account instanceof MicrosoftAccount || account instanceof OfflineAccount) fxAvatarBinding(canvas, skinBinding(account)); else { unbindAvatar(canvas); From fe608e32f1b751de3cb6f47e28748844c070a146 Mon Sep 17 00:00:00 2001 From: Glavo Date: Tue, 13 Feb 2024 10:26:41 +0800 Subject: [PATCH 11/33] =?UTF-8?q?Fix=20#2783:=20Alex=20=E7=9A=AE=E8=82=A4?= =?UTF-8?q?=E5=BA=94=E4=B8=BA=20Slim=20=E6=A8=A1=E5=9E=8B=20(#2789)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java index 443e2296c4..fcd570eea5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/offline/Skin.java @@ -155,7 +155,8 @@ public Task load(String username) { if (defaultSkinLoader == null) { return Task.supplyAsync(() -> null); } - return Task.supplyAsync(() -> new LoadedSkin(TextureModel.STEVE, Texture.loadTexture(defaultSkinLoader.apply(type)), null)); + TextureModel model = type == Type.ALEX ? TextureModel.ALEX : TextureModel.STEVE; + return Task.supplyAsync(() -> new LoadedSkin(model, Texture.loadTexture(defaultSkinLoader.apply(type)), null)); case LOCAL_FILE: return Task.supplyAsync(() -> { Texture skin = null, cape = null; From 95afaa51122907835e1790eb265b184a79f895c7 Mon Sep 17 00:00:00 2001 From: Glavo Date: Tue, 13 Feb 2024 13:46:01 +0800 Subject: [PATCH 12/33] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B8=B8=E6=88=8F?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E6=AF=94=E8=BE=83=E8=A7=84=E5=88=99=20(#2700?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create GameVersionNumber * Update GameVersionNumber * Rename DefaultVersionNumber * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update * update versions.txt * update * update * update --- .../jackhuang/hmcl/game/HMCLGameLauncher.java | 4 +- .../jackhuang/hmcl/game/LauncherHelper.java | 7 +- .../hmcl/setting/VersionSetting.java | 6 +- .../org/jackhuang/hmcl/ui/InstallerItem.java | 4 +- .../org/jackhuang/hmcl/ui/main/RootPage.java | 2 +- .../hmcl/ui/versions/DownloadListPage.java | 26 +- .../hmcl/ui/versions/DownloadPage.java | 4 +- .../hmcl/ui/versions/VersionSettingsPage.java | 6 +- .../hmcl/ui/versions/WorldListItem.java | 5 +- .../jackhuang/hmcl/upgrade/UpdateChecker.java | 9 +- .../jackhuang/hmcl/util/NativePatcher.java | 8 +- .../hmcl/download/forge/ForgeInstallTask.java | 4 +- .../game/GameVerificationFixTask.java | 4 +- .../optifine/OptiFineInstallTask.java | 2 +- .../org/jackhuang/hmcl/game/GameVersion.java | 2 +- .../hmcl/game/JavaVersionConstraint.java | 96 +-- .../hmcl/launch/DefaultLauncher.java | 10 +- .../hmcl/mod/RemoteModRepository.java | 24 - .../util/versioning/GameVersionNumber.java | 616 +++++++++++++++ .../hmcl/util/versioning/VersionNumber.java | 20 +- .../hmcl/util/versioning/VersionRange.java | 77 +- .../main/resources/assets/game/versions.txt | 738 ++++++++++++++++++ .../hmcl/game/JavaVersionConstraintTest.java | 6 +- .../versioning/GameVersionNumberTest.java | 218 ++++++ .../util/versioning/VersionRangeTest.java | 189 ++--- 25 files changed, 1813 insertions(+), 274 deletions(-) create mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java create mode 100644 HMCLCore/src/main/resources/assets/game/versions.txt create mode 100644 HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/GameVersionNumberTest.java diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java index 26f2884369..f37ffe862b 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameLauncher.java @@ -25,7 +25,7 @@ import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.FileUtils; import org.jackhuang.hmcl.util.platform.ManagedProcess; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.File; import java.io.IOException; @@ -84,7 +84,7 @@ private void generateOptionsTxt() { 1.11 ~ 12:zh_cn 时正常,zh_CN 时虽然显示了中文但语言设置会错误地显示选择英文 1.13+ :zh_cn 时正常,zh_CN 时自动切换为英文 */ - VersionNumber gameVersion = VersionNumber.asVersion(repository.getGameVersion(version).orElse("0.0")); + GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(repository.getGameVersion(version)); if (gameVersion.compareTo("1.1") < 0) { lang = null; } else if (gameVersion.compareTo("1.11") < 0) { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java index 24e46a21ec..a00b8ee9a6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java @@ -45,6 +45,7 @@ import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.ResponseCodeException; import org.jackhuang.hmcl.util.platform.*; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; @@ -332,7 +333,7 @@ public void onStop(boolean success, TaskExecutor executor) { } private static Task checkGameState(Profile profile, VersionSetting setting, Version version) { - VersionNumber gameVersion = VersionNumber.asVersion(profile.getRepository().getGameVersion(version).orElse("Unknown")); + GameVersionNumber gameVersion = GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(version)); if (setting.isNotCheckJVM()) { return Task.composeAsync(() -> setting.getJavaVersion(gameVersion, version)) @@ -549,14 +550,14 @@ private static Task checkGameState(Profile profile, VersionSetting // Forge 2760~2773 will crash game with LiteLoader. boolean hasForge2760 = forgeVersion != null && (forgeVersion.compareTo("1.12.2-14.23.5.2760") >= 0) && (forgeVersion.compareTo("1.12.2-14.23.5.2773") < 0); boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader")); - if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) { + if (hasForge2760 && hasLiteLoader && gameVersion.compareTo("1.12.2") == 0) { suggestions.add(i18n("launch.advice.forge2760_liteloader")); } // OptiFine 1.14.4 is not compatible with Forge 28.2.2 and later versions. boolean hasForge28_2_2 = forgeVersion != null && (forgeVersion.compareTo("1.14.4-28.2.2") >= 0); boolean hasOptiFine = version.getLibraries().stream().anyMatch(it -> it.is("optifine", "OptiFine")); - if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo(VersionNumber.asVersion("1.14.4")) == 0) { + if (hasForge28_2_2 && hasOptiFine && gameVersion.compareTo("1.14.4") == 0) { suggestions.add(i18n("launch.advice.forge28_2_2_optifine")); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java index 9b9b752bf0..0d4feb2f8d 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/VersionSetting.java @@ -31,7 +31,7 @@ import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.Platform; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.IOException; import java.lang.reflect.Type; @@ -644,11 +644,11 @@ public void setLauncherVisibility(LauncherVisibility launcherVisibility) { launcherVisibilityProperty.set(launcherVisibility); } - public Task getJavaVersion(VersionNumber gameVersion, Version version) { + public Task getJavaVersion(GameVersionNumber gameVersion, Version version) { return getJavaVersion(gameVersion, version, true); } - public Task getJavaVersion(VersionNumber gameVersion, Version version, boolean checkJava) { + public Task getJavaVersion(GameVersionNumber gameVersion, Version version, boolean checkJava) { return Task.runAsync(Schedulers.javafx(), () -> { if (StringUtils.isBlank(getJava())) { setJava(StringUtils.isBlank(getJavaDir()) ? "Auto" : "Custom"); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java index 9c3da9be6f..4fe9995aa8 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/InstallerItem.java @@ -39,7 +39,7 @@ import org.jackhuang.hmcl.setting.VersionIconType; import org.jackhuang.hmcl.ui.construct.RipplerContainer; import org.jackhuang.hmcl.util.i18n.I18n; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.util.HashMap; import java.util.HashSet; @@ -204,7 +204,7 @@ public InstallerItemGroup(String gameVersion) { if (gameVersion == null) { this.libraries = new InstallerItem[]{game, forge, neoForge, liteLoader, optiFine, fabric, fabricApi, quilt, quiltApi}; - } else if (VersionNumber.compare(gameVersion, "1.13") < 0) { + } else if (GameVersionNumber.compare(gameVersion, "1.13") < 0) { this.libraries = new InstallerItem[]{game, forge, liteLoader, optiFine}; } else { this.libraries = new InstallerItem[]{game, forge, neoForge, optiFine, fabric, fabricApi, quilt, quiltApi}; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index 36d5af536f..b5cee07e54 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -120,7 +120,7 @@ public MainPage getMainPage() { .sorted(Comparator .comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime()) - .thenComparing(a -> VersionNumber.asVersion(a.getId()))) + .thenComparing(version -> VersionNumber.asVersion(repository.getGameVersion(version).orElse(version.getId())))) .collect(Collectors.toList()); runInFX(() -> { if (profile == Profiles.getSelectedProfile()) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java index 17ad04b11c..0f300cae19 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadListPage.java @@ -37,7 +37,6 @@ import javafx.scene.control.SkinBase; import javafx.scene.image.ImageView; import javafx.scene.layout.*; -import org.jackhuang.hmcl.game.GameVersion; import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.mod.RemoteModRepository; @@ -57,8 +56,8 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.javafx.BindingMapping; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; -import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -163,24 +162,23 @@ public void search(String userGameVersion, RemoteModRepository.Category category retrySearch = null; setLoading(true); setFailed(false); - File versionJar = StringUtils.isNotBlank(version.get().getVersion()) - ? version.get().getProfile().getRepository().getVersionJar(version.get().getVersion()) - : null; + if (executor != null && !executor.isCancelled()) { executor.cancel(); } executor = Task.supplyAsync(() -> { - String gameVersion; - if (StringUtils.isBlank(version.get().getVersion())) { - gameVersion = userGameVersion; + Profile.ProfileVersion version = this.version.get(); + if (StringUtils.isBlank(version.getVersion())) { + return userGameVersion; } else { - gameVersion = GameVersion.minecraftVersion(versionJar).orElse(""); + return StringUtils.isNotBlank(version.getVersion()) + ? version.getProfile().getRepository().getGameVersion(version.getVersion()).orElse("") + : ""; } - return gameVersion; - }).thenApplyAsync(gameVersion -> { - return repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC); - }).whenComplete(Schedulers.javafx(), (result, exception) -> { + }).thenApplyAsync(gameVersion -> + repository.search(gameVersion, category, pageOffset, 50, searchFilter, sort, RemoteModRepository.SortOrder.DESC) + ).whenComplete(Schedulers.javafx(), (result, exception) -> { setLoading(false); if (exception == null) { items.setAll(result.getResults().collect(Collectors.toList())); @@ -295,7 +293,7 @@ protected ModDownloadListPageSkin(DownloadListPage control) { JFXComboBox gameVersionField = new JFXComboBox<>(); gameVersionField.setMaxWidth(Double.MAX_VALUE); gameVersionField.setEditable(true); - gameVersionField.getItems().setAll(RemoteModRepository.DEFAULT_GAME_VERSIONS); + gameVersionField.getItems().setAll(GameVersionNumber.getDefaultGameVersions()); Label lblGameVersion = new Label(i18n("world.game_version")); searchPane.addRow(rowIndex++, new Label(i18n("mods.name")), nameField, lblGameVersion, gameVersionField); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index df728ff131..5ccc6b6665 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -51,7 +51,7 @@ import org.jackhuang.hmcl.util.i18n.I18n; import org.jackhuang.hmcl.util.io.NetworkUtils; import org.jackhuang.hmcl.util.javafx.BindingMapping; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -309,7 +309,7 @@ protected ModDownloadPageSkin(DownloadPage control) { } for (String gameVersion : control.versions.keys().stream() - .sorted(Collections.reverseOrder(VersionNumber::compare)) + .sorted(Collections.reverseOrder(GameVersionNumber::compare)) .collect(Collectors.toList())) { ComponentList sublist = new ComponentList(() -> control.versions.get(gameVersion).stream() diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java index 45b8357430..15c451a4fd 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java @@ -46,7 +46,7 @@ import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.File; import java.nio.file.Path; @@ -611,10 +611,10 @@ private void initJavaSubtitle() { Task.composeAsync(Schedulers.javafx(), () -> { if (versionId == null) { - return versionSetting.getJavaVersion(VersionNumber.asVersion("Unknown"), null); + return versionSetting.getJavaVersion(GameVersionNumber.unknown(), null); } else { return versionSetting.getJavaVersion( - VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(versionId)).orElse("Unknown")), + GameVersionNumber.asGameVersion(profile.getRepository().getGameVersion(versionId)), profile.getRepository().getVersion(versionId)); } }).thenAcceptAsync(Schedulers.javafx(), javaVersion -> javaSublist.setSubtitle(Optional.ofNullable(javaVersion) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java index 2bc3119aef..5f98c22554 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListItem.java @@ -29,7 +29,7 @@ import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.ui.FXUtils; import org.jackhuang.hmcl.ui.wizard.SinglePageWizardProvider; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.File; import java.time.Instant; @@ -94,8 +94,7 @@ public void reveal() { public void manageDatapacks() { if (world.getGameVersion() == null || // old game will not write game version to level.dat - (VersionNumber.isIntVersionNumber(world.getGameVersion()) // we don't parse snapshot version - && VersionNumber.asVersion(world.getGameVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) { + GameVersionNumber.compare(world.getGameVersion(), "1.13") < 0) { Controllers.dialog(i18n("world.datapack.1_13")); return; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java index f91b4cc793..10569497b6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/UpdateChecker.java @@ -32,13 +32,12 @@ import static org.jackhuang.hmcl.util.Lang.thread; import static org.jackhuang.hmcl.util.Logging.LOG; import static org.jackhuang.hmcl.util.Pair.pair; -import static org.jackhuang.hmcl.util.versioning.VersionNumber.asVersion; public final class UpdateChecker { private UpdateChecker() {} - private static ObjectProperty latestVersion = new SimpleObjectProperty<>(); - private static BooleanBinding outdated = Bindings.createBooleanBinding( + private static final ObjectProperty latestVersion = new SimpleObjectProperty<>(); + private static final BooleanBinding outdated = Bindings.createBooleanBinding( () -> { RemoteVersion latest = latestVersion.get(); if (latest == null || isDevelopmentVersion(Metadata.VERSION)) { @@ -46,11 +45,11 @@ private UpdateChecker() {} } else { // We can update from development version to stable version, // which can be downgrading. - return asVersion(latest.getVersion()).compareTo(asVersion(Metadata.VERSION)) != 0; + return !latest.getVersion().equals(Metadata.VERSION); } }, latestVersion); - private static ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false); + private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false); public static void init() { requestCheckUpdate(UpdateChannel.getChannel()); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java b/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java index 073f9afd93..463fddd201 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/util/NativePatcher.java @@ -8,7 +8,7 @@ import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; import org.jackhuang.hmcl.util.platform.Platform; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.IOException; import java.io.InputStreamReader; @@ -45,7 +45,7 @@ private static Map getNatives(Platform platform) { public static Version patchNative(Version version, String gameVersion, JavaVersion javaVersion, VersionSetting settings) { if (settings.getNativesDirType() == NativesDirectoryType.CUSTOM) { - if (gameVersion != null && VersionNumber.compare(gameVersion, "1.19") < 0) + if (gameVersion != null && GameVersionNumber.compare(gameVersion, "1.19") < 0) return version; ArrayList newLibraries = new ArrayList<>(); @@ -66,7 +66,7 @@ public static Version patchNative(Version version, String gameVersion, JavaVersi final boolean useNativeOpenAL = settings.isUseNativeOpenAL(); if (OperatingSystem.CURRENT_OS.isLinuxOrBSD() && (useNativeGLFW || useNativeOpenAL) - && VersionNumber.compare(gameVersion, "1.19") >= 0) { + && GameVersionNumber.compare(gameVersion, "1.19") >= 0) { version = version.setLibraries(version.getLibraries().stream() .filter(library -> { @@ -88,7 +88,7 @@ public static Version patchNative(Version version, String gameVersion, JavaVersi OperatingSystem os = javaVersion.getPlatform().getOperatingSystem(); Architecture arch = javaVersion.getArchitecture(); - VersionNumber gameVersionNumber = gameVersion != null ? VersionNumber.asVersion(gameVersion) : null; + GameVersionNumber gameVersionNumber = gameVersion != null ? GameVersionNumber.asGameVersion(gameVersion) : null; if (settings.isNotPatchNatives()) return version; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java index f2ab6befca..35a60f422a 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeInstallTask.java @@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.io.FileUtils; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.IOException; import java.nio.file.FileSystem; @@ -100,7 +100,7 @@ public Collection> getDependencies() { @Override public void execute() throws IOException, VersionMismatchException, UnsupportedInstallationException { String originalMainClass = version.resolve(dependencyManager.getGameRepository()).getMainClass(); - if (VersionNumber.compare("1.13", remote.getGameVersion()) <= 0) { + if (GameVersionNumber.compare("1.13", remote.getGameVersion()) <= 0) { // Forge 1.13 is not compatible with fabric. if (!LibraryAnalyzer.VANILLA_MAIN.equals(originalMainClass) && !LibraryAnalyzer.MOD_LAUNCHER_MAIN.equals(originalMainClass) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java index e57b0fa843..22229f2482 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameVerificationFixTask.java @@ -22,7 +22,7 @@ import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.io.CompressingUtils; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.File; import java.io.IOException; @@ -65,7 +65,7 @@ public void execute() throws IOException { File jar = dependencyManager.getGameRepository().getVersionJar(version); LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version); - if (jar.exists() && VersionNumber.compare(gameVersion, "1.6") < 0 && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) { + if (jar.exists() && GameVersionNumber.compare(gameVersion, "1.6") < 0 && analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) { try (FileSystem fs = CompressingUtils.createWritableZipFileSystem(jar.toPath(), StandardCharsets.UTF_8)) { Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.DSA")); Files.deleteIfExists(fs.getPath("META-INF/MOJANG_C.SF")); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java index 20c20830b6..03b7832f11 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/optifine/OptiFineInstallTask.java @@ -190,7 +190,7 @@ public void execute() throws Exception { if (LibraryAnalyzer.BOOTSTRAP_LAUNCHER_MAIN.equals(originalMainClass)) { // OptiFine H1 Pre2+ is compatible with Forge 1.17 - if (buildofVer.compareTo(VersionNumber.asVersion("20210924-190833")) < 0) { + if (buildofVer.compareTo("20210924-190833") < 0) { throw new UnsupportedInstallationException(UnsupportedInstallationException.FORGE_1_17_OPTIFINE_H1_PRE2); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java index 0a7787a53a..68a0f3a704 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/GameVersion.java @@ -42,7 +42,7 @@ /** * @author huangyuhui */ -public final class GameVersion { +final class GameVersion { private GameVersion() { } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java index c8f5a37993..754b99312e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java @@ -22,6 +22,7 @@ import org.jackhuang.hmcl.util.platform.Architecture; import org.jackhuang.hmcl.util.platform.JavaVersion; import org.jackhuang.hmcl.util.platform.OperatingSystem; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jackhuang.hmcl.util.versioning.VersionRange; import org.jetbrains.annotations.Nullable; @@ -30,48 +31,47 @@ import java.util.Objects; import static org.jackhuang.hmcl.download.LibraryAnalyzer.LAUNCH_WRAPPER_MAIN; -import static org.jackhuang.hmcl.util.versioning.VersionRange.*; public enum JavaVersionConstraint { // Minecraft>=1.13 requires Java 8 - VANILLA_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, atLeast("1.13"), atLeast("1.8")), + VANILLA_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8")), // Minecraft 1.17 requires Java 16 - VANILLA_JAVA_16(JavaVersionConstraint.RULE_MANDATORY, atLeast("1.17"), atLeast("16")), + VANILLA_JAVA_16(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.17"), VersionNumber.atLeast("16")), // Minecraft>=1.18 requires Java 17 - VANILLA_JAVA_17(JavaVersionConstraint.RULE_MANDATORY, atLeast("1.18"), atLeast("17")), + VANILLA_JAVA_17(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atLeast("1.18"), VersionNumber.atLeast("17")), // Minecraft<=1.7.2+Forge requires Java<=7, But LegacyModFixer may fix that problem. So only suggest user using Java 7. - MODDED_JAVA_7(JavaVersionConstraint.RULE_SUGGESTED, atMost("1.7.2"), atMost("1.7.999")) { + MODDED_JAVA_7(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atMost("1.7.2"), VersionNumber.atMost("1.7.999")) { @Override - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { return version != null && analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); } }, - MODDED_JAVA_8(JavaVersionConstraint.RULE_SUGGESTED, between("1.7.10", "1.16.999"), between("1.8", "1.8.999")) { + MODDED_JAVA_8(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.7.10", "1.16.999"), VersionNumber.between("1.8", "1.8.999")) { @Override - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); } }, - MODDED_JAVA_16(JavaVersionConstraint.RULE_SUGGESTED, between("1.17", "1.17.999"), between("16", "16.999")) { + MODDED_JAVA_16(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.17", "1.17.999"), VersionNumber.between("16", "16.999")) { @Override - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); } }, - MODDED_JAVA_17(JavaVersionConstraint.RULE_SUGGESTED, atLeast("1.18"), between("17", "17.999")) { + MODDED_JAVA_17(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atLeast("1.18"), VersionNumber.between("17", "17.999")) { @Override - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { return analyzer != null && analyzer.has(LibraryAnalyzer.LibraryType.FORGE); } }, // LaunchWrapper<=1.12 will crash because LaunchWrapper assumes the system class loader is an instance of URLClassLoader (Java 8) - LAUNCH_WRAPPER(JavaVersionConstraint.RULE_MANDATORY, atMost("1.12.999"), atMost("1.8.999")) { + LAUNCH_WRAPPER(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) { @Override - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { if (version == null) return false; return LAUNCH_WRAPPER_MAIN.equals(version.getMainClass()) && @@ -81,15 +81,15 @@ protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullabl } }, // Minecraft>=1.13 may crash when generating world on Java [1.8,1.8.0_51) - VANILLA_JAVA_8_51(JavaVersionConstraint.RULE_SUGGESTED, atLeast("1.13"), atLeast("1.8.0_51")), + VANILLA_JAVA_8_51(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.atLeast("1.13"), VersionNumber.atLeast("1.8.0_51")), // Minecraft with suggested java version recorded in game json is restrictedly constrained. GAME_JSON(JavaVersionConstraint.RULE_MANDATORY, VersionRange.all(), VersionRange.all()) { @Override - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { if (version == null) return false; // We only checks for 1.7.10 and above, since 1.7.2 with Forge can only run on Java 7, but it is recorded Java 8 in game json, which is not correct. - return gameVersionNumber.compareTo(VersionNumber.asVersion("1.7.10")) >= 0 && version.getJavaVersion() != null; + return gameVersionNumber.compareTo("1.7.10") >= 0 && version.getJavaVersion() != null; } @Override @@ -100,14 +100,14 @@ public VersionRange getJavaVersionRange(Version version) { } else { javaVersion = "1." + version.getJavaVersion().getMajorVersion(); } - return atLeast(javaVersion); + return VersionNumber.atLeast(javaVersion); } }, // On Linux, JDK 9+ cannot launch Minecraft<=1.12.2, since JDK 9+ does not accept loading native library built in different arch. // For example, JDK 9+ 64-bit cannot load 32-bit lwjgl native library. - VANILLA_LINUX_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, atMost("1.12.999"), atMost("1.8.999")) { + VANILLA_LINUX_JAVA_8(JavaVersionConstraint.RULE_MANDATORY, GameVersionNumber.atMost("1.12.999"), VersionNumber.atMost("1.8.999")) { @Override - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { return OperatingSystem.CURRENT_OS == OperatingSystem.LINUX && Architecture.SYSTEM_ARCH == Architecture.X86_64 @@ -115,33 +115,33 @@ protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullabl } @Override - public boolean checkJava(VersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { + public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { return javaVersion.getArchitecture() != Architecture.X86_64 || super.checkJava(gameVersionNumber, version, javaVersion); } }, // Minecraft currently does not provide official support for architectures other than x86 and x86-64. VANILLA_X86(JavaVersionConstraint.RULE_SUGGESTED, VersionRange.all(), VersionRange.all()) { @Override - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { if (javaVersion == null || javaVersion.getArchitecture() != Architecture.ARM64) return false; if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) - return gameVersionNumber.compareTo(VersionNumber.asVersion("1.6")) < 0; + return gameVersionNumber.compareTo("1.6") < 0; return false; } @Override - public boolean checkJava(VersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { + public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { return javaVersion.getArchitecture().isX86(); } }, // Minecraft 1.16+Forge with crash because JDK-8273826 - MODLAUNCHER_8(JavaVersionConstraint.RULE_SUGGESTED, between("1.16.3", "1.17.1"), VersionRange.all()) { + MODLAUNCHER_8(JavaVersionConstraint.RULE_SUGGESTED, GameVersionNumber.between("1.16.3", "1.17.1"), VersionRange.all()) { @Override - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { if (version == null || javaVersion == null || analyzer == null) return false; VersionNumber forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE) @@ -158,14 +158,14 @@ protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullabl case "1.16.5": return forgePatchVersion.compareTo(VersionNumber.asVersion("36.2.23")) <= 0; case "1.17.1": - return between("37.0.60", "37.0.75").contains(forgePatchVersion); + return VersionNumber.between("37.0.60", "37.0.75").contains(forgePatchVersion); default: return false; } } @Override - public boolean checkJava(VersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { + public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { int parsedJavaVersion = javaVersion.getParsedVersion(); if (parsedJavaVersion > 17) { return false; @@ -181,13 +181,13 @@ public boolean checkJava(VersionNumber gameVersionNumber, Version version, JavaV return true; } } - };; + }; private final int type; - private final VersionRange gameVersionRange; - private final VersionRange javaVersionRange; + private final VersionRange gameVersionRange; + private final VersionRange javaVersionRange; - JavaVersionConstraint(int type, VersionRange gameVersionRange, VersionRange javaVersionRange) { + JavaVersionConstraint(int type, VersionRange gameVersionRange, VersionRange javaVersionRange) { this.type = type; this.gameVersionRange = gameVersionRange; this.javaVersionRange = javaVersionRange; @@ -197,39 +197,39 @@ public int getType() { return type; } - public VersionRange getGameVersionRange() { + public VersionRange getGameVersionRange() { return gameVersionRange; } - public VersionRange getJavaVersionRange(Version version) { + public VersionRange getJavaVersionRange(Version version) { return javaVersionRange; } - public final boolean appliesToVersion(@Nullable VersionNumber gameVersionNumber, @Nullable Version version, + public final boolean appliesToVersion(@Nullable GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, LibraryAnalyzer analyzer) { return gameVersionRange.contains(gameVersionNumber) && appliesToVersionImpl(gameVersionNumber, version, javaVersion, analyzer); } - protected boolean appliesToVersionImpl(VersionNumber gameVersionNumber, @Nullable Version version, + protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nullable Version version, @Nullable JavaVersion javaVersion, @Nullable LibraryAnalyzer analyzer) { return true; } @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean checkJava(VersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { + public boolean checkJava(GameVersionNumber gameVersionNumber, Version version, JavaVersion javaVersion) { return getJavaVersionRange(version).contains(javaVersion.getVersionNumber()); } public static final List ALL = Lang.immutableListOf(values()); - public static VersionRanges findSuitableJavaVersionRange(VersionNumber gameVersion, Version version) { - VersionRange mandatoryJavaRange = VersionRange.all(); - VersionRange suggestedJavaRange = VersionRange.all(); + public static VersionRanges findSuitableJavaVersionRange(GameVersionNumber gameVersion, Version version) { + VersionRange mandatoryJavaRange = VersionRange.all(); + VersionRange suggestedJavaRange = VersionRange.all(); LibraryAnalyzer analyzer = version != null ? LibraryAnalyzer.analyze(version) : null; for (JavaVersionConstraint java : ALL) { if (java.appliesToVersion(gameVersion, version, null, analyzer)) { - VersionRange javaVersionRange = java.getJavaVersionRange(version); + VersionRange javaVersionRange = java.getJavaVersionRange(version); if (java.type == RULE_MANDATORY) { mandatoryJavaRange = mandatoryJavaRange.intersectionWith(javaVersionRange); suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange); @@ -242,12 +242,12 @@ public static VersionRanges findSuitableJavaVersionRange(VersionNumber gameVersi } @Nullable - public static JavaVersion findSuitableJavaVersion(VersionNumber gameVersion, Version version) throws InterruptedException { + public static JavaVersion findSuitableJavaVersion(GameVersionNumber gameVersion, Version version) throws InterruptedException { VersionRanges range = findSuitableJavaVersionRange(gameVersion, version); boolean forceX86 = Architecture.SYSTEM_ARCH == Architecture.ARM64 && (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) - && gameVersion.compareTo(VersionNumber.asVersion("1.6")) < 0; + && gameVersion.compareTo("1.6") < 0; JavaVersion mandatory = null; JavaVersion suggested = null; @@ -298,19 +298,19 @@ private static int compareJavaVersion(JavaVersion javaVersion1, JavaVersion java public static final int RULE_SUGGESTED = 2; public static final class VersionRanges { - private final VersionRange mandatory; - private final VersionRange suggested; + private final VersionRange mandatory; + private final VersionRange suggested; - public VersionRanges(VersionRange mandatory, VersionRange suggested) { + public VersionRanges(VersionRange mandatory, VersionRange suggested) { this.mandatory = mandatory; this.suggested = suggested; } - public VersionRange getMandatory() { + public VersionRange getMandatory() { return mandatory; } - public VersionRange getSuggested() { + public VersionRange getSuggested() { return suggested; } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java index 39869b1001..0c692f3558 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java @@ -28,7 +28,7 @@ import org.jackhuang.hmcl.util.io.Unzipper; import org.jackhuang.hmcl.util.platform.Bits; import org.jackhuang.hmcl.util.platform.*; -import org.jackhuang.hmcl.util.versioning.VersionNumber; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import java.io.*; import java.net.InetSocketAddress; @@ -230,7 +230,7 @@ private Command generateCommandLine(File nativeFolder) throws IOException { Path tempNativeFolder = null; if ((OperatingSystem.CURRENT_OS == OperatingSystem.LINUX || OperatingSystem.CURRENT_OS == OperatingSystem.OSX) && !StringUtils.isASCII(nativeFolderPath) - && gameVersion.isPresent() && VersionNumber.compare(gameVersion.get(), "1.19") < 0) { + && gameVersion.isPresent() && GameVersionNumber.compare(gameVersion.get(), "1.19") < 0) { tempNativeFolder = Paths.get("/", "tmp", "hmcl-natives-" + UUID.randomUUID()); nativeFolderPath = tempNativeFolder + File.pathSeparator + nativeFolderPath; } @@ -259,7 +259,7 @@ private Command generateCommandLine(File nativeFolder) throws IOException { if (StringUtils.isNotBlank(options.getServerIp())) { String[] args = options.getServerIp().split(":"); - if (VersionNumber.compare(gameVersion.orElse("0.0"), "1.20") < 0) { + if (GameVersionNumber.asGameVersion(gameVersion).compareTo("1.20") < 0) { res.add("--server"); res.add(args[0]); res.add("--port"); @@ -357,7 +357,7 @@ public void decompressNatives(File destination) throws NotDecompressingNativesEx } private boolean isUsingLog4j() { - return VersionNumber.compare(repository.getGameVersion(version).orElse("1.7"), "1.7") >= 0; + return GameVersionNumber.compare(repository.getGameVersion(version).orElse("1.7"), "1.7") >= 0; } public File getLog4jConfigurationFile() { @@ -367,7 +367,7 @@ public File getLog4jConfigurationFile() { public void extractLog4jConfigurationFile() throws IOException { File targetFile = getLog4jConfigurationFile(); InputStream source; - if (VersionNumber.compare(repository.getGameVersion(version).orElse("0.0"), "1.12") < 0) { + if (GameVersionNumber.asGameVersion(repository.getGameVersion(version)).compareTo("1.12") < 0) { source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.7.xml"); } else { source = DefaultLauncher.class.getResourceAsStream("/assets/game/log4j2-1.12.xml"); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java index a45f8618a8..3dc48fde39 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteModRepository.java @@ -119,28 +119,4 @@ public List getSubcategories() { return subcategories; } } - - String[] DEFAULT_GAME_VERSIONS = new String[]{ - "1.20.4", "1.20.3", "1.20.2", "1.20.1", "1.20", - "1.19.4", "1.19.3", "1.19.2", "1.19.1", "1.19", - "1.18.2", "1.18.1", "1.18", - "1.17.1", "1.17", - "1.16.5", "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.16", - "1.15.2", "1.15.1", "1.15", - "1.14.4", "1.14.3", "1.14.2", "1.14.1", "1.14", - "1.13.2", "1.13.1", "1.13", - "1.12.2", "1.12.1", "1.12", - "1.11.2", "1.11.1", "1.11", - "1.10.2", "1.10.1", "1.10", - "1.9.4", "1.9.3", "1.9.2", "1.9.1", "1.9", - "1.8.9", "1.8.8", "1.8.7", "1.8.6", "1.8.5", "1.8.4", "1.8.3", "1.8.2", "1.8.1", "1.8", - "1.7.10", "1.7.9", "1.7.8", "1.7.7", "1.7.6", "1.7.5", "1.7.4", "1.7.3", "1.7.2", - "1.6.4", "1.6.2", "1.6.1", - "1.5.2", "1.5.1", - "1.4.7", "1.4.6", "1.4.5", "1.4.4", "1.4.2", - "1.3.2", "1.3.1", - "1.2.5", "1.2.4", "1.2.3", "1.2.2", "1.2.1", - "1.1", - "1.0" - }; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java new file mode 100644 index 0000000000..65564db578 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/GameVersionNumber.java @@ -0,0 +1,616 @@ +package org.jackhuang.hmcl.util.versioning; + +import org.jetbrains.annotations.NotNull; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class GameVersionNumber implements Comparable { + + public static String[] getDefaultGameVersions() { + return Versions.DEFAULT_GAME_VERSIONS; + } + + public static GameVersionNumber asGameVersion(String version) { + try { + if (!version.isEmpty()) { + char ch = version.charAt(0); + switch (ch) { + case 'r': + return Old.parsePreClassic(version); + case 'a': + case 'b': + case 'c': + return Old.parseAlphaBetaClassic(version); + case 'i': + return Old.parseInfdev(version); + } + + if (version.equals("0.0")) { + return Release.ZERO; + } + + if (version.startsWith("1.")) { + return Release.parse(version); + } + + if (version.length() == 6 && version.charAt(2) == 'w') { + return Snapshot.parse(version); + } + } + } catch (IllegalArgumentException ignore) { + } + + Special special = Versions.SPECIALS.get(version); + if (special == null) { + special = new Special(version); + } + return special; + } + + public static GameVersionNumber asGameVersion(Optional version) { + return version.isPresent() ? asGameVersion(version.get()) : unknown(); + } + + public static GameVersionNumber unknown() { + return Release.ZERO; + } + + public static int compare(String version1, String version2) { + return asGameVersion(version1).compareTo(asGameVersion(version2)); + } + + public static VersionRange between(String minimum, String maximum) { + return VersionRange.between(asGameVersion(minimum), asGameVersion(maximum)); + } + + public static VersionRange atLeast(String minimum) { + return VersionRange.atLeast(asGameVersion(minimum)); + } + + public static VersionRange atMost(String maximum) { + return VersionRange.atMost(asGameVersion(maximum)); + } + + final String value; + + GameVersionNumber(String value) { + this.value = value; + } + + enum Type { + PRE_CLASSIC, CLASSIC, INFDEV, ALPHA, BETA, NEW + } + + abstract Type getType(); + + abstract int compareToImpl(@NotNull GameVersionNumber other); + + public int compareTo(@NotNull String other) { + return this.compareTo(asGameVersion(other)); + } + + @Override + public int compareTo(@NotNull GameVersionNumber other) { + if (this.getType() != other.getType()) { + return Integer.compare(this.getType().ordinal(), other.getType().ordinal()); + } + + return compareToImpl(other); + } + + @Override + public String toString() { + return value; + } + + static final class Old extends GameVersionNumber { + + private static final Pattern PATTERN = Pattern.compile("[abc](?[0-9]+)\\.(?[0-9]+)(\\.(?[0-9]+))?([^0-9]*(?[0-9]+).*)?"); + + static Old parsePreClassic(String value) { + int version; + try { + version = Integer.parseInt(value.substring("rd-".length())); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(e); + } + return new Old(value, Type.PRE_CLASSIC, version, 0, 0, 0); + } + + static Old parseAlphaBetaClassic(String value) { + Matcher matcher = PATTERN.matcher(value); + if (!matcher.matches()) { + throw new IllegalArgumentException(value); + } + + Type type; + switch (value.charAt(0)) { + case 'a': + type = Type.ALPHA; + break; + case 'b': + type = Type.BETA; + break; + case 'c': + type = Type.CLASSIC; + break; + default: + throw new AssertionError(value); + } + + int major = Integer.parseInt(matcher.group("major")); + int minor = Integer.parseInt(matcher.group("minor")); + + String patchString = matcher.group("patch"); + int patch = patchString != null ? Integer.parseInt(patchString) : 0; + + String additionalString = matcher.group("additional"); + int additional = additionalString != null ? Integer.parseInt(additionalString) : 0; + + return new Old(value, type, major, minor, patch, additional); + } + + static Old parseInfdev(String value) { + String version = value.substring("inf-".length()); + int major; + int patch; + + try { + major = Integer.parseInt(version); + patch = 0; + } catch (NumberFormatException e) { + int idx = version.indexOf('-'); + if (idx >= 0) { + try { + major = Integer.parseInt(version.substring(0, idx)); + patch = Integer.parseInt(version.substring(idx + 1)); + } catch (NumberFormatException ignore) { + throw new IllegalArgumentException(value); + } + } else { + throw new IllegalArgumentException(value); + } + } + + return new Old(value, Type.INFDEV, major, 0, patch, 0); + } + + + final Type type; + final int major; + final int minor; + final int patch; + final int additional; + + private Old(String value, Type type, int major, int minor, int patch, int additional) { + super(value); + this.type = type; + this.major = major; + this.minor = minor; + this.patch = patch; + this.additional = additional; + } + + @Override + Type getType() { + return type; + } + + @Override + int compareToImpl(@NotNull GameVersionNumber other) { + Old that = (Old) other; + int c = Integer.compare(this.major, that.major); + if (c != 0) { + return c; + } + + c = Integer.compare(this.minor, that.minor); + if (c != 0) { + return c; + } + + c = Integer.compare(this.patch, that.patch); + if (c != 0) { + return c; + } + + return Integer.compare(this.additional, that.additional); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Old other = (Old) o; + return major == other.major && minor == other.minor && patch == other.patch && additional == other.additional && type == other.type; + } + + @Override + public int hashCode() { + return Objects.hash(type, major, minor, patch, additional); + } + } + + static final class Release extends GameVersionNumber { + + private static final Pattern PATTERN = Pattern.compile("1\\.(?[0-9]+)(\\.(?[0-9]+))?((?(-[a-zA-Z]+| Pre-Release ))(?[0-9]+))?"); + + static final int TYPE_GA = Integer.MAX_VALUE; + + static final int TYPE_UNKNOWN = 0; + static final int TYPE_EXP = 1; + static final int TYPE_PRE = 2; + static final int TYPE_RC = 3; + + static final Release ZERO = new Release("0.0", 0, 0, 0, TYPE_GA, 0); + + static Release parse(String value) { + Matcher matcher = PATTERN.matcher(value); + if (!matcher.matches()) { + throw new IllegalArgumentException(value); + } + + int minor = Integer.parseInt(matcher.group("minor")); + + String patchString = matcher.group("patch"); + int patch = patchString != null ? Integer.parseInt(patchString) : 0; + + String eaTypeString = matcher.group("eaType"); + int eaType; + if (eaTypeString == null) { + eaType = TYPE_GA; + } else if ("-pre".equals(eaTypeString) || " Pre-Release ".equals(eaTypeString)) { + eaType = TYPE_PRE; + } else if ("-rc".equals(eaTypeString)) { + eaType = TYPE_RC; + } else if ("-exp".equals(eaTypeString)) { + eaType = TYPE_EXP; + } else { + eaType = TYPE_UNKNOWN; + } + + String eaVersionString = matcher.group("eaVersion"); + int eaVersion = eaVersionString == null ? 0 : Integer.parseInt(eaVersionString); + + return new Release(value, 1, minor, patch, eaType, eaVersion); + } + + private final int major; + private final int minor; + private final int patch; + + private final int eaType; + private final int eaVersion; + + Release(String value, int major, int minor, int patch, int eaType, int eaVersion) { + super(value); + this.major = major; + this.minor = minor; + this.patch = patch; + this.eaType = eaType; + this.eaVersion = eaVersion; + } + + @Override + Type getType() { + return Type.NEW; + } + + int compareToRelease(Release other) { + int c = Integer.compare(this.major, other.major); + if (c != 0) { + return c; + } + + c = Integer.compare(this.minor, other.minor); + if (c != 0) { + return c; + } + + c = Integer.compare(this.patch, other.patch); + if (c != 0) { + return c; + } + + c = Integer.compare(this.eaType, other.eaType); + if (c != 0) { + return c; + } + + return Integer.compare(this.eaVersion, other.eaVersion); + } + + int compareToSnapshot(Snapshot other) { + int idx = Arrays.binarySearch(Versions.SNAPSHOT_INTS, other.intValue); + if (idx >= 0) { + return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1; + } + + idx = -(idx + 1); + if (idx == Versions.SNAPSHOT_INTS.length) { + return -1; + } + + return this.compareToRelease(Versions.SNAPSHOT_PREV[idx]) <= 0 ? -1 : 1; + } + + @Override + int compareToImpl(@NotNull GameVersionNumber other) { + if (other instanceof Release) { + return compareToRelease((Release) other); + } + + if (other instanceof Snapshot) { + return compareToSnapshot((Snapshot) other); + } + + if (other instanceof Special) { + return -((Special) other).compareToReleaseOrSnapshot(this); + } + + throw new AssertionError(other.getClass()); + } + + @Override + public int hashCode() { + return Objects.hash(major, minor, patch, eaType, eaVersion); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Release other = (Release) o; + return major == other.major && minor == other.minor && patch == other.patch && eaType == other.eaType && eaVersion == other.eaVersion; + } + } + + static final class Snapshot extends GameVersionNumber { + static Snapshot parse(String value) { + if (value.length() != 6 || value.charAt(2) != 'w') { + throw new IllegalArgumentException(value); + } + + int year; + int week; + try { + year = Integer.parseInt(value.substring(0, 2)); + week = Integer.parseInt(value.substring(3, 5)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(value); + } + + char suffix = value.charAt(5); + if ((suffix < 'a' || suffix > 'z') && suffix != '~') { + throw new IllegalArgumentException(value); + } + + return new Snapshot(value, year, week, suffix); + } + + static int toInt(int year, int week, char suffix) { + return (year << 16) | (week << 8) | suffix; + } + + final int intValue; + + Snapshot(String value, int year, int week, char suffix) { + super(value); + this.intValue = toInt(year, week, suffix); + } + + @Override + Type getType() { + return Type.NEW; + } + + @Override + int compareToImpl(@NotNull GameVersionNumber other) { + if (other instanceof Release) { + return -((Release) other).compareToSnapshot(this); + } + + if (other instanceof Snapshot) { + return Integer.compare(this.intValue, ((Snapshot) other).intValue); + } + + if (other instanceof Special) { + return -((Special) other).compareToReleaseOrSnapshot(this); + } + + throw new AssertionError(other.getClass()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Snapshot other = (Snapshot) o; + return this.intValue == other.intValue; + } + + @Override + public int hashCode() { + return intValue; + } + } + + static final class Special extends GameVersionNumber { + private VersionNumber versionNumber; + + private GameVersionNumber prev; + + Special(String value) { + super(value); + } + + @Override + Type getType() { + return Type.NEW; + } + + boolean isUnknown() { + return prev == null; + } + + VersionNumber asVersionNumber() { + if (versionNumber != null) { + return versionNumber; + } + + return versionNumber = VersionNumber.asVersion(value); + } + + GameVersionNumber getPrevNormalVersion() { + GameVersionNumber v = prev; + while (v instanceof Special) { + v = ((Special) v).prev; + } + + if (v == null) { + throw new AssertionError("version: " + value); + } + + return v; + } + + int compareToReleaseOrSnapshot(GameVersionNumber other) { + if (isUnknown()) { + return 1; + } + + if (getPrevNormalVersion().compareTo(other) >= 0) { + return 1; + } + + return -1; + } + + int compareToSpecial(Special other) { + if (this.isUnknown()) { + return other.isUnknown() ? this.asVersionNumber().compareTo(other.asVersionNumber()) : 1; + } + + if (other.isUnknown()) { + return -1; + } + + if (this.value.equals(other.value)) { + return 0; + } + + int c = this.getPrevNormalVersion().compareTo(other.getPrevNormalVersion()); + if (c != 0) { + return c; + } + + GameVersionNumber v = prev; + while (v instanceof Special) { + if (v == other) { + return 1; + } + + v = ((Special) v).prev; + } + + return -1; + } + + @Override + int compareToImpl(@NotNull GameVersionNumber o) { + if (o instanceof Release) { + return compareToReleaseOrSnapshot(o); + } + + if (o instanceof Snapshot) { + return compareToReleaseOrSnapshot(o); + } + + if (o instanceof Special) { + return compareToSpecial((Special) o); + } + + throw new AssertionError(o.getClass()); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Special other = (Special) o; + return Objects.equals(this.value, other.value); + } + } + + static final class Versions { + static final HashMap SPECIALS = new HashMap<>(); + static final String[] DEFAULT_GAME_VERSIONS; + + static final int[] SNAPSHOT_INTS; + static final Release[] SNAPSHOT_PREV; + + static { + ArrayDeque defaultGameVersions = new ArrayDeque<>(64); + + List snapshots = new ArrayList<>(1024); + List snapshotPrev = new ArrayList<>(1024); + + // Convert it to dynamic resource after the website is repaired? + try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.US_ASCII))) { + Release currentRelease = null; + GameVersionNumber prev = null; + + for (String line; (line = reader.readLine()) != null; ) { + if (line.isEmpty()) + continue; + + GameVersionNumber version = GameVersionNumber.asGameVersion(line); + + if (currentRelease == null) + currentRelease = (Release) version; + + if (version instanceof Snapshot) { + Snapshot snapshot = (Snapshot) version; + snapshots.add(snapshot); + snapshotPrev.add(currentRelease); + } else if (version instanceof Release) { + currentRelease = (Release) version; + + if (currentRelease.eaType == Release.TYPE_GA) { + defaultGameVersions.addFirst(currentRelease.value); + } + } else if (version instanceof Special) { + Special special = (Special) version; + special.prev = prev; + SPECIALS.put(special.value, special); + } else + throw new AssertionError("version: " + version); + + prev = version; + } + } catch (IOException e) { + throw new AssertionError(e); + } + + DEFAULT_GAME_VERSIONS = defaultGameVersions.toArray(new String[0]); + + SNAPSHOT_INTS = new int[snapshots.size()]; + for (int i = 0; i < snapshots.size(); i++) { + SNAPSHOT_INTS[i] = snapshots.get(i).intValue; + } + + SNAPSHOT_PREV = snapshotPrev.toArray(new Release[SNAPSHOT_INTS.length]); + } + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java index 164602fbae..0344125500 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionNumber.java @@ -83,6 +83,18 @@ public static boolean isIntVersionNumber(String version) { return true; } + public static VersionRange between(String minimum, String maximum) { + return VersionRange.between(asVersion(minimum), asVersion(maximum)); + } + + public static VersionRange atLeast(String minimum) { + return VersionRange.atLeast(asVersion(minimum)); + } + + public static VersionRange atMost(String maximum) { + return VersionRange.atMost(asVersion(maximum)); + } + private interface Item { int LONG_ITEM = 0; int BIGINTEGER_ITEM = 1; @@ -475,14 +487,6 @@ public String getCanonical() { return canonical; } - public VersionNumber min(VersionNumber that) { - return this.compareTo(that) <= 0 ? this : that; - } - - public VersionNumber max(VersionNumber that) { - return this.compareTo(that) >= 0 ? this : that; - } - @Override public boolean equals(Object o) { return o instanceof VersionNumber && canonical.equals(((VersionNumber) o).canonical); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionRange.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionRange.java index c8b628724d..c0d6cd1912 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionRange.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/versioning/VersionRange.java @@ -2,58 +2,50 @@ import java.util.Objects; -public final class VersionRange { - private static final VersionRange EMPTY = new VersionRange(null, null); - private static final VersionRange ALL = new VersionRange(null, null); +/** + * @author Glavo + */ +@SuppressWarnings("unchecked") +public final class VersionRange> { + private static final VersionRange EMPTY = new VersionRange<>(null, null); + private static final VersionRange ALL = new VersionRange<>(null, null); - public static VersionRange empty() { - return EMPTY; + public static > VersionRange empty() { + return (VersionRange) EMPTY; } - public static VersionRange all() { - return ALL; + public static > VersionRange all() { + return (VersionRange) ALL; } - public static VersionRange between(String minimum, String maximum) { - return between(VersionNumber.asVersion(minimum), VersionNumber.asVersion(maximum)); - } - - public static VersionRange between(VersionNumber minimum, VersionNumber maximum) { + public static > VersionRange between(T minimum, T maximum) { assert minimum.compareTo(maximum) <= 0; - return new VersionRange(minimum, maximum); + return new VersionRange<>(minimum, maximum); } - public static VersionRange atLeast(String minimum) { - return atLeast(VersionNumber.asVersion(minimum)); - } - - public static VersionRange atLeast(VersionNumber minimum) { + public static > VersionRange atLeast(T minimum) { assert minimum != null; - return new VersionRange(minimum, null); + return new VersionRange<>(minimum, null); } - public static VersionRange atMost(String maximum) { - return atMost(VersionNumber.asVersion(maximum)); - } - - public static VersionRange atMost(VersionNumber maximum) { + public static > VersionRange atMost(T maximum) { assert maximum != null; - return new VersionRange(null, maximum); + return new VersionRange<>(null, maximum); } - private final VersionNumber minimum; - private final VersionNumber maximum; + private final T minimum; + private final T maximum; - private VersionRange(VersionNumber minimum, VersionNumber maximum) { + private VersionRange(T minimum, T maximum) { this.minimum = minimum; this.maximum = maximum; } - public VersionNumber getMinimum() { + public T getMinimum() { return minimum; } - public VersionNumber getMaximum() { + public T getMaximum() { return maximum; } @@ -65,12 +57,7 @@ public boolean isAll() { return !isEmpty() && minimum == null && maximum == null; } - public boolean contains(String versionNumber) { - if (versionNumber == null) return false; - return contains(VersionNumber.asVersion(versionNumber)); - } - - public boolean contains(VersionNumber versionNumber) { + public boolean contains(T versionNumber) { if (versionNumber == null) return false; if (isEmpty()) return false; if (isAll()) return true; @@ -78,7 +65,7 @@ public boolean contains(VersionNumber versionNumber) { return (minimum == null || minimum.compareTo(versionNumber) <= 0) && (maximum == null || maximum.compareTo(versionNumber) >= 0); } - public boolean isOverlappedBy(final VersionRange that) { + public boolean isOverlappedBy(final VersionRange that) { if (this.isEmpty() || that.isEmpty()) return false; @@ -94,32 +81,32 @@ public boolean isOverlappedBy(final VersionRange that) { return that.contains(minimum) || that.contains(maximum) || (that.minimum != null && contains(that.minimum)); } - public VersionRange intersectionWith(VersionRange that) { + public VersionRange intersectionWith(VersionRange that) { if (this.isAll()) return that; if (that.isAll()) return this; if (!isOverlappedBy(that)) - return EMPTY; + return empty(); - VersionNumber newMinimum; + T newMinimum; if (this.minimum == null) newMinimum = that.minimum; else if (that.minimum == null) newMinimum = this.minimum; else - newMinimum = this.minimum.max(that.minimum); + newMinimum = this.minimum.compareTo(that.minimum) >= 0 ? this.minimum : that.minimum; - VersionNumber newMaximum; + T newMaximum; if (this.maximum == null) newMaximum = that.maximum; else if (that.maximum == null) newMaximum = this.maximum; else - newMaximum = this.maximum.min(that.maximum); + newMaximum = this.maximum.compareTo(that.maximum) <= 0 ? this.maximum : that.maximum; - return new VersionRange(newMinimum, newMaximum); + return new VersionRange<>(newMinimum, newMaximum); } @Override @@ -139,7 +126,7 @@ public boolean equals(Object obj) { if (!(obj instanceof VersionRange)) return false; - VersionRange that = (VersionRange) obj; + VersionRange that = (VersionRange) obj; return this.isEmpty() == that.isEmpty() && this.isAll() == that.isAll() && Objects.equals(this.minimum, that.minimum) diff --git a/HMCLCore/src/main/resources/assets/game/versions.txt b/HMCLCore/src/main/resources/assets/game/versions.txt new file mode 100644 index 0000000000..273408a1a8 --- /dev/null +++ b/HMCLCore/src/main/resources/assets/game/versions.txt @@ -0,0 +1,738 @@ +1.0 +11w47a +11w48a +11w49a +11w50a +12w01a +1.1 +12w03a +12w04a +12w05a +12w05b +12w06a +12w07a +12w07b +12w08a +1.2 +1.2.1 +1.2.2 +1.2.3 +1.2.4 +1.2.5 +12w15a +12w16a +12w17a +12w18a +12w19a +12w21a +12w21b +12w22a +12w23a +12w23b +12w24a +12w25a +12w26a +12w27a +12w30a +12w30b +12w30c +12w30d +12w30e +1.3 +1.3.1 +1.3.2 +12w32a +12w34a +12w34b +12w36a +12w37a +12w38a +12w38b +12w39a +12w39b +12w40a +12w40b +12w41a +12w41b +12w42a +12w42b +1.4 +1.4.1 +1.4.2 +1.4.3 +1.4.4 +12w49a +12w50a +12w50b +1.4.5 +1.4.6 +1.4.7 +13w01a +13w01b +13w02a +13w02b +13w03a +13w04a +13w05a +13w05b +13w06a +13w07a +13w09a +13w09b +13w09c +13w10a +13w10b +1.5 +13w11a +13w12~ +1.5.1 +2.0 +1.5.2 +13w16a +13w16b +13w17a +13w18a +13w18b +13w18c +13w19a +13w21a +13w21b +13w22a +13w23a +13w23b +13w24a +13w24b +13w25a +13w25b +13w25c +13w26a +1.6 +1.6.1 +1.6.2 +1.6.3 +1.6.4 +13w36a +13w36b +13w37a +13w37b +13w38a +13w38b +13w38c +13w39a +13w39b +13w41a +13w41b +13w42a +13w42b +13w43a +1.7 +1.7.1 +1.7.2 +13w47a +13w47b +13w47c +13w47d +13w47e +13w48a +13w48b +13w49a +1.7.3 +1.7.4 +1.7.5 +1.7.6-pre1 +1.7.6-pre2 +1.7.6 +1.7.7 +1.7.8 +1.7.9 +1.7.10-pre1 +1.7.10-pre2 +1.7.10-pre3 +1.7.10-pre4 +1.7.10 +14w02a +14w02b +14w02c +14w03a +14w03b +14w04a +14w04b +14w05a +14w05b +14w06a +14w06b +14w07a +14w08a +14w10a +14w10b +14w10c +14w11a +14w11b +14w17a +14w18a +14w18b +14w19a +14w20a +14w20b +14w21a +14w21b +14w25a +14w25b +14w26a +14w26b +14w26c +14w27a +14w27b +14w28a +14w28b +14w29a +14w29b +14w30a +14w30b +14w30c +14w31a +14w32a +14w32b +14w32c +14w32d +14w33a +14w33b +14w33c +14w34a +14w34b +14w34c +14w34d +1.8-pre1 +1.8-pre2 +1.8-pre3 +1.8 +1.8.1-pre1 +1.8.1-pre2 +1.8.1-pre3 +1.8.1-pre4 +1.8.1-pre5 +1.8.1 +1.8.2-pre1 +1.8.2-pre2 +1.8.2-pre3 +1.8.2-pre4 +1.8.2-pre5 +1.8.2-pre6 +1.8.2-pre7 +1.8.2 +1.8.3 +15w14a +1.8.4 +1.8.5 +1.8.6 +1.8.7 +1.8.8 +1.8.9 +15w31a +15w31b +15w31c +15w32a +15w32b +15w32c +15w33a +15w33b +15w33c +15w34a +15w34b +15w34c +15w34d +15w35a +15w35b +15w35c +15w35d +15w35e +15w36a +15w36b +15w36c +15w36d +15w37a +15w38a +15w38b +15w39a +15w39b +15w39c +15w40a +15w40b +15w41a +15w41b +15w42a +15w43a +15w43b +15w43c +15w44a +15w44b +15w45a +15w46a +15w47a +15w47b +15w47c +15w49a +15w49b +15w50a +15w51a +15w51b +16w02a +16w03a +16w04a +16w05a +16w05b +16w06a +16w07a +16w07b +1.9-pre1 +1.9-pre2 +1.9-pre3 +1.9-pre4 +1.9 +1.9.1-pre1 +1.9.1-pre2 +1.9.1-pre3 +1.9.1 +1.9.2 +1.RV-Pre1 +16w14a +16w15a +16w15b +1.9.3-pre1 +1.9.3-pre2 +1.9.3-pre3 +1.9.3 +1.9.4 +16w20a +16w21a +16w21b +1.10-pre1 +1.10-pre2 +1.10 +1.10.1 +1.10.2 +16w32a +16w32b +16w33a +16w35a +16w36a +16w38a +16w39a +16w39b +16w39c +16w40a +16w41a +16w42a +16w43a +16w44a +1.11-pre1 +1.11 +16w50a +1.11.1 +1.11.2 +17w06a +17w13a +17w13b +17w14a +17w15a +17w16a +17w16b +17w17a +17w17b +17w18a +17w18b +1.12-pre1 +1.12-pre2 +1.12-pre3 +1.12-pre4 +1.12-pre5 +1.12-pre6 +1.12-pre7 +1.12 +17w31a +1.12.1-pre1 +1.12.1 +1.12.2-pre1 +1.12.2-pre2 +1.12.2 +17w43a +17w43b +17w45a +17w45b +17w46a +17w47a +17w47b +17w48a +17w49a +17w49b +17w50a +18w01a +18w02a +18w03a +18w03b +18w05a +18w06a +18w07a +18w07b +18w07c +18w08a +18w08b +18w09a +18w10a +18w10b +18w10c +18w10d +18w11a +18w14a +18w14b +18w15a +18w16a +18w19a +18w19b +18w20a +18w20b +18w20c +18w21a +18w21b +18w22a +18w22b +18w22c +1.13-pre1 +1.13-pre2 +1.13-pre3 +1.13-pre4 +1.13-pre5 +1.13-pre6 +1.13-pre7 +1.13-pre8 +1.13-pre9 +1.13-pre10 +1.13 +18w30a +18w30b +18w31a +18w32a +18w33a +1.13.1-pre1 +1.13.1-pre2 +1.13.1 +1.13.2-pre1 +1.13.2-pre2 +1.13.2 +18w43a +18w43b +18w43c +18w44a +18w45a +18w46a +18w47a +18w47b +18w48a +18w48b +18w49a +18w50a +19w02a +19w03a +19w03b +19w03c +19w04a +19w04b +19w05a +19w06a +19w07a +19w08a +19w08b +19w09a +19w11a +19w11b +19w12a +19w12b +19w13a +19w13b +3D Shareware v1.34 +19w14a +19w14b +1.14 Pre-Release 1 +1.14 Pre-Release 2 +1.14 Pre-Release 3 +1.14 Pre-Release 4 +1.14 Pre-Release 5 +1.14 +1.14.1 Pre-Release 1 +1.14.1 Pre-Release 2 +1.14.1 +1.14.2 Pre-Release 1 +1.14.2 Pre-Release 2 +1.14.2 Pre-Release 3 +1.14.2 Pre-Release 4 +1.14.2 +1.14.3-pre1 +1.14.3-pre2 +1.14.3-pre3 +1.14.3-pre4 +1.14.3 +1.14.4-pre1 +1.14.4-pre2 +1.14.4-pre3 +1.14.4-pre4 +1.14.4-pre5 +1.14.4-pre6 +1.14.4-pre7 +1.14.4 +19w34a +19w35a +19w36a +19w37a +19w38a +19w38b +19w39a +19w40a +19w41a +19w42a +19w44a +19w45a +19w45b +19w46a +19w46b +1.15-pre1 +1.15-pre2 +1.15-pre3 +1.15-pre4 +1.15-pre5 +1.15-pre6 +1.15-pre7 +1.15 +1.15.1-pre1 +1.15.1 +1.15.2-pre1 +1.15.2-pre2 +1.15.2 +20w06a +20w07a +20w08a +20w09a +20w10a +20w11a +20w12a +20w13a +20w13b +20w14infinite +20w14a +20w15a +20w16a +20w17a +20w18a +20w19a +20w20a +20w20b +20w21a +20w22a +1.16-pre1 +1.16-pre2 +1.16-pre3 +1.16-pre4 +1.16-pre5 +1.16-pre6 +1.16-pre7 +1.16-pre8 +1.16-rc1 +1.16 +1.16.1 +20w27a +20w28a +20w29a +20w30a +1.16.2-pre1 +1.16.2-pre2 +1.16.2-pre3 +1.16.2-rc1 +1.16.2-rc2 +1.16.2 +1.16.3-rc1 +1.16.3 +1.16.4-pre1 +1.16.4-pre2 +1.16.4-rc1 +1.16.4 +1.16.5-rc1 +1.16.5 +20w45a +20w46a +20w48a +20w49a +20w51a +21w03a +21w05a +21w05b +21w06a +21w07a +21w08a +21w08b +21w10a +21w11a +21w13a +21w14a +21w15a +21w16a +21w17a +21w18a +21w19a +21w20a +1.17-pre1 +1.17-pre2 +1.17-pre3 +1.17-pre4 +1.17-pre5 +1.17-rc1 +1.17-rc2 +1.17 +1.17.1-pre1 +1.17.1-pre2 +1.17.1-pre3 +1.17.1-rc1 +1.17.1-rc2 +1.17.1 +21w37a +21w38a +21w39a +21w40a +21w41a +21w42a +21w43a +21w44a +1.18-pre1 +1.18-pre2 +1.18-pre3 +1.18-pre4 +1.18-pre5 +1.18-pre6 +1.18-pre7 +1.18-pre8 +1.18-rc1 +1.18-rc2 +1.18-rc3 +1.18-rc4 +1.18 +1.18.1-pre1 +1.18.1-rc1 +1.18.1-rc2 +1.18.1-rc3 +1.18.1 +22w03a +22w05a +22w06a +22w07a +1.18.2-pre1 +1.18.2-pre2 +1.18.2-pre3 +1.18.2-rc1 +1.18.2 +22w13oneblockatatime +22w11a +22w12a +22w13a +22w14a +22w15a +22w16a +22w16b +22w17a +22w18a +22w19a +1.19-pre1 +1.19-pre2 +1.19-pre3 +1.19-pre4 +1.19-pre5 +1.19-rc1 +1.19-rc2 +1.19 +22w24a +1.19.1-pre1 +1.19.1-pre2 +1.19.1-pre3 +1.19.1-pre4 +1.19.1-pre5 +1.19.1-pre6 +1.19.1-rc1 +1.19.1-rc2 +1.19.1-rc3 +1.19.1 +1.19.2-rc1 +1.19.2-rc2 +1.19.2 +22w42a +22w43a +22w44a +22w45a +22w46a +1.19.3-pre1 +1.19.3-pre2 +1.19.3-pre3 +1.19.3-rc1 +1.19.3-rc2 +1.19.3-rc3 +1.19.3 +23w03a +23w04a +23w05a +23w06a +23w07a +1.19.4-pre1 +1.19.4-pre2 +1.19.4-pre3 +1.19.4-pre4 +1.19.4-rc1 +1.19.4-rc2 +1.19.4-rc3 +1.19.4 +23w12a +23w13a +23w13a_or_b +23w14a +23w16a +23w17a +23w18a +1.20-pre1 +1.20-pre2 +1.20-pre3 +1.20-pre4 +1.20-pre5 +1.20-pre6 +1.20-pre7 +1.20-rc1 +1.20 +1.20.1-rc1 +1.20.1 +23w31a +23w32a +23w33a +23w35a +1.20.2-pre1 +1.20.2-pre2 +1.20.2-pre3 +1.20.2-pre4 +1.20.2-rc1 +1.20.2-rc2 +1.20.2 +23w40a +23w41a +23w42a +23w43a +23w43b +23w44a +23w45a +23w46a +1.20.3-pre1 +1.20.3-pre2 +1.20.3-pre3 +1.20.3-pre4 +1.20.3-rc1 +1.20.3 +1.20.4-rc1 +1.20.4 +23w51a +23w51b +24w03a +24w03b +24w04a +24w05a diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/JavaVersionConstraintTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/JavaVersionConstraintTest.java index 6bc3ce5856..ddc1ddfe23 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/JavaVersionConstraintTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/JavaVersionConstraintTest.java @@ -17,8 +17,8 @@ */ package org.jackhuang.hmcl.game; +import org.jackhuang.hmcl.util.versioning.GameVersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber; -import org.jackhuang.hmcl.util.versioning.VersionRange; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -28,10 +28,10 @@ public class JavaVersionConstraintTest { @Test public void vanillaJava16() { JavaVersionConstraint.VersionRanges range = JavaVersionConstraint.findSuitableJavaVersionRange( - VersionNumber.asVersion("1.17"), + GameVersionNumber.asGameVersion("1.17"), null ); - assertEquals(VersionRange.atLeast("16"), range.getMandatory()); + assertEquals(VersionNumber.atLeast("16"), range.getMandatory()); } } diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/GameVersionNumberTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/GameVersionNumberTest.java new file mode 100644 index 0000000000..509c40f31b --- /dev/null +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/GameVersionNumberTest.java @@ -0,0 +1,218 @@ +package org.jackhuang.hmcl.util.versioning; + +import org.junit.jupiter.api.Test; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.*; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Glavo + */ +public class GameVersionNumberTest { + + @Test + public void testSortVersions() throws IOException { + List versions = new ArrayList<>(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(GameVersionNumber.class.getResourceAsStream("/assets/game/versions.txt"), StandardCharsets.UTF_8))) { + for (String line; (line = reader.readLine()) != null && !line.isEmpty(); ) { + versions.add(line); + } + } + + List copied = new ArrayList<>(versions); + Collections.shuffle(copied, new Random(0)); + copied.sort(Comparator.comparing(GameVersionNumber::asGameVersion)); + + assertIterableEquals(versions, copied); + } + + private static String errorMessage(String version1, String version2) { + return String.format("version1=%s, version2=%s", version1, version2); + } + + private static void assertGameVersionEquals(String version) { + assertGameVersionEquals(version, version); + } + + private static void assertGameVersionEquals(String version1, String version2) { + assertEquals(0, GameVersionNumber.asGameVersion(version1).compareTo(version2), errorMessage(version1, version2)); + } + + private static void assertLessThan(String version1, String version2) { + assertTrue(GameVersionNumber.asGameVersion(version1).compareTo(version2) < 0, errorMessage(version1, version2)); + } + + private static void assertOrder(String... versions) { + for (int i = 0; i < versions.length - 1; i++) { + GameVersionNumber version1 = GameVersionNumber.asGameVersion(versions[i]); + + //noinspection EqualsWithItself + assertEquals(0, version1.compareTo(version1), "version=" + versions[i]); + + for (int j = i + 1; j < versions.length; j++) { + GameVersionNumber version2 = GameVersionNumber.asGameVersion(versions[j]); + + assertEquals(-1, version1.compareTo(version2), String.format("version1=%s, version2=%s", versions[i], versions[j])); + assertEquals(1, version2.compareTo(version1), String.format("version1=%s, version2=%s", versions[i], versions[j])); + } + } + + assertGameVersionEquals(versions[versions.length - 1]); + } + + @Test + public void testParseOld() { + { + GameVersionNumber version = GameVersionNumber.asGameVersion("b1.0"); + assertInstanceOf(GameVersionNumber.Old.class, version); + GameVersionNumber.Old old = (GameVersionNumber.Old) version; + assertEquals(GameVersionNumber.Type.BETA, old.type); + assertEquals(1, old.major); + assertEquals(0, old.minor); + assertEquals(0, old.patch); + assertEquals(0, old.additional); + } + + { + GameVersionNumber version = GameVersionNumber.asGameVersion("b1.0_01"); + assertInstanceOf(GameVersionNumber.Old.class, version); + GameVersionNumber.Old old = (GameVersionNumber.Old) version; + assertEquals(GameVersionNumber.Type.BETA, old.type); + assertEquals(1, old.major); + assertEquals(0, old.minor); + assertEquals(0, old.patch); + assertEquals(1, old.additional); + } + } + + @Test + public void testCompareRelease() { + assertGameVersionEquals("0.0"); + assertGameVersionEquals("1.100"); + assertGameVersionEquals("1.100.1"); + assertGameVersionEquals("1.100.1-pre1"); + assertGameVersionEquals("1.100.1-pre1", "1.100.1 Pre-Release 1"); + + assertOrder( + "0.0", + "1.0", + "1.99", + "1.99.1-unknown1", + "1.99.1-pre1", + "1.99.1 Pre-Release 2", + "1.99.1-rc1", + "1.99.1", + "1.100", + "1.100.1" + ); + } + + @Test + public void testCompareSnapshot() { + assertOrder( + "90w01a", + "90w01b", + "90w01e", + "90w01~", + "90w02a" + ); + } + + @Test + public void testCompareMix() { + assertOrder( + "rd-132211", + "rd-161348", + "rd-20090515", + "c0.0.11a", + "c0.0.13a", + "c0.0.13a_03", + "c0.30_01c", + "inf-20100330-1", + "inf-20100330-2", + "inf-20100618", + "a1.0.4", + "a1.0.17_02", + "a1.0.17_04", + "a1.1.0", + "b1.0", + "b1.0_01", + "b1.1_02", + "b1.2", + "b1.8.1", + "0.0", + "1.0", + "11w47a", + "1.1", + "1.5.1", + "2.0", + "1.5.2", + "1.9.2", + "1.RV-Pre1", + "16w14a", + "1.9.3-pre1", + "1.13.2", + "19w13b", + "3D Shareware v1.34", + "19w14a", + "1.14 Pre-Release 1", + "1.14", + "1.15.2", + "20w06a", + "20w14infinite", + "20w22a", + "1.16-pre1", + "1.16", + "1.18.2", + "22w13oneblockatatime", + "22w11a", + "1.19-pre1", + "1.19.4", + "23w13a", + "23w13a_or_b", + "23w14a", + "1.20", + + "Unknown", + "100.0" + ); + } + + @Test + public void testCompareUnknown() { + assertOrder( + "23w35a", + "1.20.2-pre1", + "1.20.2-rc1", + "1.20.2", + "23w35b", // fictional version number + "23w40a" + ); + + assertOrder( + "1.20.4", + "24w04a", + "1.100" // fictional version number + ); + + assertOrder( + "1.19.4", + "23w18a", // fictional version number + "1.19.5", + "1.20" + ); + + assertOrder( + "1.0", + "10w47a", // fictional version number + "11w47a", + "1.1" + ); + } +} diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/VersionRangeTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/VersionRangeTest.java index 0f8ae788d1..fcec6a1dea 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/VersionRangeTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/versioning/VersionRangeTest.java @@ -9,43 +9,46 @@ public class VersionRangeTest { @Test public void testContains() { - assertTrue(between("10", "20").contains("10")); - assertTrue(between("10", "20").contains("15")); - assertTrue(between("10", "20").contains("20")); - assertFalse(between("10", "20").contains("5")); - assertFalse(between("10", "20").contains("25")); - - assertTrue(between("10", "10").contains("10")); - assertFalse(between("10", "10").contains("5")); - assertFalse(between("10", "10").contains("15")); - - assertTrue(atLeast("10").contains("10")); - assertTrue(atLeast("10").contains("20")); - assertFalse(atLeast("10").contains("5")); - - assertTrue(atMost("10").contains("10")); - assertTrue(atMost("10").contains("5")); - assertFalse(atMost("10").contains("20")); - - assertFalse(empty().contains("0")); - assertFalse(empty().contains("10")); - - assertTrue(all().contains("0")); - assertTrue(all().contains("10")); - - assertFalse(all().contains((String) null)); - assertFalse(empty().contains((String) null)); - assertFalse(between("0", "10").contains((String) null)); - assertFalse(atLeast("10").contains((String) null)); - assertFalse(atMost("10").contains((String) null)); - assertFalse(all().contains((VersionNumber) null)); - assertFalse(empty().contains((VersionNumber) null)); - assertFalse(between("0", "10").contains((VersionNumber) null)); - assertFalse(atLeast("10").contains((VersionNumber) null)); - assertFalse(atMost("10").contains((VersionNumber) null)); + VersionRange empty = VersionRange.empty(); + VersionRange all = all(); + + assertTrue(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("10"))); + assertTrue(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("15"))); + assertTrue(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("20"))); + assertFalse(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("5"))); + assertFalse(VersionNumber.between("10", "20").contains(VersionNumber.asVersion("25"))); + + assertTrue(VersionNumber.between("10", "10").contains(VersionNumber.asVersion("10"))); + assertFalse(VersionNumber.between("10", "10").contains(VersionNumber.asVersion("5"))); + assertFalse(VersionNumber.between("10", "10").contains(VersionNumber.asVersion("15"))); + + assertTrue(VersionNumber.atLeast("10").contains(VersionNumber.asVersion("10"))); + assertTrue(VersionNumber.atLeast("10").contains(VersionNumber.asVersion("20"))); + assertFalse(VersionNumber.atLeast("10").contains(VersionNumber.asVersion("5"))); + + assertTrue(VersionNumber.atMost("10").contains(VersionNumber.asVersion("10"))); + assertTrue(VersionNumber.atMost("10").contains(VersionNumber.asVersion("5"))); + assertFalse(VersionNumber.atMost("10").contains(VersionNumber.asVersion("20"))); + + assertFalse(empty.contains(VersionNumber.asVersion("0"))); + assertFalse(empty.contains(VersionNumber.asVersion("10"))); + + assertTrue(all.contains(VersionNumber.asVersion("0"))); + assertTrue(all.contains(VersionNumber.asVersion("10"))); + + assertFalse(all.contains(null)); + assertFalse(empty.contains( null)); + assertFalse(VersionNumber.between("0", "10").contains(null)); + assertFalse(VersionNumber.atLeast("10").contains(null)); + assertFalse(VersionNumber.atMost("10").contains(null)); + assertFalse(all.contains(null)); + assertFalse(empty.contains(null)); + assertFalse(VersionNumber.between("0", "10").contains(null)); + assertFalse(VersionNumber.atLeast("10").contains(null)); + assertFalse(VersionNumber.atMost("10").contains(null)); } - private static void assertIsOverlappedBy(boolean value, VersionRange range1, VersionRange range2) { + private static void assertIsOverlappedBy(boolean value, VersionRange range1, VersionRange range2) { assertEquals(value, range1.isOverlappedBy(range2)); assertEquals(value, range2.isOverlappedBy(range1)); } @@ -56,38 +59,38 @@ public void testIsOverlappedBy() { assertIsOverlappedBy(false, all(), empty()); assertIsOverlappedBy(false, empty(), empty()); - assertIsOverlappedBy(true, all(), between("10", "20")); - assertIsOverlappedBy(true, all(), atLeast("10")); - assertIsOverlappedBy(true, all(), atMost("10")); - - assertIsOverlappedBy(false, empty(), between("10", "20")); - assertIsOverlappedBy(false, empty(), atLeast("10")); - assertIsOverlappedBy(false, empty(), atMost("10")); - - assertIsOverlappedBy(true, between("10", "20"), between("10", "20")); - assertIsOverlappedBy(true, between("10", "20"), between("5", "20")); - assertIsOverlappedBy(true, between("10", "20"), between("5", "15")); - assertIsOverlappedBy(true, between("10", "20"), between("5", "10")); - assertIsOverlappedBy(false, between("10", "20"), between("5", "5")); - assertIsOverlappedBy(true, between("10", "20"), between("10", "30")); - assertIsOverlappedBy(true, between("10", "20"), between("15", "30")); - assertIsOverlappedBy(true, between("10", "20"), between("20", "30")); - assertIsOverlappedBy(false, between("10", "20"), between("21", "30")); - assertIsOverlappedBy(true, between("10", "20"), atLeast("5")); - assertIsOverlappedBy(true, between("10", "20"), atLeast("10")); - assertIsOverlappedBy(true, between("10", "20"), atLeast("15")); - assertIsOverlappedBy(true, between("10", "20"), atLeast("20")); - assertIsOverlappedBy(false, between("10", "20"), atLeast("25")); - - assertIsOverlappedBy(true, atLeast("10"), atLeast("10")); - assertIsOverlappedBy(true, atLeast("10"), atLeast("20")); - assertIsOverlappedBy(true, atLeast("10"), atLeast("5")); - assertIsOverlappedBy(true, atLeast("10"), atMost("10")); - assertIsOverlappedBy(true, atLeast("10"), atMost("20")); - assertIsOverlappedBy(false, atLeast("10"), atMost("5")); + assertIsOverlappedBy(true, all(), VersionNumber.between("10", "20")); + assertIsOverlappedBy(true, all(), VersionNumber.atLeast("10")); + assertIsOverlappedBy(true, all(), VersionNumber.atMost("10")); + + assertIsOverlappedBy(false, empty(), VersionNumber.between("10", "20")); + assertIsOverlappedBy(false, empty(), VersionNumber.atLeast("10")); + assertIsOverlappedBy(false, empty(), VersionNumber.atMost("10")); + + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("10", "20")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("5", "20")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("5", "15")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("5", "10")); + assertIsOverlappedBy(false, VersionNumber.between("10", "20"), VersionNumber.between("5", "5")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("10", "30")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("15", "30")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.between("20", "30")); + assertIsOverlappedBy(false, VersionNumber.between("10", "20"), VersionNumber.between("21", "30")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.atLeast("5")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.atLeast("10")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.atLeast("15")); + assertIsOverlappedBy(true, VersionNumber.between("10", "20"), VersionNumber.atLeast("20")); + assertIsOverlappedBy(false, VersionNumber.between("10", "20"), VersionNumber.atLeast("25")); + + assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atLeast("10")); + assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atLeast("20")); + assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atLeast("5")); + assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atMost("10")); + assertIsOverlappedBy(true, VersionNumber.atLeast("10"), VersionNumber.atMost("20")); + assertIsOverlappedBy(false, VersionNumber.atLeast("10"), VersionNumber.atMost("5")); } - private static void assertIntersectionWith(VersionRange range1, VersionRange range2, VersionRange result) { + private static void assertIntersectionWith(VersionRange range1, VersionRange range2, VersionRange result) { assertEquals(result, range1.intersectionWith(range2)); assertEquals(result, range2.intersectionWith(range1)); } @@ -96,35 +99,35 @@ private static void assertIntersectionWith(VersionRange range1, VersionRange ran public void testIntersectionWith() { assertIntersectionWith(all(), all(), all()); assertIntersectionWith(all(), empty(), empty()); - assertIntersectionWith(all(), between("10", "20"), between("10", "20")); - assertIntersectionWith(all(), atLeast("10"), atLeast("10")); - assertIntersectionWith(all(), atMost("10"), atMost("10")); + assertIntersectionWith(all(), VersionNumber.between("10", "20"), VersionNumber.between("10", "20")); + assertIntersectionWith(all(), VersionNumber.atLeast("10"), VersionNumber.atLeast("10")); + assertIntersectionWith(all(), VersionNumber.atMost("10"), VersionNumber.atMost("10")); assertIntersectionWith(empty(), empty(), empty()); - assertIntersectionWith(empty(), between("10", "20"), empty()); - assertIntersectionWith(empty(), atLeast("10"), empty()); - assertIntersectionWith(empty(), atMost("10"), empty()); - - assertIntersectionWith(between("10", "20"), between("10", "20"), between("10", "20")); - assertIntersectionWith(between("10", "20"), between("5", "20"), between("10", "20")); - assertIntersectionWith(between("10", "20"), between("10", "25"), between("10", "20")); - assertIntersectionWith(between("10", "20"), between("5", "25"), between("10", "20")); - assertIntersectionWith(between("10", "20"), between("15", "20"), between("15", "20")); - assertIntersectionWith(between("10", "20"), between("10", "15"), between("10", "15")); - assertIntersectionWith(between("10", "20"), between("14", "16"), between("14", "16")); - assertIntersectionWith(between("10", "20"), atLeast("5"), between("10", "20")); - assertIntersectionWith(between("10", "20"), atLeast("10"), between("10", "20")); - assertIntersectionWith(between("10", "20"), atLeast("15"), between("15", "20")); - assertIntersectionWith(between("10", "20"), atLeast("20"), between("20", "20")); - assertIntersectionWith(between("10", "20"), atLeast("25"), empty()); - assertIntersectionWith(between("10", "20"), atMost("25"), between("10", "20")); - assertIntersectionWith(between("10", "20"), atMost("20"), between("10", "20")); - assertIntersectionWith(between("10", "20"), atMost("15"), between("10", "15")); - assertIntersectionWith(between("10", "20"), atMost("10"), between("10", "10")); - assertIntersectionWith(between("10", "20"), atMost("5"), empty()); - - assertIntersectionWith(atLeast("10"), atMost("10"), between("10", "10")); - assertIntersectionWith(atLeast("10"), atMost("20"), between("10", "20")); - assertIntersectionWith(atLeast("10"), atMost("5"), empty()); + assertIntersectionWith(empty(), VersionNumber.between("10", "20"), empty()); + assertIntersectionWith(empty(), VersionNumber.atLeast("10"), empty()); + assertIntersectionWith(empty(), VersionNumber.atMost("10"), empty()); + + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("10", "20"), VersionNumber.between("10", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("5", "20"), VersionNumber.between("10", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("10", "25"), VersionNumber.between("10", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("5", "25"), VersionNumber.between("10", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("15", "20"), VersionNumber.between("15", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("10", "15"), VersionNumber.between("10", "15")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.between("14", "16"), VersionNumber.between("14", "16")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("5"), VersionNumber.between("10", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("10"), VersionNumber.between("10", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("15"), VersionNumber.between("15", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("20"), VersionNumber.between("20", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atLeast("25"), empty()); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("25"), VersionNumber.between("10", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("20"), VersionNumber.between("10", "20")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("15"), VersionNumber.between("10", "15")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("10"), VersionNumber.between("10", "10")); + assertIntersectionWith(VersionNumber.between("10", "20"), VersionNumber.atMost("5"), empty()); + + assertIntersectionWith(VersionNumber.atLeast("10"), VersionNumber.atMost("10"), VersionNumber.between("10", "10")); + assertIntersectionWith(VersionNumber.atLeast("10"), VersionNumber.atMost("20"), VersionNumber.between("10", "20")); + assertIntersectionWith(VersionNumber.atLeast("10"), VersionNumber.atMost("5"), empty()); } } From c54cb88db8b6dd60260cf7c2a8a07deb45392280 Mon Sep 17 00:00:00 2001 From: zkitefly Date: Tue, 13 Feb 2024 14:11:21 +0800 Subject: [PATCH 13/33] add Crash report analysis (#2790) * fix i18n * add https://github.com/HMCL-dev/HMCL/discussions/1904#discussioncomment-4339947 --- .../assets/lang/I18N_zh_CN.properties | 1 - .../hmcl/game/CrashReportAnalyzer.java | 2 +- .../hmcl/game/CrashReportAnalyzerTest.java | 7 +++++++ .../logs/java_version_is_too_high.txt | 20 +++++++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 HMCLCore/src/test/resources/logs/java_version_is_too_high.txt diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 078caa3d89..541428d705 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -434,7 +434,6 @@ game.crash.reason.memory_exceeded=当前游戏因为分配的内存过大,无 game.crash.reason.mac_jdk_8u261=当前游戏因为你所使用的 Forge 或 OptiFine 与 Java 冲突崩溃。\n请尝试更新 Forge 和 OptiFine,或使用 Java 8u251 及更早版本启动。 game.crash.reason.mod=当前游戏因为 %1$s 的问题,无法继续运行。\n你可以更新或删除已经安装的 %1$s 再试。 game.crash.reason.mod_resolution=当前游戏因为 Mod 依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s -game.crash.reason.forgemod_resolution=当前游戏因为 Mod 依赖问题,无法继续运行。Forge 提供了如下信息:\n%1$s game.crash.reason.mod_resolution_collection=当前游戏因为前置 Mod 版本不匹配,无法继续运行。\n%1$s 需要前置 Mod:%2$s 才能继续运行。\n这表示你需要更新或降级前置。你可以到下载页的模组下载,或到网上下载 %3$s。 game.crash.reason.mod_resolution_conflict=当前游戏因为 Mod 冲突,无法继续运行。\n%1$s 与 %2$s 不能兼容。 game.crash.reason.mod_resolution_missing=当前游戏因为缺少 Mod 前置,无法继续运行。\n%1$s 需要前置 Mod:%2$s 才能继续运行。\n这表示你少安装了 Mod,或该 Mod 版本不够。你可以到下载页的模组下载,或到网上下载 %3$s。 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java index 662b517fae..fb0fd83080 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java @@ -114,7 +114,7 @@ public enum Rule { MOD_RESOLUTION0(Pattern.compile("(\tMod File:|-- MOD |\tFailure message:)")), FORGE_REPEAT_INSTALLATION(Pattern.compile("MultipleArgumentsForOptionException: Found multiple arguments for option (.*?), but you asked for only one")),//https://github.com/HMCL-dev/HMCL/issues/1880 OPTIFINE_REPEAT_INSTALLATION(Pattern.compile("ResolutionException: Module optifine reads another module named optifine")),//Optifine 重复安装(及Mod文件夹有,自动安装也有) - JAVA_VERSION_IS_TOO_HIGH(Pattern.compile("(Unable to make protected final java\\.lang\\.Class java\\.lang\\.ClassLoader\\.defineClass|java\\.lang\\.NoSuchFieldException: ucp|Unsupported class file major version|because module java\\.base does not export|java\\.lang\\.ClassNotFoundException: jdk\\.nashorn\\.api\\.scripting\\.NashornScriptEngineFactory|java\\.lang\\.ClassNotFoundException: java\\.lang\\.invoke\\.LambdaMetafactory)")),//Java版本过高 + JAVA_VERSION_IS_TOO_HIGH(Pattern.compile("(Unable to make protected final java\\.lang\\.Class java\\.lang\\.ClassLoader\\.defineClass|java\\.lang\\.NoSuchFieldException: ucp|Unsupported class file major version|because module java\\.base does not export|java\\.lang\\.ClassNotFoundException: jdk\\.nashorn\\.api\\.scripting\\.NashornScriptEngineFactory|java\\.lang\\.ClassNotFoundException: java\\.lang\\.invoke\\.LambdaMetafactory|Exception in thread \"main\" java\\.lang\\.NullPointerException: Cannot read the array length because \"urls\" is null)")),//Java版本过高 INSTALL_MIXINBOOTSTRAP(Pattern.compile("java\\.lang\\.ClassNotFoundException: org\\.spongepowered\\.asm\\.launch\\.MixinTweaker")), //Forge 默认会把每一个 mod jar 都当做一个 JPMS 的模块(Module)加载。在这个 jar 没有给出 module-info 声明的情况下,JPMS 会采用这样的顺序决定 module 名字: diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java index 41b3477594..dd8a0fec5a 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java @@ -166,6 +166,13 @@ public void tooOldJava2() throws IOException { assertEquals("52", result.getMatcher().group("expected")); } + @Test + public void javaVersionIsTooHigh() throws IOException { + CrashReportAnalyzer.Result result = findResultByRule( + CrashReportAnalyzer.anaylze(loadLog("/logs/java_version_is_too_high.txt")), + CrashReportAnalyzer.Rule.JAVA_VERSION_IS_TOO_HIGH); + } + @Test public void securityException() throws IOException { CrashReportAnalyzer.Result result = findResultByRule( diff --git a/HMCLCore/src/test/resources/logs/java_version_is_too_high.txt b/HMCLCore/src/test/resources/logs/java_version_is_too_high.txt new file mode 100644 index 0000000000..452c1d4921 --- /dev/null +++ b/HMCLCore/src/test/resources/logs/java_version_is_too_high.txt @@ -0,0 +1,20 @@ +[authlib-injector] [INFO] Logging file: F:\.minecraft\authlib-injector.log +[authlib-injector] [INFO] Version: 1.2.1 +[authlib-injector] [INFO] Authentication server: https://littleskin.cn/api/yggdrasil/ +2022-12-08 13:15:41,589 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND +[13:15:43] [main/INFO]: ModLauncher running: args [--username, 哎呀呀呀, --version, 1.16.2, --gameDir, F:\\.minecraft, --assetsDir, F:\.minecraft\assets, --assetIndex, 1.16, --uuid, e3c2fb57f8764ecfa1564c1cc92143f2, --accessToken, ❄❄❄❄❄❄❄❄, --userType, mojang, --versionType, HMCL 3.5.3.228, --width, 854, --height, 480, --launchTarget, fmlclient, --fml.forgeVersion, 33.0.61, --fml.mcVersion, 1.16.2, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20200812.004259] +[13:15:43] [main/INFO]: ModLauncher 7.0.1+78+master.e9771d8 starting: java version 17.0.4.1 by Oracle Corporation +java.lang.NoSuchFieldException: ucp + at java.base/java.lang.Class.getDeclaredField(Class.java:2610) + at cpw.mods.gross.Java9ClassLoaderUtil.getSystemClassPathURLs(Java9ClassLoaderUtil.java:28) + at cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(TransformationServicesHandler.java:139) + at cpw.mods.modlauncher.Launcher.run(Launcher.java:74) + at cpw.mods.modlauncher.Launcher.main(Launcher.java:65) +Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "urls" is null + at java.base/jdk.internal.loader.URLClassPath.(URLClassPath.java:155) + at java.base/jdk.internal.loader.URLClassPath.(URLClassPath.java:176) + at java.base/java.net.URLClassLoader.(URLClassLoader.java:152) + at cpw.mods.modlauncher.TransformationServicesHandler$TransformerClassLoader.(TransformationServicesHandler.java:159) + at cpw.mods.modlauncher.TransformationServicesHandler.discoverServices(TransformationServicesHandler.java:139) + at cpw.mods.modlauncher.Launcher.run(Launcher.java:74) + at cpw.mods.modlauncher.Launcher.main(Launcher.java:65) \ No newline at end of file From 95707560ef7dc2cc255e85338faed5be591eff08 Mon Sep 17 00:00:00 2001 From: zkitefly Date: Tue, 13 Feb 2024 14:14:27 +0800 Subject: [PATCH 14/33] fix icon (#2791) --- .../main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java index 612f423b05..e411cc7f89 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/WorldListPage.java @@ -171,7 +171,7 @@ protected List initializeToolbar(WorldListPage skinnable) { return Arrays.asList(chkShowAll, createToolbarButton2(i18n("button.refresh"), SVG.REFRESH, skinnable::refresh), createToolbarButton2(i18n("world.add"), SVG.PLUS, skinnable::add), - createToolbarButton2(i18n("world.download"), SVG.PLUS, skinnable::download)); + createToolbarButton2(i18n("world.download"), SVG.DOWNLOAD_OUTLINE, skinnable::download)); } } } From 5d26a106e92e80452a6ea7574addc2ce56f54823 Mon Sep 17 00:00:00 2001 From: Glavo Date: Tue, 13 Feb 2024 15:29:54 +0800 Subject: [PATCH 15/33] =?UTF-8?q?=E4=BD=BF=E7=94=A8=20Instant=20=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=20Date=20(#2713)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update * update * update * update * update * Update * update * update * Add more tests * fix checkstyle --- .../org/jackhuang/hmcl/countly/Countly.java | 7 +- .../jackhuang/hmcl/countly/CrashReport.java | 6 +- .../hmcl/game/HMCLGameRepository.java | 3 +- .../hmcl/ui/download/VersionsPage.java | 2 +- .../org/jackhuang/hmcl/ui/main/RootPage.java | 6 +- .../jackhuang/hmcl/ui/main/SponsorPage.java | 10 +- .../hmcl/ui/versions/DownloadPage.java | 2 +- .../hmcl/download/RemoteVersion.java | 10 +- .../fabric/FabricAPIRemoteVersion.java | 4 +- .../download/forge/ForgeBMCLVersionList.java | 2 +- .../download/forge/ForgeRemoteVersion.java | 4 +- .../hmcl/download/game/GameRemoteVersion.java | 4 +- .../download/game/GameRemoteVersionInfo.java | 16 +-- .../download/quilt/QuiltAPIRemoteVersion.java | 4 +- .../jackhuang/hmcl/game/ClassicVersion.java | 4 +- .../java/org/jackhuang/hmcl/game/Version.java | 16 +-- .../hmcl/game/tlauncher/TLauncherVersion.java | 8 +- .../org/jackhuang/hmcl/mod/RemoteMod.java | 8 +- .../jackhuang/hmcl/mod/curse/CurseAddon.java | 29 ++--- .../modrinth/ModrinthRemoteModRepository.java | 27 ++--- .../java/org/jackhuang/hmcl/util/Logging.java | 45 ++++---- .../hmcl/util/gson/DateTypeAdapter.java | 106 ------------------ .../hmcl/util/gson/InstantTypeAdapter.java | 64 ++++++++--- .../jackhuang/hmcl/util/gson/JsonUtils.java | 2 - ...rTest.java => InstantTypeAdapterTest.java} | 19 +++- 25 files changed, 177 insertions(+), 231 deletions(-) delete mode 100644 HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/DateTypeAdapter.java rename HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/{DateTypeAdapterTest.java => InstantTypeAdapterTest.java} (52%) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/countly/Countly.java b/HMCL/src/main/java/org/jackhuang/hmcl/countly/Countly.java index f22dd1d0c1..f18b196859 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/countly/Countly.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/countly/Countly.java @@ -20,10 +20,9 @@ import org.jackhuang.hmcl.util.io.HttpRequest; import java.io.IOException; +import java.time.ZonedDateTime; import java.util.Calendar; -import java.util.Date; import java.util.Locale; -import java.util.TimeZone; import static org.jackhuang.hmcl.util.Pair.pair; @@ -40,7 +39,7 @@ public void sendMetric(String metrics) throws IOException { pair("metrics", metrics), pair("device_id", deviceId), pair("timestamp", Long.toString(System.currentTimeMillis())), - pair("tz", Integer.toString(TimeZone.getDefault().getOffset(new Date().getTime()) / 60000)), + pair("tz", Integer.toString(getTimezoneOffset())), pair("hour", Integer.toString(currentHour())), pair("dow", Integer.toString(currentDayOfWeek())), pair("app_key", APP_KEY), @@ -50,7 +49,7 @@ public void sendMetric(String metrics) throws IOException { } private static int getTimezoneOffset() { - return TimeZone.getDefault().getOffset(new Date().getTime()) / 60000; + return ZonedDateTime.now().getOffset().getTotalSeconds() / 60; } private static String getLocale() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/countly/CrashReport.java b/HMCL/src/main/java/org/jackhuang/hmcl/countly/CrashReport.java index 75f562a275..66668719b2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/countly/CrashReport.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/countly/CrashReport.java @@ -7,8 +7,8 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem; import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Map; import static org.jackhuang.hmcl.util.Lang.mapOf; @@ -68,7 +68,7 @@ public Map getMetrics(long runningTime) { public String getDisplayText() { return "---- Hello Minecraft! Crash Report ----\n" + " Version: " + Metadata.VERSION + "\n" + - " Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "\n" + + " Time: " + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now()) + "\n" + " Thread: " + thread + "\n" + "\n Content: \n " + stackTrace + "\n\n" + diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java index c0526e144f..fdc12283b6 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLGameRepository.java @@ -48,6 +48,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Instant; import java.util.*; import java.util.logging.Level; import java.util.stream.Collectors; @@ -103,7 +104,7 @@ public File getRunDirectory(String id) { public Stream getDisplayVersions() { return getVersions().stream() .filter(v -> !v.isHidden()) - .sorted(Comparator.comparing((Version v) -> v.getReleaseTime() == null ? new Date(0L) : v.getReleaseTime()) + .sorted(Comparator.comparing((Version v) -> Lang.requireNonNullElse(v.getReleaseTime(), Instant.EPOCH)) .thenComparing(v -> VersionNumber.asVersion(v.getId()))); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java index dc5703c5f5..c1aec271cb 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/download/VersionsPage.java @@ -298,7 +298,7 @@ public void updateItem(RemoteVersion remoteVersion, boolean empty) { content.setTitle(remoteVersion.getSelfVersion()); if (remoteVersion.getReleaseDate() != null) { - content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate().toInstant())); + content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate())); } else { content.setSubtitle(null); } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java index b5cee07e54..7191966f93 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/RootPage.java @@ -44,14 +44,15 @@ import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem; import org.jackhuang.hmcl.ui.versions.Versions; import org.jackhuang.hmcl.upgrade.UpdateChecker; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.TaskCancellationAction; import org.jackhuang.hmcl.util.io.CompressingUtils; import org.jackhuang.hmcl.util.versioning.VersionNumber; import java.io.File; +import java.time.Instant; import java.util.Comparator; -import java.util.Date; import java.util.List; import java.util.Locale; import java.util.logging.Level; @@ -118,8 +119,7 @@ public MainPage getMainPage() { List children = repository.getVersions().parallelStream() .filter(version -> !version.isHidden()) .sorted(Comparator - .comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) - : version.getReleaseTime()) + .comparing((Version version) -> Lang.requireNonNullElse(version.getReleaseTime(), Instant.EPOCH)) .thenComparing(version -> VersionNumber.asVersion(repository.getGameVersion(version).orElse(version.getId())))) .collect(Collectors.toList()); runInFX(() -> { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SponsorPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SponsorPage.java index 07716990bc..ea21757c69 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SponsorPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/SponsorPage.java @@ -34,7 +34,7 @@ import org.jackhuang.hmcl.util.io.HttpRequest; import java.math.BigDecimal; -import java.util.Date; +import java.time.Instant; import java.util.List; import static org.jackhuang.hmcl.util.i18n.I18n.i18n; @@ -128,7 +128,7 @@ private static class Sponsor { private final String name; @SerializedName("create_time") - private final Date createTime; + private final Instant createTime; @SerializedName("money") private final BigDecimal money; @@ -140,10 +140,10 @@ private static class Sponsor { private final String afdianId; public Sponsor() { - this("", new Date(), BigDecimal.ZERO, "", ""); + this("", Instant.now(), BigDecimal.ZERO, "", ""); } - public Sponsor(String name, Date createTime, BigDecimal money, String contact, String afdianId) { + public Sponsor(String name, Instant createTime, BigDecimal money, String contact, String afdianId) { this.name = name; this.createTime = createTime; this.money = money; @@ -155,7 +155,7 @@ public String getName() { return name; } - public Date getCreateTime() { + public Instant getCreateTime() { return createTime; } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java index 5ccc6b6665..db2ffd8f18 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/DownloadPage.java @@ -382,7 +382,7 @@ private static final class ModItem extends StackPane { TwoLineListItem content = new TwoLineListItem(); HBox.setHgrow(content, Priority.ALWAYS); content.setTitle(dataItem.getName()); - content.setSubtitle(FORMATTER.format(dataItem.getDatePublished().toInstant())); + content.setSubtitle(FORMATTER.format(dataItem.getDatePublished())); switch (dataItem.getVersionType()) { case Alpha: diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java index 9322a26af0..4bc1a90c04 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/RemoteVersion.java @@ -22,7 +22,7 @@ import org.jackhuang.hmcl.util.ToStringBuilder; import org.jackhuang.hmcl.util.versioning.VersionNumber; -import java.util.Date; +import java.time.Instant; import java.util.List; import java.util.Objects; @@ -36,7 +36,7 @@ public class RemoteVersion implements Comparable { private final String libraryId; private final String gameVersion; private final String selfVersion; - private final Date releaseDate; + private final Instant releaseDate; private final List urls; private final Type type; @@ -47,7 +47,7 @@ public class RemoteVersion implements Comparable { * @param selfVersion the version string of the remote version. * @param urls the installer or universal jar original URL. */ - public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Date releaseDate, List urls) { + public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, List urls) { this(libraryId, gameVersion, selfVersion, releaseDate, Type.UNCATEGORIZED, urls); } @@ -58,7 +58,7 @@ public RemoteVersion(String libraryId, String gameVersion, String selfVersion, D * @param selfVersion the version string of the remote version. * @param urls the installer or universal jar URL. */ - public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Date releaseDate, Type type, List urls) { + public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, Type type, List urls) { this.libraryId = Objects.requireNonNull(libraryId); this.gameVersion = Objects.requireNonNull(gameVersion); this.selfVersion = Objects.requireNonNull(selfVersion); @@ -83,7 +83,7 @@ public String getFullVersion() { return getSelfVersion(); } - public Date getReleaseDate() { + public Instant getReleaseDate() { return releaseDate; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIRemoteVersion.java index 4bc1303d19..e1522bc93d 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/fabric/FabricAPIRemoteVersion.java @@ -24,7 +24,7 @@ import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.task.Task; -import java.util.Date; +import java.time.Instant; import java.util.List; public class FabricAPIRemoteVersion extends RemoteVersion { @@ -38,7 +38,7 @@ public class FabricAPIRemoteVersion extends RemoteVersion { * @param selfVersion the version string of the remote version. * @param urls the installer or universal jar original URL. */ - FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Date datePublished, RemoteMod.Version version, List urls) { + FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List urls) { super(LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId(), gameVersion, selfVersion, datePublished, urls); this.fullVersion = fullVersion; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java index f71076f251..3ffd7d5ea3 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeBMCLVersionList.java @@ -109,7 +109,7 @@ public CompletableFuture refreshAsync(String gameVersion) { } versions.put(gameVersion, new ForgeRemoteVersion( - version.getGameVersion(), version.getVersion(), releaseDate == null ? null : Date.from(releaseDate), urls)); + version.getGameVersion(), version.getVersion(), releaseDate, urls)); } } finally { lock.writeLock().unlock(); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java index 3f3e9bd491..5550506fc6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/forge/ForgeRemoteVersion.java @@ -23,7 +23,7 @@ import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.task.Task; -import java.util.Date; +import java.time.Instant; import java.util.List; public class ForgeRemoteVersion extends RemoteVersion { @@ -34,7 +34,7 @@ public class ForgeRemoteVersion extends RemoteVersion { * @param selfVersion the version string of the remote version. * @param url the installer or universal jar original URL. */ - public ForgeRemoteVersion(String gameVersion, String selfVersion, Date releaseDate, List url) { + public ForgeRemoteVersion(String gameVersion, String selfVersion, Instant releaseDate, List url) { super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, releaseDate, url); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersion.java index 08cbd40c65..64b9a0e10e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersion.java @@ -25,7 +25,7 @@ import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.util.Immutable; -import java.util.Date; +import java.time.Instant; import java.util.List; /** @@ -37,7 +37,7 @@ public final class GameRemoteVersion extends RemoteVersion { private final ReleaseType type; - public GameRemoteVersion(String gameVersion, String selfVersion, List url, ReleaseType type, Date releaseDate) { + public GameRemoteVersion(String gameVersion, String selfVersion, List url, ReleaseType type, Instant releaseDate) { super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, releaseDate, getReleaseType(type), url); this.type = type; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersionInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersionInfo.java index ba47ffe213..430ddec370 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersionInfo.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/game/GameRemoteVersionInfo.java @@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.gson.Validation; -import java.util.Date; +import java.time.Instant; /** * @@ -36,10 +36,10 @@ public final class GameRemoteVersionInfo implements Validation { private final String gameVersion; @SerializedName("time") - private final Date time; + private final Instant time; @SerializedName("releaseTime") - private final Date releaseTime; + private final Instant releaseTime; @SerializedName("type") private final ReleaseType type; @@ -48,14 +48,14 @@ public final class GameRemoteVersionInfo implements Validation { private final String url; public GameRemoteVersionInfo() { - this("", new Date(), new Date(), ReleaseType.UNKNOWN); + this("", Instant.now(), Instant.now(), ReleaseType.UNKNOWN); } - public GameRemoteVersionInfo(String gameVersion, Date time, Date releaseTime, ReleaseType type) { + public GameRemoteVersionInfo(String gameVersion, Instant time, Instant releaseTime, ReleaseType type) { this(gameVersion, time, releaseTime, type, Constants.DEFAULT_LIBRARY_URL + gameVersion + "/" + gameVersion + ".json"); } - public GameRemoteVersionInfo(String gameVersion, Date time, Date releaseTime, ReleaseType type, String url) { + public GameRemoteVersionInfo(String gameVersion, Instant time, Instant releaseTime, ReleaseType type, String url) { this.gameVersion = gameVersion; this.time = time; this.releaseTime = releaseTime; @@ -67,11 +67,11 @@ public String getGameVersion() { return gameVersion; } - public Date getTime() { + public Instant getTime() { return time; } - public Date getReleaseTime() { + public Instant getReleaseTime() { return releaseTime; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIRemoteVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIRemoteVersion.java index 4399c71bea..c67572f39e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIRemoteVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/download/quilt/QuiltAPIRemoteVersion.java @@ -24,7 +24,7 @@ import org.jackhuang.hmcl.mod.RemoteMod; import org.jackhuang.hmcl.task.Task; -import java.util.Date; +import java.time.Instant; import java.util.List; public class QuiltAPIRemoteVersion extends RemoteVersion { @@ -38,7 +38,7 @@ public class QuiltAPIRemoteVersion extends RemoteVersion { * @param selfVersion the version string of the remote version. * @param urls the installer or universal jar original URL. */ - QuiltAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Date datePublished, RemoteMod.Version version, List urls) { + QuiltAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List urls) { super(LibraryAnalyzer.LibraryType.QUILT_API.getPatchId(), gameVersion, selfVersion, datePublished, urls); this.fullVersion = fullVersion; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java index f283eeaa79..42c8683a5f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/ClassicVersion.java @@ -18,8 +18,8 @@ package org.jackhuang.hmcl.game; import java.io.File; +import java.time.Instant; import java.util.Arrays; -import java.util.Date; /** * The Minecraft version for 1.5.x and earlier. @@ -32,7 +32,7 @@ public ClassicVersion() { super(true, "Classic", null, null, "${auth_player_name} ${auth_session} --workDir ${game_directory}", null, "net.minecraft.client.Minecraft", null, null, null, null, null, null, Arrays.asList(new ClassicLibrary("lwjgl"), new ClassicLibrary("jinput"), new ClassicLibrary("lwjgl_util")), - null, null, null, ReleaseType.UNKNOWN, new Date(), new Date(), 0, false, false, null); + null, null, null, ReleaseType.UNKNOWN, Instant.now(), Instant.now(), 0, false, false, null); } private static class ClassicLibrary extends Library { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java index 04f693f499..9948861175 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/Version.java @@ -28,10 +28,10 @@ import org.jackhuang.hmcl.util.gson.Validation; import org.jetbrains.annotations.Nullable; +import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -66,8 +66,8 @@ public class Version implements Comparable, Validation { private final JsonMap downloads; private final JsonMap logging; private final ReleaseType type; - private final Date time; - private final Date releaseTime; + private final Instant time; + private final Instant releaseTime; private final Integer minimumLauncherVersion; private final Boolean root; private final Boolean hidden; @@ -93,7 +93,7 @@ public Version(String id, String version, int priority, Arguments arguments, Str this(false, id, version, priority, null, arguments, mainClass, null, null, null, null, null, null, libraries, null, null, null, null, null, null, null, null, null, null); } - public Version(boolean resolved, String id, String version, Integer priority, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, Integer complianceLevel, GameJavaVersion javaVersion, List libraries, List compatibilityRules, Map downloads, Map logging, ReleaseType type, Date time, Date releaseTime, Integer minimumLauncherVersion, Boolean hidden, Boolean root, List patches) { + public Version(boolean resolved, String id, String version, Integer priority, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, Integer complianceLevel, GameJavaVersion javaVersion, List libraries, List compatibilityRules, Map downloads, Map logging, ReleaseType type, Instant time, Instant releaseTime, Integer minimumLauncherVersion, Boolean hidden, Boolean root, List patches) { this.resolved = resolved; this.id = id; this.version = version; @@ -112,8 +112,8 @@ public Version(boolean resolved, String id, String version, Integer priority, St this.downloads = downloads == null ? null : new JsonMap<>(downloads); this.logging = logging == null ? null : new JsonMap<>(logging); this.type = type; - this.time = time == null ? null : (Date) time.clone(); - this.releaseTime = releaseTime == null ? null : (Date) releaseTime.clone(); + this.time = time; + this.releaseTime = releaseTime; this.minimumLauncherVersion = minimumLauncherVersion; this.hidden = hidden; this.root = root; @@ -132,7 +132,7 @@ public String getMainClass() { return mainClass; } - public Date getTime() { + public Instant getTime() { return time; } @@ -158,7 +158,7 @@ public ReleaseType getType() { return type == null ? ReleaseType.UNKNOWN : type; } - public Date getReleaseTime() { + public Instant getReleaseTime() { return releaseTime; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherVersion.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherVersion.java index 36ed576600..98d7261621 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherVersion.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/tlauncher/TLauncherVersion.java @@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.gson.Validation; import org.jetbrains.annotations.Nullable; -import java.util.Date; +import java.time.Instant; import java.util.List; import java.util.stream.Collectors; @@ -46,12 +46,12 @@ public class TLauncherVersion implements Validation { private final JsonMap downloads; private final JsonMap logging; private final ReleaseType type; - private final Date time; - private final Date releaseTime; + private final Instant time; + private final Instant releaseTime; private final Integer minimumLauncherVersion; private final Integer tlauncherVersion; - public TLauncherVersion(String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, Integer complianceLevel, @Nullable GameJavaVersion javaVersion, List libraries, List compatibilityRules, JsonMap downloads, JsonMap logging, ReleaseType type, Date time, Date releaseTime, Integer minimumLauncherVersion, Integer tlauncherVersion) { + public TLauncherVersion(String id, String minecraftArguments, Arguments arguments, String mainClass, String inheritsFrom, String jar, AssetIndexInfo assetIndex, String assets, Integer complianceLevel, @Nullable GameJavaVersion javaVersion, List libraries, List compatibilityRules, JsonMap downloads, JsonMap logging, ReleaseType type, Instant time, Instant releaseTime, Integer minimumLauncherVersion, Integer tlauncherVersion) { this.id = id; this.minecraftArguments = minecraftArguments; this.arguments = arguments; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java index 95258f7c55..23365637c5 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/RemoteMod.java @@ -22,7 +22,7 @@ import org.jackhuang.hmcl.task.FileDownloadTask; import java.io.IOException; -import java.util.Date; +import java.time.Instant; import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -218,14 +218,14 @@ public static class Version { private final String name; private final String version; private final String changelog; - private final Date datePublished; + private final Instant datePublished; private final VersionType versionType; private final File file; private final List dependencies; private final List gameVersions; private final List loaders; - public Version(IVersion self, String modid, String name, String version, String changelog, Date datePublished, VersionType versionType, File file, List dependencies, List gameVersions, List loaders) { + public Version(IVersion self, String modid, String name, String version, String changelog, Instant datePublished, VersionType versionType, File file, List dependencies, List gameVersions, List loaders) { this.self = self; this.modid = modid; this.name = name; @@ -259,7 +259,7 @@ public String getChangelog() { return changelog; } - public Date getDatePublished() { + public Instant getDatePublished() { return datePublished; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java index fa474943a9..e77fb259e1 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseAddon.java @@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -58,15 +59,15 @@ public class CurseAddon implements RemoteMod.IMod { private final int mainFileId; private final List latestFiles; private final List latestFileIndices; - private final Date dateCreated; - private final Date dateModified; - private final Date dateReleased; + private final Instant dateCreated; + private final Instant dateModified; + private final Instant dateReleased; private final boolean allowModDistribution; private final int gamePopularityRank; private final boolean isAvailable; private final int thumbsUpCount; - public CurseAddon(int id, int gameId, String name, String slug, Links links, String summary, int status, int downloadCount, boolean isFeatured, int primaryCategoryId, List categories, int classId, List authors, Logo logo, int mainFileId, List latestFiles, List latestFileIndices, Date dateCreated, Date dateModified, Date dateReleased, boolean allowModDistribution, int gamePopularityRank, boolean isAvailable, int thumbsUpCount) { + public CurseAddon(int id, int gameId, String name, String slug, Links links, String summary, int status, int downloadCount, boolean isFeatured, int primaryCategoryId, List categories, int classId, List authors, Logo logo, int mainFileId, List latestFiles, List latestFileIndices, Instant dateCreated, Instant dateModified, Instant dateReleased, boolean allowModDistribution, int gamePopularityRank, boolean isAvailable, int thumbsUpCount) { this.id = id; this.gameId = gameId; this.name = name; @@ -161,15 +162,15 @@ public List getLatestFileIndices() { return latestFileIndices; } - public Date getDateCreated() { + public Instant getDateCreated() { return dateCreated; } - public Date getDateModified() { + public Instant getDateModified() { return dateModified; } - public Date getDateReleased() { + public Instant getDateReleased() { return dateReleased; } @@ -438,7 +439,7 @@ public static class LatestFile implements RemoteMod.IVersion { private final int releaseType; private final int fileStatus; private final List hashes; - private final Date fileDate; + private final Instant fileDate; private final int fileLength; private final int downloadCount; private final String downloadUrl; @@ -448,7 +449,7 @@ public static class LatestFile implements RemoteMod.IVersion { private final boolean isServerPack; private final long fileFingerprint; - public LatestFile(int id, int gameId, int modId, boolean isAvailable, String displayName, String fileName, int releaseType, int fileStatus, List hashes, Date fileDate, int fileLength, int downloadCount, String downloadUrl, List gameVersions, List dependencies, int alternateFileId, boolean isServerPack, long fileFingerprint) { + public LatestFile(int id, int gameId, int modId, boolean isAvailable, String displayName, String fileName, int releaseType, int fileStatus, List hashes, Instant fileDate, int fileLength, int downloadCount, String downloadUrl, List gameVersions, List dependencies, int alternateFileId, boolean isServerPack, long fileFingerprint) { this.id = id; this.gameId = gameId; this.modId = modId; @@ -505,7 +506,7 @@ public List getHashes() { return hashes; } - public Date getFileDate() { + public Instant getFileDate() { return fileDate; } @@ -650,7 +651,7 @@ public static class Category { private final String slug; private final String url; private final String iconUrl; - private final Date dateModified; + private final Instant dateModified; private final boolean isClass; private final int classId; private final int parentCategoryId; @@ -658,10 +659,10 @@ public static class Category { private transient final List subcategories; public Category() { - this(0, 0, "", "", "", "", new Date(), false, 0, 0); + this(0, 0, "", "", "", "", Instant.now(), false, 0, 0); } - public Category(int id, int gameId, String name, String slug, String url, String iconUrl, Date dateModified, boolean isClass, int classId, int parentCategoryId) { + public Category(int id, int gameId, String name, String slug, String url, String iconUrl, Instant dateModified, boolean isClass, int classId, int parentCategoryId) { this.id = id; this.gameId = gameId; this.name = name; @@ -700,7 +701,7 @@ public String getIconUrl() { return iconUrl; } - public Date getDateModified() { + public Instant getDateModified() { return dateModified; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java index e0d9cc9a56..4cc9e06329 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modrinth/ModrinthRemoteModRepository.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -210,13 +211,13 @@ public static class Project implements RemoteMod.IMod { private final String team; - private final Date published; + private final Instant published; - private final Date updated; + private final Instant updated; private final List versions; - public Project(String slug, String title, String description, List categories, String body, String projectType, int downloads, String iconUrl, String id, String team, Date published, Date updated, List versions) { + public Project(String slug, String title, String description, List categories, String body, String projectType, int downloads, String iconUrl, String id, String team, Instant published, Instant updated, List versions) { this.slug = slug; this.title = title; this.description = description; @@ -272,11 +273,11 @@ public String getTeam() { return team; } - public Date getPublished() { + public Instant getPublished() { return published; } - public Date getUpdated() { + public Instant getUpdated() { return updated; } @@ -381,7 +382,7 @@ public static class ProjectVersion implements RemoteMod.IVersion { private final String authorId; @SerializedName("date_published") - private final Date datePublished; + private final Instant datePublished; private final int downloads; @@ -390,7 +391,7 @@ public static class ProjectVersion implements RemoteMod.IVersion { private final List files; - public ProjectVersion(String name, String versionNumber, String changelog, List dependencies, List gameVersions, String versionType, List loaders, boolean featured, String id, String projectId, String authorId, Date datePublished, int downloads, String changelogUrl, List files) { + public ProjectVersion(String name, String versionNumber, String changelog, List dependencies, List gameVersions, String versionType, List loaders, boolean featured, String id, String projectId, String authorId, Instant datePublished, int downloads, String changelogUrl, List files) { this.name = name; this.versionNumber = versionNumber; this.changelog = changelog; @@ -452,7 +453,7 @@ public String getAuthorId() { return authorId; } - public Date getDatePublished() { + public Instant getDatePublished() { return datePublished; } @@ -587,15 +588,15 @@ public static class ProjectSearchResult implements RemoteMod.IMod { private final List versions; @SerializedName("date_created") - private final Date dateCreated; + private final Instant dateCreated; @SerializedName("date_modified") - private final Date dateModified; + private final Instant dateModified; @SerializedName("latest_version") private final String latestVersion; - public ProjectSearchResult(String slug, String title, String description, List categories, String projectType, int downloads, String iconUrl, String projectId, String author, List versions, Date dateCreated, Date dateModified, String latestVersion) { + public ProjectSearchResult(String slug, String title, String description, List categories, String projectType, int downloads, String iconUrl, String projectId, String author, List versions, Instant dateCreated, Instant dateModified, String latestVersion) { this.slug = slug; this.title = title; this.description = description; @@ -651,11 +652,11 @@ public List getVersions() { return versions; } - public Date getDateCreated() { + public Instant getDateCreated() { return dateCreated; } - public Date getDateModified() { + public Instant getDateModified() { return dateModified; } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Logging.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Logging.java index a34625b705..48f58f6fcd 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Logging.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Logging.java @@ -22,9 +22,10 @@ import java.io.*; import java.nio.file.Files; import java.nio.file.Path; -import java.text.MessageFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Arrays; -import java.util.Date; import java.util.logging.*; /** @@ -120,35 +121,39 @@ public static String getLogs() { } } - private static final MessageFormat FORMAT = new MessageFormat("[{0,date,HH:mm:ss}] [{1}.{2}/{3}] {4}\n"); + private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.systemDefault()); private static String format(LogRecord record) { String message = filterForbiddenToken(record.getMessage()); - Throwable thrown = record.getThrown(); + StringBuilder builder = new StringBuilder(128 + message.length()); + builder.append('['); + TIME_FORMATTER.formatTo(Instant.ofEpochMilli(record.getMillis()), builder); + builder.append(']'); - StringWriter writer; - StringBuffer buffer; - if (thrown == null) { - writer = null; - buffer = new StringBuffer(256); - } else { - writer = new StringWriter(1024); - buffer = writer.getBuffer(); - } + builder.append(" [") + .append(record.getSourceClassName()) + .append('.') + .append(record.getSourceMethodName()) + .append('/') + .append(record.getLevel().getName()) + .append("] ") + .append(message) + .append('\n'); - FORMAT.format(new Object[]{ - new Date(record.getMillis()), - record.getSourceClassName(), record.getSourceMethodName(), record.getLevel().getName(), - message - }, buffer, null); - if (thrown != null) { + Throwable thrown = record.getThrown(); + if (thrown == null) { + return builder.toString(); + } else { + StringWriter writer = new StringWriter(builder.length() + 2048); + writer.getBuffer().append(builder); try (PrintWriter printWriter = new PrintWriter(writer)) { thrown.printStackTrace(printWriter); } + + return writer.toString(); } - return buffer.toString(); } private static final class DefaultFormatter extends Formatter { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/DateTypeAdapter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/DateTypeAdapter.java deleted file mode 100644 index 6d1d007b71..0000000000 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/DateTypeAdapter.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Hello Minecraft! Launcher - * Copyright (C) 2020 huangyuhui and contributors - * - * 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 . - */ -package org.jackhuang.hmcl.util.gson; - -import com.google.gson.*; - -import java.lang.reflect.Type; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeFormatterBuilder; -import java.time.format.DateTimeParseException; -import java.util.Date; -import java.util.Locale; - -import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; - -/** - * - * @author huangyuhui - */ -public final class DateTypeAdapter implements JsonSerializer, JsonDeserializer { - - public static final DateTypeAdapter INSTANCE = new DateTypeAdapter(); - - private DateTypeAdapter() { - } - - @Override - public JsonElement serialize(Date t, Type type, JsonSerializationContext jsc) { - synchronized (EN_US_FORMAT) { - return new JsonPrimitive(serializeToString(t)); - } - } - - @Override - public Date deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { - if (!(json instanceof JsonPrimitive)) - throw new JsonParseException("The date should be a string value"); - else { - Date date = deserializeToDate(json.getAsString()); - if (type == Date.class) - return date; - else - throw new IllegalArgumentException(this.getClass().toString() + " cannot be deserialized to " + type); - } - } - - public static final DateFormat EN_US_FORMAT = DateFormat.getDateTimeInstance(2, 2, Locale.US); - public static final DateFormat ISO_8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); - - public static final DateTimeFormatter ISO_DATE_TIME = new DateTimeFormatterBuilder() - .append(ISO_LOCAL_DATE_TIME) - .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd() - .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd() - .optionalStart().appendOffset("+HH", "Z").optionalEnd() - .optionalStart().appendOffsetId().optionalEnd() - .toFormatter(); - - public static Date deserializeToDate(String string) { - synchronized (EN_US_FORMAT) { - try { - return EN_US_FORMAT.parse(string); - } catch (ParseException ex1) { - try { - ZonedDateTime zonedDateTime = ZonedDateTime.parse(string, ISO_DATE_TIME); - return Date.from(zonedDateTime.toInstant()); - } catch (DateTimeParseException e) { - try { - LocalDateTime localDateTime = LocalDateTime.parse(string, ISO_LOCAL_DATE_TIME); - return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); - } catch (DateTimeParseException e2) { - throw new JsonParseException("Invalid date: " + string, e); - } - } - } - } - } - - public static String serializeToString(Date date) { - synchronized (EN_US_FORMAT) { - String result = ISO_8601_FORMAT.format(date); - return result.substring(0, 22) + ":" + result.substring(22); - } - } - -} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapter.java index 89468f3479..82af720d97 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapter.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapter.java @@ -20,9 +20,15 @@ import com.google.gson.*; import java.lang.reflect.Type; -import java.time.Instant; -import java.time.ZoneId; +import java.time.*; import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.format.FormatStyle; +import java.time.temporal.ChronoUnit; +import java.util.Locale; + +import static java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME; public final class InstantTypeAdapter implements JsonSerializer, JsonDeserializer { public static final InstantTypeAdapter INSTANCE = new InstantTypeAdapter(); @@ -31,21 +37,53 @@ private InstantTypeAdapter() { } @Override - public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - if (!(json instanceof JsonPrimitive)) { + public JsonElement serialize(Instant t, Type type, JsonSerializationContext jsc) { + return new JsonPrimitive(serializeToString(t, ZoneId.systemDefault())); + } + + @Override + public Instant deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + if (!(json instanceof JsonPrimitive)) throw new JsonParseException("The instant should be a string value"); - } else { - Instant instant = Instant.parse(json.getAsString()); - if (typeOfT == Instant.class) { - return instant; - } else { - throw new IllegalArgumentException(this.getClass() + " cannot be deserialized to " + typeOfT); + else { + Instant time = deserializeToInstant(json.getAsString()); + if (type == Instant.class) + return time; + else + throw new IllegalArgumentException(this.getClass() + " cannot be deserialized to " + type); + } + } + + private static final DateTimeFormatter EN_US_FORMAT = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.MEDIUM) + .withLocale(Locale.US) + .withZone(ZoneId.systemDefault()); + private static final DateTimeFormatter ISO_DATE_TIME = new DateTimeFormatterBuilder() + .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .optionalStart().appendOffset("+HH:MM", "+00:00").optionalEnd() + .optionalStart().appendOffset("+HHMM", "+0000").optionalEnd() + .optionalStart().appendOffset("+HH", "Z").optionalEnd() + .optionalStart().appendOffsetId().optionalEnd() + .toFormatter(); + + public static Instant deserializeToInstant(String string) { + try { + return ZonedDateTime.parse(string, EN_US_FORMAT).toInstant(); + } catch (DateTimeParseException ex1) { + try { + ZonedDateTime zonedDateTime = ZonedDateTime.parse(string, ISO_DATE_TIME); + return zonedDateTime.toInstant(); + } catch (DateTimeParseException e) { + try { + LocalDateTime localDateTime = LocalDateTime.parse(string, ISO_LOCAL_DATE_TIME); + return localDateTime.atZone(ZoneId.systemDefault()).toInstant(); + } catch (DateTimeParseException e2) { + throw new JsonParseException("Invalid instant: " + string, e); + } } } } - @Override - public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) { - return new JsonPrimitive(DateTimeFormatter.ISO_DATE_TIME.withZone(ZoneId.systemDefault()).format(src)); + public static String serializeToString(Instant instant, ZoneId zone) { + return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.ofInstant(instant, zone).truncatedTo(ChronoUnit.SECONDS)); } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java index fec40f665f..cbd5f88e25 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/gson/JsonUtils.java @@ -29,7 +29,6 @@ import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.time.Instant; -import java.util.Date; import java.util.UUID; /** @@ -113,7 +112,6 @@ public static GsonBuilder defaultGsonBuilder() { .enableComplexMapKeySerialization() .setPrettyPrinting() .registerTypeAdapter(Instant.class, InstantTypeAdapter.INSTANCE) - .registerTypeAdapter(Date.class, DateTypeAdapter.INSTANCE) .registerTypeAdapter(UUID.class, UUIDTypeAdapter.INSTANCE) .registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE) .registerTypeAdapterFactory(ValidationTypeAdapterFactory.INSTANCE) diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/DateTypeAdapterTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapterTest.java similarity index 52% rename from HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/DateTypeAdapterTest.java rename to HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapterTest.java index 66f34c4064..649afa883e 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/DateTypeAdapterTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/util/gson/InstantTypeAdapterTest.java @@ -2,29 +2,38 @@ import org.junit.jupiter.api.Test; +import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.ZoneOffset; import static org.junit.jupiter.api.Assertions.*; -public class DateTypeAdapterTest { +public class InstantTypeAdapterTest { @Test - public void parse() { + public void testDeserialize() { assertEquals( LocalDateTime.of(2017, 6, 8, 4, 26, 33) .atOffset(ZoneOffset.UTC).toInstant(), - DateTypeAdapter.deserializeToDate("2017-06-08T04:26:33+0000").toInstant()); + InstantTypeAdapter.deserializeToInstant("2017-06-08T04:26:33+0000")); assertEquals( LocalDateTime.of(2021, 1, 3, 0, 53, 34) .atOffset(ZoneOffset.UTC).toInstant(), - DateTypeAdapter.deserializeToDate("2021-01-03T00:53:34+00:00").toInstant()); + InstantTypeAdapter.deserializeToInstant("2021-01-03T00:53:34+00:00")); assertEquals( LocalDateTime.of(2021, 1, 3, 0, 53, 34) .atZone(ZoneId.systemDefault()).toInstant(), - DateTypeAdapter.deserializeToDate("2021-01-03T00:53:34").toInstant()); + InstantTypeAdapter.deserializeToInstant("2021-01-03T00:53:34")); + } + + @Test + public void testSerialize() { + assertEquals( + "2024-02-13T15:11:06+08:00", + InstantTypeAdapter.serializeToString(Instant.ofEpochMilli(1707808266154L), ZoneOffset.ofHours(8)) + ); } } From 95ecd290185ea09d7578e25c750733a1f5da2731 Mon Sep 17 00:00:00 2001 From: Glavo Date: Wed, 14 Feb 2024 13:42:23 +0800 Subject: [PATCH 16/33] Bump JUnit to 5.10.2 (#2803) --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 50a4f2c000..deccd9e5c6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -35,7 +35,7 @@ subprojects { } dependencies { - "testImplementation"("org.junit.jupiter:junit-jupiter:5.9.1") + "testImplementation"("org.junit.jupiter:junit-jupiter:5.10.2") } tasks.withType { From 4d6cb547ea23a552e51f31d17c4e8720d9d1c62c Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Thu, 15 Feb 2024 18:56:46 +0800 Subject: [PATCH 17/33] =?UTF-8?q?Fix=20#2809=20=E6=9C=AC=E5=9C=B0=E6=A8=A1?= =?UTF-8?q?=E7=BB=84=E5=88=97=E8=A1=A8=E6=90=9C=E7=B4=A2=20(#2810)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java index 72c3a7bda9..138472c399 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/ModListPageSkin.java @@ -223,7 +223,7 @@ private void search() { } } else { String lowerQueryString = queryString.toLowerCase(Locale.ROOT); - predicate = s -> s.contains(lowerQueryString); + predicate = s -> s.toLowerCase(Locale.ROOT).contains(lowerQueryString); } // Do we need to search in the background thread? From 56c48e559523e5718e52f013b3429aa5ae7febbb Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Thu, 15 Feb 2024 19:56:50 +0800 Subject: [PATCH 18/33] Fix: English I18N for fatal.javafx.missing is incorrect. (#2811) --- HMCL/src/main/resources/assets/lang/I18N.properties | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index 57e217dcaa..e13db11ce0 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -356,9 +356,7 @@ HMCL cannot automatically install JavaFX under Java versions below 11.\n\ Please update your Java to version 11 or higher. fatal.javafx.incomplete=The JavaFX environment is incomplete.\n\ Please try replacing your Java or reinstalling OpenJFX. -fatal.javafx.missing=Missing JavaFX environment.\n\ -If you are using Java 11 or higher, please downgrade it to Oracle JRE 8 (java.com), or install BellSoft Liberica Full JRE (bell-sw.com/pages/downloads/?package\=jre-full).\n\ -Or, if you are using OpenJDK distributions, please make sure it has OpenJFX included. +fatal.javafx.missing=Missing JavaFX environment. Please launch Hello Minecraft! Launcher with a Java which includes OpenJFX. fatal.config_change_owner_root=You are using the root account to start Hello Minecraft! Launcher, this may cause you to fail to start Hello Minecraft! Launcher with other account in the future.\n\ Do you still want to continue? fatal.config_in_temp_dir=You are start Hello Minecraft! Launcher in a temporary directory, your settings and game data may be lost.\n\ From b09a5868d49cf266de413fbc093cdc55a221d0a0 Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Fri, 16 Feb 2024 17:53:33 +0800 Subject: [PATCH 19/33] Fix: Raw use of parameterized class 'VersionRange'. (#2815) --- .../java/org/jackhuang/hmcl/game/JavaVersionConstraint.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java index 754b99312e..6d28371c8e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/JavaVersionConstraint.java @@ -93,7 +93,7 @@ protected boolean appliesToVersionImpl(GameVersionNumber gameVersionNumber, @Nul } @Override - public VersionRange getJavaVersionRange(Version version) { + public VersionRange getJavaVersionRange(Version version) { String javaVersion; if (Objects.requireNonNull(version.getJavaVersion()).getMajorVersion() >= 9) { javaVersion = "" + version.getJavaVersion().getMajorVersion(); From 9361719a6762ff44e2b3d86ef2fe0f07e52d3394 Mon Sep 17 00:00:00 2001 From: yuhuihuang Date: Mon, 19 Feb 2024 14:48:44 +0800 Subject: [PATCH 20/33] fix: remove mcbbs download source. --- .../hmcl/setting/DownloadProviders.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java index de4975fa8d..4e9a970da9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/DownloadProviders.java @@ -55,10 +55,10 @@ private DownloadProviders() {} private static final MojangDownloadProvider MOJANG; private static final BMCLAPIDownloadProvider BMCLAPI; - private static final BMCLAPIDownloadProvider MCBBS; + // private static final BMCLAPIDownloadProvider MCBBS; public static final String DEFAULT_PROVIDER_ID = "balanced"; - public static final String DEFAULT_RAW_PROVIDER_ID = "mcbbs"; + public static final String DEFAULT_RAW_PROVIDER_ID = "bmclapi"; private static final InvalidationListener observer; @@ -69,21 +69,27 @@ private DownloadProviders() {} MOJANG = new MojangDownloadProvider(); BMCLAPI = new BMCLAPIDownloadProvider(bmclapiRoot); - MCBBS = new BMCLAPIDownloadProvider("https://download.mcbbs.net"); + // MCBBS = new BMCLAPIDownloadProvider("https://download.mcbbs.net"); rawProviders = mapOf( pair("mojang", MOJANG), - pair("bmclapi", BMCLAPI), - pair("mcbbs", MCBBS) + pair("bmclapi", BMCLAPI) + // pair("mcbbs", MCBBS) ); AdaptedDownloadProvider fileProvider = new AdaptedDownloadProvider(); - fileProvider.setDownloadProviderCandidates(Arrays.asList(MCBBS, BMCLAPI, MOJANG)); - BalancedDownloadProvider balanced = new BalancedDownloadProvider(MOJANG, MCBBS, BMCLAPI); + fileProvider.setDownloadProviderCandidates(Arrays.asList( + // MCBBS, + BMCLAPI, + MOJANG)); + BalancedDownloadProvider balanced = new BalancedDownloadProvider( + MOJANG, + // MCBBS, + BMCLAPI); providersById = mapOf( pair("official", new AutoDownloadProvider(MOJANG, fileProvider)), pair("balanced", new AutoDownloadProvider(balanced, fileProvider)), - pair("mirror", new AutoDownloadProvider(MCBBS, fileProvider))); + pair("mirror", new AutoDownloadProvider(BMCLAPI /* MCBBS */, fileProvider))); observer = FXUtils.observeWeak(() -> { FetchTask.setDownloadExecutorConcurrency( From 420d77de9f9b28615cf7b9c86fbe8af8e628d3a1 Mon Sep 17 00:00:00 2001 From: huangyuhui Date: Mon, 19 Feb 2024 15:21:10 +0800 Subject: [PATCH 21/33] fix: remove thanks to mcbbs. --- .../java/org/jackhuang/hmcl/ui/main/AboutPage.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java index a3f295d00d..da84c3b6bc 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java @@ -85,11 +85,11 @@ public AboutPage() { redLnn.setImage(FXUtils.newBuiltinImage("/assets/img/red_lnn.png")); redLnn.setSubtitle(i18n("about.thanks_to.red_lnn.statement")); - IconedTwoLineListItem mcbbs = new IconedTwoLineListItem(); - mcbbs.setImage(FXUtils.newBuiltinImage("/assets/img/chest.png")); - mcbbs.setTitle(i18n("about.thanks_to.mcbbs")); - mcbbs.setSubtitle(i18n("about.thanks_to.mcbbs.statement")); - mcbbs.setExternalLink("https://www.mcbbs.net/"); + // IconedTwoLineListItem mcbbs = new IconedTwoLineListItem(); + // mcbbs.setImage(FXUtils.newBuiltinImage("/assets/img/chest.png")); + // mcbbs.setTitle(i18n("about.thanks_to.mcbbs")); + // mcbbs.setSubtitle(i18n("about.thanks_to.mcbbs.statement")); + // mcbbs.setExternalLink("https://www.mcbbs.net/"); IconedTwoLineListItem mcmod = new IconedTwoLineListItem(); mcmod.setImage(FXUtils.newBuiltinImage("/assets/img/mcmod.png")); @@ -109,7 +109,7 @@ public AboutPage() { users.setSubtitle(i18n("about.thanks_to.users.statement")); users.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/sponsor"); - thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zkitefly ,mcbbs, mcmod, gamerteam, redLnn, contributors, users); + thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zkitefly, /* mcbbs, */ mcmod, gamerteam, redLnn, contributors, users); } ComponentList dep = new ComponentList(); From 0e9e9811eb6ae1e2e2df17b839734c2e0dc5f3dd Mon Sep 17 00:00:00 2001 From: Glavo Date: Mon, 19 Feb 2024 15:40:23 +0800 Subject: [PATCH 22/33] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=85=B3=E4=BA=8E?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E5=9B=BE=E6=A0=87=20(#2835)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java index da84c3b6bc..f33095ea20 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/main/AboutPage.java @@ -34,7 +34,7 @@ public AboutPage() { ComponentList about = new ComponentList(); { IconedTwoLineListItem launcher = new IconedTwoLineListItem(); - launcher.setImage(FXUtils.newBuiltinImage("/assets/img/craft_table.png")); + launcher.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png")); launcher.setTitle("Hello Minecraft! Launcher"); launcher.setSubtitle(Metadata.VERSION); launcher.setExternalLink("https://hmcl.huangyuhui.net"); @@ -104,7 +104,7 @@ public AboutPage() { contributors.setExternalLink("https://github.com/HMCL-dev/HMCL/graphs/contributors"); IconedTwoLineListItem users = new IconedTwoLineListItem(); - users.setImage(FXUtils.newBuiltinImage("/assets/img/craft_table.png")); + users.setImage(FXUtils.newBuiltinImage("/assets/img/icon.png")); users.setTitle(i18n("about.thanks_to.users")); users.setSubtitle(i18n("about.thanks_to.users.statement")); users.setExternalLink("https://hmcl.huangyuhui.net/api/redirect/sponsor"); From 83e8ea3735e05b8c54d8404ffc0d820e732f0adc Mon Sep 17 00:00:00 2001 From: zkitefly Date: Mon, 19 Feb 2024 15:41:28 +0800 Subject: [PATCH 23/33] =?UTF-8?q?=E6=8A=8A=E2=80=9C=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E5=B9=B6=E5=8F=91=E6=95=B0=E2=80=9D=E9=80=89?= =?UTF-8?q?=E9=A1=B9=E9=BB=98=E8=AE=A4=E5=BC=80=E5=90=AF=20(#2805)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 430d2f1ea1..6ab9febd41 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -131,7 +131,7 @@ public static Config fromJson(String json) throws JsonParseException { private ObjectProperty localization = new SimpleObjectProperty<>(Locales.DEFAULT); @SerializedName("autoDownloadThreads") - private BooleanProperty autoDownloadThreads = new SimpleBooleanProperty(false); + private BooleanProperty autoDownloadThreads = new SimpleBooleanProperty(true); @SerializedName("downloadThreads") private IntegerProperty downloadThreads = new SimpleIntegerProperty(64); From f354f65adbc76fc7a8093d080f5063d2883c3b8d Mon Sep 17 00:00:00 2001 From: zkitefly Date: Mon, 19 Feb 2024 15:46:57 +0800 Subject: [PATCH 24/33] add-forge-found-duplicate-mods (#2828) * add * Update CrashReportAnalyzer.java --------- Co-authored-by: Glavo --- .../resources/assets/lang/I18N.properties | 1 + .../resources/assets/lang/I18N_es.properties | 1 + .../resources/assets/lang/I18N_ja.properties | 1 + .../resources/assets/lang/I18N_ru.properties | 1 + .../resources/assets/lang/I18N_zh.properties | 1 + .../assets/lang/I18N_zh_CN.properties | 1 + .../hmcl/game/CrashReportAnalyzer.java | 1 + .../hmcl/game/CrashReportAnalyzerTest.java | 9 +++ .../logs/forge_found_duplicate_mods.txt | 79 +++++++++++++++++++ 9 files changed, 95 insertions(+) create mode 100644 HMCLCore/src/test/resources/logs/forge_found_duplicate_mods.txt diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index e13db11ce0..bd7a4a5107 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -520,6 +520,7 @@ game.crash.reason.forgemod_resolution=The game crashed due to mod resolution fai \n\ Forge provided the following details:\n\ %1$s +game.crash.reason.forge_found_duplicate_mods=The game cannot continue due to a duplicate mods issue. Forge provides the following information: \n%1$s game.crash.reason.mod_resolution_collection=The game crashed because the mod version is not compatible.\n\ \n\ %1$s requires %2$s.\n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 0f5edd830f..96eaca5d86 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -451,6 +451,7 @@ game.crash.reason.forgemod_resolution=El juego se ha bloqueado debido a un fallo \n\ Forge proporcionó los siguientes detalles\:\n\ %1$s +game.crash.reason.forge_found_duplicate_mods=El juego no puede continuar debido a un problema de duplicación de mods. Forge proporciona la siguiente información: \n%1$s game.crash.reason.night_config_fixes=El juego actual no puede seguir ejecutándose debido a algunos problemas con Night Config. \nPuedes intentar instalar el mod Night Config Fixes, que puede ayudarte con este problema. \nPara obtener más información, visite el repositorio de GitHub del mod. game.crash.reason.mod_resolution_collection=El juego se ha bloqueado porque la versión del mod no es compatible.\n\ \n\ diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index 4a3b195c12..261fa36dd7 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -378,6 +378,7 @@ game.crash.reason.memory_exceeded=JVMが割り当てるのに十分なメモリ game.crash.reason.mod=modが原因でゲームがクラッシュしました:%1$s。\nmodを更新または削除して、再試行できます。 game.crash.reason.mod_resolution=modの解決に失敗したため、ゲームがクラッシュしました。\nFabricは次の情報を提供します:\n%s game.crash.reason.forgemod_resolution=modの解決に失敗したため、ゲームがクラッシュしました。\nForgeは次の情報を提供します:\n%s +game.crash.reason.forge_found_duplicate_mods=現在のゲームは、モッズの重複の問題により、続行できません。Forge が次の情報を提供しました:\n%1$s game.crash.reason.mod_resolution_collection=改造前のバージョンと一致しないため、現在のゲームを続行できない。\n%1$s Mod: %2$s が必要です。をクリックすると実行を継続します。\nつまり、フロントエンドをアップデートするか、ダウングレードする必要があるのです。ダウンロードページからMODをダウンロードするか、ウェブから %3$s をダウンロードすることができます。 game.crash.reason.mod_resolution_conflict=modが競合しているため、ゲームがクラッシュしました。\n%1$s が %2$s と競合しています。 game.crash.reason.mod_resolution_missing=Modプレフィックスがないため、現在のゲームを続けることができません。\続行するにはMod: %2$s が必要です。\これはMODがインストールされていないか、そのMODのバージョンが足りないことを意味します。ダウンロードページからMODをダウンロードするか、ウェブから %3$s をダウンロードすることができます。 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 12feb785b3..5be8da76a4 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -401,6 +401,7 @@ game.crash.reason.memory_exceeded=Игра вылетела, потому что game.crash.reason.mod=Игра вылетела из-за мода: %1$s.\nВы можете обновить или удалить мод и повторить попытку. game.crash.reason.mod_resolution=Игра вылетела из-за сбоя разрешения мода.\nFabric даёт некоторую информацию:\n%s game.crash.reason.forgemod_resolution=Игра вылетела из-за сбоя разрешения мода.\nForge даёт некоторую информацию:\n%s +game.crash.reason.forge_found_duplicate_mods=В настоящее время игра не может продолжаться из-за проблемы с повторяющимися модами. Forge предоставляет следующую информацию: \n%1$s game.crash.reason.mod_resolution_collection=Игра вылетела из-за несовместимости требуемой версии мода.\n%1$s требует %2$s.\nЭто означает, что вам необходимо обновить или понизить версию %2$s. Вам необходимо скачать %3$s. game.crash.reason.mod_resolution_conflict=Игра вылетела из-за конфликтующих модов.\n%1$s конфликтуют с %2$s. game.crash.reason.mod_resolution_missing=Игра вылетела, потому что мод, от которого зависит другой мод, не установлен.\n%1$s требует установки мода: %2$s.\nЭто означает, что вам необходимо скачать %2$s со страницы скачивания или через сайт. diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index 13b4360bab..ffb1fc73f8 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -441,6 +441,7 @@ game.crash.reason.memory_exceeded=當前遊戲因為分配的記憶體過大, game.crash.reason.mod=當前遊戲因為 %1$s 的問題,無法繼續運行。\n你可以更新或刪除已經安裝的 %1$s 再試。 game.crash.reason.mod_resolution=當前遊戲因為 Mod 依賴問題,無法繼續運行。Fabric 提供了如下訊息:\n%1$s game.crash.reason.forgemod_resolution=當前遊戲因為 Mod 依賴問題,無法繼續運行。Forge 提供了如下訊息:\n%1$s +game.crash.reason.forge_found_duplicate_mods=遊戲崩潰原因模組重複的問題,無法繼續運行。Forge 提供了以下信息:\n%1$s game.crash.reason.mod_resolution_collection=當前遊戲因為前置 Mod 版本不匹配,無法繼續運行。\n%1$s 需要前置 Mod:%2$s 才能繼續運行。\n這表示你需要更新或降級前置。你可以到下載頁的模組下載,或到網路上下載 %3$s。 game.crash.reason.mod_resolution_conflict=當前遊戲因為 Mod 衝突,無法繼續運行。\n%1$s 與 %2$s 不能相容。 game.crash.reason.mod_resolution_missing=當前遊戲因為缺少 Mod 前置,無法繼續運行。\n%1$s 需要前置 Mod:%2$s 才能繼續運行。\n這表示你少安裝了 Mod,或該 Mod 版本不夠。你可以到下載頁的模組下載,或到網路上下載 %2$s。 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 541428d705..781c352f6a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -444,6 +444,7 @@ game.crash.reason.forge_repeat_installation=当前游戏因为 Forge 重复安 game.crash.reason.optifine_repeat_installation=当前游戏因为 Optifine 重复安装,无法继续运行。\n请删除 Mod 文件夹下的 Optifine 或前往 游戏管理-自动安装 卸载自动安装的 Optifine。 game.crash.reason.mod_resolution=当前游戏因为模组依赖问题,无法继续运行。Fabric 提供了如下信息:\n%1$s game.crash.reason.forgemod_resolution=当前游戏因为模组依赖问题,无法继续运行。Forge 提供了如下信息:\n%1$s +game.crash.reason.forge_found_duplicate_mods=当前游戏因为模组重复问题,无法继续运行。Forge 提供了如下信息:\n%1$s game.crash.reason.mod_resolution_collection=当前游戏因为前置模组版本不匹配,无法继续运行。\n%1$s 需要前置模组:%2$s 才能继续运行。\n这表示你需要更新或降级前置。你可以到下载页的模组下载,或到网上下载 %3$s。 game.crash.reason.mod_resolution_conflict=当前游戏因为模组冲突,无法继续运行。\n%1$s 与 %2$s 不能兼容。 game.crash.reason.mod_resolution_missing=当前游戏因为缺少模组前置,无法继续运行。\n%1$s 需要前置模组:%2$s 才能继续运行。\n这表示你少安装了模组,或该模组版本不够。你可以到下载页的模组下载,或到网上下载 %3$s。 diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java index fb0fd83080..a00af7aaeb 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java @@ -71,6 +71,7 @@ public enum Rule { // Fabric mod resolution MOD_RESOLUTION(Pattern.compile("ModResolutionException: (?(.*)[\\n\\r]*( - (.*)[\\n\\r]*)+)"), "reason"), FORGEMOD_RESOLUTION(Pattern.compile("Missing or unsupported mandatory dependencies:(?(.*)[\\n\\r]*(\t(.*)[\\n\\r]*)+)"), "reason"), + FORGE_FOUND_DUPLICATE_MODS(Pattern.compile("Found duplicate mods:(?(.*)\\R*(\t(.*)\\R*)+)"), "reason"), MOD_RESOLUTION_CONFLICT(Pattern.compile("ModResolutionException: Found conflicting mods: (?.*) conflicts with (?.*)"), "sourcemod", "destmod"), MOD_RESOLUTION_MISSING(Pattern.compile("ModResolutionException: Could not find required mod: (?.*) requires (?.*)"), "sourcemod", "destmod"), MOD_RESOLUTION_MISSING_MINECRAFT(Pattern.compile("ModResolutionException: Could not find required mod: (?.*) requires \\{minecraft @ (?.*)}"), "mod", "version"), diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java index dd8a0fec5a..dfd244150a 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java @@ -100,6 +100,15 @@ public void forgemodResolution() throws IOException { result.getMatcher().group("reason").replaceAll("\\s+", "")); } + @Test + public void forgeFoundDuplicateMods() throws IOException { + CrashReportAnalyzer.Result result = findResultByRule( + CrashReportAnalyzer.anaylze(loadLog("/logs/forge_found_duplicate_mods.txt")), + CrashReportAnalyzer.Rule.FORGE_FOUND_DUPLICATE_MODS); + assertEquals(("\tMod ID: 'jei' from mod files: REIPluginCompatibilities-forge-12.0.93.jar, jei-1.20.1-forge-15.2.0.27.jar\n").replaceAll("\\s+", ""), + result.getMatcher().group("reason").replaceAll("\\s+", "")); + } + @Test public void modResolutionCollection() throws IOException { CrashReportAnalyzer.Result result = findResultByRule( diff --git a/HMCLCore/src/test/resources/logs/forge_found_duplicate_mods.txt b/HMCLCore/src/test/resources/logs/forge_found_duplicate_mods.txt new file mode 100644 index 0000000000..e0d599bd35 --- /dev/null +++ b/HMCLCore/src/test/resources/logs/forge_found_duplicate_mods.txt @@ -0,0 +1,79 @@ +[10:41:20] [main/INFO]: ModLauncher running: args [--username, pretentiou, --version, 1.20.1, --gameDir, C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft, --assetsDir, C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\assets, --assetIndex, 5, --uuid, 202b1899ef5247368d9a289c5d2ec63f, --accessToken, ❄❄❄❄❄❄❄❄, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, msa, --versionType, HMCL 3.5.5.235, --width, 854, --height, 480, --launchTarget, forgeclient, --fml.forgeVersion, 47.2.16, --fml.mcVersion, 1.20.1, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20230612.114412] +[10:41:20] [main/INFO]: ModLauncher 10.0.9+10.0.9+main.dcd20f30 starting: java version 17.0.8 by Microsoft; OS Windows 10 arch amd64 version 10.0 +[10:41:20] [main/INFO]: Loading ImmediateWindowProvider fmlearlywindow +[10:41:20] [main/INFO]: Trying GL version 4.6 +[10:41:20] [main/INFO]: Requested GL version 4.6 got version 4.6 +[10:41:21] [main/INFO]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/C:/Users/姚资柱/Desktop/新建文件夹/game/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%23100!/ Service=ModLauncher Env=CLIENT +[10:41:21] [pool-2-thread-1/INFO]: GL info: AMD Radeon(TM) Graphics GL version 4.6.13596 Core Profile Forward-Compatible Context 20.10.28.10 27.20.11028.10001, ATI Technologies Inc. +[10:41:21] [main/INFO]: Found mod file architectury-9.1.13-forge.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file cloth-config-11.1.118-forge.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file create-1.20.1-0.5.1.f.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file ftb-library-forge-2001.1.5.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file ftb-ultimine-forge-2001.1.4.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file Jade-1.20.1-forge-11.7.1.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file jei-1.20.1-forge-15.2.0.27.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file REIPluginCompatibilities-forge-12.0.93.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file RoughlyEnoughItems-12.0.684-forge.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file Xaeros_Minimap_23.9.7_Forge_1.20.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/INFO]: Found mod file XaerosWorldMap_1.37.7_Forge_1.20.jar of type MOD with provider {mods folder locator at C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\mods} +[10:41:21] [main/WARN]: Mod file C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\libraries\net\minecraftforge\fmlcore\1.20.1-47.2.16\fmlcore-1.20.1-47.2.16.jar is missing mods.toml file +[10:41:21] [main/WARN]: Mod file C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\libraries\net\minecraftforge\javafmllanguage\1.20.1-47.2.16\javafmllanguage-1.20.1-47.2.16.jar is missing mods.toml file +[10:41:21] [main/WARN]: Mod file C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\libraries\net\minecraftforge\lowcodelanguage\1.20.1-47.2.16\lowcodelanguage-1.20.1-47.2.16.jar is missing mods.toml file +[10:41:21] [main/WARN]: Mod file C:\Users\姚资柱\Desktop\新建文件夹\game\.minecraft\libraries\net\minecraftforge\mclanguage\1.20.1-47.2.16\mclanguage-1.20.1-47.2.16.jar is missing mods.toml file +[10:41:21] [main/INFO]: Found mod file fmlcore-1.20.1-47.2.16.jar of type LIBRARY with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d +[10:41:21] [main/INFO]: Found mod file javafmllanguage-1.20.1-47.2.16.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d +[10:41:21] [main/INFO]: Found mod file lowcodelanguage-1.20.1-47.2.16.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d +[10:41:21] [main/INFO]: Found mod file mclanguage-1.20.1-47.2.16.jar of type LANGPROVIDER with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d +[10:41:21] [main/INFO]: Found mod file client-1.20.1-20230612.114412-srg.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d +[10:41:21] [main/INFO]: Found mod file forge-1.20.1-47.2.16-universal.jar of type MOD with provider net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator@878537d +[10:41:21] [main/ERROR]: Found duplicate mods: + Mod ID: 'jei' from mod files: REIPluginCompatibilities-forge-12.0.93.jar, jei-1.20.1-forge-15.2.0.27.jar +[10:41:21] [main/ERROR]: Failed to build unique mod list after mod discovery. +net.minecraftforge.fml.loading.EarlyLoadingException: Duplicate mods found + at net.minecraftforge.fml.loading.UniqueModListBuilder.buildUniqueList(UniqueModListBuilder.java:87) ~[fmlloader-1.20.1-47.2.16.jar:1.0] + at net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer.discoverMods(ModDiscoverer.java:106) ~[fmlloader-1.20.1-47.2.16.jar:?] + at net.minecraftforge.fml.loading.FMLLoader.beginModScan(FMLLoader.java:164) ~[fmlloader-1.20.1-47.2.16.jar:1.0] + at net.minecraftforge.fml.loading.FMLServiceProvider.beginScanning(FMLServiceProvider.java:86) ~[fmlloader-1.20.1-47.2.16.jar:1.0] + at cpw.mods.modlauncher.TransformationServiceDecorator.runScan(TransformationServiceDecorator.java:112) ~[modlauncher-10.0.9.jar:?] + at cpw.mods.modlauncher.TransformationServicesHandler.lambda$runScanningTransformationServices$8(TransformationServicesHandler.java:100) ~[modlauncher-10.0.9.jar:?] + at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[?:?] + at java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779) ~[?:?] + at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[?:?] + at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[?:?] + at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) ~[?:?] + at java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) ~[?:?] + at java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) ~[?:?] + at java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) ~[?:?] + at java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) ~[?:?] + at cpw.mods.modlauncher.TransformationServicesHandler.runScanningTransformationServices(TransformationServicesHandler.java:102) ~[modlauncher-10.0.9.jar:?] + at cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:55) ~[modlauncher-10.0.9.jar:?] + at cpw.mods.modlauncher.Launcher.run(Launcher.java:88) ~[modlauncher-10.0.9.jar:?] + at cpw.mods.modlauncher.Launcher.main(Launcher.java:78) ~[modlauncher-10.0.9.jar:?] + at cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) ~[modlauncher-10.0.9.jar:?] + at cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) ~[modlauncher-10.0.9.jar:?] + at cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) ~[bootstraplauncher-1.1.2.jar:?] +[10:41:21] [main/ERROR]: Mod Discovery failed. Skipping dependency discovery. +Exception in thread "main" java.lang.IllegalStateException: Failed to find system mod: minecraft + at MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.ModSorter.detectSystemMods(ModSorter.java:181) + at MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.ModSorter.buildUniqueList(ModSorter.java:145) + at MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.ModSorter.sort(ModSorter.java:53) + at MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.moddiscovery.ModValidator.stage2Validation(ModValidator.java:98) + at MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.FMLLoader.completeScan(FMLLoader.java:172) + at MC-BOOTSTRAP/fmlloader@1.20.1-47.2.16/net.minecraftforge.fml.loading.FMLServiceProvider.completeScan(FMLServiceProvider.java:91) + at MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.TransformationServiceDecorator.onCompleteScan(TransformationServiceDecorator.java:174) + at MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.TransformationServicesHandler.lambda$triggerScanCompletion$24(TransformationServicesHandler.java:145) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779) + at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) + at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) + at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) + at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) + at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) + at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) + at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) + at MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.TransformationServicesHandler.triggerScanCompletion(TransformationServicesHandler.java:147) + at MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.run(Launcher.java:95) + at MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.Launcher.main(Launcher.java:78) + at MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) + at MC-BOOTSTRAP/cpw.mods.modlauncher@10.0.9/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) + at cpw.mods.bootstraplauncher@1.1.2/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:141) \ No newline at end of file From 14db21211f186e0b6ffa276dd1db758a6de805ed Mon Sep 17 00:00:00 2001 From: zkitefly Date: Mon, 19 Feb 2024 15:47:52 +0800 Subject: [PATCH 25/33] updata Exitwaiter (#2813) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加检测日志 参考自 https://github.com/Hex-Dragon/PCL2/blob/8c8b1066bff6476a14437a14bc16a281e25145bd/Plain%20Craft%20Launcher%202/Modules/Minecraft/ModWatcher.vb#L209-L228 * updata --- .../src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java index 1c0bade687..71dba4885e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/launch/ExitWaiter.java @@ -69,7 +69,10 @@ public void run() { "A fatal exception has occurred. Program will exit.")) { EventBus.EVENT_BUS.fireEvent(new JVMLaunchFailedEvent(this, process)); exitType = ProcessListener.ExitType.JVM_ERROR; - } else if (exitCode != 0 || StringUtils.containsOne(errorLines, "Unable to launch")) { + } else if (exitCode != 0 || StringUtils.containsOne(errorLines, + "Someone is closing me!", + "Crash report saved to", "Could not save crash report to", "This crash report has been saved to:", + "Unable to launch", "An exception was thrown, the game will display an error screen and halt.")) { EventBus.EVENT_BUS.fireEvent(new ProcessExitedAbnormallyEvent(this, process)); if (exitCode == 137 && OperatingSystem.CURRENT_OS.isLinuxOrBSD()) { From d6ea2ef1f997079ae420b9b810457fb7a795bec0 Mon Sep 17 00:00:00 2001 From: zkitefly Date: Mon, 19 Feb 2024 15:54:21 +0800 Subject: [PATCH 26/33] updata-incomplete_forge_installation (#2834) * updata-incomplete_forge_installation * Update CrashReportAnalyzer.java --------- Co-authored-by: Glavo --- .../hmcl/game/CrashReportAnalyzer.java | 2 +- .../hmcl/game/CrashReportAnalyzerTest.java | 7 +++ .../logs/incomplete_forge_installation7.txt | 51 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 HMCLCore/src/test/resources/logs/incomplete_forge_installation7.txt diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java index a00af7aaeb..571604f2a0 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/game/CrashReportAnalyzer.java @@ -125,7 +125,7 @@ public enum Rule { MOD_NAME(Pattern.compile("Invalid module name: '' is not a Java identifier")), //Forge 安装不完整 - INCOMPLETE_FORGE_INSTALLATION(Pattern.compile("(java\\.io\\.UncheckedIOException: java\\.io\\.IOException: Invalid paths argument, contained no existing paths: \\[(.*?)forge-(.*?)-client\\.jar\\]|Failed to find Minecraft resource version (.*?) at (.*?)forge-(.*?)-client\\.jar|Cannot find launch target fmlclient, unable to launch|java\\.lang\\.IllegalStateException: Could not find net/minecraft/client/Minecraft\\.class in classloader SecureModuleClassLoader)")), + INCOMPLETE_FORGE_INSTALLATION(Pattern.compile("(java\\.io\\.UncheckedIOException: java\\.io\\.IOException: Invalid paths argument, contained no existing paths: \\[(.*?)(forge-(.*?)-client\\.jar|fmlcore-(.*?)\\.jar)\\]|Failed to find Minecraft resource version (.*?) at (.*?)forge-(.*?)-client\\.jar|Cannot find launch target fmlclient, unable to launch|java\\.lang\\.IllegalStateException: Could not find net/minecraft/client/Minecraft\\.class in classloader SecureModuleClassLoader)")), NIGHT_CONFIG_FIXES(Pattern.compile("com\\.electronwill\\.nightconfig\\.core\\.io\\.ParsingException: Not enough data available")),//https://github.com/Fuzss/nightconfigfixes //Shaders Mod detected. Please remove it, OptiFine has built-in support for shaders. diff --git a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java index dfd244150a..13ea2b6ced 100644 --- a/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java +++ b/HMCLCore/src/test/java/org/jackhuang/hmcl/game/CrashReportAnalyzerTest.java @@ -506,6 +506,13 @@ public void incompleteForgeInstallation6() throws IOException { CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION); } + @Test + public void incompleteForgeInstallation7() throws IOException { + CrashReportAnalyzer.Result result = findResultByRule( + CrashReportAnalyzer.anaylze(loadLog("/logs/incomplete_forge_installation7.txt")), + CrashReportAnalyzer.Rule.INCOMPLETE_FORGE_INSTALLATION); + } + @Test public void forgeRepeatInstallation() throws IOException { CrashReportAnalyzer.Result result = findResultByRule( diff --git a/HMCLCore/src/test/resources/logs/incomplete_forge_installation7.txt b/HMCLCore/src/test/resources/logs/incomplete_forge_installation7.txt new file mode 100644 index 0000000000..1b55cf95c4 --- /dev/null +++ b/HMCLCore/src/test/resources/logs/incomplete_forge_installation7.txt @@ -0,0 +1,51 @@ +2024-02-19 11:23:55,738 main WARN Advanced terminal features are not available in this environment +[11:23:55] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher running: args [--username, Lod_Superdark, --version, The Legend of Tinker, --gameDir, F:\我你爹\The Legend of Tinker\.minecraft\versions\The Legend of Tinker, --assetsDir, F:\我你爹\The Legend of Tinker\.minecraft\assets, --assetIndex, 1.18, --uuid, 000000000000300D9C90BD99F9099C10, --accessToken, ????????, --clientId, ${clientid}, --xuid, ${auth_xuid}, --userType, Legacy, --versionType, sad, --width, 854, --height, 480, --launchTarget, forgeclient, --fml.forgeVersion, 40.2.10, --fml.mcVersion, 1.18.2, --fml.forgeGroup, net.minecraftforge, --fml.mcpVersion, 20220404.173914] +[11:23:55] [main/INFO] [cp.mo.mo.Launcher/MODLAUNCHER]: ModLauncher 9.1.3+9.1.3+main.9b69c82a starting: java version 17.0.2 by Oracle Corporation +[11:23:56] [main/INFO] [mixin/]: SpongePowered MIXIN Subsystem Version=0.8.5 Source=union:/F:/我你爹/The%20Legend%20of%20Tinker/.minecraft/libraries/org/spongepowered/mixin/0.8.5/mixin-0.8.5.jar%2348!/ Service=ModLauncher Env=CLIENT +[2024-02-19 11:23:56] [INFO]: I18nUpdate Mod 3.4.1 is loaded in 1.18.2 with Forge +Exception in thread "main" java.lang.reflect.InvocationTargetException + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) + at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.base/java.lang.reflect.Method.invoke(Method.java:568) + at oolloo.jlw.Wrapper.invokeMain(Wrapper.java:58) + at oolloo.jlw.Wrapper.main(Wrapper.java:51) +Caused by: java.io.UncheckedIOException: java.io.IOException: Invalid paths argument, contained no existing paths: [F:\我你爹\The Legend of Tinker\.minecraft\libraries\net\minecraftforge\fmlcore\1.18.2-40.2.10\fmlcore-1.18.2-40.2.10.jar] + at cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.impl.Jar.(Jar.java:74) + at cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.SecureJar.from(SecureJar.java:58) + at cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.SecureJar.from(SecureJar.java:54) + at cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.SecureJar.from(SecureJar.java:46) + at cpw.mods.securejarhandler@1.0.8/cpw.mods.jarhandling.SecureJar.from(SecureJar.java:38) + at MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator.lambda$scanMods$3(MinecraftLocator.java:35) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.AbstractList$RandomAccessSpliterator.forEachRemaining(AbstractList.java:720) + at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) + at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) + at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) + at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) + at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682) + at MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.moddiscovery.MinecraftLocator.scanMods(MinecraftLocator.java:37) + at MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.moddiscovery.ModDiscoverer.discoverMods(ModDiscoverer.java:59) + at MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.FMLLoader.beginModScan(FMLLoader.java:166) + at MC-BOOTSTRAP/fmlloader@1.18.2-40.2.10/net.minecraftforge.fml.loading.FMLServiceProvider.beginScanning(FMLServiceProvider.java:86) + at MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.TransformationServiceDecorator.runScan(TransformationServiceDecorator.java:112) + at MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.TransformationServicesHandler.lambda$runScanningTransformationServices$8(TransformationServicesHandler.java:100) + at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) + at java.base/java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1779) + at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) + at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) + at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) + at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) + at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) + at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) + at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) + at MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.TransformationServicesHandler.runScanningTransformationServices(TransformationServicesHandler.java:102) + at MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.TransformationServicesHandler.initializeTransformationServices(TransformationServicesHandler.java:55) + at MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.Launcher.run(Launcher.java:87) + at MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.Launcher.main(Launcher.java:77) + at MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:26) + at MC-BOOTSTRAP/cpw.mods.modlauncher@9.1.3/cpw.mods.modlauncher.BootstrapLaunchConsumer.accept(BootstrapLaunchConsumer.java:23) + at cpw.mods.bootstraplauncher@1.0.0/cpw.mods.bootstraplauncher.BootstrapLauncher.main(BootstrapLauncher.java:149) + ... 6 more +Caused by: java.io.IOException: Invalid paths argument, contained no existing paths: [F:\我你爹\The Legend of Tinker\.minecraft\libraries\net\minecraftforge\fmlcore\1.18.2-40.2.10\fmlcore-1.18.2-40.2.10.jar] + ... 41 more \ No newline at end of file From 76f11b6a6fedf49602f34de3fae647c3dad9039c Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:07:49 +0800 Subject: [PATCH 27/33] Fix #2799 (#2800) * Fix #2799 * Remove codes in ConfigUpgrader. * delete collectDFU * update --------- Co-authored-by: Glavo --- .../org/jackhuang/hmcl/setting/Config.java | 8 +- .../hmcl/setting/ConfigUpgrader.java | 121 +++++++----------- .../org/jackhuang/hmcl/ui/Controllers.java | 35 ++--- 3 files changed, 66 insertions(+), 98 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 6ab9febd41..4220337311 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -393,8 +393,8 @@ public DoubleProperty xProperty() { return x; } - public void setX(double height) { - this.x.set(height); + public void setX(double x) { + this.x.set(x); } public double getY() { @@ -405,8 +405,8 @@ public DoubleProperty yProperty() { return y; } - public void setY(double height) { - this.y.set(height); + public void setY(double y) { + this.y.set(y); } public double getWidth() { diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java index be202785f5..1a034979c7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java @@ -18,16 +18,11 @@ package org.jackhuang.hmcl.setting; import com.google.gson.Gson; -import org.jackhuang.hmcl.ui.Controllers; -import org.jackhuang.hmcl.util.Lang; -import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.StringUtils; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.function.BiConsumer; import java.util.logging.Level; import static org.jackhuang.hmcl.util.Lang.tryCast; @@ -46,94 +41,66 @@ private ConfigUpgrader() { * @param rawContent raw json content of the config settings without modification */ static void upgradeConfig(Config deserialized, String rawContent) { - if (deserialized.getConfigVersion() == CURRENT_VERSION) { + int configVersion = deserialized.getConfigVersion(); + + if (configVersion == CURRENT_VERSION) { return; } - int configVersion = deserialized.getConfigVersion(); if (configVersion > CURRENT_VERSION) { LOG.log(Level.WARNING, String.format("Current HMCL only support the configuration version up to %d. However, the version now is %d.", CURRENT_VERSION, configVersion)); + deserialized.setConfigVersion(CURRENT_VERSION); return; } LOG.log(Level.INFO, String.format("Updating configuration from %d to %d.", configVersion, CURRENT_VERSION)); - Map unmodifiableRawJson = Collections.unmodifiableMap(new Gson().>fromJson(rawContent, Map.class)); - for (Map.Entry>> dfu : collectDFU()) { - if (configVersion < dfu.getKey()) { - dfu.getValue().accept(deserialized, unmodifiableRawJson); - configVersion = dfu.getKey(); - } - } - - deserialized.setConfigVersion(CURRENT_VERSION); - } + Map rawJson = Collections.unmodifiableMap(new Gson().>fromJson(rawContent, Map.class)); - /** - *

Initialize the dfu of HMCL. Feel free to use lambda as all the lambda here would not be initialized unless HMCL needs to update the configuration. - * For each item in this list, it should be a Map.Entry.

- * - *

The key should be a version number. All the configuration with a version number which is less than the specific one will be applied to this upgrader.

- *

The value should be the upgrader. The configuration which is waited to being processed, and the raw unmodifiable value of the json from the configuration file.

- *

The return value should a target version number of this item.

- * - *

The last item must return CURRENT_VERSION, as the config file should always being updated to the latest version.

- */ - private static List>>> collectDFU() { - List>>> dfu = Lang.immutableListOf( - Pair.pair(1, (deserialized, rawJson) -> { - // Upgrade configuration of HMCL 2.x: Convert OfflineAccounts whose stored uuid is important. - tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> { - tryCast(auth.get("offline"), Map.class).ifPresent(offline -> { - String selected = rawJson.containsKey("selectedAccount") ? null - : tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null); + if (configVersion < 1) { + // Upgrade configuration of HMCL 2.x: Convert OfflineAccounts whose stored uuid is important. + tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> { + tryCast(auth.get("offline"), Map.class).ifPresent(offline -> { + String selected = rawJson.containsKey("selectedAccount") ? null + : tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null); - tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> { - ((Map) uuidMap).forEach((key, value) -> { - Map storage = new HashMap<>(); - storage.put("type", "offline"); - storage.put("username", key); - storage.put("uuid", value); - if (key.equals(selected)) { - storage.put("selected", true); - } - deserialized.getAccountStorages().add(storage); - }); - }); + tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> { + ((Map) uuidMap).forEach((key, value) -> { + Map storage = new HashMap<>(); + storage.put("type", "offline"); + storage.put("username", key); + storage.put("uuid", value); + if (key.equals(selected)) { + storage.put("selected", true); + } + deserialized.getAccountStorages().add(storage); }); }); + }); + }); - // Upgrade configuration of HMCL earlier than 3.1.70 - if (!rawJson.containsKey("commonDirType")) - deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM); - if (!rawJson.containsKey("backgroundType")) - deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT); - if (!rawJson.containsKey("hasProxy")) - deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost())); - if (!rawJson.containsKey("hasProxyAuth")) - deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser())); + // Upgrade configuration of HMCL earlier than 3.1.70 + if (!rawJson.containsKey("commonDirType")) + deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM); + if (!rawJson.containsKey("backgroundType")) + deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT); + if (!rawJson.containsKey("hasProxy")) + deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost())); + if (!rawJson.containsKey("hasProxyAuth")) + deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser())); - if (!rawJson.containsKey("downloadType")) { - tryCast(rawJson.get("downloadtype"), Number.class) - .map(Number::intValue) - .ifPresent(id -> { - if (id == 0) { - deserialized.setDownloadType("mojang"); - } else if (id == 1) { - deserialized.setDownloadType("bmclapi"); - } - }); - } - }), - Pair.pair(2, (deserialized, rawJson) -> { - deserialized.setX(0.5D - deserialized.getWidth() / Controllers.SCREEN.getBounds().getWidth() / 2); - deserialized.setY(0.5D - deserialized.getHeight() / Controllers.SCREEN.getBounds().getHeight() / 2); - }) - ); - - if (dfu.get(dfu.size() - 1).getKey() != CURRENT_VERSION) { - throw new IllegalStateException("The last dfu must adapt all the config version below CURRENT_VERSION"); + if (!rawJson.containsKey("downloadType")) { + tryCast(rawJson.get("downloadtype"), Number.class) + .map(Number::intValue) + .ifPresent(id -> { + if (id == 0) { + deserialized.setDownloadType("mojang"); + } else if (id == 1) { + deserialized.setDownloadType("bmclapi"); + } + }); + } } - return dfu; + deserialized.setConfigVersion(CURRENT_VERSION); } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index acedb629c8..5a4fe371df 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -37,10 +37,7 @@ import org.jackhuang.hmcl.Launcher; import org.jackhuang.hmcl.Metadata; import org.jackhuang.hmcl.game.ModpackHelper; -import org.jackhuang.hmcl.setting.Accounts; -import org.jackhuang.hmcl.setting.EnumCommonDirectory; -import org.jackhuang.hmcl.setting.Profiles; -import org.jackhuang.hmcl.setting.Theme; +import org.jackhuang.hmcl.setting.*; import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.TaskExecutor; import org.jackhuang.hmcl.ui.account.AccountListPage; @@ -68,6 +65,8 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n; public final class Controllers { + public static final int MIN_WIDTH = 800 + 2 + 16; // bg width + border width*2 + shadow width*2 + public static final int MIN_HEIGHT = 450 + 2 + 40 + 16; // bg height + border width*2 + toolbar height + shadow width*2 public static final Screen SCREEN = Screen.getPrimary(); private static InvalidationListener stageSizeChangeListener; private static DoubleProperty stageX = new SimpleDoubleProperty(); @@ -205,35 +204,37 @@ public static void initialize(Stage stage) { WeakInvalidationListener weakListener = new WeakInvalidationListener(stageSizeChangeListener); - double initHeight = config().getHeight(); - double initWidth = config().getWidth(); - double initX = config().getX() * SCREEN.getBounds().getWidth(); - double initY = config().getY() * SCREEN.getBounds().getHeight(); + double initWidth = Math.max(MIN_WIDTH, config().getWidth()); + double initHeight = Math.max(MIN_HEIGHT, config().getHeight()); { + double initX = config().getX() * SCREEN.getBounds().getWidth(); + double initY = config().getY() * SCREEN.getBounds().getHeight(); + boolean invalid = true; double border = 20D; for (Screen screen : Screen.getScreens()) { Rectangle2D bound = screen.getBounds(); - if (bound.getMinX() + border <= initX + initWidth && initX <= bound.getMaxX() - border && bound.getMinY() + border <= initY + initHeight && initY <= bound.getMaxY() - border) { + if (bound.getMinX() + border <= initX + initWidth && initX <= bound.getMaxX() - border && bound.getMinY() + border <= initY && initY <= bound.getMaxY() - border) { invalid = false; break; } } if (invalid) { - initX = (0.5D - initWidth / Controllers.SCREEN.getBounds().getWidth() / 2) * SCREEN.getBounds().getWidth(); - initY = (0.5D - initHeight / Controllers.SCREEN.getBounds().getHeight() / 2) * SCREEN.getBounds().getHeight(); + initX = (0.5D - initWidth / SCREEN.getBounds().getWidth() / 2) * SCREEN.getBounds().getWidth(); + initY = (0.5D - initHeight / SCREEN.getBounds().getHeight() / 2) * SCREEN.getBounds().getHeight(); } + + stage.setX(initX); + stage.setY(initY); + stageX.set(initX); + stageY.set(initY); } - stage.setX(initX); - stage.setY(initY); stage.setHeight(initHeight); stage.setWidth(initWidth); - stageX.set(initX); - stageY.set(initY); stageHeight.set(initHeight); stageWidth.set(initWidth); stage.xProperty().addListener(weakListener); @@ -255,8 +256,8 @@ public static void initialize(Stage stage) { scene = new Scene(decorator.getDecorator()); scene.setFill(Color.TRANSPARENT); - stage.setMinHeight(450 + 2 + 40 + 16); // bg height + border width*2 + toolbar height + shadow width*2 - stage.setMinWidth(800 + 2 + 16); // bg width + border width*2 + shadow width*2 + stage.setMinWidth(MIN_WIDTH); + stage.setMinHeight(MIN_HEIGHT); decorator.getDecorator().prefWidthProperty().bind(scene.widthProperty()); decorator.getDecorator().prefHeightProperty().bind(scene.heightProperty()); scene.getStylesheets().setAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily())); From dc31f0d5c97342cec5177fcbdcaee6fbc83f0109 Mon Sep 17 00:00:00 2001 From: zkitefly Date: Mon, 19 Feb 2024 16:12:33 +0800 Subject: [PATCH 28/33] Updata slug (#2801) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add curse.category.4736 * add * 调整顺序 --- HMCL/src/main/resources/assets/lang/I18N.properties | 7 ++++++- HMCL/src/main/resources/assets/lang/I18N_es.properties | 7 ++++++- HMCL/src/main/resources/assets/lang/I18N_ja.properties | 7 ++++++- HMCL/src/main/resources/assets/lang/I18N_ru.properties | 7 ++++++- HMCL/src/main/resources/assets/lang/I18N_zh.properties | 7 ++++++- HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties | 7 ++++++- 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/HMCL/src/main/resources/assets/lang/I18N.properties b/HMCL/src/main/resources/assets/lang/I18N.properties index bd7a4a5107..53a3063b24 100644 --- a/HMCL/src/main/resources/assets/lang/I18N.properties +++ b/HMCL/src/main/resources/assets/lang/I18N.properties @@ -204,7 +204,7 @@ curse.category.4477=Mini Game curse.category.4478=Quests curse.category.4484=Multiplayer curse.category.4476=Exploration -curse.category.6145=Skyblock +curse.category.4736=Skyblock curse.category.4475=Adventure and RPG curse.category.4487=FTB curse.category.4480=Map Based @@ -219,6 +219,11 @@ curse.category.5299=Education curse.category.5232=Galacticraft curse.category.5129=Vanilla+ curse.category.5189=Utility & QOL +curse.category.6814=Performance +curse.category.6954=Integrated Dynamics +curse.category.6484=Create +curse.category.6821=Bug Fixes +curse.category.6145=Skyblock curse.category.5190=QoL curse.category.5191=Utility & QoL curse.category.5192=FancyMenu diff --git a/HMCL/src/main/resources/assets/lang/I18N_es.properties b/HMCL/src/main/resources/assets/lang/I18N_es.properties index 96eaca5d86..f337df82c8 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_es.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_es.properties @@ -187,7 +187,7 @@ curse.category.4477=Mini Juego curse.category.4478=Quests curse.category.4484=Multijugador curse.category.4476=Exploración -curse.category.6145=Skyblock +curse.category.4736=Skyblock curse.category.4475=Aventura y RPG curse.category.4487=FTB curse.category.4480=Basado en mapa @@ -203,6 +203,11 @@ curse.category.5232=Galacticraft curse.category.5129=Vanilla+ curse.category.5189=Utilidad y calidad de vida curse.category.5190=Calidad de vida +curse.category.6145=Skyblock +curse.category.6814=Performance +curse.category.6954=Integrated Dynamics +curse.category.6484=Create +curse.category.6821=Bug Fixes curse.category.5191=Utilidad y calidad de vida curse.category.5192=Menú elegante curse.category.423=Mapa e información diff --git a/HMCL/src/main/resources/assets/lang/I18N_ja.properties b/HMCL/src/main/resources/assets/lang/I18N_ja.properties index 261fa36dd7..c555c26a6c 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ja.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ja.properties @@ -171,7 +171,7 @@ curse.category.4477=ミニゲーム curse.category.4478=クエスト curse.category.4484=マルチプレイヤー curse.category.4476=探索 -curse.category.6145=Skyblock +curse.category.4736=Skyblock curse.category.4475=アドベンチャーとRPG curse.category.4487=FTB curse.category.4480=マップベース @@ -187,6 +187,11 @@ curse.category.5190=QoL curse.category.5191=Utility & QoL curse.category.5192=FancyMenu curse.category.423=地図と情報 +curse.category.6814=パフォーマンス +curse.category.6484=Create +curse.category.6954=Integrated Dynamics +curse.category.6145=Skyblock +curse.category.6821=バグ修正 curse.category.426=Addons curse.category.434=武器、道具、武器 curse.category.409=構造 diff --git a/HMCL/src/main/resources/assets/lang/I18N_ru.properties b/HMCL/src/main/resources/assets/lang/I18N_ru.properties index 5be8da76a4..7504de125a 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_ru.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_ru.properties @@ -185,7 +185,7 @@ curse.category.4477=Mini Game curse.category.4478=Quests curse.category.4484=Multiplayer curse.category.4476=Exploration -curse.category.6145=Skyblock +curse.category.4736=Skyblock curse.category.4475=Adventure and RPG curse.category.4487=FTB curse.category.4480=Map Based @@ -203,6 +203,11 @@ curse.category.5189=Utility & QOL curse.category.5190=QoL curse.category.5191=Utility & QoL curse.category.5192=FancyMenu +curse.category.6814=Performance +curse.category.6954=Integrated Dynamics +curse.category.6484=Create +curse.category.6821=Bug Fixes +curse.category.6145=Skyblock curse.category.423=Map and Information curse.category.426=Addons curse.category.434=Armor, Tools, and Weapons diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh.properties b/HMCL/src/main/resources/assets/lang/I18N_zh.properties index ffb1fc73f8..7c297b5db9 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh.properties @@ -212,7 +212,7 @@ curse.category.4477=小遊戲 curse.category.4478=任務 curse.category.4484=多人 curse.category.4476=探索 -curse.category.6145=空島 +curse.category.4736=空島 curse.category.4475=冒險 RPG curse.category.4487=FTB 模組包 curse.category.4480=有特定地圖 @@ -227,6 +227,11 @@ curse.category.5299=教育 curse.category.5232=額外行星 curse.category.5129=原版增強 curse.category.5189=實用與QOL +curse.category.6814=性能 +curse.category.6954=動態聯合/集成動力 (Integrated Dynamics) +curse.category.6484=機械動力 (Create) +curse.category.6821=錯誤修復 +curse.category.6145=空島 curse.category.5190=QoL curse.category.5191=實用與QoL curse.category.5192=夢幻菜單 diff --git a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties index 781c352f6a..16a5891e64 100644 --- a/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties +++ b/HMCL/src/main/resources/assets/lang/I18N_zh_CN.properties @@ -213,7 +213,7 @@ curse.category.4477=小游戏 curse.category.4478=任务 curse.category.4484=多人 curse.category.4476=探索 -curse.category.6145=空岛 +curse.category.4736=空岛 curse.category.4475=冒险 RPG curse.category.4487=FTB 整合包 curse.category.4480=有特定地图 @@ -231,6 +231,11 @@ curse.category.5189=实用与QOL curse.category.5190=QoL curse.category.5191=实用与QoL curse.category.5192=梦幻菜单 +curse.category.6145=空岛 +curse.category.6814=性能 +curse.category.6954=动态联合/集成动力 (Integrated Dynamics) +curse.category.6484=机械动力 (Create) +curse.category.6821=错误修复 curse.category.423=信息展示 curse.category.426=模组扩展 curse.category.434=装备武器 From e9ae43b1f1830629a8560ab234670d93328a7193 Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:19:48 +0800 Subject: [PATCH 29/33] Fix 2774: Local NeoForge mods shouldn't be identified as Forge mods. (#2775) --- .../hmcl/mod/modinfo/ForgeNewModMetadata.java | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java index c74adfb247..28be575277 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/modinfo/ForgeNewModMetadata.java @@ -13,7 +13,7 @@ import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.jar.Attributes; import java.util.jar.Manifest; @@ -33,10 +33,6 @@ public final class ForgeNewModMetadata { private final List mods; - public ForgeNewModMetadata() { - this("", "", "", "", Collections.emptyList()); - } - public ForgeNewModMetadata(String modLoader, String loaderVersion, String logoFile, String license, List mods) { this.modLoader = modLoader; this.loaderVersion = loaderVersion; @@ -120,8 +116,9 @@ public String getDescription() { public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSystem fs) throws IOException, JsonParseException { Path modstoml = fs.getPath("META-INF/mods.toml"); if (Files.notExists(modstoml)) - throw new IOException("File " + modFile + " is not a Forge 1.13+ mod."); - ForgeNewModMetadata metadata = new Toml().read(FileUtils.readText(modstoml)).to(ForgeNewModMetadata.class); + throw new IOException("File " + modFile + " is not a Forge 1.13+ or NeoForge mod."); + Toml toml = new Toml().read(FileUtils.readText(modstoml)); + ForgeNewModMetadata metadata = toml.to(ForgeNewModMetadata.class); if (metadata == null || metadata.getMods().isEmpty()) throw new IOException("Mod " + modFile + " `mods.toml` is malformed.."); Mod mod = metadata.getMods().get(0); @@ -135,9 +132,29 @@ public static LocalModFile fromFile(ModManager modManager, Path modFile, FileSys LOG.log(Level.WARNING, "Failed to parse MANIFEST.MF in file " + modFile); } } - return new LocalModFile(modManager, modManager.getLocalMod(mod.getModId(), ModLoaderType.FORGE), modFile, mod.getDisplayName(), new LocalModFile.Description(mod.getDescription()), + + ModLoaderType modLoaderType = analyzeModLoader(toml, mod.getModId()); + if (modLoaderType == null) { + throw new IOException("File " + modFile + " is not a Forge 1.13+ or NeoForge mod."); + } + + return new LocalModFile(modManager, modManager.getLocalMod(mod.getModId(), modLoaderType), modFile, mod.getDisplayName(), new LocalModFile.Description(mod.getDescription()), mod.getAuthors(), jarVersion == null ? mod.getVersion() : mod.getVersion().replace("${file.jarVersion}", jarVersion), "", mod.getDisplayURL(), metadata.getLogoFile()); } + + private static ModLoaderType analyzeModLoader(Toml toml, String modID) { + List> dependencies = toml.getList("dependencies." + modID); + if (dependencies != null) { + for (HashMap dependency : dependencies) { + switch ((String) dependency.get("modId")) { + case "forge": return ModLoaderType.FORGE; + case "neoforge": return ModLoaderType.NEO_FORGED; + } + } + } + + return null; + } } From a81462425d40be82f3d5b731ef91623300dd250e Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:28:07 +0800 Subject: [PATCH 30/33] =?UTF-8?q?Fix=20#2762:=20=E5=85=89=E5=BD=B1?= =?UTF-8?q?=E5=BA=94=E5=BD=93=E8=A2=AB=E4=B8=8B=E8=BD=BD=E8=87=B3=20./shad?= =?UTF-8?q?erpacks=20=E4=B8=8B=20(#2770)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix #2762: Shader packs should be downloaded in to the ./shaderpacks directory. * Make the network requests parallely in order to make CurseCompletionTask faster. * Support displaying the process. * Fix: checkstyle * update * update * update * update --------- Co-authored-by: Glavo --- .../hmcl/mod/curse/CurseCompletionTask.java | 76 +++++++++++++------ 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java index bc357f548e..d40699219c 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/curse/CurseCompletionTask.java @@ -33,13 +33,13 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Complete the CurseForge version. @@ -53,7 +53,7 @@ public final class CurseCompletionTask extends Task { private final ModManager modManager; private final String version; private CurseManifest manifest; - private final List> dependencies = new ArrayList<>(); + private List> dependencies; private final AtomicBoolean allNameKnown = new AtomicBoolean(true); private final AtomicInteger finished = new AtomicInteger(0); @@ -138,28 +138,31 @@ public void execute() throws Exception { .collect(Collectors.toList())); FileUtils.writeText(new File(root, "manifest.json"), JsonUtils.GSON.toJson(newManifest)); - File resourcePacks = new File(repository.getVersionRoot(modManager.getVersion()), "resourcepacks"); - for (CurseManifestFile file : newManifest.getFiles()) - if (StringUtils.isNotBlank(file.getFileName())) { - RemoteMod mod = CurseForgeRemoteModRepository.MODS.getModById(Integer.toString(file.getProjectID())); - File target; - if (((CurseAddon) mod.getData()).getClassId() == 12) { - target = new File(resourcePacks, file.getFileName()); - if (target.exists()) { - continue; + File versionRoot = repository.getVersionRoot(modManager.getVersion()); + File resourcePacksRoot = new File(versionRoot, "resourcepacks"), shaderPacksRoot = new File(versionRoot, "shaderpacks"); + finished.set(0); + dependencies = newManifest.getFiles() + .stream().parallel() + .filter(f -> f.getFileName() != null) + .flatMap(f -> { + try { + File path = guessFilePath(f, resourcePacksRoot, shaderPacksRoot); + if (path == null) { + return Stream.empty(); + } + + FileDownloadTask task = new FileDownloadTask(f.getUrl(), path); + task.setCacheRepository(dependency.getCacheRepository()); + task.setCaching(true); + return Stream.of(task.withCounter("hmcl.modpack.download")); + } catch (IOException e) { + Logging.LOG.log(Level.WARNING, "Could not query api.curseforge.com for mod: " + f.getProjectID() + ", " + f.getFileID(), e); + return Stream.empty(); // Ignore this file. + } finally { + updateProgress(finished.incrementAndGet(), newManifest.getFiles().size()); } - } else { - if (modManager.hasSimpleMod(file.getFileName())) { - continue; - } - target = modManager.getSimpleModPath(file.getFileName()).toFile(); - } - - FileDownloadTask task = new FileDownloadTask(file.getUrl(), target); - task.setCacheRepository(dependency.getCacheRepository()); - task.setCaching(true); - dependencies.add(task.withCounter("hmcl.modpack.download")); - } + }) + .collect(Collectors.toList()); if (!dependencies.isEmpty()) { getProperties().put("total", dependencies.size()); @@ -167,6 +170,33 @@ public void execute() throws Exception { } } + /** + * Guess where to store the file. + * @param file The file. + * @param resourcePacksRoot ./resourcepacks. + * @param shaderPacksRoot ./shaderpacks. + * @return ./resourcepacks/$filename or ./shaderpacks/$filename or ./mods/$filename if the file doesn't exist. null if the file existed. + * @throws IOException If IOException was encountered during getting data from CurseForge. + */ + private File guessFilePath(CurseManifestFile file, File resourcePacksRoot, File shaderPacksRoot) throws IOException { + RemoteMod mod = CurseForgeRemoteModRepository.MODS.getModById(Integer.toString(file.getProjectID())); + int classID = ((CurseAddon) mod.getData()).getClassId(); + String fileName = file.getFileName(); + switch (classID) { + case 12: // Resource pack + case 6552: { // Shader pack + File res = new File(classID == 12 ? resourcePacksRoot : shaderPacksRoot, fileName); + return res.exists() ? null : res; + } + default: { + if (modManager.hasSimpleMod(fileName)) { + return null; + } + return modManager.getSimpleModPath(fileName).toFile(); + } + } + } + @Override public boolean doPostExecute() { return true; From 0bff282d3db42219140c30fbfc7bcadc19817f0a Mon Sep 17 00:00:00 2001 From: Burning_TNT <88144530+burningtnt@users.noreply.github.com> Date: Mon, 19 Feb 2024 17:28:55 +0800 Subject: [PATCH 31/33] Fix #2826. (#2827) --- HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java index a3c09efa02..1db2b3b231 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/FXUtils.java @@ -907,7 +907,9 @@ public static void copyText(String text) { content.putString(text); Clipboard.getSystemClipboard().setContent(content); - Controllers.showToast(i18n("message.copied")); + if (!Controllers.isStopped()) { + Controllers.showToast(i18n("message.copied")); + } } public static List parseSegment(String segment, Consumer hyperlinkAction) { From 63345f52ce1f4e6be041d9a9a8ced2a66cc723cd Mon Sep 17 00:00:00 2001 From: Glavo Date: Mon, 19 Feb 2024 17:43:22 +0800 Subject: [PATCH 32/33] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20PUBLISH=5FURL=20(#28?= =?UTF-8?q?36)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 更新 PUBLISH_URL * update --- HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 9d2e0c383b..06198bfb93 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -41,7 +41,7 @@ private Metadata() {} public static final String CONTACT_URL = "https://docs.hmcl.net/help.html"; public static final String HELP_URL = "https://docs.hmcl.net"; public static final String CHANGELOG_URL = "https://docs.hmcl.net/changelog/"; - public static final String PUBLISH_URL = "https://www.mcbbs.net/thread-142335-1-1.html"; + public static final String PUBLISH_URL = "https://hmcl.huangyuhui.net"; public static final String EULA_URL = "https://docs.hmcl.net/eula/hmcl.html"; public static final String BUILD_CHANNEL = JarUtils.getManifestAttribute("Build-Channel", "nightly"); From 94c1ed10534f2ce033d6644661225f514abe99c2 Mon Sep 17 00:00:00 2001 From: coldshineb Date: Tue, 20 Feb 2024 17:31:27 -0500 Subject: [PATCH 33/33] 1.1.10 merged upstream 63345f52 --- HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java | 2 +- HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java | 2 +- HMCL/src/main/resources/assets/css/blue.css | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java index 2cf78ec83e..0b3e30e8e9 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Metadata.java @@ -32,7 +32,7 @@ private Metadata() {} public static final String NAME = "Moonlight Land 启动器"; public static final String FULL_NAME = "Moonlight Land 启动器"; - public static final String VERSION = "1.1.9"; + public static final String VERSION = "1.1.10"; public static final String TITLE = NAME + " " + VERSION; public static final String FULL_TITLE = FULL_NAME + " " + VERSION; diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java index 5a4fe371df..c4c954dac0 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/ui/Controllers.java @@ -66,7 +66,7 @@ public final class Controllers { public static final int MIN_WIDTH = 800 + 2 + 16; // bg width + border width*2 + shadow width*2 - public static final int MIN_HEIGHT = 450 + 2 + 40 + 16; // bg height + border width*2 + toolbar height + shadow width*2 + public static final int MIN_HEIGHT = 450 + 2 + 40 + 16 + 60; // bg height + border width*2 + toolbar height + shadow width*2 public static final Screen SCREEN = Screen.getPrimary(); private static InvalidationListener stageSizeChangeListener; private static DoubleProperty stageX = new SimpleDoubleProperty(); diff --git a/HMCL/src/main/resources/assets/css/blue.css b/HMCL/src/main/resources/assets/css/blue.css index c02cbb1b89..d4cc8d705b 100644 --- a/HMCL/src/main/resources/assets/css/blue.css +++ b/HMCL/src/main/resources/assets/css/blue.css @@ -22,7 +22,7 @@ -fx-rippler-color: rgba(92, 107, 192, 0.3); -fx-base-rippler-color: derive(rgba(92, 107, 192, 0.3), 100%); -fx-base-disabled-text-fill: rgba(256, 256, 256, 0.7); - -fx-base-text-fill: white; + -fx-base-text-fill: black; -theme-thumb: rgba(92, 107, 192, 0.7); } \ No newline at end of file