Support install Forge/OptiFine from local file

This commit is contained in:
huanghongxun
2019-04-30 20:26:31 +08:00
parent 6595e0a3cf
commit 5e659352d7
12 changed files with 285 additions and 23 deletions

View File

@@ -21,6 +21,7 @@ import javafx.scene.Node;
import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.RemoteVersion; import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.download.game.LibraryDownloadException; import org.jackhuang.hmcl.download.game.LibraryDownloadException;
import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask; import org.jackhuang.hmcl.download.optifine.OptiFineInstallTask;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
@@ -143,6 +144,11 @@ public final class InstallerWizardProvider implements WizardProvider {
} }
} else if (exception instanceof OptiFineInstallTask.UnsupportedOptiFineInstallationException) { } else if (exception instanceof OptiFineInstallTask.UnsupportedOptiFineInstallationException) {
Controllers.dialog(i18n("install.failed.optifine_conflict"), i18n("install.failed"), MessageType.ERROR, next); Controllers.dialog(i18n("install.failed.optifine_conflict"), i18n("install.failed"), MessageType.ERROR, next);
} else if (exception instanceof UnsupportedOperationException) {
Controllers.dialog(i18n("install.failed.install_online"), i18n("install.failed"), MessageType.ERROR, next);
} else if (exception instanceof VersionMismatchException) {
VersionMismatchException e = ((VersionMismatchException) exception);
Controllers.dialog(i18n("install.failed.version_mismatch", e.getExpect(), e.getActual()), i18n("install.failed"), MessageType.ERROR, next);
} else { } else {
Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageType.ERROR, next); Controllers.dialog(StringUtils.getStackTrace(exception), i18n("install.failed"), MessageType.ERROR, next);
} }

View File

@@ -17,6 +17,9 @@
*/ */
package org.jackhuang.hmcl.ui.versions; package org.jackhuang.hmcl.ui.versions;
import javafx.scene.Node;
import javafx.scene.control.Skin;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.download.LibraryAnalyzer; import org.jackhuang.hmcl.download.LibraryAnalyzer;
import org.jackhuang.hmcl.download.MaintainTask; import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask; import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
@@ -26,25 +29,42 @@ import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Profile; import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers; import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers; import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.InstallerItem; import org.jackhuang.hmcl.task.TaskListener;
import org.jackhuang.hmcl.ui.ListPage; import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider; import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider; import org.jackhuang.hmcl.ui.download.UpdateInstallerWizardProvider;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.util.Arrays;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*; import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n; import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class InstallerListPage extends ListPage<InstallerItem> { public class InstallerListPage extends ListPageBase<InstallerItem> {
private Profile profile; private Profile profile;
private String versionId; private String versionId;
private Version version; private Version version;
private String gameVersion; private String gameVersion;
{
FXUtils.applyDragListener(this, it -> Arrays.asList("jar", "exe").contains(FileUtils.getExtension(it)), mods -> {
if (!mods.isEmpty())
doInstallOffline(mods.get(0));
});
}
@Override
protected Skin<?> createDefaultSkin() {
return new InstallerListPageSkin();
}
public void loadVersion(Profile profile, String versionId) { public void loadVersion(Profile profile, String versionId) {
this.profile = profile; this.profile = profile;
this.versionId = versionId; this.versionId = versionId;
@@ -83,11 +103,54 @@ public class InstallerListPage extends ListPage<InstallerItem> {
}).start(); }).start();
} }
@Override public void installOnline() {
public void add() {
if (gameVersion == null) if (gameVersion == null)
Controllers.dialog(i18n("version.cannot_read")); Controllers.dialog(i18n("version.cannot_read"));
else else
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion, version)); Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion, version));
} }
public void installOffline() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("install.installer.install_offline.extension"), "*.jar", "*.exe"));
File file = chooser.showOpenDialog(Controllers.getStage());
if (file != null) doInstallOffline(file);
}
private void doInstallOffline(File file) {
Task task = profile.getDependency().installLibraryAsync(version, file.toPath())
.then(profile.getRepository().refreshVersionsAsync());
task.setName(i18n("install.installer.install_offline"));
TaskExecutor executor = task.executor(new TaskListener() {
@Override
public void onStop(boolean success, TaskExecutor executor) {
runInFX(() -> {
if (success) {
loadVersion(profile, versionId);
Controllers.dialog(i18n("install.success"));
} else {
if (executor.getLastException() == null)
return;
InstallerWizardProvider.alertFailureMessage(executor.getLastException(), null);
}
});
}
});
Controllers.taskDialog(executor, i18n("install.installer.install_offline"));
executor.start();
}
private class InstallerListPageSkin extends ToolbarListPageSkin<InstallerListPage> {
InstallerListPageSkin() {
super(InstallerListPage.this);
}
@Override
protected List<Node> initializeToolbar(InstallerListPage skinnable) {
return Arrays.asList(
createToolbarButton(i18n("install.installer.install_online"), SVG::plus, skinnable::installOnline),
createToolbarButton(i18n("install.installer.install_offline"), SVG::plus, skinnable::installOffline));
}
}
} }

