Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d69e944f6 | ||
|
|
1dc4e3f730 | ||
|
|
14d1bccd14 | ||
|
|
5de150b06e | ||
|
|
e34dd39b9a | ||
|
|
e7d5faeeac | ||
|
|
46a7269087 | ||
|
|
dc31674721 | ||
|
|
afe6bc7456 | ||
|
|
378fd04463 | ||
|
|
3e751ba5c6 | ||
|
|
8fc1ec7bb8 | ||
|
|
e5326a3c4d | ||
|
|
fe279234d8 | ||
|
|
ef9b041310 | ||
|
|
9db576d4bc | ||
|
|
a84971abfc | ||
|
|
a7a79db842 | ||
|
|
d5da14fb1e | ||
|
|
0d2e287f67 | ||
|
|
c329f1cafb | ||
|
|
ab1a2f6fcb | ||
|
|
463dc56702 | ||
|
|
88e23cd0d2 | ||
|
|
9776aafab6 | ||
|
|
fbb5a03e5d | ||
|
|
7c11ce53f3 | ||
|
|
66f4c022d5 | ||
|
|
4b36450f9a | ||
|
|
36b58bd1bb | ||
|
|
0296b6f73d | ||
|
|
447afaa76f | ||
|
|
a0191aa7c2 | ||
|
|
e7c9fcf767 | ||
|
|
fc017515c5 | ||
|
|
6d75c4d34f | ||
|
|
258a6628e4 | ||
|
|
228695d511 | ||
|
|
c2af530abb | ||
|
|
69bc826e30 | ||
|
|
5d0944ed19 | ||
|
|
43f4da5dd3 | ||
|
|
c6ab2cec35 | ||
|
|
b0dc689ba7 | ||
|
|
e11b1f37b9 | ||
|
|
3e5fac4df6 | ||
|
|
e63fa40822 | ||
|
|
e01096bff0 | ||
|
|
2a0cfb1125 | ||
|
|
d4f0a04ccd | ||
|
|
7cbe24b35b | ||
|
|
b7da21e5ce | ||
|
|
0ae1411f0a | ||
|
|
66889cac27 | ||
|
|
67bd2ac386 | ||
|
|
1f278c2a49 | ||
|
|
1674f1828f | ||
|
|
472a0a039f |
No files matched your search
@@ -1 +0,0 @@
|
||||
*.exe filter=lfs diff=lfs merge=lfs -text
|
||||
@@ -1,6 +1,6 @@
|
||||
name: Java CI
|
||||
|
||||
on: [push]
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -8,11 +8,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
java-package: jdk+fx
|
||||
- name: Check style main
|
||||
run: ./gradlew checkstyleMain
|
||||
- name: Check style test
|
||||
run: ./gradlew checkstyleTest
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: HMCL
|
||||
path: HMCL/build/libs
|
||||
+8
-2
@@ -11,7 +11,6 @@ hs_err_pid*
|
||||
*.log
|
||||
.mine*
|
||||
NVIDIA
|
||||
*.json
|
||||
|
||||
# gradle build
|
||||
/build/
|
||||
@@ -38,4 +37,11 @@ NVIDIA
|
||||
# netbeans
|
||||
.nb-gradle
|
||||
|
||||
*.exe
|
||||
*.exe
|
||||
!/HMCL/src/main/resources/assets/HMCLauncher.exe
|
||||
|
||||
# macos
|
||||
.DS_Store
|
||||
|
||||
# test
|
||||
/hmcl.json
|
||||
+17
-8
@@ -1,9 +1,11 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'org.tukaani:xz:1.8'
|
||||
classpath 'org.glavo:pack200:0.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,18 +21,21 @@ import java.security.Signature
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.util.jar.JarFile
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Pack200
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import java.util.zip.ZipFile
|
||||
import java.nio.file.Files
|
||||
|
||||
import org.tukaani.xz.LZMA2Options
|
||||
import org.tukaani.xz.XZOutputStream
|
||||
import org.glavo.pack200.Pack200
|
||||
|
||||
def buildnumber = System.getenv("BUILD_NUMBER") ?: "SNAPSHOT"
|
||||
def dev = null
|
||||
def shortcommit = System.getenv("GITHUB_SHA")?.toLowerCase()?.substring(0, 7) ?: null
|
||||
if (shortcommit != null && !shortcommit.isEmpty()) dev = "dev-" + shortcommit
|
||||
def buildnumber = System.getenv("BUILD_NUMBER") ?: dev ?: "SNAPSHOT"
|
||||
if (System.getenv("BUILD_NUMBER") != null && System.getenv("BUILD_NUMBER_OFFSET") != null)
|
||||
buildnumber = (Integer.parseInt(System.getenv("BUILD_NUMBER")) - Integer.parseInt(System.getenv("BUILD_NUMBER_OFFSET"))).toString()
|
||||
def versionroot = System.getenv("VERSION_ROOT") ?: "3.2"
|
||||
def versionroot = System.getenv("VERSION_ROOT") ?: "3.3"
|
||||
version = versionroot + '.' + buildnumber
|
||||
|
||||
mainClassName = 'org.jackhuang.hmcl.Main'
|
||||
@@ -96,8 +101,10 @@ sourceSets {
|
||||
}
|
||||
|
||||
compileJava11Java {
|
||||
javaCompiler = javaToolchains.compilerFor {
|
||||
languageVersion = JavaLanguageVersion.of(11)
|
||||
if(JavaVersion.current() < JavaVersion.VERSION_11) {
|
||||
javaCompiler = javaToolchains.compilerFor {
|
||||
languageVersion = JavaLanguageVersion.of(11)
|
||||
}
|
||||
}
|
||||
options.compilerArgs.add('--add-exports=java.base/jdk.internal.loader=ALL-UNNAMED')
|
||||
sourceCompatibility = 11
|
||||
@@ -117,6 +124,7 @@ shadowJar {
|
||||
'Main-Class': mainClassName,
|
||||
'Multi-Release': 'true',
|
||||
'Implementation-Version': version,
|
||||
'Class-Path': 'pack200.jar',
|
||||
'Add-Opens': [
|
||||
'java.base/java.lang',
|
||||
'java.base/java.lang.reflect',
|
||||
@@ -161,8 +169,9 @@ processResources {
|
||||
def cssFile = new File(this.projectDir, "src/main/resources/" + resource)
|
||||
def bssFile = new File(this.projectDir, "build/compiled-resources/" + resource[0..-4] + "bss")
|
||||
bssFile.parentFile.mkdirs()
|
||||
exec {
|
||||
commandLine 'javapackager', '-createbss', '-outdir', bssFile.parent, '-srcfiles', cssFile.path
|
||||
javaexec {
|
||||
main = "com.sun.javafx.css.parser.Css2Bin"
|
||||
args = [cssFile, bssFile]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,4 +216,4 @@ task makeExecutables(dependsOn: jar) doLast {
|
||||
|
||||
build.dependsOn makePackXz
|
||||
build.dependsOn makePackGz
|
||||
build.dependsOn makeExecutables
|
||||
build.dependsOn makeExecutables
|
||||
@@ -36,6 +36,7 @@ import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import java.awt.*;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.*;
|
||||
import java.nio.file.Paths;
|
||||
@@ -160,10 +161,15 @@ public final class Launcher extends Application {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
File jarFile = new File(Launcher.class.getProtectionDomain().getCodeSource().getLocation().getPath());
|
||||
String ext = FileUtils.getExtension(jarFile);
|
||||
if ("jar".equals(ext) || "exe".equals(ext))
|
||||
result.add(jarFile);
|
||||
try {
|
||||
File jarFile = new File(URLDecoder.decode(Launcher.class.getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8"));
|
||||
String ext = FileUtils.getExtension(jarFile);
|
||||
if ("jar".equals(ext) || "exe".equals(ext))
|
||||
result.add(jarFile);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
LOG.log(Level.WARNING, "Failed to decode jar path", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty())
|
||||
return null;
|
||||
|
||||
@@ -43,6 +43,9 @@ import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Main {
|
||||
|
||||
private Main() {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("java.net.useSystemProxies", "true");
|
||||
System.setProperty("http.agent", "HMCL/" + Metadata.VERSION);
|
||||
|
||||
@@ -64,7 +64,7 @@ public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
try {
|
||||
// TODO: Dirty implementation here
|
||||
if (I18n.getCurrentLocale().getLocale() == Locale.CHINA)
|
||||
FileUtils.writeText(optionsFile, "lang:zh_cn\nforceUnicodeFont:true\n");
|
||||
FileUtils.writeText(optionsFile, "lang:zh_CN\nforceUnicodeFont:true\n");
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to generate options.txt", e);
|
||||
}
|
||||
|
||||
@@ -235,10 +235,7 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
File iconFile = getVersionIconFile(id);
|
||||
if (iconFile.exists())
|
||||
return new Image("file:" + iconFile.getAbsolutePath());
|
||||
else if (version.getMainClass() != null &&
|
||||
(LibraryAnalyzer.LAUNCH_WRAPPER_MAIN.equals(version.getMainClass())
|
||||
|| version.getMainClass().startsWith("net.fabricmc")
|
||||
|| LibraryAnalyzer.MOD_LAUNCHER_MAIN.equals(version.getMainClass())))
|
||||
else if (LibraryAnalyzer.isModded(this, version))
|
||||
return newImage("/assets/img/furnace.png");
|
||||
else
|
||||
return newImage("/assets/img/grass.png");
|
||||
@@ -306,7 +303,9 @@ public class HMCLGameRepository extends DefaultGameRepository {
|
||||
.setServerIp(vs.getServerIp())
|
||||
.setWrapper(vs.getWrapper())
|
||||
.setPrecalledCommand(vs.getPreLaunchCommand())
|
||||
.setNoGeneratedJVMArgs(vs.isNoJVMArgs());
|
||||
.setNoGeneratedJVMArgs(vs.isNoJVMArgs())
|
||||
.setNativesDirType(vs.getNativesDirType())
|
||||
.setNativesDir(vs.getNativesDir());
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxy(ProxyManager.getProxy());
|
||||
if (config().hasProxyAuth()) {
|
||||
|
||||
@@ -31,15 +31,17 @@ import java.nio.file.Path;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class HMCLModpackManager {
|
||||
private HMCLModpackManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the manifest in a HMCL modpack.
|
||||
*
|
||||
* @param file a HMCL modpack file.
|
||||
* @param file a HMCL modpack file.
|
||||
* @param encoding encoding of modpack zip file.
|
||||
* @throws IOException if the file is not a valid zip file.
|
||||
* @throws JsonParseException if the manifest.json is missing or malformed.
|
||||
* @return the manifest of HMCL modpack.
|
||||
* @throws IOException if the file is not a valid zip file.
|
||||
* @throws JsonParseException if the manifest.json is missing or malformed.
|
||||
*/
|
||||
public static Modpack readHMCLModpackManifest(Path file, Charset encoding) throws IOException, JsonParseException {
|
||||
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json", encoding);
|
||||
|
||||
@@ -68,6 +68,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
@@ -325,9 +326,19 @@ public final class LauncherHelper {
|
||||
if (acceptableJava.isPresent()) {
|
||||
setting.setJavaVersion(acceptableJava.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), version.getJavaVersion().getMajorVersion()), i18n("message.warning"), MessageType.WARNING, () -> {
|
||||
downloadJava(version.getJavaVersion(), profile).thenAccept(x -> onAccept.run());
|
||||
});
|
||||
Controllers.confirm(i18n("launch.advice.require_newer_java_version", gameVersion.toString(), version.getJavaVersion().getMajorVersion()), i18n("message.warning"), () -> {
|
||||
downloadJava(version.getJavaVersion(), profile)
|
||||
.thenAcceptAsync(x -> {
|
||||
try {
|
||||
Optional<JavaVersion> newAcceptableJava = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() >= version.getJavaVersion().getMajorVersion())
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
newAcceptableJava.ifPresent(setting::setJavaVersion);
|
||||
} catch (InterruptedException e) {
|
||||
LOG.log(Level.SEVERE, "Cannot list javas", e);
|
||||
}
|
||||
}, Platform::runLater).thenAccept(x -> onAccept.run());
|
||||
}, null);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
@@ -477,7 +488,7 @@ public final class LauncherHelper {
|
||||
super.onStop(success, executor);
|
||||
Platform.runLater(() -> {
|
||||
if (!success) {
|
||||
future.completeExceptionally(executor.getException());
|
||||
future.completeExceptionally(Optional.ofNullable(executor.getException()).orElseGet(InterruptedException::new));
|
||||
} else {
|
||||
future.complete(null);
|
||||
}
|
||||
|
||||
@@ -26,11 +26,7 @@ import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.auth.microsoft.MicrosoftAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.*;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.ResourceNotFoundError;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
@@ -39,13 +35,15 @@ import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -263,4 +261,4 @@ public final class TexturesLoader {
|
||||
}
|
||||
return wr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,11 @@ import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class Settings {
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public final class Settings {
|
||||
|
||||
private static Settings instance;
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.google.gson.annotations.JsonAdapter;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.*;
|
||||
import org.jackhuang.hmcl.game.GameDirectoryType;
|
||||
import org.jackhuang.hmcl.game.NativesDirectoryType;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
@@ -43,8 +44,6 @@ import java.util.stream.Collectors;
|
||||
@JsonAdapter(VersionSetting.Serializer.class)
|
||||
public final class VersionSetting implements Cloneable {
|
||||
|
||||
public transient String id;
|
||||
|
||||
private boolean global = false;
|
||||
|
||||
public boolean isGlobal() {
|
||||
@@ -66,7 +65,7 @@ public final class VersionSetting implements Cloneable {
|
||||
* 1. Global settings.
|
||||
* 2. Version settings.
|
||||
* If a version claims that it uses global settings, its version setting will be disabled.
|
||||
*
|
||||
* <p>
|
||||
* Defaults false because if one version uses global first, custom version file will not be generated.
|
||||
*/
|
||||
public boolean isUsesGlobal() {
|
||||
@@ -119,6 +118,39 @@ public final class VersionSetting implements Cloneable {
|
||||
defaultJavaPathProperty.set(defaultJavaPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 0 - .minecraft/versions/<version>/natives/<br/>
|
||||
*/
|
||||
private final ObjectProperty<NativesDirectoryType> nativesDirTypeProperty = new SimpleObjectProperty<>(this, "nativesDirType", NativesDirectoryType.VERSION_FOLDER);
|
||||
|
||||
public ObjectProperty<NativesDirectoryType> nativesDirTypeProperty() {
|
||||
return nativesDirTypeProperty;
|
||||
}
|
||||
|
||||
public NativesDirectoryType getNativesDirType() {
|
||||
return nativesDirTypeProperty.get();
|
||||
}
|
||||
|
||||
public void setNativesDirType(NativesDirectoryType nativesDirType) {
|
||||
nativesDirTypeProperty.set(nativesDirType);
|
||||
}
|
||||
|
||||
// Path to lwjgl natives directory
|
||||
|
||||
private final StringProperty nativesDirProperty = new SimpleStringProperty(this, "nativesDirProperty", "");
|
||||
|
||||
public StringProperty nativesDirProperty() {
|
||||
return nativesDirProperty;
|
||||
}
|
||||
|
||||
public String getNativesDir() {
|
||||
return nativesDirProperty.get();
|
||||
}
|
||||
|
||||
public void setNativesDir(String nativesDir) {
|
||||
nativesDirProperty.set(nativesDir);
|
||||
}
|
||||
|
||||
private final StringProperty javaDirProperty = new SimpleStringProperty(this, "javaDir", "");
|
||||
|
||||
public StringProperty javaDirProperty() {
|
||||
@@ -336,7 +368,7 @@ public final class VersionSetting implements Cloneable {
|
||||
|
||||
/**
|
||||
* The server ip that will be entered after Minecraft successfully loaded ly.
|
||||
*
|
||||
* <p>
|
||||
* Format: ip:port or without port.
|
||||
*/
|
||||
public String getServerIp() {
|
||||
@@ -373,7 +405,7 @@ public final class VersionSetting implements Cloneable {
|
||||
|
||||
/**
|
||||
* The width of Minecraft window, defaults 800.
|
||||
*
|
||||
* <p>
|
||||
* The field saves int value.
|
||||
* String type prevents unexpected value from JsonParseException.
|
||||
* We can only reset this field instead of recreating the whole setting file.
|
||||
@@ -395,7 +427,7 @@ public final class VersionSetting implements Cloneable {
|
||||
|
||||
/**
|
||||
* The height of Minecraft window, defaults 480.
|
||||
*
|
||||
* <p>
|
||||
* The field saves int value.
|
||||
* String type prevents unexpected value from JsonParseException.
|
||||
* We can only reset this field instead of recreating the whole setting file.
|
||||
@@ -526,6 +558,8 @@ public final class VersionSetting implements Cloneable {
|
||||
gameDirProperty.addListener(listener);
|
||||
launcherVisibilityProperty.addListener(listener);
|
||||
defaultJavaPathProperty.addListener(listener);
|
||||
nativesDirProperty.addListener(listener);
|
||||
nativesDirTypeProperty.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -553,6 +587,7 @@ public final class VersionSetting implements Cloneable {
|
||||
versionSetting.setGameDirType(getGameDirType());
|
||||
versionSetting.setGameDir(getGameDir());
|
||||
versionSetting.setLauncherVisibility(getLauncherVisibility());
|
||||
versionSetting.setNativesDir(getNativesDir());
|
||||
return versionSetting;
|
||||
}
|
||||
|
||||
@@ -584,6 +619,8 @@ public final class VersionSetting implements Cloneable {
|
||||
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
|
||||
obj.addProperty("gameDirType", src.getGameDirType().ordinal());
|
||||
obj.addProperty("defaultJavaPath", src.getDefaultJavaPath());
|
||||
obj.addProperty("nativesDir", src.getNativesDir());
|
||||
obj.addProperty("nativesDirType", src.getNativesDirType().ordinal());
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -613,6 +650,7 @@ public final class VersionSetting implements Cloneable {
|
||||
vs.setJava(Optional.ofNullable(obj.get("java")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setWrapper(Optional.ofNullable(obj.get("wrapper")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setGameDir(Optional.ofNullable(obj.get("gameDir")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setNativesDir(Optional.ofNullable(obj.get("nativesDir")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setFullscreen(Optional.ofNullable(obj.get("fullscreen")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNoJVMArgs(Optional.ofNullable(obj.get("noJVMArgs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotCheckGame(Optional.ofNullable(obj.get("notCheckGame")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
@@ -621,6 +659,7 @@ public final class VersionSetting implements Cloneable {
|
||||
vs.setLauncherVisibility(LauncherVisibility.values()[Optional.ofNullable(obj.get("launcherVisibility")).map(JsonElement::getAsInt).orElse(1)]);
|
||||
vs.setGameDirType(GameDirectoryType.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(0)]);
|
||||
vs.setDefaultJavaPath(Optional.ofNullable(obj.get("defaultJavaPath")).map(JsonElement::getAsString).orElse(null));
|
||||
vs.setNativesDirType(NativesDirectoryType.values()[Optional.ofNullable(obj.get("nativesDirType")).map(JsonElement::getAsInt).orElse(0)]);
|
||||
|
||||
return vs;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ package org.jackhuang.hmcl.ui;
|
||||
import java.awt.*;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class AwtUtils {
|
||||
public final class AwtUtils {
|
||||
private AwtUtils() {
|
||||
}
|
||||
|
||||
public static void setAppleIcon(Image image) {
|
||||
try {
|
||||
Class<?> taskbarClass = Class.forName("java.awt.TaskBar");
|
||||
|
||||
@@ -66,6 +66,9 @@ public final class Controllers {
|
||||
private static RootPage rootPage;
|
||||
private static DecoratorController decorator;
|
||||
|
||||
private Controllers() {
|
||||
}
|
||||
|
||||
public static Scene getScene() {
|
||||
return scene;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.runInFX;
|
||||
|
||||
public final class DialogController {
|
||||
private DialogController() {
|
||||
}
|
||||
|
||||
public static AuthInfo logIn(Account account) throws CancellationException, AuthenticationException, InterruptedException {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
@@ -38,8 +40,8 @@ public final class DialogController {
|
||||
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
|
||||
runInFX(() -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, it -> {
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
}, latch::countDown);
|
||||
Controllers.dialog(pane);
|
||||
});
|
||||
|
||||
@@ -430,6 +430,7 @@ public final class FXUtils {
|
||||
* You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.
|
||||
* @param comboBox the combo box being bound with the property which can be inferred by {@code bindEnum}.
|
||||
* @see #bindEnum(JFXComboBox, Property)
|
||||
* @deprecated Use {@link ExtendedProperties#selectedItemPropertyFor(ComboBox)}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
|
||||
@@ -102,11 +102,11 @@ public class InstallerItem extends Control {
|
||||
}
|
||||
|
||||
public static class InstallerItemGroup {
|
||||
public InstallerItem game = new InstallerItem(MINECRAFT);
|
||||
public InstallerItem fabric = new InstallerItem(FABRIC);
|
||||
public InstallerItem forge = new InstallerItem(FORGE);
|
||||
public InstallerItem liteLoader = new InstallerItem(LITELOADER);
|
||||
public InstallerItem optiFine = new InstallerItem(OPTIFINE);
|
||||
public final InstallerItem game = new InstallerItem(MINECRAFT);
|
||||
public final InstallerItem fabric = new InstallerItem(FABRIC);
|
||||
public final InstallerItem forge = new InstallerItem(FORGE);
|
||||
public final InstallerItem liteLoader = new InstallerItem(LITELOADER);
|
||||
public final InstallerItem optiFine = new InstallerItem(OPTIFINE);
|
||||
|
||||
public InstallerItemGroup() {
|
||||
forge.incompatibleLibraryName.bind(Bindings.createStringBinding(() -> {
|
||||
@@ -133,7 +133,7 @@ public class InstallerItem extends Control {
|
||||
}
|
||||
|
||||
public InstallerItem[] getLibraries() {
|
||||
return new InstallerItem[]{game, fabric, forge, liteLoader, optiFine};
|
||||
return new InstallerItem[]{game, fabric, forge, liteLoader, optiFine};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -87,8 +87,8 @@ public class AddAccountPane extends StackPane {
|
||||
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
|
||||
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
|
||||
Bindings.bindContent(cboServers.getItems(), config().getAuthlibInjectorServers());
|
||||
cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer));
|
||||
selectDefaultServer();
|
||||
cboServers.getItems().addListener(onInvalidating(this::resetServerSelection));
|
||||
resetServerSelection();
|
||||
|
||||
cboType.getItems().setAll(Accounts.FACTORIES);
|
||||
cboType.setConverter(stringConverter(Accounts::getLocalizedLoginTypeName));
|
||||
@@ -174,12 +174,13 @@ public class AddAccountPane extends StackPane {
|
||||
return unmodifiableList(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the first server if no server is selected.
|
||||
*/
|
||||
private void selectDefaultServer() {
|
||||
if (!cboServers.getItems().isEmpty() && cboServers.getSelectionModel().isEmpty()) {
|
||||
cboServers.getSelectionModel().select(0);
|
||||
private void resetServerSelection() {
|
||||
if (!cboServers.getItems().isEmpty()) {
|
||||
Platform.runLater(() -> {
|
||||
// the selection will not be updated as expected
|
||||
// if we call it immediately
|
||||
cboServers.getSelectionModel().selectFirst();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -129,7 +129,7 @@ public class AddAuthlibInjectorServerPane extends StackPane implements DialogAwa
|
||||
@FXML
|
||||
private void onAddFinish() {
|
||||
if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) {
|
||||
config().getAuthlibInjectorServers().add(serverBeingAdded);
|
||||
config().getAuthlibInjectorServers().add(0, serverBeingAdded);
|
||||
}
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ import javafx.util.Duration;
|
||||
|
||||
public interface AnimationHandler {
|
||||
Duration getDuration();
|
||||
|
||||
Pane getCurrentRoot();
|
||||
|
||||
Node getPreviousNode();
|
||||
|
||||
Node getCurrentNode();
|
||||
}
|
||||
@@ -99,9 +99,10 @@ public class FloatScrollBarSkin implements Skin<ScrollBar> {
|
||||
// we're getting dragged without getting a mouse press
|
||||
dragStart = thumb.localToParent(me.getX(), me.getY());
|
||||
}
|
||||
double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY(): cur.getX() - dragStart.getX();
|
||||
double dragPos = getSkinnable().getOrientation() == Orientation.VERTICAL ? cur.getY() - dragStart.getY() : cur.getX() - dragStart.getX();
|
||||
double position = preDragThumbPos + dragPos / (trackLength() - thumbLength());
|
||||
if (!getSkinnable().isFocused() && getSkinnable().isFocusTraversable()) getSkinnable().requestFocus();
|
||||
if (!getSkinnable().isFocused() && getSkinnable().isFocusTraversable())
|
||||
getSkinnable().requestFocus();
|
||||
double newValue = (position * (getSkinnable().getMax() - getSkinnable().getMin())) + getSkinnable().getMin();
|
||||
if (!Double.isNaN(newValue)) {
|
||||
getSkinnable().setValue(Lang.clamp(getSkinnable().getMin(), newValue, getSkinnable().getMax()));
|
||||
|
||||
@@ -42,11 +42,11 @@ public class SpinnerPane extends Control {
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading");
|
||||
|
||||
public void showSpinner() {
|
||||
setLoading(true);
|
||||
setLoading(true);
|
||||
}
|
||||
|
||||
public void hideSpinner() {
|
||||
setLoading(false);
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
public Node getContent() {
|
||||
@@ -62,15 +62,15 @@ public class SpinnerPane extends Control {
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return loading.get();
|
||||
return loading.get();
|
||||
}
|
||||
|
||||
|
||||
public BooleanProperty loadingProperty() {
|
||||
return loading;
|
||||
return loading;
|
||||
}
|
||||
|
||||
|
||||
public void setLoading(boolean loading) {
|
||||
this.loading.set(loading);
|
||||
this.loading.set(loading);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -174,7 +174,7 @@ public interface TabControl {
|
||||
}
|
||||
}
|
||||
|
||||
public static class Tab {
|
||||
class Tab {
|
||||
private final StringProperty id = new SimpleStringProperty(this, "id");
|
||||
private final StringProperty text = new SimpleStringProperty(this, "text");
|
||||
private final ReadOnlyBooleanWrapper selected = new ReadOnlyBooleanWrapper(this, "selected");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -18,26 +18,18 @@
|
||||
package org.jackhuang.hmcl.ui.decorator;
|
||||
|
||||
import com.jfoenix.controls.JFXDialog;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
|
||||
import org.jackhuang.hmcl.setting.Config;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
@@ -55,22 +47,22 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.newImage;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.io.FileUtils.getExtension;
|
||||
|
||||
public class DecoratorController {
|
||||
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = DecoratorController.class.getName() + ".dialog.closeListener";
|
||||
|
||||
private final Decorator decorator;
|
||||
private final ImageView welcomeView;
|
||||
private final Navigator navigator;
|
||||
|
||||
private JFXDialog dialog;
|
||||
@@ -89,27 +81,6 @@ public class DecoratorController {
|
||||
decorator.onBackNavButtonActionProperty().set(e -> back());
|
||||
decorator.onRefreshNavButtonActionProperty().set(e -> refresh());
|
||||
|
||||
welcomeView = new ImageView();
|
||||
welcomeView.setImage(newImage("/assets/img/welcome.png"));
|
||||
welcomeView.setCursor(Cursor.HAND);
|
||||
FXUtils.limitSize(welcomeView, 796, 517);
|
||||
welcomeView.setOnMouseClicked(e -> {
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(
|
||||
new KeyFrame(Duration.ZERO, new KeyValue(welcomeView.opacityProperty(), 1.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(new Duration(300), new KeyValue(welcomeView.opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(new Duration(300), e2 -> decorator.getContainer().remove(welcomeView))
|
||||
);
|
||||
nowAnimation.play();
|
||||
});
|
||||
|
||||
if (switchedToNewUI()) {
|
||||
if (config().getLocalization().getLocale() == Locale.CHINA) {
|
||||
// currently, user guide is only available in Chinese
|
||||
decorator.getContainer().setAll(welcomeView);
|
||||
}
|
||||
}
|
||||
|
||||
setupBackground();
|
||||
|
||||
setupAuthlibInjectorDnD();
|
||||
@@ -119,17 +90,6 @@ public class DecoratorController {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the user is seeing the current version of UI for the first time.
|
||||
*/
|
||||
private boolean switchedToNewUI() {
|
||||
if (config().getUiVersion() < Config.CURRENT_UI_VERSION) {
|
||||
config().setUiVersion(Config.CURRENT_UI_VERSION);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==== Background ====
|
||||
|
||||
private void setupBackground() {
|
||||
@@ -174,10 +134,10 @@ public class DecoratorController {
|
||||
List<Path> candidates;
|
||||
try (Stream<Path> stream = Files.list(imageDir)) {
|
||||
candidates = stream
|
||||
.filter(Files::isRegularFile)
|
||||
.filter(Files::isReadable)
|
||||
.filter(it -> {
|
||||
String filename = it.getFileName().toString();
|
||||
return filename.endsWith(".png") || filename.endsWith(".jpg");
|
||||
String ext = getExtension(it).toLowerCase();
|
||||
return ext.equals("png") || ext.equals("jpg");
|
||||
})
|
||||
.collect(toList());
|
||||
} catch (IOException e) {
|
||||
@@ -199,13 +159,23 @@ public class DecoratorController {
|
||||
}
|
||||
|
||||
private Optional<Image> tryLoadImage(Path path) {
|
||||
if (Files.isRegularFile(path)) {
|
||||
try {
|
||||
return Optional.of(new Image(path.toAbsolutePath().toUri().toString()));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
if (!Files.isReadable(path))
|
||||
return Optional.empty();
|
||||
|
||||
Image img;
|
||||
try {
|
||||
img = new Image(path.toAbsolutePath().toUri().toString());
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.log(WARNING, "Couldn't load background image", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.empty();
|
||||
|
||||
if (img.getException() != null) {
|
||||
LOG.log(WARNING, "Couldn't load background image", img.getException());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(img);
|
||||
}
|
||||
|
||||
// ==== Navigation ====
|
||||
|
||||
@@ -25,7 +25,6 @@ import org.jackhuang.hmcl.ui.construct.PageCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.wizard.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class DecoratorWizardDisplayer extends DecoratorTransitionPage implements WizardDisplayer {
|
||||
|
||||
@@ -87,21 +87,24 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
|
||||
if (file.isDirectory()) {
|
||||
File[] files = file.listFiles();
|
||||
if (files != null)
|
||||
if (files != null) {
|
||||
for (File it : files) {
|
||||
CheckBoxTreeItem<String> subNode = getTreeItem(it, basePath + "/" + it.getName());
|
||||
if (subNode != null) {
|
||||
node.setSelected(subNode.isSelected() || node.isSelected());
|
||||
if (!subNode.isSelected())
|
||||
node.setIndeterminate(true);
|
||||
node.getChildren().add(subNode);
|
||||
if (subNode != null) {
|
||||
node.setSelected(subNode.isSelected() || node.isSelected());
|
||||
if (!subNode.isSelected()) {
|
||||
node.setIndeterminate(true);
|
||||
}
|
||||
node.getChildren().add(subNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!node.isSelected()) node.setIndeterminate(false);
|
||||
|
||||
// Empty folder need not to be displayed.
|
||||
if (node.getChildren().isEmpty())
|
||||
if (node.getChildren().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
HBox graphic = new HBox();
|
||||
@@ -126,12 +129,13 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
|
||||
|
||||
private void getFilesNeeded(CheckBoxTreeItem<String> node, String basePath, List<String> list) {
|
||||
if (node == null) return;
|
||||
if (node.isSelected()) {
|
||||
if (node.isSelected() || node.isIndeterminate()) {
|
||||
if (basePath.length() > "minecraft/".length())
|
||||
list.add(StringUtils.substringAfter(basePath, "minecraft/"));
|
||||
for (TreeItem<String> child : node.getChildren()) {
|
||||
if (child instanceof CheckBoxTreeItem)
|
||||
if (child instanceof CheckBoxTreeItem) {
|
||||
getFilesNeeded(((CheckBoxTreeItem<String>) child), basePath + "/" + child.getValue(), list);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,13 +78,13 @@ public final class ModpackInfoPage extends Control implements WizardPage {
|
||||
private final SimpleStringProperty url = new SimpleStringProperty("");
|
||||
private final SimpleBooleanProperty forceUpdate = new SimpleBooleanProperty();
|
||||
private final SimpleBooleanProperty packWithLauncher = new SimpleBooleanProperty();
|
||||
private final SimpleStringProperty fileApi = new SimpleStringProperty();
|
||||
private final SimpleStringProperty fileApi = new SimpleStringProperty("");
|
||||
private final SimpleIntegerProperty minMemory = new SimpleIntegerProperty(0);
|
||||
private final SimpleStringProperty authlibInjectorServer = new SimpleStringProperty();
|
||||
private final SimpleStringProperty launchArguments = new SimpleStringProperty("");
|
||||
private final SimpleStringProperty javaArguments = new SimpleStringProperty("");
|
||||
private final ObjectProperty<EventHandler<? super MouseEvent>> next = new SimpleObjectProperty<>();
|
||||
private final SimpleStringProperty mcbbsThreadId = new SimpleStringProperty();
|
||||
private final SimpleStringProperty mcbbsThreadId = new SimpleStringProperty("");
|
||||
|
||||
public ModpackInfoPage(WizardController controller, HMCLGameRepository gameRepository, String version) {
|
||||
this.controller = controller;
|
||||
|
||||
@@ -535,8 +535,12 @@ public abstract class SettingsView extends StackPane {
|
||||
}
|
||||
|
||||
protected abstract void onUpdate();
|
||||
|
||||
protected abstract void onHelp();
|
||||
|
||||
protected abstract void onExportLogs();
|
||||
|
||||
protected abstract void onSponsor();
|
||||
|
||||
protected abstract void clearCacheDirectory();
|
||||
}
|
||||
@@ -68,6 +68,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
|
||||
{
|
||||
setMessage(i18n("profile.already_exists"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void eval() {
|
||||
JFXTextField control = (JFXTextField) this.getSrcControl();
|
||||
|
||||
@@ -35,7 +35,11 @@ import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jackhuang.hmcl.game.GameDirectoryType;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.game.NativesDirectoryType;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
@@ -76,6 +80,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
@FXML private JFXTextField txtWidth;
|
||||
@FXML private JFXTextField txtHeight;
|
||||
@FXML private JFXTextField txtMaxMemory;
|
||||
@FXML private JFXTextField txtNativesPath;
|
||||
@FXML private JFXTextField txtJVMArgs;
|
||||
@FXML private JFXTextField txtGameArgs;
|
||||
@FXML private JFXTextField txtMetaspace;
|
||||
@@ -88,11 +93,13 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
@FXML private JFXComboBox<LauncherVisibility> cboLauncherVisibility;
|
||||
@FXML private JFXCheckBox chkFullscreen;
|
||||
@FXML private Label lblPhysicalMemory;
|
||||
@FXML private Label lblCustomizedNativesPath;
|
||||
@FXML private JFXToggleButton chkNoJVMArgs;
|
||||
@FXML private JFXToggleButton chkNoGameCheck;
|
||||
@FXML private JFXToggleButton chkNoJVMCheck;
|
||||
@FXML private MultiFileItem<JavaVersion> javaItem;
|
||||
@FXML private MultiFileItem<GameDirectoryType> gameDirItem;
|
||||
@FXML private MultiFileItem<NativesDirectoryType> nativesDirItem;
|
||||
@FXML private JFXToggleButton chkShowLogs;
|
||||
@FXML private ImagePickerItem iconPickerItem;
|
||||
@FXML private JFXCheckBox chkEnableSpecificSettings;
|
||||
@@ -138,6 +145,11 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
gameDirItem.createChildren(i18n("settings.advanced.game_dir.independent"), GameDirectoryType.VERSION_FOLDER)
|
||||
));
|
||||
|
||||
nativesDirItem.setCustomUserData(NativesDirectoryType.CUSTOM);
|
||||
nativesDirItem.loadChildren(Arrays.asList(
|
||||
nativesDirItem.createChildren(i18n("settings.advanced.natives_directory.default"), NativesDirectoryType.VERSION_FOLDER)
|
||||
));
|
||||
|
||||
chkEnableSpecificSettings.selectedProperty().addListener((a, b, newValue) -> {
|
||||
if (versionId == null) return;
|
||||
|
||||
@@ -179,6 +191,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
FXUtils.unbindInt(txtMaxMemory, lastVersionSetting.maxMemoryProperty());
|
||||
FXUtils.unbindString(javaItem.getTxtCustom(), lastVersionSetting.javaDirProperty());
|
||||
FXUtils.unbindString(gameDirItem.getTxtCustom(), lastVersionSetting.gameDirProperty());
|
||||
FXUtils.unbindString(nativesDirItem.getTxtCustom(), lastVersionSetting.nativesDirProperty());
|
||||
FXUtils.unbindString(txtJVMArgs, lastVersionSetting.javaArgsProperty());
|
||||
FXUtils.unbindString(txtGameArgs, lastVersionSetting.minecraftArgsProperty());
|
||||
FXUtils.unbindString(txtMetaspace, lastVersionSetting.permSizeProperty());
|
||||
@@ -198,6 +211,9 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
|
||||
gameDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().unbind();
|
||||
|
||||
nativesDirItem.selectedDataProperty().unbindBidirectional(lastVersionSetting.nativesDirTypeProperty());
|
||||
nativesDirItem.subtitleProperty().unbind();
|
||||
}
|
||||
|
||||
// unbind data fields
|
||||
@@ -209,6 +225,7 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
FXUtils.bindInt(txtMaxMemory, versionSetting.maxMemoryProperty());
|
||||
FXUtils.bindString(javaItem.getTxtCustom(), versionSetting.javaDirProperty());
|
||||
FXUtils.bindString(gameDirItem.getTxtCustom(), versionSetting.gameDirProperty());
|
||||
FXUtils.bindString(nativesDirItem.getTxtCustom(), versionSetting.nativesDirProperty());
|
||||
FXUtils.bindString(txtJVMArgs, versionSetting.javaArgsProperty());
|
||||
FXUtils.bindString(txtGameArgs, versionSetting.minecraftArgsProperty());
|
||||
FXUtils.bindString(txtMetaspace, versionSetting.permSizeProperty());
|
||||
@@ -240,6 +257,10 @@ public final class VersionSettingsPage extends StackPane implements DecoratorPag
|
||||
gameDirItem.selectedDataProperty().bindBidirectional(versionSetting.gameDirTypeProperty());
|
||||
gameDirItem.subtitleProperty().bind(Bindings.createStringBinding(() -> Paths.get(profile.getRepository().getRunDirectory(versionId).getAbsolutePath()).normalize().toString(),
|
||||
versionSetting.gameDirProperty(), versionSetting.gameDirTypeProperty()));
|
||||
|
||||
nativesDirItem.selectedDataProperty().bindBidirectional(versionSetting.nativesDirTypeProperty());
|
||||
nativesDirItem.subtitleProperty().bind(Bindings.createStringBinding(() -> Paths.get(profile.getRepository().getRunDirectory(versionId).getAbsolutePath() + "/natives").normalize().toString(),
|
||||
versionSetting.nativesDirProperty(), versionSetting.nativesDirTypeProperty()));
|
||||
|
||||
lastVersionSetting = versionSetting;
|
||||
|
||||
|
||||
@@ -46,7 +46,9 @@ import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class Versions {
|
||||
public final class Versions {
|
||||
private Versions() {
|
||||
}
|
||||
|
||||
public static void deleteVersion(Profile profile, String version) {
|
||||
boolean isIndependent = profile.getVersionSetting(version).getGameDirType() == GameDirectoryType.VERSION_FOLDER;
|
||||
@@ -156,7 +158,7 @@ public class Versions {
|
||||
Controllers.getRootPage().checkAccount();
|
||||
else if (id == null || !profile.getRepository().isLoaded() || !profile.getRepository().hasVersion(id))
|
||||
Controllers.dialog(i18n("version.empty.launch"), i18n("launch.failed"), MessageDialogPane.MessageType.ERROR, () -> {
|
||||
Controllers.getRootPage().getSelectionModel().select(Controllers.getRootPage().getGameTab());
|
||||
Controllers.getRootPage().getSelectionModel().select(Controllers.getRootPage().getGameTab());
|
||||
});
|
||||
else
|
||||
return true;
|
||||
|
||||
@@ -50,7 +50,6 @@ public class WorldExportPage extends WizardSinglePage {
|
||||
return new WorldExportPageSkin(this);
|
||||
}
|
||||
|
||||
|
||||
public StringProperty pathProperty() {
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -94,5 +94,4 @@ public class WorldExportPageSkin extends SkinBase<WorldExportPage> {
|
||||
getChildren().setAll(container);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -22,11 +22,17 @@ import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
|
||||
public interface Navigation {
|
||||
|
||||
void onStart();
|
||||
|
||||
void onNext();
|
||||
|
||||
void onPrev(boolean cleanUp);
|
||||
|
||||
boolean canPrev();
|
||||
|
||||
void onFinish();
|
||||
|
||||
void onEnd();
|
||||
|
||||
void onCancel();
|
||||
|
||||
enum NavigationDirection {
|
||||
|
||||
@@ -23,9 +23,13 @@ import java.util.Map;
|
||||
|
||||
public interface WizardProvider {
|
||||
void start(Map<String, Object> settings);
|
||||
|
||||
Object finish(Map<String, Object> settings);
|
||||
|
||||
Node createPage(WizardController controller, int step, Map<String, Object> settings);
|
||||
|
||||
boolean cancel();
|
||||
|
||||
default boolean cancelIfCannotGoBack() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.upgrade;
|
||||
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
@@ -26,7 +27,6 @@ import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Pack200;
|
||||
|
||||
class HMCLDownloadTask extends FileDownloadTask {
|
||||
|
||||
@@ -52,7 +52,7 @@ class HMCLDownloadTask extends FileDownloadTask {
|
||||
byte[] raw = Files.readAllBytes(target);
|
||||
try (InputStream in = new XZInputStream(new ByteArrayInputStream(raw));
|
||||
JarOutputStream out = new JarOutputStream(Files.newOutputStream(target))) {
|
||||
Pack200.newUnpacker().unpack(in, out);
|
||||
Pack200Utils.unpack(in, out);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
@@ -38,7 +38,7 @@ public class RemoteVersion {
|
||||
String jarHash = Optional.ofNullable(response.get("jarsha1")).map(JsonElement::getAsString).orElse(null);
|
||||
String packXZUrl = Optional.ofNullable(response.get("packxz")).map(JsonElement::getAsString).orElse(null);
|
||||
String packXZHash = Optional.ofNullable(response.get("packxzsha1")).map(JsonElement::getAsString).orElse(null);
|
||||
if (SystemUtils.JRE_CAPABILITY_PACK200 && packXZUrl != null && packXZHash != null) {
|
||||
if (Pack200Utils.isSupported() && packXZUrl != null && packXZHash != null) {
|
||||
return new RemoteVersion(version, packXZUrl, Type.PACK_XZ, new IntegrityCheck("SHA-1", packXZHash));
|
||||
} else if (jarUrl != null && jarHash != null) {
|
||||
return new RemoteVersion(version, jarUrl, Type.JAR, new IntegrityCheck("SHA-1", jarHash));
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.nio.file.Path;
|
||||
@@ -8,7 +25,10 @@ import java.util.Set;
|
||||
*
|
||||
* @author ZekerZhayard
|
||||
*/
|
||||
public class JavaFXPatcher {
|
||||
public final class JavaFXPatcher {
|
||||
private JavaFXPatcher() {
|
||||
}
|
||||
|
||||
public static void patch(Set<String> modules, Path... jarPaths) {
|
||||
// Nothing to do with Java 8
|
||||
}
|
||||
|
||||
@@ -1,55 +1,144 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2017-2021 Matthew Coley
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.ForkJoinTask;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.lang.Class.forName;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
import static org.jackhuang.hmcl.Metadata.HMCL_DIRECTORY;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.platform.JavaVersion.CURRENT_JAVA;
|
||||
|
||||
/**
|
||||
* Utility for patching self when missing dependencies.
|
||||
* Copy from https://github.com/Col-E/Recaf/blob/master/src/main/java/me/coley/recaf/util/self/SelfDependencyPatcher.java
|
||||
*
|
||||
* @author Matt
|
||||
*/
|
||||
public class SelfDependencyPatcher {
|
||||
private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies");
|
||||
private static final String DEFAULT_JFX_VERSION = "16";
|
||||
private static final Map<String, String> JFX_DEPENDENCIES = new HashMap<>();
|
||||
import java.awt.Dialog;
|
||||
import java.awt.GridBagConstraints;
|
||||
import java.awt.GridBagLayout;
|
||||
import java.awt.Insets;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
static {
|
||||
addJfxDependency("base");
|
||||
addJfxDependency("controls");
|
||||
addJfxDependency("fxml");
|
||||
addJfxDependency("graphics");
|
||||
addJfxDependency("media");
|
||||
// Fix #874: Remove the dependency on javafx.swing
|
||||
// addJfxDependency("swing");
|
||||
addJfxDependency("web");
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JDialog;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JProgressBar;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.ChecksumMismatchException;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.platform.Architecture;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
// From: https://github.com/Col-E/Recaf/blob/7378b397cee664ae81b7963b0355ef8ff013c3a7/src/main/java/me/coley/recaf/util/self/SelfDependencyPatcher.java
|
||||
public final class SelfDependencyPatcher {
|
||||
private SelfDependencyPatcher() {
|
||||
}
|
||||
|
||||
private static void addJfxDependency(String name) {
|
||||
JFX_DEPENDENCIES.put("javafx." + name, jfxUrl(name));
|
||||
static class DependencyDescriptor {
|
||||
|
||||
private static final String REPOSITORY_URL = "https://maven.aliyun.com/repository/central/";
|
||||
private static final Path DEPENDENCIES_DIR_PATH = HMCL_DIRECTORY.resolve("dependencies");
|
||||
|
||||
private static String currentArchClassifier() {
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case LINUX:
|
||||
return "linux";
|
||||
case OSX:
|
||||
return "mac";
|
||||
default:
|
||||
return "win";
|
||||
}
|
||||
}
|
||||
|
||||
public String module;
|
||||
public String groupId;
|
||||
public String artifactId;
|
||||
public String version;
|
||||
public Map<String, String> sha1;
|
||||
|
||||
public String filename() {
|
||||
return artifactId + "-" + version + "-" + currentArchClassifier() + ".jar";
|
||||
}
|
||||
|
||||
public String sha1() {
|
||||
return sha1.get(currentArchClassifier());
|
||||
}
|
||||
|
||||
public String url() {
|
||||
return REPOSITORY_URL + groupId.replace('.', '/') + "/" + artifactId + "/" + version + "/" + filename();
|
||||
}
|
||||
|
||||
public Path localPath() {
|
||||
return DEPENDENCIES_DIR_PATH.resolve(filename());
|
||||
}
|
||||
}
|
||||
|
||||
private static final String DEPENDENCIES_LIST_FILE = "/assets/openjfx-dependencies.json";
|
||||
|
||||
private static List<DependencyDescriptor> readDependencies() {
|
||||
String content;
|
||||
try (InputStream in = SelfDependencyPatcher.class.getResourceAsStream(DEPENDENCIES_LIST_FILE)) {
|
||||
content = IOUtils.readFullyAsString(in, UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
return new Gson().fromJson(content, TypeToken.getParameterized(List.class, DependencyDescriptor.class).getType());
|
||||
}
|
||||
|
||||
private static final List<DependencyDescriptor> JFX_DEPENDENCIES = readDependencies();
|
||||
|
||||
/**
|
||||
* Patch in any missing dependencies, if any.
|
||||
*/
|
||||
@@ -81,17 +170,17 @@ public class SelfDependencyPatcher {
|
||||
|
||||
// Otherwise we're free to download in Java 11+
|
||||
LOG.info("Missing JavaFX dependencies, attempting to patch in missing classes");
|
||||
// Check if dependencies need to be downloaded
|
||||
if (!hasCachedDependencies()) {
|
||||
LOG.info(" - No local cache, downloading dependencies...");
|
||||
|
||||
// Download missing dependencies
|
||||
List<DependencyDescriptor> missingDependencies = checkMissingDependencies();
|
||||
if (!missingDependencies.isEmpty()) {
|
||||
try {
|
||||
fetchDependencies();
|
||||
} catch (Exception ex) {
|
||||
throw new PatchException("Failed to download dependencies", ex);
|
||||
fetchDependencies(missingDependencies);
|
||||
} catch (IOException e) {
|
||||
throw new PatchException("Failed to download dependencies", e);
|
||||
}
|
||||
} else {
|
||||
LOG.info(" - Local cache found!");
|
||||
}
|
||||
|
||||
// Add the dependencies
|
||||
try {
|
||||
loadFromCache();
|
||||
@@ -106,17 +195,21 @@ public class SelfDependencyPatcher {
|
||||
/**
|
||||
* Inject them into the current classpath.
|
||||
*
|
||||
* @throws IOException
|
||||
* When the locally cached dependency urls cannot be resolved.
|
||||
* @throws ReflectiveOperationException
|
||||
* When the call to add these urls to the system classpath failed.
|
||||
* @throws IOException When the locally cached dependency urls cannot be resolved.
|
||||
* @throws ReflectiveOperationException When the call to add these urls to the system classpath failed.
|
||||
*/
|
||||
private static void loadFromCache() throws IOException, ReflectiveOperationException {
|
||||
LOG.info(" - Loading dependencies...");
|
||||
List<Path> jarPaths = new ArrayList<>();
|
||||
List<String> jfxDepFile = JFX_DEPENDENCIES.values().stream().map(SelfDependencyPatcher::getFileName).collect(Collectors.toList());
|
||||
Files.walk(DEPENDENCIES_DIR_PATH).filter(p -> jfxDepFile.contains(p.getFileName().toString())).forEach(jarPaths::add);
|
||||
JavaFXPatcher.patch(JFX_DEPENDENCIES.keySet(), jarPaths.toArray(new Path[0]));
|
||||
|
||||
Set<String> modules = JFX_DEPENDENCIES.stream()
|
||||
.map(it -> it.module)
|
||||
.collect(toSet());
|
||||
|
||||
Path[] jars = JFX_DEPENDENCIES.stream()
|
||||
.map(it -> it.localPath())
|
||||
.toArray(Path[]::new);
|
||||
|
||||
JavaFXPatcher.patch(modules, jars);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,106 +217,57 @@ public class SelfDependencyPatcher {
|
||||
*
|
||||
* @throws IOException When the files cannot be fetched or saved.
|
||||
*/
|
||||
private static void fetchDependencies() throws Exception {
|
||||
// Get dir to store dependencies in
|
||||
Path dependenciesDir = DEPENDENCIES_DIR_PATH;
|
||||
if (!Files.isDirectory(dependenciesDir)) {
|
||||
Files.createDirectories(dependenciesDir);
|
||||
}
|
||||
private static void fetchDependencies(List<DependencyDescriptor> dependencies) throws IOException {
|
||||
ProgressFrame dialog = new ProgressFrame(i18n("download.javafx"));
|
||||
|
||||
ForkJoinTask<Void> task = ForkJoinPool.commonPool().submit(() -> {
|
||||
// Download each dependency
|
||||
Collection<String> dependencies = JFX_DEPENDENCIES.values();
|
||||
int i = 1;
|
||||
for (String dependencyUrlPath : dependencies) {
|
||||
URL depURL = new URL(dependencyUrlPath);
|
||||
Path dependencyFilePath = DEPENDENCIES_DIR_PATH.resolve(getFileName(dependencyUrlPath));
|
||||
int finalI = i;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dialog.setStatus(dependencyUrlPath);
|
||||
dialog.setProgress(finalI, dependencies.size());
|
||||
});
|
||||
Files.copy(depURL.openStream(), dependencyFilePath, StandardCopyOption.REPLACE_EXISTING);
|
||||
checksum(dependencyFilePath, dependencyUrlPath);
|
||||
i++;
|
||||
}
|
||||
|
||||
dialog.dispose();
|
||||
return null;
|
||||
});
|
||||
|
||||
dialog.setVisible(true);
|
||||
|
||||
task.get();
|
||||
int progress = 0;
|
||||
for (DependencyDescriptor dependency : dependencies) {
|
||||
int currentProgress = ++progress;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
dialog.setStatus(dependency.url());
|
||||
dialog.setProgress(currentProgress, dependencies.size());
|
||||
});
|
||||
|
||||
LOG.info("Downloading " + dependency.url());
|
||||
Files.createDirectories(dependency.localPath().getParent());
|
||||
Files.copy(new URL(dependency.url()).openStream(), dependency.localPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
verifyChecksum(dependency);
|
||||
}
|
||||
|
||||
dialog.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} when the dependencies directory has files in it.
|
||||
*/
|
||||
private static boolean hasCachedDependencies() {
|
||||
if (!Files.isDirectory(DEPENDENCIES_DIR_PATH))
|
||||
return false;
|
||||
private static List<DependencyDescriptor> checkMissingDependencies() {
|
||||
List<DependencyDescriptor> missing = new ArrayList<>();
|
||||
|
||||
for (String url : JFX_DEPENDENCIES.values()) {
|
||||
Path dependencyFilePath = DEPENDENCIES_DIR_PATH.resolve(getFileName(url));
|
||||
if (!Files.exists(dependencyFilePath))
|
||||
return false;
|
||||
for (DependencyDescriptor dependency : JFX_DEPENDENCIES) {
|
||||
if (!Files.exists(dependency.localPath())) {
|
||||
missing.add(dependency);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
checksum(dependencyFilePath, url);
|
||||
verifyChecksum(dependency);
|
||||
} catch (ChecksumMismatchException e) {
|
||||
return false;
|
||||
} catch (IOException ignored) {
|
||||
// Ignore other situations
|
||||
LOG.warning("Corrupted dependency " + dependency.filename() + ": " + e.getMessage());
|
||||
missing.add(dependency);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
return missing;
|
||||
}
|
||||
|
||||
private static void checksum(Path dependencyFilePath, String dependencyUrlPath) throws IOException {
|
||||
String expectedHash = NetworkUtils.doGet(NetworkUtils.toURL(dependencyUrlPath + ".sha1"));
|
||||
String actualHash = Hex.encodeHex(DigestUtils.digest("SHA-1", dependencyFilePath));
|
||||
private static void verifyChecksum(DependencyDescriptor dependency) throws IOException, ChecksumMismatchException {
|
||||
String expectedHash = dependency.sha1();
|
||||
String actualHash = Hex.encodeHex(DigestUtils.digest("SHA-1", dependency.localPath()));
|
||||
if (!expectedHash.equalsIgnoreCase(actualHash)) {
|
||||
throw new ChecksumMismatchException("SHA-1", expectedHash, actualHash);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param url Full url path.
|
||||
* @return Name of file at url.
|
||||
*/
|
||||
private static String getFileName(String url) {
|
||||
return url.substring(url.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param component Name of the component.
|
||||
* @return Formed URL for the component.
|
||||
*/
|
||||
private static String jfxUrl(String component) {
|
||||
return jfxUrl(component, DEFAULT_JFX_VERSION);
|
||||
}
|
||||
|
||||
private static String jfxUrl(String component, String version) {
|
||||
// https://repo1.maven.org/maven2/org/openjfx/javafx-%s/%s/javafx-%s-%s-%s.jar
|
||||
return String.format("https://maven.aliyun.com/repository/central/org/openjfx/javafx-%s/%s/javafx-%s-%s-%s.jar",
|
||||
component, version, component, version, getMvnName());
|
||||
// return String.format("https://bmclapi.bangbang93.com/maven/org/openjfx/javafx-%s/%s/javafx-%s-%s-%s.jar",
|
||||
// component, version, component, version, getMvnName());
|
||||
}
|
||||
|
||||
private static String getMvnName() {
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case LINUX:
|
||||
return "linux";
|
||||
case OSX:
|
||||
return "mac";
|
||||
default:
|
||||
return "win";
|
||||
}
|
||||
}
|
||||
|
||||
public static class PatchException extends Exception {
|
||||
PatchException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
@@ -234,8 +278,6 @@ public class SelfDependencyPatcher {
|
||||
}
|
||||
|
||||
public static class ProgressFrame extends JDialog {
|
||||
private int totalTasks = 0;
|
||||
private int finishedTasks = 0;
|
||||
|
||||
private final JProgressBar progressBar;
|
||||
private final JLabel progressText;
|
||||
@@ -253,10 +295,10 @@ public class SelfDependencyPatcher {
|
||||
setLocationRelativeTo(null);
|
||||
|
||||
GridBagLayout gridBagLayout = new GridBagLayout();
|
||||
gridBagLayout.columnWidths = new int[]{600, 0};
|
||||
gridBagLayout.rowHeights = new int[]{0, 0, 0, 200};
|
||||
gridBagLayout.columnWeights = new double[]{1.0, Double.MIN_VALUE};
|
||||
gridBagLayout.rowWeights = new double[]{0.0, 0.0, 0.0, 1.0};
|
||||
gridBagLayout.columnWidths = new int[] { 600, 0 };
|
||||
gridBagLayout.rowHeights = new int[] { 0, 0, 0, 200 };
|
||||
gridBagLayout.columnWeights = new double[] { 1.0, Double.MIN_VALUE };
|
||||
gridBagLayout.rowWeights = new double[] { 0.0, 0.0, 0.0, 1.0 };
|
||||
panel.setLayout(gridBagLayout);
|
||||
|
||||
progressText = new JLabel("");
|
||||
|
||||
@@ -28,6 +28,8 @@ import java.util.Locale;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public final class Locales {
|
||||
private Locales() {
|
||||
}
|
||||
|
||||
public static final SupportedLocale DEFAULT = new SupportedLocale(Locale.getDefault(), "lang.default");
|
||||
|
||||
@@ -61,12 +63,18 @@ public final class Locales {
|
||||
public static SupportedLocale getLocaleByName(String name) {
|
||||
if (name == null) return DEFAULT;
|
||||
switch (name.toLowerCase()) {
|
||||
case "en": return EN;
|
||||
case "zh": return ZH;
|
||||
case "zh_cn": return ZH_CN;
|
||||
case "es": return ES;
|
||||
case "ru": return RU;
|
||||
default: return DEFAULT;
|
||||
case "en":
|
||||
return EN;
|
||||
case "zh":
|
||||
return ZH;
|
||||
case "zh_cn":
|
||||
return ZH_CN;
|
||||
case "es":
|
||||
return ES;
|
||||
case "ru":
|
||||
return RU;
|
||||
default:
|
||||
return DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,31 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.module.Configuration;
|
||||
import java.lang.module.ModuleFinder;
|
||||
import java.lang.module.ModuleReference;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import jdk.internal.loader.BuiltinClassLoader;
|
||||
|
||||
@@ -25,11 +34,34 @@ import jdk.internal.loader.BuiltinClassLoader;
|
||||
*
|
||||
* @author ZekerZhayard
|
||||
*/
|
||||
public class JavaFXPatcher {
|
||||
public final class JavaFXPatcher {
|
||||
private JavaFXPatcher() {
|
||||
}
|
||||
|
||||
private static final String[] addOpens = {
|
||||
"javafx.base/com.sun.javafx.runtime",
|
||||
"javafx.base/com.sun.javafx.binding",
|
||||
"javafx.base/com.sun.javafx.event",
|
||||
"javafx.graphics/javafx.css",
|
||||
"javafx.graphics/com.sun.javafx.stage",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"javafx.controls/com.sun.javafx.scene.control"
|
||||
};
|
||||
|
||||
private static final String[] addExports = {
|
||||
"javafx.base/com.sun.javafx.binding",
|
||||
"javafx.base/com.sun.javafx.event",
|
||||
"javafx.graphics/com.sun.javafx.stage",
|
||||
"javafx.controls/com.sun.javafx.scene.control.behavior",
|
||||
"javafx.controls/javafx.scene.control.skin",
|
||||
"javafx.controls/com.sun.javafx.scene.control"
|
||||
};
|
||||
|
||||
/**
|
||||
* Add JavaFX to module path at runtime.
|
||||
*
|
||||
* @param modules All module names
|
||||
* @param modules All module names
|
||||
* @param jarPaths All jar paths
|
||||
* @throws ReflectiveOperationException When the call to add these jars to the system module path failed.
|
||||
*/
|
||||
@@ -47,32 +79,26 @@ public class JavaFXPatcher {
|
||||
ModuleLayer layer = ModuleLayer.defineModules(config, List.of(ModuleLayer.boot()), name -> ClassLoader.getSystemClassLoader()).layer();
|
||||
|
||||
// Add-Exports and Add-Opens
|
||||
try (
|
||||
FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + JavaFXPatcher.class.getProtectionDomain().getCodeSource().getLocation().toURI()), Map.of("create", "true"));
|
||||
InputStream stream = Files.newInputStream(fs.getPath("/META-INF/MANIFEST.MF"))
|
||||
) {
|
||||
try {
|
||||
// Some hacks
|
||||
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(Module.class, MethodHandles.lookup());
|
||||
Attributes attributes = new Manifest(stream).getMainAttributes();
|
||||
addExportsOrOpens(attributes.getValue("Add-Exports"), layer, lookup.findVirtual(Module.class, "implAddExportsToAllUnnamed", MethodType.methodType(void.class, String.class)));
|
||||
addExportsOrOpens(attributes.getValue("Add-Opens"), layer, lookup.findVirtual(Module.class, "implAddOpensToAllUnnamed", MethodType.methodType(void.class, String.class)));
|
||||
addExportsOrOpens(addExports, layer, lookup.findVirtual(Module.class, "implAddExportsToAllUnnamed", MethodType.methodType(void.class, String.class)));
|
||||
addExportsOrOpens(addOpens, layer, lookup.findVirtual(Module.class, "implAddOpensToAllUnnamed", MethodType.methodType(void.class, String.class)));
|
||||
} catch (Throwable t) {
|
||||
throw new ReflectiveOperationException(t);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addExportsOrOpens(String targets, ModuleLayer layer, MethodHandle handle) {
|
||||
for (String target : targets.split("\\s+")) {
|
||||
private static void addExportsOrOpens(String[] targets, ModuleLayer layer, MethodHandle handle) {
|
||||
for (String target : targets) {
|
||||
String[] name = target.split("/", 2); // <module>/<package>
|
||||
if (name[0].startsWith("javafx.")) {
|
||||
layer.findModule(name[0]).ifPresent(m -> {
|
||||
try {
|
||||
handle.invokeWithArguments(m, name[1]);
|
||||
} catch (Throwable throwable) {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
layer.findModule(name[0]).ifPresent(m -> {
|
||||
try {
|
||||
handle.invokeWithArguments(m, name[1]);
|
||||
} catch (Throwable throwable) {
|
||||
throw new RuntimeException(throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -53,7 +53,7 @@
|
||||
</JFXTextField>
|
||||
</right>
|
||||
</BorderPane>
|
||||
|
||||
|
||||
<BorderPane> <!-- Launcher Visibility -->
|
||||
<left>
|
||||
<Label text="%settings.advanced.launcher_visible" BorderPane.alignment="CENTER_LEFT"/>
|
||||
@@ -128,6 +128,9 @@
|
||||
fx:id="txtPrecallingCommand" StackPane.margin="$insets"/>
|
||||
<JFXTextField labelFloat="true" promptText="%settings.advanced.server_ip" styleClass="fit-width"
|
||||
fx:id="txtServerIP" StackPane.margin="$insets"/>
|
||||
|
||||
<MultiFileItem fx:id="nativesDirItem" title="%settings.advanced.natives_directory" chooserTitle="%settings.advanced.natives_directory.choose"
|
||||
hasSubtitle="true" customText="%settings.custom" directory="true" />
|
||||
<BorderPane>
|
||||
<left>
|
||||
<Label BorderPane.alignment="CENTER_LEFT" text="%settings.advanced.no_jvm_args"/>
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 292 KiB |
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
# Contributors: dxNeil, machinesmith42
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright (c) 2020 huangyuhui.
|
||||
about.copyright.statement=Copyright (c) 2021 huangyuhui.
|
||||
about.author=Author
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=Thanks to
|
||||
@@ -196,7 +196,7 @@ launch.state.dependencies=Dependencies
|
||||
launch.state.done=Done
|
||||
launch.state.logging_in=Logging In
|
||||
launch.state.modpack=Loading modpack
|
||||
launch.state.waiting_launching=Launching modpack
|
||||
launch.state.waiting_launching=Launching Game
|
||||
launch.wrong_javadir=Invalid Java directory, default Java path will be applied.
|
||||
|
||||
launcher=Launcher
|
||||
@@ -364,13 +364,16 @@ settings.advanced.dont_check_jvm_validity=Don't check whether JVM can launch the
|
||||
settings.advanced.game_dir.default=Standard (.minecraft/)
|
||||
settings.advanced.game_dir.independent=Independent (.minecraft/versions/<version name>/, except for assets and libraries)
|
||||
settings.advanced.java_permanent_generation_space=PermGen Space/MB
|
||||
settings.advanced.jvm_args=Java VM Arguments
|
||||
settings.advanced.jvm_args=Java VM Arguments (overriding defaults)
|
||||
settings.advanced.launcher_visibility.close=Close the launcher when the game launched.
|
||||
settings.advanced.launcher_visibility.hide=Hide the launcher when the game launched.
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=Hide the launcher and re-open when game closes.
|
||||
settings.advanced.launcher_visibility.keep=Keep the launcher visible.
|
||||
settings.advanced.launcher_visible=Launcher Visibility
|
||||
settings.advanced.minecraft_arguments=Minecraft Arguments
|
||||
settings.advanced.natives_directory=Local Library Path (LWJGL)
|
||||
settings.advanced.natives_directory.choose=Choose Local Library Path
|
||||
settings.advanced.natives_directory.default=Standerd (.minecraft/versions/<version name>/natives/)
|
||||
settings.advanced.no_jvm_args=No Default JVM Args
|
||||
settings.advanced.precall_command=Pre-Launch command (will be executed before game starts)
|
||||
settings.advanced.server_ip=Server Address
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
# Contributors: poopingpenis
|
||||
about.copyright=Copyright
|
||||
about.copyright.statement=Copyright (c) 2020 huangyuhui.
|
||||
about.copyright.statement=Copyright (c) 2021 huangyuhui.
|
||||
about.author=Autor
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=Gracias a
|
||||
@@ -335,13 +335,16 @@ settings.advanced.dont_check_jvm_validity=No verificar si JVM puede lanzar el ju
|
||||
settings.advanced.game_dir.default=Por defecto (.minecraft/)
|
||||
settings.advanced.game_dir.independent=Independiente (.minecraft/versions/<version name>/, excepto assets,libraries)
|
||||
settings.advanced.java_permanent_generation_space=PermGen Space/MB
|
||||
settings.advanced.jvm_args=Java VM Arguments
|
||||
settings.advanced.jvm_args=Java VM Arguments (overriding defaults)
|
||||
settings.advanced.launcher_visibility.close=Cerrar launcher cuando juego es iniciado.
|
||||
settings.advanced.launcher_visibility.hide=Esconder launcher cuando juego is iniciado.
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=Esconder launcher y reabrir cuando juego cierre.
|
||||
settings.advanced.launcher_visibility.keep=Mantener launcher visible.
|
||||
settings.advanced.launcher_visible=Visibilidad de launcher
|
||||
settings.advanced.minecraft_arguments=Minecraft Arguments
|
||||
settings.advanced.natives_directory=Loka biblioteko- vojo (LWJGL)
|
||||
settings.advanced.natives_directory.choose=Elektu lokan bibliotekvojn
|
||||
settings.advanced.natives_directory.default=Por defecto (.minecraft/versions/<version name>/natives/)
|
||||
settings.advanced.no_jvm_args=No JVM Args por defecto
|
||||
settings.advanced.precall_command=Comando pre-inicio (será ejecutado antes de inicio del juego)
|
||||
settings.advanced.server_ip=Host de servidor
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
# Содействующие: Botylyov Yury, The_BadUser
|
||||
about.copyright=Авторские права
|
||||
about.copyright.statement=(C) 2020 huangyuhui.
|
||||
about.copyright.statement=(C) 2021 huangyuhui.
|
||||
about.author=Автор
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=Отдельное спасибо
|
||||
@@ -359,6 +359,9 @@ settings.advanced.launcher_visibility.hide_and_reopen=Сворачивать л
|
||||
settings.advanced.launcher_visibility.keep=Оставлять лаунчер видимым.
|
||||
settings.advanced.launcher_visible=Видимость лаунчера
|
||||
settings.advanced.minecraft_arguments=Параметры Minecraft
|
||||
settings.advanced.natives_directory=путь к локальной библиотеке (LWJGL)
|
||||
settings.advanced.natives_directory.choose=Выберите путь к локальной библиотеке
|
||||
settings.advanced.natives_directory.default=По умолчанию (.minecraft/versions/<версия>/natives/)
|
||||
settings.advanced.no_jvm_args=По умолчанию параметры JVM отсутствуют
|
||||
settings.advanced.precall_command=Команда предзапуска (будет выполнено до запуска игры)
|
||||
settings.advanced.server_ip=Адрес сервера
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
# Contributors: pan93412
|
||||
about.copyright=著作權
|
||||
about.copyright.statement=著作權所有 (c) 2020 huangyuhui。
|
||||
about.copyright.statement=著作權所有 (c) 2021 huangyuhui。
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=鳴謝
|
||||
@@ -358,13 +358,16 @@ settings.advanced.dont_check_jvm_validity=不檢查 JVM 與遊戲的相容性
|
||||
settings.advanced.game_dir.default=預設(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本獨立(.minecraft/versions/<版本名>/,除 assets、libraries)
|
||||
settings.advanced.java_permanent_generation_space=記憶體永久儲存區域(不必填寫,格式: MB)
|
||||
settings.advanced.jvm_args=Java 虛擬機參數(不必填寫)
|
||||
settings.advanced.jvm_args=Java 虛擬機參數(不必填寫,覆蓋默認設置)
|
||||
settings.advanced.launcher_visibility.close=遊戲啟動後結束啟動器
|
||||
settings.advanced.launcher_visibility.hide=遊戲啟動後隱藏啟動器
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=隱藏啟動器並在遊戲結束後重新開啟
|
||||
settings.advanced.launcher_visibility.keep=不隱藏啟動器
|
||||
settings.advanced.launcher_visible=啟動器可見性
|
||||
settings.advanced.minecraft_arguments=Minecraft 額外參數(不必填寫)
|
||||
settings.advanced.natives_directory=本地庫路徑(LWJGL)
|
||||
settings.advanced.natives_directory.choose=選擇本地庫路徑
|
||||
settings.advanced.natives_directory.default=預設(.minecraft/versions/<版本名>/natives/)
|
||||
settings.advanced.no_jvm_args=不使用預設的 JVM 參數
|
||||
settings.advanced.precall_command=啟動前執行指令(不必填寫,將在遊戲啟動前呼叫使用)
|
||||
settings.advanced.server_ip=直接進入伺服器 IP 位址(不必填寫,啟動遊戲後直接進入對應伺服器)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#
|
||||
# Hello Minecraft! Launcher
|
||||
# Copyright (C) 2020 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
# Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
# Contributors: huangyuhui
|
||||
about.copyright=版权
|
||||
about.copyright.statement=版权所有 (c) 2020 huangyuhui.
|
||||
about.copyright.statement=版权所有 (c) 2021 huangyuhui.
|
||||
about.author=作者
|
||||
about.author.statement=huanghongxun (hmcl@huangyuhui.net)
|
||||
about.thanks_to=鸣谢
|
||||
@@ -364,13 +364,16 @@ settings.advanced.dont_check_jvm_validity=不检查 JVM 与游戏的兼容性
|
||||
settings.advanced.game_dir.default=默认(.minecraft/)
|
||||
settings.advanced.game_dir.independent=各版本独立(.minecraft/versions/<版本名>/,除 assets、libraries)
|
||||
settings.advanced.java_permanent_generation_space=内存永久保存区域(不必填写,单位 MB)
|
||||
settings.advanced.jvm_args=Java 虚拟机参数(不必填写)
|
||||
settings.advanced.jvm_args=Java 虚拟机参数(不必填写,覆盖默认设置)
|
||||
settings.advanced.launcher_visibility.close=游戏启动后结束启动器
|
||||
settings.advanced.launcher_visibility.hide=游戏启动后隐藏启动器
|
||||
settings.advanced.launcher_visibility.hide_and_reopen=隐藏启动器并在游戏结束后重新打开
|
||||
settings.advanced.launcher_visibility.keep=保持启动器可见
|
||||
settings.advanced.launcher_visible=启动器可见性
|
||||
settings.advanced.minecraft_arguments=Minecraft 额外参数(不必填写)
|
||||
settings.advanced.natives_directory=本地库路径(LWJGL)
|
||||
settings.advanced.natives_directory.choose=选择本地库路径
|
||||
settings.advanced.natives_directory.default=预设(.minecraft/versions/<版本名>/natives/)
|
||||
settings.advanced.no_jvm_args=不添加默认的 JVM 参数
|
||||
settings.advanced.precall_command=启动前执行命令(不必填写,将在游戏启动前调用)
|
||||
settings.advanced.server_ip=直入服务器 IP 地址(不必填写,启动游戏后直接进入对应服务器)
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
[
|
||||
{
|
||||
"module": "javafx.base",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-base",
|
||||
"version": "16",
|
||||
"sha1": {
|
||||
"linux": "7d6b85ec89e99ea40c2a63bb1f23d43e05cd2557",
|
||||
"mac": "ea97d8a5ab95d070df86de1215f29f08378c98c8",
|
||||
"win": "e0564fea3f27dd3a10fa62e5005612333fc244bc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.controls",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-controls",
|
||||
"version": "16",
|
||||
"sha1": {
|
||||
"linux": "116b127e512d23ddb84c62017c256e7f67f5b9eb",
|
||||
"mac": "8df933b095bdd8203dc937e39e3a9f199654e47e",
|
||||
"win": "65cdeae29c67d25932dcc66ca4f7d269923631ba"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.fxml",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-fxml",
|
||||
"version": "16",
|
||||
"sha1": {
|
||||
"linux": "c7147c450773c3d4fd038ac9d1a6fdebbc3c11e0",
|
||||
"mac": "7e4f07a331991485560673a398d6988cc805b67f",
|
||||
"win": "369f646fc29d2223cc98cfc3c059f2409b3e8fcb"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.graphics",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-graphics",
|
||||
"version": "16",
|
||||
"sha1": {
|
||||
"linux": "01bf2fd9f083daa2f492c4c58c4f7d5acb6f4b7d",
|
||||
"mac": "a45de3d40e217f7ba22cecf54abc281d13a172f6",
|
||||
"win": "0cd18f8477818b40cfdbcfec7e2fc15f632f0a2f"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.media",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-media",
|
||||
"version": "16",
|
||||
"sha1": {
|
||||
"linux": "10edb5fd8eb64b3312b31225c396e1850b9d178b",
|
||||
"mac": "9e0248083267cdc22b2b698f0b0b62b7399ad32d",
|
||||
"win": "6d776658906fc25051734bc6b03f214cc72c808e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"module": "javafx.web",
|
||||
"groupId": "org.openjfx",
|
||||
"artifactId": "javafx-web",
|
||||
"version": "16",
|
||||
"sha1": {
|
||||
"linux": "522f626e1798a7d589d9b187ca5c74c5c2f8a0ee",
|
||||
"mac": "32b0e13b649ea828e7d185187f178680cc04e564",
|
||||
"win": "d2f80089a4ae1629a5ec14637abf4f6d70b96dab"
|
||||
}
|
||||
}
|
||||
]
|
||||
+6
-6
@@ -17,19 +17,19 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.authlibinjector;
|
||||
|
||||
import static org.jackhuang.hmcl.util.io.NetworkUtils.decodeURL;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.input.DragEvent;
|
||||
import javafx.scene.input.Dragboard;
|
||||
import javafx.scene.input.TransferMode;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.io.NetworkUtils.decodeURL;
|
||||
|
||||
/**
|
||||
* @author yushijinhun
|
||||
* @see https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#dnd-%E6%96%B9%E5%BC%8F%E6%B7%BB%E5%8A%A0-yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF
|
||||
* @see <a href="https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#dnd-%E6%96%B9%E5%BC%8F%E6%B7%BB%E5%8A%A0-yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF">Launcher Technical Specification for Authlib-Injector</a>
|
||||
*/
|
||||
public final class AuthlibInjectorDnD {
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
||||
public final class OfflineAccountFactory extends AccountFactory<OfflineAccount> {
|
||||
public static final OfflineAccountFactory INSTANCE = new OfflineAccountFactory();
|
||||
|
||||
private OfflineAccountFactory() {
|
||||
|
||||
+3
-10
@@ -17,20 +17,13 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.auth.yggdrasil;
|
||||
|
||||
import static java.util.Collections.unmodifiableMap;
|
||||
import com.google.gson.*;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import static java.util.Collections.unmodifiableMap;
|
||||
|
||||
public class PropertyMapSerializer implements JsonSerializer<Map<String, String>>, JsonDeserializer<Map<String, String>> {
|
||||
|
||||
@@ -57,4 +50,4 @@ public class PropertyMapSerializer implements JsonSerializer<Map<String, String>
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -78,7 +78,6 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
|
||||
|
||||
@Override
|
||||
public String injectURL(String baseURL) {
|
||||
if (baseURL.contains("v1/products/java-runtime")) return baseURL;
|
||||
return baseURL
|
||||
.replace("https://bmclapi2.bangbang93.com", apiRoot)
|
||||
.replace("https://launchermeta.mojang.com", apiRoot)
|
||||
|
||||
@@ -38,7 +38,7 @@ public abstract class GameBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* The new game version name, for .minecraft/<version name>.
|
||||
* The new game version name, for .minecraft/<version name>.
|
||||
*
|
||||
* @param name the name of new game version.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.VersionProvider;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -84,6 +85,11 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
return modLauncher.equals(version.getMainClass()) || version.getPatches().stream().anyMatch(patch -> modLauncher.equals(patch.getMainClass()));
|
||||
}
|
||||
|
||||
public boolean hasBootstrapLauncher() {
|
||||
final String bootstrapLauncher = "cpw.mods.bootstraplauncher.BootstrapLauncher";
|
||||
return bootstrapLauncher.equals(version.getMainClass()) || version.getPatches().stream().anyMatch(patch -> bootstrapLauncher.equals(patch.getMainClass()));
|
||||
}
|
||||
|
||||
private Version removingMatchedLibrary(Version version, String libraryId) {
|
||||
LibraryType type = LibraryType.fromPatchId(libraryId);
|
||||
if (type == null) return version;
|
||||
@@ -141,10 +147,16 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
return new LibraryAnalyzer(version, libraries);
|
||||
}
|
||||
|
||||
public static boolean isModded(VersionProvider provider, Version version) {
|
||||
Version resolvedVersion = version.resolve(provider);
|
||||
String mainClass = resolvedVersion.getMainClass();
|
||||
return mainClass != null && (LAUNCH_WRAPPER_MAIN.equals(mainClass) || mainClass.startsWith("net.fabricmc") || mainClass.startsWith("cpw.mods"));
|
||||
}
|
||||
|
||||
public enum LibraryType {
|
||||
MINECRAFT(true, "game", Pattern.compile("^$"), Pattern.compile("^$")),
|
||||
FABRIC(true, "fabric", Pattern.compile("net\\.fabricmc"), Pattern.compile("fabric-loader")),
|
||||
FORGE(true, "forge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("forge")),
|
||||
FORGE(true, "forge", Pattern.compile("net\\.minecraftforge"), Pattern.compile("(forge|fmlloader)")),
|
||||
LITELOADER(true, "liteloader", Pattern.compile("com\\.mumfrey"), Pattern.compile("liteloader")),
|
||||
OPTIFINE(false, "optifine", Pattern.compile("(net\\.)?optifine"), Pattern.compile("^(?!.*launchwrapper).*$"));
|
||||
|
||||
@@ -202,4 +214,5 @@ public final class LibraryAnalyzer implements Iterable<LibraryAnalyzer.LibraryMa
|
||||
public static final String VANILLA_MAIN = "net.minecraft.client.main.Main";
|
||||
public static final String LAUNCH_WRAPPER_MAIN = "net.minecraft.launchwrapper.Launch";
|
||||
public static final String MOD_LAUNCHER_MAIN = "cpw.mods.modlauncher.Launcher";
|
||||
public static final String BOOTSTRAP_LAUNCHER_MAIN = "cpw.mods.bootstraplauncher.BootstrapLauncher";
|
||||
}
|
||||
@@ -17,29 +17,26 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.download;
|
||||
|
||||
import org.jackhuang.hmcl.game.Artifact;
|
||||
import org.jackhuang.hmcl.game.CompatibilityRule;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.game.VersionLibraryBuilder;
|
||||
import org.jackhuang.hmcl.game.*;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.SimpleMultimap;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.FORGE;
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.LITELOADER;
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.OPTIFINE;
|
||||
import static org.jackhuang.hmcl.download.LibraryAnalyzer.LibraryType.*;
|
||||
|
||||
public class MaintainTask extends Task<Version> {
|
||||
private final GameRepository repository;
|
||||
@@ -68,7 +65,10 @@ public class MaintainTask extends Task<Version> {
|
||||
return maintainOptiFineLibrary(repository, maintainGameWithLaunchWrapper(unique(version), true), false);
|
||||
} else if (mainClass != null && mainClass.equals(LibraryAnalyzer.MOD_LAUNCHER_MAIN)) {
|
||||
// Forge 1.13 and OptiFine
|
||||
return maintainOptiFineLibrary(repository, maintainGameWithModLauncher(repository, unique(version)), true);
|
||||
return maintainOptiFineLibrary(repository, maintainGameWithCpwModLauncher(repository, unique(version)), true);
|
||||
} else if (mainClass != null && mainClass.equals(LibraryAnalyzer.BOOTSTRAP_LAUNCHER_MAIN)) {
|
||||
// Forge 1.17
|
||||
return maintainGameWithCpwBoostrapLauncher(repository, unique(version));
|
||||
} else {
|
||||
// Vanilla Minecraft does not need maintain
|
||||
// Fabric does not need maintain, nothing compatible with fabric now.
|
||||
@@ -122,7 +122,7 @@ public class MaintainTask extends Task<Version> {
|
||||
return mainClass == null ? ret : ret.setMainClass(mainClass);
|
||||
}
|
||||
|
||||
private static Version maintainGameWithModLauncher(GameRepository repository, Version version) {
|
||||
private static Version maintainGameWithCpwModLauncher(GameRepository repository, Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
|
||||
@@ -133,7 +133,7 @@ public class MaintainTask extends Task<Version> {
|
||||
Optional<Library> optiFine = version.getLibraries().stream().filter(library -> library.is("optifine", "OptiFine")).findAny();
|
||||
boolean libraryExisting = version.getLibraries().stream().anyMatch(library -> library.is("org.jackhuang.hmcl", "transformer-discovery-service"));
|
||||
optiFine.ifPresent(library -> {
|
||||
builder.addJvmArgument("-Dhmcl.transformer.candidates=${libraries_directory}/" + library.getPath());
|
||||
builder.addJvmArgument("-Dhmcl.transformer.candidates=${library_directory}/" + library.getPath());
|
||||
if (!libraryExisting) builder.addLibrary(hmclTransformerDiscoveryService);
|
||||
Path libraryPath = repository.getLibraryFile(version, hmclTransformerDiscoveryService).toPath();
|
||||
try {
|
||||
@@ -149,6 +149,59 @@ public class MaintainTask extends Task<Version> {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static String updateIgnoreList(GameRepository repository, Version version, String ignoreList) {
|
||||
String[] ignores = ignoreList.split(",");
|
||||
List<String> newIgnoreList = new ArrayList<>();
|
||||
|
||||
// To resolve the the problem that name of primary jar may conflict with the module naming convention,
|
||||
// we need to manually ignore ${primary_jar}.
|
||||
newIgnoreList.add("${primary_jar}");
|
||||
|
||||
Path libraryDirectory = repository.getLibrariesDirectory(version).toPath().toAbsolutePath();
|
||||
|
||||
// The default ignoreList is too loose and may cause some problems, we replace them with the absolute version.
|
||||
// For example, if "client-extra" is in ignoreList, and game directory contains "client-extra" component, all
|
||||
// libraries will be ignored, which is not expected.
|
||||
for (String classpathName : repository.getClasspath(version)) {
|
||||
Path classpathFile = Paths.get(classpathName).toAbsolutePath();
|
||||
String fileName = classpathFile.getFileName().toString();
|
||||
if (Stream.of(ignores).anyMatch(fileName::contains)) {
|
||||
// This library should be ignored for Jigsaw module finding by Forge.
|
||||
String absolutePath;
|
||||
if (classpathFile.startsWith(libraryDirectory)) {
|
||||
// Note: It's assumed using "/" instead of File.separator in classpath
|
||||
absolutePath = "${library_directory}${file_separator}" + libraryDirectory.relativize(classpathFile).toString().replace(File.separator, "${file_separator}");
|
||||
} else {
|
||||
absolutePath = classpathFile.toString();
|
||||
}
|
||||
newIgnoreList.add(StringUtils.substringBefore(absolutePath, ","));
|
||||
}
|
||||
}
|
||||
return String.join(",", newIgnoreList);
|
||||
}
|
||||
|
||||
// Fix wrong configurations when launching 1.17+ with Forge.
|
||||
private static Version maintainGameWithCpwBoostrapLauncher(GameRepository repository, Version version) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
VersionLibraryBuilder builder = new VersionLibraryBuilder(version);
|
||||
|
||||
if (!libraryAnalyzer.has(FORGE)) return version;
|
||||
|
||||
// The default ignoreList set by Forge installer does not fulfill our requirements
|
||||
List<Argument> jvm = builder.getMutableJvmArguments();
|
||||
for (int i = 0; i < jvm.size(); i++) {
|
||||
Argument jvmArg = jvm.get(i);
|
||||
if (jvmArg instanceof StringArgument) {
|
||||
String jvmArgStr = jvmArg.toString();
|
||||
if (jvmArgStr.startsWith("-DignoreList=")) {
|
||||
jvm.set(i, new StringArgument("-DignoreList=" + updateIgnoreList(repository, version, jvmArgStr.substring("-DignoreList=".length()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static Version maintainOptiFineLibrary(GameRepository repository, Version version, boolean remove) {
|
||||
LibraryAnalyzer libraryAnalyzer = LibraryAnalyzer.analyze(version);
|
||||
List<Library> libraries = new ArrayList<>(version.getLibraries());
|
||||
|
||||
@@ -173,7 +173,7 @@ public final class ForgeInstallTask extends Task<Version> {
|
||||
ForgeNewInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeNewInstallProfile.class);
|
||||
if (!gameVersion.get().equals(profile.getMinecraft()))
|
||||
throw new VersionMismatchException(profile.getMinecraft(), gameVersion.get());
|
||||
return new ForgeNewInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getPath().getVersion().replaceAll("(?i)forge", "")), installer);
|
||||
return new ForgeNewInstallTask(dependencyManager, version, modifyVersion(gameVersion.get(), profile.getVersion()), installer);
|
||||
} else if (installProfile.containsKey("install") && installProfile.containsKey("versionInfo")) {
|
||||
ForgeInstallProfile profile = JsonUtils.fromNonNullJson(installProfileText, ForgeInstallProfile.class);
|
||||
if (!gameVersion.get().equals(profile.getInstall().getMinecraft()))
|
||||
|
||||
+15
-8
@@ -25,10 +25,7 @@ import org.jackhuang.hmcl.util.function.ExceptionalFunction;
|
||||
import org.jackhuang.hmcl.util.gson.TolerableValidationException;
|
||||
import org.jackhuang.hmcl.util.gson.Validation;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Immutable
|
||||
@@ -37,15 +34,17 @@ public class ForgeNewInstallProfile implements Validation {
|
||||
private final int spec;
|
||||
private final String minecraft;
|
||||
private final String json;
|
||||
private final String version;
|
||||
private final Artifact path;
|
||||
private final List<Library> libraries;
|
||||
private final List<Processor> processors;
|
||||
private final Map<String, Datum> data;
|
||||
|
||||
public ForgeNewInstallProfile(int spec, String minecraft, String json, Artifact path, List<Library> libraries, List<Processor> processors, Map<String, Datum> data) {
|
||||
public ForgeNewInstallProfile(int spec, String minecraft, String json, String version, Artifact path, List<Library> libraries, List<Processor> processors, Map<String, Datum> data) {
|
||||
this.spec = spec;
|
||||
this.minecraft = minecraft;
|
||||
this.json = json;
|
||||
this.version = version;
|
||||
this.path = path;
|
||||
this.libraries = libraries;
|
||||
this.processors = processors;
|
||||
@@ -74,12 +73,20 @@ public class ForgeNewInstallProfile implements Validation {
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return forge version.
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maven artifact path for the main jar to install.
|
||||
* @return artifact path of the main jar.
|
||||
*/
|
||||
public Artifact getPath() {
|
||||
return path;
|
||||
public Optional<Artifact> getPath() {
|
||||
return Optional.ofNullable(path);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +119,7 @@ public class ForgeNewInstallProfile implements Validation {
|
||||
|
||||
@Override
|
||||
public void validate() throws JsonParseException, TolerableValidationException {
|
||||
if (minecraft == null || json == null || path == null)
|
||||
if (minecraft == null || json == null || version == null)
|
||||
throw new JsonParseException("ForgeNewInstallProfile is malformed");
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,50 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
setSignificance(TaskSignificance.MINOR);
|
||||
}
|
||||
|
||||
private static String replaceTokens(Map<String, String> tokens, String value) {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (int x = 0; x < value.length(); x++) {
|
||||
char c = value.charAt(x);
|
||||
if (c == '\\') {
|
||||
if (x == value.length() - 1)
|
||||
throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value);
|
||||
buf.append(value.charAt(++x));
|
||||
} else if (c == '{' || c == '\'') {
|
||||
StringBuilder key = new StringBuilder();
|
||||
for (int y = x + 1; y <= value.length(); y++) {
|
||||
if (y == value.length())
|
||||
throw new IllegalArgumentException("Illegal pattern (Unclosed " + c + "): " + value);
|
||||
char d = value.charAt(y);
|
||||
if (d == '\\') {
|
||||
if (y == value.length() - 1)
|
||||
throw new IllegalArgumentException("Illegal pattern (Bad escape): " + value);
|
||||
key.append(value.charAt(++y));
|
||||
} else {
|
||||
if (c == '{' && d == '}') {
|
||||
x = y;
|
||||
break;
|
||||
}
|
||||
if (c == '\'' && d == '\'') {
|
||||
x = y;
|
||||
break;
|
||||
}
|
||||
key.append(d);
|
||||
}
|
||||
}
|
||||
if (c == '\'') {
|
||||
buf.append(key);
|
||||
} else {
|
||||
if (!tokens.containsKey(key.toString()))
|
||||
throw new IllegalArgumentException("Illegal pattern: " + value + " Missing Key: " + key);
|
||||
buf.append(tokens.get(key.toString()));
|
||||
}
|
||||
} else {
|
||||
buf.append(c);
|
||||
}
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private <E extends Exception> String parseLiteral(String literal, Map<String, String> var, ExceptionalFunction<String, String, E> plainConverter) throws E {
|
||||
if (StringUtils.isSurrounded(literal, "{", "}"))
|
||||
return var.get(StringUtils.removeSurrounding(literal, "{", "}"));
|
||||
@@ -84,7 +128,7 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
else if (StringUtils.isSurrounded(literal, "[", "]"))
|
||||
return gameRepository.getArtifactFile(version, Artifact.fromDescriptor(StringUtils.removeSurrounding(literal, "[", "]"))).toString();
|
||||
else
|
||||
return plainConverter.apply(literal);
|
||||
return plainConverter.apply(replaceTokens(var, literal));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,10 +160,10 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Path mainJar = profile.getPath().getPath(fs.getPath("maven"));
|
||||
if (profile.getPath().isPresent()) {
|
||||
Path mainJar = profile.getPath().get().getPath(fs.getPath("maven"));
|
||||
if (Files.exists(mainJar)) {
|
||||
Path dest = gameRepository.getArtifactFile(version, profile.getPath());
|
||||
Path dest = gameRepository.getArtifactFile(version, profile.getPath().get());
|
||||
FileUtils.copyFile(mainJar, dest);
|
||||
}
|
||||
}
|
||||
@@ -130,7 +174,7 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Path temp = Files.createTempDirectory("forge_installer");
|
||||
Path temp = Files.createTempDirectory("forge_installer");
|
||||
int finished = 0;
|
||||
try (FileSystem fs = CompressingUtils.createReadOnlyZipFileSystem(installer)) {
|
||||
List<ForgeNewInstallProfile.Processor> processors = profile.getProcessors();
|
||||
@@ -153,6 +197,10 @@ public class ForgeNewInstallTask extends Task<Version> {
|
||||
|
||||
data.put("SIDE", "client");
|
||||
data.put("MINECRAFT_JAR", gameRepository.getVersionJar(version).getAbsolutePath());
|
||||
data.put("MINECRAFT_VERSION", gameRepository.getVersionJar(version).getAbsolutePath());
|
||||
data.put("ROOT", gameRepository.getBaseDirectory().getAbsolutePath());
|
||||
data.put("INSTALLER", installer.toAbsolutePath().toString());
|
||||
data.put("LIBRARY_DIR", gameRepository.getLibrariesDirectory(version).getAbsolutePath());
|
||||
|
||||
for (ForgeNewInstallProfile.Processor processor : processors) {
|
||||
Map<String, String> outputs = new HashMap<>();
|
||||
|
||||
-1
@@ -105,7 +105,6 @@ public final class GameAssetIndexDownloadTask extends Task<Void> {
|
||||
dependencies.add(task);
|
||||
}
|
||||
|
||||
|
||||
public static class GameAssetIndexMalformedException extends IOException {
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,8 @@ package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.LibraryAnalyzer;
|
||||
import org.jackhuang.hmcl.game.GameVersion;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
@@ -33,11 +29,9 @@ import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Remove class digital verification file in game jar
|
||||
|
||||
@@ -25,10 +25,10 @@ import org.jackhuang.hmcl.task.DownloadException;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.jackhuang.hmcl.util.platform.SystemUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.*;
|
||||
@@ -38,7 +38,9 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.jar.*;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.DigestUtils.digest;
|
||||
@@ -129,7 +131,7 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
if (SystemUtils.JRE_CAPABILITY_PACK200 && testURLExistence(url)) {
|
||||
if (Pack200Utils.isSupported() && testURLExistence(url)) {
|
||||
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url + ".pack.xz");
|
||||
task = new FileDownloadTask(urls, xzFile, null);
|
||||
task.setCacheRepository(cacheRepository);
|
||||
@@ -264,7 +266,7 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
}
|
||||
|
||||
try (FileOutputStream jarBytes = new FileOutputStream(dest); JarOutputStream jos = new JarOutputStream(jarBytes)) {
|
||||
Pack200.newUnpacker().unpack(temp.toFile(), jos);
|
||||
Pack200Utils.unpack(temp.toFile(), jos);
|
||||
|
||||
JarEntry checksumsFile = new JarEntry("checksums.sha1");
|
||||
checksumsFile.setTime(0L);
|
||||
|
||||
@@ -51,8 +51,8 @@ public class JavaDownloadTask extends Task<Void> {
|
||||
public JavaDownloadTask(GameJavaVersion javaVersion, Path rootDir, DownloadProvider downloadProvider) {
|
||||
this.javaVersion = javaVersion;
|
||||
this.rootDir = rootDir;
|
||||
this.javaDownloadsTask = new GetTask(NetworkUtils.toURL(
|
||||
"https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"))
|
||||
this.javaDownloadsTask = new GetTask(NetworkUtils.toURL(downloadProvider.injectURL(
|
||||
"https://launchermeta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json")))
|
||||
.thenComposeAsync(javaDownloadsJson -> {
|
||||
JavaDownloads allDownloads = JsonUtils.fromNonNullJson(javaDownloadsJson, JavaDownloads.class);
|
||||
if (!allDownloads.getDownloads().containsKey(platform)) throw new UnsupportedPlatformException();
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
@@ -17,6 +18,8 @@ import java.util.logging.Level;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class JavaRepository {
|
||||
private JavaRepository() {
|
||||
}
|
||||
|
||||
public static Task<?> downloadJava(GameJavaVersion javaVersion, DownloadProvider downloadProvider) {
|
||||
return new JavaDownloadTask(javaVersion, getJavaStoragePath(), downloadProvider)
|
||||
@@ -41,12 +44,17 @@ public final class JavaRepository {
|
||||
Optional<String> platformOptional = getCurrentJavaPlatform();
|
||||
if (platformOptional.isPresent()) {
|
||||
String platform = platformOptional.get();
|
||||
for (Path component : Files.newDirectoryStream(getJavaStoragePath())) {
|
||||
Path javaHome = component.resolve(platform).resolve(component.getFileName());
|
||||
try {
|
||||
addJava(javaHome);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to determine Java at " + javaHome, e);
|
||||
Path javaStoragePath = getJavaStoragePath();
|
||||
if (Files.isDirectory(javaStoragePath)) {
|
||||
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(javaStoragePath)) {
|
||||
for (Path component : dirStream) {
|
||||
Path javaHome = component.resolve(platform).resolve(component.getFileName());
|
||||
try {
|
||||
addJava(javaHome);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to determine Java at " + javaHome, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -199,10 +199,10 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
* Install OptiFine library from existing local file.
|
||||
*
|
||||
* @param dependencyManager game repository
|
||||
* @param version version.json
|
||||
* @param installer the OptiFine installer
|
||||
* @param version version.json
|
||||
* @param installer the OptiFine installer
|
||||
* @return the task to install library
|
||||
* @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.
|
||||
* @throws IOException if unable to read compressed content of installer file, or installer file is corrupted, or the installer is not the one we want.
|
||||
* @throws VersionMismatchException if required game version of installer does not match the actual one.
|
||||
*/
|
||||
public static Task<Version> install(DefaultDependencyManager dependencyManager, Version version, Path installer) throws IOException, VersionMismatchException {
|
||||
@@ -227,7 +227,7 @@ public final class OptiFineInstallTask extends Task<Version> {
|
||||
throw new VersionMismatchException(mcVersion, gameVersion.get());
|
||||
|
||||
return new OptiFineInstallTask(dependencyManager, version,
|
||||
new OptiFineRemoteVersion(mcVersion, ofEdition + "_" + ofRelease, Collections.singletonList(""), false), installer);
|
||||
new OptiFineRemoteVersion(mcVersion, ofEdition + "_" + ofRelease, Collections.singletonList(""), false), installer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
|
||||
/**
|
||||
* This event gets fired when a JavaProcess exited abnormally and the exit code is not zero.
|
||||
* <br></br>
|
||||
*
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -34,7 +34,7 @@ public final class ProcessExitedAbnormallyEvent extends Event {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.launch.ExitWaiter}
|
||||
* @param source {@link org.jackhuang.hmcl.launch.ExitWaiter}
|
||||
* @param process The process that exited abnormally.
|
||||
*/
|
||||
public ProcessExitedAbnormallyEvent(Object source, ManagedProcess process) {
|
||||
|
||||
@@ -29,7 +29,7 @@ public final class RefreshedVersionsEvent extends Event {
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param source {@link org.jackhuang.hmcl.game.GameRepository]
|
||||
* @param source {@link org.jackhuang.hmcl.game.GameRepository}
|
||||
*/
|
||||
public RefreshedVersionsEvent(Object source) {
|
||||
super(source);
|
||||
|
||||
@@ -63,7 +63,7 @@ public class DownloadInfo implements Validation {
|
||||
}
|
||||
|
||||
public String getSha1() {
|
||||
return sha1;
|
||||
return "invalid".equals(sha1) ? null : sha1;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
|
||||
@@ -35,4 +35,4 @@ public enum GameDirectoryType {
|
||||
* user customized directory.
|
||||
*/
|
||||
CUSTOM
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Supports operations on versioning.
|
||||
@@ -88,7 +90,7 @@ public interface GameRepository extends VersionProvider {
|
||||
/**
|
||||
* Gets the root folder of specific version.
|
||||
* The root folders the versions must be unique.
|
||||
* For example, .minecraft/versions/<version name>/.
|
||||
* For example, .minecraft/versions/<version name>/.
|
||||
*/
|
||||
File getVersionRoot(String id);
|
||||
|
||||
@@ -220,4 +222,14 @@ public interface GameRepository extends VersionProvider {
|
||||
*/
|
||||
File getLoggingObject(String version, String assetId, LoggingInfo loggingInfo);
|
||||
|
||||
default List<String> getClasspath(Version version) {
|
||||
List<String> classpath = new ArrayList<>();
|
||||
for (Library library : version.getLibraries())
|
||||
if (library.appliesToCurrentEnvironment() && !library.isNative()) {
|
||||
File f = getLibraryFile(version, library);
|
||||
if (f.exists() && f.isFile())
|
||||
classpath.add(f.getAbsolutePath());
|
||||
}
|
||||
return classpath;
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,9 @@ import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class GameVersion {
|
||||
private GameVersion() {
|
||||
}
|
||||
|
||||
private static Optional<String> getVersionFromJson(Path versionJson) {
|
||||
try {
|
||||
Map<?, ?> version = JsonUtils.fromNonNullJson(FileUtils.readText(versionJson), Map.class);
|
||||
|
||||
@@ -53,6 +53,8 @@ public class LaunchOptions implements Serializable {
|
||||
private String proxyPass;
|
||||
private boolean noGeneratedJVMArgs;
|
||||
private String preLaunchCommand;
|
||||
private NativesDirectoryType nativesDirType;
|
||||
private String nativesDir;
|
||||
|
||||
/**
|
||||
* The game directory
|
||||
@@ -200,6 +202,21 @@ public class LaunchOptions implements Serializable {
|
||||
return preLaunchCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* 0 - ./minecraft/versions/<version>/natives
|
||||
* 1 - custom natives directory
|
||||
*/
|
||||
public NativesDirectoryType getNativesDirType() {
|
||||
return nativesDirType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to the natives directory, optional
|
||||
*/
|
||||
public String getNativesDir() {
|
||||
return nativesDir;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private final LaunchOptions options = new LaunchOptions();
|
||||
@@ -352,6 +369,14 @@ public class LaunchOptions implements Serializable {
|
||||
return options.preLaunchCommand;
|
||||
}
|
||||
|
||||
public NativesDirectoryType getNativesDirType() {
|
||||
return options.nativesDirType;
|
||||
}
|
||||
|
||||
public String getNativesDir() {
|
||||
return options.nativesDir;
|
||||
}
|
||||
|
||||
public Builder setGameDir(File gameDir) {
|
||||
options.gameDir = gameDir;
|
||||
return this;
|
||||
@@ -454,5 +479,15 @@ public class LaunchOptions implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNativesDirType(NativesDirectoryType nativesDirType) {
|
||||
options.nativesDirType = nativesDirType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNativesDir(String nativesDir) {
|
||||
options.nativesDir = nativesDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
public enum NativesDirectoryType {
|
||||
/**
|
||||
* .minecraft/versions/<version>/natives
|
||||
*/
|
||||
VERSION_FOLDER,
|
||||
/**
|
||||
* user customized directory.
|
||||
*/
|
||||
CUSTOM
|
||||
}
|
||||
@@ -163,7 +163,7 @@ public class Version implements Comparable<Version>, Validation {
|
||||
}
|
||||
|
||||
public String getJar() {
|
||||
return jar;
|
||||
return jar == null ? id : jar;
|
||||
}
|
||||
|
||||
public String getInheritsFrom() {
|
||||
@@ -275,14 +275,14 @@ public class Version implements Comparable<Version>, Validation {
|
||||
|
||||
if (inheritsFrom == null) {
|
||||
if (isRoot())
|
||||
thisVersion = new Version(id).setPatches(patches).setJar(id);
|
||||
thisVersion = new Version(id).setPatches(patches);
|
||||
else
|
||||
thisVersion = this.jar == null ? this.setJar(id) : this;
|
||||
thisVersion = this;
|
||||
} else {
|
||||
// To maximize the compatibility.
|
||||
if (!resolvedSoFar.add(id)) {
|
||||
Logging.LOG.log(Level.WARNING, "Found circular dependency versions: " + resolvedSoFar);
|
||||
thisVersion = this.jar == null ? this.setJar(id) : this;
|
||||
thisVersion = this;
|
||||
} else {
|
||||
// It is supposed to auto install an version in getVersion.
|
||||
thisVersion = merge(provider.getVersion(inheritsFrom).resolve(provider, resolvedSoFar), false);
|
||||
|
||||
@@ -20,7 +20,8 @@ package org.jackhuang.hmcl.game;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -145,6 +146,11 @@ public final class VersionLibraryBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
public List<Argument> getMutableJvmArguments() {
|
||||
jvmChanged = true;
|
||||
return jvm;
|
||||
}
|
||||
|
||||
public void addGameArgument(String... args) {
|
||||
for (String arg : args)
|
||||
game.add(new StringArgument(arg));
|
||||
|
||||
@@ -23,6 +23,7 @@ import org.jackhuang.hmcl.game.Arguments;
|
||||
import org.jackhuang.hmcl.game.GameRepository;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.game.NativesDirectoryType;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
@@ -44,19 +45,13 @@ import java.io.OutputStreamWriter;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class DefaultLauncher extends Launcher {
|
||||
@@ -88,53 +83,63 @@ public class DefaultLauncher extends Launcher {
|
||||
if (!options.isNoGeneratedJVMArgs()) {
|
||||
appendJvmArgs(res);
|
||||
|
||||
res.add("-Dminecraft.client.jar=" + repository.getVersionJar(version));
|
||||
res.addDefault("-Dminecraft.client.jar=", repository.getVersionJar(version).toString());
|
||||
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX) {
|
||||
res.add("-Xdock:name=Minecraft " + version.getId());
|
||||
res.add("-Xdock:icon=" + repository.getAssetObject(version.getId(), version.getAssetIndex().getId(), "icons/minecraft.icns").getAbsolutePath());
|
||||
res.addDefault("-Xdock:name=", "Minecraft " + version.getId());
|
||||
res.addDefault("-Xdock:icon=", repository.getAssetObject(version.getId(), version.getAssetIndex().getId(), "icons/minecraft.icns").getAbsolutePath());
|
||||
}
|
||||
|
||||
if (OperatingSystem.CURRENT_OS != OperatingSystem.WINDOWS)
|
||||
res.add("-Duser.home=" + options.getGameDir().getParent());
|
||||
res.addDefault("-Duser.home=", options.getGameDir().getParent());
|
||||
|
||||
// Force using G1GC with its settings
|
||||
// Using G1GC with its settings by default
|
||||
if (options.getJava().getParsedVersion() >= JavaVersion.JAVA_8) {
|
||||
res.add("-XX:+UnlockExperimentalVMOptions");
|
||||
res.add("-XX:+UseG1GC");
|
||||
res.add("-XX:G1NewSizePercent=20");
|
||||
res.add("-XX:G1ReservePercent=20");
|
||||
res.add("-XX:MaxGCPauseMillis=50");
|
||||
res.add("-XX:G1HeapRegionSize=16M");
|
||||
boolean addG1Args = true;
|
||||
for (String javaArg : options.getJavaArguments()) {
|
||||
if ("-XX:-UseG1GC".equals(javaArg) || (javaArg.startsWith("-XX:+Use") && javaArg.endsWith("GC"))) {
|
||||
addG1Args = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (addG1Args) {
|
||||
res.addUnstableDefault("UnlockExperimentalVMOptions", true);
|
||||
res.addUnstableDefault("UseG1GC", true);
|
||||
res.addUnstableDefault("G1NewSizePercent", "20");
|
||||
res.addUnstableDefault("G1ReservePercent", "20");
|
||||
res.addUnstableDefault("MaxGCPauseMillis", "50");
|
||||
res.addUnstableDefault("G1HeapRegionSize", "16m");
|
||||
}
|
||||
}
|
||||
|
||||
if (options.getMetaspace() != null && options.getMetaspace() > 0)
|
||||
if (options.getJava().getParsedVersion() < JavaVersion.JAVA_8)
|
||||
res.add("-XX:PermSize= " + options.getMetaspace() + "m");
|
||||
res.addDefault("-XX:PermSize=", options.getMetaspace() + "m");
|
||||
else
|
||||
res.add("-XX:MetaspaceSize=" + options.getMetaspace() + "m");
|
||||
res.addDefault("-XX:MetaspaceSize=", options.getMetaspace() + "m");
|
||||
|
||||
res.add("-XX:-UseAdaptiveSizePolicy");
|
||||
res.add("-XX:-OmitStackTraceInFastThrow");
|
||||
res.add("-Xmn128m");
|
||||
res.addUnstableDefault("UseAdaptiveSizePolicy", false);
|
||||
res.addUnstableDefault("OmitStackTraceInFastThrow", false);
|
||||
res.addUnstableDefault("DontCompileHugeMethods", false);
|
||||
res.addDefault("-Xmn", "128m");
|
||||
|
||||
// As 32-bit JVM allocate 320KB for stack by default rather than 64-bit version allocating 1MB,
|
||||
// causing Minecraft 1.13 crashed accounting for java.lang.StackOverflowError.
|
||||
if (options.getJava().getPlatform() == Platform.BIT_32) {
|
||||
res.add("-Xss1M");
|
||||
res.addDefault("-Xss", "1m");
|
||||
}
|
||||
|
||||
if (options.getMaxMemory() != null && options.getMaxMemory() > 0)
|
||||
res.add("-Xmx" + options.getMaxMemory() + "m");
|
||||
res.addDefault("-Xmx", options.getMaxMemory() + "m");
|
||||
|
||||
if (options.getMinMemory() != null && options.getMinMemory() > 0)
|
||||
res.add("-Xms" + options.getMinMemory() + "m");
|
||||
res.addDefault("-Xms", options.getMinMemory() + "m");
|
||||
|
||||
if (options.getJava().getParsedVersion() == JavaVersion.JAVA_16)
|
||||
res.add("--illegal-access=permit");
|
||||
res.addDefault("--illegal-access=", "permit");
|
||||
|
||||
res.add("-Dfml.ignoreInvalidMinecraftCertificates=true");
|
||||
res.add("-Dfml.ignorePatchDiscrepancies=true");
|
||||
res.addDefault("-Dfml.ignoreInvalidMinecraftCertificates=", "true");
|
||||
res.addDefault("-Dfml.ignorePatchDiscrepancies=", "true");
|
||||
}
|
||||
|
||||
Proxy proxy = options.getProxy();
|
||||
@@ -144,24 +149,18 @@ public class DefaultLauncher extends Launcher {
|
||||
String host = address.getHostString();
|
||||
int port = address.getPort();
|
||||
if (proxy.type() == Proxy.Type.HTTP) {
|
||||
res.add("-Dhttp.proxyHost=" + host);
|
||||
res.add("-Dhttp.proxyPort=" + port);
|
||||
res.add("-Dhttps.proxyHost=" + host);
|
||||
res.add("-Dhttps.proxyPort=" + port);
|
||||
res.addDefault("-Dhttp.proxyHost=", host);
|
||||
res.addDefault("-Dhttp.proxyPort=", String.valueOf(port));
|
||||
res.addDefault("-Dhttps.proxyHost=", host);
|
||||
res.addDefault("-Dhttps.proxyPort=", String.valueOf(port));
|
||||
} else if (proxy.type() == Proxy.Type.SOCKS) {
|
||||
res.add("-DsocksProxyHost=" + host);
|
||||
res.add("-DsocksProxyPort=" + port);
|
||||
res.addDefault("-DsocksProxyHost=", host);
|
||||
res.addDefault("-DsocksProxyPort=", String.valueOf(port));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LinkedList<String> classpath = new LinkedList<>();
|
||||
for (Library library : version.getLibraries())
|
||||
if (library.appliesToCurrentEnvironment() && !library.isNative()) {
|
||||
File f = repository.getLibraryFile(version, library);
|
||||
if (f.exists() && f.isFile())
|
||||
classpath.add(f.getAbsolutePath());
|
||||
}
|
||||
List<String> classpath = repository.getClasspath(version);
|
||||
|
||||
File jar = repository.getVersionJar(version);
|
||||
if (!jar.exists() || !jar.isFile())
|
||||
@@ -175,7 +174,6 @@ public class DefaultLauncher extends Launcher {
|
||||
configuration.put("${natives_directory}", nativeFolder.getAbsolutePath());
|
||||
configuration.put("${game_assets}", gameAssets.getAbsolutePath());
|
||||
configuration.put("${assets_root}", gameAssets.getAbsolutePath());
|
||||
configuration.put("${libraries_directory}", repository.getLibrariesDirectory(version).getAbsolutePath());
|
||||
|
||||
res.addAll(Arguments.parseArguments(version.getArguments().map(Arguments::getJvm).orElseGet(this::getDefaultJVMArguments), configuration));
|
||||
if (authInfo.getArguments() != null && authInfo.getArguments().getJvm() != null && !authInfo.getArguments().getJvm().isEmpty())
|
||||
@@ -220,7 +218,7 @@ public class DefaultLauncher extends Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
res.addAllWithoutParsing(options.getGameArguments());
|
||||
res.addAllWithoutParsing(Arguments.parseStringArguments(options.getGameArguments(), configuration));
|
||||
|
||||
res.removeIf(it -> getForbiddens().containsKey(it) && getForbiddens().get(it).get());
|
||||
return res;
|
||||
@@ -281,6 +279,7 @@ public class DefaultLauncher extends Launcher {
|
||||
|
||||
protected Map<String, String> getConfigurations() {
|
||||
return mapOf(
|
||||
// defined by Minecraft official launcher
|
||||
pair("${auth_player_name}", authInfo.getUsername()),
|
||||
pair("${auth_session}", authInfo.getAccessToken()),
|
||||
pair("${auth_access_token}", authInfo.getAccessToken()),
|
||||
@@ -293,13 +292,30 @@ public class DefaultLauncher extends Launcher {
|
||||
pair("${assets_index_name}", version.getAssetIndex().getId()),
|
||||
pair("${user_properties}", authInfo.getUserProperties()),
|
||||
pair("${resolution_width}", options.getWidth().toString()),
|
||||
pair("${resolution_height}", options.getHeight().toString())
|
||||
pair("${resolution_height}", options.getHeight().toString()),
|
||||
pair("${library_directory}", repository.getLibrariesDirectory(version).getAbsolutePath()),
|
||||
pair("${classpath_separator}", OperatingSystem.PATH_SEPARATOR),
|
||||
pair("${primary_jar}", repository.getVersionJar(version).getAbsolutePath()),
|
||||
pair("${language}", Locale.getDefault().toString()),
|
||||
|
||||
// defined by HMCL
|
||||
// libraries_directory stands for historical reasons here. We don't know the official launcher
|
||||
// had already defined "library_directory" as the placeholder for path to ".minecraft/libraries"
|
||||
// when we propose this placeholder.
|
||||
pair("${libraries_directory}", repository.getLibrariesDirectory(version).getAbsolutePath()),
|
||||
// file_separator is used in -DignoreList
|
||||
pair("${file_separator}", OperatingSystem.FILE_SEPARATOR)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedProcess launch() throws IOException, InterruptedException {
|
||||
File nativeFolder = repository.getNativeDirectory(version.getId());
|
||||
File nativeFolder = null;
|
||||
if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
|
||||
nativeFolder = repository.getNativeDirectory(version.getId());
|
||||
} else {
|
||||
nativeFolder = new File(options.getNativesDir());
|
||||
}
|
||||
|
||||
// To guarantee that when failed to generate launch command line, we will not call pre-launch command
|
||||
List<String> rawCommandLine = generateCommandLine(nativeFolder).asList();
|
||||
@@ -308,7 +324,9 @@ public class DefaultLauncher extends Launcher {
|
||||
throw new IllegalStateException("Illegal command line " + rawCommandLine);
|
||||
}
|
||||
|
||||
decompressNatives(nativeFolder);
|
||||
if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
File runDirectory = repository.getRunDirectory(version.getId());
|
||||
|
||||
@@ -348,8 +366,16 @@ public class DefaultLauncher extends Launcher {
|
||||
public void makeLaunchScript(File scriptFile) throws IOException {
|
||||
boolean isWindows = OperatingSystem.WINDOWS == OperatingSystem.CURRENT_OS;
|
||||
|
||||
File nativeFolder = repository.getNativeDirectory(version.getId());
|
||||
decompressNatives(nativeFolder);
|
||||
File nativeFolder = null;
|
||||
if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
|
||||
nativeFolder = repository.getNativeDirectory(version.getId());
|
||||
} else {
|
||||
nativeFolder = new File(options.getNativesDir());
|
||||
}
|
||||
|
||||
if (options.getNativesDirType() == NativesDirectoryType.VERSION_FOLDER) {
|
||||
decompressNatives(nativeFolder);
|
||||
}
|
||||
|
||||
if (isWindows && !FileUtils.getExtension(scriptFile).equals("bat"))
|
||||
throw new IllegalArgumentException("The extension of " + scriptFile + " is not 'bat' in Windows");
|
||||
|
||||
@@ -68,7 +68,7 @@ public final class FabricModMetadata {
|
||||
FabricModMetadata metadata = JsonUtils.fromNonNullJson(FileUtils.readText(mcmod), FabricModMetadata.class);
|
||||
String authors = metadata.authors == null ? "" : metadata.authors.stream().map(author -> author.name).collect(Collectors.joining(", "));
|
||||
return new ModInfo(modManager, modFile, metadata.name, new ModInfo.Description(metadata.description),
|
||||
authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "");
|
||||
authors, metadata.version, "", metadata.contact != null ? metadata.contact.getOrDefault("homepage", "") : "");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,9 +67,9 @@ public final class ModInfo implements Comparable<ModInfo> {
|
||||
|
||||
try {
|
||||
if (get())
|
||||
ModInfo.this.file = modManager.enableMod(path);
|
||||
ModInfo.this.file = modManager.enableMod(path);
|
||||
else
|
||||
ModInfo.this.file = modManager.disableMod(path);
|
||||
ModInfo.this.file = modManager.disableMod(path);
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to invert state of mod file " + path, e);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ public final class ModpackConfiguration<T> implements Validation {
|
||||
private final List<FileInformation> overrides;
|
||||
|
||||
public ModpackConfiguration() {
|
||||
this(null, null, "", null, Collections.emptyList());
|
||||
this(null, null, "", null, Collections.emptyList());
|
||||
}
|
||||
|
||||
public ModpackConfiguration(T manifest, String type, String name, String version, List<FileInformation> overrides) {
|
||||
@@ -110,6 +110,7 @@ public final class ModpackConfiguration<T> implements Validation {
|
||||
|
||||
/**
|
||||
* The relative path to Minecraft run directory
|
||||
*
|
||||
* @return the relative path to Minecraft run directory.
|
||||
*/
|
||||
public String getPath() {
|
||||
|
||||
@@ -83,6 +83,8 @@ public final class CurseInstallTask extends Task<Void> {
|
||||
for (CurseManifestModLoader modLoader : manifest.getMinecraft().getModLoaders())
|
||||
if (modLoader.getId().startsWith("forge-"))
|
||||
builder.version("forge", modLoader.getId().substring("forge-".length()));
|
||||
else if (modLoader.getId().startsWith("fabric-"))
|
||||
builder.version("fabric", modLoader.getId().substring("fabric-".length()));
|
||||
dependents.add(builder.buildAsync());
|
||||
|
||||
onDone().register(event -> {
|
||||
|
||||
@@ -23,7 +23,7 @@ import org.jackhuang.hmcl.util.Immutable;
|
||||
/**
|
||||
* CurseMetaMod is JSON structure for
|
||||
* https://cursemeta.dries007.net/<projectID>/<fileID>.json
|
||||
* https://addons-ecs.forgesvc.net/api/v2/addon/<projectID>/file/<fileID>
|
||||
* https://addons-ecs.forgesvc.net/api/v2/addon/<projectID>/file/<fileID>
|
||||
*/
|
||||
@Immutable
|
||||
public final class CurseMetaMod {
|
||||
|
||||
@@ -202,7 +202,7 @@ public class McbbsModpackManifest implements Validation {
|
||||
}
|
||||
|
||||
public static final class Settings {
|
||||
@SerializedName("install_modes")
|
||||
@SerializedName("install_mods")
|
||||
private final boolean installMods;
|
||||
|
||||
@SerializedName("install_resourcepack")
|
||||
|
||||
@@ -79,8 +79,6 @@ public final class MultiMCManifest {
|
||||
@SerializedName("suggests")
|
||||
private final String suggests;
|
||||
|
||||
|
||||
|
||||
public MultiMCManifestCachedRequires(String equalsVersion, String uid, String suggests) {
|
||||
this.equalsVersion = equalsVersion;
|
||||
this.uid = uid;
|
||||
|
||||
+5
-5
@@ -56,7 +56,7 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
private final DefaultGameRepository repository;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
|
||||
|
||||
public MultiMCModpackInstallTask(DefaultDependencyManager dependencyManager, File zipFile, Modpack modpack, MultiMCInstanceConfiguration manifest, String name) {
|
||||
this.zipFile = zipFile;
|
||||
this.modpack = modpack;
|
||||
@@ -96,7 +96,7 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
repository.removeVersionFromDisk(name);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
@@ -138,7 +138,7 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
public List<Task<?>> getDependents() {
|
||||
return dependents;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
Version version = repository.readVersionJson(name);
|
||||
@@ -169,11 +169,11 @@ public final class MultiMCModpackInstallTask extends Task<Void> {
|
||||
|
||||
Path libraries = root.resolve("libraries");
|
||||
if (Files.exists(libraries))
|
||||
FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).toPath().resolve("libraries"));
|
||||
FileUtils.copyDirectory(libraries, repository.getVersionRoot(name).toPath().resolve("libraries"));
|
||||
|
||||
Path jarmods = root.resolve("jarmods");
|
||||
if (Files.exists(jarmods))
|
||||
FileUtils.copyDirectory(jarmods, repository.getVersionRoot(name).toPath().resolve("jarmods"));
|
||||
FileUtils.copyDirectory(jarmods, repository.getVersionRoot(name).toPath().resolve("jarmods"));
|
||||
}
|
||||
|
||||
dependencies.add(repository.saveAsync(version));
|
||||
|
||||
@@ -280,7 +280,6 @@ public abstract class Task<T> {
|
||||
*/
|
||||
public void postExecute() throws Exception {}
|
||||
|
||||
|
||||
/**
|
||||
* The collection of sub-tasks that should execute **before** this task running.
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,7 @@ public enum Log4jLevel {
|
||||
public boolean lessOrEqual(Log4jLevel level) {
|
||||
return this.level <= level.level;
|
||||
}
|
||||
|
||||
public static final Pattern MINECRAFT_LOGGER = Pattern.compile("\\[(?<timestamp>[0-9:]+)] \\[[^/]+/(?<level>[^]]+)]");
|
||||
public static final Pattern MINECRAFT_LOGGER_CATEGORY = Pattern.compile("\\[(?<timestamp>[0-9:]+)] \\[[^/]+/(?<level>[^]]+)] \\[(?<category>[^]]+)]");
|
||||
public static final String JAVA_SYMBOL = "([a-zA-Z_$][a-zA-Z\\d_$]*\\.)+[a-zA-Z_$][a-zA-Z\\d_$]*";
|
||||
|
||||
@@ -31,6 +31,8 @@ import java.util.logging.*;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Logging {
|
||||
private Logging() {
|
||||
}
|
||||
|
||||
public static final Logger LOG = Logger.getLogger("HMCL");
|
||||
private static ByteArrayOutputStream storedLogs = new ByteArrayOutputStream();
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2021 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
/**
|
||||
* @author Glavo
|
||||
*/
|
||||
public final class Pack200Utils {
|
||||
private Pack200Utils() {
|
||||
}
|
||||
|
||||
private static final String[] IMPL_NAMES = {
|
||||
"java.util.jar.Pack200",
|
||||
"org.glavo.pack200.Pack200",
|
||||
"io.pack200.Pack200"
|
||||
};
|
||||
|
||||
private static final MethodHandle newUnpackerHandle;
|
||||
private static final MethodHandle unpackHandle;
|
||||
private static final MethodHandle unpackFileHandle;
|
||||
|
||||
static {
|
||||
Class<?> pack200Class = null;
|
||||
Class<?> unpackerClass = null;
|
||||
|
||||
for (String implName : IMPL_NAMES) {
|
||||
try {
|
||||
pack200Class = Class.forName(implName);
|
||||
unpackerClass = Class.forName(implName + "$Unpacker");
|
||||
break;
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
if (pack200Class == null) {
|
||||
LOG.warning("Pack200 not found");
|
||||
newUnpackerHandle = null;
|
||||
unpackHandle = null;
|
||||
unpackFileHandle = null;
|
||||
} else {
|
||||
final MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||
MethodHandle newUnpacker = null;
|
||||
MethodHandle unpack = null;
|
||||
MethodHandle unpackFile = null;
|
||||
try {
|
||||
newUnpacker = lookup.findStatic(pack200Class, "newUnpacker", MethodType.methodType(unpackerClass));
|
||||
unpack = lookup.findVirtual(unpackerClass, "unpack", MethodType.methodType(void.class, InputStream.class, JarOutputStream.class));
|
||||
unpackFile = lookup.findVirtual(unpackerClass, "unpack", MethodType.methodType(void.class, File.class, JarOutputStream.class));
|
||||
} catch (Throwable e) {
|
||||
LOG.log(Level.WARNING, "Failed to find pack200 methods", e);
|
||||
}
|
||||
|
||||
if (newUnpacker != null) {
|
||||
newUnpackerHandle = newUnpacker;
|
||||
unpackHandle = unpack;
|
||||
unpackFileHandle = unpackFile;
|
||||
} else {
|
||||
newUnpackerHandle = null;
|
||||
unpackHandle = null;
|
||||
unpackFileHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static boolean isSupported() {
|
||||
return newUnpackerHandle != null;
|
||||
}
|
||||
|
||||
public static void unpack(InputStream in, JarOutputStream out) throws IOException {
|
||||
if (newUnpackerHandle == null) {
|
||||
throw new UnsupportedOperationException("Pack200");
|
||||
}
|
||||
|
||||
try {
|
||||
unpackHandle.invoke(newUnpackerHandle.invoke(), in, out);
|
||||
} catch (IOException | RuntimeException | Error e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void unpack(File in, JarOutputStream out) throws IOException {
|
||||
if (newUnpackerHandle == null) {
|
||||
throw new UnsupportedOperationException("Pack200");
|
||||
}
|
||||
|
||||
try {
|
||||
unpackFileHandle.invoke(newUnpackerHandle.invoke(), in, out);
|
||||
} catch (IOException | RuntimeException | Error e) {
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import java.util.Objects;
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class Pair<K, V> implements Map.Entry<K, V> {
|
||||
public final class Pair<K, V> implements Map.Entry<K, V> {
|
||||
|
||||
public static <K, V> Pair<K, V> pair(K key, V value) {
|
||||
return new Pair<>(key, value);
|
||||
|
||||
@@ -27,13 +27,16 @@ import java.util.function.Predicate;
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class ReflectionHelper {
|
||||
private ReflectionHelper() {
|
||||
}
|
||||
|
||||
private static Method accessible0;
|
||||
|
||||
static {
|
||||
try {
|
||||
accessible0 = AccessibleObject.class.getDeclaredMethod("setAccessible0", boolean.class);
|
||||
accessible0.setAccessible(true);
|
||||
} catch (Throwable ex) {
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +46,7 @@ public final class ReflectionHelper {
|
||||
|
||||
/**
|
||||
* Get caller, this method is caller sensitive.
|
||||
*
|
||||
* @param packageFilter returns false if we consider the given package is internal calls, not the caller
|
||||
* @return the caller, method name, source file, line number
|
||||
*/
|
||||
|
||||
@@ -26,4 +26,4 @@ public interface ExceptionalBiFunction<T, U, R, E extends Exception> {
|
||||
requireNonNull(after);
|
||||
return (t, u) -> after.apply(apply(t, u));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,6 @@ public class JsonTypeAdapterFactory implements TypeAdapterFactory {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private <T> TypeAdapter<T> createForJsonSubtype(Gson gson, TypeToken<T> type) {
|
||||
Class<? super T> rawType = type.getRawType();
|
||||
if (rawType.getSuperclass() == null) return null;
|
||||
|
||||
@@ -29,7 +29,7 @@ public interface Validation {
|
||||
/**
|
||||
* 1. Check some non-null fields and;
|
||||
* 2. Check strings and;
|
||||
* 3. Check generic type of lists <T> and maps <K, V> are correct.
|
||||
* 3. Check generic type of lists <T> and maps <K, V> are correct.
|
||||
*
|
||||
* Will be called immediately after initialization.
|
||||
* Throw an exception when values are malformed.
|
||||
|
||||
@@ -21,9 +21,9 @@ import java.io.IOException;
|
||||
|
||||
public class ChecksumMismatchException extends IOException {
|
||||
|
||||
private String algorithm;
|
||||
private String expectedChecksum;
|
||||
private String actualChecksum;
|
||||
private final String algorithm;
|
||||
private final String expectedChecksum;
|
||||
private final String actualChecksum;
|
||||
|
||||
public ChecksumMismatchException(String algorithm, String expectedChecksum, String actualChecksum) {
|
||||
super("Incorrect checksum (" + algorithm + "), expected: " + expectedChecksum + ", actual: " + actualChecksum);
|
||||
|
||||
@@ -124,10 +124,11 @@ public final class FileUtils {
|
||||
|
||||
/**
|
||||
* Write plain text to file. Characters are encoded into bytes using UTF-8.
|
||||
*
|
||||
* <p>
|
||||
* We don't care about platform difference of line separator. Because readText accept all possibilities of line separator.
|
||||
* It will create the file if it does not exist, or truncate the existing file to empty for rewriting.
|
||||
* All characters in text will be written into the file in binary format. Existing data will be erased.
|
||||
*
|
||||
* @param file the path to the file
|
||||
* @param text the text being written to file
|
||||
* @throws IOException if an I/O error occurs
|
||||
@@ -138,12 +139,13 @@ public final class FileUtils {
|
||||
|
||||
/**
|
||||
* Write plain text to file.
|
||||
*
|
||||
* <p>
|
||||
* We don't care about platform difference of line separator. Because readText accept all possibilities of line separator.
|
||||
* It will create the file if it does not exist, or truncate the existing file to empty for rewriting.
|
||||
* All characters in text will be written into the file in binary format. Existing data will be erased.
|
||||
* @param file the path to the file
|
||||
* @param text the text being written to file
|
||||
*
|
||||
* @param file the path to the file
|
||||
* @param text the text being written to file
|
||||
* @param charset the charset to use for encoding
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@@ -155,7 +157,8 @@ public final class FileUtils {
|
||||
* Write byte array to file.
|
||||
* It will create the file if it does not exist, or truncate the existing file to empty for rewriting.
|
||||
* All bytes in byte array will be written into the file in binary format. Existing data will be erased.
|
||||
* @param file the path to the file
|
||||
*
|
||||
* @param file the path to the file
|
||||
* @param array the data being written to file
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@@ -192,7 +195,7 @@ public final class FileUtils {
|
||||
* Copy directory.
|
||||
* Paths of all files relative to source directory will be the same as the ones relative to destination directory.
|
||||
*
|
||||
* @param src the source directory.
|
||||
* @param src the source directory.
|
||||
* @param dest the destination directory, which will be created if not existing.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
@@ -201,7 +204,7 @@ public final class FileUtils {
|
||||
}
|
||||
|
||||
public static void copyDirectory(Path src, Path dest, Predicate<String> filePredicate) throws IOException {
|
||||
Files.walkFileTree(src, new SimpleFileVisitor<Path>(){
|
||||
Files.walkFileTree(src, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if (!filePredicate.test(src.relativize(file).toString())) {
|
||||
@@ -228,7 +231,7 @@ public final class FileUtils {
|
||||
|
||||
/**
|
||||
* Move file to trash.
|
||||
*
|
||||
* <p>
|
||||
* This method is only implemented in Java 9. Please check we are using Java 9 by invoking isMovingToTrashSupported.
|
||||
* Example:
|
||||
* <pre>{@code
|
||||
@@ -236,9 +239,10 @@ public final class FileUtils {
|
||||
* FileUtils.moveToTrash(file);
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param file the file being moved to trash.
|
||||
* @see FileUtils#isMovingToTrashSupported()
|
||||
* @return false if moveToTrash does not exist, or platform does not support Desktop.Action.MOVE_TO_TRASH
|
||||
* @see FileUtils#isMovingToTrashSupported()
|
||||
*/
|
||||
public static boolean moveToTrash(File file) {
|
||||
try {
|
||||
@@ -253,6 +257,7 @@ public final class FileUtils {
|
||||
|
||||
/**
|
||||
* Check if {@code java.awt.Desktop.moveToTrash} exists.
|
||||
*
|
||||
* @return true if the method exists.
|
||||
*/
|
||||
public static boolean isMovingToTrashSupported() {
|
||||
|
||||
@@ -31,6 +31,8 @@ import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public final class JarUtils {
|
||||
private JarUtils() {
|
||||
}
|
||||
|
||||
public static Optional<Path> thisJar() {
|
||||
CodeSource codeSource = JarUtils.class.getProtectionDomain().getCodeSource();
|
||||
|
||||
@@ -86,6 +86,7 @@ public final class NetworkUtils {
|
||||
break;
|
||||
case '?':
|
||||
left = false;
|
||||
// fallthrough
|
||||
default:
|
||||
if (ch >= 0x80)
|
||||
sb.append(encodeURL(Character.toString(ch)));
|
||||
|
||||
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user