HMCLCore authlib injector support

This commit is contained in:
huanghongxun 2018-02-17 11:01:08 +08:00
parent bfed651263
commit 83bc6748a3
22 changed files with 329 additions and 44 deletions

View File

@ -121,11 +121,11 @@ public final class Main extends Application {
} }
public static final File MINECRAFT_DIRECTORY = getWorkingDirectory("minecraft"); 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 VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@";
public static final String NAME = "HMCL"; public static final String NAME = "HMCL";
public static final String TITLE = NAME + " " + VERSION; 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 ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION)); public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION));
public static final CrashReporter CRASH_REPORTER = new CrashReporter(); public static final CrashReporter CRASH_REPORTER = new CrashReporter();

View File

@ -21,6 +21,7 @@ import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.mod.Modpack; import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.util.CompressingUtils; import org.jackhuang.hmcl.util.CompressingUtils;
import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils; import org.jackhuang.hmcl.util.StringUtils;
import java.io.File; import java.io.File;
@ -81,13 +82,9 @@ public final class HMCLModpackManager {
*/ */
public static Modpack readHMCLModpackManifest(File file) throws IOException, JsonParseException { public static Modpack readHMCLModpackManifest(File file) throws IOException, JsonParseException {
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json"); String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json");
Modpack manifest = Constants.GSON.fromJson(manifestJson, Modpack.class); Modpack manifest = Lang.requireJsonNonNull(Constants.GSON.fromJson(manifestJson, Modpack.class));
if (manifest == null)
throw new JsonParseException("`modpack.json` not found. " + file + " is not a valid HMCL modpack.");
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json"); String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json");
Version game = Constants.GSON.fromJson(gameJson, Version.class); Version game = Lang.requireJsonNonNull(Constants.GSON.fromJson(gameJson, Version.class));
if (game == null)
throw new JsonParseException("`minecraft/pack.json` not found. " + file + " iot a valid HMCL modpack.");
if (game.getJar() == null) if (game.getJar() == null)
if (StringUtils.isBlank(manifest.getVersion())) if (StringUtils.isBlank(manifest.getVersion()))
throw new JsonParseException("Cannot recognize the game version of modpack " + file + "."); throw new JsonParseException("Cannot recognize the game version of modpack " + file + ".");

View File

@ -17,15 +17,19 @@
*/ */
package org.jackhuang.hmcl.setting; package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.OfflineAccount; import org.jackhuang.hmcl.auth.OfflineAccount;
import org.jackhuang.hmcl.auth.OfflineAccountFactory; import org.jackhuang.hmcl.auth.OfflineAccountFactory;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount; import org.jackhuang.hmcl.auth.yggdrasil.*;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory; import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Lang; import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair; import org.jackhuang.hmcl.util.Pair;
import java.io.File;
import java.net.URL;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -37,14 +41,17 @@ public final class Accounts {
public static final String OFFLINE_ACCOUNT_KEY = "offline"; public static final String OFFLINE_ACCOUNT_KEY = "offline";
public static final String YGGDRASIL_ACCOUNT_KEY = "yggdrasil"; public static final String YGGDRASIL_ACCOUNT_KEY = "yggdrasil";
public static final String AUTHLIB_INJECTOR_ACCOUNT_KEY = "authlibInjector";
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = Lang.mapOf( public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = Lang.mapOf(
new Pair<>(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE), 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) { public static String getAccountType(Account account) {
if (account instanceof OfflineAccount) return OFFLINE_ACCOUNT_KEY; 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 if (account instanceof YggdrasilAccount) return YGGDRASIL_ACCOUNT_KEY;
else return YGGDRASIL_ACCOUNT_KEY; else return YGGDRASIL_ACCOUNT_KEY;
} }
@ -75,4 +82,16 @@ public final class Accounts {
static String getAccountId(String username, String character) { static String getAccountId(String username, String character) {
return username + ":" + 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();
}
} }

View File

@ -19,6 +19,7 @@ package org.jackhuang.hmcl.setting;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorBuildInfo;
import org.jackhuang.hmcl.util.JavaVersion; import org.jackhuang.hmcl.util.JavaVersion;
import java.util.LinkedList; import java.util.LinkedList;
@ -91,6 +92,9 @@ public final class Config {
@SerializedName("logLines") @SerializedName("logLines")
private int logLines = 100; private int logLines = 100;
@SerializedName("authlibInjectorServerURL")
private String authlibInjectorServerURL = AuthlibInjectorBuildInfo.UPDATE_URL;
public String getSelectedProfile() { public String getSelectedProfile() {
return selectedProfile; return selectedProfile;
} }
@ -278,4 +282,18 @@ public final class Config {
public void setLogLines(int logLines) { public void setLogLines(int logLines) {
this.logLines = 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.
}
} }

View File

@ -28,6 +28,7 @@ import javafx.scene.text.Font;
import org.jackhuang.hmcl.Main; import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.Account; import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory; import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.yggdrasil.AuthlibInjectorBuildInfo;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider; import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider; import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider; import org.jackhuang.hmcl.download.MojangDownloadProvider;
@ -39,10 +40,7 @@ import org.jackhuang.hmcl.util.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.Authenticator; import java.net.*;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.util.*; import java.util.*;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -106,6 +104,13 @@ public class Settings {
}); });
loadProxy(); 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() { private Config initSettings() {
@ -275,6 +280,10 @@ public class Settings {
SETTINGS.setLogLines(logLines); SETTINGS.setLogLines(logLines);
} }
public String getAuthlibInjectorServerURL() {
return SETTINGS.getAuthlibInjectorServerURL();
}
public DownloadProvider getDownloadProvider() { public DownloadProvider getDownloadProvider() {
switch (SETTINGS.getDownloadType()) { switch (SETTINGS.getDownloadType()) {
case 0: case 0:

View File

@ -154,7 +154,7 @@ public class AppDataUpgrader extends IUpgrader {
public static class AppDataUpgraderPackGzTask extends Task { 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 final File HMCL_VER_FILE = new File(BASE_FOLDER, "hmclver.json");
public static File getSelf(String ver) { public static File getSelf(String ver) {

View File

@ -28,10 +28,14 @@ import java.util.Map;
public abstract class AccountFactory<T extends Account> { public abstract class AccountFactory<T extends Account> {
public final T fromUsername(String username) { 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<Object, Object> storage); protected abstract T fromStorageImpl(Map<Object, Object> storage);

View File

@ -18,12 +18,15 @@
package org.jackhuang.hmcl.auth; package org.jackhuang.hmcl.auth;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile; 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; import org.jackhuang.hmcl.util.UUIDTypeAdapter;
/** /**
* *
* @author huangyuhui * @author huangyuhui
*/ */
@Immutable
public final class AuthInfo { public final class AuthInfo {
private final String username; private final String username;
@ -32,6 +35,7 @@ public final class AuthInfo {
private final UserType userType; private final UserType userType;
private final String userProperties; private final String userProperties;
private final String userPropertyMap; private final String userPropertyMap;
private final Arguments arguments;
public AuthInfo(String username, String userId, String authToken) { public AuthInfo(String username, String userId, String authToken) {
this(username, userId, authToken, UserType.LEGACY); 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) { 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.username = username;
this.userId = userId; this.userId = userId;
this.authToken = authToken; this.authToken = authToken;
this.userType = userType; this.userType = userType;
this.userProperties = userProperties; this.userProperties = userProperties;
this.userPropertyMap = userPropertyMap; this.userPropertyMap = userPropertyMap;
this.arguments = arguments;
} }
public AuthInfo(GameProfile profile, String authToken, UserType userType, String userProperties) { public AuthInfo(GameProfile profile, String authToken, UserType userType, String userProperties) {
@ -93,4 +102,12 @@ public final class AuthInfo {
public String getUserPropertyMap() { public String getUserPropertyMap() {
return userPropertyMap; return userPropertyMap;
} }
public Arguments getArguments() {
return arguments;
}
public AuthInfo setArguments(Arguments arguments) {
return new AuthInfo(username, userId, authToken, userType, userProperties, userPropertyMap, arguments);
}
} }

View File

@ -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<String, ?> injectorJarPath;
public AuthlibInjectorAccount(ExceptionalSupplier<String, ?> 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<Object, Object> toStorageImpl() {
Map<Object, Object> 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";
}

View File

@ -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<YggdrasilAccount> {
private final ExceptionalSupplier<String, ?> injectorJarPathSupplier;
public AuthlibInjectorAccountFactory(ExceptionalSupplier<String, ?> 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<Object, Object> 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<String> profileId = Lang.get(storage, STORAGE_KEY_PROFILE_ID, String.class);
Optional<String> 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;
}
}

View File

@ -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";
}

View File

@ -35,7 +35,7 @@ import java.util.*;
* *
* @author huang * @author huang
*/ */
public final class YggdrasilAccount extends Account { public class YggdrasilAccount extends Account {
private final String username; private final String username;
private String password; private String password;
@ -48,8 +48,15 @@ public final class YggdrasilAccount extends Account {
private GameProfile[] profiles; private GameProfile[] profiles;
private UserType userType = UserType.LEGACY; 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.username = username;
this.routeAuthenticate = NetworkUtils.toURL(baseAuthServer + "authenticate");
this.routeRefresh = NetworkUtils.toURL(baseAuthServer + "refresh");
this.routeValidate = NetworkUtils.toURL(baseAuthServer + "validate");
} }
@Override @Override
@ -136,9 +143,9 @@ public final class YggdrasilAccount extends Account {
isOnline = true; isOnline = true;
return; return;
} }
logIn1(ROUTE_REFRESH, new RefreshRequest(accessToken, clientToken), proxy); logIn1(routeRefresh, new RefreshRequest(accessToken, clientToken), proxy);
} else if (StringUtils.isNotBlank(password)) } else if (StringUtils.isNotBlank(password))
logIn1(ROUTE_AUTHENTICATE, new AuthenticationRequest(username, password, clientToken), proxy); logIn1(routeAuthenticate, new AuthenticationRequest(username, password, clientToken), proxy);
else else
throw new AuthenticationException("Password cannot be blank"); throw new AuthenticationException("Password cannot be blank");
} }
@ -250,7 +257,7 @@ public final class YggdrasilAccount extends Account {
return false; return false;
try { try {
makeRequest(ROUTE_VALIDATE, new ValidateRequest(accessToken, clientToken), proxy); makeRequest(routeValidate, new ValidateRequest(accessToken, clientToken), proxy);
return true; return true;
} catch (AuthenticationException e) { } catch (AuthenticationException e) {
return false; return false;
@ -268,7 +275,7 @@ public final class YggdrasilAccount extends Account {
if (StringUtils.isBlank(userId)) if (StringUtils.isBlank(userId))
throw new IllegalStateException("Not logged in"); 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(); if (response.getProperties() == null) return Optional.empty();
Property textureProperty = response.getProperties().get("textures"); Property textureProperty = response.getProperties().get("textures");
if (textureProperty == null) return Optional.empty(); if (textureProperty == null) return Optional.empty();
@ -287,11 +294,12 @@ public final class YggdrasilAccount extends Account {
return "YggdrasilAccount[username=" + getUsername() + "]"; return "YggdrasilAccount[username=" + getUsername() + "]";
} }
private static final String BASE_URL = "https://authserver.mojang.com/"; private final String baseAuthServer;
private static final String BASE_PROFILE = "https://sessionserver.mojang.com/session/minecraft/profile/"; private final String baseSessionServer;
private static final URL ROUTE_AUTHENTICATE = NetworkUtils.toURL(BASE_URL + "authenticate"); private final String baseProfile;
private static final URL ROUTE_REFRESH = NetworkUtils.toURL(BASE_URL + "refresh"); private final URL routeAuthenticate;
private static final URL ROUTE_VALIDATE = NetworkUtils.toURL(BASE_URL + "validate"); private final URL routeRefresh;
private final URL routeValidate;
static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken"; static final String STORAGE_KEY_ACCESS_TOKEN = "accessToken";
static final String STORAGE_KEY_PROFILE_NAME = "displayName"; static final String STORAGE_KEY_PROFILE_NAME = "displayName";

View File

@ -31,15 +31,23 @@ import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
* *
* @author huangyuhui * @author huangyuhui
*/ */
public final class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> { public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
public static final YggdrasilAccountFactory INSTANCE = new YggdrasilAccountFactory();
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 @Override
public YggdrasilAccount fromUsername(String username, String password) { public YggdrasilAccount fromUsername(String username, String password, Object additionalData) {
YggdrasilAccount account = new YggdrasilAccount(username); YggdrasilAccount account = new YggdrasilAccount(MOJANG_AUTH_SERVER, MOJANG_SESSION_SERVER, username);
account.setPassword(password); account.setPassword(password);
return account; return account;
} }
@ -49,7 +57,7 @@ public final class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccou
String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class) String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class)
.orElseThrow(() -> new IllegalArgumentException("storage does not have key " + STORAGE_KEY_USER_NAME)); .orElseThrow(() -> 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.setUserId(Lang.get(storage, STORAGE_KEY_USER_ID, String.class, username));
account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class, null)); account.setAccessToken(Lang.get(storage, STORAGE_KEY_ACCESS_TOKEN, String.class, null));
account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class) account.setClientToken(Lang.get(storage, STORAGE_KEY_CLIENT_TOKEN, String.class)
@ -69,4 +77,6 @@ public final class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccou
return account; return account;
} }
private static final String MOJANG_AUTH_SERVER = "https://authserver.mojang.com/";
private static final String MOJANG_SESSION_SERVER = "https://sessionserver.mojang.com/";
} }

View File

@ -66,6 +66,18 @@ public final class Arguments {
return new Arguments(Lang.merge(arguments.getGame(), list), arguments.getJvm()); return new Arguments(Lang.merge(arguments.getGame(), list), arguments.getJvm());
} }
public static Arguments addJVMArguments(Arguments arguments, String... jvmArguments) {
return addJVMArguments(arguments, Arrays.asList(jvmArguments));
}
public static Arguments addJVMArguments(Arguments arguments, List<String> jvmArguments) {
List<Argument> 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) { public static Arguments merge(Arguments a, Arguments b) {
if (a == null) if (a == null)
return b; return b;

View File

@ -22,6 +22,7 @@ import com.google.gson.annotations.SerializedName;
import org.jackhuang.hmcl.util.CompressingUtils; import org.jackhuang.hmcl.util.CompressingUtils;
import org.jackhuang.hmcl.util.Constants; import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Immutable; import org.jackhuang.hmcl.util.Immutable;
import org.jackhuang.hmcl.util.Lang;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -119,9 +120,7 @@ public final class CurseManifest {
*/ */
public static Modpack readCurseForgeModpackManifest(File f) throws IOException, JsonParseException { public static Modpack readCurseForgeModpackManifest(File f) throws IOException, JsonParseException {
String json = CompressingUtils.readTextZipEntry(f, "manifest.json"); String json = CompressingUtils.readTextZipEntry(f, "manifest.json");
CurseManifest manifest = Constants.GSON.fromJson(json, CurseManifest.class); CurseManifest manifest = Lang.requireJsonNonNull(Constants.GSON.fromJson(json, CurseManifest.class));
if (manifest == null)
throw new JsonParseException("`manifest.json` not found. Not a valid Curse modpack.");
return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), manifest.getMinecraft().getGameVersion(), return new Modpack(manifest.getName(), manifest.getAuthor(), manifest.getVersion(), manifest.getMinecraft().getGameVersion(),
CompressingUtils.readTextZipEntryQuietly(f, "modlist.html").orElse( "No description"), manifest); CompressingUtils.readTextZipEntryQuietly(f, "modlist.html").orElse( "No description"), manifest);
} }

View File

@ -73,7 +73,7 @@ public final class DateTypeAdapter implements JsonSerializer<Date>, JsonDeserial
cleaned = cleaned.substring(0, 22) + cleaned.substring(23); cleaned = cleaned.substring(0, 22) + cleaned.substring(23);
return ISO_8601_FORMAT.parse(cleaned); return ISO_8601_FORMAT.parse(cleaned);
} catch (Exception e) { } catch (Exception e) {
throw new JsonSyntaxException("Invalid date: " + string, e); throw new JsonParseException("Invalid date: " + string, e);
} }
} }
} }

View File

@ -5,6 +5,8 @@
*/ */
package org.jackhuang.hmcl.util; package org.jackhuang.hmcl.util;
import com.google.gson.JsonParseException;
import java.util.*; import java.util.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -22,6 +24,16 @@ public final class Lang {
public static final Consumer EMPTY_CONSUMER = a -> { public static final Consumer EMPTY_CONSUMER = a -> {
}; };
public static <T> T requireJsonNonNull(T obj) throws JsonParseException {
return requireJsonNonNull(obj, "Json object cannot be null.");
}
public static <T> T requireJsonNonNull(T obj, String message) throws JsonParseException {
if (obj == null)
throw new JsonParseException(message);
return obj;
}
public static <K, V> Map<K, V> mapOf(Pair<K, V>... pairs) { public static <K, V> Map<K, V> mapOf(Pair<K, V>... pairs) {
HashMap<K, V> map = new HashMap<>(); HashMap<K, V> map = new HashMap<>();
for (Pair<K, V> pair : pairs) for (Pair<K, V> pair : pairs)

View File

@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.Proxy; import java.net.Proxy;
import java.net.URL; import java.net.URL;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
@ -187,4 +188,13 @@ public final class NetworkUtils {
public static URL toURL(String str) { public static URL toURL(String str) {
return Lang.invoke(() -> new URL(str)); return Lang.invoke(() -> new URL(str));
} }
public static boolean isURL(String str) {
try {
new URL(str);
return true;
} catch (MalformedURLException e) {
return false;
}
}
} }

Binary file not shown.

View File

@ -1,6 +1,5 @@
#Sun Oct 22 14:32:40 CST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists 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

4
gradlew vendored
View File

@ -78,14 +78,14 @@ if [ -n "$JAVA_HOME" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
path of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" 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. 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 Please set the JAVA_HOME variable in your environment to match the
path of your Java installation." location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.

4
gradlew.bat vendored
View File

@ -27,7 +27,7 @@ echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the 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 goto fail
@ -41,7 +41,7 @@ echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. echo.
echo Please set the JAVA_HOME variable in your environment to match the 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 goto fail