View File

@@ -64,7 +64,6 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
@Override @Override
public void onStop(boolean success, TaskExecutor executor) { public void onStop(boolean success, TaskExecutor executor) {
runInFX(() -> { runInFX(() -> {
pane.fireEvent(new DialogCloseEvent());
if (success) { if (success) {
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String) if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
Controllers.dialog((String) settings.get("success_message"), null, MessageType.FINE, () -> onEnd()); Controllers.dialog((String) settings.get("success_message"), null, MessageType.FINE, () -> onEnd());

View File

@@ -131,11 +131,18 @@ install.failed=Failed to install
install.failed.downloading=Failed to install due to some files not downloaded successfully install.failed.downloading=Failed to install due to some files not downloaded successfully
install.failed.downloading.detail=Failed to download file: %s install.failed.downloading.detail=Failed to download file: %s
install.failed.downloading.timeout=Download timed out: %s install.failed.downloading.timeout=Download timed out: %s
install.failed.install_online=Unable to recognize what you provided installer file is
install.failed.optifine_conflict=OptiFine and Forge are both installed simultaneously on Minecraft 1.13 install.failed.optifine_conflict=OptiFine and Forge are both installed simultaneously on Minecraft 1.13
install.failed.version_mismatch=The library requires game version %s, but actual version is %s.
install.installer.choose=Choose a %s version install.installer.choose=Choose a %s version
install.installer.forge=Forge install.installer.forge=Forge
install.installer.game=Game install.installer.game=Game
install.installer.install= install.installer.install=Install %s
install.installer.install_offline=Install/Upgrade from local file
install.installer.install_offline.extension=Forge/OptiFine installer
install.installer.install_offline.tooltip=Support importing Forge/OptiFine installer jar file
install.installer.install_online=Install Online
install.installer.install_online.tooltip=Support Forge, OptiFine, LiteLoader installation.
install.installer.liteloader=LiteLoader install.installer.liteloader=LiteLoader
install.installer.not_installed=%s not Installed install.installer.not_installed=%s not Installed
install.installer.optifine=OptiFine install.installer.optifine=OptiFine

View File

@@ -130,11 +130,18 @@ install.failed=安裝失敗
install.failed.downloading=安裝失敗,部分文件未能完成下載 install.failed.downloading=安裝失敗,部分文件未能完成下載
install.failed.downloading.detail=未能下載檔案:%s install.failed.downloading.detail=未能下載檔案:%s
install.failed.downloading.timeout=下載超時:%s install.failed.downloading.timeout=下載超時:%s
install.failed.optifine_conflict=暫不支持 OptiFine 與 Forge 同時安裝於 Minecraft 1.13 install.failed.install_online=無法識別要安裝的軟體
install.failed.optifine_conflict=暫不支持 OptiFine 與 Forge 同時安裝在 Minecraft 1.13 上
install.failed.version_mismatch=該軟體需要的遊戲版本為 %s但實際的遊戲版本為 %s。
install.installer.choose=選擇 %s 版本 install.installer.choose=選擇 %s 版本
install.installer.forge=Forge install.installer.forge=Forge
install.installer.game=遊戲 install.installer.game=遊戲
install.installer.install=安裝 %s install.installer.install=安裝 %s
install.installer.install_offline=從本地檔案安裝/升級
install.installer.install_offline.extension=Forge/OptiFine 安裝器
install.installer.install_offline.tooltip=支持導入已經下載好的 Forge/OptiFine 安裝器
install.installer.install_online=在線安裝
install.installer.install_online.tooltip=支持安裝 Forge、OptiFine、LiteLoader
install.installer.liteloader=LiteLoader install.installer.liteloader=LiteLoader
install.installer.not_installed=暫時不安裝 %s可以點擊此處安裝 install.installer.not_installed=暫時不安裝 %s可以點擊此處安裝
install.installer.optifine=OptiFine install.installer.optifine=OptiFine

View File

@@ -130,11 +130,18 @@ install.failed=安装失败
install.failed.downloading=安装失败,部分文件未能完成下载 install.failed.downloading=安装失败,部分文件未能完成下载
install.failed.downloading.detail=未能下载文件:%s install.failed.downloading.detail=未能下载文件:%s
install.failed.downloading.timeout=下载超时:%s install.failed.downloading.timeout=下载超时:%s
install.failed.optifine_conflict=暂不支持 OptiFine 与 Forge 同时安装于 Minecraft 1.13 install.failed.install_online=无法识别要安装的软件
install.failed.optifine_conflict=暂不支持 OptiFine 与 Forge 同时安装在 Minecraft 1.13 上
install.failed.version_mismatch=该软件需要的游戏版本为 %s但实际的游戏版本为 %s。
install.installer.choose=选择 %s 版本 install.installer.choose=选择 %s 版本
install.installer.forge=Forge install.installer.forge=Forge
install.installer.game=游戏 install.installer.game=游戏
install.installer.install=安装 %s install.installer.install=安装 %s
install.installer.install_offline=从本地文件安装/升级
install.installer.install_offline.extension=Forge/OptiFine 安装器
install.installer.install_offline.tooltip=支持导入已经下载好的 Forge/OptiFine 安装器
install.installer.install_online=在线安装
install.installer.install_online.tooltip=支持安装 Forge、OptiFine、LiteLoader
install.installer.liteloader=LiteLoader install.installer.liteloader=LiteLoader
install.installer.not_installed=暂不安装 %s可以点击此处安装 install.installer.not_installed=暂不安装 %s可以点击此处安装
install.installer.optifine=OptiFine install.installer.optifine=OptiFine

View File

@@ -31,6 +31,9 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult; import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import java.io.IOException;
import java.nio.file.Path;
/** /**
* Note: This class has no state. * Note: This class has no state.
* *
@@ -112,8 +115,29 @@ public class DefaultDependencyManager extends AbstractDependencyManager {
.thenCompose(newVersion -> new VersionJsonSaveTask(repository, newVersion)); .thenCompose(newVersion -> new VersionJsonSaveTask(repository, newVersion));
} }
public ExceptionalFunction<Version, TaskResult<Version>, ?> installLibraryAsync(RemoteVersion libraryVersion) { public ExceptionalFunction<Version, TaskResult<Version>, ?> installLibraryAsync(RemoteVersion libraryVersion) {
return version -> installLibraryAsync(version, libraryVersion); return version -> installLibraryAsync(version, libraryVersion);
} }
public Task installLibraryAsync(Version oldVersion, Path installer) {
return Task
.of(() -> {
})
.thenCompose(() -> {
try {
return ForgeInstallTask.install(this, oldVersion, installer);
} catch (IOException ignore) {
}
try {
return OptiFineInstallTask.install(this, oldVersion, installer);
} catch (IOException ignore) {
}
throw new UnsupportedOperationException("Library cannot be recognized");
})
.thenCompose(LibrariesUniqueTask::new)
.thenCompose(MaintainTask::new)
.thenCompose(newVersion -> new VersionJsonSaveTask(repository, newVersion));
}
} }

View File

@@ -0,0 +1,37 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 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.download;
public class VersionMismatchException extends Exception {
private final String expect, actual;
public VersionMismatchException(String expect, String actual) {
super("Mismatched game version requirement, library requires game to be " + expect + ", but actual is " + actual);
this.expect = expect;
this.actual = actual;
}
public String getExpect() {
return expect;
}
public String getActual() {
return actual;
}
}

View File

@@ -18,17 +18,26 @@
package org.jackhuang.hmcl.download.forge; package org.jackhuang.hmcl.download.forge;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Version; import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.FileDownloadTask; import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult; import org.jackhuang.hmcl.task.TaskResult;
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.io.NetworkUtils; import org.jackhuang.hmcl.util.io.NetworkUtils;
import org.jackhuang.hmcl.util.versioning.VersionNumber; import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import java.util.Optional;
/** /**
* *
@@ -90,4 +99,36 @@ public final class ForgeInstallTask extends TaskResult<Version> {
else else
dependency = new ForgeOldInstallTask(dependencyManager, version, installer); dependency = new ForgeOldInstallTask(dependencyManager, version, installer);
} }
/**
* Install Forge library from existing local file.
*
* @param dependencyManager game repository
* @param version version.json
* @param installer the Forge installer, either the new or old one.
* @return the task to install library
* @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.
* @throws VersionMismatchException if required game version of installer does not match the actual one.
*/
public static TaskResult<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
Optional<String> gameVersion = GameVersion.minecraftVersion(dependencyManager.getGameRepository().getVersionJar(version));
if (!gameVersion.isPresent()) throw new IOException();
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
String installProfileText = FileUtils.readText(fs.getPath("install_profile.json"));
Map installProfile = JsonUtils.fromNonNullJson(installProfileText, Map.class);
if (installProfile.containsKey("spec")) {
ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);
if (!gameVersion.get().equals(profile.getMinecraft()))
throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());
return new ForgeNewInstallTask(dependencyManager, version, installer);
} else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) {
ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class);
if (!gameVersion.get().equals(profile.getInstall().getMinecraft()))
throw new VersionMismatchException(profile.getInstall().getMinecraft(), gameVersion.get());
return new ForgeOldInstallTask(dependencyManager, version, installer);
} else {
throw new IOException();
}
}
}
} }

