mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2025-09-13 13:56:55 -04:00
Refactor authlib-injector downloading, switch to new API
This commit is contained in:
parent
6fae131287
commit
2a232f70db
@ -22,19 +22,14 @@ import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
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.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
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.net.URL;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@ -55,7 +50,9 @@ public final class Accounts {
|
||||
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = mapOf(
|
||||
pair(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.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) {
|
||||
@ -73,22 +70,6 @@ public final class Accounts {
|
||||
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) {
|
||||
return Settings.SETTINGS.authlibInjectorServers.stream()
|
||||
.filter(server -> url.equals(server.getUrl()))
|
||||
|
@ -31,19 +31,23 @@ import org.jackhuang.hmcl.util.NetworkUtils;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
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 org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
private final AuthlibInjectorServer server;
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPath;
|
||||
private AuthlibInjectorServer server;
|
||||
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);
|
||||
|
||||
this.injectorJarPath = injectorJarPath;
|
||||
this.authlibInjectorDownloader = authlibInjectorDownloader;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@ -57,25 +61,42 @@ public class AuthlibInjectorAccount extends YggdrasilAccount {
|
||||
return inject(() -> super.logInWithPassword(password, selector));
|
||||
}
|
||||
|
||||
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> supplier) throws AuthenticationException {
|
||||
// Authlib Injector recommends launchers to pre-fetch the server basic information before launched the game to save time.
|
||||
GetTask getTask = new GetTask(NetworkUtils.toURL(server.getUrl()));
|
||||
AtomicBoolean flag = new AtomicBoolean(true);
|
||||
Thread thread = Lang.thread(() -> flag.set(getTask.test()));
|
||||
private AuthInfo inject(ExceptionalSupplier<AuthInfo, AuthenticationException> loginAction) throws AuthenticationException {
|
||||
// Pre-fetch metadata
|
||||
GetTask metadataFetchTask = new GetTask(NetworkUtils.toURL(server.getUrl()));
|
||||
Thread metadataFetchThread = Lang.thread(() -> {
|
||||
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 {
|
||||
thread.join();
|
||||
|
||||
Arguments arguments = new Arguments().addJVMArguments("-javaagent:" + injectorJarPath.get() + "=" + server.getUrl());
|
||||
|
||||
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);
|
||||
artifact = authlibInjectorDownloader.get();
|
||||
} catch (IOException e) {
|
||||
throw new AuthenticationException("Failed to download authlib-injector", 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
|
||||
|
@ -7,6 +7,7 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilSession;
|
||||
import org.jackhuang.hmcl.util.ExceptionalSupplier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@ -15,14 +16,14 @@ import java.util.function.Function;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjectorAccount> {
|
||||
private final ExceptionalSupplier<String, ?> injectorJarPathSupplier;
|
||||
private ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader;
|
||||
private Function<String, AuthlibInjectorServer> serverLookup;
|
||||
|
||||
/**
|
||||
* @param serverLookup a function that looks up {@link AuthlibInjectorServer} by url
|
||||
*/
|
||||
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> injectorJarPathSupplier, Function<String, AuthlibInjectorServer> serverLookup) {
|
||||
this.injectorJarPathSupplier = injectorJarPathSupplier;
|
||||
public AuthlibInjectorAccountFactory(ExceptionalSupplier<AuthlibInjectorArtifactInfo, ? extends IOException> authlibInjectorDownloader, Function<String, AuthlibInjectorServer> serverLookup) {
|
||||
this.authlibInjectorDownloader = authlibInjectorDownloader;
|
||||
this.serverLookup = serverLookup;
|
||||
}
|
||||
|
||||
@ -36,7 +37,7 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
AuthlibInjectorServer server = (AuthlibInjectorServer) additionalData;
|
||||
|
||||
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);
|
||||
return account;
|
||||
}
|
||||
@ -56,6 +57,6 @@ public class AuthlibInjectorAccountFactory extends AccountFactory<AuthlibInjecto
|
||||
AuthlibInjectorServer server = serverLookup.apply(apiRoot);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user