diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/Main.java b/HMCL/src/main/java/org/jackhuang/hmcl/Main.java index 075b10f33..ab0a06685 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/Main.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/Main.java @@ -121,11 +121,11 @@ public final class Main extends Application { } public static final File MINECRAFT_DIRECTORY = getWorkingDirectory("minecraft"); + public static final File HMCL_DIRECTORY = getWorkingDirectory("hmcl"); public static final String VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@"; public static final String NAME = "HMCL"; public static final String TITLE = NAME + " " + VERSION; - public static final File APPDATA = getWorkingDirectory("hmcl"); public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle(); public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION)); public static final CrashReporter CRASH_REPORTER = new CrashReporter(); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackManager.java b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackManager.java index 07640f1a2..cc4d38111 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackManager.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/game/HMCLModpackManager.java @@ -21,6 +21,7 @@ import com.google.gson.JsonParseException; import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.util.CompressingUtils; import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.StringUtils; import java.io.File; @@ -81,13 +82,9 @@ public final class HMCLModpackManager { */ public static Modpack readHMCLModpackManifest(File file) throws IOException, JsonParseException { String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json"); - Modpack manifest = Constants.GSON.fromJson(manifestJson, Modpack.class); - if (manifest == null) - throw new JsonParseException("`modpack.json` not found. " + file + " is not a valid HMCL modpack."); + Modpack manifest = Lang.requireJsonNonNull(Constants.GSON.fromJson(manifestJson, Modpack.class)); String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json"); - Version game = Constants.GSON.fromJson(gameJson, Version.class); - if (game == null) - throw new JsonParseException("`minecraft/pack.json` not found. " + file + " iot a valid HMCL modpack."); + Version game = Lang.requireJsonNonNull(Constants.GSON.fromJson(gameJson, Version.class)); if (game.getJar() == null) if (StringUtils.isBlank(manifest.getVersion())) throw new JsonParseException("Cannot recognize the game version of modpack " + file + "."); diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java index 4b342137e..2766141fa 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Accounts.java @@ -17,15 +17,19 @@ */ package org.jackhuang.hmcl.setting; +import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.OfflineAccount; import org.jackhuang.hmcl.auth.OfflineAccountFactory; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; -import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory; +import org.jackhuang.hmcl.auth.yggdrasil.*; +import org.jackhuang.hmcl.task.FileDownloadTask; +import org.jackhuang.hmcl.util.FileUtils; import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Pair; +import java.io.File; +import java.net.URL; import java.util.Map; import java.util.Optional; @@ -37,14 +41,17 @@ public final class Accounts { public static final String OFFLINE_ACCOUNT_KEY = "offline"; public static final String YGGDRASIL_ACCOUNT_KEY = "yggdrasil"; + public static final String AUTHLIB_INJECTOR_ACCOUNT_KEY = "authlibInjector"; public static final Map> ACCOUNT_FACTORY = Lang.mapOf( new Pair<>(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE), - new Pair<>(YGGDRASIL_ACCOUNT_KEY, YggdrasilAccountFactory.INSTANCE) + new Pair<>(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory()), + new Pair<>(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector)) ); public static String getAccountType(Account account) { if (account instanceof OfflineAccount) return OFFLINE_ACCOUNT_KEY; + else if (account instanceof AuthlibInjectorAccount) return AUTHLIB_INJECTOR_ACCOUNT_KEY; else if (account instanceof YggdrasilAccount) return YGGDRASIL_ACCOUNT_KEY; else return YGGDRASIL_ACCOUNT_KEY; } @@ -75,4 +82,16 @@ public final class Accounts { static String getAccountId(String username, String character) { return username + ":" + character; } + + private static String downloadAuthlibInjector() throws Exception { + AuthlibInjectorBuildInfo buildInfo = AuthlibInjectorBuildInfo.requestBuildInfo(); + File jar = new File(Main.HMCL_DIRECTORY, "authlib-injector.jar"); + File local = new File(Main.HMCL_DIRECTORY, "authlib-injector.txt"); + int buildNumber = Integer.parseInt(FileUtils.readText(local)); + if (buildNumber < buildInfo.getBuildNumber()) { + new FileDownloadTask(new URL(buildInfo.getUrl()), jar).run(); + FileUtils.writeText(local, String.valueOf(buildInfo.getBuildNumber())); + } + return jar.getAbsolutePath(); + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java index 95fb31b97..cc5110ea2 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Config.java @@ -19,6 +19,7 @@ package org.jackhuang.hmcl.setting; import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.Main; +import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorBuildInfo; import org.jackhuang.hmcl.util.JavaVersion; import java.util.LinkedList; @@ -91,6 +92,9 @@ public final class Config { @SerializedName("logLines") private int logLines = 100; + @SerializedName("authlibInjectorServerURL") + private String authlibInjectorServerURL = AuthlibInjectorBuildInfo.UPDATE_URL; + public String getSelectedProfile() { return selectedProfile; } @@ -278,4 +282,18 @@ public final class Config { public void setLogLines(int logLines) { this.logLines = logLines; } + + public String getAuthlibInjectorServerURL() { + return authlibInjectorServerURL; + } + + /** + * Will not invoke Settings.INSTANCE.save() + * @param authlibInjectorServerURL new server url. + */ + public void setAuthlibInjectorServerURL(String authlibInjectorServerURL) { + this.authlibInjectorServerURL = authlibInjectorServerURL; + // Do not invoke Settings.INSTANCE.save() + // Because we want users set it theirself. + } } diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java index 43adbe380..a1f5a60d7 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/setting/Settings.java @@ -28,6 +28,7 @@ import javafx.scene.text.Font; import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.AccountFactory; +import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorBuildInfo; import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider; import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.MojangDownloadProvider; @@ -39,10 +40,7 @@ import org.jackhuang.hmcl.util.*; import java.io.File; import java.io.IOException; -import java.net.Authenticator; -import java.net.InetSocketAddress; -import java.net.PasswordAuthentication; -import java.net.Proxy; +import java.net.*; import java.util.*; import java.util.logging.Level; import java.util.stream.Collectors; @@ -106,6 +104,13 @@ public class Settings { }); loadProxy(); + + try { + new URL(SETTINGS.getAuthlibInjectorServerURL()); + } catch (MalformedURLException ex) { + Logging.LOG.log(Level.SEVERE, "Authlib injector server URL is malformed, using official update url.", ex); + SETTINGS.setAuthlibInjectorServerURL(AuthlibInjectorBuildInfo.UPDATE_URL); + } } private Config initSettings() { @@ -275,6 +280,10 @@ public class Settings { SETTINGS.setLogLines(logLines); } + public String getAuthlibInjectorServerURL() { + return SETTINGS.getAuthlibInjectorServerURL(); + } + public DownloadProvider getDownloadProvider() { switch (SETTINGS.getDownloadType()) { case 0: diff --git a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/AppDataUpgrader.java b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/AppDataUpgrader.java index 27dcdd62d..8d36d8e25 100644 --- a/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/AppDataUpgrader.java +++ b/HMCL/src/main/java/org/jackhuang/hmcl/upgrade/AppDataUpgrader.java @@ -154,7 +154,7 @@ public class AppDataUpgrader extends IUpgrader { public static class AppDataUpgraderPackGzTask extends Task { - public static final File BASE_FOLDER = Main.getWorkingDirectory("hmcl"); + public static final File BASE_FOLDER = Main.HMCL_DIRECTORY; public static final File HMCL_VER_FILE = new File(BASE_FOLDER, "hmclver.json"); public static File getSelf(String ver) { diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java index dbb6b3acd..502dfb01e 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AccountFactory.java @@ -28,10 +28,14 @@ import java.util.Map; public abstract class AccountFactory { public final T fromUsername(String username) { - return fromUsername(username, ""); + return fromUsername(username, "", null); } - public abstract T fromUsername(String username, String password); + public final T fromUsername(String username, String password) { + return fromUsername(username, password, null); + } + + public abstract T fromUsername(String username, String password, Object additionalData); protected abstract T fromStorageImpl(Map storage); diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java index 4e0488df0..4eed07e0b 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/AuthInfo.java @@ -18,12 +18,15 @@ package org.jackhuang.hmcl.auth; import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.UUIDTypeAdapter; /** * * @author huangyuhui */ +@Immutable public final class AuthInfo { private final String username; @@ -32,6 +35,7 @@ public final class AuthInfo { private final UserType userType; private final String userProperties; private final String userPropertyMap; + private final Arguments arguments; public AuthInfo(String username, String userId, String authToken) { this(username, userId, authToken, UserType.LEGACY); @@ -46,12 +50,17 @@ public final class AuthInfo { } public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap) { + this(username, userId, authToken, userType, userProperties, userPropertyMap, null); + } + + public AuthInfo(String username, String userId, String authToken, UserType userType, String userProperties, String userPropertyMap, Arguments arguments) { this.username = username; this.userId = userId; this.authToken = authToken; this.userType = userType; this.userProperties = userProperties; this.userPropertyMap = userPropertyMap; + this.arguments = arguments; } public AuthInfo(GameProfile profile, String authToken, UserType userType, String userProperties) { @@ -93,4 +102,12 @@ public final class AuthInfo { public String getUserPropertyMap() { return userPropertyMap; } + + public Arguments getArguments() { + return arguments; + } + + public AuthInfo setArguments(Arguments arguments) { + return new AuthInfo(username, userId, authToken, userType, userProperties, userPropertyMap, arguments); + } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccount.java new file mode 100644 index 000000000..33a3a0bc2 --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccount.java @@ -0,0 +1,68 @@ +package org.jackhuang.hmcl.auth.yggdrasil; + +import org.jackhuang.hmcl.auth.AuthInfo; +import org.jackhuang.hmcl.auth.AuthenticationException; +import org.jackhuang.hmcl.auth.MultiCharacterSelector; +import org.jackhuang.hmcl.game.Arguments; +import org.jackhuang.hmcl.task.GetTask; +import org.jackhuang.hmcl.util.ExceptionalSupplier; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.NetworkUtils; + +import java.net.Proxy; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +public class AuthlibInjectorAccount extends YggdrasilAccount { + private final String serverBaseURL; + private final ExceptionalSupplier injectorJarPath; + + public AuthlibInjectorAccount(ExceptionalSupplier injectorJarPath, String serverBaseURL, String username) { + super(serverBaseURL + "authserver/", serverBaseURL + "sessionserver/", username); + + this.injectorJarPath = injectorJarPath; + this.serverBaseURL = serverBaseURL; + } + + @Override + public AuthInfo logIn(MultiCharacterSelector selector, Proxy proxy) 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(serverBaseURL)); + AtomicBoolean flag = new AtomicBoolean(true); + Thread thread = Lang.thread(() -> { + try { + getTask.run(); + } catch (Exception ignore) { + flag.set(false); + } + }); + + AuthInfo info = super.logIn(selector, proxy); + try { + thread.join(); + + String arg = "-javaagent:" + injectorJarPath.get() + "=" + serverBaseURL; + Arguments arguments = Arguments.addJVMArguments(null, arg); + + if (flag.get()) + arguments = Arguments.addJVMArguments(arguments, "-Dorg.to2mbn.authlibinjector.config.prefetched=" + getTask.getResult()); + + return info.setArguments(arguments); + } catch (Exception e) { + throw new AuthenticationException("Unable to get authlib injector jar path", e); + } + } + + @Override + public Map toStorageImpl() { + Map map = super.toStorageImpl(); + map.put(STORAGE_KEY_SERVER_BASE_URL, serverBaseURL); + return map; + } + + public String getServerBaseURL() { + return serverBaseURL; + } + + public static final String STORAGE_KEY_SERVER_BASE_URL = "serverBaseURL"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccountFactory.java new file mode 100644 index 000000000..95cff3d9f --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorAccountFactory.java @@ -0,0 +1,60 @@ +package org.jackhuang.hmcl.auth.yggdrasil; + +import org.jackhuang.hmcl.auth.AccountFactory; +import org.jackhuang.hmcl.util.ExceptionalSupplier; +import org.jackhuang.hmcl.util.Lang; +import org.jackhuang.hmcl.util.NetworkUtils; +import org.jackhuang.hmcl.util.UUIDTypeAdapter; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; + +import static org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorAccount.STORAGE_KEY_SERVER_BASE_URL; +import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*; + +public class AuthlibInjectorAccountFactory extends AccountFactory { + private final ExceptionalSupplier injectorJarPathSupplier; + + public AuthlibInjectorAccountFactory(ExceptionalSupplier injectorJarPathSupplier) { + this.injectorJarPathSupplier = injectorJarPathSupplier; + } + + @Override + public AuthlibInjectorAccount fromUsername(String username, String password, Object additionalData) { + if (!(additionalData instanceof String) || !NetworkUtils.isURL((String) additionalData)) + throw new IllegalArgumentException("Additional data should be server base url string for authlib injector accounts."); + + AuthlibInjectorAccount account = new AuthlibInjectorAccount(injectorJarPathSupplier, (String) additionalData, username); + account.setPassword(password); + return account; + } + + @Override + public AuthlibInjectorAccount fromStorageImpl(Map storage) { + String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class) + .orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME)); + String serverBaseURL = Lang.get(storage, STORAGE_KEY_SERVER_BASE_URL, String.class) + .orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_SERVER_BASE_URL)); + + AuthlibInjectorAccount account = new AuthlibInjectorAccount(injectorJarPathSupplier, serverBaseURL, username); + account.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class, username)); + account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class, null)); + account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class) + .orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_CLIENT_TOKEN))); + + Lang.get(storage, STORAGE_KEY_USER_PROPERTIES, List.class) + .ifPresent(account.getUserProperties()::fromList); + Optional profileId = Lang.get(storage, STORAGE_KEY_PROFILE_ID, String.class); + Optional profileName = Lang.get(storage, STORAGE_KEY_PROFILE_NAME, String.class); + GameProfile profile = null; + if (profileId.isPresent() && profileName.isPresent()) { + profile = new GameProfile(UUIDTypeAdapter.fromString(profileId.get()), profileName.get()); + Lang.get(storage, STORAGE_KEY_PROFILE_PROPERTIES, List.class) + .ifPresent(profile.getProperties()::fromList); + } + account.setSelectedProfile(profile); + return account; + } +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorBuildInfo.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorBuildInfo.java new file mode 100644 index 000000000..f27b9650b --- /dev/null +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/AuthlibInjectorBuildInfo.java @@ -0,0 +1,43 @@ +package org.jackhuang.hmcl.auth.yggdrasil; + +import com.google.gson.JsonParseException; +import org.jackhuang.hmcl.util.Constants; +import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; +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 Lang.requireJsonNonNull(Constants.GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(updateUrl)), AuthlibInjectorBuildInfo.class)); + } + + public static final String UPDATE_URL = "https://authlib-injector.to2mbn.org/api/buildInfo"; +} diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java index 12baa8818..f633ade4f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccount.java @@ -35,7 +35,7 @@ import java.util.*; * * @author huang */ -public final class YggdrasilAccount extends Account { +public class YggdrasilAccount extends Account { private final String username; private String password; @@ -48,8 +48,15 @@ public final class YggdrasilAccount extends Account { private GameProfile[] profiles; private UserType userType = UserType.LEGACY; - public YggdrasilAccount(String username) { + public YggdrasilAccount(String baseAuthServer, String baseSessionServer, String username) { + this.baseAuthServer = baseAuthServer; + this.baseSessionServer = baseSessionServer; + this.baseProfile = baseSessionServer + "session/minecraft/profile/"; this.username = username; + + this.routeAuthenticate = NetworkUtils.toURL(baseAuthServer + "authenticate"); + this.routeRefresh = NetworkUtils.toURL(baseAuthServer + "refresh"); + this.routeValidate = NetworkUtils.toURL(baseAuthServer + "validate"); } @Override @@ -136,9 +143,9 @@ public final class YggdrasilAccount extends Account { isOnline = true; return; } - logIn1(ROUTE_REFRESH, new RefreshRequest(accessToken, clientToken), proxy); + logIn1(routeRefresh, new RefreshRequest(accessToken, clientToken), proxy); } else if (StringUtils.isNotBlank(password)) - logIn1(ROUTE_AUTHENTICATE, new AuthenticationRequest(username, password, clientToken), proxy); + logIn1(routeAuthenticate, new AuthenticationRequest(username, password, clientToken), proxy); else throw new AuthenticationException("Password cannot be blank"); } @@ -250,7 +257,7 @@ public final class YggdrasilAccount extends Account { return false; try { - makeRequest(ROUTE_VALIDATE, new ValidateRequest(accessToken, clientToken), proxy); + makeRequest(routeValidate, new ValidateRequest(accessToken, clientToken), proxy); return true; } catch (AuthenticationException e) { return false; @@ -268,7 +275,7 @@ public final class YggdrasilAccount extends Account { if (StringUtils.isBlank(userId)) throw new IllegalStateException("Not logged in"); - ProfileResponse response = GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(BASE_PROFILE + UUIDTypeAdapter.fromUUID(profile.getId()))), ProfileResponse.class); + ProfileResponse response = GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(baseProfile + UUIDTypeAdapter.fromUUID(profile.getId()))), ProfileResponse.class); if (response.getProperties() == null) return Optional.empty(); Property textureProperty = response.getProperties().get("textures"); if (textureProperty == null) return Optional.empty(); @@ -287,11 +294,12 @@ public final class YggdrasilAccount extends Account { return "YggdrasilAccount[username=" + getUsername() + "]"; } - private static final String BASE_URL = "https://authserver.mojang.com/"; - private static final String BASE_PROFILE = "https://sessionserver.mojang.com/session/minecraft/profile/"; - private static final URL ROUTE_AUTHENTICATE = NetworkUtils.toURL(BASE_URL + "authenticate"); - private static final URL ROUTE_REFRESH = NetworkUtils.toURL(BASE_URL + "refresh"); - private static final URL ROUTE_VALIDATE = NetworkUtils.toURL(BASE_URL + "validate"); + private final String baseAuthServer; + private final String baseSessionServer; + private final String baseProfile; + private final URL routeAuthenticate; + private final URL routeRefresh; + private final URL routeValidate; static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken"; static final String STORAGE_KEY_PROFILE_NAME = "displayName"; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java index 1a96ddddc..f7c7a714f 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/auth/yggdrasil/YggdrasilAccountFactory.java @@ -31,15 +31,23 @@ import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*; * * @author huangyuhui */ -public final class YggdrasilAccountFactory extends AccountFactory { - public static final YggdrasilAccountFactory INSTANCE = new YggdrasilAccountFactory(); +public class YggdrasilAccountFactory extends AccountFactory { - private YggdrasilAccountFactory() { + private final String baseAuthServer; + private final String baseSessionServer; + + public YggdrasilAccountFactory() { + this(MOJANG_AUTH_SERVER, MOJANG_SESSION_SERVER); + } + + public YggdrasilAccountFactory(String baseAuthServer, String baseSessionServer) { + this.baseAuthServer = baseAuthServer; + this.baseSessionServer = baseSessionServer; } @Override - public YggdrasilAccount fromUsername(String username, String password) { - YggdrasilAccount account = new YggdrasilAccount(username); + public YggdrasilAccount fromUsername(String username, String password, Object additionalData) { + YggdrasilAccount account = new YggdrasilAccount(MOJANG_AUTH_SERVER, MOJANG_SESSION_SERVER, username); account.setPassword(password); return account; } @@ -49,7 +57,7 @@ public final class YggdrasilAccountFactory extends AccountFactory new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME)); - YggdrasilAccount account = new YggdrasilAccount(username); + YggdrasilAccount account = new YggdrasilAccount(baseAuthServer, baseSessionServer, username); account.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class, username)); account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class, null)); account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class) @@ -69,4 +77,6 @@ public final class YggdrasilAccountFactory extends AccountFactory jvmArguments) { + List list = jvmArguments.stream().map(StringArgument::new).collect(Collectors.toList()); + if (arguments == null) + return new Arguments(null, list); + else + return new Arguments(arguments.getGame(), Lang.merge(arguments.getJvm(), list)); + } + public static Arguments merge(Arguments a, Arguments b) { if (a == null) return b; diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifest.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifest.java index 2815803c2..90b9ecab6 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifest.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/mod/CurseManifest.java @@ -22,6 +22,7 @@ import com.google.gson.annotations.SerializedName; import org.jackhuang.hmcl.util.CompressingUtils; import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.Immutable; +import org.jackhuang.hmcl.util.Lang; import java.io.File; import java.io.IOException; @@ -119,9 +120,7 @@ public final class CurseManifest { */ public static Modpack readCurseForgeModpackManifest(File f) throws IOException, JsonParseException { String json = CompressingUtils.readTextZipEntry(f, "manifest.json"); - CurseManifest manifest = Constants.GSON.fromJson(json, CurseManifest.class); - if (manifest == null) - throw new JsonParseException("`manifest.json` not found. Not a valid Curse modpack."); + CurseManifest manifest = Lang.requireJsonNonNull(Constants.GSON.fromJson(json, CurseManifest.class)); return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), manifest.getMinecraft().getGameVersion(), CompressingUtils.readTextZipEntryQuietly(f, "modlist.html").orElse( "No description"), manifest); } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DateTypeAdapter.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DateTypeAdapter.java index c89b5e46a..008873114 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DateTypeAdapter.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/DateTypeAdapter.java @@ -73,7 +73,7 @@ public final class DateTypeAdapter implements JsonSerializer, JsonDeserial cleaned = cleaned.substring(0, 22) + cleaned.substring(23); return ISO_8601_FORMAT.parse(cleaned); } catch (Exception e) { - throw new JsonSyntaxException("Invalid date: " + string, e); + throw new JsonParseException("Invalid date: " + string, e); } } } diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java index 6e39b8fa0..40b7f4256 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/Lang.java @@ -5,6 +5,8 @@ */ package org.jackhuang.hmcl.util; +import com.google.gson.JsonParseException; + import java.util.*; import java.util.function.Consumer; import java.util.function.Function; @@ -22,6 +24,16 @@ public final class Lang { public static final Consumer EMPTY_CONSUMER = a -> { }; + public static T requireJsonNonNull(T obj) throws JsonParseException { + return requireJsonNonNull(obj, "Json object cannot be null."); + } + + public static T requireJsonNonNull(T obj, String message) throws JsonParseException { + if (obj == null) + throw new JsonParseException(message); + return obj; + } + public static Map mapOf(Pair... pairs) { HashMap map = new HashMap<>(); for (Pair pair : pairs) diff --git a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java index a5546bd24..2b1519498 100644 --- a/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java +++ b/HMCLCore/src/main/java/org/jackhuang/hmcl/util/NetworkUtils.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.Proxy; import java.net.URL; import java.security.GeneralSecurityException; @@ -187,4 +188,13 @@ public final class NetworkUtils { public static URL toURL(String str) { return Lang.invoke(() -> new URL(str)); } + + public static boolean isURL(String str) { + try { + new URL(str); + return true; + } catch (MalformedURLException e) { + return false; + } + } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7a3265ee9..27768f1bb 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9eb8b1334..92165eede 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Sun Oct 22 14:32:40 CST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip diff --git a/gradlew b/gradlew index 8b3531e29..cccdd3d51 100644 --- a/gradlew +++ b/gradlew @@ -78,14 +78,14 @@ if [ -n "$JAVA_HOME" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the -path of your Java installation." +location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the -path of your Java installation." +location of your Java installation." fi # Increase the maximum file descriptors if we can. diff --git a/gradlew.bat b/gradlew.bat index 2797c4770..f9553162f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -27,7 +27,7 @@ echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the -echo path of your Java installation. +echo location of your Java installation. goto fail @@ -41,7 +41,7 @@ echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the -echo path of your Java installation. +echo location of your Java installation. goto fail