Skip to content

Commit

Permalink
Merge branch 'javafx' into prs-base
Browse files Browse the repository at this point in the history
  • Loading branch information
burningtnt committed Jan 9, 2024
2 parents 1176397 + fbc6677 commit c8caa8a
Show file tree
Hide file tree
Showing 49 changed files with 1,459 additions and 250 deletions.
3 changes: 2 additions & 1 deletion HMCL/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ tasks.getByName<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("sha
"javafx.graphics/com.sun.prism",
"javafx.controls/com.sun.javafx.scene.control",
"javafx.controls/com.sun.javafx.scene.control.behavior",
"javafx.controls/javafx.scene.control.skin"
"javafx.controls/javafx.scene.control.skin",
"jdk.attach/sun.tools.attach"
).joinToString(" ")
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ else if (LibraryAnalyzer.isModded(this, version)) {
return newBuiltinImage("/assets/img/fabric.png");
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.FORGE))
return newBuiltinImage("/assets/img/forge.png");
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.NEO_FORGE))
return newBuiltinImage("/assets/img/neoforge.png");
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.QUILT))
return newBuiltinImage("/assets/img/quilt.png");
else if (libraryAnalyzer.has(LibraryAnalyzer.LibraryType.OPTIFINE))
Expand Down
20 changes: 11 additions & 9 deletions HMCL/src/main/java/org/jackhuang/hmcl/game/LauncherHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
import org.jackhuang.hmcl.mod.ModpackCompletionException;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.ModpackProvider;
import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.setting.DownloadProviders;
import org.jackhuang.hmcl.setting.LauncherVisibility;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.*;
import org.jackhuang.hmcl.ui.*;
import org.jackhuang.hmcl.ui.construct.*;
Expand Down Expand Up @@ -216,9 +219,10 @@ private void launch0() {
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
});
}
}).thenRunAsync(() -> {
launchingLatch.await();
}).withStage("launch.state.waiting_launching"))
}).withFakeProgress(
i18n("message.doing"),
() -> launchingLatch.getCount() == 0, 6.95
).withStage("launch.state.waiting_launching"))
.withStagesHint(Lang.immutableListOf(
"launch.state.java",
"launch.state.dependencies",
Expand Down Expand Up @@ -506,9 +510,7 @@ private static Task<JavaVersion> checkGameState(Profile profile, VersionSetting
break;
case MODDED_JAVA_16:
// Minecraft<=1.17.1+Forge[37.0.0,37.0.60) not compatible with Java 17
String forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE)
.map(LibraryAnalyzer.LibraryType.FORGE::patchVersion)
.orElse(null);
String forgePatchVersion = analyzer.getVersion(LibraryAnalyzer.LibraryType.FORGE).orElse(null);
if (forgePatchVersion != null && VersionNumber.VERSION_COMPARATOR.compare(forgePatchVersion, "37.0.60") < 0)
suggestions.add(i18n("launch.advice.forge37_0_60"));
else
Expand Down Expand Up @@ -617,7 +619,7 @@ private static CompletableFuture<JavaVersion> downloadJava(String gameVersion, G
/**
* Directly start java downloading.
*
* @param javaVersion target Java version
* @param javaVersion target Java version
* @param downloadProvider download provider
* @return JavaVersion, null if we failed to download java, failed if an error occurred when downloading.
*/
Expand Down Expand Up @@ -745,7 +747,7 @@ public void setProcess(ManagedProcess process) {

if (showLogs)
Platform.runLater(() -> {
logWindow = new LogWindow();
logWindow = new LogWindow(process);
logWindow.showNormal();
logWindowLatch.countDown();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,70 +22,76 @@
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
import org.jackhuang.hmcl.util.gson.Validation;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.JarUtils;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;

import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Logging.LOG;

public class AuthlibInjectorServers implements Validation {
public final class AuthlibInjectorServers implements Validation {

public static final String CONFIG_FILENAME = "authlib-injectors.json";

private final List<String> urls;
private static final Set<AuthlibInjectorServer> servers = new CopyOnWriteArraySet<>();

public AuthlibInjectorServers(List<String> urls) {
this.urls = urls;
public static Set<AuthlibInjectorServer> getServers() {
return servers;
}

public List<String> getUrls() {
return urls;
private final List<String> urls;

private AuthlibInjectorServers(List<String> urls) {
this.urls = urls;
}

@Override
public void validate() throws JsonParseException {
if (urls == null)
throw new JsonParseException("authlib-injectors.json -> urls cannot be null");
public void validate() throws JsonParseException, TolerableValidationException {
if (this.urls == null) {
throw new JsonParseException("authlib-injectors.json -> urls cannot be null.");
}
}

private static final Path configLocation = Paths.get(CONFIG_FILENAME);
private static AuthlibInjectorServers configInstance;

public synchronized static void init() {
if (configInstance != null) {
throw new IllegalStateException("AuthlibInjectorServers is already loaded");
public static void init() {
Path configLocation;
Path jarPath = JarUtils.thisJarPath();
if (jarPath != null && Files.isRegularFile(jarPath) && Files.isWritable(jarPath)) {
configLocation = jarPath.getParent().resolve(CONFIG_FILENAME);
} else {
configLocation = Paths.get(CONFIG_FILENAME);
}

configInstance = new AuthlibInjectorServers(Collections.emptyList());

if (Files.exists(configLocation)) {
if (ConfigHolder.isNewlyCreated() && Files.exists(configLocation)) {
AuthlibInjectorServers configInstance;
try {
String content = FileUtils.readText(configLocation);
configInstance = JsonUtils.GSON.fromJson(content, AuthlibInjectorServers.class);
} catch (IOException | JsonParseException e) {
LOG.log(Level.WARNING, "Malformed authlib-injectors.json", e);
return;
}
}

if (ConfigHolder.isNewlyCreated() && !AuthlibInjectorServers.getConfigInstance().getUrls().isEmpty()) {
config().setPreferredLoginType(Accounts.getLoginType(Accounts.FACTORY_AUTHLIB_INJECTOR));
for (String url : AuthlibInjectorServers.getConfigInstance().getUrls()) {
Task.supplyAsync(Schedulers.io(), () -> AuthlibInjectorServer.locateServer(url))
.thenAcceptAsync(Schedulers.javafx(), server -> config().getAuthlibInjectorServers().add(server))
.start();
if (!configInstance.urls.isEmpty()) {
config().setPreferredLoginType(Accounts.getLoginType(Accounts.FACTORY_AUTHLIB_INJECTOR));
for (String url : configInstance.urls) {
Task.supplyAsync(Schedulers.io(), () -> AuthlibInjectorServer.locateServer(url))
.thenAcceptAsync(Schedulers.javafx(), server -> {
config().getAuthlibInjectorServers().add(server);
servers.add(server);
})
.start();
}
}
}
}

public static AuthlibInjectorServers getConfigInstance() {
return configInstance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/
package org.jackhuang.hmcl.setting;

import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.util.InvocationDispatcher;
Expand All @@ -28,7 +27,6 @@

import java.io.IOException;
import java.nio.file.*;
import java.util.Map;
import java.util.logging.Level;

import static org.jackhuang.hmcl.util.Logging.LOG;
Expand Down Expand Up @@ -167,8 +165,7 @@ private static Config loadConfig() throws IOException {
if (deserialized == null) {
LOG.info("Config is empty");
} else {
Map<?, ?> raw = new Gson().fromJson(content, Map.class);
ConfigUpgrader.upgradeConfig(deserialized, raw);
ConfigUpgrader.upgradeConfig(deserialized, content);
return deserialized;
}
} catch (JsonParseException e) {
Expand Down
150 changes: 86 additions & 64 deletions HMCL/src/main/java/org/jackhuang/hmcl/setting/ConfigUpgrader.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,96 +17,118 @@
*/
package org.jackhuang.hmcl.setting;

import com.google.gson.Gson;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.logging.Level;

import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG;

final class ConfigUpgrader {
private static final int VERSION = 0;

private ConfigUpgrader() {
}

private static final int CURRENT_VERSION = 1;

/**
* This method is for the compatibility with old HMCL 3.x as well as HMCL 2.x.
* This method is for the compatibility with old HMCL versions.
*
* @param deserialized deserialized config settings
* @param rawJson raw json structure of the config settings without modification
* @return true if config version is upgraded
* @param rawContent raw json content of the config settings without modification
*/
static boolean upgradeConfig(Config deserialized, Map<?, ?> rawJson) {
boolean upgraded;
if (deserialized.getConfigVersion() < VERSION) {
deserialized.setConfigVersion(VERSION);
// TODO: Add upgrade code here.
upgraded = true;
} else {
upgraded = false;
static void upgradeConfig(Config deserialized, String rawContent) {
if (deserialized.getConfigVersion() == CURRENT_VERSION) {
return;
}

int configVersion = deserialized.getConfigVersion();
if (configVersion > CURRENT_VERSION) {
LOG.log(Level.WARNING, String.format("Current HMCL only support the configuration version up to %d. However, the version now is %d.", CURRENT_VERSION, configVersion));
return;
}

upgradeV2(deserialized, rawJson);
upgradeV3(deserialized, rawJson);
LOG.log(Level.INFO, String.format("Updating configuration from %d to %d.", configVersion, CURRENT_VERSION));
Map<?, ?> unmodifiableRawJson = Collections.unmodifiableMap(new Gson().<Map<?, ?>>fromJson(rawContent, Map.class));
for (Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>> dfu : collectDFU()) {
if (configVersion < dfu.getKey()) {
dfu.getValue().accept(deserialized, unmodifiableRawJson);
configVersion = dfu.getKey();
}
}

return upgraded;
deserialized.setConfigVersion(CURRENT_VERSION);
}

/**
* Upgrade configuration of HMCL 2.x
* <p>Initialize the dfu of HMCL. Feel free to use lambda as all the lambda here would not be initialized unless HMCL needs to update the configuration.
* For each item in this list, it should be a Map.Entry.</p>
*
* @param deserialized deserialized config settings
* @param rawJson raw json structure of the config settings without modification
* <p>The key should be a version number. All the configuration with a version number which is less than the specific one will be applied to this upgrader.</p>
* <p>The value should be the upgrader. The configuration which is waited to being processed, and the raw unmodifiable value of the json from the configuration file.</p>
* <p>The return value should a target version number of this item.</p>
*
* <p>The last item must return CURRENT_VERSION, as the config file should always being updated to the latest version.</p>
*/
private static void upgradeV2(Config deserialized, Map<?, ?> rawJson) {
// Convert OfflineAccounts whose stored uuid is important.
tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> {
tryCast(auth.get("offline"), Map.class).ifPresent(offline -> {
String selected = rawJson.containsKey("selectedAccount") ? null
: tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null);
private static List<Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>>> collectDFU() {
List<Map.Entry<Integer, BiConsumer<Config, Map<?, ?>>>> dfu = Lang.immutableListOf(
Pair.pair(1, (deserialized, rawJson) -> {
// Upgrade configuration of HMCL 2.x: Convert OfflineAccounts whose stored uuid is important.
tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> {
tryCast(auth.get("offline"), Map.class).ifPresent(offline -> {
String selected = rawJson.containsKey("selectedAccount") ? null
: tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null);

tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
((Map<?, ?>) uuidMap).forEach((key, value) -> {
Map<Object, Object> storage = new HashMap<>();
storage.put("type", "offline");
storage.put("username", key);
storage.put("uuid", value);
if (key.equals(selected)) {
storage.put("selected", true);
}
deserialized.getAccountStorages().add(storage);
tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
((Map<?, ?>) uuidMap).forEach((key, value) -> {
Map<Object, Object> storage = new HashMap<>();
storage.put("type", "offline");
storage.put("username", key);
storage.put("uuid", value);
if (key.equals(selected)) {
storage.put("selected", true);
}
deserialized.getAccountStorages().add(storage);
});
});
});
});
});
});
});
}

/**
* Upgrade configuration of HMCL earlier than 3.1.70
*
* @param deserialized deserialized config settings
* @param rawJson raw json structure of the config settings without modification
*/
private static void upgradeV3(Config deserialized, Map<?, ?> rawJson) {
if (!rawJson.containsKey("commonDirType"))
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
if (!rawJson.containsKey("backgroundType"))
deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
if (!rawJson.containsKey("hasProxy"))
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
if (!rawJson.containsKey("hasProxyAuth"))
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
// Upgrade configuration of HMCL earlier than 3.1.70
if (!rawJson.containsKey("commonDirType"))
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
if (!rawJson.containsKey("backgroundType"))
deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
if (!rawJson.containsKey("hasProxy"))
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
if (!rawJson.containsKey("hasProxyAuth"))
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));

if (!rawJson.containsKey("downloadType")) {
tryCast(rawJson.get("downloadtype"), Number.class)
.map(Number::intValue)
.ifPresent(id -> {
if (id == 0) {
deserialized.setDownloadType("mojang");
} else if (id == 1) {
deserialized.setDownloadType("bmclapi");
}
});
if (!rawJson.containsKey("downloadType")) {
tryCast(rawJson.get("downloadtype"), Number.class)
.map(Number::intValue)
.ifPresent(id -> {
if (id == 0) {
deserialized.setDownloadType("mojang");
} else if (id == 1) {
deserialized.setDownloadType("bmclapi");
}
});
}
})
);

if (dfu.get(dfu.size() - 1).getKey() != CURRENT_VERSION) {
throw new IllegalStateException("The last dfu must adapt all the config version below CURRENT_VERSION");
}

return dfu;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public enum VersionIconType {
CRAFT_TABLE("/assets/img/craft_table.png"),
FABRIC("/assets/img/fabric.png"),
FORGE("/assets/img/forge.png"),
NEO_FORGE("/assets/img/neoforge.png"),
FURNACE("/assets/img/furnace.png"),
QUILT("/assets/img/quilt.png");

Expand Down
Loading

0 comments on commit c8caa8a

Please sign in to comment.