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 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();

View File

@ -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 + ".");

View File

@ -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<String, AccountFactory<?>> 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();
}
}

View File

@ -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.
}
}

View File

@ -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:

View File

@ -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) {

View File

@ -28,10 +28,14 @@ import java.util.Map;
public abstract class AccountFactory<T extends Account> {
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);

View File

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

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
*/
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";

View File

@ -31,15 +31,23 @@ import static org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount.*;
*
* @author huangyuhui
*/
public final class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
public static final YggdrasilAccountFactory INSTANCE = new YggdrasilAccountFactory();
public class YggdrasilAccountFactory extends AccountFactory<YggdrasilAccount> {
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<YggdrasilAccou
String username = Lang.get(storage, STORAGE_KEY_USER_NAME, String.class)
.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.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<YggdrasilAccou
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());
}
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) {
if (a == null)
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.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);
}

View File

@ -73,7 +73,7 @@ public final class DateTypeAdapter implements JsonSerializer<Date>, 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);
}
}
}

View File

@ -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> 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) {
HashMap<K, V> map = new HashMap<>();
for (Pair<K, V> pair : pairs)

View File

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

Binary file not shown.

View File

@ -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

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
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.

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.
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