Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69c0382bd5 | ||
|
|
1d80cd9fa2 | ||
|
|
3721842c8b | ||
|
|
47d8b59b93 | ||
|
|
b5fbc69ee6 | ||
|
|
9d95d3ea66 | ||
|
|
06b3ec8337 | ||
|
|
fabb6b50b4 | ||
|
|
ca627b8a2d |
No files matched your search
+5
-5
@@ -24,9 +24,9 @@ import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
plugins {
|
||||
id "edu.sc.seis.macAppBundle" version "2.1.6"
|
||||
id "me.tatarka.retrolambda" version "3.5.0"
|
||||
id 'edu.sc.seis.launch4j' version '2.3.0'
|
||||
id "edu.sc.seis.macAppBundle" version "2.1.8"
|
||||
id "me.tatarka.retrolambda" version "3.7.0"
|
||||
id 'edu.sc.seis.launch4j' version '2.4.2'
|
||||
//id "org.jetbrains.kotlin.jvm" version "1.1.2-2"
|
||||
}
|
||||
|
||||
@@ -38,11 +38,11 @@ def buildnumber = System.getenv("TRAVIS_BUILD_NUMBER")
|
||||
if (buildnumber == null)
|
||||
buildnumber = System.getenv("BUILD_NUMBER")
|
||||
if (buildnumber == null)
|
||||
buildnumber = "40"
|
||||
buildnumber = "52"
|
||||
|
||||
def versionroot = System.getenv("VERSION_ROOT")
|
||||
if (versionroot == null)
|
||||
versionroot = "2.7.8"
|
||||
versionroot = "2.7.9"
|
||||
|
||||
String mavenGroupId = 'HMCL'
|
||||
String mavenVersion = versionroot + '.' + buildnumber
|
||||
|
||||
@@ -47,7 +47,6 @@ import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.ui.MainFrame;
|
||||
import org.jackhuang.hmcl.util.CrashReporter;
|
||||
import org.jackhuang.hmcl.util.DefaultPlugin;
|
||||
import org.jackhuang.hmcl.util.MathUtils;
|
||||
import org.jackhuang.hmcl.util.ui.MessageBox;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.util.lang.SupportedLocales;
|
||||
@@ -101,6 +100,11 @@ public final class Main {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
{
|
||||
if (new File("").getAbsolutePath().contains("!")) {
|
||||
MessageBox.show("HMCL cannot run at such path that contains '!'");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
File file = new File("hmcl.log").getAbsoluteFile();
|
||||
if (!file.exists() && !file.createNewFile())
|
||||
|
||||
@@ -69,7 +69,6 @@ import org.jackhuang.hmcl.api.auth.IAuthenticator;
|
||||
import org.jackhuang.hmcl.api.ui.Theme;
|
||||
import org.jackhuang.hmcl.api.ui.TopTabPage;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
@@ -256,7 +255,7 @@ public final class MainFrame extends DraggableFrame implements IRepaint {
|
||||
|
||||
realPanel.setBounds(1, 0, 800, 511);
|
||||
add(realPanel);
|
||||
|
||||
|
||||
reloadColor(Settings.getInstance().getTheme());
|
||||
}
|
||||
|
||||
@@ -396,11 +395,10 @@ public final class MainFrame extends DraggableFrame implements IRepaint {
|
||||
public void reloadColor(Theme t) {
|
||||
if (isShowedMessage)
|
||||
return;
|
||||
for (Map.Entry<String, String> entry : t.settings.entrySet()) {
|
||||
for (Map.Entry<String, String> entry : t.settings.entrySet())
|
||||
if (entry.getValue().startsWith("#"))
|
||||
UIManager.put(entry.getKey(), GraphicsUtils.getWebColor(entry.getValue()));
|
||||
}
|
||||
|
||||
|
||||
initBorderColor();
|
||||
if (border != null)
|
||||
border.setColor(borderColor);
|
||||
@@ -423,14 +421,14 @@ public final class MainFrame extends DraggableFrame implements IRepaint {
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
if (!enableShadow)
|
||||
int off = enableShadow ? 16 : 0;
|
||||
int width = this.getWidth();
|
||||
int height = this.getHeight();
|
||||
int contentWidth = width - off - off;
|
||||
int contentHeight = height - off - off;
|
||||
if (!enableShadow || contentWidth <= 0 || contentHeight <= 0)
|
||||
paintImpl(g);
|
||||
else {
|
||||
int off = enableShadow ? 16 : 0;
|
||||
int width = this.getWidth();
|
||||
int height = this.getHeight();
|
||||
int contentWidth = width - off - off;
|
||||
int contentHeight = height - off - off;
|
||||
BufferedImage contentImage = new BufferedImage(contentWidth,
|
||||
contentHeight, Transparency.OPAQUE);
|
||||
Graphics2D contentG2d = contentImage.createGraphics();
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.api.auth.UserProfileProvider;
|
||||
import org.jackhuang.hmcl.api.game.LaunchOptions;
|
||||
@@ -36,10 +37,10 @@ public class HMCLMinecraftLoader extends MinecraftLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void appendJVMArgs(List<String> list) {
|
||||
super.appendJVMArgs(list);
|
||||
|
||||
list.add("-Dminecraft.launcher.version=" + Main.LAUNCHER_VERSION);
|
||||
list.add("-Dminecraft.launcher.brand=" + Main.LAUNCHER_NAME);
|
||||
protected Map<String, String> getConfigurations() {
|
||||
Map<String, String> map = super.getConfigurations();
|
||||
map.put("${launcher_version}", Main.LAUNCHER_VERSION);
|
||||
map.put("${launcher_name}", Main.LAUNCHER_NAME);
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,6 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
@@ -27,20 +24,15 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import org.jackhuang.hmcl.api.HMCLApi;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.api.event.process.JavaProcessStoppedEvent;
|
||||
import org.jackhuang.hmcl.api.func.Consumer;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.util.log.Level;
|
||||
import org.jackhuang.hmcl.util.sys.ProcessMonitor;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.XMLReader;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
import org.xml.sax.helpers.XMLReaderFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -48,70 +40,28 @@ import org.xml.sax.helpers.XMLReaderFactory;
|
||||
*/
|
||||
public class Log4jHandler extends Thread implements Consumer<JavaProcessStoppedEvent> {
|
||||
|
||||
XMLReader reader;
|
||||
ProcessMonitor monitor;
|
||||
PipedInputStream inputStream;
|
||||
PipedOutputStream outputStream;
|
||||
List<Pair<String, String>> forbiddenTokens = new LinkedList<>();
|
||||
AtomicBoolean interrupted = new AtomicBoolean(false);
|
||||
ExecutorService executorService = Executors.newSingleThreadExecutor();
|
||||
boolean enabled = true;
|
||||
|
||||
public Log4jHandler(ProcessMonitor monitor, PipedOutputStream outputStream, boolean enabled) throws ParserConfigurationException, IOException, SAXException {
|
||||
reader = XMLReaderFactory.createXMLReader();
|
||||
inputStream = new PipedInputStream(outputStream);
|
||||
this.outputStream = outputStream;
|
||||
public Log4jHandler(ProcessMonitor monitor, PipedOutputStream outputStream, boolean enabled) {
|
||||
this.monitor = monitor;
|
||||
this.enabled = enabled;
|
||||
|
||||
|
||||
HMCLApi.EVENT_BUS.channel(JavaProcessStoppedEvent.class).register((Consumer<JavaProcessStoppedEvent>) this);
|
||||
}
|
||||
|
||||
|
||||
public void addForbiddenToken(String token, String replacement) {
|
||||
forbiddenTokens.add(new Pair<>(token, replacement));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
setName("log4j-handler");
|
||||
if (enabled)
|
||||
newLogLine("<output>");
|
||||
reader.setContentHandler(new Log4jHandlerImpl());
|
||||
reader.parse(new InputSource(inputStream));
|
||||
} catch (InterruptedIOException e) {
|
||||
interrupted.set(true);
|
||||
} catch (SAXException | IOException e) {
|
||||
throw new Error(e);
|
||||
} finally {
|
||||
executorService.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(JavaProcessStoppedEvent t) {
|
||||
if (!interrupted.get())
|
||||
return;
|
||||
if (t.getSource() == monitor) {
|
||||
try {
|
||||
executorService.submit(() -> {
|
||||
if (!interrupted.get()) {
|
||||
if (enabled) {
|
||||
Future f = newLogLine("</output>");
|
||||
if (f != null)
|
||||
f.get();
|
||||
}
|
||||
outputStream.close();
|
||||
join();
|
||||
}
|
||||
return null;
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
HMCLog.err("Please contact author", e);
|
||||
} finally {
|
||||
executorService.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,32 +76,12 @@ public class Log4jHandler extends Thread implements Consumer<JavaProcessStoppedE
|
||||
* @param content The content to be written to the log
|
||||
*/
|
||||
public Future newLogLine(String content) {
|
||||
try {
|
||||
return executorService.submit(() -> {
|
||||
if (enabled)
|
||||
try {
|
||||
String log = content;
|
||||
if (!log.trim().startsWith("<")) { // without logging configuration.
|
||||
log = "<![CDATA[" + log.replace("]]>", "") + "]]>";
|
||||
}
|
||||
outputStream.write(log
|
||||
.replace("log4j:Event", "log4j_Event")
|
||||
.replace("log4j:Message", "log4j_Message")
|
||||
.replace("log4j:Throwable", "log4j_Throwable")
|
||||
.getBytes());
|
||||
outputStream.flush();
|
||||
} catch (IOException ignore) { // won't happen
|
||||
throw new Error(ignore);
|
||||
}
|
||||
else
|
||||
printlnImpl(content, Level.guessLevel(content));
|
||||
});
|
||||
} catch (RejectedExecutionException e) {
|
||||
return null;
|
||||
}
|
||||
printlnImpl(content, Level.guessLevel(content));
|
||||
return null;
|
||||
}
|
||||
|
||||
class Log4jHandlerImpl extends DefaultHandler {
|
||||
|
||||
private final SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");
|
||||
|
||||
String date = "", thread = "", logger = "";
|
||||
@@ -168,7 +98,7 @@ public class Log4jHandler extends Thread implements Consumer<JavaProcessStoppedE
|
||||
date = df.format(d);
|
||||
try {
|
||||
l = Level.valueOf(attributes.getValue("level"));
|
||||
} catch(IllegalArgumentException e) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
l = Level.INFO;
|
||||
}
|
||||
thread = attributes.getValue("thread");
|
||||
@@ -197,7 +127,8 @@ public class Log4jHandler extends Thread implements Consumer<JavaProcessStoppedE
|
||||
@Override
|
||||
public void characters(char[] ch, int start, int length) throws SAXException {
|
||||
String line = new String(ch, start, length);
|
||||
if (line.trim().isEmpty()) return;
|
||||
if (line.trim().isEmpty())
|
||||
return;
|
||||
if (readingMessage)
|
||||
message.append(line).append(C.LINE_SEPARATOR);
|
||||
else
|
||||
@@ -208,7 +139,7 @@ public class Log4jHandler extends Thread implements Consumer<JavaProcessStoppedE
|
||||
printlnImpl(message, l);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void printlnImpl(String message, Level l) {
|
||||
for (Pair<String, String> entry : forbiddenTokens)
|
||||
message = message.replace(entry.key, entry.value);
|
||||
|
||||
@@ -86,12 +86,9 @@ public abstract class AbstractMinecraftLoader implements IMinecraftLoader {
|
||||
appendJVMArgs(res);
|
||||
|
||||
if (jv == null || !jv.isEarlyAccess()) {
|
||||
if (OS.os() == OS.WINDOWS)
|
||||
res.add("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump");
|
||||
if (jv != null && jv.getParsedVersion() >= JdkVersion.JAVA_17)
|
||||
res.add("-XX:+UseG1GC");
|
||||
else
|
||||
res.add("-Xincgc");
|
||||
|
||||
res.add("-XX:-UseAdaptiveSizePolicy");
|
||||
res.add("-XX:-OmitStackTraceInFastThrow");
|
||||
|
||||
@@ -131,7 +128,6 @@ public abstract class AbstractMinecraftLoader implements IMinecraftLoader {
|
||||
res.add(a);
|
||||
}
|
||||
|
||||
res.add("-Djava.library.path=" + service.version().getDecompressNativesToLocation(version).getAbsolutePath());
|
||||
res.add("-Dfml.ignoreInvalidMinecraftCertificates=true");
|
||||
res.add("-Dfml.ignorePatchDiscrepancies=true");
|
||||
|
||||
@@ -150,13 +146,6 @@ public abstract class AbstractMinecraftLoader implements IMinecraftLoader {
|
||||
|
||||
HMCLog.log("On making launcher args.");
|
||||
|
||||
if (StrUtils.isNotBlank(options.getHeight()) && StrUtils.isNotBlank(options.getWidth())) {
|
||||
res.add("--height");
|
||||
res.add(options.getHeight());
|
||||
res.add("--width");
|
||||
res.add(options.getWidth());
|
||||
}
|
||||
|
||||
String serverIp = options.getServerIp();
|
||||
if (StrUtils.isNotBlank(serverIp)) {
|
||||
String[] args = serverIp.split(":");
|
||||
|
||||
@@ -19,7 +19,6 @@ package org.jackhuang.hmcl.core.launch;
|
||||
|
||||
import org.jackhuang.hmcl.api.game.LaunchOptions;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import org.jackhuang.hmcl.core.download.DownloadLibraryJob;
|
||||
import org.jackhuang.hmcl.api.auth.LoginInfo;
|
||||
|
||||
@@ -21,8 +21,11 @@ import org.jackhuang.hmcl.api.game.LaunchOptions;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.util.sys.OS;
|
||||
@@ -30,7 +33,8 @@ import org.jackhuang.hmcl.core.GameException;
|
||||
import org.jackhuang.hmcl.api.auth.UserProfileProvider;
|
||||
import org.jackhuang.hmcl.core.version.MinecraftLibrary;
|
||||
import org.jackhuang.hmcl.core.service.IMinecraftService;
|
||||
import org.jackhuang.hmcl.core.version.LoggingInfo;
|
||||
import org.jackhuang.hmcl.core.version.Argument;
|
||||
import org.jackhuang.hmcl.core.version.Arguments;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -40,10 +44,28 @@ public class MinecraftLoader extends AbstractMinecraftLoader {
|
||||
|
||||
public MinecraftLoader(LaunchOptions p, IMinecraftService provider, UserProfileProvider lr) throws GameException {
|
||||
super(p, provider, p.getLaunchVersion(), lr);
|
||||
|
||||
if (version.arguments == null)
|
||||
version.arguments = new Arguments();
|
||||
if (version.arguments.game == null)
|
||||
version.arguments.game = getDefaultGameArguments();
|
||||
if (version.arguments.jvm == null)
|
||||
version.arguments.jvm = getDefaultJVMArguments();
|
||||
}
|
||||
|
||||
protected List<Argument> getDefaultJVMArguments() {
|
||||
return Arguments.DEFAULT_JVM_ARGUMENTS;
|
||||
}
|
||||
|
||||
protected List<Argument> getDefaultGameArguments() {
|
||||
return Arguments.DEFAULT_GAME_ARGUMENTS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void makeSelf(List<String> res) throws GameException {
|
||||
String game_assets = assetProvider.provide(version, !options.isNotCheckGame());
|
||||
Map<String, String> configuration = getConfigurations();
|
||||
|
||||
StringBuilder library = new StringBuilder("");
|
||||
ArrayList<MinecraftLibrary> opt = new ArrayList<>();
|
||||
for (MinecraftLibrary l : version.libraries)
|
||||
@@ -67,37 +89,44 @@ public class MinecraftLoader extends AbstractMinecraftLoader {
|
||||
if (!f.exists())
|
||||
throw new GameException("Minecraft jar does not exist");
|
||||
library.append(f.getAbsolutePath()).append(File.pathSeparator);
|
||||
res.add("-cp");
|
||||
res.add(library.toString().substring(0, library.length() - File.pathSeparator.length()));
|
||||
String classpath = library.toString().substring(0, library.length() - File.pathSeparator.length());
|
||||
configuration.put("${classpath}", classpath);
|
||||
configuration.put("${natives_directory}", service.version().getDecompressNativesToLocation(version).getAbsolutePath());
|
||||
configuration.put("${game_assets}", game_assets);
|
||||
|
||||
res.addAll(Arguments.parseArguments(version.arguments.jvm, configuration));
|
||||
res.add(version.mainClass);
|
||||
|
||||
if (version.minecraftArguments == null)
|
||||
throw new GameException(new NullPointerException("Minecraft Arguments can not be null."));
|
||||
String[] splitted = StrUtils.tokenize(version.minecraftArguments);
|
||||
Map<String, Boolean> features = getFeatures();
|
||||
|
||||
String game_assets = assetProvider.provide(version, !options.isNotCheckGame());
|
||||
res.addAll(Arguments.parseArguments(version.arguments.game, configuration, features));
|
||||
res.addAll(Arguments.parseStringArguments(Arrays.asList(StrUtils.tokenize(version.minecraftArguments)), configuration));
|
||||
}
|
||||
|
||||
protected Map<String, Boolean> getFeatures() {
|
||||
Map<String, Boolean> features = new HashMap<>();
|
||||
features.put("has_custom_resolution", StrUtils.isNotBlank(options.getHeight()) && StrUtils.isNotBlank(options.getWidth()));
|
||||
return features;
|
||||
}
|
||||
|
||||
for (String t : splitted) {
|
||||
t = t.replace("${auth_player_name}", lr.getUserName());
|
||||
t = t.replace("${auth_session}", lr.getSession());
|
||||
t = t.replace("${auth_uuid}", lr.getUserId());
|
||||
t = t.replace("${version_name}", options.getVersionName());
|
||||
t = t.replace("${profile_name}", options.getName());
|
||||
t = t.replace("${version_type}", options.getType());
|
||||
t = t.replace("${game_directory}", service.version().getRunDirectory(version.id).getAbsolutePath());
|
||||
t = t.replace("${game_assets}", game_assets);
|
||||
t = t.replace("${assets_root}", service.asset().getAssets(version.getAssetsIndex().getId()).getAbsolutePath());
|
||||
t = t.replace("${auth_access_token}", lr.getAccessToken());
|
||||
t = t.replace("${user_type}", lr.getUserType());
|
||||
t = t.replace("${assets_index_name}", version.getAssetsIndex().getId());
|
||||
t = t.replace("${user_properties}", lr.getUserProperties());
|
||||
res.add(t);
|
||||
}
|
||||
|
||||
if (res.indexOf("--gameDir") != -1 && res.indexOf("--workDir") != -1) {
|
||||
res.add("--workDir");
|
||||
res.add(gameDir.getAbsolutePath());
|
||||
}
|
||||
protected Map<String, String> getConfigurations() {
|
||||
HashMap<String, String> map = new HashMap();
|
||||
map.put("${auth_player_name}", lr.getUserName());
|
||||
map.put("${auth_session}", lr.getSession());
|
||||
map.put("${auth_uuid}", lr.getUserId());
|
||||
map.put("${version_name}", options.getVersionName());
|
||||
map.put("${profile_name}", options.getName());
|
||||
map.put("${version_type}", options.getType());
|
||||
map.put("${game_directory}", service.version().getRunDirectory(version.id).getAbsolutePath());
|
||||
map.put("${assets_root}", service.asset().getAssets(version.getAssetsIndex().getId()).getAbsolutePath());
|
||||
map.put("${auth_access_token}", lr.getAccessToken());
|
||||
map.put("${user_type}", lr.getUserType());
|
||||
map.put("${assets_index_name}", version.getAssetsIndex().getId());
|
||||
map.put("${user_properties}", lr.getUserProperties());
|
||||
map.put("${resolution_width}", options.getWidth());
|
||||
map.put("${resolution_height}", options.getHeight());
|
||||
map.put("${natives_directory}", service.version().getDecompressNativesToLocation(version).getAbsolutePath());
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,9 +141,9 @@ public class MinecraftLoader extends AbstractMinecraftLoader {
|
||||
} catch (IOException e) {
|
||||
HMCLog.err("Failed to append jvm arguments when searching for asset objects.", e);
|
||||
}
|
||||
|
||||
|
||||
list.add("-Dminecraft.client.jar=" + version.getJar(service.baseDirectory()).getAbsolutePath());
|
||||
|
||||
|
||||
/*
|
||||
if (version.logging != null && version.logging.containsKey("client")) {
|
||||
LoggingInfo logging = version.logging.get("client");
|
||||
@@ -122,7 +151,7 @@ public class MinecraftLoader extends AbstractMinecraftLoader {
|
||||
if (file.exists())
|
||||
list.add(logging.argument.replace("${path}", file.getAbsolutePath()));
|
||||
}
|
||||
*/
|
||||
*/
|
||||
}
|
||||
|
||||
private final IAssetProvider DEFAULT_ASSET_PROVIDER = (t, allow) -> {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.core.version;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
*/
|
||||
public interface Argument extends Cloneable {
|
||||
|
||||
/**
|
||||
* Parse this argument in form: ${key name} or simply a string.
|
||||
*
|
||||
* @param keys the parse map
|
||||
* @param features the map that contains some features such as 'is_demo_user', 'has_custom_resolution'
|
||||
* @return parsed argument element, empty if this argument is ignored and will not be added.
|
||||
*/
|
||||
List<String> toString(Map<String, String> keys, Map<String, Boolean> features);
|
||||
|
||||
public Object clone();
|
||||
|
||||
public static class ArgumentSerializer implements JsonDeserializer<Argument>, JsonSerializer<Argument> {
|
||||
|
||||
@Override
|
||||
public Argument deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (json.isJsonPrimitive())
|
||||
return new StringArgument(json.getAsString());
|
||||
else
|
||||
return context.deserialize(json, RuledArgument.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(Argument src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
if (src instanceof StringArgument)
|
||||
return new JsonPrimitive(((StringArgument) src).argument);
|
||||
else if (src instanceof RuledArgument)
|
||||
return context.serialize(src, RuledArgument.class);
|
||||
else
|
||||
throw new AssertionError("Unrecognized argument type: " + src);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.core.version;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.ArrayUtils;
|
||||
import org.jackhuang.hmcl.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
*/
|
||||
public class Arguments {
|
||||
@SerializedName("game")
|
||||
public List<Argument> game;
|
||||
@SerializedName("jvm")
|
||||
public List<Argument> jvm;
|
||||
|
||||
public static Arguments clone(Arguments original) {
|
||||
if (original == null)
|
||||
return null;
|
||||
Arguments ret = new Arguments();
|
||||
ret.game = CollectionUtils.deepCopy(original.game, value -> (Argument) value.clone());
|
||||
ret.jvm = CollectionUtils.deepCopy(original.jvm, value -> (Argument) value.clone());
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Arguments merge(Arguments a, Arguments b) {
|
||||
if (a == null) return b;
|
||||
else if (b == null) return a;
|
||||
Arguments ret = new Arguments();
|
||||
ret.game = ArrayUtils.merge(a.game, b.game);
|
||||
ret.jvm = ArrayUtils.merge(a.jvm, b.jvm);
|
||||
if (ret.game == null && ret.jvm == null) return null;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static List<String> parseStringArguments(List<String> arguments, Map<String, String> keys) {
|
||||
return CollectionUtils.map(arguments, str -> keys.getOrDefault(str, str));
|
||||
}
|
||||
|
||||
public static List<String> parseArguments(List<Argument> arguments, Map<String, String> keys) {
|
||||
return parseArguments(arguments, keys, Collections.EMPTY_MAP);
|
||||
}
|
||||
|
||||
public static List<String> parseArguments(List<Argument> arguments, Map<String, String> keys, Map<String, Boolean> features) {
|
||||
return CollectionUtils.flatMap(arguments, arg -> arg.toString(keys, features));
|
||||
}
|
||||
|
||||
public static final List<Argument> DEFAULT_JVM_ARGUMENTS;
|
||||
public static final List<Argument> DEFAULT_GAME_ARGUMENTS;
|
||||
|
||||
static {
|
||||
List<Argument> jvm = new LinkedList<>();
|
||||
jvm.add(new RuledArgument(Collections.singletonList(new Rules("allow", new OSRestriction("windows"))), Collections.singletonList("-XX:HeapDumpPath=MojangTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump")));
|
||||
jvm.add(new RuledArgument(Collections.singletonList(new Rules("allow", new OSRestriction("windows", "^10\\."))), Arrays.asList("-Dos.name=Windows 10", "-Dos.version=10.0")));
|
||||
jvm.add(new StringArgument("-Djava.library.path=${natives_directory}"));
|
||||
jvm.add(new StringArgument("-Dminecraft.launcher.brand=${launcher_name}"));
|
||||
jvm.add(new StringArgument("-Dminecraft.launcher.version=${launcher_version}"));
|
||||
jvm.add(new StringArgument("-cp"));
|
||||
jvm.add(new StringArgument("${classpath}"));
|
||||
DEFAULT_JVM_ARGUMENTS = Collections.unmodifiableList(jvm);
|
||||
|
||||
List<Argument> game = new LinkedList<>();
|
||||
game.add(new RuledArgument(Collections.singletonList(new Rules("allow", Collections.singletonMap("has_custom_resolution", true))), Arrays.asList("--width", "${resolution_width}", "--height", "${resolution_height}")));
|
||||
DEFAULT_GAME_ARGUMENTS = Collections.unmodifiableList(game);
|
||||
}
|
||||
}
|
||||
@@ -54,16 +54,7 @@ public class MinecraftLibrary extends AbstractMinecraftLibrary {
|
||||
*/
|
||||
@Override
|
||||
public boolean allow() {
|
||||
if (rules != null) {
|
||||
boolean flag = false;
|
||||
for (Rules r : rules)
|
||||
if ("disallow".equals(r.action()))
|
||||
return false;
|
||||
else if ("allow".equals(r.action()))
|
||||
flag = true;
|
||||
return flag;
|
||||
} else
|
||||
return true;
|
||||
return Rules.allow(rules);
|
||||
}
|
||||
|
||||
private String formatArch(String nati) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.jackhuang.hmcl.core.GameException;
|
||||
import org.jackhuang.hmcl.core.service.IMinecraftProvider;
|
||||
import org.jackhuang.hmcl.core.asset.AssetsIndex;
|
||||
import org.jackhuang.hmcl.util.ArrayUtils;
|
||||
import org.jackhuang.hmcl.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -43,6 +44,8 @@ public class MinecraftVersion implements Cloneable, Comparable<MinecraftVersion>
|
||||
|
||||
@SerializedName("minecraftArguments")
|
||||
public String minecraftArguments;
|
||||
@SerializedName("arguments")
|
||||
public Arguments arguments;
|
||||
@SerializedName("mainClass")
|
||||
public String mainClass;
|
||||
@SerializedName("time")
|
||||
@@ -79,8 +82,9 @@ public class MinecraftVersion implements Cloneable, Comparable<MinecraftVersion>
|
||||
public MinecraftVersion() {
|
||||
}
|
||||
|
||||
public MinecraftVersion(String minecraftArguments, String mainClass, String time, String id, String type, String processArguments, String releaseTime, String assets, String jar, String inheritsFrom, String runDir, int minimumLauncherVersion, List<MinecraftLibrary> libraries, boolean hidden, Map<String, GameDownloadInfo> downloads, AssetIndexDownloadInfo assetIndexDownloadInfo, Map<String, LoggingInfo> logging) {
|
||||
public MinecraftVersion(Arguments arguments, String minecraftArguments, String mainClass, String time, String id, String type, String processArguments, String releaseTime, String assets, String jar, String inheritsFrom, String runDir, int minimumLauncherVersion, List<MinecraftLibrary> libraries, boolean hidden, Map<String, GameDownloadInfo> downloads, AssetIndexDownloadInfo assetIndexDownloadInfo, Map<String, LoggingInfo> logging) {
|
||||
this();
|
||||
this.arguments = Arguments.clone(arguments);
|
||||
this.minecraftArguments = minecraftArguments;
|
||||
this.mainClass = mainClass;
|
||||
this.time = time;
|
||||
@@ -98,28 +102,9 @@ public class MinecraftVersion implements Cloneable, Comparable<MinecraftVersion>
|
||||
this.assetIndex = null;
|
||||
else
|
||||
this.assetIndex = (AssetIndexDownloadInfo) assetIndexDownloadInfo.clone();
|
||||
if (libraries == null)
|
||||
this.libraries = new ArrayList<>();
|
||||
else {
|
||||
this.libraries = new ArrayList<>(libraries.size());
|
||||
for (MinecraftLibrary library : libraries)
|
||||
if (library != null)
|
||||
this.libraries.add((MinecraftLibrary) library.clone());
|
||||
}
|
||||
if (downloads == null)
|
||||
this.downloads = null;
|
||||
else {
|
||||
this.downloads = new HashMap<>(downloads.size());
|
||||
for (Map.Entry<String, GameDownloadInfo> entry : downloads.entrySet())
|
||||
this.downloads.put(entry.getKey(), (GameDownloadInfo) entry.getValue().clone());
|
||||
}
|
||||
if (logging == null)
|
||||
this.logging = null;
|
||||
else {
|
||||
this.logging = new HashMap<>(logging.size());
|
||||
for (Map.Entry<String, LoggingInfo> entry : logging.entrySet())
|
||||
this.logging.put(entry.getKey(), (LoggingInfo) entry.getValue().clone());
|
||||
}
|
||||
this.libraries = CollectionUtils.deepCopy(libraries, value -> (MinecraftLibrary) value.clone());
|
||||
this.downloads = CollectionUtils.deepCopy(downloads, value -> (GameDownloadInfo) value.clone());
|
||||
this.logging = CollectionUtils.deepCopy(logging, value -> (LoggingInfo) value.clone());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,16 +136,17 @@ public class MinecraftVersion implements Cloneable, Comparable<MinecraftVersion>
|
||||
}
|
||||
parent = parent.resolve(provider, resolvedSoFar);
|
||||
MinecraftVersion result = new MinecraftVersion(
|
||||
this.minecraftArguments != null ? this.minecraftArguments : parent.minecraftArguments,
|
||||
this.mainClass != null ? this.mainClass : parent.mainClass,
|
||||
this.time, this.id, this.type, parent.processArguments, this.releaseTime,
|
||||
this.assets != null ? this.assets : parent.assets,
|
||||
this.jar != null ? this.jar : parent.jar,
|
||||
null, this.runDir, parent.minimumLauncherVersion,
|
||||
this.libraries != null ? ArrayUtils.merge(this.libraries, parent.libraries) : parent.libraries, this.hidden,
|
||||
this.downloads != null ? this.downloads : parent.downloads,
|
||||
this.assetIndex != null ? this.assetIndex : parent.assetIndex,
|
||||
this.logging != null ? this.logging : parent.logging);
|
||||
Arguments.merge(parent.arguments, this.arguments),
|
||||
this.minecraftArguments != null ? this.minecraftArguments : parent.minecraftArguments,
|
||||
this.mainClass != null ? this.mainClass : parent.mainClass,
|
||||
this.time, this.id, this.type, parent.processArguments, this.releaseTime,
|
||||
this.assets != null ? this.assets : parent.assets,
|
||||
this.jar != null ? this.jar : parent.jar,
|
||||
null, this.runDir, parent.minimumLauncherVersion,
|
||||
this.libraries != null ? ArrayUtils.merge(this.libraries, parent.libraries) : parent.libraries, this.hidden,
|
||||
this.downloads != null ? this.downloads : parent.downloads,
|
||||
this.assetIndex != null ? this.assetIndex : parent.assetIndex,
|
||||
this.logging != null ? this.logging : parent.logging);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -203,7 +189,7 @@ public class MinecraftVersion implements Cloneable, Comparable<MinecraftVersion>
|
||||
return false;
|
||||
return Objects.equals(this.id, ((MinecraftVersion) obj).id);
|
||||
}
|
||||
|
||||
|
||||
public AssetIndexDownloadInfo getAssetsIndex() {
|
||||
if (assetIndex == null)
|
||||
assetIndex = new AssetIndexDownloadInfo(assets == null ? AssetsIndex.DEFAULT_ASSET_NAME : assets);
|
||||
|
||||
@@ -18,19 +18,40 @@
|
||||
package org.jackhuang.hmcl.core.version;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.jackhuang.hmcl.util.sys.OS;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class OSRestriction {
|
||||
public class OSRestriction implements Cloneable {
|
||||
|
||||
@SerializedName("version")
|
||||
private String version;
|
||||
@SerializedName("name")
|
||||
public String name;
|
||||
@SerializedName("arch")
|
||||
public String arch;
|
||||
|
||||
public OSRestriction() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public OSRestriction(String name) {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
public OSRestriction(String name, String version) {
|
||||
this(name, version, null);
|
||||
}
|
||||
|
||||
public OSRestriction(String name, String version, String arch) {
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.arch = arch;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
@@ -49,6 +70,33 @@ public class OSRestriction {
|
||||
}
|
||||
|
||||
public boolean isCurrentOS() {
|
||||
return StrUtils.isBlank(getName()) || OS.os().name().equalsIgnoreCase(getName());
|
||||
if (name != null && !OS.os().name().equalsIgnoreCase(name))
|
||||
return false;
|
||||
if (version != null) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(version);
|
||||
Matcher matcher = pattern.matcher(System.getProperty("os.version"));
|
||||
if (!matcher.matches())
|
||||
return false;
|
||||
} catch (Throwable t) {}
|
||||
}
|
||||
if (arch != null) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(arch);
|
||||
Matcher matcher = pattern.matcher(System.getProperty("os.arch"));
|
||||
if (!matcher.matches())
|
||||
return false;
|
||||
} catch (Throwable t) {}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.core.version;
|
||||
|
||||
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 com.google.gson.reflect.TypeToken;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
*/
|
||||
public class RuledArgument implements Argument {
|
||||
public List<Rules> rules;
|
||||
public List<String> value;
|
||||
|
||||
public RuledArgument() {
|
||||
}
|
||||
|
||||
public RuledArgument(List<Rules> rules, List<String> args) {
|
||||
this.rules = rules;
|
||||
this.value = args;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
RuledArgument ret = (RuledArgument) super.clone();
|
||||
ret.rules = CollectionUtils.deepCopy(rules, value -> (Rules) value.clone());
|
||||
ret.value = CollectionUtils.copy(value);
|
||||
return ret;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> toString(Map<String, String> keys, Map<String, Boolean> features) {
|
||||
if (!Rules.allow(rules, features))
|
||||
return Collections.EMPTY_LIST;
|
||||
else
|
||||
return CollectionUtils.map(value, a -> new StringArgument(a).toString(keys, features).get(0));
|
||||
}
|
||||
|
||||
public static class RuledArgumentSerializer implements JsonSerializer<RuledArgument>, JsonDeserializer<RuledArgument> {
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(RuledArgument src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
JsonObject obj = new JsonObject();
|
||||
obj.add("rules", context.serialize(src.rules));
|
||||
obj.add("value", context.serialize(src.value));
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuledArgument deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
JsonObject obj = json.getAsJsonObject();
|
||||
RuledArgument a = new RuledArgument();
|
||||
a.rules = context.deserialize(obj.get("rules"), new TypeToken<List<Rules>>(){}.getType());
|
||||
if (obj.get("value").isJsonPrimitive())
|
||||
a.value = Collections.singletonList(obj.get("value").getAsString());
|
||||
else
|
||||
a.value = context.<List<String>>deserialize(obj.get("value"), new TypeToken<List<String>>(){}.getType());
|
||||
return a;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,23 +18,77 @@
|
||||
package org.jackhuang.hmcl.core.version;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class Rules {
|
||||
public class Rules implements Cloneable {
|
||||
|
||||
@SerializedName("action")
|
||||
private String action;
|
||||
@SerializedName("os")
|
||||
private OSRestriction os;
|
||||
@SerializedName("features")
|
||||
private Map<String, Boolean> features;
|
||||
|
||||
public Rules() {
|
||||
}
|
||||
|
||||
public Rules(String action, OSRestriction os) {
|
||||
this.action = action;
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
public Rules(String action, Map<String, Boolean> features) {
|
||||
this.action = action;
|
||||
this.features = features;
|
||||
}
|
||||
|
||||
public String action() {
|
||||
return os == null || os.isCurrentOS() ? action : null;
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
Rules rules = (Rules) super.clone();
|
||||
rules.os = (OSRestriction) os.clone();
|
||||
rules.features = CollectionUtils.copy(features);
|
||||
return rules;
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public String action(Map<String, Boolean> features) {
|
||||
if (os != null && !os.isCurrentOS()) return null;
|
||||
if (this.features != null) {
|
||||
for (Map.Entry<String, Boolean> entry : this.features.entrySet())
|
||||
if (!features.containsKey(entry.getKey()))
|
||||
return null;
|
||||
else
|
||||
if (!features.get(entry.getKey()).equals(entry.getValue()))
|
||||
return null;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
|
||||
public static boolean allow(Collection<Rules> c) {
|
||||
return allow(c, Collections.EMPTY_MAP);
|
||||
}
|
||||
|
||||
public static boolean allow(Collection<Rules> c, Map<String, Boolean> features) {
|
||||
if (c != null) {
|
||||
boolean flag = false;
|
||||
for (Rules r : c)
|
||||
if ("disallow".equals(r.action(features)))
|
||||
return false;
|
||||
else if ("allow".equals(r.action(features)))
|
||||
flag = true;
|
||||
return flag;
|
||||
} else
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.core.version;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
*/
|
||||
public class StringArgument implements Argument {
|
||||
|
||||
public String argument;
|
||||
|
||||
public StringArgument(String argument) {
|
||||
this.argument = argument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException ex) {
|
||||
throw new AssertionError(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> toString(Map<String, String> keys, Map<String, Boolean> features) {
|
||||
String res = argument;
|
||||
Pattern pattern = Pattern.compile("\\$\\{(.*?)\\}");
|
||||
Matcher m = pattern.matcher(argument);
|
||||
while (m.find()) {
|
||||
String entry = m.group();
|
||||
res = res.replace(entry, keys.getOrDefault(entry, entry));
|
||||
}
|
||||
return Collections.singletonList(res);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,6 +73,9 @@ public final class ArrayUtils {
|
||||
}
|
||||
|
||||
public static <T> ArrayList<T> merge(List<T> a, List<T> b) {
|
||||
if (a == null && b == null) return null;
|
||||
else if (a == null) return new ArrayList<>(b);
|
||||
else if (b == null) return new ArrayList<>(a);
|
||||
ArrayList<T> al = new ArrayList<>(a.size() + b.size());
|
||||
al.addAll(a);
|
||||
al.addAll(b);
|
||||
|
||||
@@ -20,6 +20,8 @@ package org.jackhuang.hmcl.util;
|
||||
import org.jackhuang.hmcl.util.lang.SupportedLocales;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.jackhuang.hmcl.core.version.Argument;
|
||||
import org.jackhuang.hmcl.core.version.RuledArgument;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -27,7 +29,10 @@ import com.google.gson.GsonBuilder;
|
||||
*/
|
||||
public final class C {
|
||||
|
||||
public static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(RuledArgument.class, new RuledArgument.RuledArgumentSerializer())
|
||||
.registerTypeAdapter(Argument.class, new Argument.ArgumentSerializer())
|
||||
.setPrettyPrinting().create();
|
||||
|
||||
//http://repo1.maven.org/maven2
|
||||
public static final String URL_PUBLISH = "http://www.mcbbs.net/thread-142335-1-1.html";
|
||||
@@ -35,7 +40,7 @@ public final class C {
|
||||
|
||||
public static final String URL_FORGE_LIST = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/json";
|
||||
public static final String URL_LITELOADER_LIST = "http://dl.liteloader.com/versions/versions.json";
|
||||
|
||||
|
||||
public static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||
|
||||
private C() {
|
||||
|
||||
@@ -20,7 +20,11 @@ package org.jackhuang.hmcl.util;
|
||||
import org.jackhuang.hmcl.api.func.Predicate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.api.func.Function;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -31,7 +35,7 @@ public final class CollectionUtils {
|
||||
private CollectionUtils() {
|
||||
}
|
||||
|
||||
public static <T> ArrayList<T> filter(Collection<T> coll, Predicate<T> p) {
|
||||
public static <T> List<T> filter(Collection<T> coll, Predicate<T> p) {
|
||||
ArrayList<T> newColl = new ArrayList<>();
|
||||
for (T t : coll)
|
||||
if (p.apply(t))
|
||||
@@ -39,6 +43,20 @@ public final class CollectionUtils {
|
||||
return newColl;
|
||||
}
|
||||
|
||||
public static <U, V> List<V> map(Collection<U> coll, Function<U, V> p) {
|
||||
ArrayList<V> newColl = new ArrayList<>(coll.size());
|
||||
for (U t : coll)
|
||||
newColl.add(p.apply(t));
|
||||
return newColl;
|
||||
}
|
||||
|
||||
public static <U, V> List<V> flatMap(Collection<U> coll, Function<U, Collection<V>> p) {
|
||||
ArrayList<V> newColl = new ArrayList<>(coll.size());
|
||||
for (U t : coll)
|
||||
newColl.addAll(p.apply(t));
|
||||
return newColl;
|
||||
}
|
||||
|
||||
public static <T> boolean removeIf(Collection<T> coll, Predicate<T> p) {
|
||||
boolean removed = false;
|
||||
final Iterator<T> each = coll.iterator();
|
||||
@@ -49,4 +67,40 @@ public final class CollectionUtils {
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
|
||||
public static <T extends Cloneable> ArrayList<T> deepCopy(List<T> original, Function<T, T> clone) {
|
||||
if (original == null)
|
||||
return null;
|
||||
ArrayList<T> ret = new ArrayList<>(original.size());
|
||||
for (T x : original)
|
||||
ret.add(clone.apply(x));
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static <T> ArrayList<T> copy(List<T> original) {
|
||||
if (original == null)
|
||||
return null;
|
||||
ArrayList<T> ret = new ArrayList<>(original.size());
|
||||
for (T x : original)
|
||||
ret.add(x);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static <K, V extends Cloneable> Map<K, V> deepCopy(Map<K, V> original, Function<V, V> clone) {
|
||||
if (original == null)
|
||||
return null;
|
||||
HashMap<K, V> ret = new HashMap<>(original.size());
|
||||
for (HashMap.Entry<K, V> x : original.entrySet())
|
||||
ret.put(x.getKey(), clone.apply(x.getValue()));
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> copy(Map<K, V> original) {
|
||||
if (original == null)
|
||||
return null;
|
||||
HashMap<K, V> ret = new HashMap<>(original.size());
|
||||
for (HashMap.Entry<K, V> x : original.entrySet())
|
||||
ret.put(x.getKey(), x.getValue());
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
import org.jackhuang.hmcl.api.func.Function;
|
||||
import org.jackhuang.hmcl.api.func.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -87,10 +86,10 @@ public final class StrUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean containsOne(List<String> base, List<String> match, Predicate<String> pred) {
|
||||
public static boolean containsOne(Collection<String> base, String... match) {
|
||||
for (String a : base)
|
||||
for (String b : match)
|
||||
if (pred.apply(a) && a.toLowerCase().contains(b.toLowerCase()))
|
||||
if (a.toLowerCase().contains(b.toLowerCase()))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
@@ -154,8 +153,11 @@ public final class StrUtils {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String[] tokenize(String paramString1) {
|
||||
return tokenize(paramString1, " \t\n\r\f");
|
||||
public static String[] tokenize(String str) {
|
||||
if (str == null)
|
||||
return new String[0];
|
||||
else
|
||||
return tokenize(str, " \t\n\r\f");
|
||||
}
|
||||
|
||||
public static String[] tokenize(String str, String delim) {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* 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 {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.sys;
|
||||
|
||||
import java.util.Collection;
|
||||
import org.jackhuang.hmcl.api.HMCLApi;
|
||||
import org.jackhuang.hmcl.api.IProcess;
|
||||
import org.jackhuang.hmcl.api.event.process.JVMLaunchFailedEvent;
|
||||
import org.jackhuang.hmcl.api.event.process.JavaProcessExitedAbnormallyEvent;
|
||||
import org.jackhuang.hmcl.api.event.process.JavaProcessStoppedEvent;
|
||||
import org.jackhuang.hmcl.util.CollectionUtils;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.util.log.Level;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
*/
|
||||
public class ExitWaiter extends Thread {
|
||||
|
||||
private final ProcessMonitor monitor;
|
||||
private final IProcess process;
|
||||
private final Runnable callback;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param monitor
|
||||
* @param callback nullable, will be called when process exited.
|
||||
*/
|
||||
public ExitWaiter(ProcessMonitor monitor, Runnable callback) {
|
||||
this.monitor = monitor;
|
||||
this.process = monitor.getProcess();
|
||||
this.callback = callback;
|
||||
|
||||
setName("exit-waitor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
int exitCode = process.getRawProcess().waitFor();
|
||||
monitor.errorThread.join();
|
||||
monitor.inputThread.join();
|
||||
|
||||
Collection<String> errorLines = CollectionUtils.filter(process.getStdOutLines(), str -> Level.isError(Level.guessLevel(str)));
|
||||
|
||||
// LaunchWrapper will terminate the application with exit code 0, though this is error state.
|
||||
if (exitCode != 0 || StrUtils.containsOne(errorLines, "Unable to launch"))
|
||||
HMCLApi.EVENT_BUS.fireChannel(new JavaProcessExitedAbnormallyEvent(monitor, process));
|
||||
else if (exitCode != 0 && StrUtils.containsOne(errorLines,
|
||||
"Could not create the Java Virtual Machine.",
|
||||
"Error occurred during initialization of VM",
|
||||
"A fatal exception has occurred. Program will exit.",
|
||||
"Unable to launch"))
|
||||
HMCLApi.EVENT_BUS.fireChannel(new JVMLaunchFailedEvent(monitor, process));
|
||||
else
|
||||
HMCLApi.EVENT_BUS.fireChannel(new JavaProcessStoppedEvent(this, process));
|
||||
|
||||
if (callback != null)
|
||||
callback.run();
|
||||
} catch (InterruptedException ex) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
|
||||
@@ -121,6 +121,8 @@ public class Java {
|
||||
try {
|
||||
queryJava(ans, "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\");
|
||||
queryJava(ans, "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\");
|
||||
queryJava(ans, "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JRE\\");
|
||||
queryJava(ans, "HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\JDK\\");
|
||||
} catch (IOException | InterruptedException ex) {
|
||||
HMCLog.err("Faield to query java", ex);
|
||||
}
|
||||
|
||||
@@ -17,19 +17,11 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.util.sys;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import org.jackhuang.hmcl.api.HMCLApi;
|
||||
import org.jackhuang.hmcl.api.event.process.JVMLaunchFailedEvent;
|
||||
import org.jackhuang.hmcl.api.event.process.JavaProcessExitedAbnormallyEvent;
|
||||
import org.jackhuang.hmcl.api.event.process.JavaProcessStartingEvent;
|
||||
import org.jackhuang.hmcl.api.event.process.JavaProcessStoppedEvent;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.util.log.Level;
|
||||
import org.jackhuang.hmcl.api.IProcess;
|
||||
import org.jackhuang.hmcl.api.event.SimpleEvent;
|
||||
import org.jackhuang.hmcl.api.func.Consumer;
|
||||
|
||||
/**
|
||||
@@ -43,6 +35,7 @@ public class ProcessMonitor {
|
||||
private final CountDownLatch latch = new CountDownLatch(2);
|
||||
ProcessThread inputThread;
|
||||
ProcessThread errorThread;
|
||||
ExitWaiter waitorThread;
|
||||
WaitForThread waitForThread;
|
||||
private final IProcess p;
|
||||
|
||||
@@ -50,9 +43,7 @@ public class ProcessMonitor {
|
||||
this.p = p;
|
||||
inputThread = new ProcessThread(this, false);
|
||||
errorThread = new ProcessThread(this, true);
|
||||
inputThread.stopEvent.register(this::threadStopped);
|
||||
inputThread.stopEvent.register(event -> processThreadStopped((ProcessThread) event.getSource()));
|
||||
errorThread.stopEvent.register(this::threadStopped);
|
||||
waitorThread = new ExitWaiter(this, this::processThreadStopped);
|
||||
}
|
||||
|
||||
public IProcess getProcess() {
|
||||
@@ -75,51 +66,14 @@ public class ProcessMonitor {
|
||||
}
|
||||
|
||||
public void start() {
|
||||
hasFired = false;
|
||||
MONITORS.add(this);
|
||||
HMCLApi.EVENT_BUS.fireChannel(new JavaProcessStartingEvent(this, p));
|
||||
inputThread.start();
|
||||
errorThread.start();
|
||||
}
|
||||
|
||||
private void threadStopped(SimpleEvent<IProcess> event) {
|
||||
latch.countDown();
|
||||
ProcessThread t = (ProcessThread) event.getSource();
|
||||
int exitCode = Integer.MAX_VALUE;
|
||||
try {
|
||||
exitCode = p.getExitCode();
|
||||
} catch(IllegalThreadStateException e) {
|
||||
HMCLog.err("Failed to get exit code ", e);
|
||||
}
|
||||
if (p.getExitCode() != 0 || StrUtils.containsOne(t.getLines(),
|
||||
Arrays.asList("Unable to launch"), // LaunchWrapper will terminate the application returning exit code 0, but this is an error state.
|
||||
x -> Level.isError(Level.guessLevel(x))))
|
||||
synchronized (this) {
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
HMCLApi.EVENT_BUS.fireChannel(new JavaProcessExitedAbnormallyEvent(ProcessMonitor.this, p));
|
||||
}
|
||||
}
|
||||
if (p.getExitCode() != 0 && StrUtils.containsOne(t.getLines(),
|
||||
Arrays.asList("Could not create the Java Virtual Machine.",
|
||||
"Error occurred during initialization of VM",
|
||||
"A fatal exception has occurred. Program will exit.",
|
||||
"Unable to launch"),
|
||||
x -> Level.isError(Level.guessLevel(x))))
|
||||
synchronized (this) {
|
||||
if (!hasFired) {
|
||||
hasFired = true;
|
||||
HMCLApi.EVENT_BUS.fireChannel(new JVMLaunchFailedEvent(ProcessMonitor.this, p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean hasFired = false;
|
||||
|
||||
private void processThreadStopped(ProcessThread t1) {
|
||||
private void processThreadStopped() {
|
||||
MONITORS.remove(this);
|
||||
errorThread.interrupt();
|
||||
HMCLApi.EVENT_BUS.fireChannel(new JavaProcessStoppedEvent(this, p));
|
||||
}
|
||||
|
||||
public static void stopAll() {
|
||||
@@ -127,6 +81,7 @@ public class ProcessMonitor {
|
||||
monitor.getProcess().getRawProcess().destroy();
|
||||
monitor.inputThread.interrupt();
|
||||
monitor.errorThread.interrupt();
|
||||
monitor.waitorThread.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https://services.gradle.org/distributions/gradle-3.3-bin.zip
|
||||
distributionUrl=https://services.gradle.org/distributions/gradle-4.3-bin.zip
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
rootProject.name = 'Hello Minecraft!'
|
||||
rootProject.name = 'Hello_Minecraft'
|
||||
|
||||
// Find the directories containing a "build.gradle" file in the root directory
|
||||
// of the project. That is, every directory containing a "build.gradle" will
|
||||
|
||||
Reference in new issue
Block a user