Merge pull request #358 from yushijinhun/authlib-injector
重构 authlib-injector 下载,并切换到新 API
This commit is contained in:
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.game;
|
|||||||
import org.jackhuang.hmcl.setting.Profile;
|
import org.jackhuang.hmcl.setting.Profile;
|
||||||
import org.jackhuang.hmcl.setting.Settings;
|
import org.jackhuang.hmcl.setting.Settings;
|
||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
|
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.FileUtils;
|
import org.jackhuang.hmcl.util.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
@@ -70,7 +71,7 @@ public class HMCLGameDownloadTask extends Task {
|
|||||||
NetworkUtils.toURL(profile.getDependency().getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
NetworkUtils.toURL(profile.getDependency().getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
||||||
cache,
|
cache,
|
||||||
profile.getDependency().getProxy(),
|
profile.getDependency().getProxy(),
|
||||||
version.getDownloadInfo().getSha1()
|
new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1())
|
||||||
).then(Task.of(v -> FileUtils.copyFile(cache, jar))));
|
).then(Task.of(v -> FileUtils.copyFile(cache, jar))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,19 +22,14 @@ import org.jackhuang.hmcl.auth.Account;
|
|||||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorBuildInfo;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloader;
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
|
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
|
||||||
import org.jackhuang.hmcl.util.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@@ -55,7 +50,9 @@ public final class Accounts {
|
|||||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = mapOf(
|
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = mapOf(
|
||||||
pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
|
||||||
pair(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
pair(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
|
||||||
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector, Accounts::getOrCreateAuthlibInjectorServer))
|
pair(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(
|
||||||
|
new AuthlibInjectorDownloader(Launcher.HMCL_DIRECTORY.toPath(), () -> Settings.INSTANCE.getDownloadProvider())::getArtifactInfo,
|
||||||
|
Accounts::getOrCreateAuthlibInjectorServer))
|
||||||
);
|
);
|
||||||
|
|
||||||
public static String getAccountType(Account account) {
|
public static String getAccountType(Account account) {
|
||||||
@@ -73,22 +70,6 @@ public final class Accounts {
|
|||||||
return username + ":" + character;
|
return username + ":" + character;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String downloadAuthlibInjector() throws Exception {
|
|
||||||
AuthlibInjectorBuildInfo buildInfo = AuthlibInjectorBuildInfo.requestBuildInfo();
|
|
||||||
File jar = new File(Launcher.HMCL_DIRECTORY, "authlib-injector.jar");
|
|
||||||
File local = new File(Launcher.HMCL_DIRECTORY, "authlib-injector.txt");
|
|
||||||
int buildNumber = 0;
|
|
||||||
try {
|
|
||||||
buildNumber = Integer.parseInt(FileUtils.readText(local));
|
|
||||||
} catch (IOException | NumberFormatException ignore) {
|
|
||||||
}
|
|
||||||
if (buildNumber < buildInfo.getBuildNumber()) {
|
|
||||||
new FileDownloadTask(new URL(buildInfo.getUrl()), jar).run();
|
|
||||||
FileUtils.writeText(local, String.valueOf(buildInfo.getBuildNumber()));
|
|
||||||
}
|
|
||||||
return jar.getAbsolutePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
|
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
|
||||||
return Settings.SETTINGS.authlibInjectorServers.stream()
|
return Settings.SETTINGS.authlibInjectorServers.stream()
|
||||||
.filter(server -> url.equals(server.getUrl()))
|
.filter(server -> url.equals(server.getUrl()))
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ public class AddAccountPane extends StackPane {
|
|||||||
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
|
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
|
||||||
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
|
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
|
||||||
cboServers.setItems(Settings.SETTINGS.authlibInjectorServers);
|
cboServers.setItems(Settings.SETTINGS.authlibInjectorServers);
|
||||||
|
cboServers.setPromptText(Launcher.i18n("general.prompt.empty"));
|
||||||
|
|
||||||
// workaround: otherwise the combox will be black
|
// workaround: otherwise the combox will be black
|
||||||
if (!cboServers.getItems().isEmpty())
|
if (!cboServers.getItems().isEmpty())
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import javafx.util.Duration;
|
|||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.Launcher;
|
import org.jackhuang.hmcl.Launcher;
|
||||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
|
||||||
import org.jackhuang.hmcl.util.*;
|
import org.jackhuang.hmcl.util.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.jfoenix.concurrency.JFXUtilities;
|
|||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import org.jackhuang.hmcl.Launcher;
|
import org.jackhuang.hmcl.Launcher;
|
||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
|
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||||
import org.jackhuang.hmcl.ui.Controllers;
|
import org.jackhuang.hmcl.ui.Controllers;
|
||||||
@@ -182,7 +183,7 @@ public class AppDataUpgrader extends IUpgrader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Task> getDependents() {
|
public Collection<Task> getDependents() {
|
||||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, hash));
|
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, new IntegrityCheck("SHA-1", hash)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -232,7 +233,7 @@ public class AppDataUpgrader extends IUpgrader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Task> getDependents() {
|
public Collection<Task> getDependents() {
|
||||||
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, hash));
|
return Collections.singleton(new FileDownloadTask(downloadLink, tempFile, Proxy.NO_PROXY, new IntegrityCheck("SHA-1", hash)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ folder.resourcepacks=Resourcepacks
|
|||||||
folder.saves=Saves
|
folder.saves=Saves
|
||||||
folder.screenshots=Screenshots
|
folder.screenshots=Screenshots
|
||||||
|
|
||||||
|
general.prompt.empty=(None)
|
||||||
|
|
||||||
input.email=The username must be an e-mail.
|
input.email=The username must be an e-mail.
|
||||||
input.number=Must be a number.
|
input.number=Must be a number.
|
||||||
input.not_empty=Required field
|
input.not_empty=Required field
|
||||||
|
|||||||
@@ -99,6 +99,8 @@ folder.resourcepacks=资源包文件夹
|
|||||||
folder.saves=存档文件夹
|
folder.saves=存档文件夹
|
||||||
folder.screenshots=截图文件夹
|
folder.screenshots=截图文件夹
|
||||||
|
|
||||||
|
general.prompt.empty=(无)
|
||||||
|
|
||||||
input.email=用户名必须是邮箱
|
input.email=用户名必须是邮箱
|
||||||
input.number=必须是数字
|
input.number=必须是数字
|
||||||
input.not_empty=必填项
|
input.not_empty=必填项
|
||||||
|
|||||||
@@ -31,19 +31,23 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
|||||||
|
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class AuthlibInjectorAccount extends YggdrasilAccount {
|
public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||||
private final AuthlibInjectorServer server;
|
private AuthlibInjectorServer server;
|
||||||
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
private ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader;
|
||||||
|
|
||||||
protected AuthlibInjectorAccount(YggdrasilService service, AuthlibInjectorServer server, ExceptionalSupplier<String, ?> injectorJarPath, String username, UUID characterUUID, YggdrasilSession session) {
|
protected AuthlibInjectorAccount(YggdrasilService service, AuthlibInjectorServer server, ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader, String username, UUID characterUUID, YggdrasilSession session) {
|
||||||
super(service, username, characterUUID, session);
|
super(service, username, characterUUID, session);
|
||||||
|
|
||||||
this.injectorJarPath = injectorJarPath;
|
this.authlibInjectorDownloader = authlibInjectorDownloader;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,25 +61,42 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
|||||||
return inject(() -> super.logInWithPassword(password, selector));
|
return inject(() -> super.logInWithPassword(password, selector));
|
||||||
}
|
}
|
||||||
|
|
||||||
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> supplier) throws AuthenticationException {
|
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> loginAction) throws AuthenticationException {
|
||||||
// Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time.
|
// Pre-fetch metadata
|
||||||
GetTask getTask = new GetTask(NetworkUtils.toURL(server.getUrl()));
|
GetTask metadataFetchTask = new GetTask(NetworkUtils.toURL(server.getUrl()));
|
||||||
AtomicBoolean flag = new AtomicBoolean(true);
|
Thread metadataFetchThread = Lang.thread(() -> {
|
||||||
Thread thread = Lang.thread(() -> flag.set(getTask.test()));
|
try {
|
||||||
|
metadataFetchTask.run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to pre-fetch Yggdrasil metadata", e);
|
||||||
|
}
|
||||||
|
}, "Yggdrasil metadata fetch thread");
|
||||||
|
|
||||||
AuthInfo info = supplier.get();
|
// Update authlib-injector
|
||||||
|
AuthlibInjectorArtifactInfo artifact;
|
||||||
try {
|
try {
|
||||||
thread.join();
|
artifact = authlibInjectorDownloader.get();
|
||||||
|
} catch (IOException e) {
|
||||||
Arguments arguments = new Arguments().addJVMArguments("-javaagent:" + injectorJarPath.get() + "=" + server.getUrl());
|
throw new AuthenticationException("Failed to download authlib-injector", e);
|
||||||
|
|
||||||
if (flag.get())
|
|
||||||
arguments = arguments.addJVMArguments("-Dorg.to2mbn.authlibinjector.config.prefetched=" + new String(Base64.getEncoder().encode(getTask.getResult().getBytes()), UTF_8));
|
|
||||||
|
|
||||||
return info.withArguments(arguments);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new AuthenticationException("Unable to get authlib injector jar path", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Perform authentication
|
||||||
|
AuthInfo info = loginAction.get();
|
||||||
|
Arguments arguments = new Arguments().addJVMArguments("-javaagent:" + artifact.getLocation().toString() + "=" + server.getUrl());
|
||||||
|
|
||||||
|
// Wait for metadata to be fetched
|
||||||
|
try {
|
||||||
|
metadataFetchThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
Optional<String> metadata = Optional.ofNullable(metadataFetchTask.getResult());
|
||||||
|
if (metadata.isPresent()) {
|
||||||
|
arguments = arguments.addJVMArguments(
|
||||||
|
"-Dorg.to2mbn.authlibinjector.config.prefetched=" + Base64.getEncoder().encodeToString(metadata.get().getBytes(UTF_8)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return info.withArguments(arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||||
@@ -7,6 +24,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
|||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -15,14 +33,14 @@ import java.util.function.Function;
|
|||||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||||
|
|
||||||
public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjectorAccount> {
|
public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjectorAccount> {
|
||||||
private final ExceptionalSupplier<String, ?> injectorJarPathSupplier;
|
private ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader;
|
||||||
private Function<String, AuthlibInjectorServer> serverLookup;
|
private Function<String, AuthlibInjectorServer> serverLookup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param serverLookup a function that looks up {@link AuthlibInjectorServer} by url
|
* @param serverLookup a function that looks up {@link AuthlibInjectorServer} by url
|
||||||
*/
|
*/
|
||||||
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> injectorJarPathSupplier, Function<String, AuthlibInjectorServer> serverLookup) {
|
public AuthlibInjectorAccountFactory(ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader, Function<String, AuthlibInjectorServer> serverLookup) {
|
||||||
this.injectorJarPathSupplier = injectorJarPathSupplier;
|
this.authlibInjectorDownloader = authlibInjectorDownloader;
|
||||||
this.serverLookup = serverLookup;
|
this.serverLookup = serverLookup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +54,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
|||||||
AuthlibInjectorServer server = (AuthlibInjectorServer) additionalData;
|
AuthlibInjectorServer server = (AuthlibInjectorServer) additionalData;
|
||||||
|
|
||||||
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
AuthlibInjectorAccount account = new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||||
server, injectorJarPathSupplier, username, null, null);
|
server, authlibInjectorDownloader, username, null, null);
|
||||||
account.logInWithPassword(password, selector);
|
account.logInWithPassword(password, selector);
|
||||||
return account;
|
return account;
|
||||||
}
|
}
|
||||||
@@ -56,6 +74,6 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
|||||||
AuthlibInjectorServer server = serverLookup.apply(apiRoot);
|
AuthlibInjectorServer server = serverLookup.apply(apiRoot);
|
||||||
|
|
||||||
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
return new AuthlibInjectorAccount(new YggdrasilService(new AuthlibInjectorProvider(server.getUrl()), proxy),
|
||||||
server, injectorJarPathSupplier, username, session.getSelectedProfile().getId(), session);
|
server, authlibInjectorDownloader, username, session.getSelectedProfile().getId(), session);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class AuthlibInjectorArtifactInfo {
|
||||||
|
|
||||||
|
private int buildNumber;
|
||||||
|
private String version;
|
||||||
|
private Path location;
|
||||||
|
|
||||||
|
public AuthlibInjectorArtifactInfo(int buildNumber, String version, Path location) {
|
||||||
|
this.buildNumber = buildNumber;
|
||||||
|
this.version = version;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBuildNumber() {
|
||||||
|
return buildNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getVersion() {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getLocation() {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "authlib-injector [buildNumber=" + buildNumber + ", version=" + version + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* Hello Minecraft! Launcher.
|
|
||||||
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
|
||||||
*
|
|
||||||
* 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 {http://www.gnu.org/licenses/}.
|
|
||||||
*/
|
|
||||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
|
||||||
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import org.jackhuang.hmcl.util.Immutable;
|
|
||||||
import org.jackhuang.hmcl.util.JsonUtils;
|
|
||||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
public final class AuthlibInjectorBuildInfo {
|
|
||||||
|
|
||||||
private final int buildNumber;
|
|
||||||
private final String url;
|
|
||||||
|
|
||||||
public AuthlibInjectorBuildInfo() {
|
|
||||||
this(0, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthlibInjectorBuildInfo(int buildNumber, String url) {
|
|
||||||
this.buildNumber = buildNumber;
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getBuildNumber() {
|
|
||||||
return buildNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUrl() {
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AuthlibInjectorBuildInfo requestBuildInfo() throws IOException, JsonParseException {
|
|
||||||
return requestBuildInfo(UPDATE_URL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AuthlibInjectorBuildInfo requestBuildInfo(String updateUrl) throws IOException, JsonParseException {
|
|
||||||
return JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(updateUrl)), AuthlibInjectorBuildInfo.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final String UPDATE_URL = "https://authlib-injector.to2mbn.org/api/buildInfo";
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||||
|
|
||||||
|
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||||
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
|
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||||
|
import org.jackhuang.hmcl.util.JsonUtils;
|
||||||
|
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||||
|
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class AuthlibInjectorDownloader {
|
||||||
|
|
||||||
|
private static final String LATEST_BUILD_URL = "https://authlib-injector.yushi.moe/artifact/latest.json";
|
||||||
|
|
||||||
|
private Path artifactLocation;
|
||||||
|
private Supplier<DownloadProvider> downloadProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param artifactsDirectory where to save authlib-injector artifacts
|
||||||
|
*/
|
||||||
|
public AuthlibInjectorDownloader(Path artifactsDirectory, Supplier<DownloadProvider> downloadProvider) {
|
||||||
|
this.artifactLocation = artifactsDirectory.resolve("authlib-injector.jar");
|
||||||
|
this.downloadProvider = downloadProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthlibInjectorArtifactInfo getArtifactInfo() throws IOException {
|
||||||
|
synchronized (artifactLocation) {
|
||||||
|
Optional<AuthlibInjectorArtifactInfo> local = getLocalArtifact();
|
||||||
|
|
||||||
|
try {
|
||||||
|
update(local);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Failed to download authlib-injector", e);
|
||||||
|
if (!local.isPresent()) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
LOG.warning("Fallback to use cached artifact: " + local.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
return getLocalArtifact().orElseThrow(() -> new IOException("The updated authlib-inejector cannot be recognized"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update(Optional<AuthlibInjectorArtifactInfo> local) throws IOException {
|
||||||
|
AuthlibInjectorVersionInfo latest = getLatestArtifactInfo();
|
||||||
|
|
||||||
|
if (local.isPresent() && local.get().getBuildNumber() >= latest.buildNumber) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new FileDownloadTask(new URL(downloadProvider.get().injectURL(latest.downloadUrl)), artifactLocation.toFile(), Proxy.NO_PROXY,
|
||||||
|
Optional.ofNullable(latest.checksums.get("sha256"))
|
||||||
|
.map(checksum -> new IntegrityCheck("SHA-256", checksum))
|
||||||
|
.orElse(null))
|
||||||
|
.run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IOException("Failed to download authlib-injector", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.info("Updated authlib-injector to " + latest.version);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthlibInjectorVersionInfo getLatestArtifactInfo() throws IOException {
|
||||||
|
try {
|
||||||
|
return JsonUtils.fromNonNullJson(
|
||||||
|
NetworkUtils.doGet(
|
||||||
|
new URL(downloadProvider.get().injectURL(LATEST_BUILD_URL))),
|
||||||
|
AuthlibInjectorVersionInfo.class);
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
throw new IOException("Malformed response", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Optional<AuthlibInjectorArtifactInfo> getLocalArtifact() {
|
||||||
|
if (!Files.isRegularFile(artifactLocation)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Optional.of(readArtifactInfo(artifactLocation));
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.log(Level.WARNING, "Bad authlib-injector artifact", e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AuthlibInjectorArtifactInfo readArtifactInfo(Path location) throws IOException {
|
||||||
|
try (JarFile jarFile = new JarFile(location.toFile())) {
|
||||||
|
Attributes attributes = jarFile.getManifest().getMainAttributes();
|
||||||
|
|
||||||
|
String title = Optional.ofNullable(attributes.getValue("Implementation-Title"))
|
||||||
|
.orElseThrow(() -> new IOException("Missing Implementation-Title"));
|
||||||
|
if (!"authlib-injector".equals(title)) {
|
||||||
|
throw new IOException("Bad Implementation-Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
String version = Optional.ofNullable(attributes.getValue("Implementation-Version"))
|
||||||
|
.orElseThrow(() -> new IOException("Missing Implementation-Version"));
|
||||||
|
|
||||||
|
int buildNumber;
|
||||||
|
try {
|
||||||
|
buildNumber = Optional.ofNullable(attributes.getValue("Build-Number"))
|
||||||
|
.map(Integer::parseInt)
|
||||||
|
.orElseThrow(() -> new IOException("Missing Build-Number"));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IOException("Bad Build-Number", e);
|
||||||
|
}
|
||||||
|
return new AuthlibInjectorArtifactInfo(buildNumber, version, location.toAbsolutePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AuthlibInjectorVersionInfo {
|
||||||
|
@SerializedName("build_number")
|
||||||
|
public int buildNumber;
|
||||||
|
|
||||||
|
@SerializedName("version")
|
||||||
|
public String version;
|
||||||
|
|
||||||
|
@SerializedName("download_url")
|
||||||
|
public String downloadUrl;
|
||||||
|
|
||||||
|
@SerializedName("checksums")
|
||||||
|
public Map<String, String> checksums;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||||
|
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilProvider;
|
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilProvider;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
|||||||
import org.jackhuang.hmcl.game.AssetObject;
|
import org.jackhuang.hmcl.game.AssetObject;
|
||||||
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.FileDownloadTask.IntegrityCheck;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.FileUtils;
|
import org.jackhuang.hmcl.util.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
@@ -106,7 +107,7 @@ public final class GameAssetDownloadTask extends Task {
|
|||||||
flag = !file.exists();
|
flag = !file.exists();
|
||||||
}
|
}
|
||||||
if (flag) {
|
if (flag) {
|
||||||
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, dependencyManager.getProxy(), assetObject.getHash());
|
FileDownloadTask task = new FileDownloadTask(NetworkUtils.toURL(url), file, dependencyManager.getProxy(), new IntegrityCheck("SHA-1", assetObject.getHash()));
|
||||||
task.setName(assetObject.getHash());
|
task.setName(assetObject.getHash());
|
||||||
dependencies.add(task);
|
dependencies.add(task);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package org.jackhuang.hmcl.download.game;
|
|||||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||||
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.FileDownloadTask.IntegrityCheck;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.NetworkUtils;
|
import org.jackhuang.hmcl.util.NetworkUtils;
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ public final class GameDownloadTask extends Task {
|
|||||||
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
|
||||||
jar,
|
jar,
|
||||||
dependencyManager.getProxy(),
|
dependencyManager.getProxy(),
|
||||||
version.getDownloadInfo().getSha1()
|
new IntegrityCheck("SHA-1", version.getDownloadInfo().getSha1())
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
|||||||
import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||||
import org.jackhuang.hmcl.game.Library;
|
import org.jackhuang.hmcl.game.Library;
|
||||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
|
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||||
import org.jackhuang.hmcl.task.Task;
|
import org.jackhuang.hmcl.task.Task;
|
||||||
import org.jackhuang.hmcl.util.FileUtils;
|
import org.jackhuang.hmcl.util.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.IOUtils;
|
import org.jackhuang.hmcl.util.IOUtils;
|
||||||
@@ -48,7 +49,7 @@ public final class LibraryDownloadTask extends Task {
|
|||||||
setSignificance(TaskSignificance.MODERATE);
|
setSignificance(TaskSignificance.MODERATE);
|
||||||
|
|
||||||
task = new FileDownloadTask(NetworkUtils.toURL(url),
|
task = new FileDownloadTask(NetworkUtils.toURL(url),
|
||||||
file, dependencyManager.getProxy(), library.getDownload().getSha1());
|
file, dependencyManager.getProxy(), new IntegrityCheck("SHA-1", library.getDownload().getSha1()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.task;
|
|||||||
|
|
||||||
import org.jackhuang.hmcl.event.EventManager;
|
import org.jackhuang.hmcl.event.EventManager;
|
||||||
import org.jackhuang.hmcl.event.FailedEvent;
|
import org.jackhuang.hmcl.event.FailedEvent;
|
||||||
|
import org.jackhuang.hmcl.util.ChecksumMismatchException;
|
||||||
import org.jackhuang.hmcl.util.FileUtils;
|
import org.jackhuang.hmcl.util.FileUtils;
|
||||||
import org.jackhuang.hmcl.util.IOUtils;
|
import org.jackhuang.hmcl.util.IOUtils;
|
||||||
import org.jackhuang.hmcl.util.Logging;
|
import org.jackhuang.hmcl.util.Logging;
|
||||||
@@ -35,6 +36,7 @@ import java.net.URL;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
|
import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,9 +46,38 @@ import static org.jackhuang.hmcl.util.DigestUtils.getDigest;
|
|||||||
*/
|
*/
|
||||||
public class FileDownloadTask extends Task {
|
public class FileDownloadTask extends Task {
|
||||||
|
|
||||||
|
public static class IntegrityCheck {
|
||||||
|
private String algorithm;
|
||||||
|
private String checksum;
|
||||||
|
|
||||||
|
public IntegrityCheck(String algorithm, String checksum) {
|
||||||
|
this.algorithm = requireNonNull(algorithm);
|
||||||
|
this.checksum = requireNonNull(checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getChecksum() {
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageDigest createDigest() {
|
||||||
|
return getDigest(algorithm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void performCheck(MessageDigest digest) throws ChecksumMismatchException {
|
||||||
|
String actualChecksum = String.format("%1$040x", new BigInteger(1, digest.digest()));
|
||||||
|
if (!checksum.equalsIgnoreCase(actualChecksum)) {
|
||||||
|
throw new ChecksumMismatchException(algorithm, checksum, actualChecksum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final URL url;
|
private final URL url;
|
||||||
private final File file;
|
private final File file;
|
||||||
private final String hash;
|
private final IntegrityCheck integrityCheck;
|
||||||
private final int retry;
|
private final int retry;
|
||||||
private final Proxy proxy;
|
private final Proxy proxy;
|
||||||
private final EventManager<FailedEvent<URL>> onFailed = new EventManager<>();
|
private final EventManager<FailedEvent<URL>> onFailed = new EventManager<>();
|
||||||
@@ -74,23 +105,23 @@ public class FileDownloadTask extends Task {
|
|||||||
* @param url the URL of remote file.
|
* @param url the URL of remote file.
|
||||||
* @param file the location that download to.
|
* @param file the location that download to.
|
||||||
* @param proxy the proxy.
|
* @param proxy the proxy.
|
||||||
* @param hash the SHA-1 hash code of remote file, null if the hash is unknown or it is no need to check the hash code.
|
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||||
*/
|
*/
|
||||||
public FileDownloadTask(URL url, File file, Proxy proxy, String hash) {
|
public FileDownloadTask(URL url, File file, Proxy proxy, IntegrityCheck integrityCheck) {
|
||||||
this(url, file, proxy, hash, 5);
|
this(url, file, proxy, integrityCheck, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param url the URL of remote file.
|
* @param url the URL of remote file.
|
||||||
* @param file the location that download to.
|
* @param file the location that download to.
|
||||||
* @param hash the SHA-1 hash code of remote file, null if the hash is unknown or it is no need to check the hash code.
|
* @param integrityCheck the integrity check to perform, null if no integrity check is to be performed
|
||||||
* @param retry the times for retrying if downloading fails.
|
* @param retry the times for retrying if downloading fails.
|
||||||
* @param proxy the proxy.
|
* @param proxy the proxy.
|
||||||
*/
|
*/
|
||||||
public FileDownloadTask(URL url, File file, Proxy proxy, String hash, int retry) {
|
public FileDownloadTask(URL url, File file, Proxy proxy, IntegrityCheck integrityCheck, int retry) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.hash = hash;
|
this.integrityCheck = integrityCheck;
|
||||||
this.retry = retry;
|
this.retry = retry;
|
||||||
this.proxy = proxy;
|
this.proxy = proxy;
|
||||||
|
|
||||||
@@ -159,7 +190,7 @@ public class FileDownloadTask extends Task {
|
|||||||
temp = FileUtils.createTempFile();
|
temp = FileUtils.createTempFile();
|
||||||
rFile = new RandomAccessFile(temp, "rw");
|
rFile = new RandomAccessFile(temp, "rw");
|
||||||
|
|
||||||
MessageDigest digest = getDigest("SHA-1");
|
MessageDigest digest = integrityCheck == null ? null : integrityCheck.createDigest();
|
||||||
|
|
||||||
stream = con.getInputStream();
|
stream = con.getInputStream();
|
||||||
int lastDownloaded = 0, downloaded = 0;
|
int lastDownloaded = 0, downloaded = 0;
|
||||||
@@ -175,8 +206,9 @@ public class FileDownloadTask extends Task {
|
|||||||
if (read == -1)
|
if (read == -1)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (hash != null)
|
if (digest != null) {
|
||||||
digest.update(buffer, 0, read);
|
digest.update(buffer, 0, read);
|
||||||
|
}
|
||||||
|
|
||||||
// Write buffer to file.
|
// Write buffer to file.
|
||||||
rFile.write(buffer, 0, read);
|
rFile.write(buffer, 0, read);
|
||||||
@@ -214,11 +246,9 @@ public class FileDownloadTask extends Task {
|
|||||||
if (downloaded != contentLength)
|
if (downloaded != contentLength)
|
||||||
throw new IllegalStateException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
throw new IllegalStateException("Unexpected file size: " + downloaded + ", expected: " + contentLength);
|
||||||
|
|
||||||
// Check hash code
|
// Integrity check
|
||||||
if (hash != null) {
|
if (integrityCheck != null) {
|
||||||
String hashCode = String.format("%1$040x", new BigInteger(1, digest.digest()));
|
integrityCheck.performCheck(digest);
|
||||||
if (!hash.equalsIgnoreCase(hashCode))
|
|
||||||
throw new IllegalStateException("Unexpected hash code: " + hashCode + ", expected: " + hash);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Hello Minecraft! Launcher.
|
||||||
|
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
|
||||||
|
*
|
||||||
|
* 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 {http://www.gnu.org/licenses/}.
|
||||||
|
*/
|
||||||
|
package org.jackhuang.hmcl.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ChecksumMismatchException extends IOException {
|
||||||
|
|
||||||
|
private String algorithm;
|
||||||
|
private String expectedChecksum;
|
||||||
|
private String actualChecksum;
|
||||||
|
|
||||||
|
public ChecksumMismatchException(String algorithm, String expectedChecksum, String actualChecksum) {
|
||||||
|
super("Incorrect checksum (" + algorithm + "), expected: " + expectedChecksum + ", actual: " + actualChecksum);
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
this.expectedChecksum = expectedChecksum;
|
||||||
|
this.actualChecksum = actualChecksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlgorithm() {
|
||||||
|
return algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExpectedChecksum() {
|
||||||
|
return expectedChecksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getActualChecksum() {
|
||||||
|
return actualChecksum;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user