Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f84c4310f9 | ||
|
|
97e8ff91ea | ||
|
|
1c865e3d8d | ||
|
|
6ca7a86189 | ||
|
|
3c716ea170 | ||
|
|
3a7800db61 | ||
|
|
4382326dec | ||
|
|
5adbac3cc9 | ||
|
|
d8b84a2574 | ||
|
|
0d59e52ff9 | ||
|
|
feb30a7349 | ||
|
|
3082e4cd16 |
No files matched your search
@@ -383,13 +383,13 @@ public final class LauncherHelper {
|
||||
setting.getMaxMemory() > 1.5 * 1024) {
|
||||
// 1.5 * 1024 is an inaccurate number.
|
||||
// Actual memory limit depends on operating system and memory.
|
||||
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
Controllers.confirm(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), onAccept, null);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
Controllers.confirm(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), onAccept, null);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
@@ -401,7 +401,7 @@ public final class LauncherHelper {
|
||||
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "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) {
|
||||
Controllers.dialog(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
Controllers.confirm(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), onAccept, null);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,8 @@ public final class LogWindow extends Stage {
|
||||
private JFXComboBox<String> cboLines = new JFXComboBox<>();
|
||||
|
||||
LogWindowImpl() {
|
||||
getStyleClass().add("log-window");
|
||||
|
||||
boolean flag = false;
|
||||
cboLines.getItems().setAll("500", "2000", "5000");
|
||||
for (String i : cboLines.getItems())
|
||||
@@ -178,6 +180,7 @@ public final class LogWindow extends Stage {
|
||||
}
|
||||
|
||||
private void onClear() {
|
||||
impl.listView.getItems().clear();
|
||||
logs.clear();
|
||||
}
|
||||
|
||||
@@ -279,7 +282,6 @@ public final class LogWindow extends Stage {
|
||||
prefWidthProperty().bind(clippedContainer.widthProperty());
|
||||
}
|
||||
setPadding(new Insets(2));
|
||||
getStyleClass().add("log");
|
||||
setWrapText(true);
|
||||
setGraphic(null);
|
||||
}
|
||||
|
||||
@@ -93,5 +93,15 @@ public abstract class ToolbarListPageSkin<T extends ListPageBase<? extends Node>
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static JFXButton createDecoratorButton(String tooltip, SVG.SVGIcon creator, Runnable onClick) {
|
||||
JFXButton ret = new JFXButton();
|
||||
ret.getStyleClass().add("jfx-decorator-button");
|
||||
ret.textFillProperty().bind(Theme.foregroundFillBinding());
|
||||
ret.setGraphic(wrap(creator.createIcon(Theme.foregroundFillBinding(), -1, -1)));
|
||||
FXUtils.installFastTooltip(ret, tooltip);
|
||||
ret.setOnMouseClicked(e -> onClick.run());
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected abstract List<Node> initializeToolbar(T skinnable);
|
||||
}
|
||||
@@ -146,13 +146,13 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
|
||||
if (scrollBar.getOrientation() == Orientation.HORIZONTAL) {
|
||||
thumb.relocate(0, -5);
|
||||
thumb.widthProperty().bind(Bindings.max(5, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.widthProperty())));
|
||||
thumb.widthProperty().bind(Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.widthProperty())));
|
||||
thumb.setHeight(5);
|
||||
thumb.xProperty().bind(Bindings.subtract(scrollBar.widthProperty(), thumb.widthProperty()).multiply(position));
|
||||
} else {
|
||||
thumb.relocate(-5, 0);
|
||||
thumb.setWidth(5);
|
||||
thumb.heightProperty().bind(Bindings.max(5, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.heightProperty())));
|
||||
thumb.heightProperty().bind(Bindings.max(20, scrollBar.visibleAmountProperty().divide(range).multiply(scrollBar.heightProperty())));
|
||||
thumb.yProperty().bind(Bindings.subtract(scrollBar.heightProperty(), thumb.heightProperty()).multiply(position));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,32 +216,32 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
HBox navLeft = new HBox();
|
||||
navLeft.setAlignment(Pos.CENTER_LEFT);
|
||||
navLeft.setPadding(new Insets(0, 5, 0, 5));
|
||||
{
|
||||
JFXButton backNavButton = new JFXButton();
|
||||
backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), -1, -1));
|
||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
backNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
|
||||
backNavButton.visibleProperty().set(canBack);
|
||||
|
||||
JFXButton closeNavButton = new JFXButton();
|
||||
JFXButton backNavButton = new JFXButton();
|
||||
backNavButton.setGraphic(SVG.back(Theme.foregroundFillBinding(), -1, -1));
|
||||
backNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
backNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
backNavButton.onActionProperty().bind(skinnable.onBackNavButtonActionProperty());
|
||||
backNavButton.visibleProperty().set(canBack);
|
||||
|
||||
JFXButton closeNavButton = new JFXButton();
|
||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||
closeNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
closeNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
||||
|
||||
if (canBack) navLeft.getChildren().add(backNavButton);
|
||||
if (canClose) navLeft.getChildren().add(closeNavButton);
|
||||
if (showCloseAsHome)
|
||||
closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1));
|
||||
else
|
||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||
closeNavButton.getStyleClass().add("jfx-decorator-button");
|
||||
closeNavButton.ripplerFillProperty().bind(Theme.whiteFillBinding());
|
||||
closeNavButton.onActionProperty().bind(skinnable.onCloseNavButtonActionProperty());
|
||||
|
||||
if (canBack) navLeft.getChildren().add(backNavButton);
|
||||
if (canClose) navLeft.getChildren().add(closeNavButton);
|
||||
if (showCloseAsHome)
|
||||
closeNavButton.setGraphic(SVG.home(Theme.foregroundFillBinding(), -1, -1));
|
||||
else
|
||||
closeNavButton.setGraphic(SVG.close(Theme.foregroundFillBinding(), -1, -1));
|
||||
}
|
||||
navBar.setLeft(navLeft);
|
||||
|
||||
BorderPane center = new BorderPane();
|
||||
if (title != null) {
|
||||
Label titleLabel = new Label();
|
||||
BorderPane.setAlignment(titleLabel, Pos.CENTER_LEFT);
|
||||
titleLabel.getStyleClass().add("jfx-decorator-title");
|
||||
if (titleBarTransparent) titleLabel.pseudoClassStateChanged(TRANSPARENT, true);
|
||||
if (!canClose && !canBack) {
|
||||
@@ -250,6 +250,11 @@ public class DecoratorSkin extends SkinBase<Decorator> {
|
||||
// we should have width 2 * 10 reduced
|
||||
titleLabel.setPrefWidth(leftPaneWidth - 20);
|
||||
}
|
||||
if (titleNode != null) {
|
||||
titleLabel.prefWidthProperty().bind(Bindings.createDoubleBinding(() -> {
|
||||
return leftPaneWidth - 20 - backNavButton.getWidth() - closeNavButton.getWidth();
|
||||
}, backNavButton.widthProperty(), closeNavButton.widthProperty()));
|
||||
}
|
||||
titleLabel.setText(title);
|
||||
center.setLeft(titleLabel);
|
||||
BorderPane.setAlignment(titleLabel, Pos.CENTER_LEFT);
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ public class InstallerWizardDownloadProvider implements DownloadProvider {
|
||||
|
||||
@Override
|
||||
public List<URL> getAssetObjectCandidates(String assetObjectLocation) {
|
||||
return fallback.injectURLWithCandidates(assetObjectLocation);
|
||||
return fallback.getAssetObjectCandidates(assetObjectLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -367,13 +367,16 @@
|
||||
|
||||
.jfx-tool-bar {
|
||||
-fx-font-size: 13.0;
|
||||
-fx-font-weight: BOLD;
|
||||
-fx-pref-width: 100.0%;
|
||||
-fx-min-height: 40px;
|
||||
-fx-max-height: 40px;
|
||||
-fx-pref-height: 40px;
|
||||
}
|
||||
|
||||
.jfx-tool-bar .jfx-decorator-title {
|
||||
-fx-font-weight: BOLD;
|
||||
}
|
||||
|
||||
.jfx-tool-bar .background {
|
||||
-fx-background-color: -fx-base-color;
|
||||
}
|
||||
@@ -948,37 +951,41 @@
|
||||
-fx-text-fill: gray;
|
||||
}
|
||||
|
||||
.log {
|
||||
.log-window .scroll-bar .thumb {
|
||||
-fx-fill: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.log-window .list-cell {
|
||||
-fx-text-fill: black;
|
||||
-fx-border-width: 0 0 1 0;
|
||||
-fx-border-color: #dddddd;
|
||||
}
|
||||
|
||||
.log:empty {
|
||||
.log-window .list-cell:empty {
|
||||
-fx-border-width: 0;
|
||||
}
|
||||
|
||||
.log:fatal {
|
||||
.log-window .list-cell:fatal {
|
||||
-fx-background-color: #F7A699;
|
||||
}
|
||||
|
||||
.log:error {
|
||||
.log-window .list-cell:error {
|
||||
-fx-background-color: #FFCCBB;
|
||||
}
|
||||
|
||||
.log:warn {
|
||||
.log-window .list-cell:warn {
|
||||
-fx-background-color: #FFEECC;
|
||||
}
|
||||
|
||||
.log:info {
|
||||
.log-window .list-cell:info {
|
||||
-fx-background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.log:debug {
|
||||
.log-window .list-cell:debug {
|
||||
-fx-background-color: #EEE9E0;
|
||||
}
|
||||
|
||||
.log:trace {
|
||||
.log-window .list-cell:trace {
|
||||
-fx-background-color: #EEE9E0;
|
||||
}
|
||||
|
||||
@@ -1131,6 +1138,7 @@
|
||||
|
||||
.jfx-decorator-tab .tab-container:selected .tab-label {
|
||||
-fx-text-fill: -fx-base-text-fill;
|
||||
-fx-font-weight: BOLD;
|
||||
}
|
||||
|
||||
.window {
|
||||
|
||||
@@ -168,13 +168,13 @@ lang.default=Use system language
|
||||
launch.advice.corrected=We have already fixed the JVM selection. If you want to keep your choice of Java version, you can disable the Java VM check in game settings.
|
||||
launch.advice.uncorrected=If you are sure that the game can be started normally, you can disable the Java VM check in game settings.
|
||||
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
|
||||
launch.advice.forge2760_liteloader=Forge 2760 and higher are not compatible with LiteLoader, please consider upgrading Forge to 2773 or later.
|
||||
launch.advice.forge2760_liteloader=Forge 2760 and higher are not compatible with LiteLoader, please consider upgrading Forge to 2773 or later. Shall we continue launching?
|
||||
launch.advice.java8_1_13=Minecraft 1.13 and later can only run on Java 8 or later.
|
||||
launch.advice.java8_51_1_13=Minecraft 1.13 may crash on Java 8 earlier than 1.8.0_51. Please install the latest version of Java 8.
|
||||
launch.advice.java9=You cannot launch Minecraft 1.12 or earlier with Java 9 or later versions of Java.
|
||||
launch.advice.newer_java=Java 8 is recommended to make the game faster. For many Minecraft 1.12 of higher, and most mods, Java 8 is required.
|
||||
launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game may crash. The launcher attempt to launch.
|
||||
launch.advice.too_large_memory_for_32bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game may crash. The maximum memory capacity for 32 bit systems is is 1024MB. The launcher will attempt to launch.
|
||||
launch.advice.not_enough_space=You have allocated too much memory, because the physical memory size is %dMB, your game may crash. Shall we continue launching?
|
||||
launch.advice.too_large_memory_for_32bit=You have allocated too much memory, because of your 32-Bit Java Runtime Environment, your game may crash. The maximum memory capacity for 32 bit systems is is 1024MB. Shall we continue launching?
|
||||
launch.failed=Unable to launch
|
||||
launch.failed.cannot_create_jvm=Java virtual machine could not be created. Java arguments may cause issues. Please restart without JVM arguments.
|
||||
launch.failed.creating_process=Failed to create process. Check your Java path.
|
||||
|
||||
@@ -167,13 +167,13 @@ lang.default=使用系統語言
|
||||
launch.advice.corrected=我們已經修正了問題。如果您確實希望使用您自訂的 Java 虛擬機,您可以在遊戲設定中關閉 Java 虛擬機相容性檢查。
|
||||
launch.advice.uncorrected=如果您確實希望使用您自訂的 Java 虛擬機,您可以在遊戲設定中關閉 Java 虛擬機相容性檢查。
|
||||
launch.advice.different_platform=您的系統為 64 位元,但是 Java 是 32 位元,建議您安裝 64 位 Java。
|
||||
launch.advice.forge2760_liteloader=Forge 2760 與 LiteLoader 不相容。請刪除 LiteLoader 或者變更 Forge 至 2773 或更新的版本。
|
||||
launch.advice.forge2760_liteloader=Forge 2760 與 LiteLoader 不相容。請刪除 LiteLoader 或者變更 Forge 至 2773 或更新的版本。是否繼續啟動?
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支援 Java 8 或更高版本,請使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低於 1.8.0_51 的 Java 版本可能會導致 Minecraft 1.13 崩潰。建議您到 https://java.com 安裝最新版的 Java 8。
|
||||
launch.advice.java9=低於 (包含) 1.13 的有安裝 Mod 的 Minecraft 版本不支援 Java 9 或更高版本,請使用 Java 8。
|
||||
launch.advice.newer_java=偵測到您未使用 Java 8 及更新版本,Java 8 能使遊戲更順暢而且 Minecraft 1.12 及更新版本和很多 Mod 強制需要 Java 8 版本。
|
||||
launch.advice.not_enough_space=您設定的記憶體大小過大,由於超過了系統記憶體大小 %dMB,所以可能影響遊戲體驗或無法啟動遊戲。啟動器仍會嘗試啟動。
|
||||
launch.advice.too_large_memory_for_32bit=您設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024MB 的值。啟動器仍會嘗試啟動。
|
||||
launch.advice.not_enough_space=您設定的記憶體大小過大,由於超過了系統記憶體大小 %dMB,所以可能影響遊戲體驗或無法啟動遊戲。是否繼續啟動?
|
||||
launch.advice.too_large_memory_for_32bit=您設定的記憶體大小過大,由於可能超過了 32 位元 Java 的記憶體分配限制,所以可能無法啟動遊戲,請將記憶體調至低於 1024MB 的值。是否繼續啟動?
|
||||
launch.failed=啟動失敗
|
||||
launch.failed.cannot_create_jvm=偵測到無法建立 Java 虛擬機,可能是 Java 參數有問題。可以在設定中開啟無參數模式啟動。
|
||||
launch.failed.creating_process=啟動失敗,在建立新處理程序時發生錯誤。可能是 Java 路徑錯誤。
|
||||
|
||||
@@ -167,13 +167,13 @@ lang.default=跟随系统语言
|
||||
launch.advice.corrected=我们已经修复了问题。如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置中关闭 Java 虚拟机兼容性检查。
|
||||
launch.advice.uncorrected=如果您确实希望使用您自定义的 Java 虚拟机,您可以在游戏设置中关闭 Java 虚拟机兼容性检查。
|
||||
launch.advice.different_platform=您的系统是 64 位,但是 Java 是 32 位的,建议您安装 64 位 Java。
|
||||
launch.advice.forge2760_liteloader=Forge 2760 与 LiteLoader 不兼容。请删除 LiteLoader 或者更换 Forge 至 2773 或更新的版本。
|
||||
launch.advice.forge2760_liteloader=Forge 2760 与 LiteLoader 不兼容。请删除 LiteLoader 或者更换 Forge 至 2773 或更新的版本。是否继续启动?
|
||||
launch.advice.java8_1_13=Minecraft 1.13 只支持 Java 8 或更高版本,请使用 Java 8 或最新版本。
|
||||
launch.advice.java8_51_1_13=低于 1.8.0_51 的 Java 版本可能会导致 Minecraft 1.13 崩溃。建议您到 https://java.com 安装最新版的 Java 8。
|
||||
launch.advice.java9=低于 1.13 的有安装 Mod 的 Minecraft 版本不支持 Java 9 或更高版本,请使用 Java 8。
|
||||
launch.advice.newer_java=检测到您未使用 Java 8 及更新版本,Java 8 能使游戏更流畅,而且 Minecraft 1.12 及更新版本和很多 Mod 强制需要 Java 8。
|
||||
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小 %dMB,所以可能影响游戏体验或无法启动游戏,启动器仍会尝试启动。
|
||||
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小,启动器仍会尝试启动。
|
||||
launch.advice.not_enough_space=您设置的内存大小过大,由于超过了系统内存大小 %dMB,所以可能影响游戏体验或无法启动游戏。是否继续启动?
|
||||
launch.advice.too_large_memory_for_32bit=您设置的内存大小过大,由于可能超过了 32 位 Java 的内存分配限制,所以可能无法启动游戏,请将内存调至 1024MB 或更小。是否继续启动?
|
||||
launch.failed=启动失败
|
||||
launch.failed.cannot_create_jvm=截获到无法创建 Java 虚拟机,可能是 Java 参数有问题,可以在设置中开启无参数模式启动。
|
||||
launch.failed.creating_process=启动失败,在创建新进程时发生错误,可能是 Java 路径错误。
|
||||
|
||||
@@ -63,8 +63,20 @@ public class AdaptedDownloadProvider implements DownloadProvider {
|
||||
public List<URL> injectURLWithCandidates(String baseURL) {
|
||||
List<DownloadProvider> d = downloadProviderCandidates;
|
||||
List<URL> results = new ArrayList<>(d.size());
|
||||
for (int i = 0; i < d.size(); i++) {
|
||||
results.add(NetworkUtils.toURL(d.get(i).injectURL(baseURL)));
|
||||
for (DownloadProvider downloadProvider : d) {
|
||||
results.add(NetworkUtils.toURL(downloadProvider.injectURL(baseURL)));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<URL> injectURLsWithCandidates(List<String> urls) {
|
||||
List<DownloadProvider> d = downloadProviderCandidates;
|
||||
List<URL> results = new ArrayList<>(d.size());
|
||||
for (DownloadProvider downloadProvider : d) {
|
||||
for (String baseURL : urls) {
|
||||
results.add(NetworkUtils.toURL(downloadProvider.injectURL(baseURL)));
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ public final class ForgeBMCLVersionList extends VersionList<ForgeRemoteVersion>
|
||||
+ (StringUtils.isNotBlank(version.getBranch()) ? "-" + version.getBranch() : "");
|
||||
String fileName1 = "forge-" + classifier + "-" + file.getCategory() + "." + file.getFormat();
|
||||
String fileName2 = "forge-" + classifier + "-" + gameVersion + "-" + file.getCategory() + "." + file.getFormat();
|
||||
urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + gameVersion + "/" + fileName2);
|
||||
urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "/" + fileName1);
|
||||
urls.add("https://files.minecraftforge.net/maven/net/minecraftforge/forge/" + classifier + "-" + gameVersion + "/" + fileName2);
|
||||
urls.add(NetworkUtils.withQuery("https://bmclapi2.bangbang93.com/forge/download", mapOf(
|
||||
pair("mcversion", version.getGameVersion()),
|
||||
pair("version", version.getVersion()),
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.download.forge;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.DependencyManager;
|
||||
import org.jackhuang.hmcl.download.VersionMismatchException;
|
||||
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
@@ -27,6 +28,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 java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
@@ -97,7 +99,19 @@ public final class ForgeInstallTask extends Task<Version> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws IOException, VersionMismatchException {
|
||||
public void execute() throws IOException, VersionMismatchException, OptiFineInstallTask.UnsupportedOptiFineInstallationException {
|
||||
String originalMainClass = version.resolve(dependencyManager.getGameRepository()).getMainClass();
|
||||
if (VersionNumber.VERSION_COMPARATOR.compare("1.13", remote.getGameVersion()) <= 0) {
|
||||
// Forge 1.13 is not compatible with any other libraries.
|
||||
if (!"net.minecraft.client.main.Main".equals(originalMainClass) && !"cpw.mods.modlauncher.Launcher".equals(originalMainClass))
|
||||
throw new OptiFineInstallTask.UnsupportedOptiFineInstallationException();
|
||||
} else {
|
||||
// Forge 1.12 and older versions is compatible with vanilla and launchwrapper.
|
||||
// if (!"net.minecraft.client.main.Main".equals(originalMainClass) && !"net.minecraft.launchwrapper.Launch".equals(originalMainClass))
|
||||
// throw new OptiFineInstallTask.UnsupportedOptiFineInstallationException();
|
||||
}
|
||||
|
||||
|
||||
if (detectForgeInstallerType(dependencyManager, version, installer))
|
||||
dependency = new ForgeNewInstallTask(dependencyManager, version, remote.getSelfVersion(), installer);
|
||||
else
|
||||
|
||||
@@ -131,10 +131,6 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
String originalMainClass = version.resolve(dependencyManager.getGameRepository()).getMainClass();
|
||||
if (!"net.minecraft.client.main.Main".equals(originalMainClass) && !"cpw.mods.modlauncher.Launcher".equals(originalMainClass))
|
||||
throw new OptiFineInstallTask.UnsupportedOptiFineInstallationException();
|
||||
|
||||
Path temp = Files.createTempDirectory("forge_installer");
|
||||
int finished = 0;
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
|
||||
|
||||
@@ -22,12 +22,14 @@ import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
@@ -84,7 +86,18 @@ public final class GameLibrariesTask extends Task<Void> {
|
||||
if (!download && integrityCheck && !library.getDownload().validateChecksum(jar, true)) download = true;
|
||||
if (!download && integrityCheck &&
|
||||
library.getChecksums() != null && !library.getChecksums().isEmpty() &&
|
||||
!LibraryDownloadTask.checksumValid(jar.toFile(), library.getChecksums())) download = true;
|
||||
!LibraryDownloadTask.checksumValid(file, library.getChecksums())) download = true;
|
||||
if (!download && integrityCheck) {
|
||||
String ext = FileUtils.getExtension(file);
|
||||
if (ext.equals("jar")) {
|
||||
try (JarFile jarFile = new JarFile(file)) {
|
||||
jarFile.getManifest();
|
||||
} catch (IOException ignored) {
|
||||
// the Jar file is malformed, so re-download it.
|
||||
download = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to calc hash value of file " + jar, e);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
@@ -34,13 +36,11 @@ import org.tukaani.xz.XZInputStream;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
import java.util.jar.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
|
||||
@@ -30,13 +30,16 @@ import org.jackhuang.hmcl.event.RemoveVersionEvent;
|
||||
import org.jackhuang.hmcl.event.RenameVersionEvent;
|
||||
import org.jackhuang.hmcl.mod.ModManager;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.ToStringBuilder;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
@@ -154,28 +157,32 @@ public class DefaultGameRepository implements GameRepository {
|
||||
|
||||
try {
|
||||
Version fromVersion = getVersion(from);
|
||||
File fromDir = getVersionRoot(from);
|
||||
File toDir = getVersionRoot(to);
|
||||
if (!fromDir.renameTo(toDir))
|
||||
return false;
|
||||
Path fromDir = getVersionRoot(from).toPath();
|
||||
Path toDir = getVersionRoot(to).toPath();
|
||||
Files.move(fromDir, toDir);
|
||||
|
||||
File toJson = new File(toDir, to + ".json");
|
||||
File toJar = new File(toDir, to + ".jar");
|
||||
Path fromJson = toDir.resolve(from + ".json");
|
||||
Path fromJar = toDir.resolve(from + ".jar");
|
||||
Path toJson = toDir.resolve(to + ".json");
|
||||
Path toJar = toDir.resolve(to + ".jar");
|
||||
|
||||
if (!new File(toDir, from + ".json").renameTo(toJson)
|
||||
|| !new File(toDir, from + ".jar").renameTo(toJar)) {
|
||||
try {
|
||||
Files.move(fromJson, toJson);
|
||||
Files.move(fromJar, toJar);
|
||||
} catch (IOException e) {
|
||||
// recovery
|
||||
toJson.renameTo(new File(toDir, from + ".json"));
|
||||
toJar.renameTo(new File(toDir, from + ".jar"));
|
||||
toDir.renameTo(fromDir);
|
||||
return false;
|
||||
Lang.ignoringException(() -> Files.move(toJson, fromJson));
|
||||
Lang.ignoringException(() -> Files.move(toJar, fromJar));
|
||||
Lang.ignoringException(() -> Files.move(toDir, fromDir));
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (fromVersion.getId().equals(fromVersion.getJar()))
|
||||
fromVersion = fromVersion.setJar(null);
|
||||
FileUtils.writeText(toJson, JsonUtils.GSON.toJson(fromVersion.setId(to)));
|
||||
FileUtils.writeText(toJson.toFile(), JsonUtils.GSON.toJson(fromVersion.setId(to)));
|
||||
return true;
|
||||
} catch (IOException | JsonParseException | VersionNotFoundException e) {
|
||||
LOG.log(Level.WARNING, "Unable to rename version " + from + " to " + to, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ public class DefaultLauncher extends Launcher {
|
||||
.replace("$INST_MC_DIR", repository.getRunDirectory(version.getId()).getAbsolutePath())
|
||||
.replace("$INST_JAVA", options.getJava().getBinary().toString());
|
||||
|
||||
new ProcessBuilder(preLaunchCommand)
|
||||
new ProcessBuilder(StringUtils.tokenize(preLaunchCommand))
|
||||
.directory(runDirectory).start().waitFor();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import java.io.RandomAccessFile;
|
||||
import java.math.BigInteger;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.MessageDigest;
|
||||
@@ -91,6 +92,7 @@ public class FileDownloadTask extends Task<Void> {
|
||||
private CacheRepository repository = CacheRepository.getInstance();
|
||||
private RandomAccessFile rFile;
|
||||
private InputStream stream;
|
||||
private final ArrayList<IntegrityCheckHandler> integrityCheckHandlers = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param url the URL of remote file.
|
||||
@@ -154,6 +156,8 @@ public class FileDownloadTask extends Task<Void> {
|
||||
this.integrityCheck = integrityCheck;
|
||||
this.retry = retry;
|
||||
|
||||
this.addIntegrityCheckHandler(ZIP_INTEGRITY_CHECK_HANDLER);
|
||||
|
||||
setName(file.getName());
|
||||
setExecutor(Schedulers.io());
|
||||
}
|
||||
@@ -196,6 +200,10 @@ public class FileDownloadTask extends Task<Void> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public void addIntegrityCheckHandler(IntegrityCheckHandler handler) {
|
||||
integrityCheckHandlers.add(Objects.requireNonNull(handler));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
boolean checkETag;
|
||||
@@ -219,88 +227,99 @@ public class FileDownloadTask extends Task<Void> {
|
||||
Exception exception = null;
|
||||
URL failedURL = null;
|
||||
|
||||
for (int repeat = 0; repeat < retry * urls.size(); repeat++) {
|
||||
URL url = urls.get(repeat / retry);
|
||||
if (isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
Logging.LOG.log(Level.FINER, "Downloading " + url + " to " + file);
|
||||
Path temp = null;
|
||||
|
||||
try {
|
||||
updateProgress(0);
|
||||
|
||||
HttpURLConnection con = NetworkUtils.createConnection(url);
|
||||
if (checkETag) repository.injectConnection(con);
|
||||
con = NetworkUtils.resolveConnection(con);
|
||||
|
||||
if (con.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
// Handle cache
|
||||
try {
|
||||
Path cache = repository.getCachedRemoteFile(con);
|
||||
FileUtils.copyFile(cache.toFile(), file);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to use cached file, redownload it", e);
|
||||
repository.removeRemoteEntry(con);
|
||||
// Now we must reconnect the server since 304 may result in empty content,
|
||||
// if we want to redownload the file, we must reconnect the server without etag settings.
|
||||
repeat--;
|
||||
continue;
|
||||
}
|
||||
} else if (con.getResponseCode() / 100 != 2) {
|
||||
throw new ResponseCodeException(url, con.getResponseCode());
|
||||
int repeat = 0;
|
||||
download: for (URL url : urls) {
|
||||
for (int retryTime = 0; retryTime < retry; retryTime++) {
|
||||
if (isCancelled()) {
|
||||
break download;
|
||||
}
|
||||
|
||||
int contentLength = con.getContentLength();
|
||||
if (contentLength < 0)
|
||||
throw new IOException("The content length is invalid.");
|
||||
Logging.LOG.log(Level.FINER, "Downloading " + url + " to " + file);
|
||||
Path temp = null;
|
||||
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
throw new IOException("Could not make directory " + file.getAbsoluteFile().getParent());
|
||||
try {
|
||||
updateProgress(0);
|
||||
|
||||
temp = Files.createTempFile(null, null);
|
||||
rFile = new RandomAccessFile(temp.toFile(), "rw");
|
||||
HttpURLConnection con = NetworkUtils.createConnection(url);
|
||||
if (checkETag) repository.injectConnection(con);
|
||||
con = NetworkUtils.resolveConnection(con);
|
||||
|
||||
MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest();
|
||||
|
||||
stream = con.getInputStream();
|
||||
int lastDownloaded = 0, downloaded = 0;
|
||||
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
while (true) {
|
||||
if (isCancelled()) {
|
||||
break;
|
||||
if (con.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
// Handle cache
|
||||
try {
|
||||
Path cache = repository.getCachedRemoteFile(con);
|
||||
FileUtils.copyFile(cache.toFile(), file);
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to use cached file, redownload it", e);
|
||||
repository.removeRemoteEntry(con);
|
||||
// Now we must reconnect the server since 304 may result in empty content,
|
||||
// if we want to redownload the file, we must reconnect the server without etag settings.
|
||||
retryTime--;
|
||||
continue;
|
||||
}
|
||||
} else if (con.getResponseCode() / 100 == 4) {
|
||||
break; // we will not try this URL again
|
||||
} else if (con.getResponseCode() / 100 != 2) {
|
||||
throw new ResponseCodeException(url, con.getResponseCode());
|
||||
}
|
||||
|
||||
int read = stream.read(buffer);
|
||||
if (read == -1)
|
||||
break;
|
||||
int contentLength = con.getContentLength();
|
||||
if (contentLength < 0)
|
||||
throw new IOException("The content length is invalid.");
|
||||
|
||||
if (digest != null) {
|
||||
digest.update(buffer, 0, read);
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
throw new IOException("Could not make directory " + file.getAbsoluteFile().getParent());
|
||||
|
||||
temp = Files.createTempFile(null, null);
|
||||
rFile = new RandomAccessFile(temp.toFile(), "rw");
|
||||
|
||||
MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest();
|
||||
|
||||
stream = con.getInputStream();
|
||||
int lastDownloaded = 0, downloaded = 0;
|
||||
byte[] buffer = new byte[IOUtils.DEFAULT_BUFFER_SIZE];
|
||||
while (true) {
|
||||
if (isCancelled()) {
|
||||
break;
|
||||
}
|
||||
|
||||
int read = stream.read(buffer);
|
||||
if (read == -1)
|
||||
break;
|
||||
|
||||
if (digest != null) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
|
||||
// Write buffer to file.
|
||||
rFile.write(buffer, 0, read);
|
||||
downloaded += read;
|
||||
|
||||
// Update progress information per second
|
||||
updateProgress(downloaded, contentLength);
|
||||
|
||||
updateDownloadSpeed(downloaded - lastDownloaded);
|
||||
lastDownloaded = downloaded;
|
||||
}
|
||||
|
||||
// Write buffer to file.
|
||||
rFile.write(buffer, 0, read);
|
||||
downloaded += read;
|
||||
|
||||
// Update progress information per second
|
||||
updateProgress(downloaded, contentLength);
|
||||
|
||||
updateDownloadSpeed(downloaded - lastDownloaded);
|
||||
lastDownloaded = downloaded;
|
||||
}
|
||||
|
||||
updateDownloadSpeed(downloaded - lastDownloaded);
|
||||
closeFiles();
|
||||
|
||||
closeFiles();
|
||||
if (downloaded != contentLength)
|
||||
throw new IOException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
||||
|
||||
// Restore temp file to original name.
|
||||
if (isCancelled()) {
|
||||
temp.toFile().delete();
|
||||
break download;
|
||||
}
|
||||
|
||||
for (IntegrityCheckHandler handler : integrityCheckHandlers) {
|
||||
handler.checkIntegrity(temp, file.toPath());
|
||||
}
|
||||
|
||||
// Restore temp file to original name.
|
||||
if (isCancelled()) {
|
||||
temp.toFile().delete();
|
||||
break;
|
||||
} else {
|
||||
Files.deleteIfExists(file.toPath());
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
throw new IOException("Unable to make parent directory " + file);
|
||||
@@ -309,37 +328,34 @@ public class FileDownloadTask extends Task<Void> {
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Unable to move temp file from " + temp + " to " + file, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (downloaded != contentLength)
|
||||
throw new IOException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
||||
|
||||
// Integrity check
|
||||
if (integrityCheck != null) {
|
||||
integrityCheck.performCheck(digest);
|
||||
}
|
||||
|
||||
if (caching && integrityCheck != null) {
|
||||
try {
|
||||
repository.cacheFile(file.toPath(), integrityCheck.getAlgorithm(), integrityCheck.getChecksum());
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to cache file", e);
|
||||
// Integrity check
|
||||
if (integrityCheck != null) {
|
||||
integrityCheck.performCheck(digest);
|
||||
}
|
||||
}
|
||||
|
||||
if (checkETag) {
|
||||
repository.cacheRemoteFile(file.toPath(), con);
|
||||
}
|
||||
if (caching && integrityCheck != null) {
|
||||
try {
|
||||
repository.cacheFile(file.toPath(), integrityCheck.getAlgorithm(), integrityCheck.getChecksum());
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Failed to cache file", e);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
if (temp != null)
|
||||
temp.toFile().delete();
|
||||
failedURL = url;
|
||||
exception = e;
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + (repeat + 1), e);
|
||||
} finally {
|
||||
closeFiles();
|
||||
if (checkETag) {
|
||||
repository.cacheRemoteFile(file.toPath(), con);
|
||||
}
|
||||
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
if (temp != null)
|
||||
temp.toFile().delete();
|
||||
failedURL = url;
|
||||
exception = e;
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + (++repeat), e);
|
||||
} finally {
|
||||
closeFiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +391,7 @@ public class FileDownloadTask extends Task<Void> {
|
||||
|
||||
/**
|
||||
* Download speed in byte/sec.
|
||||
* @return
|
||||
* @return download speed
|
||||
*/
|
||||
public int getSpeed() {
|
||||
return speed;
|
||||
@@ -387,4 +403,22 @@ public class FileDownloadTask extends Task<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
public interface IntegrityCheckHandler {
|
||||
/**
|
||||
* Check whether the file is corrupted or not.
|
||||
* @param filePath the file locates in (maybe in temp directory)
|
||||
* @param destinationPath for real file name
|
||||
* @throws IOException if the file is corrupted
|
||||
*/
|
||||
void checkIntegrity(Path filePath, Path destinationPath) throws IOException;
|
||||
}
|
||||
|
||||
public static final IntegrityCheckHandler ZIP_INTEGRITY_CHECK_HANDLER = (filePath, destinationPath) -> {
|
||||
String ext = FileUtils.getExtension(destinationPath).toLowerCase();
|
||||
if (ext.equals("zip") || ext.equals("jar")) {
|
||||
try (FileSystem ignored = CompressingUtils.createReadOnlyZipFileSystem(filePath)) {
|
||||
// test for zip format
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user