Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f03a4ff807 | ||
|
|
de9a105f2f | ||
|
|
b5d3653c9a | ||
|
|
554503fff0 | ||
|
|
785f334241 | ||
|
|
0b87959716 | ||
|
|
ae18ca7567 | ||
|
|
7ebdc783c6 | ||
|
|
39abdf1ecf | ||
|
|
8df6563d56 | ||
|
|
f62c12fd3c | ||
|
|
1205212e52 |
No files matched your search
+27
-19
@@ -1,4 +1,7 @@
|
||||
import org.jackhuang.hmcl.gradle.ci.GitHubActionUtils
|
||||
import org.jackhuang.hmcl.gradle.ci.JenkinsUtils
|
||||
import org.jackhuang.hmcl.gradle.mod.ParseModDataTask
|
||||
import org.jackhuang.hmcl.gradle.utils.PropertiesUtils
|
||||
import java.net.URI
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
@@ -12,30 +15,36 @@ plugins {
|
||||
alias(libs.plugins.shadow)
|
||||
}
|
||||
|
||||
val isOfficial = System.getenv("HMCL_SIGNATURE_KEY") != null
|
||||
|| (System.getenv("GITHUB_REPOSITORY_OWNER") == "HMCL-dev" && System.getenv("GITHUB_BASE_REF")
|
||||
.isNullOrEmpty())
|
||||
val projectConfig = PropertiesUtils.load(rootProject.file("config/project.properties").toPath())
|
||||
|
||||
val isOfficial = JenkinsUtils.IS_ON_CI || GitHubActionUtils.IS_ON_OFFICIAL_REPO
|
||||
|
||||
val buildNumber = System.getenv("BUILD_NUMBER")?.toInt().let { number ->
|
||||
val offset = System.getenv("BUILD_NUMBER_OFFSET")?.toInt() ?: 0
|
||||
if (number != null) {
|
||||
(number - offset).toString()
|
||||
} else {
|
||||
val shortCommit = System.getenv("GITHUB_SHA")?.lowercase()?.substring(0, 7)
|
||||
val prefix = if (isOfficial) "dev" else "unofficial"
|
||||
if (!shortCommit.isNullOrEmpty()) "$prefix-$shortCommit" else "SNAPSHOT"
|
||||
}
|
||||
}
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: "3.6"
|
||||
val versionType = System.getenv("VERSION_TYPE") ?: if (isOfficial) "nightly" else "unofficial"
|
||||
val versionRoot = System.getenv("VERSION_ROOT") ?: projectConfig.getProperty("versionRoot") ?: "3"
|
||||
|
||||
val microsoftAuthId = System.getenv("MICROSOFT_AUTH_ID") ?: ""
|
||||
val microsoftAuthSecret = System.getenv("MICROSOFT_AUTH_SECRET") ?: ""
|
||||
val curseForgeApiKey = System.getenv("CURSEFORGE_API_KEY") ?: ""
|
||||
|
||||
val launcherExe = System.getenv("HMCL_LAUNCHER_EXE")
|
||||
val launcherExe = System.getenv("HMCL_LAUNCHER_EXE") ?: ""
|
||||
|
||||
version = "$versionRoot.$buildNumber"
|
||||
val buildNumber = System.getenv("BUILD_NUMBER")?.toInt()
|
||||
if (buildNumber != null) {
|
||||
version = if (JenkinsUtils.IS_ON_CI && versionType == "dev") {
|
||||
"$versionRoot.0.$buildNumber"
|
||||
} else {
|
||||
"$versionRoot.$buildNumber"
|
||||
}
|
||||
} else {
|
||||
val shortCommit = System.getenv("GITHUB_SHA")?.lowercase()?.substring(0, 7)
|
||||
version = if (shortCommit.isNullOrBlank()) {
|
||||
"$versionRoot.SNAPSHOT"
|
||||
} else if (isOfficial) {
|
||||
"$versionRoot.dev-$shortCommit"
|
||||
} else {
|
||||
"$versionRoot.unofficial-$shortCommit"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":HMCLCore"))
|
||||
@@ -43,7 +52,7 @@ dependencies {
|
||||
implementation(libs.twelvemonkeys.imageio.webp)
|
||||
implementation(libs.java.info)
|
||||
|
||||
if (launcherExe == null) {
|
||||
if (launcherExe.isBlank()) {
|
||||
implementation(libs.hmclauncher)
|
||||
}
|
||||
}
|
||||
@@ -138,7 +147,6 @@ tasks.shadowJar {
|
||||
"Microsoft-Auth-Secret" to microsoftAuthSecret,
|
||||
"CurseForge-Api-Key" to curseForgeApiKey,
|
||||
"Build-Channel" to versionType,
|
||||
"Class-Path" to "pack200.jar",
|
||||
"Add-Opens" to listOf(
|
||||
"java.base/java.lang",
|
||||
"java.base/java.lang.reflect",
|
||||
@@ -162,7 +170,7 @@ tasks.shadowJar {
|
||||
}
|
||||
}
|
||||
|
||||
if (launcherExe != null) {
|
||||
if (launcherExe.isNotBlank()) {
|
||||
into("assets") {
|
||||
from(file(launcherExe))
|
||||
}
|
||||
|
||||
@@ -255,16 +255,15 @@ public final class JavaManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static int compareJavaVersion(JavaRuntime java1, JavaRuntime java2, GameJavaVersion suggestedJavaVersion) {
|
||||
if (suggestedJavaVersion != null) {
|
||||
boolean b1 = java1.getParsedVersion() == suggestedJavaVersion.getMajorVersion();
|
||||
boolean b2 = java2.getParsedVersion() == suggestedJavaVersion.getMajorVersion();
|
||||
private static JavaRuntime chooseJava(@Nullable JavaRuntime java1, JavaRuntime java2) {
|
||||
if (java1 == null)
|
||||
return java2;
|
||||
|
||||
if (b1 != b2)
|
||||
return b1 ? 1 : -1;
|
||||
}
|
||||
|
||||
return java1.getVersionNumber().compareTo(java2.getVersionNumber());
|
||||
if (java1.getParsedVersion() != java2.getParsedVersion())
|
||||
// Prefer the Java version that is closer to the game's recommended Java version
|
||||
return java1.getParsedVersion() < java2.getParsedVersion() ? java1 : java2;
|
||||
else
|
||||
return java1.getVersionNumber().compareTo(java2.getVersionNumber()) >= 0 ? java1 : java2;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -280,9 +279,6 @@ public final class JavaManager {
|
||||
&& (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS || OperatingSystem.CURRENT_OS == OperatingSystem.MACOS)
|
||||
&& (gameVersion == null || gameVersion.compareTo("1.6") < 0);
|
||||
|
||||
GameJavaVersion suggestedJavaVersion =
|
||||
(version != null && gameVersion != null && gameVersion.compareTo("1.7.10") >= 0) ? version.getJavaVersion() : null;
|
||||
|
||||
JavaRuntime mandatory = null;
|
||||
JavaRuntime suggested = null;
|
||||
for (JavaRuntime java : javaRuntimes) {
|
||||
@@ -310,15 +306,10 @@ public final class JavaManager {
|
||||
}
|
||||
|
||||
if (!violationMandatory) {
|
||||
if (mandatory == null) mandatory = java;
|
||||
else if (compareJavaVersion(java, mandatory, suggestedJavaVersion) > 0)
|
||||
mandatory = java;
|
||||
mandatory = chooseJava(mandatory, java);
|
||||
|
||||
if (!violationSuggested) {
|
||||
if (suggested == null) suggested = java;
|
||||
else if (compareJavaVersion(java, suggested, suggestedJavaVersion) > 0)
|
||||
suggested = java;
|
||||
}
|
||||
if (!violationSuggested)
|
||||
suggested = chooseJava(suggested, java);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,9 @@ public final class Config implements Observable {
|
||||
@SerializedName("promptedVersion")
|
||||
private StringProperty promptedVersion = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("acceptPreviewUpdate")
|
||||
private BooleanProperty acceptPreviewUpdate = new SimpleBooleanProperty(false);
|
||||
|
||||
@SerializedName("_version")
|
||||
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
|
||||
|
||||
@@ -671,6 +674,18 @@ public final class Config implements Observable {
|
||||
this.promptedVersion.set(promptedVersion);
|
||||
}
|
||||
|
||||
public boolean isAcceptPreviewUpdate() {
|
||||
return acceptPreviewUpdate.get();
|
||||
}
|
||||
|
||||
public BooleanProperty acceptPreviewUpdateProperty() {
|
||||
return acceptPreviewUpdate;
|
||||
}
|
||||
|
||||
public void setAcceptPreviewUpdate(boolean acceptPreviewUpdate) {
|
||||
this.acceptPreviewUpdate.set(acceptPreviewUpdate);
|
||||
}
|
||||
|
||||
public ObservableMap<String, Object> getShownTips() {
|
||||
return shownTips;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,10 @@ public final class ProxyManager {
|
||||
String password = config().getProxyPass();
|
||||
|
||||
if (username != null || password != null)
|
||||
return new SimpleAuthenticator(username, password.toCharArray());
|
||||
return new SimpleAuthenticator(
|
||||
Lang.requireNonNullElse(username, ""),
|
||||
Lang.requireNonNullElse(password, "").toCharArray()
|
||||
);
|
||||
else
|
||||
return null;
|
||||
} else
|
||||
|
||||
@@ -22,6 +22,7 @@ import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
@@ -312,7 +313,9 @@ public class InstallerItem extends Control {
|
||||
private static final class InstallerItemSkin extends SkinBase<InstallerItem> {
|
||||
private static final PseudoClass LIST_ITEM = PseudoClass.getPseudoClass("list-item");
|
||||
private static final PseudoClass CARD = PseudoClass.getPseudoClass("card");
|
||||
private static final WeakListenerHolder holder = new WeakListenerHolder();
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "unused"})
|
||||
private final ChangeListener<Number> holder;
|
||||
|
||||
InstallerItemSkin(InstallerItem control) {
|
||||
super(control);
|
||||
@@ -320,9 +323,14 @@ public class InstallerItem extends Control {
|
||||
Pane pane;
|
||||
if (control.style == Style.CARD) {
|
||||
pane = new VBox();
|
||||
holder.add(FXUtils.onWeakChange(pane.widthProperty(), v -> FXUtils.setLimitHeight(pane, v.doubleValue() * 0.7)));
|
||||
|
||||
if (FXUtils.JAVAFX_MAJOR_VERSION == 8)
|
||||
holder = FXUtils.onWeakChange(pane.widthProperty(), v -> FXUtils.setLimitHeight(pane, v.doubleValue() * 0.7));
|
||||
else
|
||||
holder = FXUtils.onWeakChangeAndOperate(pane.widthProperty(), v -> FXUtils.setLimitHeight(pane, v.doubleValue() * 0.7));
|
||||
} else {
|
||||
pane = new HBox();
|
||||
holder = null;
|
||||
}
|
||||
pane.getStyleClass().add("installer-item");
|
||||
RipplerContainer container = new RipplerContainer(pane);
|
||||
|
||||
@@ -48,11 +48,22 @@ public final class UpgradeDialog extends JFXDialogLayout {
|
||||
setBody(new ProgressIndicator());
|
||||
|
||||
String url = CHANGELOG_URL + remoteVersion.getChannel().channelName + ".html";
|
||||
boolean isPreview = remoteVersion.isPreview();
|
||||
Task.supplyAsync(Schedulers.io(), () -> {
|
||||
Document document = Jsoup.parse(new URL(url), 30 * 1000);
|
||||
Node node = document.selectFirst("#nowchange");
|
||||
String id = null;
|
||||
Node node = null;
|
||||
if (isPreview) {
|
||||
id = "nowpreview";
|
||||
node = document.selectFirst("#" + id);
|
||||
}
|
||||
if (node == null) {
|
||||
id = "nowchange";
|
||||
node = document.selectFirst("#" + id);
|
||||
}
|
||||
|
||||
if (node == null || !"h1".equals(node.nodeName()))
|
||||
throw new IOException("Cannot find #nowchange in document");
|
||||
throw new IOException("Cannot find current changelog in document");
|
||||
|
||||
HTMLRenderer renderer = new HTMLRenderer(uri -> {
|
||||
LOG.info("Open link: " + uri);
|
||||
@@ -60,7 +71,7 @@ public final class UpgradeDialog extends JFXDialogLayout {
|
||||
});
|
||||
|
||||
do {
|
||||
if ("h1".equals(node.nodeName()) && !"nowchange".equals(node.attr("id"))) {
|
||||
if ("h1".equals(node.nodeName()) && !id.equals(node.attr("id"))) {
|
||||
break;
|
||||
}
|
||||
renderer.appendNode(node);
|
||||
|
||||
@@ -78,15 +78,22 @@ public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
setActionButtonVisible(false);
|
||||
|
||||
setOnScroll(event -> {
|
||||
double deltaY = event.getDeltaY();
|
||||
if (deltaY == 0)
|
||||
return;
|
||||
|
||||
Account current = account.get();
|
||||
if (current == null) return;
|
||||
|
||||
ObservableList<Account> accounts = Accounts.getAccounts();
|
||||
int currentIndex = accounts.indexOf(account.get());
|
||||
if (event.getDeltaY() > 0) { // up
|
||||
int currentIndex = accounts.indexOf(current);
|
||||
if (currentIndex < 0) return;
|
||||
|
||||
if (deltaY > 0) // up
|
||||
currentIndex--;
|
||||
} else { // down
|
||||
else // down
|
||||
currentIndex++;
|
||||
}
|
||||
|
||||
Accounts.setSelectedAccount(accounts.get((currentIndex + accounts.size()) % accounts.size()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ public class CreateAccountPane extends JFXDialogLayout implements DialogAware {
|
||||
}).executor(true);
|
||||
};
|
||||
|
||||
if (factory instanceof OfflineAccountFactory && username != null && !USERNAME_CHECKER_PATTERN.matcher(username).matches()) {
|
||||
if (factory instanceof OfflineAccountFactory && username != null && (!USERNAME_CHECKER_PATTERN.matcher(username).matches() || username.length() > 16)) {
|
||||
JFXButton btnYes = new JFXButton(i18n("button.ok"));
|
||||
btnYes.getStyleClass().add("dialog-error");
|
||||
btnYes.setOnAction(e -> doCreate.run());
|
||||
|
||||
@@ -124,7 +124,7 @@ public abstract class AbstractInstallersPage extends Control implements WizardPa
|
||||
libraryPane.setVgap(16);
|
||||
libraryPane.setHgap(16);
|
||||
|
||||
if (libraries.length <= 8) {
|
||||
if (FXUtils.JAVAFX_MAJOR_VERSION < 25 && libraries.length <= 8) {
|
||||
BorderPane.setMargin(libraryPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(libraryPane);
|
||||
} else {
|
||||
@@ -133,6 +133,9 @@ public abstract class AbstractInstallersPage extends Control implements WizardPa
|
||||
scrollPane.setFitToHeight(true);
|
||||
BorderPane.setMargin(scrollPane, new Insets(16, 0, 16, 0));
|
||||
root.setCenter(scrollPane);
|
||||
|
||||
if (libraries.length <= 8)
|
||||
scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,12 +62,12 @@ import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.SINE;
|
||||
@@ -197,15 +197,17 @@ public final class MainPage extends StackPane implements DecoratorPage {
|
||||
launchPane.setMaxWidth(230);
|
||||
launchPane.setMaxHeight(55);
|
||||
launchPane.setOnScroll(event -> {
|
||||
int index = IntStream.range(0, versions.size())
|
||||
.filter(i -> versions.get(i).getId().equals(getCurrentGame()))
|
||||
.findFirst().orElse(-1);
|
||||
double deltaY = event.getDeltaY();
|
||||
if (deltaY == 0)
|
||||
return;
|
||||
|
||||
String currentId = getCurrentGame();
|
||||
int index = Lang.indexWhere(versions, instance -> instance.getId().equals(currentId));
|
||||
if (index < 0) return;
|
||||
if (event.getDeltaY() > 0) {
|
||||
if (deltaY > 0) // up
|
||||
index--;
|
||||
} else {
|
||||
else // down
|
||||
index++;
|
||||
}
|
||||
profile.setSelectedVersion(versions.get((index + versions.size()) % versions.size()).getId());
|
||||
});
|
||||
StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);
|
||||
|
||||
@@ -106,9 +106,12 @@ public final class SettingsPage extends SettingsView {
|
||||
chkUpdateStable.setUserData(UpdateChannel.STABLE);
|
||||
ObjectProperty<UpdateChannel> updateChannel = selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class);
|
||||
updateChannel.set(UpdateChannel.getChannel());
|
||||
updateChannel.addListener((a, b, newValue) -> {
|
||||
UpdateChecker.requestCheckUpdate(newValue);
|
||||
});
|
||||
|
||||
InvalidationListener checkUpdateListener = e -> {
|
||||
UpdateChecker.requestCheckUpdate(updateChannel.get(), previewPane.isSelected());
|
||||
};
|
||||
updateChannel.addListener(checkUpdateListener);
|
||||
previewPane.selectedProperty().addListener(checkUpdateListener);
|
||||
// ====
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList;
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentSublist;
|
||||
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
|
||||
import org.jackhuang.hmcl.ui.construct.OptionToggleButton;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
@@ -56,6 +57,7 @@ public abstract class SettingsView extends StackPane {
|
||||
protected final JFXRadioButton chkUpdateStable;
|
||||
protected final JFXRadioButton chkUpdateDev;
|
||||
protected final JFXButton btnUpdate;
|
||||
protected final OptionToggleButton previewPane;
|
||||
protected final ScrollPane scroll;
|
||||
|
||||
public SettingsView() {
|
||||
@@ -145,6 +147,15 @@ public abstract class SettingsView extends StackPane {
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
{
|
||||
previewPane = new OptionToggleButton();
|
||||
previewPane.setTitle(i18n("update.preview"));
|
||||
previewPane.selectedProperty().bindBidirectional(config().acceptPreviewUpdateProperty());
|
||||
FXUtils.installFastTooltip(previewPane, i18n("update.preview.tooltip"));
|
||||
|
||||
settingsPane.getContent().add(previewPane);
|
||||
}
|
||||
|
||||
{
|
||||
fileCommonLocation = new MultiFileItem<>();
|
||||
fileCommonLocationSublist = new ComponentSublist();
|
||||
|
||||
@@ -18,15 +18,9 @@
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
class HMCLDownloadTask extends FileDownloadTask {
|
||||
|
||||
@@ -47,15 +41,6 @@ class HMCLDownloadTask extends FileDownloadTask {
|
||||
switch (archiveFormat) {
|
||||
case JAR:
|
||||
break;
|
||||
|
||||
case PACK_XZ:
|
||||
byte[] raw = Files.readAllBytes(target);
|
||||
try (InputStream in = new XZInputStream(new ByteArrayInputStream(raw));
|
||||
JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) {
|
||||
Pack200Utils.unpack(in, out);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown format: " + archiveFormat);
|
||||
}
|
||||
|
||||
@@ -21,28 +21,23 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
|
||||
public class RemoteVersion {
|
||||
public final class RemoteVersion {
|
||||
|
||||
public static RemoteVersion fetch(UpdateChannel channel, String url) throws IOException {
|
||||
public static RemoteVersion fetch(UpdateChannel channel, boolean preview, String url) throws IOException {
|
||||
try {
|
||||
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class);
|
||||
String version = Optional.ofNullable(response.get("version")).map(JsonElement::getAsString).orElseThrow(() -> new IOException("version is missing"));
|
||||
String jarUrl = Optional.ofNullable(response.get("jar")).map(JsonElement::getAsString).orElse(null);
|
||||
String jarHash = Optional.ofNullable(response.get("jarsha1")).map(JsonElement::getAsString).orElse(null);
|
||||
String packXZUrl = Optional.ofNullable(response.get("packxz")).map(JsonElement::getAsString).orElse(null);
|
||||
String packXZHash = Optional.ofNullable(response.get("packxzsha1")).map(JsonElement::getAsString).orElse(null);
|
||||
boolean force = Optional.ofNullable(response.get("force")).map(JsonElement::getAsBoolean).orElse(false);
|
||||
if (Pack200Utils.isSupported() && packXZUrl != null && packXZHash != null) {
|
||||
return new RemoteVersion(channel, version, packXZUrl, Type.PACK_XZ, new IntegrityCheck("SHA-1", packXZHash), force);
|
||||
} else if (jarUrl != null && jarHash != null) {
|
||||
return new RemoteVersion(channel, version, jarUrl, Type.JAR, new IntegrityCheck("SHA-1", jarHash), force);
|
||||
if (jarUrl != null && jarHash != null) {
|
||||
return new RemoteVersion(channel, version, jarUrl, Type.JAR, new IntegrityCheck("SHA-1", jarHash), preview, force);
|
||||
} else {
|
||||
throw new IOException("No download url is available");
|
||||
}
|
||||
@@ -56,14 +51,16 @@ public class RemoteVersion {
|
||||
private final String url;
|
||||
private final Type type;
|
||||
private final IntegrityCheck integrityCheck;
|
||||
private final boolean preview;
|
||||
private final boolean force;
|
||||
|
||||
public RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck, boolean force) {
|
||||
public RemoteVersion(UpdateChannel channel, String version, String url, Type type, IntegrityCheck integrityCheck, boolean preview, boolean force) {
|
||||
this.channel = channel;
|
||||
this.version = version;
|
||||
this.url = url;
|
||||
this.type = type;
|
||||
this.integrityCheck = integrityCheck;
|
||||
this.preview = preview;
|
||||
this.force = force;
|
||||
}
|
||||
|
||||
@@ -87,6 +84,10 @@ public class RemoteVersion {
|
||||
return integrityCheck;
|
||||
}
|
||||
|
||||
public boolean isPreview() {
|
||||
return preview;
|
||||
}
|
||||
|
||||
public boolean isForce() {
|
||||
return force;
|
||||
}
|
||||
@@ -97,7 +98,6 @@ public class RemoteVersion {
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
PACK_XZ,
|
||||
JAR
|
||||
}
|
||||
}
|
||||
@@ -27,14 +27,15 @@ import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.*;
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
public final class UpdateChecker {
|
||||
private UpdateChecker() {}
|
||||
private UpdateChecker() {
|
||||
}
|
||||
|
||||
private static final ObjectProperty<RemoteVersion> latestVersion = new SimpleObjectProperty<>();
|
||||
private static final BooleanBinding outdated = Bindings.createBooleanBinding(
|
||||
@@ -55,7 +56,7 @@ public final class UpdateChecker {
|
||||
private static final ReadOnlyBooleanWrapper checkingUpdate = new ReadOnlyBooleanWrapper(false);
|
||||
|
||||
public static void init() {
|
||||
requestCheckUpdate(UpdateChannel.getChannel());
|
||||
requestCheckUpdate(UpdateChannel.getChannel(), config().isAcceptPreviewUpdate());
|
||||
}
|
||||
|
||||
public static RemoteVersion getLatestVersion() {
|
||||
@@ -82,16 +83,17 @@ public final class UpdateChecker {
|
||||
return checkingUpdate.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
private static RemoteVersion checkUpdate(UpdateChannel channel) throws IOException {
|
||||
private static RemoteVersion checkUpdate(UpdateChannel channel, boolean preview) throws IOException {
|
||||
if (!IntegrityChecker.DISABLE_SELF_INTEGRITY_CHECK && !IntegrityChecker.isSelfVerified()) {
|
||||
throw new IOException("Self verification failed");
|
||||
}
|
||||
|
||||
String url = NetworkUtils.withQuery(Metadata.HMCL_UPDATE_URL, mapOf(
|
||||
pair("version", Metadata.VERSION),
|
||||
pair("channel", channel.channelName)));
|
||||
LinkedHashMap<String, String> query = new LinkedHashMap<>();
|
||||
query.put("version", Metadata.VERSION);
|
||||
query.put("channel", preview ? channel.channelName + "-preview" : channel.channelName);
|
||||
|
||||
return RemoteVersion.fetch(channel, url);
|
||||
String url = NetworkUtils.withQuery(Metadata.HMCL_UPDATE_URL, query);
|
||||
return RemoteVersion.fetch(channel, preview, url);
|
||||
}
|
||||
|
||||
private static boolean isDevelopmentVersion(String version) {
|
||||
@@ -99,7 +101,7 @@ public final class UpdateChecker {
|
||||
version.contains("SNAPSHOT"); // eg. 3.5.SNAPSHOT
|
||||
}
|
||||
|
||||
public static void requestCheckUpdate(UpdateChannel channel) {
|
||||
public static void requestCheckUpdate(UpdateChannel channel, boolean preview) {
|
||||
Platform.runLater(() -> {
|
||||
if (isCheckingUpdate())
|
||||
return;
|
||||
@@ -108,8 +110,8 @@ public final class UpdateChecker {
|
||||
thread(() -> {
|
||||
RemoteVersion result = null;
|
||||
try {
|
||||
result = checkUpdate(channel);
|
||||
LOG.info("Latest version (" + channel + ") is " + result);
|
||||
result = checkUpdate(channel, preview);
|
||||
LOG.info("Latest version (" + channel + ", preview=" + preview + ") is " + result);
|
||||
} catch (IOException e) {
|
||||
LOG.warning("Failed to check for update", e);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ account.methods.microsoft.purchase=Buy Minecraft
|
||||
account.methods.microsoft.snapshot=You are using an unofficial build of HMCL. Please download the official build to log in.
|
||||
account.methods.microsoft.snapshot.website=Official Website
|
||||
account.methods.offline=Offline
|
||||
account.methods.offline.name.special_characters=Using only English letters, numbers, and underscores is recommended
|
||||
account.methods.offline.name.special_characters=Use only letters, numbers, and underscores (max 16 chars)
|
||||
account.methods.offline.name.invalid=It is recommended to use only English letters, numbers and underscores for the username, and the length should not exceed 16 characters.\n\
|
||||
\n\
|
||||
\ · Legitimate: HuangYu, huang_Yu, Huang_Yu_123;\n\
|
||||
@@ -1423,6 +1423,8 @@ update.note=Beta and Nightly channels may have more features or fixes, but they
|
||||
update.latest=This is the latest version
|
||||
update.no_browser=Cannot open in system browser. But we copied the link to your clipboard, and you can open it manually.
|
||||
update.tooltip=Update
|
||||
update.preview=Preview HMCL releases early
|
||||
update.preview.tooltip=Enable this option to receive new versions of HMCL early for testing before their official release.
|
||||
|
||||
version=Games
|
||||
version.name=Instance Name
|
||||
|
||||
@@ -1419,6 +1419,8 @@ update.note=Advertencia: Las versiones beta y las versiones nocturnas pueden ten
|
||||
update.latest=Esta es la última versión.
|
||||
update.no_browser=No se puede abrir en el navegador del sistema. Pero, hemos copiado el enlace a su portapapeles y puede abrirlo manualmente.
|
||||
update.tooltip=Actualización
|
||||
update.preview=Vista previa de actualizaciones anticipadas
|
||||
update.preview.tooltip=Activa esta opción para recibir nuevas versiones del lanzador antes de su lanzamiento oficial para probarlas
|
||||
|
||||
version=Juegos
|
||||
version.name=Nombre de instancia
|
||||
|
||||
@@ -131,7 +131,7 @@ account.methods.ban_query=查詢帳戶是否被封禁
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。請下載官方版本進行登入。
|
||||
account.methods.microsoft.snapshot.website=官方網站
|
||||
account.methods.offline=離線模式
|
||||
account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名
|
||||
account.methods.offline.name.special_characters=建議使用英文字母、數字以及底線命名,且長度不超過 16 個字元
|
||||
account.methods.offline.name.invalid=遊戲使用者名稱建議僅使用英文字母、數字及底線,且長度不超過 16 個字元。\n\
|
||||
\n\
|
||||
\ · 一些有效的使用者名稱:HuangYu、huang_Yu、Huang_Yu_123;\n\
|
||||
@@ -1207,6 +1207,8 @@ update.note=開發版與預覽版包含更多的功能以及錯誤修復,但
|
||||
update.latest=目前版本為最新版本
|
||||
update.no_browser=無法開啟瀏覽器。網址已經複製到剪貼簿了,你可以手動複製網址開啟頁面。
|
||||
update.tooltip=更新
|
||||
update.preview=提前測試 HMCL 預覽版本
|
||||
update.preview.tooltip=啟用此選項,你將可以提前取得 HMCL 的新版本,以便在正式發布前進行測試。
|
||||
|
||||
version=遊戲
|
||||
version.name=遊戲實例名稱
|
||||
|
||||
@@ -139,7 +139,7 @@ account.methods.ban_query=检测账户是否被封禁
|
||||
account.methods.microsoft.snapshot=你正在使用第三方提供的 HMCL。请下载官方版本来登录微软账户。
|
||||
account.methods.microsoft.snapshot.website=官方网站
|
||||
account.methods.offline=离线模式
|
||||
account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名
|
||||
account.methods.offline.name.special_characters=建议使用英文字符、数字以及下划线命名,且长度不超过 16 个字符
|
||||
account.methods.offline.name.invalid=游戏用户名建议仅使用英文字母、数字及下划线,且长度不超过 16 个字符。\n\
|
||||
\n\
|
||||
\ · 一些合法用户名:HuangYu、huang_Yu、Huang_Yu_123;\n\
|
||||
@@ -1217,6 +1217,8 @@ update.note=开发版与预览版包含更多的功能以及错误修复,但
|
||||
update.latest=当前版本为最新版本
|
||||
update.no_browser=无法打开浏览器。网址已经复制到剪贴板,你可以手动粘贴网址打开页面。
|
||||
update.tooltip=更新
|
||||
update.preview=提前预览 HMCL 版本
|
||||
update.preview.tooltip=启用此选项,你将可以提前获取 HMCL 的新版本,以便在正式发布前进行测试。
|
||||
|
||||
version=游戏
|
||||
version.name=游戏实例名称
|
||||
|
||||
@@ -78,7 +78,7 @@ public final class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
pair("https://maven.fabricmc.net", apiRoot + "/maven"),
|
||||
pair("https://authlib-injector.yushi.moe", apiRoot + "/mirrors/authlib-injector"),
|
||||
pair("https://repo1.maven.org/maven2", "https://mirrors.cloud.tencent.com/nexus/repository/maven-public"),
|
||||
pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://vip.123pan.cn/1821946486/unlisted-versions-of-minecraft")
|
||||
pair("https://zkitefly.github.io/unlisted-versions-of-minecraft", "https://alist.8mi.tech/d/mirror/unlisted-versions-of-minecraft/Auto")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ public class CacheRepository {
|
||||
String url = conn.getURL().toString();
|
||||
String lastModified = conn.getHeaderField("Last-Modified");
|
||||
CacheResult cacheResult = cacheSupplier.get();
|
||||
ETagItem eTagItem = new ETagItem(url, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified);
|
||||
ETagItem eTagItem = new ETagItem(url, eTag, cacheResult.hash, Files.getLastModifiedTime(cacheResult.cachedFile).toMillis(), lastModified, 0L);
|
||||
Lock writeLock = lock.writeLock();
|
||||
writeLock.lock();
|
||||
try {
|
||||
@@ -325,19 +325,24 @@ public class CacheRepository {
|
||||
@SerializedName("remote")
|
||||
private final String remoteLastModified;
|
||||
|
||||
// The `expires` field is not used in HMCL 3.6.x, but will be used in HMCL 3.7.x.
|
||||
// We backported this field to 3.6.x to avoid breaking the etag.json file created by newer HMCL versions.
|
||||
private final long expires;
|
||||
|
||||
/**
|
||||
* For Gson.
|
||||
*/
|
||||
public ETagItem() {
|
||||
this(null, null, null, 0, null);
|
||||
this(null, null, null, 0, null, 0L);
|
||||
}
|
||||
|
||||
public ETagItem(String url, String eTag, String hash, long localLastModified, String remoteLastModified) {
|
||||
public ETagItem(String url, String eTag, String hash, long localLastModified, String remoteLastModified, long expires) {
|
||||
this.url = url;
|
||||
this.eTag = eTag;
|
||||
this.hash = hash;
|
||||
this.localLastModified = localLastModified;
|
||||
this.remoteLastModified = remoteLastModified;
|
||||
this.expires = expires;
|
||||
}
|
||||
|
||||
public int compareTo(ETagItem other) {
|
||||
@@ -361,12 +366,13 @@ public class CacheRepository {
|
||||
Objects.equals(url, eTagItem.url) &&
|
||||
Objects.equals(eTag, eTagItem.eTag) &&
|
||||
Objects.equals(hash, eTagItem.hash) &&
|
||||
Objects.equals(remoteLastModified, eTagItem.remoteLastModified);
|
||||
Objects.equals(remoteLastModified, eTagItem.remoteLastModified) &&
|
||||
expires == eTagItem.expires;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(url, eTag, hash, localLastModified, remoteLastModified);
|
||||
return Objects.hash(url, eTag, hash, localLastModified, remoteLastModified, expires);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,9 +48,10 @@ public final class Lang {
|
||||
|
||||
/**
|
||||
* Construct a mutable map by given key-value pairs.
|
||||
*
|
||||
* @param pairs entries in the new map
|
||||
* @param <K> the type of keys
|
||||
* @param <V> the type of values
|
||||
* @param <K> the type of keys
|
||||
* @param <V> the type of values
|
||||
* @return the map which contains data in {@code pairs}.
|
||||
*/
|
||||
@SafeVarargs
|
||||
@@ -60,9 +61,10 @@ public final class Lang {
|
||||
|
||||
/**
|
||||
* Construct a mutable map by given key-value pairs.
|
||||
*
|
||||
* @param pairs entries in the new map
|
||||
* @param <K> the type of keys
|
||||
* @param <V> the type of values
|
||||
* @param <K> the type of keys
|
||||
* @param <V> the type of values
|
||||
* @return the map which contains data in {@code pairs}.
|
||||
*/
|
||||
public static <K, V> Map<K, V> mapOf(Iterable<Pair<K, V>> pairs) {
|
||||
@@ -122,9 +124,10 @@ public final class Lang {
|
||||
|
||||
/**
|
||||
* Cast {@code obj} to V dynamically.
|
||||
* @param obj the object reference to be cast.
|
||||
*
|
||||
* @param obj the object reference to be cast.
|
||||
* @param clazz the class reference of {@code V}.
|
||||
* @param <V> the type that {@code obj} is being cast to.
|
||||
* @param <V> the type that {@code obj} is being cast to.
|
||||
* @return {@code obj} in the type of {@code V}.
|
||||
*/
|
||||
public static <V> Optional<V> tryCast(Object obj, Class<V> clazz) {
|
||||
@@ -154,8 +157,8 @@ public final class Lang {
|
||||
/**
|
||||
* Join two collections into one list.
|
||||
*
|
||||
* @param a one collection, to be joined.
|
||||
* @param b another collection to be joined.
|
||||
* @param a one collection, to be joined.
|
||||
* @param b another collection to be joined.
|
||||
* @param <T> the super type of elements in {@code a} and {@code b}
|
||||
* @return the joint collection
|
||||
*/
|
||||
@@ -172,6 +175,16 @@ public final class Lang {
|
||||
return list == null ? null : list.isEmpty() ? null : new ArrayList<>(list);
|
||||
}
|
||||
|
||||
public static <T> int indexWhere(List<T> list, Predicate<T> predicate) {
|
||||
int idx = 0;
|
||||
for (T value : list) {
|
||||
if (predicate.test(value))
|
||||
return idx;
|
||||
idx++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void executeDelayed(Runnable runnable, TimeUnit timeUnit, long timeout, boolean isDaemon) {
|
||||
thread(() -> {
|
||||
try {
|
||||
@@ -185,6 +198,7 @@ public final class Lang {
|
||||
|
||||
/**
|
||||
* Start a thread invoking {@code runnable} immediately.
|
||||
*
|
||||
* @param runnable code to run.
|
||||
* @return the reference of the started thread
|
||||
*/
|
||||
@@ -194,8 +208,9 @@ public final class Lang {
|
||||
|
||||
/**
|
||||
* Start a thread invoking {@code runnable} immediately.
|
||||
*
|
||||
* @param runnable code to run
|
||||
* @param name the name of thread
|
||||
* @param name the name of thread
|
||||
* @return the reference of the started thread
|
||||
*/
|
||||
public static Thread thread(Runnable runnable, String name) {
|
||||
@@ -204,8 +219,9 @@ public final class Lang {
|
||||
|
||||
/**
|
||||
* Start a thread invoking {@code runnable} immediately.
|
||||
*
|
||||
* @param runnable code to run
|
||||
* @param name the name of thread
|
||||
* @param name the name of thread
|
||||
* @param isDaemon true if thread will be terminated when only daemon threads are running.
|
||||
* @return the reference of the started thread
|
||||
*/
|
||||
@@ -258,7 +274,8 @@ public final class Lang {
|
||||
|
||||
/**
|
||||
* Find the first non-null reference in given list.
|
||||
* @param t nullable references list.
|
||||
*
|
||||
* @param t nullable references list.
|
||||
* @param <T> the type of nullable references
|
||||
* @return the first non-null reference.
|
||||
*/
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <[email protected]> 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.jar.JarOutputStream;
|
||||
|
||||
import static org.jackhuang.hmcl.util.logging.Logger.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class Pack200Utils {
|
||||
private Pack200Utils() {
|
||||
}
|
||||
|
||||
private static final String[] IMPL_NAMES = {
|
||||
"java.util.jar.Pack200",
|
||||
"org.glavo.pack200.Pack200",
|
||||
"io.pack200.Pack200"
|
||||
};
|
||||
|
||||
private static final MethodHandle newUnpackerHandle;
|
||||
private static final MethodHandle unpackHandle;
|
||||
private static final MethodHandle unpackFileHandle;
|
||||
|
||||
static {
|
||||
Class<?> pack200Class = null;
|
||||
Class<?> unpackerClass = null;
|
||||
|
||||
for (String implName : IMPL_NAMES) {
|
||||
try {
|
||||
pack200Class = Class.forName(implName);
|
||||
unpackerClass = Class.forName(implName + "$Unpacker");
|
||||
break;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (pack200Class == null) {
|
||||
LOG.warning("Pack200 not found");
|
||||
newUnpackerHandle = null;
|
||||
unpackHandle = null;
|
||||
unpackFileHandle = null;
|
||||
} else {
|
||||
final MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
MethodHandle newUnpacker = null;
|
||||
MethodHandle unpack = null;
|
||||
MethodHandle unpackFile = null;
|
||||
try {
|
||||
newUnpacker = lookup.findStatic(pack200Class, "newUnpacker", MethodType.methodType(unpackerClass));
|
||||
unpack = lookup.findVirtual(unpackerClass, "unpack", MethodType.methodType(void.class, InputStream.class, JarOutputStream.class));
|
||||
unpackFile = lookup.findVirtual(unpackerClass, "unpack", MethodType.methodType(void.class, File.class, JarOutputStream.class));
|
||||
} catch (Throwable e) {
|
||||
LOG.warning("Failed to find pack200 methods", e);
|
||||
}
|
||||
|
||||
if (newUnpacker != null) {
|
||||
newUnpackerHandle = newUnpacker;
|
||||
unpackHandle = unpack;
|
||||
unpackFileHandle = unpackFile;
|
||||
} else {
|
||||
newUnpackerHandle = null;
|
||||
unpackHandle = null;
|
||||
unpackFileHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static boolean isSupported() {
|
||||
return newUnpackerHandle != null;
|
||||
}
|
||||
|
||||
public static void unpack(InputStream in, JarOutputStream out) throws IOException {
|
||||
if (newUnpackerHandle == null) {
|
||||
throw new UnsupportedOperationException("Pack200");
|
||||
}
|
||||
|
||||
try {
|
||||
unpackHandle.invoke(newUnpackerHandle.invoke(), in, out);
|
||||
} catch (IOException | RuntimeException | Error e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void unpack(File in, JarOutputStream out) throws IOException {
|
||||
if (newUnpackerHandle == null) {
|
||||
throw new UnsupportedOperationException("Pack200");
|
||||
}
|
||||
|
||||
try {
|
||||
unpackFileHandle.invoke(newUnpackerHandle.invoke(), in, out);
|
||||
} catch (IOException | RuntimeException | Error e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -831,4 +831,21 @@
|
||||
1.21.7-rc2
|
||||
1.21.7
|
||||
1.21.8-rc1
|
||||
1.21.8
|
||||
1.21.8
|
||||
25w31a
|
||||
25w32a
|
||||
25w33a
|
||||
25w34a
|
||||
25w34b
|
||||
25w35a
|
||||
25w36a
|
||||
25w36b
|
||||
25w37a
|
||||
1.21.9-pre1
|
||||
1.21.9-pre2
|
||||
1.21.9-pre3
|
||||
1.21.9-pre4
|
||||
1.21.9-rc1
|
||||
1.21.9
|
||||
1.21.10-rc1
|
||||
1.21.10
|
||||
@@ -13,6 +13,7 @@ public final class JavaRuntimeTest {
|
||||
public void testParseVersion() {
|
||||
assertEquals(8, parseVersion("1.8.0_302"));
|
||||
assertEquals(8, parseVersion("1.8-internal"));
|
||||
assertEquals(8, parseVersion("8.0"));
|
||||
assertEquals(11, parseVersion("11"));
|
||||
assertEquals(11, parseVersion("11.0.12"));
|
||||
assertEquals(11, parseVersion("11-internal"));
|
||||
|
||||
+8
-1
@@ -18,7 +18,14 @@ subprojects {
|
||||
name = "libs"
|
||||
dirs = setOf(rootProject.file("lib"))
|
||||
}
|
||||
mavenCentral()
|
||||
|
||||
System.getenv("MAVEN_CENTRAL_REPO").let { repo ->
|
||||
if (repo.isNullOrBlank())
|
||||
mavenCentral()
|
||||
else
|
||||
maven(url = repo)
|
||||
}
|
||||
|
||||
maven(url = "https://jitpack.io")
|
||||
maven(url = "https://libraries.minecraft.net")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
repositories {
|
||||
mavenCentral()
|
||||
System.getenv("MAVEN_CENTRAL_REPO").let { repo ->
|
||||
if (repo.isNullOrBlank())
|
||||
mavenCentral()
|
||||
else
|
||||
maven(url = repo)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <[email protected]> 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.gradle.ci;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/// @author Glavo
|
||||
public final class GitHubActionUtils {
|
||||
private static final String OFFICIAL_ORGANIZATION = "HMCL-dev";
|
||||
|
||||
public static final boolean IS_ON_OFFICIAL_REPO =
|
||||
OFFICIAL_ORGANIZATION.equalsIgnoreCase(System.getenv("GITHUB_REPOSITORY_OWNER"))
|
||||
&& Objects.requireNonNullElse(System.getenv("GITHUB_BASE_REF"), "").isBlank();
|
||||
|
||||
private GitHubActionUtils() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <[email protected]> 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.gradle.ci;
|
||||
|
||||
/// @author Glavo
|
||||
public final class JenkinsUtils {
|
||||
|
||||
public static final boolean IS_ON_CI = "1".equals(System.getenv("HMCL_CI"));
|
||||
|
||||
private JenkinsUtils() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2025 huangyuhui <[email protected]> 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.gradle.utils;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
|
||||
/// @author Glavo
|
||||
public final class PropertiesUtils {
|
||||
public static @NotNull Properties load(Path path) throws IOException {
|
||||
Properties properties = new Properties();
|
||||
try (var reader = Files.newBufferedReader(path)) {
|
||||
properties.load(reader);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
private PropertiesUtils() {
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
_HMCL_PROJECT_ROOT=$(dirname "${BASH_SOURCE[0]}")
|
||||
|
||||
sed -i 's,//services.gradle.org/distributions/,//mirrors.cloud.tencent.com/gradle/,g' "$_HMCL_PROJECT_ROOT/gradle/wrapper/gradle-wrapper.properties"
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
sed -i 's,//services.gradle.org/distributions/,//mirrors.cloud.tencent.com/gradle/,g' "$PWD/gradle/wrapper/gradle-wrapper.properties"
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
HMCL_CI = '1'
|
||||
|
||||
VERSION_TYPE = 'dev'
|
||||
MICROSOFT_AUTH_ID = credentials('microsoft_auth_id')
|
||||
MICROSOFT_AUTH_SECRET = credentials('microsoft_auth_secret')
|
||||
CURSEFORGE_API_KEY = credentials('curseforge_api_key')
|
||||
HMCL_SIGNATURE_KEY = credentials('hmcl_signature_key')
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'bash ./config/jenkins/config-jenkins.sh'
|
||||
sh './gradlew clean makeExecutables --stacktrace --no-daemon'
|
||||
archiveArtifacts artifacts: 'HMCL/build/libs/*'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
environment {
|
||||
HMCL_CI = '1'
|
||||
|
||||
VERSION_TYPE = 'stable'
|
||||
MICROSOFT_AUTH_ID = credentials('microsoft_auth_id')
|
||||
MICROSOFT_AUTH_SECRET = credentials('microsoft_auth_secret')
|
||||
CURSEFORGE_API_KEY = credentials('curseforge_api_key')
|
||||
HMCL_SIGNATURE_KEY = credentials('hmcl_signature_key')
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'bash ./config/jenkins/config-jenkins.sh'
|
||||
sh './gradlew clean makeExecutables --stacktrace --no-daemon'
|
||||
archiveArtifacts artifacts: 'HMCL/build/libs/*'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2025 huangyuhui <[email protected]> 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/>.
|
||||
#
|
||||
versionRoot=3.6
|
||||
Reference in new issue
Block a user