diff --git a/.idea/misc.xml b/.idea/misc.xml index 4e3cbfe..73c86c0 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -12,7 +12,7 @@ - + diff --git a/LaunchServer/resources/launchserver/defaults/profile1.11.2.cfg b/LaunchServer/resources/launchserver/defaults/profile1.11.2.cfg new file mode 100644 index 0000000..6aa6c45 --- /dev/null +++ b/LaunchServer/resources/launchserver/defaults/profile1.11.2.cfg @@ -0,0 +1,43 @@ +version: "1.11.2"; +assetIndex: "1.11.2"; # 1.7.10+ only + +# Runtime-dependent params +dir: "XXXXX"; +assetDir: "asset1.11.2"; + +# Client params +sortIndex: 0; +title: "XXXXX"; +serverAddress: "server.tld"; +serverPort: 25565; + +# Updater and client watch service +update: [ + "servers\\.dat" +]; +updateVerify: [ + "libraries", "natives", "mods", + "minecraft\\.jar", "forge\\.jar", "liteloader\\.jar" +]; +updateExclusions: []; + +# Client launcher params +mainClass: "net.minecraft.launchwrapper.Launch"; +classPath: [ "forge.jar", "liteloader.jar", "minecraft.jar", "libraries" ]; +jvmArgs: [ + "-Dfml.ignorePatchDiscrepancies=true", + "-Dfml.ignoreInvalidMinecraftCertificates=true", + + # Some options from Mojang's launcher + "-XX:+UseConcMarkSweepGC", + "-XX:+CMSIncrementalMode", + "-XX:-UseAdaptiveSizePolicy", + "-Xmn128M", + + # JVM Attach protection + "-XX:+DisableAttachMechanism" +]; +clientArgs: [ + "--tweakClass", "net.minecraftforge.fml.common.launcher.FMLTweaker", + "--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker" +]; diff --git a/LaunchServer/resources/launchserver/defaults/profile1.11.cfg b/LaunchServer/resources/launchserver/defaults/profile1.11.cfg deleted file mode 100644 index 374e94d..0000000 --- a/LaunchServer/resources/launchserver/defaults/profile1.11.cfg +++ /dev/null @@ -1,43 +0,0 @@ -version: "1.11"; -assetIndex: "1.11"; # 1.7.10+ only - -# Runtime-dependent params -dir: "XXXXX"; -assetDir: "asset1.11"; - -# Client params -sortIndex: 0; -title: "XXXXX"; -serverAddress: "server.tld"; -serverPort: 25565; - -# Updater and client watch service -update: [ - "servers\\.dat" -]; -updateVerify: [ - "libraries", "natives", "mods", - "minecraft\\.jar", "forge\\.jar", "liteloader\\.jar" -]; -updateExclusions: []; - -# Client launcher params -mainClass: "net.minecraft.launchwrapper.Launch"; -classPath: [ "forge.jar", "liteloader.jar", "minecraft.jar", "libraries" ]; -jvmArgs: [ - "-Dfml.ignorePatchDiscrepancies=true", - "-Dfml.ignoreInvalidMinecraftCertificates=true", - - # Some options from Mojang's launcher - "-XX:+UseConcMarkSweepGC", - "-XX:+CMSIncrementalMode", - "-XX:-UseAdaptiveSizePolicy", - "-Xmn128M", - - # JVM Attach protection - "-XX:+DisableAttachMechanism" -]; -clientArgs: [ - "--tweakClass", "net.minecraftforge.fml.common.launcher.FMLTweaker", - "--tweakClass", "com.mumfrey.liteloader.launch.LiteLoaderTweaker" -]; diff --git a/Launcher/source/Launcher.java b/Launcher/source/Launcher.java index 0e567e3..e5202c2 100644 --- a/Launcher/source/Launcher.java +++ b/Launcher/source/Launcher.java @@ -78,7 +78,7 @@ private static final AtomicReference CONFIG = new AtomicReference<>(); // Version info - @LauncherAPI public static final String VERSION = "15.3"; + @LauncherAPI public static final String VERSION = "15.3.1"; @LauncherAPI public static final String BUILD = readBuildNumber(); @LauncherAPI public static final int PROTOCOL_MAGIC = 0x724724_16; @@ -195,7 +195,7 @@ try { Class.forName("javafx.application.Application"); bindings.put("JSApplicationClass", JSApplication.class); - } catch (ClassNotFoundException e) { + } catch (ClassNotFoundException ignored) { LogHelper.warning("JavaFX API isn't available"); } } @@ -264,7 +264,8 @@ } public static final class Config extends StreamObject { - private static final String ADDRESS_OVERRIDE = System.getProperty("launcher.addressOverride", null); + @LauncherAPI public static final String ADDRESS_OVERRIDE_PROPERTY = "launcher.addressOverride"; + @LauncherAPI public static final String ADDRESS_OVERRIDE = System.getProperty(ADDRESS_OVERRIDE_PROPERTY, null); // Instance @LauncherAPI public final InetSocketAddress address; diff --git a/Launcher/source/client/ClientLauncher.java b/Launcher/source/client/ClientLauncher.java index f54eaa2..b165bdd 100644 --- a/Launcher/source/client/ClientLauncher.java +++ b/Launcher/source/client/ClientLauncher.java @@ -28,6 +28,7 @@ import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.WriterConfig; import launcher.Launcher; +import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.client.ClientProfile.Version; import launcher.hasher.DirWatcher; @@ -109,9 +110,13 @@ args.add(javaBin.toString()); args.add(MAGICAL_INTEL_OPTION); if (params.ram > 0 && params.ram <= JVMHelper.RAM) { + args.add("-Xms" + params.ram + 'M'); args.add("-Xmx" + params.ram + 'M'); } args.add(jvmProperty(LogHelper.DEBUG_PROPERTY, Boolean.toString(LogHelper.isDebugEnabled()))); + if (Config.ADDRESS_OVERRIDE != null) { + args.add(jvmProperty(Config.ADDRESS_OVERRIDE_PROPERTY, Config.ADDRESS_OVERRIDE)); + } if (JVMHelper.OS_TYPE == OS.MUSTDIE && JVMHelper.OS_VERSION.startsWith("10.")) { LogHelper.debug("MustDie 10 fix is applied"); args.add(jvmProperty("os.name", "Windows 10")); @@ -120,10 +125,12 @@ // Add classpath and main class Collections.addAll(args, profile.object.getJvmArgs()); - Collections.addAll(args, "-classpath", IOHelper.getCodeSource(ClientLauncher.class).toString(), - ClientLauncher.class.getName()); + Collections.addAll(args, "-classpath", IOHelper.getCodeSource(ClientLauncher.class).toString(), ClientLauncher.class.getName()); args.add(paramsFile.toString()); // Add params file path to args + // Print commandline debug message + LogHelper.debug("Commandline: " + args); + // Build client process LogHelper.debug("Launching client instance"); ProcessBuilder builder = new ProcessBuilder(args); @@ -294,6 +301,7 @@ addClientLegacyArgs(args, profile, params); } Collections.addAll(args, profile.getClientArgs()); + LogHelper.debug("Args: " + args); // Add client classpath URL[] classPath = resolveClassPath(params.clientDir, profile.getClassPath()); @@ -333,6 +341,7 @@ // Client paths @LauncherAPI public final Path assetDir; @LauncherAPI public final Path clientDir; + // Client params @LauncherAPI public final PlayerProfile pp; @LauncherAPI public final String accessToken; diff --git a/Launcher/source/client/ClientProfile.java b/Launcher/source/client/ClientProfile.java index 72d6dfe..e12c97b 100644 --- a/Launcher/source/client/ClientProfile.java +++ b/Launcher/source/client/ClientProfile.java @@ -12,6 +12,7 @@ import launcher.serialize.HInput; import launcher.serialize.config.ConfigObject; import launcher.serialize.config.entry.BlockConfigEntry; +import launcher.serialize.config.entry.BooleanConfigEntry; import launcher.serialize.config.entry.ConfigEntry.Type; import launcher.serialize.config.entry.IntegerConfigEntry; import launcher.serialize.config.entry.ListConfigEntry; @@ -38,6 +39,7 @@ private final ListConfigEntry update; private final ListConfigEntry updateExclusions; private final ListConfigEntry updateVerify; + private final BooleanConfigEntry updateFastCheck; // Client launcher private final StringConfigEntry mainClass; @@ -63,6 +65,7 @@ update = block.getEntry("update", ListConfigEntry.class); updateVerify = block.getEntry("updateVerify", ListConfigEntry.class); updateExclusions = block.getEntry("updateExclusions", ListConfigEntry.class); + updateFastCheck = block.getEntry("updateFastCheck", BooleanConfigEntry.class); // Client launcher mainClass = block.getEntry("mainClass", StringConfigEntry.class); @@ -189,7 +192,7 @@ @LauncherAPI public enum Version { - MC147("1.4.7", 51), + //MC147("1.4.7", 51), MC152("1.5.2", 61), MC164("1.6.4", 78), MC172("1.7.2", 4), @@ -197,7 +200,7 @@ MC189("1.8.9", 47), MC194("1.9.4", 110), MC1102("1.10.2", 210), - MC111("1.11", 315); + MC111("1.11.2", 316); private static final Map VERSIONS; public final String name; public final int protocol; diff --git a/Launcher/source/client/PlayerProfile.java b/Launcher/source/client/PlayerProfile.java index 1916b98..80d14b8 100644 --- a/Launcher/source/client/PlayerProfile.java +++ b/Launcher/source/client/PlayerProfile.java @@ -1,6 +1,8 @@ package launcher.client; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.Objects; import java.util.UUID; @@ -62,6 +64,9 @@ } public static final class Texture extends StreamObject { + private static final DigestAlgorithm DIGEST_ALGO = DigestAlgorithm.SHA256; + + // Instance @LauncherAPI public final String url; @LauncherAPI public final byte[] digest; @@ -74,19 +79,30 @@ @LauncherAPI public Texture(String url) throws IOException { this.url = IOHelper.verifyURL(url); - digest = SecurityHelper.digest(DigestAlgorithm.SHA256, new URL(url)); + + // Fetch texture + byte[] texture; + try (InputStream input = IOHelper.newInput(new URL(url))) { + texture = IOHelper.read(input); + } + try (ByteArrayInputStream input = new ByteArrayInputStream(texture)) { + IOHelper.readTexture(input); // Verify texture + } + + // Get digest of texture + digest = SecurityHelper.digest(DIGEST_ALGO, new URL(url)); } @LauncherAPI public Texture(HInput input) throws IOException { url = IOHelper.verifyURL(input.readASCII(2048)); - digest = input.readByteArray(SecurityHelper.CRYPTO_MAX_LENGTH); + digest = input.readByteArray(-DIGEST_ALGO.bytes); } @Override public void write(HOutput output) throws IOException { output.writeASCII(url, 2048); - output.writeByteArray(digest, SecurityHelper.CRYPTO_MAX_LENGTH); + output.writeByteArray(digest, -DIGEST_ALGO.bytes); } } } diff --git a/Launcher/source/client/ServerPinger.java b/Launcher/source/client/ServerPinger.java index 50733e2..4a47ae4 100644 --- a/Launcher/source/client/ServerPinger.java +++ b/Launcher/source/client/ServerPinger.java @@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; +import java.util.Objects; import java.util.regex.Pattern; import com.eclipsesource.json.Json; @@ -32,23 +33,45 @@ // Cache private final Object cacheLock = new Object(); - private Result cache; - private Instant cacheTime; + private Result cache = null; + private Exception cacheException = null; + private Instant cacheTime = null; @LauncherAPI public ServerPinger(InetSocketAddress address, Version version) { - this.address = address; - this.version = version; + this.address = Objects.requireNonNull(address, "address"); + this.version = Objects.requireNonNull(version, "version"); } @LauncherAPI public Result ping() throws IOException { Instant now = Instant.now(); synchronized (cacheLock) { - if (cache == null || cacheTime == null || Duration.between(now, cacheTime).getSeconds() >= 30) { - cache = doPing(); + // Update ping cache + if (cacheTime == null || Duration.between(now, cacheTime).toMillis() >= IOHelper.SOCKET_TIMEOUT) { cacheTime = now; + try { + cache = doPing(); + cacheException = null; + } catch (IOException | IllegalArgumentException /* Protocol error */ e) { + cache = null; + cacheException = e; + } } + + // Verify is result available + if (cache == null) { + if (cacheException instanceof IOException) { + throw (IOException) cacheException; + } + if (cacheException instanceof IllegalArgumentException) { + throw (IllegalArgumentException) cacheException; + } + cacheException = new IOException("Unavailable"); + throw (IOException) cacheException; + } + + // We're done return cache; } } @@ -185,9 +208,6 @@ } public static final class Result { - private static final Pattern CODES_PATTERN = Pattern.compile("ยง[0-9a-fkmnor]", Pattern.CASE_INSENSITIVE); - - // Instance @LauncherAPI public final int onlinePlayers; @LauncherAPI public final int maxPlayers; @LauncherAPI public final String raw; diff --git a/Launcher/source/hasher/DirWatcher.java b/Launcher/source/hasher/DirWatcher.java index 2101e45..5f3c1b5 100644 --- a/Launcher/source/hasher/DirWatcher.java +++ b/Launcher/source/hasher/DirWatcher.java @@ -107,8 +107,7 @@ } // Forbidden modification! - throw new SecurityException(String.format("Forbidden modification (%s, %d times): '%s'", - kind, event.count(), path)); + throw new SecurityException(String.format("Forbidden modification (%s, %d times): '%s'", kind, event.count(), path)); } key.reset(); } diff --git a/Launcher/source/helper/IOHelper.java b/Launcher/source/helper/IOHelper.java index 0243e71..8d3fb46 100644 --- a/Launcher/source/helper/IOHelper.java +++ b/Launcher/source/helper/IOHelper.java @@ -47,6 +47,8 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import javax.imageio.ImageIO; @@ -67,7 +69,7 @@ Integer.parseUnsignedInt(System.getProperty("launcher.httpTimeout", Integer.toString(5000))), VerifyHelper.POSITIVE, "launcher.httpTimeout can't be <= 0"); @LauncherAPI public static final int BUFFER_SIZE = VerifyHelper.verifyInt( - Integer.parseUnsignedInt(System.getProperty("launcher.bufferSize", Integer.toString(65536))), + Integer.parseUnsignedInt(System.getProperty("launcher.bufferSize", Integer.toString(4096))), VerifyHelper.POSITIVE, "launcher.bufferSize can't be <= 0"); // Platform-dependent @@ -81,14 +83,20 @@ @LauncherAPI public static final Path HOME_DIR = Paths.get(System.getProperty("user.home")); @LauncherAPI public static final Path WORKING_DIR = Paths.get(System.getProperty("user.dir")); - // File options - private static final LinkOption[] LINK_OPTIONS = {}; + // Open options - as arrays private static final OpenOption[] READ_OPTIONS = { StandardOpenOption.READ }; - private static final CopyOption[] COPY_OPTIONS = { StandardCopyOption.REPLACE_EXISTING }; - private static final OpenOption[] APPEND_OPTIONS = { StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND }; private static final OpenOption[] WRITE_OPTIONS = { StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING }; + private static final OpenOption[] APPEND_OPTIONS = { StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND }; + + // Other options + private static final LinkOption[] LINK_OPTIONS = {}; + private static final CopyOption[] COPY_OPTIONS = { StandardCopyOption.REPLACE_EXISTING }; private static final Set WALK_OPTIONS = Collections.singleton(FileVisitOption.FOLLOW_LINKS); + // Other constants + private static final Pattern CROSS_SEPARATOR_PATTERN = Pattern.compile(CROSS_SEPARATOR, Pattern.LITERAL); + private static final Pattern PLATFORM_SEPARATOR_PATTERN = Pattern.compile(PLATFORM_SEPARATOR, Pattern.LITERAL); + private IOHelper() { } @@ -239,7 +247,7 @@ } @LauncherAPI - public static InputStream newInput(URL url) throws IOException { + public static URLConnection newConnection(URL url) throws IOException { URLConnection connection = url.openConnection(); if (connection instanceof HttpURLConnection) { connection.setReadTimeout(HTTP_TIMEOUT); @@ -248,7 +256,12 @@ } connection.setDoInput(true); connection.setDoOutput(false); - return connection.getInputStream(); + return connection; + } + + @LauncherAPI + public static InputStream newInput(URL url) throws IOException { + return newConnection(url).getInputStream(); } @LauncherAPI @@ -279,7 +292,9 @@ @LauncherAPI public static BufferedReader newReader(URL url) throws IOException { - return newReader(newInput(url)); + URLConnection connection = newConnection(url); + String charset = connection.getContentEncoding(); + return newReader(connection.getInputStream(), charset == null ? UNICODE_CHARSET : Charset.forName(charset)); } @LauncherAPI @@ -490,12 +505,12 @@ @LauncherAPI public static Path toPath(String path) { - return Paths.get(path.replace(CROSS_SEPARATOR, PLATFORM_SEPARATOR)); + return Paths.get(CROSS_SEPARATOR_PATTERN.matcher(path).replaceAll(Matcher.quoteReplacement(PLATFORM_SEPARATOR))); } @LauncherAPI public static String toString(Path path) { - return path.toString().replace(PLATFORM_SEPARATOR, CROSS_SEPARATOR); + return PLATFORM_SEPARATOR_PATTERN.matcher(path.toString()).replaceAll(Matcher.quoteReplacement(CROSS_SEPARATOR)); } @LauncherAPI diff --git a/Launcher/source/helper/JVMHelper.java b/Launcher/source/helper/JVMHelper.java index 8bbccbe..9cb3107 100644 --- a/Launcher/source/helper/JVMHelper.java +++ b/Launcher/source/helper/JVMHelper.java @@ -2,6 +2,7 @@ import java.io.File; import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -14,9 +15,14 @@ import sun.misc.URLClassPath; public final class JVMHelper { + // MXBeans exports + @LauncherAPI public static final RuntimeMXBean RUNTIME_MXBEAN = ManagementFactory.getRuntimeMXBean(); + @LauncherAPI public static final OperatingSystemMXBean OPERATING_SYSTEM_MXBEAN = + (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); + // System properties - @LauncherAPI public static final OS OS_TYPE = OS.byName(System.getProperty("os.name")); - @LauncherAPI public static final String OS_VERSION = System.getProperty("os.version"); + @LauncherAPI public static final OS OS_TYPE = OS.byName(OPERATING_SYSTEM_MXBEAN.getName()); + @LauncherAPI public static final String OS_VERSION = OPERATING_SYSTEM_MXBEAN.getVersion(); @LauncherAPI public static final int OS_BITS = getCorrectOSArch(); @LauncherAPI public static final int JVM_BITS = Integer.parseInt(System.getProperty("sun.arch.data.model")); @LauncherAPI public static final int RAM = getRAMAmount(); @@ -66,6 +72,7 @@ @LauncherAPI public static void halt0(int status) { + LogHelper.debug("Trying to halt JVM"); try { getMethod(Class.forName("java.lang.Shutdown"), "halt0", int.class).invoke(null, status); } catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { @@ -124,7 +131,7 @@ } private static int getRAMAmount() { - int physicalRam = (int) (((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getTotalPhysicalMemorySize() >> 20); + int physicalRam = (int) (OPERATING_SYSTEM_MXBEAN.getTotalPhysicalMemorySize() >> 20); return Math.min(physicalRam, OS_BITS == 32 ? 1536 : 4096); // Limit 32-bit OS to 1536 MiB, and 64-bit OS to 4096 MiB (because it's enough) } diff --git a/Launcher/source/helper/SecurityHelper.java b/Launcher/source/helper/SecurityHelper.java index 64d429b..0c97793 100644 --- a/Launcher/source/helper/SecurityHelper.java +++ b/Launcher/source/helper/SecurityHelper.java @@ -457,12 +457,21 @@ @LauncherAPI public enum DigestAlgorithm { - PLAIN("plain"), MD5("MD5"), SHA1("SHA-1"), SHA224("SHA-224"), SHA256("SHA-256"), SHA512("SHA-512"); + PLAIN("plain", -1), MD5("MD5", 128), SHA1("SHA-1", 160), SHA224("SHA-224", 224), SHA256("SHA-256", 256), SHA512("SHA-512", 512); private static final Map ALGORITHMS; - public final String name; - DigestAlgorithm(String name) { + // Instance + public final String name; + public final int bits; + public final int bytes; + + DigestAlgorithm(String name, int bits) { this.name = name; + this.bits = bits; + + // Convert to bytes + bytes = bits / Byte.SIZE; + assert bits % Byte.SIZE == 0; } @Override @@ -470,6 +479,13 @@ return name; } + public byte[] verify(byte[] digest) { + if (digest.length != bytes) { + throw new IllegalArgumentException("Invalid digest length: " + digest.length); + } + return digest; + } + public static DigestAlgorithm byName(String name) { return VerifyHelper.getMapValue(ALGORITHMS, name, String.format("Unknown digest algorithm: '%s'", name)); } diff --git a/Launcher/source/helper/VerifyHelper.java b/Launcher/source/helper/VerifyHelper.java index 6d8ae24..aa2a2f0 100644 --- a/Launcher/source/helper/VerifyHelper.java +++ b/Launcher/source/helper/VerifyHelper.java @@ -1,6 +1,7 @@ package launcher.helper; import java.util.Map; +import java.util.Objects; import java.util.function.DoublePredicate; import java.util.function.IntPredicate; import java.util.function.LongPredicate; @@ -22,7 +23,7 @@ @LauncherAPI public static V getMapValue(Map map, K key, String error) { - return verify(map.get(key), v -> v != null, error); + return verify(map.get(key), Objects::nonNull, error); } @LauncherAPI @@ -41,7 +42,7 @@ } public static void putIfAbsent(Map map, K key, V value, String error) { - verify(map.putIfAbsent(key, value), o -> o == null, error); + verify(map.putIfAbsent(key, value), Objects::isNull, error); } @LauncherAPI diff --git a/Launcher/source/request/update/LauncherRequest.java b/Launcher/source/request/update/LauncherRequest.java index 03f28e8..cd9652f 100644 --- a/Launcher/source/request/update/LauncherRequest.java +++ b/Launcher/source/request/update/LauncherRequest.java @@ -61,9 +61,15 @@ SecurityHelper.verifySign(binary, sign, publicKey); // Prepare process builder to start new instance (java -jar works for Launch4J's EXE too) - ProcessBuilder builder = new ProcessBuilder(IOHelper.resolveJavaBin(null).toString(), - ClientLauncher.jvmProperty(LogHelper.DEBUG_PROPERTY, Boolean.toString(LogHelper.isDebugEnabled())), - "-jar", BINARY_PATH.toString()); + List args = new ArrayList<>(8); + args.add(IOHelper.resolveJavaBin(null).toString()); + if (LogHelper.isDebugEnabled()) { + args.add(ClientLauncher.jvmProperty(LogHelper.DEBUG_PROPERTY, Boolean.toString(LogHelper.isDebugEnabled()))); + } + if (Config.ADDRESS_OVERRIDE != null) { + args.add(ClientLauncher.jvmProperty(Config.ADDRESS_OVERRIDE_PROPERTY, Config.ADDRESS_OVERRIDE)); + } + ProcessBuilder builder = new ProcessBuilder(args.toArray(new String[args.size()])); builder.inheritIO(); // Rewrite and start new instance