View File

@@ -17,10 +17,13 @@
*/ */
package org.jackhuang.hmcl.download.forge; package org.jackhuang.hmcl.download.forge;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.game.Artifact; import org.jackhuang.hmcl.game.Artifact;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.function.ExceptionalFunction; import org.jackhuang.hmcl.util.function.ExceptionalFunction;
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
import org.jackhuang.hmcl.util.gson.Validation;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@@ -29,7 +32,7 @@ import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Immutable @Immutable
public class ForgeNewInstallProfile { public class ForgeNewInstallProfile implements Validation {
private final int spec; private final int spec;
private final String minecraft; private final String minecraft;
@@ -97,6 +100,8 @@ public class ForgeNewInstallProfile {
/** /**
* Data for processors. * Data for processors.
*
* @return a mutable data map for processors.
*/ */
public Map<String, String> getData() { public Map<String, String> getData() {
if (data == null) if (data == null)
@@ -105,7 +110,13 @@ public class ForgeNewInstallProfile {
return data.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getClient())); return data.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getClient()));
} }
public static class Processor { @Override
public void validate() throws JsonParseException, TolerableValidationException {
if (minecraft == null || json == null || path == null)
throw new JsonParseException("ForgeNewInstallProfile is malformed");
}
public static class Processor implements Validation {
private final List<String> sides; private final List<String> sides;
private final Artifact jar; private final Artifact jar;
private final List<Artifact> classpath; private final List<Artifact> classpath;
@@ -170,6 +181,12 @@ public class ForgeNewInstallProfile {
public Map<String, String> getOutputs() { public Map<String, String> getOutputs() {
return outputs == null ? Collections.emptyMap() : outputs; return outputs == null ? Collections.emptyMap() : outputs;
} }
@Override
public void validate() throws JsonParseException, TolerableValidationException {
if (jar == null)
throw new JsonParseException("Processor::jar cannot be null");
}
} }
public static class Datum { public static class Datum {

View File

@@ -18,19 +18,26 @@
package org.jackhuang.hmcl.download.optifine; package org.jackhuang.hmcl.download.optifine;
import org.jackhuang.hmcl.download.DefaultDependencyManager; import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.game.LibrariesDownloadInfo; import org.jackhuang.hmcl.download.VersionMismatchException;
import org.jackhuang.hmcl.game.Library; import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.game.LibraryDownloadInfo;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.task.Task; import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskResult; import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jenkinsci.constant_pool_scanner.ConstantPool;
import org.jenkinsci.constant_pool_scanner.ConstantPoolScanner;
import org.jenkinsci.constant_pool_scanner.ConstantType;
import org.jenkinsci.constant_pool_scanner.Utf8Constant;
import java.util.Arrays; import java.io.File;
import java.util.Collection; import java.io.IOException;
import java.util.LinkedList; import java.nio.file.FileSystem;
import java.util.List; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import static org.jackhuang.hmcl.util.Lang.getOrDefault;
/** /**
* <b>Note</b>: OptiFine should be installed in the end. * <b>Note</b>: OptiFine should be installed in the end.
@@ -42,13 +49,19 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
private final DefaultDependencyManager dependencyManager; private final DefaultDependencyManager dependencyManager;
private final Version version; private final Version version;
private final OptiFineRemoteVersion remote; private final OptiFineRemoteVersion remote;
private final Path installer;
private final List<Task> dependents = new LinkedList<>(); private final List<Task> dependents = new LinkedList<>();
private final List<Task> dependencies = new LinkedList<>(); private final List<Task> dependencies = new LinkedList<>();
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) { public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion) {
this(dependencyManager, version, remoteVersion, null);
}
public OptiFineInstallTask(DefaultDependencyManager dependencyManager, Version version, OptiFineRemoteVersion remoteVersion, Path installer) {
this.dependencyManager = dependencyManager; this.dependencyManager = dependencyManager;
this.version = version; this.version = version;
this.remote = remoteVersion; this.remote = remoteVersion;
this.installer = installer;
} }
@Override @Override
@@ -67,7 +80,7 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
} }
@Override @Override
public void execute() { public void execute() throws IOException {
if (!Arrays.asList("net.minecraft.client.main.Main", if (!Arrays.asList("net.minecraft.client.main.Main",
"net.minecraft.launchwrapper.Launch") "net.minecraft.launchwrapper.Launch")
.contains(version.getMainClass())) .contains(version.getMainClass()))
@@ -82,6 +95,10 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
remote.getUrl())) remote.getUrl()))
); );
if (installer != null) {
FileUtils.copyFile(installer, dependencyManager.getGameRepository().getLibraryFile(version, library).toPath());
}
List<Library> libraries = new LinkedList<>(); List<Library> libraries = new LinkedList<>();
libraries.add(library); libraries.add(library);
@@ -99,4 +116,37 @@ public final class OptiFineInstallTask extends TaskResult<Version> {
public static class UnsupportedOptiFineInstallationException extends UnsupportedOperationException { public static class UnsupportedOptiFineInstallationException extends UnsupportedOperationException {
} }
/**
* Install OptiFine library from existing local file.
*
* @param dependencyManager game repository
* @param version version.json
* @param installer the OptiFine installer
* @return the task to install library
* @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.
* @throws VersionMismatchException if required game version of installer does not match the actual one.
*/
public static TaskResult<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
File jar = dependencyManager.getGameRepository().getVersionJar(version);
Optional<String> gameVersion = GameVersion.minecraftVersion(jar);
if (!gameVersion.isPresent()) throw new IOException();
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
ConstantPool pool = ConstantPoolScanner.parse(Files.readAllBytes(fs.getPath("Config.class")), ConstantType.UTF8);
List<String> constants = new ArrayList<>();
pool.list(Utf8Constant.class).forEach(utf8 -> constants.add(utf8.get()));
String mcVersion = getOrDefault(constants, constants.indexOf("MC_VERSION") + 1, null);
String ofEdition = getOrDefault(constants, constants.indexOf("OF_EDITION") + 1, null);
String ofRelease = getOrDefault(constants, constants.indexOf("OF_RELEASE") + 1, null);
if (mcVersion == null || ofEdition == null || ofRelease == null)
throw new IOException("Unrecognized OptiFine installer");
if (!mcVersion.equals(gameVersion.get()))
throw new VersionMismatchException(mcVersion, gameVersion.get());
return new OptiFineInstallTask(dependencyManager, version,
new OptiFineRemoteVersion(mcVersion, ofEdition + "_" + ofRelease, () -> null, false), installer);
}
}
} }

View File

@@ -91,6 +91,10 @@ public final class Lang {
} }
} }
public static <T> T getOrDefault(List<T> a, int index, T defaultValue) {
return index < 0 || index >= a.size() ? defaultValue : a.get(index);
}
/** /**
* Join two collections into one list. * Join two collections into one list.
* *