Compare commits
49
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a072fb7bea | ||
|
|
08a3850dcf | ||
|
|
030fba4630 | ||
|
|
559aefd6b6 | ||
|
|
2ee5d9ceed | ||
|
|
20ed42ab8a | ||
|
|
815b90845a | ||
|
|
e9846ea644 | ||
|
|
02065094ca | ||
|
|
9f6124d617 | ||
|
|
76229a894c | ||
|
|
03de1598c8 | ||
|
|
3a13ff0e10 | ||
|
|
63345f52ce | ||
|
|
0bff282d3d | ||
|
|
a81462425d | ||
|
|
e9ae43b1f1 | ||
|
|
dc31f0d5c9 | ||
|
|
76f11b6a6f | ||
|
|
d6ea2ef1f9 | ||
|
|
14db21211f | ||
|
|
f354f65adb | ||
|
|
83e8ea3735 | ||
|
|
0e9e9811eb | ||
|
|
420d77de9f | ||
|
|
9361719a67 | ||
|
|
b09a5868d4 | ||
|
|
56c48e5595 | ||
|
|
4d6cb547ea | ||
|
|
95ecd29018 | ||
|
|
5d26a106e9 | ||
|
|
95707560ef | ||
|
|
c54cb88db8 | ||
|
|
95afaa5112 | ||
|
|
fe608e32f1 | ||
|
|
82fc1c067d | ||
|
|
94890e2924 | ||
|
|
9a76b1ff27 | ||
|
|
8db8693132 | ||
|
|
59cc5b9114 | ||
|
|
c1acd0b0b5 | ||
|
|
fb6538f042 | ||
|
|
20fe5430dd | ||
|
|
7de07319d9 | ||
|
|
8e3c19651e | ||
|
|
2740bdeb77 | ||
|
|
59ab49e0c5 | ||
|
|
bc4aa43834 | ||
|
|
c7f3f2a229 |
No files matched your search
@@ -5,6 +5,9 @@ on:
|
||||
schedule:
|
||||
- cron: '30 * * * *'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
dev-check-update:
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
@@ -66,7 +69,7 @@ jobs:
|
||||
name: ${{ env.HMCL_TAG_NAME }}
|
||||
tag_name: ${{ env.HMCL_TAG_NAME }}
|
||||
stable-check-update:
|
||||
if: ${{ github.repository_owner == 'huanghongxun' }}
|
||||
if: ${{ github.repository_owner == 'HMCL-dev' }}
|
||||
needs: dev-check-update
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -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<JavaCompile>("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")
|
||||
|
||||
@@ -33,12 +33,10 @@ import org.jackhuang.hmcl.setting.SambaException;
|
||||
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 final class Launcher extends Application {
|
||||
|
||||
UpdateChecker.init();
|
||||
|
||||
RemoteResourceManager.init();
|
||||
|
||||
RemoteResourceManager.register();
|
||||
|
||||
primaryStage.show();
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
@@ -293,10 +287,6 @@ public final class Launcher extends Application {
|
||||
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);
|
||||
|
||||
@@ -37,13 +37,11 @@ public final class 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/";
|
||||
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");
|
||||
|
||||
@@ -20,10 +20,9 @@ package org.jackhuang.hmcl.countly;
|
||||
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 class Countly {
|
||||
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 class Countly {
|
||||
}
|
||||
|
||||
private static int getTimezoneOffset() {
|
||||
return TimeZone.getDefault().getOffset(new Date().getTime()) / 60000;
|
||||
return ZonedDateTime.now().getOffset().getTotalSeconds() / 60;
|
||||
}
|
||||
|
||||
private static String getLocale() {
|
||||
|
||||
@@ -7,8 +7,8 @@ import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
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 class CrashReport {
|
||||
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" +
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.util.Logging;
|
||||
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 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
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) {
|
||||
|
||||
@@ -48,6 +48,7 @@ import java.io.IOException;
|
||||
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 class HMCLGameRepository extends DefaultGameRepository {
|
||||
public Stream<Version> 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())));
|
||||
}
|
||||
|
||||
@@ -281,7 +282,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
}
|
||||
|
||||
if (LibraryAnalyzer.isModded(this, version)) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FABRIC))
|
||||
return VersionIconType.FABRIC.getIcon();
|
||||
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE))
|
||||
|
||||
@@ -93,7 +93,7 @@ public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
public void execute() throws Exception {
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version originalVersion = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(originalVersion);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(originalVersion, null);
|
||||
Task<Version> libraryTask = Task.supplyAsync(() -> originalVersion);
|
||||
// reinstall libraries
|
||||
// libraries of Forge and OptiFine should be obtained by installation.
|
||||
|
||||
@@ -45,6 +45,7 @@ import org.jackhuang.hmcl.util.*;
|
||||
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 final class LauncherHelper {
|
||||
}
|
||||
|
||||
private static Task<JavaVersion> 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))
|
||||
@@ -416,7 +417,7 @@ public final class LauncherHelper {
|
||||
JavaVersionConstraint violatedMandatoryConstraint = null;
|
||||
List<JavaVersionConstraint> violatedSuggestedConstraints = null;
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion.toString());
|
||||
for (JavaVersionConstraint constraint : JavaVersionConstraint.ALL) {
|
||||
if (constraint.appliesToVersion(gameVersion, version, javaVersion, analyzer)) {
|
||||
if (!constraint.checkJava(gameVersion, version, javaVersion)) {
|
||||
@@ -549,14 +550,14 @@ public final class LauncherHelper {
|
||||
// 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"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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.Metadata;
|
||||
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 final class TexturesLoader {
|
||||
}
|
||||
|
||||
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 final class TexturesLoader {
|
||||
}, uuidFallback);
|
||||
}
|
||||
|
||||
public static ObjectBinding<LoadedTexture> skinBinding(Account account) {
|
||||
public static ObservableValue<LoadedTexture> 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<LoadedTexture> 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<String, String> 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 @@ public final class TexturesLoader {
|
||||
static final WeakHashMap<Canvas, SkinBindingChangeListener> hole = new WeakHashMap<>();
|
||||
|
||||
final WeakReference<Canvas> canvasRef;
|
||||
final ObjectBinding<LoadedTexture> binding;
|
||||
final ObservableValue<LoadedTexture> binding;
|
||||
|
||||
SkinBindingChangeListener(Canvas canvas, ObjectBinding<LoadedTexture> binding) {
|
||||
SkinBindingChangeListener(Canvas canvas, ObservableValue<LoadedTexture> binding) {
|
||||
this.canvasRef = new WeakReference<>(canvas);
|
||||
this.binding = binding;
|
||||
}
|
||||
@@ -293,14 +325,14 @@ public final class TexturesLoader {
|
||||
}
|
||||
}
|
||||
|
||||
public static void fxAvatarBinding(Canvas canvas, ObjectBinding<LoadedTexture> skinBinding) {
|
||||
public static void fxAvatarBinding(Canvas canvas, ObservableValue<LoadedTexture> 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 final class TexturesLoader {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -131,13 +131,13 @@ public final class Config implements Cloneable, Observable {
|
||||
private ObjectProperty<SupportedLocale> 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);
|
||||
|
||||
@SerializedName("downloadType")
|
||||
private StringProperty downloadType = new SimpleStringProperty("mcbbs");
|
||||
private StringProperty downloadType = new SimpleStringProperty(DownloadProviders.DEFAULT_RAW_PROVIDER_ID);
|
||||
|
||||
@SerializedName("autoChooseDownloadType")
|
||||
private BooleanProperty autoChooseDownloadType = new SimpleBooleanProperty(true);
|
||||
@@ -393,8 +393,8 @@ public final class Config implements Cloneable, Observable {
|
||||
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 final class Config implements Cloneable, Observable {
|
||||
return y;
|
||||
}
|
||||
|
||||
public void setY(double height) {
|
||||
this.y.set(height);
|
||||
public void setY(double y) {
|
||||
this.y.set(y);
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
|
||||
@@ -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 @@ final class 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().<Map<?, ?>>fromJson(rawContent, Map.class));
|
||||
for (Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>> dfu : collectDFU()) {
|
||||
if (configVersion < dfu.getKey()) {
|
||||
dfu.getValue().accept(deserialized, unmodifiableRawJson);
|
||||
configVersion = dfu.getKey();
|
||||
Map<?, ?> rawJson = Collections.unmodifiableMap(new Gson().<Map<?, ?>>fromJson(rawContent, Map.class));
|
||||
|
||||
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<Object, Object> 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()));
|
||||
|
||||
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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
deserialized.setConfigVersion(CURRENT_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
* <p>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.</p>
|
||||
* <p>The return value should a target version number of this item.</p>
|
||||
*
|
||||
* <p>The last item must return CURRENT_VERSION, as the config file should always being updated to the latest version.</p>
|
||||
*/
|
||||
private static List<Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>>> collectDFU() {
|
||||
List<Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>>> 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);
|
||||
|
||||
tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
|
||||
((Map<?, ?>) uuidMap).forEach((key, value) -> {
|
||||
Map<Object, Object> 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()));
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
return dfu;
|
||||
}
|
||||
}
|
||||
@@ -55,10 +55,10 @@ public final class 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 @@ public final class 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(
|
||||
|
||||
@@ -31,7 +31,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.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 final class VersionSetting implements Cloneable {
|
||||
launcherVisibilityProperty.set(launcherVisibility);
|
||||
}
|
||||
|
||||
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version) {
|
||||
public Task<JavaVersion> getJavaVersion(GameVersionNumber gameVersion, Version version) {
|
||||
return getJavaVersion(gameVersion, version, true);
|
||||
}
|
||||
|
||||
public Task<JavaVersion> getJavaVersion(VersionNumber gameVersion, Version version, boolean checkJava) {
|
||||
public Task<JavaVersion> getJavaVersion(GameVersionNumber gameVersion, Version version, boolean checkJava) {
|
||||
return Task.runAsync(Schedulers.javafx(), () -> {
|
||||
if (StringUtils.isBlank(getJava())) {
|
||||
setJava(StringUtils.isBlank(getJavaDir()) ? "Auto" : "Custom");
|
||||
|
||||
@@ -37,10 +37,7 @@ import javafx.stage.StageStyle;
|
||||
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;
|
||||
@@ -64,10 +61,11 @@ import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.*;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
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 +203,37 @@ public final class Controllers {
|
||||
|
||||
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,13 +255,13 @@ public final class Controllers {
|
||||
|
||||
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()));
|
||||
|
||||
stage.getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(stage);
|
||||
stage.setTitle(Metadata.FULL_TITLE);
|
||||
stage.initStyle(StageStyle.TRANSPARENT);
|
||||
stage.setScene(scene);
|
||||
|
||||
@@ -28,9 +28,8 @@ import javafx.scene.layout.StackPane;
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -70,7 +69,7 @@ public class CrashWindow extends Stage {
|
||||
|
||||
Scene scene = new Scene(pane, 800, 480);
|
||||
setScene(scene);
|
||||
getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(this);
|
||||
setTitle(i18n("message.error"));
|
||||
|
||||
setOnCloseRequest(e -> System.exit(1));
|
||||
|
||||
@@ -43,6 +43,7 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
@@ -676,6 +677,16 @@ public final class FXUtils {
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
}
|
||||
|
||||
public static void setIcon(Stage stage) {
|
||||
String icon;
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
icon = "/assets/img/icon.png";
|
||||
} else {
|
||||
icon = "/assets/img/icon@4x.png";
|
||||
}
|
||||
stage.getIcons().add(newBuiltinImage(icon));
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress IllegalArgumentException since the url is supposed to be correct definitely.
|
||||
*
|
||||
@@ -769,8 +780,8 @@ public final class FXUtils {
|
||||
Task.runAsync(() -> {
|
||||
Path newPath = Files.createTempFile("hmcl-net-resource-cache-", ".cache");
|
||||
try ( // Make sure the file is released from JVM before we put the path into remoteImageCache.
|
||||
OutputStream outputStream = Files.newOutputStream(newPath);
|
||||
PNGWriter writer = new PNGWriter(outputStream, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL)
|
||||
OutputStream outputStream = Files.newOutputStream(newPath);
|
||||
PNGWriter writer = new PNGWriter(outputStream, PNGType.RGBA, PNGWriter.DEFAULT_COMPRESS_LEVEL)
|
||||
) {
|
||||
writer.write(PNGJavaFXUtils.asArgbImage(image));
|
||||
} catch (IOException e) {
|
||||
@@ -907,7 +918,9 @@ public final class FXUtils {
|
||||
content.putString(text);
|
||||
Clipboard.getSystemClipboard().setContent(content);
|
||||
|
||||
Controllers.showToast(i18n("message.copied"));
|
||||
if (!Controllers.isStopped()) {
|
||||
Controllers.showToast(i18n("message.copied"));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Node> parseSegment(String segment, Consumer<String> hyperlinkAction) {
|
||||
|
||||
@@ -66,7 +66,6 @@ import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
@@ -99,7 +98,7 @@ public class GameCrashWindow extends Stage {
|
||||
this.version = version;
|
||||
this.launchOptions = launchOptions;
|
||||
this.logs = logs;
|
||||
this.analyzer = LibraryAnalyzer.analyze(version);
|
||||
this.analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
|
||||
|
||||
memory = Optional.ofNullable(launchOptions.getMaxMemory()).map(i -> i + " MB").orElse("-");
|
||||
|
||||
@@ -116,7 +115,7 @@ public class GameCrashWindow extends Stage {
|
||||
setScene(new Scene(view, 800, 480));
|
||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
setTitle(i18n("game.crash.title"));
|
||||
getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(this);
|
||||
|
||||
analyzeCrashReport();
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ import org.jackhuang.hmcl.setting.Theme;
|
||||
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;
|
||||
@@ -170,7 +170,7 @@ public class InstallerItem extends Control {
|
||||
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 -> {
|
||||
@@ -204,7 +204,7 @@ public class InstallerItem extends Control {
|
||||
|
||||
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};
|
||||
|
||||
@@ -60,7 +60,6 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.parseEscapeSequence;
|
||||
@@ -98,7 +97,7 @@ public final class LogWindow extends Stage {
|
||||
setScene(new Scene(impl, 800, 480));
|
||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
setTitle(i18n("logwindow.title"));
|
||||
getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(this);
|
||||
|
||||
levelShownMap.values().forEach(property -> property.addListener((a, b, newValue) -> shakeLogs()));
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import javafx.scene.web.WebEngine;
|
||||
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;
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newBuiltinImage;
|
||||
|
||||
public class WebStage extends Stage {
|
||||
protected final StackPane pane = new StackPane();
|
||||
@@ -44,7 +43,7 @@ public class WebStage extends Stage {
|
||||
public WebStage(int width, int height) {
|
||||
setScene(new Scene(pane, width, height));
|
||||
getScene().getStylesheets().addAll(Theme.getTheme().getStylesheets(config().getLauncherFontFamily()));
|
||||
getIcons().add(newBuiltinImage("/assets/img/icon.png"));
|
||||
FXUtils.setIcon(this);
|
||||
webView.getEngine().setUserDataDirectory(Metadata.HMCL_DIRECTORY.toFile());
|
||||
webView.setContextMenuEnabled(false);
|
||||
progressBar.progressProperty().bind(webView.getEngine().getLoadWorker().progressProperty());
|
||||
|
||||
@@ -58,7 +58,7 @@ import org.jackhuang.hmcl.ui.FXUtils;
|
||||
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;
|
||||
|
||||
@@ -40,7 +40,7 @@ import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class MultiFileItem<T> extends VBox {
|
||||
public final class MultiFileItem<T> extends VBox {
|
||||
private final ObjectProperty<T> selectedData = new SimpleObjectProperty<>(this, "selectedData");
|
||||
private final ObjectProperty<T> fallbackData = new SimpleObjectProperty<>(this, "fallbackData");
|
||||
|
||||
@@ -176,8 +176,8 @@ public class MultiFileItem<T> extends VBox {
|
||||
}
|
||||
}
|
||||
|
||||
public static class StringOption<T> extends Option<T> {
|
||||
private JFXTextField customField = new JFXTextField();
|
||||
public static final class StringOption<T> extends Option<T> {
|
||||
private final JFXTextField customField = new JFXTextField();
|
||||
|
||||
public StringOption(String title, T data) {
|
||||
super(title, data);
|
||||
@@ -196,7 +196,7 @@ public class MultiFileItem<T> extends VBox {
|
||||
}
|
||||
|
||||
public StringOption<T> bindBidirectional(Property<String> property) {
|
||||
customField.textProperty().bindBidirectional(property);
|
||||
FXUtils.bindString(customField, property);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -230,8 +230,8 @@ public class MultiFileItem<T> extends VBox {
|
||||
}
|
||||
}
|
||||
|
||||
public static class FileOption<T> extends Option<T> {
|
||||
private FileSelector selector = new FileSelector();
|
||||
public static final class FileOption<T> extends Option<T> {
|
||||
private final FileSelector selector = new FileSelector();
|
||||
|
||||
public FileOption(String title, T data) {
|
||||
super(title, data);
|
||||
|
||||
@@ -84,7 +84,8 @@ class AdditionalInstallersPage extends InstallersPage {
|
||||
|
||||
@Override
|
||||
protected void reload() {
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version.resolvePreservingPatches(repository));
|
||||
Version resolved = version.resolvePreservingPatches(repository);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved, repository.getGameVersion(resolved).orElse(null));
|
||||
String game = analyzer.getVersion(MINECRAFT).orElse(null);
|
||||
String currentGameVersion = Lang.nonNull(getVersion("game"), game);
|
||||
|
||||
|
||||
@@ -298,7 +298,7 @@ public final class VersionsPage extends BorderPane implements WizardPage, Refres
|
||||
|
||||
content.setTitle(remoteVersion.getSelfVersion());
|
||||
if (remoteVersion.getReleaseDate() != null) {
|
||||
content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate().toInstant()));
|
||||
content.setSubtitle(formatDateTime(remoteVersion.getReleaseDate()));
|
||||
} else {
|
||||
content.setSubtitle(null);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public class AboutPage extends StackPane {
|
||||
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");
|
||||
@@ -61,13 +61,19 @@ public class AboutPage extends StackPane {
|
||||
bangbang93.setTitle("bangbang93");
|
||||
bangbang93.setSubtitle(i18n("about.thanks_to.bangbang93.statement"));
|
||||
bangbang93.setExternalLink("https://bmclapi2.bangbang93.com/");
|
||||
|
||||
|
||||
IconedTwoLineListItem glavo = new IconedTwoLineListItem();
|
||||
glavo.setImage(FXUtils.newBuiltinImage("/assets/img/glavo.png"));
|
||||
glavo.setTitle("Glavo");
|
||||
glavo.setSubtitle(i18n("about.thanks_to.glavo.statement"));
|
||||
glavo.setExternalLink("https://github.com/Glavo");
|
||||
|
||||
|
||||
IconedTwoLineListItem zekerzhayard = new IconedTwoLineListItem();
|
||||
zekerzhayard.setImage(FXUtils.newBuiltinImage("/assets/img/zekerzhayard.png"));
|
||||
zekerzhayard.setTitle("ZekerZhayard");
|
||||
zekerzhayard.setSubtitle(i18n("about.thanks_to.zekerzhayard.statement"));
|
||||
zekerzhayard.setExternalLink("https://github.com/ZekerZhayard");
|
||||
|
||||
IconedTwoLineListItem zkitefly = new IconedTwoLineListItem();
|
||||
zkitefly.setImage(FXUtils.newBuiltinImage("/assets/img/zkitefly.png"));
|
||||
zkitefly.setTitle("Zkitefly");
|
||||
@@ -85,11 +91,11 @@ public class AboutPage extends StackPane {
|
||||
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"));
|
||||
@@ -104,12 +110,12 @@ public class AboutPage extends StackPane {
|
||||
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");
|
||||
|
||||
thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zkitefly ,mcbbs, mcmod, gamerteam, redLnn, contributors, users);
|
||||
thanks.getContent().setAll(yushijinhun, bangbang93, glavo, zekerzhayard, zkitefly, /* mcbbs, */ mcmod, gamerteam, redLnn, contributors, users);
|
||||
}
|
||||
|
||||
ComponentList dep = new ComponentList();
|
||||
|
||||
@@ -53,9 +53,9 @@ import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
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;
|
||||
|
||||
|
||||
@@ -43,15 +43,16 @@ import org.jackhuang.hmcl.ui.nbt.NBTEditorPage;
|
||||
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.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,9 +119,8 @@ public class RootPage extends DecoratorAnimatedPage implements DecoratorPage {
|
||||
List<Version> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted(Comparator
|
||||
.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L)
|
||||
: version.getReleaseTime())
|
||||
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.comparing((Version version) -> Lang.requireNonNullElse(version.getReleaseTime(), Instant.EPOCH))
|
||||
.thenComparing(version -> VersionNumber.asVersion(repository.getGameVersion(version).orElse(version.getId()))))
|
||||
.collect(Collectors.toList());
|
||||
runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile())
|
||||
|
||||
@@ -27,10 +27,10 @@ import org.jackhuang.hmcl.setting.Settings;
|
||||
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;
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.jackhuang.hmcl.util.Holder;
|
||||
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 @@ public class SponsorPage extends StackPane {
|
||||
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 @@ public class SponsorPage extends StackPane {
|
||||
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 class SponsorPage extends StackPane {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Date getCreateTime() {
|
||||
public Instant getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ import javafx.scene.control.Skin;
|
||||
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.Lang;
|
||||
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 class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
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 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
JFXComboBox<String> 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);
|
||||
|
||||
@@ -415,7 +413,7 @@ public class DownloadListPage extends Control implements DecoratorPage, VersionP
|
||||
searchAction.handle(event);
|
||||
});
|
||||
lastPageButton.setDisable(true);
|
||||
control.pageCount.addListener((observable, oldValue, newValue) -> lastPageButton.setDisable(control.pageCount.get() == -1));
|
||||
control.pageCount.addListener((observable, oldValue, newValue) -> lastPageButton.setDisable(control.pageCount.get() == -1 || control.pageOffset.get() >= control.pageCount.get() - 1));
|
||||
|
||||
Pane placeholder = new Pane();
|
||||
HBox.setHgrow(placeholder, Priority.SOMETIMES);
|
||||
|
||||
@@ -32,9 +32,9 @@ import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.ModLoaderType;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.mod.RemoteMod;
|
||||
import org.jackhuang.hmcl.mod.RemoteModRepository;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -51,7 +51,7 @@ import org.jackhuang.hmcl.util.*;
|
||||
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;
|
||||
@@ -254,13 +254,13 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
openMcmodButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
runInFX(() -> FXUtils.installFastTooltip(openMcmodButton, i18n("mods.mcmod")));
|
||||
|
||||
if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) {
|
||||
JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
openMcbbsButton.setExternalLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs()));
|
||||
descriptionPane.getChildren().add(openMcbbsButton);
|
||||
openMcbbsButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
runInFX(() -> FXUtils.installFastTooltip(openMcbbsButton, i18n("mods.mcbbs")));
|
||||
}
|
||||
// if (StringUtils.isNotBlank(getSkinnable().mod.getMcbbs())) {
|
||||
// JFXHyperlink openMcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
// openMcbbsButton.setExternalLink(ModManager.getMcbbsUrl(getSkinnable().mod.getMcbbs()));
|
||||
// descriptionPane.getChildren().add(openMcbbsButton);
|
||||
// openMcbbsButton.setMinWidth(Region.USE_PREF_SIZE);
|
||||
// runInFX(() -> FXUtils.installFastTooltip(openMcbbsButton, i18n("mods.mcbbs")));
|
||||
// }
|
||||
}
|
||||
|
||||
JFXHyperlink openUrlButton = new JFXHyperlink(control.page.getLocalizedOfficialPage());
|
||||
@@ -292,8 +292,9 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
if (control.versions == null) return;
|
||||
|
||||
if (control.version.getProfile() != null && control.version.getVersion() != null) {
|
||||
Version game = control.version.getProfile().getRepository().getResolvedPreservingPatchesVersion(control.version.getVersion());
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(game);
|
||||
HMCLGameRepository repository = control.version.getProfile().getRepository();
|
||||
Version game = repository.getResolvedPreservingPatchesVersion(control.version.getVersion());
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(game, repository.getGameVersion(game).orElse(null));
|
||||
libraryAnalyzer.getVersion(LibraryAnalyzer.LibraryType.MINECRAFT).ifPresent(currentGameVersion -> {
|
||||
Set<ModLoaderType> currentGameModLoaders = libraryAnalyzer.getModLoaders();
|
||||
if (control.versions.containsKey(currentGameVersion)) {
|
||||
@@ -309,7 +310,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -382,7 +383,7 @@ public class DownloadPage extends Control implements DecoratorPage {
|
||||
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:
|
||||
|
||||
@@ -59,10 +59,10 @@ public class GameItem extends Control {
|
||||
this.version = id;
|
||||
|
||||
// GameVersion.minecraftVersion() is a time-costing job (up to ~200 ms)
|
||||
CompletableFuture.supplyAsync(() -> profile.getRepository().getGameVersion(id).orElse(i18n("message.unknown")), POOL_VERSION_RESOLVE)
|
||||
CompletableFuture.supplyAsync(() -> profile.getRepository().getGameVersion(id), POOL_VERSION_RESOLVE)
|
||||
.thenAcceptAsync(game -> {
|
||||
StringBuilder libraries = new StringBuilder(game);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||
StringBuilder libraries = new StringBuilder(game.orElse(i18n("message.unknown")));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id), game.orElse(null));
|
||||
for (LibraryAnalyzer.LibraryMark mark : analyzer) {
|
||||
String libraryId = mark.getLibraryId();
|
||||
String libraryVersion = mark.getLibraryVersion();
|
||||
|
||||
@@ -72,7 +72,7 @@ public class InstallerListPage extends ListPageBase<InstallerItem> implements Ve
|
||||
CompletableFuture.supplyAsync(() -> {
|
||||
gameVersion = profile.getRepository().getGameVersion(version).orElse(null);
|
||||
|
||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId));
|
||||
return LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(versionId), gameVersion);
|
||||
}).thenAcceptAsync(analyzer -> {
|
||||
Function<String, Runnable> removeAction = libraryId -> () -> {
|
||||
profile.getDependency().removeLibraryAsync(version, libraryId)
|
||||
|
||||
@@ -24,6 +24,8 @@ import javafx.collections.ObservableList;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.mod.LocalModFile;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
@@ -84,7 +86,9 @@ public final class ModListPage extends ListPageBase<ModListPageSkin.ModInfoObjec
|
||||
this.profile = profile;
|
||||
this.versionId = id;
|
||||
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(profile.getRepository().getResolvedPreservingPatchesVersion(id));
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
Version resolved = repository.getResolvedPreservingPatchesVersion(id);
|
||||
libraryAnalyzer = LibraryAnalyzer.analyze(resolved, repository.getGameVersion(resolved).orElse(null));
|
||||
modded.set(libraryAnalyzer.hasModLoader());
|
||||
loadMods(profile.getRepository().getModManager(id));
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
}
|
||||
} 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?
|
||||
@@ -391,14 +391,14 @@ class ModListPageSkin extends SkinBase<ModListPage> {
|
||||
getActions().add(officialPageButton);
|
||||
}
|
||||
|
||||
if (modInfo.getMod() != null && StringUtils.isNotBlank(modInfo.getMod().getMcbbs())) {
|
||||
JFXHyperlink mcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
mcbbsButton.setOnAction(e -> {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
FXUtils.openLink(ModManager.getMcbbsUrl(modInfo.getMod().getMcbbs()));
|
||||
});
|
||||
getActions().add(mcbbsButton);
|
||||
}
|
||||
// if (modInfo.getMod() != null && StringUtils.isNotBlank(modInfo.getMod().getMcbbs())) {
|
||||
// JFXHyperlink mcbbsButton = new JFXHyperlink(i18n("mods.mcbbs"));
|
||||
// mcbbsButton.setOnAction(e -> {
|
||||
// fireEvent(new DialogCloseEvent());
|
||||
// FXUtils.openLink(ModManager.getMcbbsUrl(modInfo.getMod().getMcbbs()));
|
||||
// });
|
||||
// getActions().add(mcbbsButton);
|
||||
// }
|
||||
|
||||
if (modInfo.getMod() == null || StringUtils.isBlank(modInfo.getMod().getMcmod())) {
|
||||
JFXHyperlink searchButton = new JFXHyperlink(i18n("mods.mcmod.search"));
|
||||
|
||||
@@ -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 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
* @see <a href="https://www.mcmod.cn">mcmod.cn</a>
|
||||
*/
|
||||
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 enum ModTranslations {
|
||||
}
|
||||
}
|
||||
|
||||
private final String defaultResourceName;
|
||||
private final RemoteResourceManager.RemoteResourceKey remoteResourceKey;
|
||||
private final String resourceName;
|
||||
private List<Mod> mods;
|
||||
private Map<String, Mod> modIdMap; // mod id -> mod
|
||||
private Map<String, Mod> curseForgeMap; // curseforge id -> mod
|
||||
private List<Pair<String, Mod>> 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 enum ModTranslations {
|
||||
.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 @@ public enum ModTranslations {
|
||||
}
|
||||
|
||||
if (mods == null) {
|
||||
if (!loaded()) return false;
|
||||
if (!loadFromResource()) return false;
|
||||
}
|
||||
|
||||
curseForgeMap = new HashMap<>();
|
||||
@@ -166,7 +156,7 @@ public enum ModTranslations {
|
||||
}
|
||||
|
||||
if (mods == null) {
|
||||
if (!loaded()) return false;
|
||||
if (!loadFromResource()) return false;
|
||||
}
|
||||
|
||||
modIdMap = new HashMap<>();
|
||||
@@ -186,7 +176,7 @@ public enum ModTranslations {
|
||||
}
|
||||
|
||||
if (mods == null) {
|
||||
if (!loaded()) return false;
|
||||
if (!loadFromResource()) return false;
|
||||
}
|
||||
|
||||
keywords = new ArrayList<>();
|
||||
|
||||
@@ -46,7 +46,7 @@ import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
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 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
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)
|
||||
|
||||
@@ -29,7 +29,7 @@ import org.jackhuang.hmcl.game.World;
|
||||
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 class WorldListItem extends Control {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ public class WorldListPage extends ListPageBase<WorldListItem> implements Versio
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
|
||||
+5
-6
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
@@ -32,13 +32,12 @@ import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
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<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
|
||||
private static BooleanBinding outdated = Bindings.createBooleanBinding(
|
||||
private static final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
|
||||
private static final BooleanBinding outdated = Bindings.createBooleanBinding(
|
||||
() -> {
|
||||
RemoteVersion latest = latestVersion.get();
|
||||
if (latest == null || isDevelopmentVersion(Metadata.VERSION)) {
|
||||
@@ -46,11 +45,11 @@ public final class 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());
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.upgrade.hmcl;
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<InputStream, IOException> localResourceSupplier;
|
||||
private String localResourceSha1 = null;
|
||||
|
||||
public RemoteResourceKey(String namespace, String name, String version, ExceptionalSupplier<InputStream, IOException> 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<String, Map<String, Map<String, RemoteResource>>> remoteResources = new ConcurrentHashMap<>();
|
||||
|
||||
private static final Map<String, RemoteResourceKey> keys = new ConcurrentHashMap<>();
|
||||
|
||||
public static void init() {
|
||||
Task.<Map<String, Map<String, Map<String, RemoteResource>>>>supplyAsync(() ->
|
||||
IntegrityChecker.isSelfVerified() ? HttpRequest.GET(Metadata.RESOURCE_UPDATE_URL).getJson(
|
||||
new TypeToken<Map<String, Map<String, Map<String, RemoteResource>>>>() {
|
||||
}.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<InputStream, IOException> 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;
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,8 @@ import javafx.scene.control.Alert.AlertType;
|
||||
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;
|
||||
|
||||
@@ -8,7 +8,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.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 @@ public final class NativePatcher {
|
||||
|
||||
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<Library> newLibraries = new ArrayList<>();
|
||||
@@ -66,7 +66,7 @@ public final class NativePatcher {
|
||||
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 final class NativePatcher {
|
||||
|
||||
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;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
@@ -34,6 +34,7 @@ about.thanks_to.contributors=All contributors on GitHub
|
||||
about.thanks_to.contributors.statement=Without the awesome open-source community, Hello Minecraft! Launcher would not make it so far.
|
||||
about.thanks_to.gamerteam.statement=For providing the default background image.
|
||||
about.thanks_to.glavo.statement=Contribute a lot of technical support to HMCL.
|
||||
about.thanks_to.zekerzhayard.statement=Contribute a lot of technical support to HMCL.
|
||||
about.thanks_to.zkitefly.statement=Contribute a lot of technical support to HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS (Minecraft Chinese Forum)
|
||||
about.thanks_to.mcbbs.statement=For providing mcbbs.net download mirror for Mainland China users.
|
||||
@@ -204,7 +205,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 +220,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
|
||||
@@ -334,7 +340,7 @@ download.java=Downloading Java
|
||||
download.javafx=Downloading dependencies for the launcher...
|
||||
download.javafx.notes=We are currently downloading dependencies for HMCL from the Internet.\n\
|
||||
\n\
|
||||
You can click on 'Change Download Mirror' to select the download mirror or click on "Cancel" to stop and exit.\n\
|
||||
You can click on 'Change Download Source' to select the download source or\nclick on 'Cancel' to stop and exit.\n\
|
||||
Note: If your download speed is too slow, you can try switching to another mirror.
|
||||
download.javafx.component=Downloading module %s
|
||||
download.javafx.prepare=Preparing to download
|
||||
@@ -356,9 +362,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\
|
||||
@@ -522,6 +526,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\
|
||||
@@ -1165,7 +1170,7 @@ settings.type.special.edit=Edit Current Instance Settings
|
||||
settings.type.special.edit.hint=Current instance [%s] has enabled per-instance settings, all options on this page will NOT affect that instance. Click here to modify its own options.
|
||||
|
||||
sponsor=Donators
|
||||
sponsor.bmclapi=Downloads are provided by BMCLAPI and MCBBS. Click here for more information.
|
||||
sponsor.bmclapi=Downloads are provided by BMCLAPI. Click here for more information.
|
||||
sponsor.hmcl=Hello Minecraft! Launcher is a FOSS Minecraft launcher which allows users to manage multiple Minecraft instances easily. You can donate to us on Afdian to support our file hosting and project development. Click here for more information.
|
||||
|
||||
system.architecture=Architecture
|
||||
|
||||
@@ -34,6 +34,7 @@ about.thanks_to.contributors=Todos los colaboradores en GitHub
|
||||
about.thanks_to.contributors.statement=Sin la impresionante comunidad de código abierto, Hello Minecraft! Launcher no habría llegado tan lejos.
|
||||
about.thanks_to.gamerteam.statement=Por proporcionar la imagen de fondo por defecto.
|
||||
about.thanks_to.glavo.statement=Aportar mucho apoyo técnico a HMCL.
|
||||
about.thanks_to.zekerzhayard.statement=Aportar mucho apoyo técnico a HMCL.
|
||||
about.thanks_to.zkitefly.statement=Aportar mucho apoyo técnico a HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS (Foro chino de Minecraft)
|
||||
about.thanks_to.mcbbs.statement=Por proporcionar un espejo de descarga de mcbbs.net para los usuarios de China continental.
|
||||
@@ -187,7 +188,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 +204,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
|
||||
@@ -314,7 +320,7 @@ download.provider.mirror=Desde espejo
|
||||
download.javafx=Descargar las dependencias para el launcher...
|
||||
download.javafx.notes=Estamos descargando dependencias para HMCL desde Internet.\n\
|
||||
\n\
|
||||
Puede hacer clic en "Cambiar el espejo de descarga" para seleccionar el espejo de descarga o hacer clic en "Cancelar" para detener y salir.\n\
|
||||
Puede hacer clic en "Cambiar fuente de descarga" para seleccionar el espejo de descarga o hacer clic en "Cancelar" para detener y salir.\n\
|
||||
Nota: Si su velocidad de descarga es demasiado lenta, puede intentar cambiar a otro espejo.
|
||||
download.javafx.component=Descargando módulo %s
|
||||
download.javafx.prepare=Preparando la descarga
|
||||
@@ -451,6 +457,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 <a href="https://www.curseforge.com/minecraft/mc-mods/night-config-fixes">Night Config Fixes</a>, que puede ayudarte con este problema. \nPara obtener más información, visite el <a href="https://www.github.com/Fuzss/nightconfigfixes">repositorio de GitHub</a> del mod.
|
||||
game.crash.reason.mod_resolution_collection=El juego se ha bloqueado porque la versión del mod no es compatible.\n\
|
||||
\n\
|
||||
@@ -1042,7 +1049,7 @@ settings.type.special.edit=Editar ajustes de la instancia actual
|
||||
settings.type.special.edit.hint=La instancia actual [%s] tiene activada la configuración por instancia, todas las opciones de esta página NO afectarán a esa instancia. Haga clic aquí para modificar sus propias opciones.
|
||||
|
||||
sponsor=Donantes
|
||||
sponsor.bmclapi=Las descargas son proporcionadas por BMCLAPI y MCBBS. Haz clic aquí para obtener más información.
|
||||
sponsor.bmclapi=Las descargas son proporcionadas por BMCLAPI. Haz clic aquí para obtener más información.
|
||||
sponsor.hmcl=¡Hello Minecraft! Launcher es un launcher FOSS de Minecraft que permite a los usuarios gestionar múltiples instancias de Minecraft fácilmente. Usted puede donar a nosotros en Afdian para apoyar nuestro alojamiento de archivos y el desarrollo del proyecto. Haga clic aquí para obtener más información.
|
||||
|
||||
system.architecture=Arquitectura
|
||||
|
||||
@@ -32,6 +32,7 @@ about.thanks_to.contributors=問題、プルリクエストなどを介してこ
|
||||
about.thanks_to.contributors.statement=Hello Minecraftをサポートしてくれたオープンソースコミュニティに感謝します!ランチャー
|
||||
about.thanks_to.gamerteam.statement=デフォルトの背景画像プロバイダー。
|
||||
about.thanks_to.glavo.statement=HMCLに多くのテクニカルサポートを提供
|
||||
about.thanks_to.zekerzhayard.statement=HMCLに多くのテクニカルサポートを提供
|
||||
about.thanks_to.zkitefly.statement=HMCLに多くのテクニカルサポートを提供。
|
||||
about.thanks_to.mcbbs=MCBBS
|
||||
about.thanks_to.mcbbs.statement=MCBBSダウンロードソースプロバイダー。
|
||||
@@ -171,7 +172,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 +188,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=構造
|
||||
@@ -378,6 +384,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 をダウンロードすることができます。
|
||||
@@ -854,7 +861,7 @@ settings.type.special.edit=現在のゲーム設定を構成します
|
||||
settings.type.special.edit.hint=現在のゲームバージョン %s で特殊な設定が有効になっているため、現在のページのオプションはそのゲームバージョンに適用されません。リンクをクリックして、現在のゲーム設定を構成します。
|
||||
|
||||
sponsor=寄付
|
||||
sponsor.bmclapi=ダウンロードサービスはBMCLAPIとMCBBSによって提供されます。詳細については、ここをクリックしてください。
|
||||
sponsor.bmclapi=ダウンロードサービスはBMCLAPIによって提供されます。詳細については、ここをクリックしてください。
|
||||
sponsor.hmcl=こんにちはマインクラフト! Launcherは、無料のオープンソースのMinecraftランチャーであり、ユーザーは複数の個別のMinecraftインストールを簡単に管理できます。私たちはAfdianを使用して、ホスティングとプロジェクト開発の費用を引き続き支払います。詳細については、ここをクリックしてください。
|
||||
|
||||
system.architecture=Arch
|
||||
|
||||
@@ -34,6 +34,7 @@ about.thanks_to.contributors=Всем участникам на GitHub
|
||||
about.thanks_to.contributors.statement=Без потрясающего сообщества с открытым исходным кодом, Hello Minecraft! Launcher не смог бы добраться так далеко.
|
||||
about.thanks_to.gamerteam.statement=За предоставление фонового изображения.
|
||||
about.thanks_to.glavo.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.zekerzhayard.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.zkitefly.statement=Оказал большую техническую поддержку HMCL.
|
||||
about.thanks_to.mcbbs=MCBBS (Minecraft Chinese Forum)
|
||||
about.thanks_to.mcbbs.statement=За предоставление зеркала скачивания MCBBS.NET для пользователей Китая.
|
||||
@@ -185,7 +186,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 +204,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
|
||||
@@ -315,7 +321,7 @@ download.java=Скачивание Java
|
||||
download.javafx=Скачивание зависимостей лаунчера...
|
||||
download.javafx.notes=Скачиваем зависимостей лаунчера из интернета.\n\
|
||||
\n\
|
||||
Можно нажать «Изменить зеркало скачивания», чтобы выбрать зеркало загрузки, или нажать «Отмена», чтобы остановить и выйти.\n\
|
||||
Можно нажать «Изменить источник скачивания», чтобы выбрать зеркало загрузки, или нажать «Отмена», чтобы остановить и выйти.\n\
|
||||
Примечание\: Если ваша скорость скачивания слишком низкая, вы можете попробовать переключиться на другое зеркало.
|
||||
download.javafx.component=Скачивание %s
|
||||
download.javafx.prepare=Подготовка к скачиванию
|
||||
@@ -401,6 +407,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 со страницы скачивания или через сайт.
|
||||
@@ -929,7 +936,7 @@ settings.type.special.edit=Изменить настройки текущего
|
||||
settings.type.special.edit.hint=В текущем экземпляре [%s] включены раздельные настройки для экземпляров, параметры на этой странице НЕ повлияют на этот экземпляр. Нажмите здесь, чтобы изменить раздельные параметры.
|
||||
|
||||
sponsor=Спонсоры
|
||||
sponsor.bmclapi=Скачивание предоставляется BMCLAPI и MCBBS. Нажмите здесь для получения подробностей.
|
||||
sponsor.bmclapi=Скачивание предоставляется BMCLAPI. Нажмите здесь для получения подробностей.
|
||||
sponsor.hmcl=Hello Minecraft! Launcher — лаунчер Minecraft с открытым исходным кодом, который позволяет пользователям легко управлять несколькими экземплярами Minecraft. Пожертвуйте нам на Afdian для поддержки нашего файлового хостинга и развития проекта. Для получения подробностей нажмите здесь.
|
||||
|
||||
system.architecture=Архитектура
|
||||
|
||||
@@ -32,6 +32,7 @@ about.thanks_to.contributors=所有通過 Issues、Pull Requests 等管道參與
|
||||
about.thanks_to.contributors.statement=沒有開源社區的支持,Hello Minecraft! Launcher 無法走到今天
|
||||
about.thanks_to.gamerteam.statement=提供預設背景圖
|
||||
about.thanks_to.glavo.statement=為 HMCL 貢獻許多技術支持
|
||||
about.thanks_to.zekerzhayard.statement=為 HMCL 貢獻許多技術支持
|
||||
about.thanks_to.zkitefly.statement=為 HMCL 貢獻許多技術支持
|
||||
about.thanks_to.mcbbs=MCBBS 我的世界中文論壇
|
||||
about.thanks_to.mcbbs.statement=提供 MCBBS 下載地點
|
||||
@@ -212,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=有特定地圖
|
||||
@@ -227,6 +228,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=夢幻菜單
|
||||
@@ -441,6 +447,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。
|
||||
@@ -1028,7 +1035,7 @@ settings.type.special.edit=編輯遊戲特定設置
|
||||
settings.type.special.edit.hint=當前遊戲版本 %s 啟動了遊戲特定設置,本頁面選項不對當前遊戲生效。點擊連結以修改當前遊戲設置。
|
||||
|
||||
sponsor=贊助
|
||||
sponsor.bmclapi=大中華區下載源由 BMCLAPI 和我的世界中文論壇 (MCBBS) 提供高速下載服務
|
||||
sponsor.bmclapi=大中華區下載源由 BMCLAPI 提供高速下載服務
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一個免費、開源的 Minecraft 啟動器,允許玩家方便快捷地安裝、管理、執行遊戲。您的贊助將幫助 Hello Minecraft! Launcher 獲得更好的發展、支援穩定高速的遊戲安裝與檔案下載服務。點選此處查閱更多詳細訊息。
|
||||
|
||||
system.architecture=架構
|
||||
|
||||
@@ -32,6 +32,7 @@ about.thanks_to.contributors=所有通过 Issues、Pull Requests 等方式参与
|
||||
about.thanks_to.contributors.statement=没有开源社区的支持,Hello Minecraft! Launcher 就无法走到今天
|
||||
about.thanks_to.gamerteam.statement=提供默认背景图
|
||||
about.thanks_to.glavo.statement=为 HMCL 贡献许多技术支持
|
||||
about.thanks_to.zekerzhayard.statement=为 HMCL 贡献许多技术支持
|
||||
about.thanks_to.zkitefly.statement=为 HMCL 贡献许多技术支持
|
||||
about.thanks_to.mcbbs=MCBBS 我的世界中文论坛
|
||||
about.thanks_to.mcbbs.statement=提供 MCBBS 下载源
|
||||
@@ -213,7 +214,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 +232,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=装备武器
|
||||
@@ -434,7 +440,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。
|
||||
@@ -445,6 +450,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。
|
||||
@@ -1036,7 +1042,7 @@ settings.type.special.edit=编辑游戏特定设置
|
||||
settings.type.special.edit.hint=当前游戏版本 %s 启用了游戏特定设置,因此本页面选项不对该游戏生效。点击链接前往该游戏版本的游戏特定设置页
|
||||
|
||||
sponsor=赞助
|
||||
sponsor.bmclapi=国内下载源由 BMCLAPI 和我的世界中文论坛 (MCBBS) 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好的提供稳定高速的下载服务,[点击此处查阅详细信息]
|
||||
sponsor.bmclapi=国内下载源由 BMCLAPI 提供高速下载服务。BMCLAPI 为公益服务,赞助 BMCLAPI 可以帮助作者更好的提供稳定高速的下载服务,[点击此处查阅详细信息]
|
||||
sponsor.hmcl=Hello Minecraft! Launcher 是一个免费、自由、开放源代码的 Minecraft 启动器。您的赞助将帮助 Hello Minecraft! Launcher 获得更好的发展,还可以获得 HMCL 的开发进展。[点击此处查阅更多详细信息]
|
||||
|
||||
system.architecture=架构
|
||||
|
||||
@@ -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-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"url": "https://oss.sonatype.org/content/repositories/snapshots/org/lwjgl/lwjgl/3.3.4-SNAPSHOT/lwjgl-3.3.4-20231218.151521-4-natives-linux-riscv64.jar",
|
||||
"sha1": "642848dc964fc5332cda40225771a5143740b49c",
|
||||
"size": 79691
|
||||
}
|
||||
}
|
||||
},
|
||||
"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-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-jemalloc/3.3.4-SNAPSHOT/lwjgl-jemalloc-3.3.4-20231218.151521-4-natives-linux-riscv64.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-linux-riscv64.jar",
|
||||
"sha1": "01129d98674994eb8204f62c26b5717d4d989536",
|
||||
"size": 200633
|
||||
}
|
||||
}
|
||||
},
|
||||
"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-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-openal/3.3.4-SNAPSHOT/lwjgl-openal-3.3.4-20231218.151521-4-natives-linux-riscv64.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-linux-riscv64.jar",
|
||||
"sha1": "bce3174f137d0ffcbbe88ddae305de11c5131137",
|
||||
"size": 520217
|
||||
}
|
||||
}
|
||||
},
|
||||
"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-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-opengl/3.3.4-SNAPSHOT/lwjgl-opengl-3.3.4-20231218.151521-4-natives-linux-riscv64.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-linux-riscv64.jar",
|
||||
"sha1": "13dccf954b781883b0661bd55dfc423f8eece864",
|
||||
"size": 57244
|
||||
}
|
||||
}
|
||||
},
|
||||
"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-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-glfw/3.3.4-SNAPSHOT/lwjgl-glfw-3.3.4-20231218.151521-4-natives-linux-riscv64.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-linux-riscv64.jar",
|
||||
"sha1": "865417f4aa171aa8e7a448d88877a82844896c92",
|
||||
"size": 113875
|
||||
}
|
||||
}
|
||||
},
|
||||
"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-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-stb/3.3.4-SNAPSHOT/lwjgl-stb-3.3.4-20231218.151521-3-natives-linux-riscv64.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-linux-riscv64.jar",
|
||||
"sha1": "f5b96c0fd649cb1e18816f63bed49e0dfe1d1f1a",
|
||||
"size": 217005
|
||||
}
|
||||
}
|
||||
},
|
||||
"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-linux-riscv64",
|
||||
"downloads": {
|
||||
"artifact": {
|
||||
"path": "org/lwjgl/lwjgl-tinyfd/3.3.4-SNAPSHOT/lwjgl-tinyfd-3.3.4-20231218.151521-3-natives-linux-riscv64.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-linux-riscv64.jar",
|
||||
"sha1": "f042c17188c5db910b195d298c284bbfbe550c53",
|
||||
"size": 46885
|
||||
}
|
||||
}
|
||||
},
|
||||
"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,
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ public class AuthlibInjectorDownloader implements AuthlibInjectorArtifactProvide
|
||||
}
|
||||
|
||||
try {
|
||||
new FileDownloadTask(new URL(downloadProvider.get().injectURL(latest.downloadUrl)), artifactLocation.toFile(),
|
||||
new FileDownloadTask(downloadProvider.get().injectURLWithCandidates(latest.downloadUrl), artifactLocation.toFile(),
|
||||
Optional.ofNullable(latest.checksums.get("sha256"))
|
||||
.map(checksum -> new IntegrityCheck("SHA-256", checksum))
|
||||
.orElse(null))
|
||||
|
||||
@@ -155,7 +155,8 @@ public class Skin {
|
||||
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;
|
||||
|
||||
@@ -106,7 +106,7 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
Version original = repository.getVersion(version.getId());
|
||||
Version resolved = original.resolvePreservingPatches(repository);
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(resolved, gameVersion);
|
||||
for (LibraryAnalyzer.LibraryType type : LibraryAnalyzer.LibraryType.values()) {
|
||||
if (!analyzer.has(type))
|
||||
continue;
|
||||
@@ -218,8 +218,9 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
|
||||
if (version.isResolved())
|
||||
throw new IllegalArgumentException("removeLibraryWithoutSavingAsync requires non-resolved version");
|
||||
Version independentVersion = version.resolvePreservingPatches(repository);
|
||||
String gameVersion = repository.getGameVersion(independentVersion).orElse(null);
|
||||
|
||||
return Task.supplyAsync(() -> LibraryAnalyzer.analyze(independentVersion).removeLibrary(libraryId).build());
|
||||
return Task.supplyAsync(() -> LibraryAnalyzer.analyze(independentVersion, gameVersion).removeLibrary(libraryId).build());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -130,12 +130,16 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
return version;
|
||||
}
|
||||
|
||||
public static LibraryAnalyzer analyze(Version version) {
|
||||
public static LibraryAnalyzer analyze(Version version, String gameVersion) {
|
||||
if (version.getInheritsFrom() != null)
|
||||
throw new IllegalArgumentException("LibraryAnalyzer can only analyze independent game version");
|
||||
|
||||
Map<String, Pair<Library, String>> libraries = new HashMap<>();
|
||||
|
||||
if (gameVersion != null) {
|
||||
libraries.put(LibraryType.MINECRAFT.getPatchId(), pair(null, gameVersion));
|
||||
}
|
||||
|
||||
List<Library> rawLibraries = version.resolve(null).getLibraries();
|
||||
for (Library library : rawLibraries) {
|
||||
for (LibraryType type : LibraryType.values()) {
|
||||
|
||||
@@ -63,7 +63,7 @@ public class MaintainTask extends Task<Version> {
|
||||
String mainClass = version.resolve(null).getMainClass();
|
||||
|
||||
if (mainClass != null && mainClass.equals(LibraryAnalyzer.LAUNCH_WRAPPER_MAIN)) {
|
||||
version = maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(unique(version), true), false);
|
||||
version = maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(repository, unique(version), true), false);
|
||||
} else if (mainClass != null && mainClass.equals(LibraryAnalyzer.MOD_LAUNCHER_MAIN)) {
|
||||
// Forge 1.13 and OptiFine
|
||||
version = maintainOptiFineLibrary(repository, maintainGameWithCpwModLauncher(repository, unique(version)), true);
|
||||
@@ -97,8 +97,8 @@ public class MaintainTask extends Task<Version> {
|
||||
return newVersion.setPatches(version.getPatches()).markAsUnresolved();
|
||||
}
|
||||
|
||||
private static Version maintainGameWithLaunchWrapper(Version version, boolean reorderTweakClass) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
private static Version maintainGameWithLaunchWrapper(GameRepository repository, Version version, boolean reorderTweakClass) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
String mainClass = null;
|
||||
|
||||
@@ -147,7 +147,7 @@ public class MaintainTask extends Task<Version> {
|
||||
}
|
||||
|
||||
private static Version maintainGameWithCpwModLauncher(GameRepository repository, Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
|
||||
if (!libraryAnalyzer.has(FORGE)) return version;
|
||||
@@ -205,7 +205,7 @@ public class MaintainTask extends Task<Version> {
|
||||
|
||||
// Fix wrong configurations when launching 1.17+ with Forge.
|
||||
private static Version maintainGameWithCpwBoostrapLauncher(GameRepository repository, Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
|
||||
if (!libraryAnalyzer.has(FORGE) && !libraryAnalyzer.has(NEO_FORGE)) return version;
|
||||
@@ -247,7 +247,7 @@ public class MaintainTask extends Task<Version> {
|
||||
}
|
||||
|
||||
private static Version maintainOptiFineLibrary(GameRepository repository, Version version, boolean remove) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version, null);
|
||||
List<Library> libraries = new ArrayList<>(version.getLibraries());
|
||||
|
||||
if (libraryAnalyzer.has(OPTIFINE)) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
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<RemoteVersion> {
|
||||
private final String libraryId;
|
||||
private final String gameVersion;
|
||||
private final String selfVersion;
|
||||
private final Date releaseDate;
|
||||
private final Instant releaseDate;
|
||||
private final List<String> urls;
|
||||
private final Type type;
|
||||
|
||||
@@ -47,7 +47,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
* @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<String> urls) {
|
||||
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, List<String> urls) {
|
||||
this(libraryId, gameVersion, selfVersion, releaseDate, Type.UNCATEGORIZED, urls);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
* @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<String> urls) {
|
||||
public RemoteVersion(String libraryId, String gameVersion, String selfVersion, Instant releaseDate, Type type, List<String> urls) {
|
||||
this.libraryId = Objects.requireNonNull(libraryId);
|
||||
this.gameVersion = Objects.requireNonNull(gameVersion);
|
||||
this.selfVersion = Objects.requireNonNull(selfVersion);
|
||||
@@ -83,7 +83,7 @@ public class RemoteVersion implements Comparable<RemoteVersion> {
|
||||
return getSelfVersion();
|
||||
}
|
||||
|
||||
public Date getReleaseDate() {
|
||||
public Instant getReleaseDate() {
|
||||
return releaseDate;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.game.Version;
|
||||
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<String> urls) {
|
||||
FabricAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List<String> urls) {
|
||||
super(LibraryAnalyzer.LibraryType.FABRIC_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);
|
||||
|
||||
this.fullVersion = fullVersion;
|
||||
|
||||
@@ -109,7 +109,7 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.task.Task;
|
||||
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 final class ForgeInstallTask extends Task<Version> {
|
||||
@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)
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.download.RemoteVersion;
|
||||
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<String> url) {
|
||||
public ForgeRemoteVersion(String gameVersion, String selfVersion, Instant releaseDate, List<String> url) {
|
||||
super(LibraryAnalyzer.LibraryType.FORGE.getPatchId(), gameVersion, selfVersion, releaseDate, url);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.jackhuang.hmcl.game.Version;
|
||||
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<String> url, ReleaseType type, Date releaseDate) {
|
||||
public GameRemoteVersion(String gameVersion, String selfVersion, List<String> url, ReleaseType type, Instant releaseDate) {
|
||||
super(LibraryAnalyzer.LibraryType.MINECRAFT.getPatchId(), gameVersion, selfVersion, releaseDate, getReleaseType(type), url);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.Constants;
|
||||
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 final class GameRemoteVersionInfo implements Validation {
|
||||
return gameVersion;
|
||||
}
|
||||
|
||||
public Date getTime() {
|
||||
public Instant getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public Date getReleaseTime() {
|
||||
public Instant getReleaseTime() {
|
||||
return releaseTime;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -22,7 +22,7 @@ import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
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;
|
||||
@@ -63,9 +63,9 @@ public final class GameVerificationFixTask extends Task<Void> {
|
||||
@Override
|
||||
public void execute() throws IOException {
|
||||
File jar = dependencyManager.getGameRepository().getVersionJar(version);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, gameVersion);
|
||||
|
||||
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"));
|
||||
|
||||
+1
-1
@@ -190,7 +190,7 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.game.Version;
|
||||
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<String> urls) {
|
||||
QuiltAPIRemoteVersion(String gameVersion, String selfVersion, String fullVersion, Instant datePublished, RemoteMod.Version version, List<String> urls) {
|
||||
super(LibraryAnalyzer.LibraryType.QUILT_API.getPatchId(), gameVersion, selfVersion, datePublished, urls);
|
||||
|
||||
this.fullVersion = fullVersion;
|
||||
|
||||
@@ -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 class ClassicVersion extends Version {
|
||||
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 {
|
||||
|
||||
@@ -47,7 +47,7 @@ public final class CrashReportAnalyzer {
|
||||
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
|
||||
@@ -71,6 +71,7 @@ public final class CrashReportAnalyzer {
|
||||
// Fabric mod resolution
|
||||
MOD_RESOLUTION(Pattern.compile("ModResolutionException: (?<reason>(.*)[\\n\\r]*( - (.*)[\\n\\r]*)+)"), "reason"),
|
||||
FORGEMOD_RESOLUTION(Pattern.compile("Missing or unsupported mandatory dependencies:(?<reason>(.*)[\\n\\r]*(\t(.*)[\\n\\r]*)+)"), "reason"),
|
||||
FORGE_FOUND_DUPLICATE_MODS(Pattern.compile("Found duplicate mods:(?<reason>(.*)\\R*(\t(.*)\\R*)+)"), "reason"),
|
||||
MOD_RESOLUTION_CONFLICT(Pattern.compile("ModResolutionException: Found conflicting mods: (?<sourcemod>.*) conflicts with (?<destmod>.*)"), "sourcemod", "destmod"),
|
||||
MOD_RESOLUTION_MISSING(Pattern.compile("ModResolutionException: Could not find required mod: (?<sourcemod>.*) requires (?<destmod>.*)"), "sourcemod", "destmod"),
|
||||
MOD_RESOLUTION_MISSING_MINECRAFT(Pattern.compile("ModResolutionException: Could not find required mod: (?<mod>.*) requires \\{minecraft @ (?<version>.*)}"), "mod", "version"),
|
||||
@@ -92,7 +93,7 @@ public final class CrashReportAnalyzer {
|
||||
DEBUG_CRASH(Pattern.compile("Manually triggered debug crash")),
|
||||
CONFIG(Pattern.compile("Failed loading config file (?<file>.*?) of type (.*?) for modid (?<id>.*)"), "id", "file"),
|
||||
// Fabric gives some warnings
|
||||
FABRIC_WARNINGS(Pattern.compile("(Warnings were found!|Incompatible mod set!)(.*?)[\\n\\r]+(?<reason>[^\\[]+)\\["), "reason"),
|
||||
FABRIC_WARNINGS(Pattern.compile("(Warnings were found!|Incompatible mod set!|Incompatible mods found!)(.*?)[\\n\\r]+(?<reason>[^\\[]+)\\["), "reason"),
|
||||
// Game crashed when ticking entity
|
||||
ENTITY(Pattern.compile("Entity Type: (?<type>.*)[\\w\\W\\n\\r]*?Entity's Exact location: (?<location>.*)"), "type", "location"),
|
||||
// Game crashed when tesselating block model
|
||||
@@ -114,7 +115,7 @@ public final class CrashReportAnalyzer {
|
||||
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 名字:
|
||||
@@ -124,7 +125,7 @@ public final class CrashReportAnalyzer {
|
||||
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.
|
||||
|
||||
@@ -42,7 +42,7 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameVersion {
|
||||
final class GameVersion {
|
||||
private GameVersion() {
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.jackhuang.hmcl.util.Lang;
|
||||
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.List;
|
||||
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,33 +81,33 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
},
|
||||
// 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
|
||||
public VersionRange getJavaVersionRange(Version version) {
|
||||
public VersionRange<VersionNumber> getJavaVersionRange(Version version) {
|
||||
String javaVersion;
|
||||
if (Objects.requireNonNull(version.getJavaVersion()).getMajorVersion() >= 9) {
|
||||
javaVersion = "" + version.getJavaVersion().getMajorVersion();
|
||||
} 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 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
@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 @@ public enum JavaVersionConstraint {
|
||||
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 enum JavaVersionConstraint {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};;
|
||||
};
|
||||
|
||||
private final int type;
|
||||
private final VersionRange gameVersionRange;
|
||||
private final VersionRange javaVersionRange;
|
||||
private final VersionRange<GameVersionNumber> gameVersionRange;
|
||||
private final VersionRange<VersionNumber> javaVersionRange;
|
||||
|
||||
JavaVersionConstraint(int type, VersionRange gameVersionRange, VersionRange javaVersionRange) {
|
||||
JavaVersionConstraint(int type, VersionRange<GameVersionNumber> gameVersionRange, VersionRange<VersionNumber> javaVersionRange) {
|
||||
this.type = type;
|
||||
this.gameVersionRange = gameVersionRange;
|
||||
this.javaVersionRange = javaVersionRange;
|
||||
@@ -197,39 +197,39 @@ public enum JavaVersionConstraint {
|
||||
return type;
|
||||
}
|
||||
|
||||
public VersionRange getGameVersionRange() {
|
||||
public VersionRange<GameVersionNumber> getGameVersionRange() {
|
||||
return gameVersionRange;
|
||||
}
|
||||
|
||||
public VersionRange getJavaVersionRange(Version version) {
|
||||
public VersionRange<VersionNumber> 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<JavaVersionConstraint> ALL = Lang.immutableListOf(values());
|
||||
|
||||
public static VersionRanges findSuitableJavaVersionRange(VersionNumber gameVersion, Version version) {
|
||||
VersionRange mandatoryJavaRange = VersionRange.all();
|
||||
VersionRange suggestedJavaRange = VersionRange.all();
|
||||
LibraryAnalyzer analyzer = version != null ? LibraryAnalyzer.analyze(version) : null;
|
||||
public static VersionRanges findSuitableJavaVersionRange(GameVersionNumber gameVersion, Version version) {
|
||||
VersionRange<VersionNumber> mandatoryJavaRange = VersionRange.all();
|
||||
VersionRange<VersionNumber> suggestedJavaRange = VersionRange.all();
|
||||
LibraryAnalyzer analyzer = version != null ? LibraryAnalyzer.analyze(version, gameVersion != null ? gameVersion.toString() : null) : null;
|
||||
for (JavaVersionConstraint java : ALL) {
|
||||
if (java.appliesToVersion(gameVersion, version, null, analyzer)) {
|
||||
VersionRange javaVersionRange = java.getJavaVersionRange(version);
|
||||
VersionRange<VersionNumber> javaVersionRange = java.getJavaVersionRange(version);
|
||||
if (java.type == RULE_MANDATORY) {
|
||||
mandatoryJavaRange = mandatoryJavaRange.intersectionWith(javaVersionRange);
|
||||
suggestedJavaRange = suggestedJavaRange.intersectionWith(javaVersionRange);
|
||||
@@ -242,12 +242,12 @@ public enum JavaVersionConstraint {
|
||||
}
|
||||
|
||||
@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 @@ public enum JavaVersionConstraint {
|
||||
public static final int RULE_SUGGESTED = 2;
|
||||
|
||||
public static final class VersionRanges {
|
||||
private final VersionRange mandatory;
|
||||
private final VersionRange suggested;
|
||||
private final VersionRange<VersionNumber> mandatory;
|
||||
private final VersionRange<VersionNumber> suggested;
|
||||
|
||||
public VersionRanges(VersionRange mandatory, VersionRange suggested) {
|
||||
public VersionRanges(VersionRange<VersionNumber> mandatory, VersionRange<VersionNumber> suggested) {
|
||||
this.mandatory = mandatory;
|
||||
this.suggested = suggested;
|
||||
}
|
||||
|
||||
public VersionRange getMandatory() {
|
||||
public VersionRange<VersionNumber> getMandatory() {
|
||||
return mandatory;
|
||||
}
|
||||
|
||||
public VersionRange getSuggested() {
|
||||
public VersionRange<VersionNumber> getSuggested() {
|
||||
return suggested;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ import org.jackhuang.hmcl.util.gson.JsonMap;
|
||||
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<Version>, Validation {
|
||||
private final JsonMap<DownloadType, DownloadInfo> downloads;
|
||||
private final JsonMap<DownloadType, LoggingInfo> 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 class Version implements Comparable<Version>, Validation {
|
||||
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<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Date time, Date releaseTime, Integer minimumLauncherVersion, Boolean hidden, Boolean root, List<Version> 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<Library> libraries, List<CompatibilityRule> compatibilityRules, Map<DownloadType, DownloadInfo> downloads, Map<DownloadType, LoggingInfo> logging, ReleaseType type, Instant time, Instant releaseTime, Integer minimumLauncherVersion, Boolean hidden, Boolean root, List<Version> patches) {
|
||||
this.resolved = resolved;
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
@@ -112,8 +112,8 @@ public class Version implements Comparable<Version>, Validation {
|
||||
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 class Version implements Comparable<Version>, Validation {
|
||||
return mainClass;
|
||||
}
|
||||
|
||||
public Date getTime() {
|
||||
public Instant getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ public class Version implements Comparable<Version>, Validation {
|
||||
return type == null ? ReleaseType.UNKNOWN : type;
|
||||
}
|
||||
|
||||
public Date getReleaseTime() {
|
||||
public Instant getReleaseTime() {
|
||||
return releaseTime;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
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<DownloadType, DownloadInfo> downloads;
|
||||
private final JsonMap<DownloadType, LoggingInfo> 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<TLauncherLibrary> libraries, List<CompatibilityRule> compatibilityRules, JsonMap<DownloadType, DownloadInfo> downloads, JsonMap<DownloadType, LoggingInfo> 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<TLauncherLibrary> libraries, List<CompatibilityRule> compatibilityRules, JsonMap<DownloadType, DownloadInfo> downloads, JsonMap<DownloadType, LoggingInfo> logging, ReleaseType type, Instant time, Instant releaseTime, Integer minimumLauncherVersion, Integer tlauncherVersion) {
|
||||
this.id = id;
|
||||
this.minecraftArguments = minecraftArguments;
|
||||
this.arguments = arguments;
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
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;
|
||||
@@ -150,8 +150,14 @@ public class DefaultLauncher extends Launcher {
|
||||
LOG.log(Level.WARNING, "Bad file encoding", ex);
|
||||
}
|
||||
}
|
||||
res.addDefault("-Dsun.stdout.encoding=", encoding.name());
|
||||
res.addDefault("-Dsun.stderr.encoding=", encoding.name());
|
||||
|
||||
if (options.getJava().getParsedVersion() < 19) {
|
||||
res.addDefault("-Dsun.stdout.encoding=", encoding.name());
|
||||
res.addDefault("-Dsun.stderr.encoding=", encoding.name());
|
||||
} else {
|
||||
res.addDefault("-Dstdout.encoding=", encoding.name());
|
||||
res.addDefault("-Dstderr.encoding=", encoding.name());
|
||||
}
|
||||
|
||||
// Fix RCE vulnerability of log4j2
|
||||
res.addDefault("-Djava.rmi.server.useCodebaseOnly=", "true");
|
||||
@@ -230,7 +236,7 @@ public class DefaultLauncher extends Launcher {
|
||||
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 +265,7 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
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 +363,7 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
|
||||
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 +373,7 @@ public class DefaultLauncher extends Launcher {
|
||||
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");
|
||||
@@ -424,7 +430,7 @@ public class DefaultLauncher extends Launcher {
|
||||
// To guarantee that when failed to generate launch command line, we will not call pre-launch command
|
||||
List<String> rawCommandLine = command.commandLine.asList();
|
||||
if (StringUtils.isNotBlank(options.getWrapper())) {
|
||||
rawCommandLine.addAll(0, StringUtils.parseCommand(options.getWrapper(), getEnvVars()));
|
||||
rawCommandLine.addAll(0, StringUtils.tokenize(options.getWrapper(), getEnvVars()));
|
||||
}
|
||||
|
||||
if (command.tempNativeFolder != null) {
|
||||
@@ -446,7 +452,7 @@ public class DefaultLauncher extends Launcher {
|
||||
File runDirectory = repository.getRunDirectory(version.getId());
|
||||
|
||||
if (StringUtils.isNotBlank(options.getPreLaunchCommand())) {
|
||||
ProcessBuilder builder = new ProcessBuilder(StringUtils.parseCommand(options.getPreLaunchCommand(), getEnvVars())).directory(runDirectory);
|
||||
ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPreLaunchCommand(), getEnvVars())).directory(runDirectory);
|
||||
builder.environment().putAll(getEnvVars());
|
||||
SystemUtils.callExternalProcess(builder);
|
||||
}
|
||||
@@ -499,7 +505,7 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version);
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(version, repository.getGameVersion(version).orElse(null));
|
||||
if (analyzer.has(LibraryAnalyzer.LibraryType.FORGE)) {
|
||||
env.put("INST_FORGE", "1");
|
||||
}
|
||||
@@ -677,7 +683,7 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
if (StringUtils.isNotBlank(options.getPostExitCommand())) {
|
||||
try {
|
||||
ProcessBuilder builder = new ProcessBuilder(StringUtils.parseCommand(options.getPostExitCommand(), getEnvVars())).directory(options.getGameDir());
|
||||
ProcessBuilder builder = new ProcessBuilder(StringUtils.tokenize(options.getPostExitCommand(), getEnvVars())).directory(options.getGameDir());
|
||||
builder.environment().putAll(getEnvVars());
|
||||
SystemUtils.callExternalProcess(builder);
|
||||
} catch (Throwable e) {
|
||||
|
||||
@@ -69,7 +69,10 @@ final class ExitWaiter implements Runnable {
|
||||
"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()) {
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.jackhuang.hmcl.mod.modrinth.ModrinthRemoteModRepository;
|
||||
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 class RemoteMod {
|
||||
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<Dependency> dependencies;
|
||||
private final List<String> gameVersions;
|
||||
private final List<ModLoaderType> loaders;
|
||||
|
||||
public Version(IVersion self, String modid, String name, String version, String changelog, Date datePublished, VersionType versionType, File file, List<Dependency> dependencies, List<String> gameVersions, List<ModLoaderType> loaders) {
|
||||
public Version(IVersion self, String modid, String name, String version, String changelog, Instant datePublished, VersionType versionType, File file, List<Dependency> dependencies, List<String> gameVersions, List<ModLoaderType> loaders) {
|
||||
this.self = self;
|
||||
this.modid = modid;
|
||||
this.name = name;
|
||||
@@ -259,7 +259,7 @@ public class RemoteMod {
|
||||
return changelog;
|
||||
}
|
||||
|
||||
public Date getDatePublished() {
|
||||
public Instant getDatePublished() {
|
||||
return datePublished;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,28 +119,4 @@ public interface RemoteModRepository {
|
||||
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"
|
||||
};
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.util.Pair;
|
||||
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<LatestFile> latestFiles;
|
||||
private final List<LatestFileIndex> 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<Category> categories, int classId, List<Author> authors, Logo logo, int mainFileId, List<LatestFile> latestFiles, List<LatestFileIndex> 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<Category> categories, int classId, List<Author> authors, Logo logo, int mainFileId, List<LatestFile> latestFiles, List<LatestFileIndex> 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 class CurseAddon implements RemoteMod.IMod {
|
||||
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 class CurseAddon implements RemoteMod.IMod {
|
||||
private final int releaseType;
|
||||
private final int fileStatus;
|
||||
private final List<LatestFileHash> 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 class CurseAddon implements RemoteMod.IMod {
|
||||
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<LatestFileHash> hashes, Date fileDate, int fileLength, int downloadCount, String downloadUrl, List<String> gameVersions, List<Dependency> 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<LatestFileHash> hashes, Instant fileDate, int fileLength, int downloadCount, String downloadUrl, List<String> gameVersions, List<Dependency> dependencies, int alternateFileId, boolean isServerPack, long fileFingerprint) {
|
||||
this.id = id;
|
||||
this.gameId = gameId;
|
||||
this.modId = modId;
|
||||
@@ -505,7 +506,7 @@ public class CurseAddon implements RemoteMod.IMod {
|
||||
return hashes;
|
||||
}
|
||||
|
||||
public Date getFileDate() {
|
||||
public Instant getFileDate() {
|
||||
return fileDate;
|
||||
}
|
||||
|
||||
@@ -650,7 +651,7 @@ public class CurseAddon implements RemoteMod.IMod {
|
||||
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 class CurseAddon implements RemoteMod.IMod {
|
||||
private transient final List<Category> 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 class CurseAddon implements RemoteMod.IMod {
|
||||
return iconUrl;
|
||||
}
|
||||
|
||||
public Date getDateModified() {
|
||||
public Instant getDateModified() {
|
||||
return dateModified;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@ import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
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<Void> {
|
||||
private final ModManager modManager;
|
||||
private final String version;
|
||||
private CurseManifest manifest;
|
||||
private final List<Task<?>> dependencies = new ArrayList<>();
|
||||
private List<Task<?>> dependencies;
|
||||
|
||||
private final AtomicBoolean allNameKnown = new AtomicBoolean(true);
|
||||
private final AtomicInteger finished = new AtomicInteger(0);
|
||||
@@ -138,28 +138,31 @@ public final class CurseCompletionTask extends Task<Void> {
|
||||
.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;
|
||||
}
|
||||
} else {
|
||||
if (modManager.hasSimpleMod(file.getFileName())) {
|
||||
continue;
|
||||
}
|
||||
target = modManager.getSimpleModPath(file.getFileName()).toFile();
|
||||
}
|
||||
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(file.getUrl(), target);
|
||||
task.setCacheRepository(dependency.getCacheRepository());
|
||||
task.setCaching(true);
|
||||
dependencies.add(task.withCounter("hmcl.modpack.download"));
|
||||
}
|
||||
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());
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (!dependencies.isEmpty()) {
|
||||
getProperties().put("total", dependencies.size());
|
||||
@@ -167,6 +170,33 @@ public final class CurseCompletionTask extends Task<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@@ -82,9 +82,9 @@ public class McbbsModpackExportTask extends Task<Void> {
|
||||
}
|
||||
});
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(version));
|
||||
String gameVersion = repository.getGameVersion(version)
|
||||
.orElseThrow(() -> new IOException("Cannot parse the version of " + version));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(version), gameVersion);
|
||||
|
||||
// Mcbbs manifest
|
||||
List<McbbsModpackManifest.Addon> addons = new ArrayList<>();
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.io.InputStream;
|
||||
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<Mod> mods;
|
||||
|
||||
public ForgeNewModMetadata() {
|
||||
this("", "", "", "", Collections.emptyList());
|
||||
}
|
||||
|
||||
public ForgeNewModMetadata(String modLoader, String loaderVersion, String logoFile, String license, List<Mod> mods) {
|
||||
this.modLoader = modLoader;
|
||||
this.loaderVersion = loaderVersion;
|
||||
@@ -120,8 +116,9 @@ public final class ForgeNewModMetadata {
|
||||
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 final class ForgeNewModMetadata {
|
||||
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<HashMap<String, Object>> dependencies = toml.getList("dependencies." + modID);
|
||||
if (dependencies != null) {
|
||||
for (HashMap<String, Object> dependency : dependencies) {
|
||||
switch ((String) dependency.get("modId")) {
|
||||
case "forge": return ModLoaderType.FORGE;
|
||||
case "neoforge": return ModLoaderType.NEO_FORGED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+14
-13
@@ -32,6 +32,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
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 final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
private final String team;
|
||||
|
||||
private final Date published;
|
||||
private final Instant published;
|
||||
|
||||
private final Date updated;
|
||||
private final Instant updated;
|
||||
|
||||
private final List<String> versions;
|
||||
|
||||
public Project(String slug, String title, String description, List<String> categories, String body, String projectType, int downloads, String iconUrl, String id, String team, Date published, Date updated, List<String> versions) {
|
||||
public Project(String slug, String title, String description, List<String> categories, String body, String projectType, int downloads, String iconUrl, String id, String team, Instant published, Instant updated, List<String> versions) {
|
||||
this.slug = slug;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
@@ -272,11 +273,11 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
return team;
|
||||
}
|
||||
|
||||
public Date getPublished() {
|
||||
public Instant getPublished() {
|
||||
return published;
|
||||
}
|
||||
|
||||
public Date getUpdated() {
|
||||
public Instant getUpdated() {
|
||||
return updated;
|
||||
}
|
||||
|
||||
@@ -381,7 +382,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
private final String authorId;
|
||||
|
||||
@SerializedName("date_published")
|
||||
private final Date datePublished;
|
||||
private final Instant datePublished;
|
||||
|
||||
private final int downloads;
|
||||
|
||||
@@ -390,7 +391,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
|
||||
private final List<ProjectVersionFile> files;
|
||||
|
||||
public ProjectVersion(String name, String versionNumber, String changelog, List<Dependency> dependencies, List<String> gameVersions, String versionType, List<String> loaders, boolean featured, String id, String projectId, String authorId, Date datePublished, int downloads, String changelogUrl, List<ProjectVersionFile> files) {
|
||||
public ProjectVersion(String name, String versionNumber, String changelog, List<Dependency> dependencies, List<String> gameVersions, String versionType, List<String> loaders, boolean featured, String id, String projectId, String authorId, Instant datePublished, int downloads, String changelogUrl, List<ProjectVersionFile> files) {
|
||||
this.name = name;
|
||||
this.versionNumber = versionNumber;
|
||||
this.changelog = changelog;
|
||||
@@ -452,7 +453,7 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
return authorId;
|
||||
}
|
||||
|
||||
public Date getDatePublished() {
|
||||
public Instant getDatePublished() {
|
||||
return datePublished;
|
||||
}
|
||||
|
||||
@@ -587,15 +588,15 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
private final List<String> 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<String> categories, String projectType, int downloads, String iconUrl, String projectId, String author, List<String> versions, Date dateCreated, Date dateModified, String latestVersion) {
|
||||
public ProjectSearchResult(String slug, String title, String description, List<String> categories, String projectType, int downloads, String iconUrl, String projectId, String author, List<String> versions, Instant dateCreated, Instant dateModified, String latestVersion) {
|
||||
this.slug = slug;
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
@@ -651,11 +652,11 @@ public final class ModrinthRemoteModRepository implements RemoteModRepository {
|
||||
return versions;
|
||||
}
|
||||
|
||||
public Date getDateCreated() {
|
||||
public Instant getDateCreated() {
|
||||
return dateCreated;
|
||||
}
|
||||
|
||||
public Date getDateModified() {
|
||||
public Instant getDateModified() {
|
||||
return dateModified;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,9 +70,9 @@ public class MultiMCModpackExportTask extends Task<Void> {
|
||||
try (Zipper zip = new Zipper(output.toPath())) {
|
||||
zip.putDirectory(repository.getRunDirectory(versionId).toPath(), ".minecraft", path -> Modpack.acceptFile(path, blackList, whitelist));
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId));
|
||||
String gameVersion = repository.getGameVersion(versionId)
|
||||
.orElseThrow(() -> new IOException("Cannot parse the version of " + versionId));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId), gameVersion);
|
||||
List<MultiMCManifest.MultiMCManifestComponent> components = new ArrayList<>();
|
||||
components.add(new MultiMCManifest.MultiMCManifestComponent(true, false, "net.minecraft", gameVersion));
|
||||
analyzer.getVersion(FORGE).ifPresent(forgeVersion ->
|
||||
|
||||
@@ -78,9 +78,9 @@ public class ServerModpackExportTask extends Task<Void> {
|
||||
}
|
||||
});
|
||||
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId));
|
||||
String gameVersion = repository.getGameVersion(versionId)
|
||||
.orElseThrow(() -> new IOException("Cannot parse the version of " + versionId));
|
||||
LibraryAnalyzer analyzer = LibraryAnalyzer.analyze(repository.getResolvedPreservingPatchesVersion(versionId), gameVersion);
|
||||
List<ServerModpackManifest.Addon> addons = new ArrayList<>();
|
||||
addons.add(new ServerModpackManifest.Addon(MINECRAFT.getPatchId(), gameVersion));
|
||||
analyzer.getVersion(FORGE).ifPresent(forgeVersion ->
|
||||
|
||||
@@ -301,9 +301,16 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
concurrency = Math.max(concurrency, 1);
|
||||
synchronized (Schedulers.class) {
|
||||
downloadExecutorConcurrency = concurrency;
|
||||
if (DOWNLOAD_EXECUTOR != null) {
|
||||
DOWNLOAD_EXECUTOR.setCorePoolSize(concurrency);
|
||||
DOWNLOAD_EXECUTOR.setMaximumPoolSize(concurrency);
|
||||
|
||||
ThreadPoolExecutor downloadExecutor = DOWNLOAD_EXECUTOR;
|
||||
if (downloadExecutor != null) {
|
||||
if (downloadExecutor.getMaximumPoolSize() <= concurrency) {
|
||||
downloadExecutor.setMaximumPoolSize(concurrency);
|
||||
downloadExecutor.setCorePoolSize(concurrency);
|
||||
} else {
|
||||
downloadExecutor.setCorePoolSize(concurrency);
|
||||
downloadExecutor.setMaximumPoolSize(concurrency);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +287,7 @@ public class CacheRepository {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -22,9 +22,10 @@ import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
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 final class Logging {
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
StringBuilder builder = new StringBuilder(128 + message.length());
|
||||
builder.append('[');
|
||||
TIME_FORMATTER.formatTo(Instant.ofEpochMilli(record.getMillis()), builder);
|
||||
builder.append(']');
|
||||
|
||||
builder.append(" [")
|
||||
.append(record.getSourceClassName())
|
||||
.append('.')
|
||||
.append(record.getSourceMethodName())
|
||||
.append('/')
|
||||
.append(record.getLevel().getName())
|
||||
.append("] ")
|
||||
.append(message)
|
||||
.append('\n');
|
||||
|
||||
|
||||
Throwable thrown = record.getThrown();
|
||||
|
||||
StringWriter writer;
|
||||
StringBuffer buffer;
|
||||
if (thrown == null) {
|
||||
writer = null;
|
||||
buffer = new StringBuffer(256);
|
||||
return builder.toString();
|
||||
} else {
|
||||
writer = new StringWriter(1024);
|
||||
buffer = writer.getBuffer();
|
||||
}
|
||||
|
||||
FORMAT.format(new Object[]{
|
||||
new Date(record.getMillis()),
|
||||
record.getSourceClassName(), record.getSourceMethodName(), record.getLevel().getName(),
|
||||
message
|
||||
}, buffer, null);
|
||||
|
||||
if (thrown != null) {
|
||||
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 {
|
||||
|
||||
@@ -207,93 +207,135 @@ public final class StringUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static List<String> tokenize(String str) {
|
||||
if (isBlank(str)) {
|
||||
return new ArrayList<>();
|
||||
} else {
|
||||
// Split the string with ' and space cleverly.
|
||||
ArrayList<String> parts = new ArrayList<>();
|
||||
|
||||
boolean hasValue = false;
|
||||
StringBuilder current = new StringBuilder(str.length());
|
||||
for (int i = 0; i < str.length(); ) {
|
||||
char c = str.charAt(i);
|
||||
if (c == '\'') {
|
||||
hasValue = true;
|
||||
int end = str.indexOf(c, i + 1);
|
||||
if (end < 0) {
|
||||
end = str.length();
|
||||
}
|
||||
current.append(str, i + 1, end);
|
||||
i = end + 1;
|
||||
|
||||
} else if (c == '"') {
|
||||
hasValue = true;
|
||||
i++;
|
||||
while (i < str.length()) {
|
||||
c = str.charAt(i++);
|
||||
if (c == '"') {
|
||||
break;
|
||||
} else if (c == '\\' && i < str.length()) {
|
||||
c = str.charAt(i++);
|
||||
switch (c) {
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
c = '\r';
|
||||
break;
|
||||
case 't':
|
||||
c = '\t';
|
||||
break;
|
||||
case 'v':
|
||||
c = '\u000b';
|
||||
break;
|
||||
case 'a':
|
||||
c = '\u0007';
|
||||
break;
|
||||
}
|
||||
current.append(c);
|
||||
} else {
|
||||
current.append(c);
|
||||
}
|
||||
}
|
||||
} else if (c == ' ') {
|
||||
if (hasValue) {
|
||||
parts.add(current.toString());
|
||||
current.setLength(0);
|
||||
hasValue = false;
|
||||
}
|
||||
i++;
|
||||
} else {
|
||||
hasValue = true;
|
||||
current.append(c);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (hasValue) {
|
||||
parts.add(current.toString());
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
private static boolean isVarNameStart(char ch) {
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
|
||||
}
|
||||
|
||||
public static List<String> parseCommand(String command, Map<String, String> env) {
|
||||
StringBuilder stringBuilder = new StringBuilder(command);
|
||||
for (Map.Entry<String, String> entry : env.entrySet()) {
|
||||
String key = "$" + entry.getKey();
|
||||
int i = 0;
|
||||
while (true) {
|
||||
i = stringBuilder.indexOf(key, i);
|
||||
if (i == -1) {
|
||||
private static boolean isVarNamePart(char ch) {
|
||||
return isVarNameStart(ch) || (ch >= '0' && ch <= '9');
|
||||
}
|
||||
|
||||
private static int findVarEnd(String str, int offset) {
|
||||
if (offset < str.length() - 1 && isVarNameStart(str.charAt(offset))) {
|
||||
int end = offset + 1;
|
||||
while (end < str.length()) {
|
||||
if (!isVarNamePart(str.charAt(end))) {
|
||||
break;
|
||||
}
|
||||
stringBuilder.replace(i, i + key.length(), entry.getValue());
|
||||
end++;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
return tokenize(stringBuilder.toString());
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static List<String> tokenize(String str) {
|
||||
return tokenize(str, null);
|
||||
}
|
||||
|
||||
public static List<String> tokenize(String str, Map<String, String> vars) {
|
||||
if (isBlank(str)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (vars == null) {
|
||||
vars = Collections.emptyMap();
|
||||
}
|
||||
|
||||
// Split the string with ' and space cleverly.
|
||||
ArrayList<String> parts = new ArrayList<>();
|
||||
int varEnd;
|
||||
|
||||
boolean hasValue = false;
|
||||
StringBuilder current = new StringBuilder(str.length());
|
||||
for (int i = 0; i < str.length(); ) {
|
||||
char c = str.charAt(i);
|
||||
if (c == '\'') {
|
||||
hasValue = true;
|
||||
int end = str.indexOf(c, i + 1);
|
||||
if (end < 0) {
|
||||
end = str.length();
|
||||
}
|
||||
current.append(str, i + 1, end);
|
||||
i = end + 1;
|
||||
|
||||
} else if (c == '"') {
|
||||
hasValue = true;
|
||||
i++;
|
||||
while (i < str.length()) {
|
||||
c = str.charAt(i++);
|
||||
if (c == '"') {
|
||||
break;
|
||||
} else if (c == '`' && i < str.length()) {
|
||||
c = str.charAt(i++);
|
||||
switch (c) {
|
||||
case 'a':
|
||||
c = '\u0007';
|
||||
break;
|
||||
case 'b':
|
||||
c = '\b';
|
||||
break;
|
||||
case 'f':
|
||||
c = '\f';
|
||||
break;
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
case 'r':
|
||||
c = '\r';
|
||||
break;
|
||||
case 't':
|
||||
c = '\t';
|
||||
break;
|
||||
case 'v':
|
||||
c = '\u000b';
|
||||
break;
|
||||
}
|
||||
current.append(c);
|
||||
} else if (c == '$' && (varEnd = findVarEnd(str, i)) >= 0) {
|
||||
String key = str.substring(i, varEnd);
|
||||
String value = vars.get(key);
|
||||
if (value != null) {
|
||||
current.append(value);
|
||||
} else {
|
||||
current.append('$').append(key);
|
||||
}
|
||||
|
||||
i = varEnd;
|
||||
} else {
|
||||
current.append(c);
|
||||
}
|
||||
}
|
||||
} else if (c == ' ') {
|
||||
if (hasValue) {
|
||||
parts.add(current.toString());
|
||||
current.setLength(0);
|
||||
hasValue = false;
|
||||
}
|
||||
i++;
|
||||
} else if (c == '$' && (varEnd = findVarEnd(str, i + 1)) >= 0) {
|
||||
hasValue = true;
|
||||
String key = str.substring(i + 1, varEnd);
|
||||
String value = vars.get(key);
|
||||
if (value != null) {
|
||||
current.append(value);
|
||||
} else {
|
||||
current.append('$').append(key);
|
||||
}
|
||||
|
||||
i = varEnd;
|
||||
} else {
|
||||
hasValue = true;
|
||||
current.append(c);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (hasValue) {
|
||||
parts.add(current.toString());
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
public static String parseColorEscapes(String original) {
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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<Date>, JsonDeserializer<Date> {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,9 +20,15 @@ package org.jackhuang.hmcl.util.gson;
|
||||
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<Instant>, JsonDeserializer<Instant> {
|
||||
public static final InstantTypeAdapter INSTANCE = new InstantTypeAdapter();
|
||||
@@ -31,21 +37,53 @@ public final class InstantTypeAdapter implements JsonSerializer<Instant>, JsonDe
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
Loaded 100 of 128 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user