diff --git a/LaunchServer/source-testing/LaunchServerWrap.java b/LaunchServer/source-testing/LaunchServerWrap.java index 18bb598..56bad2e 100644 --- a/LaunchServer/source-testing/LaunchServerWrap.java +++ b/LaunchServer/source-testing/LaunchServerWrap.java @@ -1,10 +1,13 @@ package launchserver; -public final class LaunchServerWrap { - private LaunchServerWrap() { +public final class LaunchServerWrap +{ + private LaunchServerWrap() + { } - public static void main(String... args) throws Throwable { + public static void main(String... args) throws Throwable + { LaunchServer.main(args); // Just for test runtime } } diff --git a/LaunchServer/source/LaunchServer.java b/LaunchServer/source/LaunchServer.java index a643b44..1004261 100644 --- a/LaunchServer/source/LaunchServer.java +++ b/LaunchServer/source/LaunchServer.java @@ -1,48 +1,10 @@ package launchserver; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.URL; -import java.nio.file.DirectoryStream; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.security.KeyPair; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.zip.CRC32; -import javax.script.Bindings; -import javax.script.Invocable; -import javax.script.ScriptContext; -import javax.script.ScriptEngine; -import javax.script.ScriptException; - import launcher.Launcher; import launcher.LauncherAPI; import launcher.client.ClientProfile; import launcher.hasher.HashedDir; -import launcher.helper.CommonHelper; -import launcher.helper.IOHelper; -import launcher.helper.JVMHelper; -import launcher.helper.LogHelper; -import launcher.helper.SecurityHelper; -import launcher.helper.VerifyHelper; +import launcher.helper.*; import launcher.serialize.config.ConfigObject; import launcher.serialize.config.TextConfigReader; import launcher.serialize.config.TextConfigWriter; @@ -74,38 +36,74 @@ import launchserver.response.ServerSocketHandler.Listener; import launchserver.texture.TextureProvider; -public final class LaunchServer implements Runnable, AutoCloseable { - // Constant paths - @LauncherAPI public final Path dir; - @LauncherAPI public final Path configFile; - @LauncherAPI public final Path publicKeyFile; - @LauncherAPI public final Path privateKeyFile; - @LauncherAPI public final Path updatesDir; - @LauncherAPI public final Path profilesDir; +import javax.script.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.net.URL; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.security.KeyPair; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.zip.CRC32; - @LauncherAPI public final AuthLimiter limiter; +public final class LaunchServer implements Runnable, AutoCloseable +{ + // Constant paths + @LauncherAPI + public final Path dir; + @LauncherAPI + public final Path configFile; + @LauncherAPI + public final Path publicKeyFile; + @LauncherAPI + public final Path privateKeyFile; + @LauncherAPI + public final Path updatesDir; + @LauncherAPI + public final Path profilesDir; + + @LauncherAPI + public final AuthLimiter limiter; // Server config - @LauncherAPI public final Config config; - @LauncherAPI public final RSAPublicKey publicKey; - @LauncherAPI public final RSAPrivateKey privateKey; - @LauncherAPI public final boolean portable; + @LauncherAPI + public final Config config; + @LauncherAPI + public final RSAPublicKey publicKey; + @LauncherAPI + public final RSAPrivateKey privateKey; + @LauncherAPI + public final boolean portable; // Launcher binary - @LauncherAPI public final LauncherBinary launcherBinary; - @LauncherAPI public final LauncherBinary launcherEXEBinary; + @LauncherAPI + public final LauncherBinary launcherBinary; + @LauncherAPI + public final LauncherBinary launcherEXEBinary; // Server - @LauncherAPI public final CommandHandler commandHandler; - @LauncherAPI public final ServerSocketHandler serverSocketHandler; - @LauncherAPI public final ScriptEngine engine = CommonHelper.newScriptEngine(); + @LauncherAPI + public final CommandHandler commandHandler; + @LauncherAPI + public final ServerSocketHandler serverSocketHandler; + @LauncherAPI + public final ScriptEngine engine = CommonHelper.newScriptEngine(); private final AtomicBoolean started = new AtomicBoolean(false); // Updates and profiles private volatile List> profilesList; private volatile Map> updatesDirMap; - public LaunchServer(Path dir, boolean portable) throws IOException, InvalidKeySpecException { + public LaunchServer(Path dir, boolean portable) throws IOException, InvalidKeySpecException + { setScriptBindings(); this.portable = portable; @@ -119,16 +117,22 @@ // Set command handler CommandHandler localCommandHandler; - if (portable) { + if (portable) + { localCommandHandler = new StdCommandHandler(this, false); - } else { - try { + } + else + { + try + { Class.forName("jline.Terminal"); // JLine2 available localCommandHandler = new JLineCommandHandler(this); LogHelper.info("JLine2 terminal enabled"); - } catch (ClassNotFoundException ignored) { + } + catch (ClassNotFoundException ignored) + { localCommandHandler = new StdCommandHandler(this, true); LogHelper.warning("JLine2 isn't in classpath, using std"); } @@ -136,14 +140,18 @@ commandHandler = localCommandHandler; // Set key pair - if (IOHelper.isFile(publicKeyFile) && IOHelper.isFile(privateKeyFile)) { + if (IOHelper.isFile(publicKeyFile) && IOHelper.isFile(privateKeyFile)) + { LogHelper.info("Reading RSA keypair"); publicKey = SecurityHelper.toPublicRSAKey(IOHelper.read(publicKeyFile)); privateKey = SecurityHelper.toPrivateRSAKey(IOHelper.read(privateKeyFile)); - if (!publicKey.getModulus().equals(privateKey.getModulus())) { + if (!publicKey.getModulus().equals(privateKey.getModulus())) + { throw new IOException("Private and public key modulus mismatch"); } - } else { + } + else + { LogHelper.info("Generating RSA keypair"); KeyPair pair = SecurityHelper.genRSAKeyPair(); publicKey = (RSAPublicKey) pair.getPublic(); @@ -163,7 +171,8 @@ // Read LaunchServer config generateConfigIfNotExists(); LogHelper.info("Reading LaunchServer config file"); - try (BufferedReader reader = IOHelper.newReader(configFile)) { + try (BufferedReader reader = IOHelper.newReader(configFile)) + { config = new Config(TextConfigReader.read(reader, true)); } config.verify(); @@ -177,13 +186,15 @@ syncLauncherBinaries(); // Sync updates dir - if (!IOHelper.isDir(updatesDir)) { + if (!IOHelper.isDir(updatesDir)) + { Files.createDirectory(updatesDir); } syncUpdatesDir(null); // Sync profiles dir - if (!IOHelper.isDir(profilesDir)) { + if (!IOHelper.isDir(profilesDir)) + { Files.createDirectory(profilesDir); } syncProfilesDir(); @@ -192,203 +203,8 @@ serverSocketHandler = new ServerSocketHandler(this); } - @Override - public void close() { - serverSocketHandler.close(); - - // Close handlers & providers - try { - config.authHandler.close(); - } catch (IOException e) { - LogHelper.error(e); - } - try { - config.authProvider.close(); - } catch (IOException e) { - LogHelper.error(e); - } - try { - config.textureProvider.close(); - } catch (IOException e) { - LogHelper.error(e); - } - - // Notify script about closing - try { - ((Invocable) engine).invokeFunction("close"); - } catch (NoSuchMethodException ignored) { - // Do nothing if method simply doesn't exist - } catch (Throwable exc) { - LogHelper.error(exc); - } - - // Print last message before death :( - LogHelper.info("LaunchServer stopped"); - } - - @Override - public void run() { - if (started.getAndSet(true)) { - throw new IllegalStateException("LaunchServer has been already started"); - } - - // Load plugin script if exist - Path scriptFile = dir.resolve("plugin.js"); - if (IOHelper.isFile(scriptFile)) { - LogHelper.info("Loading plugin.js script"); - try { - loadScript(IOHelper.toURL(scriptFile)); - } catch (Throwable exc) { - throw new RuntimeException("Error while loading plugin.js", exc); - } - } - - // Add shutdown hook, then start LaunchServer - if (!portable) { - JVMHelper.RUNTIME.addShutdownHook(CommonHelper.newThread(null, false, this::close)); - CommonHelper.newThread("Command Thread", true, commandHandler).start(); - } - rebindServerSocket(); - } - - @LauncherAPI - public void buildLauncherBinaries() throws IOException { - launcherBinary.build(); - launcherEXEBinary.build(); - } - - @LauncherAPI - @SuppressWarnings("ReturnOfCollectionOrArrayField") - public Collection> getProfiles() { - return profilesList; - } - - @LauncherAPI - public SignedObjectHolder getUpdateDir(String name) { - return updatesDirMap.get(name); - } - - @LauncherAPI - public Set>> getUpdateDirs() { - return updatesDirMap.entrySet(); - } - - @LauncherAPI - public Object loadScript(URL url) throws IOException, ScriptException { - LogHelper.debug("Loading server script: '%s'", url); - try (BufferedReader reader = IOHelper.newReader(url)) { - return engine.eval(reader); - } - } - - @LauncherAPI - public void rebindServerSocket() { - serverSocketHandler.close(); - CommonHelper.newThread("Server Socket Thread", false, serverSocketHandler).start(); - } - - @LauncherAPI - public void syncLauncherBinaries() throws IOException { - LogHelper.info("Syncing launcher binaries"); - - // Syncing launcher binary - LogHelper.subInfo("Syncing launcher binary file"); - if (!launcherBinary.sync()) { - LogHelper.subWarning("Missing launcher binary file"); - } - - // Syncing launcher EXE binary - LogHelper.subInfo("Syncing launcher EXE binary file"); - if (!launcherEXEBinary.sync()) { - LogHelper.subWarning("Missing launcher EXE binary file"); - } - } - - @LauncherAPI - public void syncProfilesDir() throws IOException { - LogHelper.info("Syncing profiles dir"); - List> newProfies = new LinkedList<>(); - IOHelper.walk(profilesDir, new ProfilesFileVisitor(newProfies), false); - - // Sort and set new profiles - newProfies.sort(Comparator.comparing(a -> a.object)); - profilesList = Collections.unmodifiableList(newProfies); - } - - @LauncherAPI - public void syncUpdatesDir(Collection dirs) throws IOException { - LogHelper.info("Syncing updates dir"); - Map> newUpdatesDirMap = new HashMap<>(16); - try (DirectoryStream dirStream = Files.newDirectoryStream(updatesDir)) { - for (Path updateDir : dirStream) { - if (Files.isHidden(updateDir)) { - continue; // Skip hidden - } - - // Resolve name and verify is dir - String name = IOHelper.getFileName(updateDir); - if (!IOHelper.isDir(updateDir)) { - LogHelper.subWarning("Not update dir: '%s'", name); - continue; - } - - // Add from previous map (it's guaranteed to be non-null) - if (dirs != null && !dirs.contains(name)) { - SignedObjectHolder hdir = updatesDirMap.get(name); - if (hdir != null) { - newUpdatesDirMap.put(name, hdir); - continue; - } - } - - // Sync and sign update dir - LogHelper.subInfo("Syncing '%s' update dir", name); - HashedDir updateHDir = new HashedDir(updateDir, null, true, true); - newUpdatesDirMap.put(name, new SignedObjectHolder<>(updateHDir, privateKey)); - } - } - updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap); - } - - private void generateConfigIfNotExists() throws IOException { - if (IOHelper.isFile(configFile)) { - return; - } - - // Create new config - LogHelper.info("Creating LaunchServer config"); - Config newConfig; - try (BufferedReader reader = IOHelper.newReader(IOHelper.getResourceURL("launchserver/defaults/config.cfg"))) { - newConfig = new Config(TextConfigReader.read(reader, false)); - } - - // Set server address - if (portable) { - LogHelper.warning("Setting LaunchServer address to 'localhost'"); - newConfig.setAddress("localhost"); - } else { - LogHelper.println("LaunchServer address: "); - newConfig.setAddress(commandHandler.readLine()); - } - - // Write LaunchServer config - LogHelper.info("Writing LaunchServer config file"); - try (BufferedWriter writer = IOHelper.newWriter(configFile)) { - TextConfigWriter.write(newConfig.block, writer, true); - } - } - - private void setScriptBindings() { - LogHelper.info("Setting up server script engine bindings"); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - bindings.put("server", this); - - // Add launcher and launchserver class bindings - Launcher.addLauncherClassBindings(engine, bindings); - addLaunchServerClassBindings(engine, bindings); - } - - public static void main(String... args) throws Throwable { + public static void main(String... args) throws Throwable + { SecurityHelper.verifyCertificates(LaunchServer.class); JVMHelper.verifySystemProperties(LaunchServer.class, true); LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log")); @@ -396,9 +212,12 @@ // Start LaunchServer long start = System.currentTimeMillis(); - try { + try + { new LaunchServer(IOHelper.WORKING_DIR, false).run(); - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.error(exc); return; } @@ -406,7 +225,8 @@ LogHelper.debug("LaunchServer started in %dms", end - start); } - public static void addLaunchServerClassBindings(ScriptEngine engine, Map bindings) { + public static void addLaunchServerClassBindings(ScriptEngine engine, Map bindings) + { Launcher.addClassBinding(engine, bindings, "LaunchServer", LaunchServer.class); // Set auth class bindings @@ -430,62 +250,295 @@ Launcher.addClassBinding(engine, bindings, "ServerSocketHandlerListener", Listener.class); } - private final class ProfilesFileVisitor extends SimpleFileVisitor { - private final Collection> result; + @Override + public void close() + { + serverSocketHandler.close(); - private ProfilesFileVisitor(Collection> result) { - this.result = result; + // Close handlers & providers + try + { + config.authHandler.close(); + } + catch (IOException e) + { + LogHelper.error(e); + } + try + { + config.authProvider.close(); + } + catch (IOException e) + { + LogHelper.error(e); + } + try + { + config.textureProvider.close(); + } + catch (IOException e) + { + LogHelper.error(e); } - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - LogHelper.subInfo("Syncing '%s' profile", IOHelper.getFileName(file)); + // Notify script about closing + try + { + ((Invocable) engine).invokeFunction("close"); + } + catch (NoSuchMethodException ignored) + { + // Do nothing if method simply doesn't exist + } + catch (Throwable exc) + { + LogHelper.error(exc); + } - // Read profile - ClientProfile profile; - try (BufferedReader reader = IOHelper.newReader(file)) { - profile = new ClientProfile(TextConfigReader.read(reader, true)); + // Print last message before death :( + LogHelper.info("LaunchServer stopped"); + } + + @Override + public void run() + { + if (started.getAndSet(true)) + { + throw new IllegalStateException("LaunchServer has been already started"); + } + + // Load plugin script if exist + Path scriptFile = dir.resolve("plugin.js"); + if (IOHelper.isFile(scriptFile)) + { + LogHelper.info("Loading plugin.js script"); + try + { + loadScript(IOHelper.toURL(scriptFile)); } - profile.verify(); + catch (Throwable exc) + { + throw new RuntimeException("Error while loading plugin.js", exc); + } + } - // Add SIGNED profile to result list - result.add(new SignedObjectHolder<>(profile, privateKey)); - return super.visitFile(file, attrs); + // Add shutdown hook, then start LaunchServer + if (!portable) + { + JVMHelper.RUNTIME.addShutdownHook(CommonHelper.newThread(null, false, this::close)); + CommonHelper.newThread("Command Thread", true, commandHandler).start(); + } + rebindServerSocket(); + } + + @LauncherAPI + public void buildLauncherBinaries() throws IOException + { + launcherBinary.build(); + launcherEXEBinary.build(); + } + + @LauncherAPI + @SuppressWarnings("ReturnOfCollectionOrArrayField") + public Collection> getProfiles() + { + return profilesList; + } + + @LauncherAPI + public SignedObjectHolder getUpdateDir(String name) + { + return updatesDirMap.get(name); + } + + @LauncherAPI + public Set>> getUpdateDirs() + { + return updatesDirMap.entrySet(); + } + + @LauncherAPI + public Object loadScript(URL url) throws IOException, ScriptException + { + LogHelper.debug("Loading server script: '%s'", url); + try (BufferedReader reader = IOHelper.newReader(url)) + { + return engine.eval(reader); } } - public static final class Config extends ConfigObject { - @LauncherAPI public final int port; + @LauncherAPI + public void rebindServerSocket() + { + serverSocketHandler.close(); + CommonHelper.newThread("Server Socket Thread", false, serverSocketHandler).start(); + } + + @LauncherAPI + public void syncLauncherBinaries() throws IOException + { + LogHelper.info("Syncing launcher binaries"); + + // Syncing launcher binary + LogHelper.subInfo("Syncing launcher binary file"); + if (!launcherBinary.sync()) + { + LogHelper.subWarning("Missing launcher binary file"); + } + + // Syncing launcher EXE binary + LogHelper.subInfo("Syncing launcher EXE binary file"); + if (!launcherEXEBinary.sync()) + { + LogHelper.subWarning("Missing launcher EXE binary file"); + } + } + + @LauncherAPI + public void syncProfilesDir() throws IOException + { + LogHelper.info("Syncing profiles dir"); + List> newProfies = new LinkedList<>(); + IOHelper.walk(profilesDir, new ProfilesFileVisitor(newProfies), false); + + // Sort and set new profiles + newProfies.sort(Comparator.comparing(a -> a.object)); + profilesList = Collections.unmodifiableList(newProfies); + } + + @LauncherAPI + public void syncUpdatesDir(Collection dirs) throws IOException + { + LogHelper.info("Syncing updates dir"); + Map> newUpdatesDirMap = new HashMap<>(16); + try (DirectoryStream dirStream = Files.newDirectoryStream(updatesDir)) + { + for (Path updateDir : dirStream) + { + if (Files.isHidden(updateDir)) + { + continue; // Skip hidden + } + + // Resolve name and verify is dir + String name = IOHelper.getFileName(updateDir); + if (!IOHelper.isDir(updateDir)) + { + LogHelper.subWarning("Not update dir: '%s'", name); + continue; + } + + // Add from previous map (it's guaranteed to be non-null) + if (dirs != null && !dirs.contains(name)) + { + SignedObjectHolder hdir = updatesDirMap.get(name); + if (hdir != null) + { + newUpdatesDirMap.put(name, hdir); + continue; + } + } + + // Sync and sign update dir + LogHelper.subInfo("Syncing '%s' update dir", name); + HashedDir updateHDir = new HashedDir(updateDir, null, true, true); + newUpdatesDirMap.put(name, new SignedObjectHolder<>(updateHDir, privateKey)); + } + } + updatesDirMap = Collections.unmodifiableMap(newUpdatesDirMap); + } + + private void generateConfigIfNotExists() throws IOException + { + if (IOHelper.isFile(configFile)) + { + return; + } + + // Create new config + LogHelper.info("Creating LaunchServer config"); + Config newConfig; + try (BufferedReader reader = IOHelper.newReader(IOHelper.getResourceURL("launchserver/defaults/config.cfg"))) + { + newConfig = new Config(TextConfigReader.read(reader, false)); + } + + // Set server address + if (portable) + { + LogHelper.warning("Setting LaunchServer address to 'localhost'"); + newConfig.setAddress("localhost"); + } + else + { + LogHelper.println("LaunchServer address: "); + newConfig.setAddress(commandHandler.readLine()); + } + + // Write LaunchServer config + LogHelper.info("Writing LaunchServer config file"); + try (BufferedWriter writer = IOHelper.newWriter(configFile)) + { + TextConfigWriter.write(newConfig.block, writer, true); + } + } + + private void setScriptBindings() + { + LogHelper.info("Setting up server script engine bindings"); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + bindings.put("server", this); + + // Add launcher and launchserver class bindings + Launcher.addLauncherClassBindings(engine, bindings); + addLaunchServerClassBindings(engine, bindings); + } + + public static final class Config extends ConfigObject + { + @LauncherAPI + public final int port; // Handlers & Providers - @LauncherAPI public final AuthHandler authHandler; - @LauncherAPI public final AuthProvider authProvider; - @LauncherAPI public final TextureProvider textureProvider; + @LauncherAPI + public final AuthHandler authHandler; + @LauncherAPI + public final AuthProvider authProvider; + @LauncherAPI + public final TextureProvider textureProvider; // AuthLimiter - @LauncherAPI public final int authRateLimit; - @LauncherAPI public final int authRateLimitMilis; - @LauncherAPI public final String authRejectString; + @LauncherAPI + public final int authRateLimit; + @LauncherAPI + public final int authRateLimitMilis; + @LauncherAPI + public final String authRejectString; // Mirror - @LauncherAPI public final String mirror; + @LauncherAPI + public final String mirror; // BinaryName - @LauncherAPI public final String binaryName; + @LauncherAPI + public final String binaryName; // Misc options - @LauncherAPI public final boolean launch4J; - @LauncherAPI public final boolean compress; + @LauncherAPI + public final boolean launch4J; + @LauncherAPI + public final boolean compress; private final StringConfigEntry address; private final String bindAddress; - private Config(BlockConfigEntry block) { + private Config(BlockConfigEntry block) + { super(block); address = block.getEntry("address", StringConfigEntry.class); port = VerifyHelper.verifyInt(block.getEntryValue("port", IntegerConfigEntry.class), - VerifyHelper.range(0, 65535), "Illegal LaunchServer port"); + VerifyHelper.range(0, 65535), "Illegal LaunchServer port"); bindAddress = block.hasEntry("bindAddress") ? - block.getEntryValue("bindAddress", StringConfigEntry.class) : getAddress(); + block.getEntryValue("bindAddress", StringConfigEntry.class) : getAddress(); // Limit Autorization authRateLimit = VerifyHelper.verifyInt(block.getEntryValue("authRateLimit", IntegerConfigEntry.class), @@ -497,11 +550,11 @@ // Set handlers & providers authHandler = AuthHandler.newHandler(block.getEntryValue("authHandler", StringConfigEntry.class), - block.getEntry("authHandlerConfig", BlockConfigEntry.class)); + block.getEntry("authHandlerConfig", BlockConfigEntry.class)); authProvider = AuthProvider.newProvider(block.getEntryValue("authProvider", StringConfigEntry.class), - block.getEntry("authProviderConfig", BlockConfigEntry.class)); + block.getEntry("authProviderConfig", BlockConfigEntry.class)); textureProvider = TextureProvider.newProvider(block.getEntryValue("textureProvider", StringConfigEntry.class), - block.getEntry("textureProviderConfig", BlockConfigEntry.class)); + block.getEntry("textureProviderConfig", BlockConfigEntry.class)); // Mirror mirror = block.getEntryValue("mirror", StringConfigEntry.class); @@ -513,28 +566,61 @@ } @LauncherAPI - public String getAddress() { + public String getAddress() + { return address.getValue(); } @LauncherAPI - public void setAddress(String address) { + public void setAddress(String address) + { this.address.setValue(address); } @LauncherAPI - public String getBindAddress() { + public String getBindAddress() + { return bindAddress; } @LauncherAPI - public SocketAddress getSocketAddress() { + public SocketAddress getSocketAddress() + { return new InetSocketAddress(bindAddress, port); } @LauncherAPI - public void verify() { + public void verify() + { VerifyHelper.verify(getAddress(), VerifyHelper.NOT_EMPTY, "LaunchServer address can't be empty"); } } + + private final class ProfilesFileVisitor extends SimpleFileVisitor + { + private final Collection> result; + + private ProfilesFileVisitor(Collection> result) + { + this.result = result; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException + { + LogHelper.subInfo("Syncing '%s' profile", IOHelper.getFileName(file)); + + // Read profile + ClientProfile profile; + try (BufferedReader reader = IOHelper.newReader(file)) + { + profile = new ClientProfile(TextConfigReader.read(reader, true)); + } + profile.verify(); + + // Add SIGNED profile to result list + result.add(new SignedObjectHolder<>(profile, privateKey)); + return super.visitFile(file, attrs); + } + } } diff --git a/LaunchServer/source/auth/AuthException.java b/LaunchServer/source/auth/AuthException.java index 6e392a2..fcb7ad7 100644 --- a/LaunchServer/source/auth/AuthException.java +++ b/LaunchServer/source/auth/AuthException.java @@ -1,19 +1,22 @@ package launchserver.auth; -import java.io.IOException; - import launcher.LauncherAPI; -public final class AuthException extends IOException { +import java.io.IOException; + +public final class AuthException extends IOException +{ private static final long serialVersionUID = -2586107832847245863L; @LauncherAPI - public AuthException(String message) { + public AuthException(String message) + { super(message); } @Override - public String toString() { + public String toString() + { return getMessage(); } } diff --git a/LaunchServer/source/auth/AuthLimiter.java b/LaunchServer/source/auth/AuthLimiter.java index 6af8a0a..7488829 100644 --- a/LaunchServer/source/auth/AuthLimiter.java +++ b/LaunchServer/source/auth/AuthLimiter.java @@ -1,12 +1,49 @@ package launchserver.auth; -import java.util.HashMap; - import launcher.LauncherAPI; import launchserver.LaunchServer; +import java.util.HashMap; + public class AuthLimiter { + @LauncherAPI + public static final long TIMEOUT = 10 * 60 * 1000; //10 минут + public final int rateLimit; + public final int rateLimitMilis; + private final HashMap map; + + public AuthLimiter(LaunchServer srv) + { + map = new HashMap<>(); + rateLimit = srv.config.authRateLimit; + rateLimitMilis = srv.config.authRateLimitMilis; + } + + public boolean isLimit(String ip) + { + if (map.containsKey(ip)) + { + AuthEntry rate = map.get(ip); + long currenttime = System.currentTimeMillis(); + if (rate.ts + rateLimitMilis < currenttime) + { + rate.value = 0; + } + if (rate.value >= rateLimit && rateLimit > 0) + { + rate.value++; + rate.ts = currenttime; + return true; + } + rate.value++; + rate.ts = currenttime; + return false; + } + map.put(ip, new AuthEntry(1, System.currentTimeMillis())); + return false; + } + static class AuthEntry { public int value; @@ -60,36 +97,4 @@ return String.format("AuthEntry {value=%s, ts=%s}", value, ts); } } - - @LauncherAPI - public static final long TIMEOUT = 10 * 60 * 1000; //10 минут - public final int rateLimit; - public final int rateLimitMilis; - - private HashMap map; - - public AuthLimiter(LaunchServer srv) - { - map = new HashMap<>(); - rateLimit = srv.config.authRateLimit; - rateLimitMilis = srv.config.authRateLimitMilis; - } - - public boolean isLimit(String ip) { - if (map.containsKey(ip)) { - AuthEntry rate = map.get(ip); - long currenttime = System.currentTimeMillis(); - if (rate.ts + rateLimitMilis < currenttime) rate.value = 0; - if (rate.value >= rateLimit && rateLimit > 0) { - rate.value++; - rate.ts = currenttime; - return true; - } - rate.value++; - rate.ts = currenttime; - return false; - } - map.put(ip, new AuthEntry(1, System.currentTimeMillis())); - return false; - } } diff --git a/LaunchServer/source/auth/MySQLSourceConfig.java b/LaunchServer/source/auth/MySQLSourceConfig.java index 5aecc52..e65b26e 100644 --- a/LaunchServer/source/auth/MySQLSourceConfig.java +++ b/LaunchServer/source/auth/MySQLSourceConfig.java @@ -1,9 +1,5 @@ package launchserver.auth; -import java.sql.Connection; -import java.sql.SQLException; -import javax.sql.DataSource; - import com.mysql.jdbc.jdbc2.optional.MysqlDataSource; import com.zaxxer.hikari.HikariDataSource; import launcher.LauncherAPI; @@ -14,13 +10,19 @@ import launcher.serialize.config.entry.IntegerConfigEntry; import launcher.serialize.config.entry.StringConfigEntry; -public final class MySQLSourceConfig extends ConfigObject implements AutoCloseable { - @LauncherAPI public static final int TIMEOUT = VerifyHelper.verifyInt( - Integer.parseInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))), - VerifyHelper.POSITIVE, "launcher.mysql.idleTimeout can't be <= 5000"); +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +public final class MySQLSourceConfig extends ConfigObject implements AutoCloseable +{ + @LauncherAPI + public static final int TIMEOUT = VerifyHelper.verifyInt( + Integer.parseInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))), + VerifyHelper.POSITIVE, "launcher.mysql.idleTimeout can't be <= 5000"); private static final int MAX_POOL_SIZE = VerifyHelper.verifyInt( - Integer.parseInt(System.getProperty("launcher.mysql.maxPoolSize", Integer.toString(3))), - VerifyHelper.POSITIVE, "launcher.mysql.maxPoolSize can't be <= 0"); + Integer.parseInt(System.getProperty("launcher.mysql.maxPoolSize", Integer.toString(3))), + VerifyHelper.POSITIVE, "launcher.mysql.maxPoolSize can't be <= 0"); // Instance private final String poolName; @@ -37,32 +39,37 @@ private boolean hikari; @LauncherAPI - public MySQLSourceConfig(String poolName, BlockConfigEntry block) { + public MySQLSourceConfig(String poolName, BlockConfigEntry block) + { super(block); this.poolName = poolName; address = VerifyHelper.verify(block.getEntryValue("address", StringConfigEntry.class), - VerifyHelper.NOT_EMPTY, "MySQL address can't be empty"); + VerifyHelper.NOT_EMPTY, "MySQL address can't be empty"); port = VerifyHelper.verifyInt(block.getEntryValue("port", IntegerConfigEntry.class), - VerifyHelper.range(0, 65535), "Illegal MySQL port"); + VerifyHelper.range(0, 65535), "Illegal MySQL port"); username = VerifyHelper.verify(block.getEntryValue("username", StringConfigEntry.class), - VerifyHelper.NOT_EMPTY, "MySQL username can't be empty"); + VerifyHelper.NOT_EMPTY, "MySQL username can't be empty"); password = block.getEntryValue("password", StringConfigEntry.class); database = VerifyHelper.verify(block.getEntryValue("database", StringConfigEntry.class), - VerifyHelper.NOT_EMPTY, "MySQL database can't be empty"); + VerifyHelper.NOT_EMPTY, "MySQL database can't be empty"); // Password shouldn't be verified } @Override - public synchronized void close() { - if (hikari) { // Shutdown hikari pool + public synchronized void close() + { + if (hikari) + { // Shutdown hikari pool ((HikariDataSource) source).close(); } } @LauncherAPI - public synchronized Connection getConnection() throws SQLException { - if (source == null) { // New data source + public synchronized Connection getConnection() throws SQLException + { + if (source == null) + { // New data source MysqlDataSource mysqlSource = new MysqlDataSource(); mysqlSource.setCharacterEncoding("UTF-8"); mysqlSource.setUseSSL(false); @@ -91,7 +98,8 @@ // Try using HikariCP source = mysqlSource; - try { + try + { Class.forName("com.zaxxer.hikari.HikariDataSource"); hikari = true; // Used for shutdown. Not instanceof because of possible classpath error @@ -108,7 +116,9 @@ // Replace source with hds source = hikariSource; LogHelper.info("HikariCP pooling enabled for '%s'", poolName); - } catch (ClassNotFoundException ignored) { + } + catch (ClassNotFoundException ignored) + { LogHelper.warning("HikariCP isn't in classpath for '%s'", poolName); } } diff --git a/LaunchServer/source/auth/handler/AuthHandler.java b/LaunchServer/source/auth/handler/AuthHandler.java index 94f9313..7c69691 100644 --- a/LaunchServer/source/auth/handler/AuthHandler.java +++ b/LaunchServer/source/auth/handler/AuthHandler.java @@ -1,11 +1,5 @@ package launchserver.auth.handler; -import java.io.IOException; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - import launcher.LauncherAPI; import launcher.helper.VerifyHelper; import launcher.serialize.config.ConfigObject; @@ -13,14 +7,55 @@ import launchserver.auth.AuthException; import launchserver.auth.provider.AuthProviderResult; -public abstract class AuthHandler extends ConfigObject implements AutoCloseable { +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class AuthHandler extends ConfigObject implements AutoCloseable +{ private static final Map> AUTH_HANDLERS = new ConcurrentHashMap<>(4); + static + { + registerHandler("memory", MemoryAuthHandler::new); + registerHandler("delegate", DelegateAuthHandler::new); + + // Auth handler that doesn't do nothing :D + registerHandler("binaryFile", BinaryFileAuthHandler::new); + registerHandler("textFile", TextFileAuthHandler::new); + registerHandler("mysql", MySQLAuthHandler::new); + } + @LauncherAPI - protected AuthHandler(BlockConfigEntry block) { + protected AuthHandler(BlockConfigEntry block) + { super(block); } + @LauncherAPI + public static UUID authError(String message) throws AuthException + { + throw new AuthException(message); + } + + @LauncherAPI + public static AuthHandler newHandler(String name, BlockConfigEntry block) + { + Adapter authHandlerAdapter = VerifyHelper.getMapValue(AUTH_HANDLERS, name, + String.format("Unknown auth handler: '%s'", name)); + return authHandlerAdapter.convert(block); + } + + @LauncherAPI + public static void registerHandler(String name, Adapter adapter) + { + VerifyHelper.verifyIDName(name); + VerifyHelper.putIfAbsent(AUTH_HANDLERS, name, Objects.requireNonNull(adapter, "adapter"), + String.format("Auth handler has been already registered: '%s'", name)); + } + @Override public abstract void close() throws IOException; @@ -38,33 +73,4 @@ @LauncherAPI public abstract String uuidToUsername(UUID uuid) throws IOException; - - @LauncherAPI - public static UUID authError(String message) throws AuthException { - throw new AuthException(message); - } - - @LauncherAPI - public static AuthHandler newHandler(String name, BlockConfigEntry block) { - Adapter authHandlerAdapter = VerifyHelper.getMapValue(AUTH_HANDLERS, name, - String.format("Unknown auth handler: '%s'", name)); - return authHandlerAdapter.convert(block); - } - - @LauncherAPI - public static void registerHandler(String name, Adapter adapter) { - VerifyHelper.verifyIDName(name); - VerifyHelper.putIfAbsent(AUTH_HANDLERS, name, Objects.requireNonNull(adapter, "adapter"), - String.format("Auth handler has been already registered: '%s'", name)); - } - - static { - registerHandler("memory", MemoryAuthHandler::new); - registerHandler("delegate", DelegateAuthHandler::new); - - // Auth handler that doesn't do nothing :D - registerHandler("binaryFile", BinaryFileAuthHandler::new); - registerHandler("textFile", TextFileAuthHandler::new); - registerHandler("mysql", MySQLAuthHandler::new); - } } diff --git a/LaunchServer/source/auth/handler/BinaryFileAuthHandler.java b/LaunchServer/source/auth/handler/BinaryFileAuthHandler.java index 2ce9c3b..fc6f64b 100644 --- a/LaunchServer/source/auth/handler/BinaryFileAuthHandler.java +++ b/LaunchServer/source/auth/handler/BinaryFileAuthHandler.java @@ -1,25 +1,30 @@ package launchserver.auth.handler; -import java.io.IOException; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - import launcher.helper.IOHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; import launcher.serialize.config.entry.BlockConfigEntry; -public final class BinaryFileAuthHandler extends FileAuthHandler { - public BinaryFileAuthHandler(BlockConfigEntry block) { +import java.io.IOException; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public final class BinaryFileAuthHandler extends FileAuthHandler +{ + BinaryFileAuthHandler(BlockConfigEntry block) + { super(block); } @Override - protected void readAuthFile() throws IOException { - try (HInput input = new HInput(IOHelper.newInput(file))) { + protected void readAuthFile() throws IOException + { + try (HInput input = new HInput(IOHelper.newInput(file))) + { int count = input.readLength(0); - for (int i = 0; i < count; i++) { + for (int i = 0; i < count; i++) + { UUID uuid = input.readUUID(); Entry entry = new Entry(input); addAuth(uuid, entry); @@ -28,11 +33,14 @@ } @Override - protected void writeAuthFileTmp() throws IOException { + protected void writeAuthFileTmp() throws IOException + { Set> entrySet = entrySet(); - try (HOutput output = new HOutput(IOHelper.newOutput(fileTmp))) { + try (HOutput output = new HOutput(IOHelper.newOutput(fileTmp))) + { output.writeLength(entrySet.size(), 0); - for (Map.Entry entry : entrySet) { + for (Map.Entry entry : entrySet) + { output.writeUUID(entry.getKey()); entry.getValue().write(output); } diff --git a/LaunchServer/source/auth/handler/CachedAuthHandler.java b/LaunchServer/source/auth/handler/CachedAuthHandler.java index 5da02ca..f4c24c5 100644 --- a/LaunchServer/source/auth/handler/CachedAuthHandler.java +++ b/LaunchServer/source/auth/handler/CachedAuthHandler.java @@ -1,11 +1,5 @@ package launchserver.auth.handler; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; - import launcher.LauncherAPI; import launcher.helper.CommonHelper; import launcher.helper.SecurityHelper; @@ -13,19 +7,29 @@ import launcher.serialize.config.entry.BlockConfigEntry; import launchserver.auth.provider.AuthProviderResult; -public abstract class CachedAuthHandler extends AuthHandler { +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + +public abstract class CachedAuthHandler extends AuthHandler +{ private final Map entryCache = new HashMap<>(1024); private final Map usernamesCache = new HashMap<>(1024); @LauncherAPI - protected CachedAuthHandler(BlockConfigEntry block) { + protected CachedAuthHandler(BlockConfigEntry block) + { super(block); } @Override - public final synchronized UUID auth(AuthProviderResult result) throws IOException { + public final synchronized UUID auth(AuthProviderResult result) throws IOException + { Entry entry = getEntry(result.username); - if (entry == null || !updateAuth(entry.uuid, entry.username, result.accessToken)) { + if (entry == null || !updateAuth(entry.uuid, entry.username, result.accessToken)) + { return authError(String.format("UUID is null for username '%s'", result.username)); } @@ -37,17 +41,20 @@ } @Override - public final synchronized UUID checkServer(String username, String serverID) throws IOException { + public final synchronized UUID checkServer(String username, String serverID) throws IOException + { Entry entry = getEntry(username); return entry != null && username.equals(entry.username) && - serverID.equals(entry.serverID) ? entry.uuid : null; + serverID.equals(entry.serverID) ? entry.uuid : null; } @Override - public final synchronized boolean joinServer(String username, String accessToken, String serverID) throws IOException { + public final synchronized boolean joinServer(String username, String accessToken, String serverID) throws IOException + { Entry entry = getEntry(username); if (entry == null || !username.equals(entry.username) || !accessToken.equals(entry.accessToken) || - !updateServerID(entry.uuid, serverID)) { + !updateServerID(entry.uuid, serverID)) + { return false; // Account doesn't exist or invalid access token } @@ -57,13 +64,15 @@ } @Override - public final synchronized UUID usernameToUUID(String username) throws IOException { + public final synchronized UUID usernameToUUID(String username) throws IOException + { Entry entry = getEntry(username); return entry == null ? null : entry.uuid; } @Override - public final synchronized String uuidToUsername(UUID uuid) throws IOException { + public final synchronized String uuidToUsername(UUID uuid) throws IOException + { Entry entry = getEntry(uuid); return entry == null ? null : entry.username; } @@ -81,34 +90,42 @@ protected abstract boolean updateServerID(UUID uuid, String serverID) throws IOException; @LauncherAPI - protected void addEntry(Entry entry) { + protected void addEntry(Entry entry) + { Entry previous = entryCache.put(entry.uuid, entry); - if (previous != null) { // In case of username changing + if (previous != null) + { // In case of username changing usernamesCache.remove(CommonHelper.low(previous.username)); } usernamesCache.put(CommonHelper.low(entry.username), entry.uuid); } - private Entry getEntry(UUID uuid) throws IOException { + private Entry getEntry(UUID uuid) throws IOException + { Entry entry = entryCache.get(uuid); - if (entry == null) { + if (entry == null) + { entry = fetchEntry(uuid); - if (entry != null) { + if (entry != null) + { addEntry(entry); } } return entry; } - private Entry getEntry(String username) throws IOException { + private Entry getEntry(String username) throws IOException + { UUID uuid = usernamesCache.get(CommonHelper.low(username)); - if (uuid != null) { + if (uuid != null) + { return getEntry(uuid); } // Fetch entry by username Entry entry = fetchEntry(username); - if (entry != null) { + if (entry != null) + { addEntry(entry); } @@ -116,14 +133,17 @@ return entry; } - public static final class Entry { - @LauncherAPI public final UUID uuid; + public static final class Entry + { + @LauncherAPI + public final UUID uuid; private String username; private String accessToken; private String serverID; @LauncherAPI - public Entry(UUID uuid, String username, String accessToken, String serverID) { + public Entry(UUID uuid, String username, String accessToken, String serverID) + { this.uuid = Objects.requireNonNull(uuid, "uuid"); this.username = Objects.requireNonNull(username, "username"); this.accessToken = accessToken == null ? null : SecurityHelper.verifyToken(accessToken); diff --git a/LaunchServer/source/auth/handler/DelegateAuthHandler.java b/LaunchServer/source/auth/handler/DelegateAuthHandler.java index 6c6548b..d69a8b9 100644 --- a/LaunchServer/source/auth/handler/DelegateAuthHandler.java +++ b/LaunchServer/source/auth/handler/DelegateAuthHandler.java @@ -1,60 +1,71 @@ package launchserver.auth.handler; -import java.io.IOException; -import java.util.Objects; -import java.util.UUID; - import launcher.LauncherAPI; import launcher.helper.VerifyHelper; import launcher.serialize.config.entry.BlockConfigEntry; import launchserver.auth.provider.AuthProviderResult; -public class DelegateAuthHandler extends AuthHandler { +import java.io.IOException; +import java.util.Objects; +import java.util.UUID; + +public class DelegateAuthHandler extends AuthHandler +{ private volatile AuthHandler delegate; - public DelegateAuthHandler(BlockConfigEntry block) { + DelegateAuthHandler(BlockConfigEntry block) + { super(block); } @Override - public UUID auth(AuthProviderResult authResult) throws IOException { + public UUID auth(AuthProviderResult authResult) throws IOException + { return getDelegate().auth(authResult); } @Override - public UUID checkServer(String username, String serverID) throws IOException { + public UUID checkServer(String username, String serverID) throws IOException + { return getDelegate().checkServer(username, serverID); } @Override - public void close() throws IOException { + public void close() throws IOException + { AuthHandler delegate = this.delegate; - if (delegate != null) { + if (delegate != null) + { delegate.close(); } } @Override - public boolean joinServer(String username, String accessToken, String serverID) throws IOException { + public boolean joinServer(String username, String accessToken, String serverID) throws IOException + { return getDelegate().joinServer(username, accessToken, serverID); } @Override - public UUID usernameToUUID(String username) throws IOException { + public UUID usernameToUUID(String username) throws IOException + { return getDelegate().usernameToUUID(username); } @Override - public String uuidToUsername(UUID uuid) throws IOException { + public String uuidToUsername(UUID uuid) throws IOException + { return getDelegate().uuidToUsername(uuid); } - @LauncherAPI - public void setDelegate(AuthHandler delegate) { - this.delegate = delegate; + private AuthHandler getDelegate() + { + return VerifyHelper.verify(delegate, Objects::nonNull, "Delegate auth handler wasn't set"); } - private AuthHandler getDelegate() { - return VerifyHelper.verify(delegate, Objects::nonNull, "Delegate auth handler wasn't set"); + @LauncherAPI + public void setDelegate(AuthHandler delegate) + { + this.delegate = delegate; } } diff --git a/LaunchServer/source/auth/handler/FileAuthHandler.java b/LaunchServer/source/auth/handler/FileAuthHandler.java index 11b5132..09a8971 100644 --- a/LaunchServer/source/auth/handler/FileAuthHandler.java +++ b/LaunchServer/source/auth/handler/FileAuthHandler.java @@ -1,22 +1,8 @@ package launchserver.auth.handler; -import java.io.IOException; -import java.nio.file.Path; -import java.security.SecureRandom; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.locks.ReentrantReadWriteLock; - import launcher.LauncherAPI; import launcher.client.PlayerProfile; -import launcher.helper.CommonHelper; -import launcher.helper.IOHelper; -import launcher.helper.LogHelper; -import launcher.helper.SecurityHelper; -import launcher.helper.VerifyHelper; +import launcher.helper.*; import launcher.request.auth.JoinServerRequest; import launcher.serialize.HInput; import launcher.serialize.HOutput; @@ -26,10 +12,20 @@ import launcher.serialize.stream.StreamObject; import launchserver.auth.provider.AuthProviderResult; -public abstract class FileAuthHandler extends AuthHandler { - @LauncherAPI public final Path file; - @LauncherAPI public final Path fileTmp; - @LauncherAPI public final boolean offlineUUIDs; +import java.io.IOException; +import java.nio.file.Path; +import java.security.SecureRandom; +import java.util.*; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public abstract class FileAuthHandler extends AuthHandler +{ + @LauncherAPI + public final Path file; + @LauncherAPI + public final Path fileTmp; + @LauncherAPI + public final boolean offlineUUIDs; // Instance private final SecureRandom random = SecurityHelper.newRandom(); @@ -40,32 +36,40 @@ private final Map usernamesMap = new HashMap<>(256); @LauncherAPI - protected FileAuthHandler(BlockConfigEntry block) { + protected FileAuthHandler(BlockConfigEntry block) + { super(block); file = IOHelper.toPath(block.getEntryValue("file", StringConfigEntry.class)); fileTmp = IOHelper.toPath(block.getEntryValue("file", StringConfigEntry.class) + ".tmp"); offlineUUIDs = block.getEntryValue("offlineUUIDs", BooleanConfigEntry.class); // Read auth handler file - if (IOHelper.isFile(file)) { + if (IOHelper.isFile(file)) + { LogHelper.info("Reading auth handler file: '%s'", file); - try { + try + { readAuthFile(); - } catch (IOException e) { + } + catch (IOException e) + { LogHelper.error(e); } } } @Override - public final UUID auth(AuthProviderResult authResult) { + public final UUID auth(AuthProviderResult authResult) + { lock.writeLock().lock(); - try { + try + { UUID uuid = usernameToUUID(authResult.username); Entry entry = entryMap.get(uuid); // Not registered? Fix it! - if (entry == null) { + if (entry == null) + { entry = new Entry(authResult.username); // Generate UUID @@ -77,71 +81,94 @@ // Authenticate entry.auth(authResult.username, authResult.accessToken); return uuid; - } finally { + } + finally + { lock.writeLock().unlock(); } } @Override - public final UUID checkServer(String username, String serverID) { + public final UUID checkServer(String username, String serverID) + { lock.readLock().lock(); - try { + try + { UUID uuid = usernameToUUID(username); Entry entry = entryMap.get(uuid); // Check server (if has such account of course) return entry != null && entry.checkServer(username, serverID) ? uuid : null; - } finally { + } + finally + { lock.readLock().unlock(); } } @Override - public final void close() throws IOException { + public final void close() throws IOException + { lock.readLock().lock(); - try { + try + { LogHelper.info("Writing auth handler file (%d entries)", entryMap.size()); writeAuthFileTmp(); IOHelper.move(fileTmp, file); - } finally { + } + finally + { lock.readLock().unlock(); } } @Override - public final boolean joinServer(String username, String accessToken, String serverID) { + public final boolean joinServer(String username, String accessToken, String serverID) + { lock.writeLock().lock(); - try { + try + { Entry entry = entryMap.get(usernameToUUID(username)); return entry != null && entry.joinServer(username, accessToken, serverID); - } finally { + } + finally + { lock.writeLock().unlock(); } } @Override - public final UUID usernameToUUID(String username) { + public final UUID usernameToUUID(String username) + { lock.readLock().lock(); - try { + try + { return usernamesMap.get(CommonHelper.low(username)); - } finally { + } + finally + { lock.readLock().unlock(); } } @Override - public final String uuidToUsername(UUID uuid) { + public final String uuidToUsername(UUID uuid) + { lock.readLock().lock(); - try { + try + { Entry entry = entryMap.get(uuid); return entry == null ? null : entry.username; - } finally { + } + finally + { lock.readLock().unlock(); } } @LauncherAPI - public final Set> entrySet() { + public final Set> entrySet() + { return Collections.unmodifiableMap(entryMap).entrySet(); } @@ -152,23 +179,31 @@ protected abstract void writeAuthFileTmp() throws IOException; @LauncherAPI - protected final void addAuth(UUID uuid, Entry entry) { + protected final void addAuth(UUID uuid, Entry entry) + { lock.writeLock().lock(); - try { + try + { Entry previous = entryMap.put(uuid, entry); - if (previous != null) { // In case of username changing + if (previous != null) + { // In case of username changing usernamesMap.remove(CommonHelper.low(previous.username)); } usernamesMap.put(CommonHelper.low(entry.username), uuid); - } finally { + } + finally + { lock.writeLock().unlock(); } } - private UUID genUUIDFor(String username) { - if (offlineUUIDs) { + private UUID genUUIDFor(String username) + { + if (offlineUUIDs) + { UUID md5UUID = PlayerProfile.offlineUUID(username); - if (!entryMap.containsKey(md5UUID)) { + if (!entryMap.containsKey(md5UUID)) + { return md5UUID; } LogHelper.warning("Offline UUID collision, using random: '%s'", username); @@ -176,26 +211,32 @@ // Pick random UUID UUID uuid; - do { + do + { uuid = new UUID(random.nextLong(), random.nextLong()); - } while (entryMap.containsKey(uuid)); + } + while (entryMap.containsKey(uuid)); return uuid; } - public static final class Entry extends StreamObject { + public static final class Entry extends StreamObject + { private String username; private String accessToken; private String serverID; @LauncherAPI - public Entry(String username) { + public Entry(String username) + { this.username = VerifyHelper.verifyUsername(username); } @LauncherAPI - public Entry(String username, String accessToken, String serverID) { + public Entry(String username, String accessToken, String serverID) + { this(username); - if (accessToken == null && serverID != null) { + if (accessToken == null && serverID != null) + { throw new IllegalArgumentException("Can't set access token while server ID is null"); } @@ -205,56 +246,69 @@ } @LauncherAPI - public Entry(HInput input) throws IOException { + public Entry(HInput input) throws IOException + { username = VerifyHelper.verifyUsername(input.readString(64)); - if (input.readBoolean()) { + if (input.readBoolean()) + { accessToken = SecurityHelper.verifyToken(input.readASCII(-SecurityHelper.TOKEN_STRING_LENGTH)); - if (input.readBoolean()) { + if (input.readBoolean()) + { serverID = JoinServerRequest.verifyServerID(input.readASCII(41)); } } } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { output.writeString(username, 64); output.writeBoolean(accessToken != null); - if (accessToken != null) { + if (accessToken != null) + { output.writeASCII(accessToken, -SecurityHelper.TOKEN_STRING_LENGTH); output.writeBoolean(serverID != null); - if (serverID != null) { + if (serverID != null) + { output.writeASCII(serverID, 41); } } } @LauncherAPI - public String getAccessToken() { + public String getAccessToken() + { return accessToken; } @LauncherAPI - public String getServerID() { + public String getServerID() + { return serverID; } @LauncherAPI - public String getUsername() { + public String getUsername() + { return username; } - private void auth(String username, String accessToken) { + private void auth(String username, String accessToken) + { this.username = username; // Update username case this.accessToken = accessToken; serverID = null; } - private boolean checkServer(String username, String serverID) { + private boolean checkServer(String username, String serverID) + { return username.equals(this.username) && serverID.equals(this.serverID); } - private boolean joinServer(String username, String accessToken, String serverID) { - if (!username.equals(this.username) || !accessToken.equals(this.accessToken)) { + private boolean joinServer(String username, String accessToken, String serverID) + { + if (!username.equals(this.username) || !accessToken.equals(this.accessToken)) + { return false; // Username or access token mismatch } diff --git a/LaunchServer/source/auth/handler/MemoryAuthHandler.java b/LaunchServer/source/auth/handler/MemoryAuthHandler.java index 782d84e..1d8319c 100644 --- a/LaunchServer/source/auth/handler/MemoryAuthHandler.java +++ b/LaunchServer/source/auth/handler/MemoryAuthHandler.java @@ -1,62 +1,72 @@ package launchserver.auth.handler; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.UUID; - import launcher.helper.IOHelper; import launcher.helper.LogHelper; import launcher.helper.VerifyHelper; import launcher.serialize.config.entry.BlockConfigEntry; -public final class MemoryAuthHandler extends CachedAuthHandler { - public MemoryAuthHandler(BlockConfigEntry block) { +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.UUID; + +public final class MemoryAuthHandler extends CachedAuthHandler +{ + MemoryAuthHandler(BlockConfigEntry block) + { super(block); LogHelper.warning("Usage of MemoryAuthHandler isn't recommended!"); } - @Override - public void close() { - // Do nothing - } - - @Override - protected Entry fetchEntry(UUID uuid) { - return new Entry(uuid, toUsername(uuid), null, null); - } - - @Override - protected Entry fetchEntry(String username) { - return new Entry(toUUID(username), username, null, null); - } - - @Override - protected boolean updateAuth(UUID uuid, String username, String accessToken) { - return true; // Do nothing - } - - @Override - protected boolean updateServerID(UUID uuid, String serverID) { - return true; // Do nothing - } - - private static UUID toUUID(String username) { + private static UUID toUUID(String username) + { ByteBuffer buffer = ByteBuffer.wrap(Arrays.copyOf(IOHelper.encodeASCII(username), 16)); return new UUID(buffer.getLong(), buffer.getLong()); // MOST, LEAST } - private static String toUsername(UUID uuid) { + private static String toUsername(UUID uuid) + { byte[] bytes = ByteBuffer.allocate(16). - putLong(uuid.getMostSignificantBits()). - putLong(uuid.getLeastSignificantBits()).array(); + putLong(uuid.getMostSignificantBits()). + putLong(uuid.getLeastSignificantBits()).array(); // Find username end int length = 0; - while (length < bytes.length && bytes[length] != 0) { + while (length < bytes.length && bytes[length] != 0) + { length++; } // Decode and verify return VerifyHelper.verifyUsername(new String(bytes, 0, length, IOHelper.ASCII_CHARSET)); } + + @Override + public void close() + { + // Do nothing + } + + @Override + protected Entry fetchEntry(UUID uuid) + { + return new Entry(uuid, toUsername(uuid), null, null); + } + + @Override + protected Entry fetchEntry(String username) + { + return new Entry(toUUID(username), username, null, null); + } + + @Override + protected boolean updateAuth(UUID uuid, String username, String accessToken) + { + return true; // Do nothing + } + + @Override + protected boolean updateServerID(UUID uuid, String serverID) + { + return true; // Do nothing + } } diff --git a/LaunchServer/source/auth/handler/MySQLAuthHandler.java b/LaunchServer/source/auth/handler/MySQLAuthHandler.java index 2dc518a..f87278d 100644 --- a/LaunchServer/source/auth/handler/MySQLAuthHandler.java +++ b/LaunchServer/source/auth/handler/MySQLAuthHandler.java @@ -1,13 +1,5 @@ package launchserver.auth.handler; -import java.io.IOException; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.UUID; - import launcher.helper.LogHelper; import launcher.helper.VerifyHelper; import launcher.serialize.config.entry.BlockConfigEntry; @@ -15,7 +7,12 @@ import launcher.serialize.config.entry.StringConfigEntry; import launchserver.auth.MySQLSourceConfig; -public final class MySQLAuthHandler extends CachedAuthHandler { +import java.io.IOException; +import java.sql.*; +import java.util.UUID; + +public final class MySQLAuthHandler extends CachedAuthHandler +{ private final MySQLSourceConfig mySQLHolder; private final String uuidColumn; private final String usernameColumn; @@ -28,66 +25,77 @@ private final String updateAuthSQL; private final String updateServerIDSQL; - public MySQLAuthHandler(BlockConfigEntry block) { + MySQLAuthHandler(BlockConfigEntry block) + { super(block); mySQLHolder = new MySQLSourceConfig("authHandlerPool", block); // Read query params String table = VerifyHelper.verifyIDName( - block.getEntryValue("table", StringConfigEntry.class)); + block.getEntryValue("table", StringConfigEntry.class)); uuidColumn = VerifyHelper.verifyIDName( - block.getEntryValue("uuidColumn", StringConfigEntry.class)); + block.getEntryValue("uuidColumn", StringConfigEntry.class)); usernameColumn = VerifyHelper.verifyIDName( - block.getEntryValue("usernameColumn", StringConfigEntry.class)); + block.getEntryValue("usernameColumn", StringConfigEntry.class)); accessTokenColumn = VerifyHelper.verifyIDName( - block.getEntryValue("accessTokenColumn", StringConfigEntry.class)); + block.getEntryValue("accessTokenColumn", StringConfigEntry.class)); serverIDColumn = VerifyHelper.verifyIDName( - block.getEntryValue("serverIDColumn", StringConfigEntry.class)); + block.getEntryValue("serverIDColumn", StringConfigEntry.class)); // Prepare SQL queries queryByUUIDSQL = String.format("SELECT %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1", - uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table, uuidColumn); + uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table, uuidColumn); queryByUsernameSQL = String.format("SELECT %s, %s, %s, %s FROM %s WHERE %s=? LIMIT 1", - uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table, usernameColumn); + uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table, usernameColumn); updateAuthSQL = String.format("UPDATE %s SET %s=?, %s=?, %s=NULL WHERE %s=? LIMIT 1", - table, usernameColumn, accessTokenColumn, serverIDColumn, uuidColumn); + table, usernameColumn, accessTokenColumn, serverIDColumn, uuidColumn); updateServerIDSQL = String.format("UPDATE %s SET %s=? WHERE %s=? LIMIT 1", - table, serverIDColumn, uuidColumn); + table, serverIDColumn, uuidColumn); // Fetch all entries - if (block.getEntryValue("fetchAll", BooleanConfigEntry.class)) { + if (block.getEntryValue("fetchAll", BooleanConfigEntry.class)) + { LogHelper.info("Fetching all AuthHandler entries"); String query = String.format("SELECT %s, %s, %s, %s FROM %s", - uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table); + uuidColumn, usernameColumn, accessTokenColumn, serverIDColumn, table); try (Connection c = mySQLHolder.getConnection(); Statement statement = c.createStatement(); - ResultSet set = statement.executeQuery(query)) { - for (Entry entry = constructEntry(set); entry != null; entry = constructEntry(set)) { + ResultSet set = statement.executeQuery(query)) + { + for (Entry entry = constructEntry(set); entry != null; entry = constructEntry(set)) + { addEntry(entry); } - } catch (SQLException e) { + } + catch (SQLException e) + { LogHelper.error(e); } } } @Override - public void close() { + public void close() + { mySQLHolder.close(); } @Override - protected Entry fetchEntry(String username) throws IOException { + protected Entry fetchEntry(String username) throws IOException + { return query(queryByUsernameSQL, username); } @Override - protected Entry fetchEntry(UUID uuid) throws IOException { + protected Entry fetchEntry(UUID uuid) throws IOException + { return query(queryByUUIDSQL, uuid.toString()); } @Override - protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException { - try (Connection c = mySQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(updateAuthSQL)) { + protected boolean updateAuth(UUID uuid, String username, String accessToken) throws IOException + { + try (Connection c = mySQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(updateAuthSQL)) + { s.setString(1, username); // Username case s.setString(2, accessToken); s.setString(3, uuid.toString()); @@ -95,40 +103,52 @@ // Execute update s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); return s.executeUpdate() > 0; - } catch (SQLException e) { + } + catch (SQLException e) + { throw new IOException(e); } } @Override - protected boolean updateServerID(UUID uuid, String serverID) throws IOException { - try (Connection c = mySQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(updateServerIDSQL)) { + protected boolean updateServerID(UUID uuid, String serverID) throws IOException + { + try (Connection c = mySQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(updateServerIDSQL)) + { s.setString(1, serverID); s.setString(2, uuid.toString()); // Execute update s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); return s.executeUpdate() > 0; - } catch (SQLException e) { + } + catch (SQLException e) + { throw new IOException(e); } } - private Entry constructEntry(ResultSet set) throws SQLException { + private Entry constructEntry(ResultSet set) throws SQLException + { return set.next() ? new Entry(UUID.fromString(set.getString(uuidColumn)), set.getString(usernameColumn), - set.getString(accessTokenColumn), set.getString(serverIDColumn)) : null; + set.getString(accessTokenColumn), set.getString(serverIDColumn)) : null; } - private Entry query(String sql, String value) throws IOException { - try (Connection c = mySQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(sql)) { + private Entry query(String sql, String value) throws IOException + { + try (Connection c = mySQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(sql)) + { s.setString(1, value); // Execute query s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); - try (ResultSet set = s.executeQuery()) { + try (ResultSet set = s.executeQuery()) + { return constructEntry(set); } - } catch (SQLException e) { + } + catch (SQLException e) + { throw new IOException(e); } } diff --git a/LaunchServer/source/auth/handler/TextFileAuthHandler.java b/LaunchServer/source/auth/handler/TextFileAuthHandler.java index d4163e6..26c3535 100644 --- a/LaunchServer/source/auth/handler/TextFileAuthHandler.java +++ b/LaunchServer/source/auth/handler/TextFileAuthHandler.java @@ -1,13 +1,5 @@ package launchserver.auth.handler; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - import launcher.helper.IOHelper; import launcher.helper.VerifyHelper; import launcher.serialize.config.TextConfigReader; @@ -17,32 +9,53 @@ import launcher.serialize.config.entry.ConfigEntry.Type; import launcher.serialize.config.entry.StringConfigEntry; -public final class TextFileAuthHandler extends FileAuthHandler { - public TextFileAuthHandler(BlockConfigEntry block) { +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public final class TextFileAuthHandler extends FileAuthHandler +{ + TextFileAuthHandler(BlockConfigEntry block) + { super(block); } + private static StringConfigEntry cc(String value) + { + StringConfigEntry entry = new StringConfigEntry(value, true, 4); + entry.setComment(0, "\n\t"); // Pre-name + entry.setComment(2, " "); // Pre-value + return entry; + } + @Override - protected void readAuthFile() throws IOException { + protected void readAuthFile() throws IOException + { BlockConfigEntry authFile; - try (BufferedReader reader = IOHelper.newReader(file)) { + try (BufferedReader reader = IOHelper.newReader(file)) + { authFile = TextConfigReader.read(reader, false); } // Read auths from config block Set>> entrySet = authFile.getValue().entrySet(); - for (Map.Entry> entry : entrySet) { + for (Map.Entry> entry : entrySet) + { UUID uuid = UUID.fromString(entry.getKey()); ConfigEntry value = VerifyHelper.verify(entry.getValue(), - v -> v.getType() == Type.BLOCK, "Illegal config entry type: " + uuid); + v -> v.getType() == Type.BLOCK, "Illegal config entry type: " + uuid); // Get auth entry data BlockConfigEntry authBlock = (BlockConfigEntry) value; String username = authBlock.getEntryValue("username", StringConfigEntry.class); String accessToken = authBlock.hasEntry("accessToken") ? - authBlock.getEntryValue("accessToken", StringConfigEntry.class) : null; + authBlock.getEntryValue("accessToken", StringConfigEntry.class) : null; String serverID = authBlock.hasEntry("serverID") ? - authBlock.getEntryValue("serverID", StringConfigEntry.class) : null; + authBlock.getEntryValue("serverID", StringConfigEntry.class) : null; // Add auth entry addAuth(uuid, new Entry(username, accessToken, serverID)); @@ -50,13 +63,15 @@ } @Override - protected void writeAuthFileTmp() throws IOException { + protected void writeAuthFileTmp() throws IOException + { boolean next = false; // Write auth blocks to map Set> entrySet = entrySet(); Map> map = new LinkedHashMap<>(entrySet.size()); - for (Map.Entry entry : entrySet) { + for (Map.Entry entry : entrySet) + { UUID uuid = entry.getKey(); Entry auth = entry.getValue(); @@ -64,19 +79,24 @@ Map> authMap = new LinkedHashMap<>(entrySet.size()); authMap.put("username", cc(auth.getUsername())); String accessToken = auth.getAccessToken(); - if (accessToken != null) { + if (accessToken != null) + { authMap.put("accessToken", cc(accessToken)); } String serverID = auth.getServerID(); - if (serverID != null) { + if (serverID != null) + { authMap.put("serverID", cc(serverID)); } // Create and add auth block BlockConfigEntry authBlock = new BlockConfigEntry(authMap, true, 5); - if (next) { + if (next) + { authBlock.setComment(0, "\n"); // Pre-name - } else { + } + else + { next = true; } authBlock.setComment(2, " "); // Pre-value @@ -85,17 +105,11 @@ } // Write auth handler file - try (BufferedWriter writer = IOHelper.newWriter(fileTmp)) { + try (BufferedWriter writer = IOHelper.newWriter(fileTmp)) + { BlockConfigEntry authFile = new BlockConfigEntry(map, true, 1); authFile.setComment(0, "\n"); TextConfigWriter.write(authFile, writer, true); } } - - private static StringConfigEntry cc(String value) { - StringConfigEntry entry = new StringConfigEntry(value, true, 4); - entry.setComment(0, "\n\t"); // Pre-name - entry.setComment(2, " "); // Pre-value - return entry; - } } diff --git a/LaunchServer/source/auth/provider/AcceptAuthProvider.java b/LaunchServer/source/auth/provider/AcceptAuthProvider.java index 457cf72..65bf62b 100644 --- a/LaunchServer/source/auth/provider/AcceptAuthProvider.java +++ b/LaunchServer/source/auth/provider/AcceptAuthProvider.java @@ -3,18 +3,22 @@ import launcher.helper.SecurityHelper; import launcher.serialize.config.entry.BlockConfigEntry; -public final class AcceptAuthProvider extends AuthProvider { - public AcceptAuthProvider(BlockConfigEntry block) { +public final class AcceptAuthProvider extends AuthProvider +{ + AcceptAuthProvider(BlockConfigEntry block) + { super(block); } @Override - public AuthProviderResult auth(String login, String password, String ip) { + public AuthProviderResult auth(String login, String password, String ip) + { return new AuthProviderResult(login, SecurityHelper.randomStringToken()); // Same as login } @Override - public void close() { + public void close() + { // Do nothing } } diff --git a/LaunchServer/source/auth/provider/AuthProvider.java b/LaunchServer/source/auth/provider/AuthProvider.java index 8cfe8de..9ff33bc 100644 --- a/LaunchServer/source/auth/provider/AuthProvider.java +++ b/LaunchServer/source/auth/provider/AuthProvider.java @@ -1,50 +1,22 @@ package launchserver.auth.provider; -import java.io.IOException; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - import launcher.LauncherAPI; import launcher.helper.VerifyHelper; import launcher.serialize.config.ConfigObject; import launcher.serialize.config.entry.BlockConfigEntry; import launchserver.auth.AuthException; -public abstract class AuthProvider extends ConfigObject implements AutoCloseable { +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class AuthProvider extends ConfigObject implements AutoCloseable +{ private static final Map> AUTH_PROVIDERS = new ConcurrentHashMap<>(8); - @LauncherAPI - protected AuthProvider(BlockConfigEntry block) { - super(block); - } - - @Override - public abstract void close() throws IOException; - - @LauncherAPI - public abstract AuthProviderResult auth(String login, String password, String ip) throws Throwable; - - @LauncherAPI - public static AuthProviderResult authError(String message) throws AuthException { - throw new AuthException(message); - } - - @LauncherAPI - public static AuthProvider newProvider(String name, BlockConfigEntry block) { - VerifyHelper.verifyIDName(name); - Adapter authHandlerAdapter = VerifyHelper.getMapValue(AUTH_PROVIDERS, name, - String.format("Unknown auth provider: '%s'", name)); - return authHandlerAdapter.convert(block); - } - - @LauncherAPI - public static void registerProvider(String name, Adapter adapter) { - VerifyHelper.putIfAbsent(AUTH_PROVIDERS, name, Objects.requireNonNull(adapter, "adapter"), - String.format("Auth provider has been already registered: '%s'", name)); - } - - static { + static + { registerProvider("accept", AcceptAuthProvider::new); registerProvider("reject", RejectAuthProvider::new); registerProvider("delegate", DelegateAuthProvider::new); @@ -56,4 +28,38 @@ registerProvider("mysql-bcrypt", MySQLBcryptAuthProvider::new); registerProvider("request", RequestAuthProvider::new); } + + @LauncherAPI + protected AuthProvider(BlockConfigEntry block) + { + super(block); + } + + @LauncherAPI + public static AuthProviderResult authError(String message) throws AuthException + { + throw new AuthException(message); + } + + @LauncherAPI + public static AuthProvider newProvider(String name, BlockConfigEntry block) + { + VerifyHelper.verifyIDName(name); + Adapter authHandlerAdapter = VerifyHelper.getMapValue(AUTH_PROVIDERS, name, + String.format("Unknown auth provider: '%s'", name)); + return authHandlerAdapter.convert(block); + } + + @LauncherAPI + public static void registerProvider(String name, Adapter adapter) + { + VerifyHelper.putIfAbsent(AUTH_PROVIDERS, name, Objects.requireNonNull(adapter, "adapter"), + String.format("Auth provider has been already registered: '%s'", name)); + } + + @Override + public abstract void close() throws IOException; + + @LauncherAPI + public abstract AuthProviderResult auth(String login, String password, String ip) throws Throwable; } diff --git a/LaunchServer/source/auth/provider/AuthProviderResult.java b/LaunchServer/source/auth/provider/AuthProviderResult.java index c7be9dd..51bc771 100644 --- a/LaunchServer/source/auth/provider/AuthProviderResult.java +++ b/LaunchServer/source/auth/provider/AuthProviderResult.java @@ -1,10 +1,12 @@ package launchserver.auth.provider; -public class AuthProviderResult { +public class AuthProviderResult +{ public final String username; public final String accessToken; - public AuthProviderResult(String username, String accessToken) { + AuthProviderResult(String username, String accessToken) + { this.username = username; this.accessToken = accessToken; } diff --git a/LaunchServer/source/auth/provider/DelegateAuthProvider.java b/LaunchServer/source/auth/provider/DelegateAuthProvider.java index f705063..4f6af3b 100644 --- a/LaunchServer/source/auth/provider/DelegateAuthProvider.java +++ b/LaunchServer/source/auth/provider/DelegateAuthProvider.java @@ -1,38 +1,45 @@ package launchserver.auth.provider; -import java.io.IOException; -import java.util.Objects; - import launcher.LauncherAPI; import launcher.helper.VerifyHelper; import launcher.serialize.config.entry.BlockConfigEntry; -public class DelegateAuthProvider extends AuthProvider { +import java.io.IOException; +import java.util.Objects; + +public class DelegateAuthProvider extends AuthProvider +{ private volatile AuthProvider delegate; - public DelegateAuthProvider(BlockConfigEntry block) { + DelegateAuthProvider(BlockConfigEntry block) + { super(block); } @Override - public AuthProviderResult auth(String login, String password, String ip) throws Throwable { + public AuthProviderResult auth(String login, String password, String ip) throws Throwable + { return getDelegate().auth(login, password, ip); } @Override - public void close() throws IOException { + public void close() throws IOException + { AuthProvider delegate = this.delegate; - if (delegate != null) { + if (delegate != null) + { delegate.close(); } } - @LauncherAPI - public void setDelegate(AuthProvider delegate) { - this.delegate = delegate; + private AuthProvider getDelegate() + { + return VerifyHelper.verify(delegate, Objects::nonNull, "Delegate auth provider wasn't set"); } - private AuthProvider getDelegate() { - return VerifyHelper.verify(delegate, Objects::nonNull, "Delegate auth provider wasn't set"); + @LauncherAPI + public void setDelegate(AuthProvider delegate) + { + this.delegate = delegate; } } diff --git a/LaunchServer/source/auth/provider/DigestAuthProvider.java b/LaunchServer/source/auth/provider/DigestAuthProvider.java index a43698c..9109be2 100644 --- a/LaunchServer/source/auth/provider/DigestAuthProvider.java +++ b/LaunchServer/source/auth/provider/DigestAuthProvider.java @@ -7,29 +7,38 @@ import launcher.serialize.config.entry.StringConfigEntry; import launchserver.auth.AuthException; -public abstract class DigestAuthProvider extends AuthProvider { +public abstract class DigestAuthProvider extends AuthProvider +{ private final DigestAlgorithm digest; @LauncherAPI - protected DigestAuthProvider(BlockConfigEntry block) { + protected DigestAuthProvider(BlockConfigEntry block) + { super(block); digest = DigestAlgorithm.byName(block.getEntryValue("digest", StringConfigEntry.class)); } @LauncherAPI - protected final void verifyDigest(String validDigest, String password) throws AuthException { + protected final void verifyDigest(String validDigest, String password) throws AuthException + { boolean valid; - if (digest == DigestAlgorithm.PLAIN) { + if (digest == DigestAlgorithm.PLAIN) + { valid = password.equals(validDigest); - } else if (validDigest == null) { + } + else if (validDigest == null) + { valid = false; - } else { + } + else + { byte[] actualDigest = SecurityHelper.digest(digest, password); valid = SecurityHelper.toHex(actualDigest).equals(validDigest); } // Verify is valid - if (!valid) { + if (!valid) + { authError("Incorrect username or password"); } } diff --git a/LaunchServer/source/auth/provider/FileAuthProvider.java b/LaunchServer/source/auth/provider/FileAuthProvider.java index e8b991a..84c59ab 100644 --- a/LaunchServer/source/auth/provider/FileAuthProvider.java +++ b/LaunchServer/source/auth/provider/FileAuthProvider.java @@ -1,5 +1,13 @@ package launchserver.auth.provider; +import launcher.helper.*; +import launcher.serialize.config.ConfigObject; +import launcher.serialize.config.TextConfigReader; +import launcher.serialize.config.entry.BlockConfigEntry; +import launcher.serialize.config.entry.ConfigEntry; +import launcher.serialize.config.entry.ConfigEntry.Type; +import launcher.serialize.config.entry.StringConfigEntry; + import java.io.BufferedReader; import java.io.IOException; import java.nio.file.Path; @@ -8,19 +16,8 @@ import java.util.Map; import java.util.Set; -import launcher.helper.CommonHelper; -import launcher.helper.IOHelper; -import launcher.helper.LogHelper; -import launcher.helper.SecurityHelper; -import launcher.helper.VerifyHelper; -import launcher.serialize.config.ConfigObject; -import launcher.serialize.config.TextConfigReader; -import launcher.serialize.config.entry.BlockConfigEntry; -import launcher.serialize.config.entry.ConfigEntry; -import launcher.serialize.config.entry.ConfigEntry.Type; -import launcher.serialize.config.entry.StringConfigEntry; - -public final class FileAuthProvider extends DigestAuthProvider { +public final class FileAuthProvider extends DigestAuthProvider +{ private final Path file; // Cache @@ -28,29 +25,36 @@ private final Object cacheLock = new Object(); private FileTime cacheLastModified; - public FileAuthProvider(BlockConfigEntry block) { + FileAuthProvider(BlockConfigEntry block) + { super(block); file = IOHelper.toPath(block.getEntryValue("file", StringConfigEntry.class)); // Try to update cache - try { + try + { updateCache(); - } catch (IOException e) { + } + catch (IOException e) + { LogHelper.error(e); } } @Override - public AuthProviderResult auth(String login, String password, String ip) throws IOException { + public AuthProviderResult auth(String login, String password, String ip) throws IOException + { Entry entry; - synchronized (cacheLock) { + synchronized (cacheLock) + { updateCache(); entry = entries.get(CommonHelper.low(login)); } // Verify digest and return true username verifyDigest(entry == null ? null : entry.password, password); - if (entry == null || entry.ip != null && !entry.ip.equals(ip)) { + if (entry == null || entry.ip != null && !entry.ip.equals(ip)) + { authError("Authentication from this IP is not allowed"); } @@ -59,53 +63,60 @@ } @Override - public void close() { + public void close() + { // Do nothing } - private void updateCache() throws IOException { + private void updateCache() throws IOException + { FileTime lastModified = IOHelper.readAttributes(file).lastModifiedTime(); - if (lastModified.equals(cacheLastModified)) { + if (lastModified.equals(cacheLastModified)) + { return; // Not modified, so cache is up-to-date } // Read file LogHelper.info("Recaching auth provider file: '%s'", file); BlockConfigEntry authFile; - try (BufferedReader reader = IOHelper.newReader(file)) { + try (BufferedReader reader = IOHelper.newReader(file)) + { authFile = TextConfigReader.read(reader, false); } // Read entries from config block entries.clear(); Set>> entrySet = authFile.getValue().entrySet(); - for (Map.Entry> entry : entrySet) { + for (Map.Entry> entry : entrySet) + { String login = entry.getKey(); ConfigEntry value = VerifyHelper.verify(entry.getValue(), v -> v.getType() == Type.BLOCK, - String.format("Illegal config entry type: '%s'", login)); + String.format("Illegal config entry type: '%s'", login)); // Add auth entry Entry auth = new Entry((BlockConfigEntry) value); VerifyHelper.putIfAbsent(entries, CommonHelper.low(login), auth, - String.format("Duplicate login: '%s'", login)); + String.format("Duplicate login: '%s'", login)); } // Update last modified time cacheLastModified = lastModified; } - private static final class Entry extends ConfigObject { + private static final class Entry extends ConfigObject + { private final String username; private final String password; private final String ip; - private Entry(BlockConfigEntry block) { + private Entry(BlockConfigEntry block) + { super(block); username = VerifyHelper.verifyUsername(block.getEntryValue("username", StringConfigEntry.class)); password = VerifyHelper.verify(block.getEntryValue("password", StringConfigEntry.class), - VerifyHelper.NOT_EMPTY, String.format("Password can't be empty: '%s'", username)); + VerifyHelper.NOT_EMPTY, String.format("Password can't be empty: '%s'", username)); ip = block.hasEntry("ip") ? VerifyHelper.verify(block.getEntryValue("ip", StringConfigEntry.class), - VerifyHelper.NOT_EMPTY, String.format("IP can't be empty: '%s'", username)) : null; + VerifyHelper.NOT_EMPTY, String.format("IP can't be empty: '%s'", username)) : null; } } } diff --git a/LaunchServer/source/auth/provider/MojangAuthProvider.java b/LaunchServer/source/auth/provider/MojangAuthProvider.java index 4fe97a2..f06fbce 100644 --- a/LaunchServer/source/auth/provider/MojangAuthProvider.java +++ b/LaunchServer/source/auth/provider/MojangAuthProvider.java @@ -1,5 +1,13 @@ package launchserver.auth.provider; +import com.eclipsesource.json.Json; +import com.eclipsesource.json.JsonObject; +import com.eclipsesource.json.JsonValue; +import com.eclipsesource.json.WriterConfig; +import launcher.helper.IOHelper; +import launcher.helper.LogHelper; +import launcher.serialize.config.entry.BlockConfigEntry; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -11,35 +19,76 @@ import java.util.UUID; import java.util.regex.Pattern; -import com.eclipsesource.json.Json; -import com.eclipsesource.json.JsonObject; -import com.eclipsesource.json.JsonValue; -import com.eclipsesource.json.WriterConfig; -import launcher.helper.IOHelper; -import launcher.helper.LogHelper; -import launcher.serialize.config.entry.BlockConfigEntry; - -public final class MojangAuthProvider extends AuthProvider { +public final class MojangAuthProvider extends AuthProvider +{ private static final Pattern UUID_REGEX = Pattern.compile("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})"); private static final URL URL; - public MojangAuthProvider(BlockConfigEntry block) { + static + { + try + { + URL = new URL("https://authserver.mojang.com/authenticate"); + } + catch (MalformedURLException e) + { + throw new InternalError(e); + } + } + + MojangAuthProvider(BlockConfigEntry block) + { super(block); } + public static JsonObject makeMojangRequest(URL url, JsonObject request) throws IOException + { + HttpURLConnection connection = request == null ? + (HttpURLConnection) IOHelper.newConnection(url) : + IOHelper.newConnectionPost(url); + + // Make request + if (request != null) + { + connection.setRequestProperty("Content-Type", "application/json"); + try (OutputStream output = connection.getOutputStream()) + { + output.write(request.toString(WriterConfig.MINIMAL).getBytes(StandardCharsets.UTF_8)); + } + } + connection.getResponseCode(); // Actually make request + + // Read response + InputStream errorInput = connection.getErrorStream(); + try (InputStream input = errorInput == null ? connection.getInputStream() : errorInput) + { + String charset = connection.getContentEncoding(); + Charset charsetObject = charset == null ? + IOHelper.UNICODE_CHARSET : Charset.forName(charset); + + // Parse response + String json = new String(IOHelper.read(input), charsetObject); + LogHelper.subDebug("Raw Mojang response: '" + json + '\''); + return json.isEmpty() ? null : Json.parse(json).asObject(); + } + } + @Override - public AuthProviderResult auth(String login, String password, String ip) throws Throwable { + public AuthProviderResult auth(String login, String password, String ip) throws Throwable + { JsonObject request = Json.object(). - add("agent", Json.object().add("name", "Minecraft").add("version", 1)). - add("username", login).add("password", password); + add("agent", Json.object().add("name", "Minecraft").add("version", 1)). + add("username", login).add("password", password); // Verify there's no error JsonObject response = makeMojangRequest(URL, request); - if (response == null) { + if (response == null) + { authError("Empty mojang response"); } JsonValue errorMessage = response.get("errorMessage"); - if (errorMessage != null) { + if (errorMessage != null) + { authError(errorMessage.asString()); } @@ -55,43 +104,8 @@ } @Override - public void close() { + public void close() + { // Do nothing } - - public static JsonObject makeMojangRequest(URL url, JsonObject request) throws IOException { - HttpURLConnection connection = request == null ? - (HttpURLConnection) IOHelper.newConnection(url) : - IOHelper.newConnectionPost(url); - - // Make request - if (request != null) { - connection.setRequestProperty("Content-Type", "application/json"); - try (OutputStream output = connection.getOutputStream()) { - output.write(request.toString(WriterConfig.MINIMAL).getBytes(StandardCharsets.UTF_8)); - } - } - connection.getResponseCode(); // Actually make request - - // Read response - InputStream errorInput = connection.getErrorStream(); - try (InputStream input = errorInput == null ? connection.getInputStream() : errorInput) { - String charset = connection.getContentEncoding(); - Charset charsetObject = charset == null ? - IOHelper.UNICODE_CHARSET : Charset.forName(charset); - - // Parse response - String json = new String(IOHelper.read(input), charsetObject); - LogHelper.subDebug("Raw Mojang response: '" + json + '\''); - return json.isEmpty() ? null : Json.parse(json).asObject(); - } - } - - static { - try { - URL = new URL("https://authserver.mojang.com/authenticate"); - } catch (MalformedURLException e) { - throw new InternalError(e); - } - } } diff --git a/LaunchServer/source/auth/provider/MojangAuthProviderResult.java b/LaunchServer/source/auth/provider/MojangAuthProviderResult.java index aee029d..3f5d041 100644 --- a/LaunchServer/source/auth/provider/MojangAuthProviderResult.java +++ b/LaunchServer/source/auth/provider/MojangAuthProviderResult.java @@ -2,11 +2,13 @@ import java.util.UUID; -public final class MojangAuthProviderResult extends AuthProviderResult { +public final class MojangAuthProviderResult extends AuthProviderResult +{ public final UUID uuid; public final String launcherToken; - public MojangAuthProviderResult(String username, String accessToken, UUID uuid, String launcherToken) { + MojangAuthProviderResult(String username, String accessToken, UUID uuid, String launcherToken) + { super(username, accessToken); this.uuid = uuid; this.launcherToken = launcherToken; diff --git a/LaunchServer/source/auth/provider/MySQLAuthProvider.java b/LaunchServer/source/auth/provider/MySQLAuthProvider.java index 7a95aa5..d708729 100644 --- a/LaunchServer/source/auth/provider/MySQLAuthProvider.java +++ b/LaunchServer/source/auth/provider/MySQLAuthProvider.java @@ -1,10 +1,5 @@ package launchserver.auth.provider; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - import launcher.helper.CommonHelper; import launcher.helper.SecurityHelper; import launcher.helper.VerifyHelper; @@ -14,40 +9,52 @@ import launchserver.auth.AuthException; import launchserver.auth.MySQLSourceConfig; -public final class MySQLAuthProvider extends AuthProvider { +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public final class MySQLAuthProvider extends AuthProvider +{ private final MySQLSourceConfig mySQLHolder; private final String query; private final String[] queryParams; - public MySQLAuthProvider(BlockConfigEntry block) { + MySQLAuthProvider(BlockConfigEntry block) + { super(block); mySQLHolder = new MySQLSourceConfig("authProviderPool", block); // Read query query = VerifyHelper.verify(block.getEntryValue("query", StringConfigEntry.class), - VerifyHelper.NOT_EMPTY, "MySQL query can't be empty"); + VerifyHelper.NOT_EMPTY, "MySQL query can't be empty"); queryParams = block.getEntry("queryParams", ListConfigEntry.class). - stream(StringConfigEntry.class).toArray(String[]::new); + stream(StringConfigEntry.class).toArray(String[]::new); } @Override - public AuthProviderResult auth(String login, String password, String ip) throws SQLException, AuthException { - try (Connection c = mySQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(query)) { - String[] replaceParams = { "login", login, "password", password, "ip", ip }; - for (int i = 0; i < queryParams.length; i++) { + public AuthProviderResult auth(String login, String password, String ip) throws SQLException, AuthException + { + try (Connection c = mySQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(query)) + { + String[] replaceParams = {"login", login, "password", password, "ip", ip}; + for (int i = 0; i < queryParams.length; i++) + { s.setString(i + 1, CommonHelper.replace(queryParams[i], replaceParams)); } // Execute SQL query s.setQueryTimeout(MySQLSourceConfig.TIMEOUT); - try (ResultSet set = s.executeQuery()) { + try (ResultSet set = s.executeQuery()) + { return set.next() ? new AuthProviderResult(set.getString(1), SecurityHelper.randomStringToken()) : authError("Incorrect username or password"); } } } @Override - public void close() { + public void close() + { // Do nothing } } diff --git a/LaunchServer/source/auth/provider/MySQLBcryptAuthProvider.java b/LaunchServer/source/auth/provider/MySQLBcryptAuthProvider.java index d2d6052..2ad79d6 100644 --- a/LaunchServer/source/auth/provider/MySQLBcryptAuthProvider.java +++ b/LaunchServer/source/auth/provider/MySQLBcryptAuthProvider.java @@ -1,12 +1,5 @@ package launchserver.auth.provider; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import org.mindrot.jbcrypt.BCrypt; - import launcher.helper.CommonHelper; import launcher.helper.SecurityHelper; import launcher.helper.VerifyHelper; @@ -15,6 +8,12 @@ import launcher.serialize.config.entry.StringConfigEntry; import launchserver.auth.AuthException; import launchserver.auth.MySQLSourceConfig; +import org.mindrot.jbcrypt.BCrypt; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; public final class MySQLBcryptAuthProvider extends AuthProvider { @@ -22,7 +21,7 @@ private final String query; private final String[] queryParams; - public MySQLBcryptAuthProvider(BlockConfigEntry block) + MySQLBcryptAuthProvider(BlockConfigEntry block) { super(block); mySQLHolder = new MySQLSourceConfig("authProviderPool", block); @@ -38,7 +37,7 @@ { try (Connection c = mySQLHolder.getConnection(); PreparedStatement s = c.prepareStatement(query)) { - String[] replaceParams = { "login", login, "password", password, "ip", ip }; + String[] replaceParams = {"login", login, "password", password, "ip", ip}; for (int i = 0; i < queryParams.length; i++) { s.setString(i + 1, CommonHelper.replace(queryParams[i], replaceParams)); @@ -54,7 +53,8 @@ } @Override - public void close() { + public void close() + { mySQLHolder.close(); } } diff --git a/LaunchServer/source/auth/provider/RejectAuthProvider.java b/LaunchServer/source/auth/provider/RejectAuthProvider.java index d16c65e..9b8a9d6 100644 --- a/LaunchServer/source/auth/provider/RejectAuthProvider.java +++ b/LaunchServer/source/auth/provider/RejectAuthProvider.java @@ -5,22 +5,26 @@ import launcher.serialize.config.entry.StringConfigEntry; import launchserver.auth.AuthException; -public final class RejectAuthProvider extends AuthProvider { +public final class RejectAuthProvider extends AuthProvider +{ private final String message; - public RejectAuthProvider(BlockConfigEntry block) { + RejectAuthProvider(BlockConfigEntry block) + { super(block); message = VerifyHelper.verify(block.getEntryValue("message", StringConfigEntry.class), VerifyHelper.NOT_EMPTY, - "Auth error message can't be empty"); + "Auth error message can't be empty"); } @Override - public AuthProviderResult auth(String login, String password, String ip) throws AuthException { + public AuthProviderResult auth(String login, String password, String ip) throws AuthException + { return authError(message); } @Override - public void close() { + public void close() + { // Do nothing } } diff --git a/LaunchServer/source/auth/provider/RequestAuthProvider.java b/LaunchServer/source/auth/provider/RequestAuthProvider.java index a360ad0..55dee7b 100644 --- a/LaunchServer/source/auth/provider/RequestAuthProvider.java +++ b/LaunchServer/source/auth/provider/RequestAuthProvider.java @@ -1,21 +1,23 @@ package launchserver.auth.provider; -import java.io.IOException; -import java.net.URL; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import launcher.helper.CommonHelper; import launcher.helper.IOHelper; import launcher.helper.SecurityHelper; import launcher.serialize.config.entry.BlockConfigEntry; import launcher.serialize.config.entry.StringConfigEntry; -public final class RequestAuthProvider extends AuthProvider { +import java.io.IOException; +import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class RequestAuthProvider extends AuthProvider +{ private final String url; private final Pattern response; - public RequestAuthProvider(BlockConfigEntry block) { + RequestAuthProvider(BlockConfigEntry block) + { super(block); url = block.getEntryValue("url", StringConfigEntry.class); response = Pattern.compile(block.getEntryValue("response", StringConfigEntry.class)); @@ -25,22 +27,25 @@ } @Override - public AuthProviderResult auth(String login, String password, String ip) throws IOException { + public AuthProviderResult auth(String login, String password, String ip) throws IOException + { String currentResponse = IOHelper.request(new URL(getFormattedURL(login, password, ip))); // Match username Matcher matcher = response.matcher(currentResponse); return matcher.matches() && matcher.groupCount() >= 1 ? - new AuthProviderResult(matcher.group("username"), SecurityHelper.randomStringToken()) : - authError(currentResponse); + new AuthProviderResult(matcher.group("username"), SecurityHelper.randomStringToken()) : + authError(currentResponse); } @Override - public void close() { + public void close() + { // Do nothing } - private String getFormattedURL(String login, String password, String ip) { + private String getFormattedURL(String login, String password, String ip) + { return CommonHelper.replace(url, "login", IOHelper.urlEncode(login), "password", IOHelper.urlEncode(password), "ip", IOHelper.urlEncode(ip)); } } diff --git a/LaunchServer/source/binary/EXEL4JLauncherBinary.java b/LaunchServer/source/binary/EXEL4JLauncherBinary.java index 07e9333..529be7f 100644 --- a/LaunchServer/source/binary/EXEL4JLauncherBinary.java +++ b/LaunchServer/source/binary/EXEL4JLauncherBinary.java @@ -1,9 +1,5 @@ package launchserver.binary; -import java.io.File; -import java.io.IOException; -import java.nio.file.Path; - import launcher.Launcher; import launcher.LauncherAPI; import launcher.helper.IOHelper; @@ -11,13 +7,14 @@ import launchserver.LaunchServer; import net.sf.launch4j.Builder; import net.sf.launch4j.Log; -import net.sf.launch4j.config.Config; -import net.sf.launch4j.config.ConfigPersister; -import net.sf.launch4j.config.Jre; -import net.sf.launch4j.config.LanguageID; -import net.sf.launch4j.config.VersionInfo; +import net.sf.launch4j.config.*; -public final class EXEL4JLauncherBinary extends LauncherBinary { +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; + +public final class EXEL4JLauncherBinary extends LauncherBinary +{ // URL constants private static final String DOWNLOAD_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html"; // Oracle JRE 8 @@ -25,35 +22,44 @@ private final Path faviconFile; @LauncherAPI - public EXEL4JLauncherBinary(LaunchServer server) { + public EXEL4JLauncherBinary(LaunchServer server) + { super(server, server.dir.resolve(server.config.binaryName + ".exe")); faviconFile = server.dir.resolve("favicon.ico"); setConfig(); } @Override - public void build() throws IOException { + public void build() throws IOException + { LogHelper.info("Building launcher EXE binary file (Using Launch4J)"); // Set favicon path Config config = ConfigPersister.getInstance().getConfig(); - if (IOHelper.isFile(faviconFile)) { + if (IOHelper.isFile(faviconFile)) + { config.setIcon(new File("favicon.ico")); - } else { + } + else + { config.setIcon(null); LogHelper.warning("Missing favicon.ico file"); } // Start building Builder builder = new Builder(Launch4JLog.INSTANCE); - try { + try + { builder.build(); - } catch (Throwable e) { + } + catch (Throwable e) + { throw new IOException(e); } } - private void setConfig() { + private void setConfig() + { Config config = new Config(); // Set string options @@ -102,16 +108,19 @@ ConfigPersister.getInstance().setAntConfig(config, null); } - private static final class Launch4JLog extends Log { + private static final class Launch4JLog extends Log + { private static final Launch4JLog INSTANCE = new Launch4JLog(); @Override - public void append(String s) { + public void append(String s) + { LogHelper.subInfo(s); } @Override - public void clear() { + public void clear() + { // Do nothing } } diff --git a/LaunchServer/source/binary/EXELauncherBinary.java b/LaunchServer/source/binary/EXELauncherBinary.java index 324e925..1bf8fb9 100644 --- a/LaunchServer/source/binary/EXELauncherBinary.java +++ b/LaunchServer/source/binary/EXELauncherBinary.java @@ -1,12 +1,12 @@ package launchserver.binary; -import java.io.IOException; -import java.nio.file.Files; - import launcher.helper.IOHelper; import launcher.helper.LogHelper; import launchserver.LaunchServer; +import java.io.IOException; +import java.nio.file.Files; + public final class EXELauncherBinary extends LauncherBinary { public EXELauncherBinary(LaunchServer server) @@ -15,8 +15,10 @@ } @Override - public void build() throws IOException { - if (IOHelper.isFile(binaryFile)) { + public void build() throws IOException + { + if (IOHelper.isFile(binaryFile)) + { LogHelper.subWarning("Deleting obsolete launcher EXE binary file"); Files.delete(binaryFile); } diff --git a/LaunchServer/source/binary/JARLauncherBinary.java b/LaunchServer/source/binary/JARLauncherBinary.java index 3a66de6..0900ba1 100644 --- a/LaunchServer/source/binary/JARLauncherBinary.java +++ b/LaunchServer/source/binary/JARLauncherBinary.java @@ -1,5 +1,15 @@ package launchserver.binary; +import launcher.Launcher; +import launcher.Launcher.Config; +import launcher.LauncherAPI; +import launcher.helper.IOHelper; +import launcher.helper.LogHelper; +import launcher.helper.SecurityHelper; +import launcher.helper.SecurityHelper.DigestAlgorithm; +import launcher.serialize.HOutput; +import launchserver.LaunchServer; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -12,49 +22,48 @@ import java.util.Map; import java.util.jar.JarOutputStream; import java.util.jar.Pack200; -import java.util.zip.Deflater; -import java.util.zip.GZIPInputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; +import java.util.zip.*; -import launcher.Launcher; -import launcher.Launcher.Config; -import launcher.LauncherAPI; -import launcher.helper.IOHelper; -import launcher.helper.LogHelper; -import launcher.helper.SecurityHelper; -import launcher.helper.SecurityHelper.DigestAlgorithm; -import launcher.serialize.HOutput; -import launchserver.LaunchServer; - -public final class JARLauncherBinary extends LauncherBinary { - @LauncherAPI public final Path runtimeDir; - @LauncherAPI public final Path initScriptFile; +public final class JARLauncherBinary extends LauncherBinary +{ + @LauncherAPI + public final Path runtimeDir; + @LauncherAPI + public final Path initScriptFile; @LauncherAPI - public JARLauncherBinary(LaunchServer server) throws IOException { + public JARLauncherBinary(LaunchServer server) throws IOException + { super(server, server.dir.resolve(server.config.binaryName + ".jar")); runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR); initScriptFile = runtimeDir.resolve(Launcher.INIT_SCRIPT_FILE); tryUnpackRuntime(); } + private static ZipEntry newEntry(String fileName) + { + return IOHelper.newZipEntry(Launcher.RUNTIME_DIR + IOHelper.CROSS_SEPARATOR + fileName); + } + @Override - public void build() throws IOException { + public void build() throws IOException + { tryUnpackRuntime(); // Build launcher binary LogHelper.info("Building launcher binary file"); - try (JarOutputStream output = new JarOutputStream(IOHelper.newOutput(binaryFile))) { + try (JarOutputStream output = new JarOutputStream(IOHelper.newOutput(binaryFile))) + { output.setMethod(ZipOutputStream.DEFLATED); output.setLevel(Deflater.BEST_COMPRESSION); - try (InputStream input = new GZIPInputStream(IOHelper.newInput(IOHelper.getResourceURL("Launcher.pack.gz")))) { + try (InputStream input = new GZIPInputStream(IOHelper.newInput(IOHelper.getResourceURL("Launcher.pack.gz")))) + { Pack200.newUnpacker().unpack(input, output); } // Verify has init script file - if (!IOHelper.isFile(initScriptFile)) { + if (!IOHelper.isFile(initScriptFile)) + { throw new IOException(String.format("Missing init script file ('%s')", Launcher.INIT_SCRIPT_FILE)); } @@ -64,8 +73,10 @@ // Create launcher config file byte[] launcherConfigBytes; - try (ByteArrayOutputStream configArray = IOHelper.newByteArrayOutput()) { - try (HOutput configOutput = new HOutput(configArray)) { + try (ByteArrayOutputStream configArray = IOHelper.newByteArrayOutput()) + { + try (HOutput configOutput = new HOutput(configArray)) + { new Config(server.config.getAddress(), server.config.port, server.publicKey, runtime).write(configOutput); } launcherConfigBytes = configArray.toByteArray(); @@ -78,18 +89,23 @@ } @LauncherAPI - public void tryUnpackRuntime() throws IOException { + public void tryUnpackRuntime() throws IOException + { // Verify is runtime dir unpacked - if (IOHelper.isDir(runtimeDir)) { + if (IOHelper.isDir(runtimeDir)) + { return; // Already unpacked } // Unpack launcher runtime files Files.createDirectory(runtimeDir); LogHelper.info("Unpacking launcher runtime files"); - try (ZipInputStream input = IOHelper.newZipInput(IOHelper.getResourceURL("launchserver/defaults/runtime.zip"))) { - for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) { - if (entry.isDirectory()) { + try (ZipInputStream input = IOHelper.newZipInput(IOHelper.getResourceURL("launchserver/defaults/runtime.zip"))) + { + for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) + { + if (entry.isDirectory()) + { continue; // Skip dirs } @@ -99,28 +115,28 @@ } } - private static ZipEntry newEntry(String fileName) { - return IOHelper.newZipEntry(Launcher.RUNTIME_DIR + IOHelper.CROSS_SEPARATOR + fileName); - } - - private final class RuntimeDirVisitor extends SimpleFileVisitor { + private final class RuntimeDirVisitor extends SimpleFileVisitor + { private final ZipOutputStream output; private final Map runtime; - private RuntimeDirVisitor(ZipOutputStream output, Map runtime) { + private RuntimeDirVisitor(ZipOutputStream output, Map runtime) + { this.output = output; this.runtime = runtime; } @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException + { String dirName = IOHelper.toString(runtimeDir.relativize(dir)); output.putNextEntry(newEntry(dirName + '/')); return super.preVisitDirectory(dir, attrs); } @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException + { String fileName = IOHelper.toString(runtimeDir.relativize(file)); runtime.put(fileName, SecurityHelper.digest(DigestAlgorithm.MD5, file)); diff --git a/LaunchServer/source/binary/LauncherBinary.java b/LaunchServer/source/binary/LauncherBinary.java index f84f297..751d2ac 100644 --- a/LaunchServer/source/binary/LauncherBinary.java +++ b/LaunchServer/source/binary/LauncherBinary.java @@ -1,21 +1,24 @@ package launchserver.binary; -import java.io.IOException; -import java.nio.file.Path; - import launcher.LauncherAPI; import launcher.helper.IOHelper; import launcher.serialize.signed.SignedBytesHolder; import launchserver.LaunchServer; +import java.io.IOException; +import java.nio.file.Path; + public abstract class LauncherBinary { - @LauncherAPI protected final LaunchServer server; - @LauncherAPI protected final Path binaryFile; + @LauncherAPI + protected final LaunchServer server; + @LauncherAPI + protected final Path binaryFile; private volatile SignedBytesHolder binary; @LauncherAPI - protected LauncherBinary(LaunchServer server, Path binaryFile) { + protected LauncherBinary(LaunchServer server, Path binaryFile) + { this.server = server; this.binaryFile = binaryFile; } diff --git a/LaunchServer/source/command/Command.java b/LaunchServer/source/command/Command.java index 9cf458f..c29cca6 100644 --- a/LaunchServer/source/command/Command.java +++ b/LaunchServer/source/command/Command.java @@ -1,20 +1,49 @@ package launchserver.command; -import java.util.UUID; - import launcher.LauncherAPI; import launcher.helper.VerifyHelper; import launchserver.LaunchServer; -public abstract class Command { - @LauncherAPI protected final LaunchServer server; +import java.util.UUID; + +public abstract class Command +{ + @LauncherAPI + protected final LaunchServer server; @LauncherAPI - protected Command(LaunchServer server) { + protected Command(LaunchServer server) + { this.server = server; } @LauncherAPI + protected static UUID parseUUID(String s) throws CommandException + { + try + { + return UUID.fromString(s); + } + catch (IllegalArgumentException ignored) + { + throw new CommandException(String.format("Invalid UUID: '%s'", s)); + } + } + + @LauncherAPI + protected static String parseUsername(String username) throws CommandException + { + try + { + return VerifyHelper.verifyUsername(username); + } + catch (IllegalArgumentException e) + { + throw new CommandException(e.getMessage()); + } + } + + @LauncherAPI public abstract String getArgsDescription(); // " [optional]" @LauncherAPI @@ -24,27 +53,11 @@ public abstract void invoke(String... args) throws Throwable; @LauncherAPI - protected final void verifyArgs(String[] args, int min) throws CommandException { - if (args.length < min) { + protected final void verifyArgs(String[] args, int min) throws CommandException + { + if (args.length < min) + { throw new CommandException("Command usage: " + getArgsDescription()); } } - - @LauncherAPI - protected static UUID parseUUID(String s) throws CommandException { - try { - return UUID.fromString(s); - } catch (IllegalArgumentException ignored) { - throw new CommandException(String.format("Invalid UUID: '%s'", s)); - } - } - - @LauncherAPI - protected static String parseUsername(String username) throws CommandException { - try { - return VerifyHelper.verifyUsername(username); - } catch (IllegalArgumentException e) { - throw new CommandException(e.getMessage()); - } - } } diff --git a/LaunchServer/source/command/CommandException.java b/LaunchServer/source/command/CommandException.java index d9858e6..7c14803 100644 --- a/LaunchServer/source/command/CommandException.java +++ b/LaunchServer/source/command/CommandException.java @@ -2,21 +2,25 @@ import launcher.LauncherAPI; -public final class CommandException extends Exception { +public final class CommandException extends Exception +{ private static final long serialVersionUID = -6588814993972117772L; @LauncherAPI - public CommandException(String message) { + public CommandException(String message) + { super(message); } @LauncherAPI - public CommandException(Throwable exc) { + public CommandException(Throwable exc) + { super(exc); } @Override - public String toString() { + public String toString() + { return getMessage(); } } diff --git a/LaunchServer/source/command/auth/AuthCommand.java b/LaunchServer/source/command/auth/AuthCommand.java index 08c22b5..03bd8d6 100644 --- a/LaunchServer/source/command/auth/AuthCommand.java +++ b/LaunchServer/source/command/auth/AuthCommand.java @@ -1,29 +1,34 @@ package launchserver.command.auth; -import java.util.UUID; - import launcher.helper.LogHelper; import launchserver.LaunchServer; import launchserver.auth.provider.AuthProviderResult; import launchserver.command.Command; -public final class AuthCommand extends Command { - public AuthCommand(LaunchServer server) { +import java.util.UUID; + +public final class AuthCommand extends Command +{ + public AuthCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return " "; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Try to auth with specified login and password"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { verifyArgs(args, 2); String login = args[0]; String password = args[1]; diff --git a/LaunchServer/source/command/auth/CheckServerCommand.java b/LaunchServer/source/command/auth/CheckServerCommand.java index 8d6d59c..4c60b0d 100644 --- a/LaunchServer/source/command/auth/CheckServerCommand.java +++ b/LaunchServer/source/command/auth/CheckServerCommand.java @@ -1,28 +1,33 @@ package launchserver.command.auth; -import java.util.UUID; - import launcher.helper.LogHelper; import launchserver.LaunchServer; import launchserver.command.Command; -public final class CheckServerCommand extends Command { - public CheckServerCommand(LaunchServer server) { +import java.util.UUID; + +public final class CheckServerCommand extends Command +{ + public CheckServerCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return " "; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Try to check server with specified credentials"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { verifyArgs(args, 2); String username = args[0]; String serverID = args[1]; diff --git a/LaunchServer/source/command/auth/JoinServerCommand.java b/LaunchServer/source/command/auth/JoinServerCommand.java index a94ed88..0643204 100644 --- a/LaunchServer/source/command/auth/JoinServerCommand.java +++ b/LaunchServer/source/command/auth/JoinServerCommand.java @@ -4,23 +4,28 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class JoinServerCommand extends Command { - public JoinServerCommand(LaunchServer server) { +public final class JoinServerCommand extends Command +{ + public JoinServerCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return " "; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Try to join server with specified credentials"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { verifyArgs(args, 3); String username = args[0]; String accessToken = args[1]; diff --git a/LaunchServer/source/command/auth/UUIDToUsernameCommand.java b/LaunchServer/source/command/auth/UUIDToUsernameCommand.java index d84e206..ba436a4 100644 --- a/LaunchServer/source/command/auth/UUIDToUsernameCommand.java +++ b/LaunchServer/source/command/auth/UUIDToUsernameCommand.java @@ -1,36 +1,42 @@ package launchserver.command.auth; -import java.io.IOException; -import java.util.UUID; - import launcher.helper.LogHelper; import launchserver.LaunchServer; import launchserver.command.Command; import launchserver.command.CommandException; -public final class UUIDToUsernameCommand extends Command { - public UUIDToUsernameCommand(LaunchServer server) { +import java.io.IOException; +import java.util.UUID; + +public final class UUIDToUsernameCommand extends Command +{ + public UUIDToUsernameCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return ""; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Convert player UUID to username"; } @Override - public void invoke(String... args) throws CommandException, IOException { + public void invoke(String... args) throws CommandException, IOException + { verifyArgs(args, 1); UUID uuid = parseUUID(args[0]); // Get UUID by username String username = server.config.authHandler.uuidToUsername(uuid); - if (username == null) { + if (username == null) + { throw new CommandException("Unknown UUID: " + uuid); } diff --git a/LaunchServer/source/command/auth/UsernameToUUIDCommand.java b/LaunchServer/source/command/auth/UsernameToUUIDCommand.java index 76f497d..93b99dc 100644 --- a/LaunchServer/source/command/auth/UsernameToUUIDCommand.java +++ b/LaunchServer/source/command/auth/UsernameToUUIDCommand.java @@ -1,36 +1,42 @@ package launchserver.command.auth; -import java.io.IOException; -import java.util.UUID; - import launcher.helper.LogHelper; import launchserver.LaunchServer; import launchserver.command.Command; import launchserver.command.CommandException; -public final class UsernameToUUIDCommand extends Command { - public UsernameToUUIDCommand(LaunchServer server) { +import java.io.IOException; +import java.util.UUID; + +public final class UsernameToUUIDCommand extends Command +{ + public UsernameToUUIDCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return ""; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Convert player username to UUID"; } @Override - public void invoke(String... args) throws CommandException, IOException { + public void invoke(String... args) throws CommandException, IOException + { verifyArgs(args, 1); String username = parseUsername(args[0]); // Get UUID by username UUID uuid = server.config.authHandler.usernameToUUID(username); - if (uuid == null) { + if (uuid == null) + { throw new CommandException(String.format("Unknown username: '%s'", username)); } diff --git a/LaunchServer/source/command/basic/BuildCommand.java b/LaunchServer/source/command/basic/BuildCommand.java index 366b591..35c1dc0 100644 --- a/LaunchServer/source/command/basic/BuildCommand.java +++ b/LaunchServer/source/command/basic/BuildCommand.java @@ -3,23 +3,28 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class BuildCommand extends Command { - public BuildCommand(LaunchServer server) { +public final class BuildCommand extends Command +{ + public BuildCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Build launcher binaries"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { server.buildLauncherBinaries(); server.syncLauncherBinaries(); } diff --git a/LaunchServer/source/command/basic/ClearCommand.java b/LaunchServer/source/command/basic/ClearCommand.java index 1ce0d6e..870e854 100644 --- a/LaunchServer/source/command/basic/ClearCommand.java +++ b/LaunchServer/source/command/basic/ClearCommand.java @@ -4,23 +4,28 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class ClearCommand extends Command { - public ClearCommand(LaunchServer server) { +public final class ClearCommand extends Command +{ + public ClearCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Clear terminal"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { server.commandHandler.clear(); LogHelper.subInfo("Terminal cleared"); } diff --git a/LaunchServer/source/command/basic/DebugCommand.java b/LaunchServer/source/command/basic/DebugCommand.java index 8e601d6..e047ea2 100644 --- a/LaunchServer/source/command/basic/DebugCommand.java +++ b/LaunchServer/source/command/basic/DebugCommand.java @@ -4,28 +4,36 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class DebugCommand extends Command { - public DebugCommand(LaunchServer server) { +public final class DebugCommand extends Command +{ + public DebugCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return "[true/false]"; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Enable or disable debug logging at runtime"; } @Override - public void invoke(String... args) { + public void invoke(String... args) + { boolean newValue; - if (args.length >= 1) { + if (args.length >= 1) + { newValue = Boolean.parseBoolean(args[0]); LogHelper.setDebugEnabled(newValue); - } else { + } + else + { newValue = LogHelper.isDebugEnabled(); } LogHelper.subInfo("Debug enabled: " + newValue); diff --git a/LaunchServer/source/command/basic/EvalCommand.java b/LaunchServer/source/command/basic/EvalCommand.java index 4284c4d..15f7d97 100644 --- a/LaunchServer/source/command/basic/EvalCommand.java +++ b/LaunchServer/source/command/basic/EvalCommand.java @@ -4,23 +4,28 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class EvalCommand extends Command { - public EvalCommand(LaunchServer server) { +public final class EvalCommand extends Command +{ + public EvalCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return ""; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Evaluate JavaScript code snippet"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { LogHelper.subInfo("Eval result: " + server.engine.eval(String.join(" ", args))); } } diff --git a/LaunchServer/source/command/basic/GCCommand.java b/LaunchServer/source/command/basic/GCCommand.java index 278baed..4133893 100644 --- a/LaunchServer/source/command/basic/GCCommand.java +++ b/LaunchServer/source/command/basic/GCCommand.java @@ -5,23 +5,28 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class GCCommand extends Command { - public GCCommand(LaunchServer server) { +public final class GCCommand extends Command +{ + public GCCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Perform Garbage Collection and print memory usage"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { LogHelper.subInfo("Performing full GC"); JVMHelper.fullGC(); diff --git a/LaunchServer/source/command/basic/HelpCommand.java b/LaunchServer/source/command/basic/HelpCommand.java index 14621b2..8c5c7ba 100644 --- a/LaunchServer/source/command/basic/HelpCommand.java +++ b/LaunchServer/source/command/basic/HelpCommand.java @@ -1,31 +1,42 @@ package launchserver.command.basic; -import java.util.Map; -import java.util.Map.Entry; - import launcher.helper.LogHelper; import launchserver.LaunchServer; import launchserver.command.Command; import launchserver.command.CommandException; -public final class HelpCommand extends Command { - public HelpCommand(LaunchServer server) { +import java.util.Map.Entry; + +public final class HelpCommand extends Command +{ + public HelpCommand(LaunchServer server) + { super(server); } + private static void printCommand(String name, Command command) + { + String args = command.getArgsDescription(); + LogHelper.subInfo("%s %s - %s", name, args == null ? "[nothing]" : args, command.getUsageDescription()); + } + @Override - public String getArgsDescription() { + public String getArgsDescription() + { return "[command name]"; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Print command usage"; } @Override - public void invoke(String... args) throws CommandException { - if (args.length < 1) { + public void invoke(String... args) throws CommandException + { + if (args.length < 1) + { printCommands(); return; } @@ -34,18 +45,16 @@ printCommand(args[0]); } - private void printCommand(String name) throws CommandException { + private void printCommand(String name) throws CommandException + { printCommand(name, server.commandHandler.lookup(name)); } - private void printCommands() { - for (Entry entry : server.commandHandler.commandsMap().entrySet()) { + private void printCommands() + { + for (Entry entry : server.commandHandler.commandsMap().entrySet()) + { printCommand(entry.getKey(), entry.getValue()); } } - - private static void printCommand(String name, Command command) { - String args = command.getArgsDescription(); - LogHelper.subInfo("%s %s - %s", name, args == null ? "[nothing]" : args, command.getUsageDescription()); - } } diff --git a/LaunchServer/source/command/basic/LogConnectionsCommand.java b/LaunchServer/source/command/basic/LogConnectionsCommand.java index 29ba612..93fc900 100644 --- a/LaunchServer/source/command/basic/LogConnectionsCommand.java +++ b/LaunchServer/source/command/basic/LogConnectionsCommand.java @@ -4,28 +4,36 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class LogConnectionsCommand extends Command { - public LogConnectionsCommand(LaunchServer server) { +public final class LogConnectionsCommand extends Command +{ + public LogConnectionsCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return "[true/false]"; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Enable or disable logging connections"; } @Override - public void invoke(String... args) { + public void invoke(String... args) + { boolean newValue; - if (args.length >= 1) { + if (args.length >= 1) + { newValue = Boolean.parseBoolean(args[0]); server.serverSocketHandler.logConnections = newValue; - } else { + } + else + { newValue = server.serverSocketHandler.logConnections; } LogHelper.subInfo("Log connections: " + newValue); diff --git a/LaunchServer/source/command/basic/RebindCommand.java b/LaunchServer/source/command/basic/RebindCommand.java index d298a1b..4b70c55 100644 --- a/LaunchServer/source/command/basic/RebindCommand.java +++ b/LaunchServer/source/command/basic/RebindCommand.java @@ -3,23 +3,28 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class RebindCommand extends Command { - public RebindCommand(LaunchServer server) { +public final class RebindCommand extends Command +{ + public RebindCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Rebind server socket"; } @Override - public void invoke(String... args) { + public void invoke(String... args) + { server.rebindServerSocket(); } } diff --git a/LaunchServer/source/command/basic/StopCommand.java b/LaunchServer/source/command/basic/StopCommand.java index 8a32cab..970aa37 100644 --- a/LaunchServer/source/command/basic/StopCommand.java +++ b/LaunchServer/source/command/basic/StopCommand.java @@ -4,24 +4,29 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class StopCommand extends Command { - public StopCommand(LaunchServer server) { +public final class StopCommand extends Command +{ + public StopCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Stop LaunchServer"; } @Override @SuppressWarnings("CallToSystemExit") - public void invoke(String... args) { + public void invoke(String... args) + { JVMHelper.RUNTIME.exit(0); } } diff --git a/LaunchServer/source/command/basic/VersionCommand.java b/LaunchServer/source/command/basic/VersionCommand.java index 4c6c4cf..25f82db 100644 --- a/LaunchServer/source/command/basic/VersionCommand.java +++ b/LaunchServer/source/command/basic/VersionCommand.java @@ -5,23 +5,28 @@ import launchserver.LaunchServer; import launchserver.command.Command; -public final class VersionCommand extends Command { - public VersionCommand(LaunchServer server) { +public final class VersionCommand extends Command +{ + public VersionCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Print LaunchServer version"; } @Override - public void invoke(String... args) { + public void invoke(String... args) + { LogHelper.subInfo("LaunchServer version: %s (build #%s)", Launcher.VERSION, Launcher.BUILD); } } diff --git a/LaunchServer/source/command/handler/CommandHandler.java b/LaunchServer/source/command/handler/CommandHandler.java index af85358..ee61325 100644 --- a/LaunchServer/source/command/handler/CommandHandler.java +++ b/LaunchServer/source/command/handler/CommandHandler.java @@ -1,42 +1,26 @@ package launchserver.command.handler; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - import launcher.LauncherAPI; import launcher.helper.LogHelper; import launcher.helper.VerifyHelper; import launchserver.LaunchServer; import launchserver.command.Command; import launchserver.command.CommandException; -import launchserver.command.auth.AuthCommand; -import launchserver.command.auth.CheckServerCommand; -import launchserver.command.auth.JoinServerCommand; -import launchserver.command.auth.UUIDToUsernameCommand; -import launchserver.command.auth.UsernameToUUIDCommand; -import launchserver.command.basic.BuildCommand; -import launchserver.command.basic.ClearCommand; -import launchserver.command.basic.DebugCommand; -import launchserver.command.basic.EvalCommand; -import launchserver.command.basic.GCCommand; -import launchserver.command.basic.HelpCommand; -import launchserver.command.basic.LogConnectionsCommand; -import launchserver.command.basic.RebindCommand; -import launchserver.command.basic.StopCommand; -import launchserver.command.basic.VersionCommand; +import launchserver.command.auth.*; +import launchserver.command.basic.*; import launchserver.command.hash.*; import launchserver.command.legacy.DumpBinaryAuthHandler; -public abstract class CommandHandler implements Runnable { +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class CommandHandler implements Runnable +{ private final Map commands = new ConcurrentHashMap<>(32); - protected CommandHandler(LaunchServer server) { + protected CommandHandler(LaunchServer server) + { // Register basic commands registerCommand("help", new HelpCommand(server)); registerCommand("version", new VersionCommand(server)); @@ -71,112 +55,30 @@ registerCommand("dumpBinaryAuthHandler", new DumpBinaryAuthHandler(server)); } - @Override - public final void run() { - try { - readLoop(); - } catch (IOException e) { - LogHelper.error(e); - } - } - - @LauncherAPI - public abstract void bell() throws IOException; - - @LauncherAPI - public abstract void clear() throws IOException; - - @LauncherAPI - public abstract String readLine() throws IOException; - - @LauncherAPI - public final Map commandsMap() { - return Collections.unmodifiableMap(commands); - } - - @LauncherAPI - public final void eval(String line, boolean bell) { - LogHelper.info("Command '%s'", line); - - // Parse line to tokens - String[] args; - try { - args = parse(line); - } catch (Throwable exc) { - LogHelper.error(exc); - return; - } - - // Evaluate command - eval(args, bell); - } - - @LauncherAPI - public final void eval(String[] args, boolean bell) { - if (args.length == 0) { - return; - } - - // Measure start time and invoke command - long start = System.currentTimeMillis(); - try { - lookup(args[0]).invoke(Arrays.copyOfRange(args, 1, args.length)); - } catch (Throwable exc) { - LogHelper.error(exc); - } - - // Bell if invocation took > 1s - long end = System.currentTimeMillis(); - if (bell && end - start >= 5_000L) { - try { - bell(); - } catch (IOException e) { - LogHelper.error(e); - } - } - } - - @LauncherAPI - public final Command lookup(String name) throws CommandException { - Command command = commands.get(name); - if (command == null) { - throw new CommandException(String.format("Unknown command: '%s'", name)); - } - return command; - } - - @LauncherAPI - public final void registerCommand(String name, Command command) { - VerifyHelper.verifyIDName(name); - VerifyHelper.putIfAbsent(commands, name, Objects.requireNonNull(command, "command"), - String.format("Command has been already registered: '%s'", name)); - } - - private void readLoop() throws IOException { - for (String line = readLine(); line != null; line = readLine()) { - eval(line, true); - } - } - - private static String[] parse(CharSequence line) throws CommandException { + private static String[] parse(CharSequence line) throws CommandException + { boolean quoted = false; boolean wasQuoted = false; // Read line char by char Collection result = new LinkedList<>(); StringBuilder builder = new StringBuilder(100); - for (int i = 0; i <= line.length(); i++) { + for (int i = 0; i <= line.length(); i++) + { boolean end = i >= line.length(); char ch = end ? '\0' : line.charAt(i); // Maybe we should read next argument? - if (end || !quoted && Character.isWhitespace(ch)) { - if (end && quoted) { // Quotes should be closed + if (end || !quoted && Character.isWhitespace(ch)) + { + if (end && quoted) + { // Quotes should be closed throw new CommandException("Quotes wasn't closed"); } // Empty args are ignored (except if was quoted) - if (wasQuoted || builder.length() > 0) { + if (wasQuoted || builder.length() > 0) + { result.add(builder.toString()); } @@ -187,13 +89,15 @@ } // Append next char - switch (ch) { + switch (ch) + { case '"': // "abc"de, "abc""de" also allowed quoted = !quoted; wasQuoted = true; break; case '\\': // All escapes, including spaces etc - if (i + 1 >= line.length()) { + if (i + 1 >= line.length()) + { throw new CommandException("Escape character is not specified"); } char next = line.charAt(i + 1); @@ -209,4 +113,114 @@ // Return result as array return result.toArray(new String[result.size()]); } + + @Override + public final void run() + { + try + { + readLoop(); + } + catch (IOException e) + { + LogHelper.error(e); + } + } + + @LauncherAPI + public abstract void bell() throws IOException; + + @LauncherAPI + public abstract void clear() throws IOException; + + @LauncherAPI + public abstract String readLine() throws IOException; + + @LauncherAPI + public final Map commandsMap() + { + return Collections.unmodifiableMap(commands); + } + + @LauncherAPI + public final void eval(String line, boolean bell) + { + LogHelper.info("Command '%s'", line); + + // Parse line to tokens + String[] args; + try + { + args = parse(line); + } + catch (Throwable exc) + { + LogHelper.error(exc); + return; + } + + // Evaluate command + eval(args, bell); + } + + @LauncherAPI + public final void eval(String[] args, boolean bell) + { + if (args.length == 0) + { + return; + } + + // Measure start time and invoke command + long start = System.currentTimeMillis(); + try + { + lookup(args[0]).invoke(Arrays.copyOfRange(args, 1, args.length)); + } + catch (Throwable exc) + { + LogHelper.error(exc); + } + + // Bell if invocation took > 1s + long end = System.currentTimeMillis(); + if (bell && end - start >= 5_000L) + { + try + { + bell(); + } + catch (IOException e) + { + LogHelper.error(e); + } + } + } + + @LauncherAPI + public final Command lookup(String name) throws CommandException + { + Command command = commands.get(name); + if (command == null) + { + throw new CommandException(String.format("Unknown command: '%s'", name)); + } + return command; + } + + @LauncherAPI + public final void registerCommand(String name, Command command) + { + VerifyHelper.verifyIDName(name); + VerifyHelper.putIfAbsent(commands, name, Objects.requireNonNull(command, "command"), + String.format("Command has been already registered: '%s'", name)); + } + + private void readLoop() throws IOException + { + for (String line = readLine(); line != null; line = readLine()) + { + eval(line, true); + } + } } diff --git a/LaunchServer/source/command/handler/JLineCommandHandler.java b/LaunchServer/source/command/handler/JLineCommandHandler.java index 0198797..9544f41 100644 --- a/LaunchServer/source/command/handler/JLineCommandHandler.java +++ b/LaunchServer/source/command/handler/JLineCommandHandler.java @@ -1,16 +1,18 @@ package launchserver.command.handler; -import java.io.IOException; - import jline.console.ConsoleReader; import launcher.helper.LogHelper; import launcher.helper.LogHelper.Output; import launchserver.LaunchServer; -public final class JLineCommandHandler extends CommandHandler { +import java.io.IOException; + +public final class JLineCommandHandler extends CommandHandler +{ private final ConsoleReader reader; - public JLineCommandHandler(LaunchServer server) throws IOException { + public JLineCommandHandler(LaunchServer server) throws IOException + { super(server); // Set reader @@ -23,28 +25,36 @@ } @Override - public void bell() throws IOException { + public void bell() throws IOException + { reader.beep(); } @Override - public void clear() throws IOException { + public void clear() throws IOException + { reader.clearScreen(); } @Override - public String readLine() throws IOException { + public String readLine() throws IOException + { return reader.readLine(); } - private final class JLineOutput implements Output { + private final class JLineOutput implements Output + { @Override - public void println(String message) { - try { + public void println(String message) + { + try + { reader.println(ConsoleReader.RESET_LINE + message); reader.drawLine(); reader.flush(); - } catch (IOException ignored) { + } + catch (IOException ignored) + { // Ignored } } diff --git a/LaunchServer/source/command/handler/StdCommandHandler.java b/LaunchServer/source/command/handler/StdCommandHandler.java index 488b9c7..d2b192b 100644 --- a/LaunchServer/source/command/handler/StdCommandHandler.java +++ b/LaunchServer/source/command/handler/StdCommandHandler.java @@ -1,31 +1,36 @@ package launchserver.command.handler; -import java.io.BufferedReader; -import java.io.IOException; - import launcher.helper.IOHelper; import launchserver.LaunchServer; -public final class StdCommandHandler extends CommandHandler { +import java.io.BufferedReader; +import java.io.IOException; + +public final class StdCommandHandler extends CommandHandler +{ private final BufferedReader reader; - public StdCommandHandler(LaunchServer server, boolean readCommands) { + public StdCommandHandler(LaunchServer server, boolean readCommands) + { super(server); reader = readCommands ? IOHelper.newReader(System.in) : null; } @Override - public void bell() { + public void bell() + { // Do nothing, unsupported } @Override - public void clear() { + public void clear() + { throw new UnsupportedOperationException("clear terminal"); } @Override - public String readLine() throws IOException { + public String readLine() throws IOException + { return reader == null ? null : reader.readLine(); } } diff --git a/LaunchServer/source/command/hash/DownloadAssetCommand.java b/LaunchServer/source/command/hash/DownloadAssetCommand.java index 1b3abe4..5396f94 100644 --- a/LaunchServer/source/command/hash/DownloadAssetCommand.java +++ b/LaunchServer/source/command/hash/DownloadAssetCommand.java @@ -1,5 +1,11 @@ package launchserver.command.hash; +import launcher.client.ClientProfile.Version; +import launcher.helper.IOHelper; +import launcher.helper.LogHelper; +import launchserver.LaunchServer; +import launchserver.command.Command; + import java.io.IOException; import java.net.URL; import java.nio.file.Files; @@ -8,31 +14,49 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import launcher.client.ClientProfile.Version; -import launcher.helper.IOHelper; -import launcher.helper.LogHelper; -import launchserver.LaunchServer; -import launchserver.command.Command; - -public final class DownloadAssetCommand extends Command { +public final class DownloadAssetCommand extends Command +{ private static String ASSET_URL_MASK; - public DownloadAssetCommand(LaunchServer server) { + public DownloadAssetCommand(LaunchServer server) + { super(server); } + public static void unpack(URL url, Path dir) throws IOException + { + try (ZipInputStream input = IOHelper.newZipInput(url)) + { + for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) + { + if (entry.isDirectory()) + { + continue; // Skip directories + } + + // Unpack entry + String name = entry.getName(); + LogHelper.subInfo("Downloading file: '%s'", name); + IOHelper.transfer(input, dir.resolve(IOHelper.toPath(name))); + } + } + } + @Override - public String getArgsDescription() { + public String getArgsDescription() + { return " "; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Download asset dir"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { verifyArgs(args, 2); Version version = Version.byName(args[0]); String dirName = IOHelper.verifyFileName(args[1]); @@ -51,19 +75,4 @@ server.syncUpdatesDir(Collections.singleton(dirName)); LogHelper.subInfo("Asset successfully downloaded: '%s'", dirName); } - - public static void unpack(URL url, Path dir) throws IOException { - try (ZipInputStream input = IOHelper.newZipInput(url)) { - for (ZipEntry entry = input.getNextEntry(); entry != null; entry = input.getNextEntry()) { - if (entry.isDirectory()) { - continue; // Skip directories - } - - // Unpack entry - String name = entry.getName(); - LogHelper.subInfo("Downloading file: '%s'", name); - IOHelper.transfer(input, dir.resolve(IOHelper.toPath(name))); - } - } - } } diff --git a/LaunchServer/source/command/hash/DownloadClientCommand.java b/LaunchServer/source/command/hash/DownloadClientCommand.java index 05bb030..d5b5d07 100644 --- a/LaunchServer/source/command/hash/DownloadClientCommand.java +++ b/LaunchServer/source/command/hash/DownloadClientCommand.java @@ -1,13 +1,5 @@ package launchserver.command.hash; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; - import launcher.client.ClientProfile; import launcher.client.ClientProfile.Version; import launcher.helper.IOHelper; @@ -17,27 +9,38 @@ import launcher.serialize.config.entry.StringConfigEntry; import launchserver.LaunchServer; import launchserver.command.Command; -import launchserver.command.CommandException; -public final class DownloadClientCommand extends Command { +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; + +public final class DownloadClientCommand extends Command +{ private static String CLIENT_URL_MASK; - public DownloadClientCommand(LaunchServer server) { + public DownloadClientCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return " "; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Download client dir"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { verifyArgs(args, 2); Version version = Version.byName(args[0]); String dirName = IOHelper.verifyFileName(args[1]); @@ -51,19 +54,21 @@ LogHelper.subInfo("Downloading client, it may take some time"); CLIENT_URL_MASK = server.config.mirror + "clients/%s.zip"; DownloadAssetCommand.unpack(new URL(String.format(CLIENT_URL_MASK, - IOHelper.urlEncode(version.name))), clientDir); + IOHelper.urlEncode(version.name))), clientDir); // Create profile file LogHelper.subInfo("Creaing profile file: '%s'", dirName); ClientProfile client; String profilePath = String.format("launchserver/defaults/profile%s.cfg", version.name); - try (BufferedReader reader = IOHelper.newReader(IOHelper.getResourceURL(profilePath))) { + try (BufferedReader reader = IOHelper.newReader(IOHelper.getResourceURL(profilePath))) + { client = new ClientProfile(TextConfigReader.read(reader, false)); } client.setTitle(dirName); client.block.getEntry("dir", StringConfigEntry.class).setValue(dirName); try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(server.profilesDir, - dirName, "cfg"))) { + dirName, "cfg"))) + { TextConfigWriter.write(client.block, writer, true); } diff --git a/LaunchServer/source/command/hash/IndexAssetCommand.java b/LaunchServer/source/command/hash/IndexAssetCommand.java index 40b7103..1d75999 100644 --- a/LaunchServer/source/command/hash/IndexAssetCommand.java +++ b/LaunchServer/source/command/hash/IndexAssetCommand.java @@ -1,14 +1,5 @@ package launchserver.command.hash; -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Collections; - import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.WriterConfig; @@ -21,34 +12,61 @@ import launchserver.command.Command; import launchserver.command.CommandException; -public final class IndexAssetCommand extends Command { +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; + +public final class IndexAssetCommand extends Command +{ public static final String INDEXES_DIR = "indexes"; public static final String OBJECTS_DIR = "objects"; private static final String JSON_EXTENSION = ".json"; - public IndexAssetCommand(LaunchServer server) { + public IndexAssetCommand(LaunchServer server) + { super(server); } + @LauncherAPI + public static Path resolveIndexFile(Path assetDir, String name) + { + return assetDir.resolve(INDEXES_DIR).resolve(name + JSON_EXTENSION); + } + + @LauncherAPI + public static Path resolveObjectFile(Path assetDir, String hash) + { + return assetDir.resolve(OBJECTS_DIR).resolve(hash.substring(0, 2)).resolve(hash); + } + @Override - public String getArgsDescription() { + public String getArgsDescription() + { return " "; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Index asset dir (1.7.10+)"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { verifyArgs(args, 3); String inputAssetDirName = IOHelper.verifyFileName(args[0]); String indexFileName = IOHelper.verifyFileName(args[1]); String outputAssetDirName = IOHelper.verifyFileName(args[2]); Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName); Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName); - if (outputAssetDir.equals(inputAssetDir)) { + if (outputAssetDir.equals(inputAssetDir)) + { throw new CommandException("Unindexed and indexed asset dirs can't be same"); } @@ -63,7 +81,8 @@ // Write index file LogHelper.subInfo("Writing asset index file: '%s'", indexFileName); - try (BufferedWriter writer = IOHelper.newWriter(resolveIndexFile(outputAssetDir, indexFileName))) { + try (BufferedWriter writer = IOHelper.newWriter(resolveIndexFile(outputAssetDir, indexFileName))) + { Json.object().add(OBJECTS_DIR, objects).writeTo(writer, WriterConfig.MINIMAL); } @@ -72,29 +91,22 @@ LogHelper.subInfo("Asset successfully indexed: '%s'", inputAssetDirName); } - @LauncherAPI - public static Path resolveIndexFile(Path assetDir, String name) { - return assetDir.resolve(INDEXES_DIR).resolve(name + JSON_EXTENSION); - } - - @LauncherAPI - public static Path resolveObjectFile(Path assetDir, String hash) { - return assetDir.resolve(OBJECTS_DIR).resolve(hash.substring(0, 2)).resolve(hash); - } - - private static final class IndexAssetVisitor extends SimpleFileVisitor { + private static final class IndexAssetVisitor extends SimpleFileVisitor + { private final JsonObject objects; private final Path inputAssetDir; private final Path outputAssetDir; - private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Path outputAssetDir) { + private IndexAssetVisitor(JsonObject objects, Path inputAssetDir, Path outputAssetDir) + { this.objects = objects; this.inputAssetDir = inputAssetDir; this.outputAssetDir = outputAssetDir; } @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException + { String name = IOHelper.toString(inputAssetDir.relativize(file)); LogHelper.subInfo("Indexing: '%s'", name); diff --git a/LaunchServer/source/command/hash/SyncAllCommand.java b/LaunchServer/source/command/hash/SyncAllCommand.java index 984fbde..895f6c7 100644 --- a/LaunchServer/source/command/hash/SyncAllCommand.java +++ b/LaunchServer/source/command/hash/SyncAllCommand.java @@ -8,23 +8,28 @@ // Source: https://github.com/GravitLauncher/Launcher/commit/3e6384cad9c4bdc2fdc1a614bdcafe9cbc1df4bb // By Will0376 -public class SyncAllCommand extends Command { - public SyncAllCommand(LaunchServer server) { +public class SyncAllCommand extends Command +{ + public SyncAllCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Resync profiles & updates dirs"; } @Override - public void invoke(String... args) throws IOException { + public void invoke(String... args) throws IOException + { server.syncProfilesDir(); LogHelper.subInfo("Profiles successfully resynced"); diff --git a/LaunchServer/source/command/hash/SyncBinariesCommand.java b/LaunchServer/source/command/hash/SyncBinariesCommand.java index 9879fe3..1852a0f 100644 --- a/LaunchServer/source/command/hash/SyncBinariesCommand.java +++ b/LaunchServer/source/command/hash/SyncBinariesCommand.java @@ -1,28 +1,33 @@ package launchserver.command.hash; -import java.io.IOException; - import launcher.helper.LogHelper; import launchserver.LaunchServer; import launchserver.command.Command; -public final class SyncBinariesCommand extends Command { - public SyncBinariesCommand(LaunchServer server) { +import java.io.IOException; + +public final class SyncBinariesCommand extends Command +{ + public SyncBinariesCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Resync launcher binaries"; } @Override - public void invoke(String... args) throws IOException { + public void invoke(String... args) throws IOException + { server.syncLauncherBinaries(); LogHelper.subInfo("Binaries successfully resynced"); } diff --git a/LaunchServer/source/command/hash/SyncProfilesCommand.java b/LaunchServer/source/command/hash/SyncProfilesCommand.java index 3de7477..b2e8bd5 100644 --- a/LaunchServer/source/command/hash/SyncProfilesCommand.java +++ b/LaunchServer/source/command/hash/SyncProfilesCommand.java @@ -1,28 +1,33 @@ package launchserver.command.hash; -import java.io.IOException; - import launcher.helper.LogHelper; import launchserver.LaunchServer; import launchserver.command.Command; -public final class SyncProfilesCommand extends Command { - public SyncProfilesCommand(LaunchServer server) { +import java.io.IOException; + +public final class SyncProfilesCommand extends Command +{ + public SyncProfilesCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Resync profiles dir"; } @Override - public void invoke(String... args) throws IOException { + public void invoke(String... args) throws IOException + { server.syncProfilesDir(); LogHelper.subInfo("Profiles successfully resynced"); } diff --git a/LaunchServer/source/command/hash/SyncUpdatesCommand.java b/LaunchServer/source/command/hash/SyncUpdatesCommand.java index da90456..d331a88 100644 --- a/LaunchServer/source/command/hash/SyncUpdatesCommand.java +++ b/LaunchServer/source/command/hash/SyncUpdatesCommand.java @@ -1,33 +1,39 @@ package launchserver.command.hash; +import launcher.helper.LogHelper; +import launchserver.LaunchServer; +import launchserver.command.Command; + import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.Set; -import launcher.helper.LogHelper; -import launchserver.LaunchServer; -import launchserver.command.Command; - -public final class SyncUpdatesCommand extends Command { - public SyncUpdatesCommand(LaunchServer server) { +public final class SyncUpdatesCommand extends Command +{ + public SyncUpdatesCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return "[subdirs...]"; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Resync updates dir"; } @Override - public void invoke(String... args) throws IOException { + public void invoke(String... args) throws IOException + { Set dirs = null; - if (args.length > 0) { // Hash all updates dirs + if (args.length > 0) + { // Hash all updates dirs dirs = new HashSet<>(args.length); Collections.addAll(dirs, args); } diff --git a/LaunchServer/source/command/hash/UnindexAssetCommand.java b/LaunchServer/source/command/hash/UnindexAssetCommand.java index d4e986a..c83b181 100644 --- a/LaunchServer/source/command/hash/UnindexAssetCommand.java +++ b/LaunchServer/source/command/hash/UnindexAssetCommand.java @@ -1,10 +1,5 @@ package launchserver.command.hash; -import java.io.BufferedReader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; - import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.JsonObject.Member; @@ -14,30 +9,41 @@ import launchserver.command.Command; import launchserver.command.CommandException; -public final class UnindexAssetCommand extends Command { - public UnindexAssetCommand(LaunchServer server) { +import java.io.BufferedReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; + +public final class UnindexAssetCommand extends Command +{ + public UnindexAssetCommand(LaunchServer server) + { super(server); } @Override - public String getArgsDescription() { + public String getArgsDescription() + { return " "; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Unindex asset dir (1.7.10+)"; } @Override - public void invoke(String... args) throws Throwable { + public void invoke(String... args) throws Throwable + { verifyArgs(args, 3); String inputAssetDirName = IOHelper.verifyFileName(args[0]); String indexFileName = IOHelper.verifyFileName(args[1]); String outputAssetDirName = IOHelper.verifyFileName(args[2]); Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName); Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName); - if (outputAssetDir.equals(inputAssetDir)) { + if (outputAssetDir.equals(inputAssetDir)) + { throw new CommandException("Indexed and unindexed asset dirs can't be same"); } @@ -48,13 +54,15 @@ // Read JSON file JsonObject objects; LogHelper.subInfo("Reading asset index file: '%s'", indexFileName); - try (BufferedReader reader = IOHelper.newReader(IndexAssetCommand.resolveIndexFile(inputAssetDir, indexFileName))) { + try (BufferedReader reader = IOHelper.newReader(IndexAssetCommand.resolveIndexFile(inputAssetDir, indexFileName))) + { objects = Json.parse(reader).asObject().get(IndexAssetCommand.OBJECTS_DIR).asObject(); } // Restore objects LogHelper.subInfo("Unindexing %d objects", objects.size()); - for (Member member : objects) { + for (Member member : objects) + { String name = member.getName(); LogHelper.subInfo("Unindexing: '%s'", name); diff --git a/LaunchServer/source/command/legacy/DumpBinaryAuthHandler.java b/LaunchServer/source/command/legacy/DumpBinaryAuthHandler.java index e7396dc..2752f40 100644 --- a/LaunchServer/source/command/legacy/DumpBinaryAuthHandler.java +++ b/LaunchServer/source/command/legacy/DumpBinaryAuthHandler.java @@ -1,13 +1,5 @@ package launchserver.command.legacy; -import java.io.BufferedWriter; -import java.io.IOException; -import java.nio.file.Paths; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - import launcher.helper.IOHelper; import launcher.helper.LogHelper; import launcher.serialize.config.TextConfigWriter; @@ -19,23 +11,44 @@ import launchserver.auth.handler.FileAuthHandler.Entry; import launchserver.command.Command; -public final class DumpBinaryAuthHandler extends Command { - public DumpBinaryAuthHandler(LaunchServer server) { +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +public final class DumpBinaryAuthHandler extends Command +{ + public DumpBinaryAuthHandler(LaunchServer server) + { super(server); } + private static StringConfigEntry cc(String value) + { + StringConfigEntry entry = new StringConfigEntry(value, true, 4); + entry.setComment(0, "\n\t"); // Pre-name + entry.setComment(2, " "); // Pre-value + return entry; + } + @Override - public String getArgsDescription() { + public String getArgsDescription() + { return null; } @Override - public String getUsageDescription() { + public String getUsageDescription() + { return "Dumps BinaryAuthHandler to text file"; } @Override - public void invoke(String... args) { + public void invoke(String... args) + { LogHelper.subInfo("Dumping BinaryAuthHandler file..."); BinaryFileAuthHandler handler = (BinaryFileAuthHandler) server.config.authHandler; boolean next = false; @@ -43,7 +56,8 @@ // Write auth blocks to map Set> entrySet = handler.entrySet(); Map> map = new LinkedHashMap<>(entrySet.size()); - for (Map.Entry entry : entrySet) { + for (Map.Entry entry : entrySet) + { UUID uuid = entry.getKey(); Entry auth = entry.getValue(); @@ -51,19 +65,24 @@ Map> authMap = new LinkedHashMap<>(entrySet.size()); authMap.put("username", cc(auth.getUsername())); String accessToken = auth.getAccessToken(); - if (accessToken != null) { + if (accessToken != null) + { authMap.put("accessToken", cc(accessToken)); } String serverID = auth.getServerID(); - if (serverID != null) { + if (serverID != null) + { authMap.put("serverID", cc(serverID)); } // Create and add auth block BlockConfigEntry authBlock = new BlockConfigEntry(authMap, true, 5); - if (next) { + if (next) + { authBlock.setComment(0, "\n"); // Pre-name - } else { + } + else + { next = true; } authBlock.setComment(2, " "); // Pre-value @@ -72,19 +91,15 @@ } // Write auth handler file - try (BufferedWriter writer = IOHelper.newWriter(Paths.get("authHandler.dump.cfg"))) { + try (BufferedWriter writer = IOHelper.newWriter(Paths.get("authHandler.dump.cfg"))) + { BlockConfigEntry authFile = new BlockConfigEntry(map, true, 1); authFile.setComment(0, "\n"); TextConfigWriter.write(authFile, writer, true); - } catch (IOException e) { + } + catch (IOException e) + { e.printStackTrace(); } } - - private static StringConfigEntry cc(String value) { - StringConfigEntry entry = new StringConfigEntry(value, true, 4); - entry.setComment(0, "\n\t"); // Pre-name - entry.setComment(2, " "); // Pre-value - return entry; - } } diff --git a/LaunchServer/source/plugin/LaunchServerPluginBridge.java b/LaunchServer/source/plugin/LaunchServerPluginBridge.java index d10dabe..ccc7a0f 100644 --- a/LaunchServer/source/plugin/LaunchServerPluginBridge.java +++ b/LaunchServer/source/plugin/LaunchServerPluginBridge.java @@ -1,23 +1,34 @@ package launchserver.plugin; -import java.nio.file.Path; - import launcher.helper.JVMHelper; import launcher.helper.LogHelper; import launchserver.LaunchServer; -public final class LaunchServerPluginBridge implements Runnable, AutoCloseable { +import java.nio.file.Path; + +public final class LaunchServerPluginBridge implements Runnable, AutoCloseable +{ + static + { + //SecurityHelper.verifyCertificates(LaunchServer.class); + JVMHelper.verifySystemProperties(LaunchServer.class, false); + } + private final LaunchServer server; - public LaunchServerPluginBridge(Path dir) throws Throwable { + public LaunchServerPluginBridge(Path dir) throws Throwable + { LogHelper.addOutput(dir.resolve("LaunchServer.log")); LogHelper.printVersion("LaunchServer"); // Create new LaunchServer long start = System.currentTimeMillis(); - try { + try + { server = new LaunchServer(dir, true); - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.error(exc); throw exc; } @@ -26,21 +37,19 @@ } @Override - public void close() { + public void close() + { server.close(); } @Override - public void run() { + public void run() + { server.run(); } - public void eval(String... command) { + public void eval(String... command) + { server.commandHandler.eval(command, false); } - - static { - //SecurityHelper.verifyCertificates(LaunchServer.class); - JVMHelper.verifySystemProperties(LaunchServer.class, false); - } } diff --git a/LaunchServer/source/plugin/bukkit/LaunchServerCommandBukkit.java b/LaunchServer/source/plugin/bukkit/LaunchServerCommandBukkit.java index 2a465c3..360ced1 100644 --- a/LaunchServer/source/plugin/bukkit/LaunchServerCommandBukkit.java +++ b/LaunchServer/source/plugin/bukkit/LaunchServerCommandBukkit.java @@ -2,31 +2,34 @@ import launchserver.plugin.LaunchServerPluginBridge; import org.bukkit.ChatColor; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.ConsoleCommandSender; -import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.command.*; -public final class LaunchServerCommandBukkit implements CommandExecutor { +public final class LaunchServerCommandBukkit implements CommandExecutor +{ public final LaunchServerPluginBukkit plugin; - public LaunchServerCommandBukkit(LaunchServerPluginBukkit plugin) { + public LaunchServerCommandBukkit(LaunchServerPluginBukkit plugin) + { this.plugin = plugin; } @Override - public boolean onCommand(CommandSender sender, Command command, String alias, String... args) { - if (!(sender instanceof ConsoleCommandSender) && !(sender instanceof RemoteConsoleCommandSender)) { + public boolean onCommand(CommandSender sender, Command command, String alias, String... args) + { + if (!(sender instanceof ConsoleCommandSender) && !(sender instanceof RemoteConsoleCommandSender)) + { sender.sendMessage(ChatColor.RED + "Эту команду можно использовать только из консоли"); return true; } // Eval command LaunchServerPluginBridge bridge = plugin.bridge; - if (bridge == null) { + if (bridge == null) + { sender.sendMessage(ChatColor.RED + "Лаунчсервер не был полностью загружен"); - } else { + } + else + { bridge.eval(args); } return true; diff --git a/LaunchServer/source/plugin/bukkit/LaunchServerPluginBukkit.java b/LaunchServer/source/plugin/bukkit/LaunchServerPluginBukkit.java index fd5900e..c8b1c3c 100644 --- a/LaunchServer/source/plugin/bukkit/LaunchServerPluginBukkit.java +++ b/LaunchServer/source/plugin/bukkit/LaunchServerPluginBukkit.java @@ -4,26 +4,33 @@ import launchserver.plugin.LaunchServerPluginBridge; import org.bukkit.plugin.java.JavaPlugin; -public final class LaunchServerPluginBukkit extends JavaPlugin { +public final class LaunchServerPluginBukkit extends JavaPlugin +{ public volatile LaunchServerPluginBridge bridge = null; @Override - public void onDisable() { + public void onDisable() + { super.onDisable(); - if (bridge != null) { + if (bridge != null) + { bridge.close(); bridge = null; } } @Override - public void onEnable() { + public void onEnable() + { super.onEnable(); // Initialize LaunchServer - try { + try + { bridge = new LaunchServerPluginBridge(getDataFolder().toPath()); - } catch (Throwable exc) { + } + catch (Throwable exc) + { exc.printStackTrace(); } diff --git a/LaunchServer/source/plugin/bungee/LaunchServerCommandBungee.java b/LaunchServer/source/plugin/bungee/LaunchServerCommandBungee.java index 24d12ed..ca2fa87 100644 --- a/LaunchServer/source/plugin/bungee/LaunchServerCommandBungee.java +++ b/LaunchServer/source/plugin/bungee/LaunchServerCommandBungee.java @@ -8,30 +8,37 @@ import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.command.ConsoleCommandSender; -public final class LaunchServerCommandBungee extends Command { +public final class LaunchServerCommandBungee extends Command +{ private static final BaseComponent[] NOT_CONSOLE_MESSAGE = TextComponent.fromLegacyText(ChatColor.RED + "Эту команду можно использовать только из консоли"); private static final BaseComponent[] NOT_INITIALIZED_MESSAGE = TextComponent.fromLegacyText(ChatColor.RED + "Лаунчсервер не был полностью загружен"); // Instance public final LaunchServerPluginBungee plugin; - public LaunchServerCommandBungee(LaunchServerPluginBungee plugin) { + public LaunchServerCommandBungee(LaunchServerPluginBungee plugin) + { super("launchserver", null, "launcher", "ls", "l"); this.plugin = plugin; } @Override - public void execute(CommandSender sender, String... args) { - if (!(sender instanceof ConsoleCommandSender)) { + public void execute(CommandSender sender, String... args) + { + if (!(sender instanceof ConsoleCommandSender)) + { sender.sendMessage(NOT_CONSOLE_MESSAGE); return; } // Eval command LaunchServerPluginBridge bridge = plugin.bridge; - if (bridge == null) { + if (bridge == null) + { sender.sendMessage(NOT_INITIALIZED_MESSAGE); - } else { + } + else + { bridge.eval(args); } } diff --git a/LaunchServer/source/plugin/bungee/LaunchServerPluginBungee.java b/LaunchServer/source/plugin/bungee/LaunchServerPluginBungee.java index 3d777fb..407590a 100644 --- a/LaunchServer/source/plugin/bungee/LaunchServerPluginBungee.java +++ b/LaunchServer/source/plugin/bungee/LaunchServerPluginBungee.java @@ -4,26 +4,33 @@ import launchserver.plugin.LaunchServerPluginBridge; import net.md_5.bungee.api.plugin.Plugin; -public final class LaunchServerPluginBungee extends Plugin { +public final class LaunchServerPluginBungee extends Plugin +{ public volatile LaunchServerPluginBridge bridge = null; @Override - public void onDisable() { + public void onDisable() + { super.onDisable(); - if (bridge != null) { + if (bridge != null) + { bridge.close(); bridge = null; } } @Override - public void onEnable() { + public void onEnable() + { super.onEnable(); // Initialize LaunchServer - try { + try + { bridge = new LaunchServerPluginBridge(getDataFolder().toPath()); - } catch (Throwable exc) { + } + catch (Throwable exc) + { exc.printStackTrace(); } diff --git a/LaunchServer/source/response/PingResponse.java b/LaunchServer/source/response/PingResponse.java index 9b94ce2..421c23e 100644 --- a/LaunchServer/source/response/PingResponse.java +++ b/LaunchServer/source/response/PingResponse.java @@ -1,19 +1,22 @@ package launchserver.response; -import java.io.IOException; - import launcher.request.PingRequest; import launcher.serialize.HInput; import launcher.serialize.HOutput; import launchserver.LaunchServer; -public final class PingResponse extends Response { - public PingResponse(LaunchServer server, long id, HInput input, HOutput output) { +import java.io.IOException; + +public final class PingResponse extends Response +{ + public PingResponse(LaunchServer server, long id, HInput input, HOutput output) + { super(server, id, input, output); } @Override - public void reply() throws IOException { + public void reply() throws IOException + { output.writeUnsignedByte(PingRequest.EXPECTED_BYTE); } } diff --git a/LaunchServer/source/response/Response.java b/LaunchServer/source/response/Response.java index fb83ada..c2e1a4d 100644 --- a/LaunchServer/source/response/Response.java +++ b/LaunchServer/source/response/Response.java @@ -1,7 +1,5 @@ package launchserver.response; -import java.io.IOException; - import launcher.LauncherAPI; import launcher.helper.LogHelper; import launcher.request.RequestException; @@ -9,14 +7,22 @@ import launcher.serialize.HOutput; import launchserver.LaunchServer; -public abstract class Response { - @LauncherAPI protected final LaunchServer server; - @LauncherAPI protected final long id; - @LauncherAPI protected final HInput input; - @LauncherAPI protected final HOutput output; +import java.io.IOException; + +public abstract class Response +{ + @LauncherAPI + protected final LaunchServer server; + @LauncherAPI + protected final long id; + @LauncherAPI + protected final HInput input; + @LauncherAPI + protected final HOutput output; @LauncherAPI - protected Response(LaunchServer server, long id, HInput input, HOutput output) { + protected Response(LaunchServer server, long id, HInput input, HOutput output) + { this.server = server; this.id = id; this.input = input; @@ -24,31 +30,36 @@ } @LauncherAPI + public static void requestError(String message) throws RequestException + { + throw new RequestException(message); + } + + @LauncherAPI public abstract void reply() throws Throwable; @LauncherAPI - protected final void debug(String message) { + protected final void debug(String message) + { LogHelper.subDebug("#%d %s", id, message); } @LauncherAPI - protected final void debug(String message, Object... args) { + protected final void debug(String message, Object... args) + { debug(String.format(message, args)); } @LauncherAPI @SuppressWarnings("MethodMayBeStatic") // Intentionally not static - protected final void writeNoError(HOutput output) throws IOException { + protected final void writeNoError(HOutput output) throws IOException + { output.writeString("", 0); } - @LauncherAPI - public static void requestError(String message) throws RequestException { - throw new RequestException(message); - } - @FunctionalInterface - public interface Factory { + public interface Factory + { @LauncherAPI Response newResponse(LaunchServer server, long id, HInput input, HOutput output); } diff --git a/LaunchServer/source/response/ResponseThread.java b/LaunchServer/source/response/ResponseThread.java index 645c71f..3751729 100644 --- a/LaunchServer/source/response/ResponseThread.java +++ b/LaunchServer/source/response/ResponseThread.java @@ -1,10 +1,5 @@ package launchserver.response; -import java.io.IOException; -import java.math.BigInteger; -import java.net.Socket; -import java.net.SocketException; - import launcher.Launcher; import launcher.helper.IOHelper; import launcher.helper.LogHelper; @@ -25,12 +20,19 @@ import launchserver.response.update.UpdateListResponse; import launchserver.response.update.UpdateResponse; -public final class ResponseThread implements Runnable { +import java.io.IOException; +import java.math.BigInteger; +import java.net.Socket; +import java.net.SocketException; + +public final class ResponseThread implements Runnable +{ private final LaunchServer server; private final long id; private final Socket socket; - public ResponseThread(LaunchServer server, long id, Socket socket) throws SocketException { + public ResponseThread(LaunchServer server, long id, Socket socket) throws SocketException + { this.server = server; this.id = id; this.socket = socket; @@ -40,8 +42,10 @@ } @Override - public void run() { - if (!server.serverSocketHandler.logConnections) { + public void run() + { + if (!server.serverSocketHandler.logConnections) + { LogHelper.debug("Connection #%d from %s", id, IOHelper.getIP(socket.getRemoteSocketAddress())); } @@ -49,38 +53,51 @@ boolean cancelled = false; Throwable savedError = null; try (HInput input = new HInput(socket.getInputStream()); - HOutput output = new HOutput(socket.getOutputStream())) { + HOutput output = new HOutput(socket.getOutputStream())) + { Type type = readHandshake(input, output); - if (type == null) { // Not accepted + if (type == null) + { // Not accepted cancelled = true; return; } // Start response - try { + try + { respond(type, input, output); - } catch (RequestException e) { + } + catch (RequestException e) + { LogHelper.subDebug(String.format("#%d Request error: %s", id, e.getMessage())); output.writeString(e.getMessage(), 0); } - } catch (Throwable exc) { + } + catch (Throwable exc) + { savedError = exc; LogHelper.error(exc); - } finally { + } + finally + { IOHelper.close(socket); - if (!cancelled) { + if (!cancelled) + { server.serverSocketHandler.onDisconnect(id, savedError); } } } - private Type readHandshake(HInput input, HOutput output) throws IOException { + private Type readHandshake(HInput input, HOutput output) throws IOException + { boolean legacy = false; // Verify magic number int magicNumber = input.readInt(); - if (magicNumber != Launcher.PROTOCOL_MAGIC) { - if (magicNumber != Launcher.PROTOCOL_MAGIC - 1) { // Previous launcher protocol + if (magicNumber != Launcher.PROTOCOL_MAGIC) + { + if (magicNumber != Launcher.PROTOCOL_MAGIC - 1) + { // Previous launcher protocol output.writeBoolean(false); throw new IOException(String.format("#%d Protocol magic mismatch", id)); } @@ -89,18 +106,21 @@ // Verify key modulus BigInteger keyModulus = input.readBigInteger(SecurityHelper.RSA_KEY_LENGTH + 1); - if (!keyModulus.equals(server.privateKey.getModulus())) { + if (!keyModulus.equals(server.privateKey.getModulus())) + { output.writeBoolean(false); throw new IOException(String.format("#%d Key modulus mismatch", id)); } // Read request type Type type = Type.read(input); - if (legacy && type != Type.LAUNCHER) { + if (legacy && type != Type.LAUNCHER) + { output.writeBoolean(false); throw new IOException(String.format("#%d Not LAUNCHER request on legacy protocol", id)); } - if (!server.serverSocketHandler.onHandshake(id, type)) { + if (!server.serverSocketHandler.onHandshake(id, type)) + { output.writeBoolean(false); return null; } @@ -111,16 +131,21 @@ return type; } - private void respond(Type type, HInput input, HOutput output) throws Throwable { - if (server.serverSocketHandler.logConnections) { + private void respond(Type type, HInput input, HOutput output) throws Throwable + { + if (server.serverSocketHandler.logConnections) + { LogHelper.info("Connection #%d from %s: %s", id, IOHelper.getIP(socket.getRemoteSocketAddress()), type.name()); - } else { + } + else + { LogHelper.subDebug("#%d Type: %s", id, type.name()); } // Choose response based on type Response response; - switch (type) { + switch (type) + { case PING: response = new PingResponse(server, id, input, output); break; diff --git a/LaunchServer/source/response/ServerSocketHandler.java b/LaunchServer/source/response/ServerSocketHandler.java index 4858a4f..0b3cd79 100644 --- a/LaunchServer/source/response/ServerSocketHandler.java +++ b/LaunchServer/source/response/ServerSocketHandler.java @@ -1,5 +1,15 @@ package launchserver.response; +import launcher.LauncherAPI; +import launcher.helper.CommonHelper; +import launcher.helper.LogHelper; +import launcher.helper.VerifyHelper; +import launcher.request.Request.Type; +import launcher.serialize.HInput; +import launcher.serialize.HOutput; +import launchserver.LaunchServer; +import launchserver.response.Response.Factory; + import java.io.IOException; import java.net.InetAddress; import java.net.ServerSocket; @@ -13,52 +23,51 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; -import launcher.LauncherAPI; -import launcher.helper.CommonHelper; -import launcher.helper.LogHelper; -import launcher.helper.VerifyHelper; -import launcher.request.Request.Type; -import launcher.serialize.HInput; -import launcher.serialize.HOutput; -import launchserver.LaunchServer; -import launchserver.response.Response.Factory; - -public final class ServerSocketHandler implements Runnable, AutoCloseable { +public final class ServerSocketHandler implements Runnable, AutoCloseable +{ private static final ThreadFactory THREAD_FACTORY = r -> CommonHelper.newThread("Network Thread", true, r); - @LauncherAPI public volatile boolean logConnections = Boolean.getBoolean("launcher.logConnections"); - // Instance private final LaunchServer server; private final AtomicReference serverSocket = new AtomicReference<>(); private final ExecutorService threadPool = Executors.newCachedThreadPool(THREAD_FACTORY); - // API private final Map customResponses = new ConcurrentHashMap<>(2); private final AtomicLong idCounter = new AtomicLong(0L); + @LauncherAPI + public volatile boolean logConnections = Boolean.getBoolean("launcher.logConnections"); private volatile Listener listener; - public ServerSocketHandler(LaunchServer server) { + public ServerSocketHandler(LaunchServer server) + { this.server = server; } @Override - public void close() { + public void close() + { ServerSocket socket = serverSocket.getAndSet(null); - if (socket != null) { + if (socket != null) + { LogHelper.info("Closing server socket listener"); - try { + try + { socket.close(); - } catch (IOException e) { + } + catch (IOException e) + { LogHelper.error(e); } } } @Override - public void run() { + public void run() + { LogHelper.info("Starting server socket thread"); - try (ServerSocket serverSocket = new ServerSocket()) { - if (!this.serverSocket.compareAndSet(null, serverSocket)) { + try (ServerSocket serverSocket = new ServerSocket()) + { + if (!this.serverSocket.compareAndSet(null, serverSocket)) + { throw new IllegalStateException("Previous socket wasn't closed"); } @@ -70,56 +79,68 @@ LogHelper.info("Server socket thread successfully started"); // Listen for incoming connections - while (serverSocket.isBound()) { + while (serverSocket.isBound()) + { Socket socket = serverSocket.accept(); // Invoke pre-connect listener long id = idCounter.incrementAndGet(); - if (listener != null && !listener.onConnect(id, socket.getInetAddress())) { + if (listener != null && !listener.onConnect(id, socket.getInetAddress())) + { continue; // Listener didn't accepted this connection } // Reply in separate thread threadPool.execute(new ResponseThread(server, id, socket)); } - } catch (IOException e) { + } + catch (IOException e) + { // Ignore error after close/rebind - if (serverSocket.get() != null) { + if (serverSocket.get() != null) + { LogHelper.error(e); } } } @LauncherAPI - public Response newCustomResponse(String name, long id, HInput input, HOutput output) { + public Response newCustomResponse(String name, long id, HInput input, HOutput output) + { Factory factory = VerifyHelper.getMapValue(customResponses, name, - String.format("Unknown custom response: '%s'", name)); + String.format("Unknown custom response: '%s'", name)); return factory.newResponse(server, id, input, output); } @LauncherAPI - public void registerCustomResponse(String name, Factory factory) { + public void registerCustomResponse(String name, Factory factory) + { VerifyHelper.verifyIDName(name); VerifyHelper.putIfAbsent(customResponses, name, Objects.requireNonNull(factory, "factory"), - String.format("Custom response has been already registered: '%s'", name)); + String.format("Custom response has been already registered: '%s'", name)); } @LauncherAPI - public void setListener(Listener listener) { + public void setListener(Listener listener) + { this.listener = listener; } - /*package*/ void onDisconnect(long id, Throwable exc) { - if (listener != null) { + /*package*/ void onDisconnect(long id, Throwable exc) + { + if (listener != null) + { listener.onDisconnect(id, exc); } } - /*package*/ boolean onHandshake(long id, Type type) { + /*package*/ boolean onHandshake(long id, Type type) + { return listener == null || listener.onHandshake(id, type); } - public interface Listener { + public interface Listener + { @LauncherAPI boolean onConnect(long id, InetAddress address); diff --git a/LaunchServer/source/response/auth/AuthResponse.java b/LaunchServer/source/response/auth/AuthResponse.java index dd2098b..956270a 100644 --- a/LaunchServer/source/response/auth/AuthResponse.java +++ b/LaunchServer/source/response/auth/AuthResponse.java @@ -1,10 +1,5 @@ package launchserver.response.auth; -import java.util.Arrays; -import java.util.UUID; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; - import launcher.helper.IOHelper; import launcher.helper.LogHelper; import launcher.helper.SecurityHelper; @@ -18,6 +13,11 @@ import launchserver.response.Response; import launchserver.response.profile.ProfileByUUIDResponse; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import java.util.Arrays; +import java.util.UUID; + public final class AuthResponse extends Response { private final String ip; @@ -28,6 +28,13 @@ this.ip = ip; } + private static String echo(int length) + { + char[] chars = new char[length]; + Arrays.fill(chars, '*'); + return new String(chars); + } + @Override public void reply() throws Throwable { @@ -36,10 +43,13 @@ // Decrypt password String password; - try { + try + { password = IOHelper.decode(SecurityHelper.newRSADecryptCipher(server.privateKey). - doFinal(encryptedPassword)); - } catch (IllegalBlockSizeException | BadPaddingException ignored) { + doFinal(encryptedPassword)); + } + catch (IllegalBlockSizeException | BadPaddingException ignored) + { requestError("Password decryption error"); return; } @@ -47,20 +57,27 @@ // Authenticate debug("Login: '%s', Password: '%s'", login, echo(password.length())); AuthProviderResult result; - try { - if (server.limiter.isLimit(ip)) { + try + { + if (server.limiter.isLimit(ip)) + { AuthProvider.authError(server.config.authRejectString); return; } result = server.config.authProvider.auth(login, password, ip); - if (!VerifyHelper.isValidUsername(result.username)) { + if (!VerifyHelper.isValidUsername(result.username)) + { AuthProvider.authError(String.format("Illegal result: '%s'", result.username)); return; } - } catch (AuthException e) { + } + catch (AuthException e) + { requestError(e.getMessage()); return; - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.error(exc); requestError("Internal auth provider error"); return; @@ -69,12 +86,17 @@ // Authenticate on server (and get UUID) UUID uuid; - try { + try + { uuid = server.config.authHandler.auth(result); - } catch (AuthException e) { + } + catch (AuthException e) + { requestError(e.getMessage()); return; - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.error(exc); requestError("Internal auth handler error"); return; @@ -85,10 +107,4 @@ ProfileByUUIDResponse.getProfile(server, uuid, result.username).write(output); output.writeASCII(result.accessToken, -SecurityHelper.TOKEN_STRING_LENGTH); } - - private static String echo(int length) { - char[] chars = new char[length]; - Arrays.fill(chars, '*'); - return new String(chars); - } } diff --git a/LaunchServer/source/response/auth/CheckServerResponse.java b/LaunchServer/source/response/auth/CheckServerResponse.java index 3681632..1074285 100644 --- a/LaunchServer/source/response/auth/CheckServerResponse.java +++ b/LaunchServer/source/response/auth/CheckServerResponse.java @@ -1,8 +1,5 @@ package launchserver.response.auth; -import java.io.IOException; -import java.util.UUID; - import launcher.helper.LogHelper; import launcher.helper.VerifyHelper; import launcher.request.auth.JoinServerRequest; @@ -13,25 +10,36 @@ import launchserver.response.Response; import launchserver.response.profile.ProfileByUUIDResponse; -public final class CheckServerResponse extends Response { - public CheckServerResponse(LaunchServer server, long id, HInput input, HOutput output) { +import java.io.IOException; +import java.util.UUID; + +public final class CheckServerResponse extends Response +{ + public CheckServerResponse(LaunchServer server, long id, HInput input, HOutput output) + { super(server, id, input, output); } @Override - public void reply() throws IOException { + public void reply() throws IOException + { String username = VerifyHelper.verifyUsername(input.readString(64)); String serverID = JoinServerRequest.verifyServerID(input.readASCII(41)); // With minus sign debug("Username: %s, Server ID: %s", username, serverID); // Try check server with auth handler UUID uuid; - try { + try + { uuid = server.config.authHandler.checkServer(username, serverID); - } catch (AuthException e) { + } + catch (AuthException e) + { requestError(e.getMessage()); return; - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.error(exc); requestError("Internal auth handler error"); return; @@ -40,7 +48,8 @@ // Write profile and UUID output.writeBoolean(uuid != null); - if (uuid != null) { + if (uuid != null) + { ProfileByUUIDResponse.getProfile(server, uuid, username).write(output); } } diff --git a/LaunchServer/source/response/auth/JoinServerResponse.java b/LaunchServer/source/response/auth/JoinServerResponse.java index fec21e6..c334f9b 100644 --- a/LaunchServer/source/response/auth/JoinServerResponse.java +++ b/LaunchServer/source/response/auth/JoinServerResponse.java @@ -1,7 +1,5 @@ package launchserver.response.auth; -import java.io.IOException; - import launcher.helper.LogHelper; import launcher.helper.SecurityHelper; import launcher.helper.VerifyHelper; @@ -12,13 +10,18 @@ import launchserver.auth.AuthException; import launchserver.response.Response; -public final class JoinServerResponse extends Response { - public JoinServerResponse(LaunchServer server, long id, HInput input, HOutput output) { +import java.io.IOException; + +public final class JoinServerResponse extends Response +{ + public JoinServerResponse(LaunchServer server, long id, HInput input, HOutput output) + { super(server, id, input, output); } @Override - public void reply() throws IOException { + public void reply() throws IOException + { String username = VerifyHelper.verifyUsername(input.readString(64)); String accessToken = SecurityHelper.verifyToken(input.readASCII(-SecurityHelper.TOKEN_STRING_LENGTH)); String serverID = JoinServerRequest.verifyServerID(input.readASCII(41)); // With minus sign @@ -26,12 +29,17 @@ // Try join server with auth handler debug("Username: '%s', Access token: %s, Server ID: %s", username, accessToken, serverID); boolean success; - try { + try + { success = server.config.authHandler.joinServer(username, accessToken, serverID); - } catch (AuthException e) { + } + catch (AuthException e) + { requestError(e.getMessage()); return; - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.error(exc); requestError("Internal auth handler error"); return; diff --git a/LaunchServer/source/response/profile/BatchProfileByUsernameResponse.java b/LaunchServer/source/response/profile/BatchProfileByUsernameResponse.java index 6ad7c57..903dc08 100644 --- a/LaunchServer/source/response/profile/BatchProfileByUsernameResponse.java +++ b/LaunchServer/source/response/profile/BatchProfileByUsernameResponse.java @@ -1,8 +1,5 @@ package launchserver.response.profile; -import java.io.IOException; -import java.util.Arrays; - import launcher.helper.VerifyHelper; import launcher.request.uuid.BatchProfileByUsernameRequest; import launcher.serialize.HInput; @@ -10,21 +7,29 @@ import launchserver.LaunchServer; import launchserver.response.Response; -public final class BatchProfileByUsernameResponse extends Response { - public BatchProfileByUsernameResponse(LaunchServer server, long id, HInput input, HOutput output) { +import java.io.IOException; +import java.util.Arrays; + +public final class BatchProfileByUsernameResponse extends Response +{ + public BatchProfileByUsernameResponse(LaunchServer server, long id, HInput input, HOutput output) + { super(server, id, input, output); } @Override - public void reply() throws IOException { + public void reply() throws IOException + { String[] usernames = new String[input.readLength(BatchProfileByUsernameRequest.MAX_BATCH_SIZE)]; - for (int i = 0; i < usernames.length; i++) { + for (int i = 0; i < usernames.length; i++) + { usernames[i] = VerifyHelper.verifyUsername(input.readString(64)); } debug("Usernames: " + Arrays.toString(usernames)); // Respond with profiles array - for (String username : usernames) { + for (String username : usernames) + { ProfileByUsernameResponse.writeProfile(server, output, username); } } diff --git a/LaunchServer/source/response/profile/ProfileByUUIDResponse.java b/LaunchServer/source/response/profile/ProfileByUUIDResponse.java index f75601e..02298dd 100644 --- a/LaunchServer/source/response/profile/ProfileByUUIDResponse.java +++ b/LaunchServer/source/response/profile/ProfileByUUIDResponse.java @@ -1,8 +1,5 @@ package launchserver.response.profile; -import java.io.IOException; -import java.util.UUID; - import launcher.client.PlayerProfile; import launcher.client.PlayerProfile.Texture; import launcher.helper.LogHelper; @@ -11,19 +8,56 @@ import launchserver.LaunchServer; import launchserver.response.Response; -public final class ProfileByUUIDResponse extends Response { - public ProfileByUUIDResponse(LaunchServer server, long id, HInput input, HOutput output) { +import java.io.IOException; +import java.util.UUID; + +public final class ProfileByUUIDResponse extends Response +{ + public ProfileByUUIDResponse(LaunchServer server, long id, HInput input, HOutput output) + { super(server, id, input, output); } + public static PlayerProfile getProfile(LaunchServer server, UUID uuid, String username) + { + // Get skin texture + Texture skin; + try + { + skin = server.config.textureProvider.getSkinTexture(uuid, username); + } + catch (Throwable exc) + { + LogHelper.error(new IOException(String.format("Can't get skin texture: '%s'", username), exc)); + skin = null; + } + + // Get cloak texture + Texture cloak; + try + { + cloak = server.config.textureProvider.getCloakTexture(uuid, username); + } + catch (Throwable exc) + { + LogHelper.error(new IOException(String.format("Can't get cloak texture: '%s'", username), exc)); + cloak = null; + } + + // Return combined profile + return new PlayerProfile(uuid, username, skin, cloak); + } + @Override - public void reply() throws IOException { + public void reply() throws IOException + { UUID uuid = input.readUUID(); debug("UUID: " + uuid); // Verify has such profile String username = server.config.authHandler.uuidToUsername(uuid); - if (username == null) { + if (username == null) + { output.writeBoolean(false); return; } @@ -32,27 +66,4 @@ output.writeBoolean(true); getProfile(server, uuid, username).write(output); } - - public static PlayerProfile getProfile(LaunchServer server, UUID uuid, String username) { - // Get skin texture - Texture skin; - try { - skin = server.config.textureProvider.getSkinTexture(uuid, username); - } catch (Throwable exc) { - LogHelper.error(new IOException(String.format("Can't get skin texture: '%s'", username), exc)); - skin = null; - } - - // Get cloak texture - Texture cloak; - try { - cloak = server.config.textureProvider.getCloakTexture(uuid, username); - } catch (Throwable exc) { - LogHelper.error(new IOException(String.format("Can't get cloak texture: '%s'", username), exc)); - cloak = null; - } - - // Return combined profile - return new PlayerProfile(uuid, username, skin, cloak); - } } diff --git a/LaunchServer/source/response/profile/ProfileByUsernameResponse.java b/LaunchServer/source/response/profile/ProfileByUsernameResponse.java index b20c685..fa4169d 100644 --- a/LaunchServer/source/response/profile/ProfileByUsernameResponse.java +++ b/LaunchServer/source/response/profile/ProfileByUsernameResponse.java @@ -1,31 +1,26 @@ package launchserver.response.profile; -import java.io.IOException; -import java.util.UUID; - import launcher.helper.VerifyHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; import launchserver.LaunchServer; import launchserver.response.Response; -public final class ProfileByUsernameResponse extends Response { - public ProfileByUsernameResponse(LaunchServer server, long id, HInput input, HOutput output) { +import java.io.IOException; +import java.util.UUID; + +public final class ProfileByUsernameResponse extends Response +{ + public ProfileByUsernameResponse(LaunchServer server, long id, HInput input, HOutput output) + { super(server, id, input, output); } - @Override - public void reply() throws IOException { - String username = VerifyHelper.verifyUsername(input.readString(64)); - debug("Username: " + username); - - // Write response - writeProfile(server, output, username); - } - - public static void writeProfile(LaunchServer server, HOutput output, String username) throws IOException { + public static void writeProfile(LaunchServer server, HOutput output, String username) throws IOException + { UUID uuid = server.config.authHandler.usernameToUUID(username); - if (uuid == null) { + if (uuid == null) + { output.writeBoolean(false); return; } @@ -34,4 +29,14 @@ output.writeBoolean(true); ProfileByUUIDResponse.getProfile(server, uuid, username).write(output); } + + @Override + public void reply() throws IOException + { + String username = VerifyHelper.verifyUsername(input.readString(64)); + debug("Username: " + username); + + // Write response + writeProfile(server, output, username); + } } diff --git a/LaunchServer/source/response/update/LauncherResponse.java b/LaunchServer/source/response/update/LauncherResponse.java index b24d524..a65544d 100644 --- a/LaunchServer/source/response/update/LauncherResponse.java +++ b/LaunchServer/source/response/update/LauncherResponse.java @@ -1,8 +1,5 @@ package launchserver.response.update; -import java.io.IOException; -import java.util.Collection; - import launcher.client.ClientProfile; import launcher.helper.SecurityHelper; import launcher.serialize.HInput; @@ -12,16 +9,23 @@ import launchserver.LaunchServer; import launchserver.response.Response; -public final class LauncherResponse extends Response { - public LauncherResponse(LaunchServer server, long id, HInput input, HOutput output) { +import java.io.IOException; +import java.util.Collection; + +public final class LauncherResponse extends Response +{ + public LauncherResponse(LaunchServer server, long id, HInput input, HOutput output) + { super(server, id, input, output); } @Override - public void reply() throws IOException { + public void reply() throws IOException + { // Resolve launcher binary SignedBytesHolder bytes = (input.readBoolean() ? server.launcherEXEBinary : server.launcherBinary).getBytes(); - if (bytes == null) { + if (bytes == null) + { requestError("Missing launcher binary"); return; } @@ -30,7 +34,8 @@ // Update launcher binary output.writeByteArray(bytes.getSign(), -SecurityHelper.RSA_KEY_LENGTH); output.flush(); - if (input.readBoolean()) { + if (input.readBoolean()) + { output.writeByteArray(bytes.getBytes(), 0); return; // Launcher will be restarted } @@ -38,7 +43,8 @@ // Write clients profiles list Collection> profiles = server.getProfiles(); output.writeLength(profiles.size(), 0); - for (SignedObjectHolder profile : profiles) { + for (SignedObjectHolder profile : profiles) + { profile.write(output); } } diff --git a/LaunchServer/source/response/update/UpdateListResponse.java b/LaunchServer/source/response/update/UpdateListResponse.java index 39b0242..8e55c4d 100644 --- a/LaunchServer/source/response/update/UpdateListResponse.java +++ b/LaunchServer/source/response/update/UpdateListResponse.java @@ -1,8 +1,5 @@ package launchserver.response.update; -import java.util.Map.Entry; -import java.util.Set; - import launcher.hasher.HashedDir; import launcher.serialize.HInput; import launcher.serialize.HOutput; @@ -10,18 +7,25 @@ import launchserver.LaunchServer; import launchserver.response.Response; -public final class UpdateListResponse extends Response { - public UpdateListResponse(LaunchServer server, long id, HInput input, HOutput output) { +import java.util.Map.Entry; +import java.util.Set; + +public final class UpdateListResponse extends Response +{ + public UpdateListResponse(LaunchServer server, long id, HInput input, HOutput output) + { super(server, id, input, output); } @Override - public void reply() throws Throwable { + public void reply() throws Throwable + { Set>> updateDirs = server.getUpdateDirs(); // Write all update dirs names output.writeLength(updateDirs.size(), 0); - for (Entry> entry : updateDirs) { + for (Entry> entry : updateDirs) + { output.writeString(entry.getKey(), 255); } } diff --git a/LaunchServer/source/response/update/UpdateResponse.java b/LaunchServer/source/response/update/UpdateResponse.java index fe7aa58..3465eb7 100644 --- a/LaunchServer/source/response/update/UpdateResponse.java +++ b/LaunchServer/source/response/update/UpdateResponse.java @@ -1,13 +1,5 @@ package launchserver.response.update; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Path; -import java.util.Deque; -import java.util.LinkedList; -import java.util.zip.DeflaterOutputStream; - import launcher.hasher.HashedDir; import launcher.hasher.HashedEntry; import launcher.hasher.HashedEntry.Type; @@ -20,17 +12,29 @@ import launchserver.LaunchServer; import launchserver.response.Response; -public final class UpdateResponse extends Response { - public UpdateResponse(LaunchServer server, long id, HInput input, HOutput output) { +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.Deque; +import java.util.LinkedList; +import java.util.zip.DeflaterOutputStream; + +public final class UpdateResponse extends Response +{ + public UpdateResponse(LaunchServer server, long id, HInput input, HOutput output) + { super(server, id, input, output); } @Override - public void reply() throws IOException { + public void reply() throws IOException + { // Read update dir name String updateDirName = IOHelper.verifyFileName(input.readString(255)); SignedObjectHolder hdir = server.getUpdateDir(updateDirName); - if (hdir == null) { + if (hdir == null) + { requestError(String.format("Unknown update dir: %s", updateDirName)); return; } @@ -52,23 +56,28 @@ OutputStream fileOutput = server.config.compress ? new DeflaterOutputStream(output.stream, IOHelper.newDeflater(), IOHelper.BUFFER_SIZE, true) : output.stream; Action[] actionsSlice = new Action[UpdateRequest.MAX_QUEUE_SIZE]; loop: - while (true) { + while (true) + { // Read actions slice int length = input.readLength(actionsSlice.length); - for (int i = 0; i < length; i++) { + for (int i = 0; i < length; i++) + { actionsSlice[i] = new Action(input); } // Perform actions - for (int i = 0; i < length; i++) { + for (int i = 0; i < length; i++) + { Action action = actionsSlice[i]; - switch (action.type) { + switch (action.type) + { case CD: debug("CD '%s'", action.name); // Get hashed dir (for validation) HashedEntry hSubdir = dirStack.getLast().getEntry(action.name); - if (hSubdir == null || hSubdir.getType() != Type.DIR) { + if (hSubdir == null || hSubdir.getType() != Type.DIR) + { throw new IOException("Unknown hashed dir: " + action.name); } dirStack.add((HashedDir) hSubdir); @@ -81,19 +90,22 @@ // Get hashed file (for validation) HashedEntry hFile = dirStack.getLast().getEntry(action.name); - if (hFile == null || hFile.getType() != Type.FILE) { + if (hFile == null || hFile.getType() != Type.FILE) + { throw new IOException("Unknown hashed file: " + action.name); } // Resolve and write file Path file = dir.resolve(action.name); - if (IOHelper.readAttributes(file).size() != hFile.size()) { + if (IOHelper.readAttributes(file).size() != hFile.size()) + { fileOutput.write(0x0); fileOutput.flush(); throw new IOException("Unknown hashed file: " + action.name); } fileOutput.write(0xFF); - try (InputStream fileInput = IOHelper.newInput(file)) { + try (InputStream fileInput = IOHelper.newInput(file)) + { IOHelper.transfer(fileInput, fileOutput); } break; @@ -102,7 +114,8 @@ // Remove from hashed dir stack dirStack.removeLast(); - if (dirStack.isEmpty()) { + if (dirStack.isEmpty()) + { throw new IOException("Empty hDir stack"); } @@ -121,7 +134,8 @@ } // So we've updated :) - if (fileOutput instanceof DeflaterOutputStream) { + if (fileOutput instanceof DeflaterOutputStream) + { ((DeflaterOutputStream) fileOutput).finish(); } } diff --git a/LaunchServer/source/texture/DelegateTextureProvider.java b/LaunchServer/source/texture/DelegateTextureProvider.java index 95e470c..e9e4403 100644 --- a/LaunchServer/source/texture/DelegateTextureProvider.java +++ b/LaunchServer/source/texture/DelegateTextureProvider.java @@ -1,45 +1,53 @@ package launchserver.texture; -import java.io.IOException; -import java.util.Objects; -import java.util.UUID; - import launcher.LauncherAPI; import launcher.client.PlayerProfile.Texture; import launcher.helper.VerifyHelper; import launcher.serialize.config.entry.BlockConfigEntry; -public class DelegateTextureProvider extends TextureProvider { +import java.io.IOException; +import java.util.Objects; +import java.util.UUID; + +public class DelegateTextureProvider extends TextureProvider +{ private volatile TextureProvider delegate; - public DelegateTextureProvider(BlockConfigEntry block) { + public DelegateTextureProvider(BlockConfigEntry block) + { super(block); } @Override - public void close() throws IOException { + public void close() throws IOException + { TextureProvider delegate = this.delegate; - if (delegate != null) { + if (delegate != null) + { delegate.close(); } } @Override - public Texture getCloakTexture(UUID uuid, String username) throws IOException { + public Texture getCloakTexture(UUID uuid, String username) throws IOException + { return getDelegate().getCloakTexture(uuid, username); } @Override - public Texture getSkinTexture(UUID uuid, String username) throws IOException { + public Texture getSkinTexture(UUID uuid, String username) throws IOException + { return getDelegate().getSkinTexture(uuid, username); } - @LauncherAPI - public void setDelegate(TextureProvider delegate) { - this.delegate = delegate; + private TextureProvider getDelegate() + { + return VerifyHelper.verify(delegate, Objects::nonNull, "Delegate texture provider wasn't set"); } - private TextureProvider getDelegate() { - return VerifyHelper.verify(delegate, Objects::nonNull, "Delegate texture provider wasn't set"); + @LauncherAPI + public void setDelegate(TextureProvider delegate) + { + this.delegate = delegate; } } diff --git a/LaunchServer/source/texture/MojangTextureProvider.java b/LaunchServer/source/texture/MojangTextureProvider.java index e0400a1..ede0101 100644 --- a/LaunchServer/source/texture/MojangTextureProvider.java +++ b/LaunchServer/source/texture/MojangTextureProvider.java @@ -1,12 +1,5 @@ package launchserver.texture; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonArray; import com.eclipsesource.json.JsonObject; @@ -20,50 +13,67 @@ import launcher.serialize.config.entry.BlockConfigEntry; import launchserver.auth.provider.MojangAuthProvider; -public final class MojangTextureProvider extends TextureProvider { +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public final class MojangTextureProvider extends TextureProvider +{ @LauncherAPI public static final long CACHE_DURATION_MS = VerifyHelper.verifyLong( - Long.parseLong(System.getProperty("launcher.mysql.cacheDurationHours", Integer.toString(24))), - VerifyHelper.L_NOT_NEGATIVE, "launcher.mysql.cacheDurationHours can't be < 0") * 60L * 60L * 1000L; + Long.parseLong(System.getProperty("launcher.mysql.cacheDurationHours", Integer.toString(24))), + VerifyHelper.L_NOT_NEGATIVE, "launcher.mysql.cacheDurationHours can't be < 0") * 60L * 60L * 1000L; // Instance private final Map cache = new HashMap<>(1024); - public MojangTextureProvider(BlockConfigEntry block) { + public MojangTextureProvider(BlockConfigEntry block) + { super(block); } @Override - public void close() { + public void close() + { // Do nothing } @Override - public synchronized Texture getCloakTexture(UUID uuid, String username) { + public synchronized Texture getCloakTexture(UUID uuid, String username) + { return getCached(uuid, username).skin; } @Override - public synchronized Texture getSkinTexture(UUID uuid, String username) { + public synchronized Texture getSkinTexture(UUID uuid, String username) + { return getCached(uuid, username).cloak; } - private CacheData getCached(UUID uuid, String username) { + private CacheData getCached(UUID uuid, String username) + { CacheData result = cache.get(username); // Have cached result? - if (result != null && System.currentTimeMillis() < result.until) { - if (result.exc != null) { + if (result != null && System.currentTimeMillis() < result.until) + { + if (result.exc != null) + { JVMHelper.UNSAFE.throwException(result.exc); } return result; } - try { + try + { // TODO Don't query UUID by username if using mojang auth handler (not implemented yet) URL uuidURL = new URL("https://api.mojang.com/users/profiles/minecraft/" + IOHelper.urlEncode(username)); JsonObject uuidResponse = MojangAuthProvider.makeMojangRequest(uuidURL, null); - if (uuidResponse == null) { + if (uuidResponse == null) + { throw new IllegalArgumentException("Empty UUID response"); } String uuidResolved = uuidResponse.get("id").asString(); @@ -71,27 +81,32 @@ // Obtain player profile URL profileURL = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidResolved); JsonObject profileResponse = MojangAuthProvider.makeMojangRequest(profileURL, null); - if (profileResponse == null) { + if (profileResponse == null) + { throw new IllegalArgumentException("Empty Mojang response"); } JsonArray properties = (JsonArray) profileResponse.get("properties"); - if (properties == null) { + if (properties == null) + { LogHelper.subDebug("No properties"); return cache(username, null, null, null); } // Find textures property JsonObject texturesProperty = null; - for (JsonValue property : properties) { + for (JsonValue property : properties) + { JsonObject property0 = property.asObject(); - if (property0.get("name").asString().equals("textures")) { + if (property0.get("name").asString().equals("textures")) + { byte[] asBytes = Base64.getDecoder().decode(property0.get("value").asString()); String asString = new String(asBytes, StandardCharsets.UTF_8); texturesProperty = Json.parse(asString).asObject(); break; } } - if (texturesProperty == null) { + if (texturesProperty == null) + { LogHelper.subDebug("No textures property"); return cache(username, null, null, null); } @@ -105,7 +120,9 @@ // We're done return cache(username, skinTexture, cloakTexture, null); - } catch (Throwable exc) { + } + catch (Throwable exc) + { cache(username, null, null, exc); JVMHelper.UNSAFE.throwException(exc); } @@ -114,28 +131,33 @@ return result; } - private CacheData cache(String username, Texture skin, Texture cloak, Throwable exc) { + private CacheData cache(String username, Texture skin, Texture cloak, Throwable exc) + { long until = CACHE_DURATION_MS == 0L ? Long.MIN_VALUE : System.currentTimeMillis() + CACHE_DURATION_MS; CacheData data = exc == null ? new CacheData(skin, cloak, until) : new CacheData(exc, until); - if (CACHE_DURATION_MS != 0L) { + if (CACHE_DURATION_MS != 0L) + { cache.put(username, data); } return data; } - private static final class CacheData { + private static final class CacheData + { private final Texture skin, cloak; private final Throwable exc; private final long until; - private CacheData(Texture skin, Texture cloak, long until) { + private CacheData(Texture skin, Texture cloak, long until) + { this.skin = skin; this.cloak = cloak; this.until = until; exc = null; } - private CacheData(Throwable exc, long until) { + private CacheData(Throwable exc, long until) + { this.exc = exc; this.until = until; skin = cloak = null; diff --git a/LaunchServer/source/texture/RequestTextureProvider.java b/LaunchServer/source/texture/RequestTextureProvider.java index e7cd173..6d557b0 100644 --- a/LaunchServer/source/texture/RequestTextureProvider.java +++ b/LaunchServer/source/texture/RequestTextureProvider.java @@ -1,9 +1,5 @@ package launchserver.texture; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.UUID; - import launcher.client.ClientLauncher; import launcher.client.PlayerProfile.Texture; import launcher.helper.CommonHelper; @@ -12,14 +8,20 @@ import launcher.serialize.config.entry.BlockConfigEntry; import launcher.serialize.config.entry.StringConfigEntry; -public final class RequestTextureProvider extends TextureProvider { +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.UUID; + +public final class RequestTextureProvider extends TextureProvider +{ private static final UUID ZERO_UUID = new UUID(0, 0); // Instance private final String skinURL; private final String cloakURL; - public RequestTextureProvider(BlockConfigEntry block) { + public RequestTextureProvider(BlockConfigEntry block) + { super(block); skinURL = block.getEntryValue("skinsURL", StringConfigEntry.class); cloakURL = block.getEntryValue("cloaksURL", StringConfigEntry.class); @@ -29,33 +31,41 @@ IOHelper.verifyURL(getTextureURL(cloakURL, ZERO_UUID, "cloakUsername")); } - @Override - public void close() { - // Do nothing - } - - @Override - public Texture getCloakTexture(UUID uuid, String username) throws IOException { - return getTexture(getTextureURL(cloakURL, uuid, username), true); - } - - @Override - public Texture getSkinTexture(UUID uuid, String username) throws IOException { - return getTexture(getTextureURL(skinURL, uuid, username), false); - } - - private static Texture getTexture(String url, boolean cloak) throws IOException { + private static Texture getTexture(String url, boolean cloak) throws IOException + { LogHelper.debug("Getting texture: '%s'", url); - try { + try + { return new Texture(url, cloak); - } catch (FileNotFoundException ignored) { + } + catch (FileNotFoundException ignored) + { LogHelper.subDebug("Texture not found :("); return null; // Simply not found } } - private static String getTextureURL(String url, UUID uuid, String username) { + private static String getTextureURL(String url, UUID uuid, String username) + { return CommonHelper.replace(url, "username", IOHelper.urlEncode(username), - "uuid", IOHelper.urlEncode(uuid.toString()), "hash", IOHelper.urlEncode(ClientLauncher.toHash(uuid))); + "uuid", IOHelper.urlEncode(uuid.toString()), "hash", IOHelper.urlEncode(ClientLauncher.toHash(uuid))); + } + + @Override + public void close() + { + // Do nothing + } + + @Override + public Texture getCloakTexture(UUID uuid, String username) throws IOException + { + return getTexture(getTextureURL(cloakURL, uuid, username), true); + } + + @Override + public Texture getSkinTexture(UUID uuid, String username) throws IOException + { + return getTexture(getTextureURL(skinURL, uuid, username), false); } } diff --git a/LaunchServer/source/texture/TextureProvider.java b/LaunchServer/source/texture/TextureProvider.java index 471221d..e6a3c0b 100644 --- a/LaunchServer/source/texture/TextureProvider.java +++ b/LaunchServer/source/texture/TextureProvider.java @@ -1,25 +1,53 @@ package launchserver.texture; -import java.io.IOException; -import java.util.Map; -import java.util.Objects; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - import launcher.LauncherAPI; import launcher.client.PlayerProfile.Texture; import launcher.helper.VerifyHelper; import launcher.serialize.config.ConfigObject; import launcher.serialize.config.entry.BlockConfigEntry; -public abstract class TextureProvider extends ConfigObject implements AutoCloseable { +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class TextureProvider extends ConfigObject implements AutoCloseable +{ private static final Map> TEXTURE_PROVIDERS = new ConcurrentHashMap<>(2); + static + { + registerProvider("void", VoidTextureProvider::new); + registerProvider("delegate", DelegateTextureProvider::new); + + // Auth providers that doesn't do nothing :D + registerProvider("mojang", MojangTextureProvider::new); + registerProvider("request", RequestTextureProvider::new); + } + @LauncherAPI - protected TextureProvider(BlockConfigEntry block) { + protected TextureProvider(BlockConfigEntry block) + { super(block); } + @LauncherAPI + public static TextureProvider newProvider(String name, BlockConfigEntry block) + { + VerifyHelper.verifyIDName(name); + Adapter authHandlerAdapter = VerifyHelper.getMapValue(TEXTURE_PROVIDERS, name, + String.format("Unknown texture provider: '%s'", name)); + return authHandlerAdapter.convert(block); + } + + @LauncherAPI + public static void registerProvider(String name, Adapter adapter) + { + VerifyHelper.putIfAbsent(TEXTURE_PROVIDERS, name, Objects.requireNonNull(adapter, "adapter"), + String.format("Texture provider has been already registered: '%s'", name)); + } + @Override public abstract void close() throws IOException; @@ -28,27 +56,4 @@ @LauncherAPI public abstract Texture getSkinTexture(UUID uuid, String username) throws IOException; - - @LauncherAPI - public static TextureProvider newProvider(String name, BlockConfigEntry block) { - VerifyHelper.verifyIDName(name); - Adapter authHandlerAdapter = VerifyHelper.getMapValue(TEXTURE_PROVIDERS, name, - String.format("Unknown texture provider: '%s'", name)); - return authHandlerAdapter.convert(block); - } - - @LauncherAPI - public static void registerProvider(String name, Adapter adapter) { - VerifyHelper.putIfAbsent(TEXTURE_PROVIDERS, name, Objects.requireNonNull(adapter, "adapter"), - String.format("Texture provider has been already registered: '%s'", name)); - } - - static { - registerProvider("void", VoidTextureProvider::new); - registerProvider("delegate", DelegateTextureProvider::new); - - // Auth providers that doesn't do nothing :D - registerProvider("mojang", MojangTextureProvider::new); - registerProvider("request", RequestTextureProvider::new); - } } diff --git a/LaunchServer/source/texture/VoidTextureProvider.java b/LaunchServer/source/texture/VoidTextureProvider.java index b3e823a..e654acf 100644 --- a/LaunchServer/source/texture/VoidTextureProvider.java +++ b/LaunchServer/source/texture/VoidTextureProvider.java @@ -1,27 +1,32 @@ package launchserver.texture; -import java.util.UUID; - import launcher.client.PlayerProfile.Texture; import launcher.serialize.config.entry.BlockConfigEntry; -public final class VoidTextureProvider extends TextureProvider { - public VoidTextureProvider(BlockConfigEntry block) { +import java.util.UUID; + +public final class VoidTextureProvider extends TextureProvider +{ + public VoidTextureProvider(BlockConfigEntry block) + { super(block); } @Override - public void close() { + public void close() + { // Do nothing } @Override - public Texture getCloakTexture(UUID uuid, String username) { + public Texture getCloakTexture(UUID uuid, String username) + { return null; // Always nothing } @Override - public Texture getSkinTexture(UUID uuid, String username) { + public Texture getSkinTexture(UUID uuid, String username) + { return null; // Always nothing } } diff --git a/Launcher/source-authlib/minecraft/MinecraftProfileTexture.java b/Launcher/source-authlib/minecraft/MinecraftProfileTexture.java index c3e30a6..ced4a3b 100644 --- a/Launcher/source-authlib/minecraft/MinecraftProfileTexture.java +++ b/Launcher/source-authlib/minecraft/MinecraftProfileTexture.java @@ -4,7 +4,8 @@ import java.util.EnumSet; import java.util.Set; -public final class MinecraftProfileTexture { +public final class MinecraftProfileTexture +{ public static final Set PROFILE_TEXTURE_TYPES = Collections.unmodifiableSet(EnumSet.allOf(Type.class)); public static final int PROFILE_TEXTURE_COUNT = PROFILE_TEXTURE_TYPES.size(); @@ -12,41 +13,25 @@ private final String url; private final String hash; - public MinecraftProfileTexture(String url) { + public MinecraftProfileTexture(String url) + { this(url, baseName(url)); } - public MinecraftProfileTexture(String url, String hash) { + public MinecraftProfileTexture(String url, String hash) + { this.url = url; this.hash = hash; } - @Override - public String toString() { - return String.format("MinecraftProfileTexture{url='%s',hash=%s}", url, hash); - } - - @SuppressWarnings("unused") - public String getHash() { - return hash; - } - - @SuppressWarnings({ "unused", "SameReturnValue" }) - public String getMetadata(String key) { - return null; - } - - @SuppressWarnings("unused") - public String getUrl() { - return url; - } - - private static String baseName(String url) { + private static String baseName(String url) + { String name = url.substring(url.lastIndexOf('/') + 1); // Remove index int extensionIndex = name.lastIndexOf('.'); - if (extensionIndex >= 0) { + if (extensionIndex >= 0) + { name = name.substring(0, extensionIndex); } @@ -54,7 +39,32 @@ return name; } - public enum Type { + @Override + public String toString() + { + return String.format("MinecraftProfileTexture{url='%s',hash=%s}", url, hash); + } + + @SuppressWarnings("unused") + public String getHash() + { + return hash; + } + + @SuppressWarnings({"unused", "SameReturnValue"}) + public String getMetadata(String key) + { + return null; + } + + @SuppressWarnings("unused") + public String getUrl() + { + return url; + } + + public enum Type + { SKIN, CAPE, ELYTRA diff --git a/Launcher/source-authlib/yggdrasil/CompatBridge.java b/Launcher/source-authlib/yggdrasil/CompatBridge.java index c8c30d7..86bb45a 100644 --- a/Launcher/source-authlib/yggdrasil/CompatBridge.java +++ b/Launcher/source-authlib/yggdrasil/CompatBridge.java @@ -1,7 +1,5 @@ package com.mojang.authlib.yggdrasil; -import java.util.UUID; - import launcher.LauncherAPI; import launcher.client.ClientLauncher; import launcher.client.PlayerProfile; @@ -12,23 +10,30 @@ import launcher.request.uuid.ProfileByUUIDRequest; import launcher.request.uuid.ProfileByUsernameRequest; +import java.util.UUID; + // Used to bypass Launcher's class name obfuscation and access API @LauncherAPI -public final class CompatBridge { +public final class CompatBridge +{ public static final int PROFILES_MAX_BATCH_SIZE = BatchProfileByUsernameRequest.MAX_BATCH_SIZE; - private CompatBridge() { + private CompatBridge() + { } @SuppressWarnings("unused") - public static CompatProfile checkServer(String username, String serverID) throws Throwable { + public static CompatProfile checkServer(String username, String serverID) throws Throwable + { LogHelper.debug("CompatBridge.checkServer, Username: '%s', Server ID: %s", username, serverID); return CompatProfile.fromPlayerProfile(new CheckServerRequest(username, serverID).request()); } @SuppressWarnings("unused") - public static boolean joinServer(String username, String accessToken, String serverID) throws Throwable { - if (!ClientLauncher.isLaunched()) { + public static boolean joinServer(String username, String accessToken, String serverID) throws Throwable + { + if (!ClientLauncher.isLaunched()) + { throw new IllegalStateException("Bad Login (Cheater)"); } @@ -38,22 +43,26 @@ } @SuppressWarnings("unused") - public static CompatProfile profileByUUID(UUID uuid) throws Throwable { + public static CompatProfile profileByUUID(UUID uuid) throws Throwable + { return CompatProfile.fromPlayerProfile(new ProfileByUUIDRequest(uuid).request()); } @SuppressWarnings("unused") - public static CompatProfile profileByUsername(String username) throws Throwable { + public static CompatProfile profileByUsername(String username) throws Throwable + { return CompatProfile.fromPlayerProfile(new ProfileByUsernameRequest(username).request()); } @SuppressWarnings("unused") - public static CompatProfile[] profilesByUsername(String... usernames) throws Throwable { + public static CompatProfile[] profilesByUsername(String... usernames) throws Throwable + { PlayerProfile[] profiles = new BatchProfileByUsernameRequest(usernames).request(); // Convert profiles CompatProfile[] resultProfiles = new CompatProfile[profiles.length]; - for (int i = 0; i < profiles.length; i++) { + for (int i = 0; i < profiles.length; i++) + { resultProfiles[i] = CompatProfile.fromPlayerProfile(profiles[i]); } diff --git a/Launcher/source-authlib/yggdrasil/CompatProfile.java b/Launcher/source-authlib/yggdrasil/CompatProfile.java index 6289a7d..d27cfdb 100644 --- a/Launcher/source-authlib/yggdrasil/CompatProfile.java +++ b/Launcher/source-authlib/yggdrasil/CompatProfile.java @@ -1,14 +1,15 @@ package com.mojang.authlib.yggdrasil; -import java.util.UUID; - import launcher.LauncherAPI; import launcher.client.ClientLauncher; import launcher.client.PlayerProfile; import launcher.helper.SecurityHelper; +import java.util.UUID; + @LauncherAPI -public final class CompatProfile { +public final class CompatProfile +{ public static final String SKIN_URL_PROPERTY = ClientLauncher.SKIN_URL_PROPERTY; public static final String SKIN_DIGEST_PROPERTY = ClientLauncher.SKIN_DIGEST_PROPERTY; public static final String CLOAK_URL_PROPERTY = ClientLauncher.CLOAK_URL_PROPERTY; @@ -20,7 +21,8 @@ public final String skinURL, skinDigest; public final String cloakURL, cloakDigest; - public CompatProfile(UUID uuid, String username, String skinURL, String skinDigest, String cloakURL, String cloakDigest) { + public CompatProfile(UUID uuid, String username, String skinURL, String skinDigest, String cloakURL, String cloakDigest) + { this.uuid = uuid; uuidHash = ClientLauncher.toHash(uuid); this.username = username; @@ -30,29 +32,35 @@ this.cloakDigest = cloakDigest; } - public int countProperties() { + public static CompatProfile fromPlayerProfile(PlayerProfile profile) + { + return profile == null ? null : new CompatProfile(profile.uuid, profile.username, + profile.skin == null ? null : profile.skin.url, + profile.skin == null ? null : SecurityHelper.toHex(profile.skin.digest), + profile.cloak == null ? null : profile.cloak.url, + profile.cloak == null ? null : SecurityHelper.toHex(profile.cloak.digest) + ); + } + + public int countProperties() + { int count = 0; - if (skinURL != null) { + if (skinURL != null) + { count++; } - if (skinDigest != null) { + if (skinDigest != null) + { count++; } - if (cloakURL != null) { + if (cloakURL != null) + { count++; } - if (cloakDigest != null) { + if (cloakDigest != null) + { count++; } return count; } - - public static CompatProfile fromPlayerProfile(PlayerProfile profile) { - return profile == null ? null : new CompatProfile(profile.uuid, profile.username, - profile.skin == null ? null : profile.skin.url, - profile.skin == null ? null : SecurityHelper.toHex(profile.skin.digest), - profile.cloak == null ? null : profile.cloak.url, - profile.cloak == null ? null : SecurityHelper.toHex(profile.cloak.digest) - ); - } } diff --git a/Launcher/source-authlib/yggdrasil/LegacyBridge.java b/Launcher/source-authlib/yggdrasil/LegacyBridge.java index 05e42aa..9ad9944 100644 --- a/Launcher/source-authlib/yggdrasil/LegacyBridge.java +++ b/Launcher/source-authlib/yggdrasil/LegacyBridge.java @@ -10,41 +10,51 @@ // Used by 1.6.4 and below versions @LauncherAPI -public final class LegacyBridge { - private LegacyBridge() { +public final class LegacyBridge +{ + private LegacyBridge() + { } @SuppressWarnings("unused") - public static boolean checkServer(String username, String serverID) throws Throwable { + public static boolean checkServer(String username, String serverID) throws Throwable + { LogHelper.debug("LegacyBridge.checkServer, Username: '%s', Server ID: %s", username, serverID); return new CheckServerRequest(username, serverID).request() != null; } @SuppressWarnings("unused") - public static String getCloakURL(String username) { + public static String getCloakURL(String username) + { LogHelper.debug("LegacyBridge.getCloakURL: '%s'", username); return CommonHelper.replace(System.getProperty("launcher.legacy.cloaksURL", - "http://skins.minecraft.net/MinecraftCloaks/%username%.png"), "username", IOHelper.urlEncode(username)); + "http://skins.minecraft.net/MinecraftCloaks/%username%.png"), "username", IOHelper.urlEncode(username)); } @SuppressWarnings("unused") - public static String getSkinURL(String username) { + public static String getSkinURL(String username) + { LogHelper.debug("LegacyBridge.getSkinURL: '%s'", username); return CommonHelper.replace(System.getProperty("launcher.legacy.skinsURL", - "http://skins.minecraft.net/MinecraftSkins/%username%.png"), "username", IOHelper.urlEncode(username)); + "http://skins.minecraft.net/MinecraftSkins/%username%.png"), "username", IOHelper.urlEncode(username)); } @SuppressWarnings("unused") - public static String joinServer(String username, String accessToken, String serverID) { - if (!ClientLauncher.isLaunched()) { + public static String joinServer(String username, String accessToken, String serverID) + { + if (!ClientLauncher.isLaunched()) + { return "Bad Login (Cheater)"; } // Join server LogHelper.debug("LegacyBridge.joinServer, Username: '%s', Access token: %s, Server ID: %s", username, accessToken, serverID); - try { + try + { return new JoinServerRequest(username, accessToken, serverID).request() ? "OK" : "Bad Login (Clientside)"; - } catch (Throwable exc) { + } + catch (Throwable exc) + { return exc.toString(); } } diff --git a/Launcher/source-authlib/yggdrasil/YggdrasilAuthenticationService.java b/Launcher/source-authlib/yggdrasil/YggdrasilAuthenticationService.java index 53ebe91..19c9922 100644 --- a/Launcher/source-authlib/yggdrasil/YggdrasilAuthenticationService.java +++ b/Launcher/source-authlib/yggdrasil/YggdrasilAuthenticationService.java @@ -1,7 +1,5 @@ package com.mojang.authlib.yggdrasil; -import java.net.Proxy; - import com.mojang.authlib.Agent; import com.mojang.authlib.AuthenticationService; import com.mojang.authlib.GameProfileRepository; @@ -9,24 +7,31 @@ import com.mojang.authlib.minecraft.MinecraftSessionService; import launcher.helper.LogHelper; -public class YggdrasilAuthenticationService implements AuthenticationService { +import java.net.Proxy; + +public class YggdrasilAuthenticationService implements AuthenticationService +{ @SuppressWarnings("UnusedParameters") - public YggdrasilAuthenticationService(Proxy proxy, String clientToken) { + public YggdrasilAuthenticationService(Proxy proxy, String clientToken) + { LogHelper.debug("Patched AuthenticationService created: '%s'", clientToken); } @Override - public MinecraftSessionService createMinecraftSessionService() { + public MinecraftSessionService createMinecraftSessionService() + { return new YggdrasilMinecraftSessionService(this); } @Override - public GameProfileRepository createProfileRepository() { + public GameProfileRepository createProfileRepository() + { return new YggdrasilGameProfileRepository(); } @Override - public UserAuthentication createUserAuthentication(Agent agent) { + public UserAuthentication createUserAuthentication(Agent agent) + { throw new UnsupportedOperationException("createUserAuthentication is used only by Mojang Launcher"); } } diff --git a/Launcher/source-authlib/yggdrasil/YggdrasilGameProfileRepository.java b/Launcher/source-authlib/yggdrasil/YggdrasilGameProfileRepository.java index e6c1f5e..01ccbe8 100644 --- a/Launcher/source-authlib/yggdrasil/YggdrasilGameProfileRepository.java +++ b/Launcher/source-authlib/yggdrasil/YggdrasilGameProfileRepository.java @@ -1,8 +1,5 @@ package com.mojang.authlib.yggdrasil; -import java.util.Arrays; -import java.util.UUID; - import com.mojang.authlib.Agent; import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfileRepository; @@ -12,35 +9,59 @@ import launcher.helper.VerifyHelper; import launcher.request.uuid.BatchProfileByUsernameRequest; -public class YggdrasilGameProfileRepository implements GameProfileRepository { - private static final long BUSY_WAIT_MS = VerifyHelper.verifyLong( - Long.parseLong(System.getProperty("launcher.authlib.busyWait", Long.toString(100L))), - VerifyHelper.L_NOT_NEGATIVE, "launcher.authlib.busyWait can't be < 0"); - private static final long ERROR_BUSY_WAIT_MS = VerifyHelper.verifyLong( - Long.parseLong(System.getProperty("launcher.authlib.errorBusyWait", Long.toString(500L))), - VerifyHelper.L_NOT_NEGATIVE, "launcher.authlib.errorBusyWait can't be < 0"); +import java.util.Arrays; +import java.util.UUID; - public YggdrasilGameProfileRepository() { +public class YggdrasilGameProfileRepository implements GameProfileRepository +{ + private static final long BUSY_WAIT_MS = VerifyHelper.verifyLong( + Long.parseLong(System.getProperty("launcher.authlib.busyWait", Long.toString(100L))), + VerifyHelper.L_NOT_NEGATIVE, "launcher.authlib.busyWait can't be < 0"); + private static final long ERROR_BUSY_WAIT_MS = VerifyHelper.verifyLong( + Long.parseLong(System.getProperty("launcher.authlib.errorBusyWait", Long.toString(500L))), + VerifyHelper.L_NOT_NEGATIVE, "launcher.authlib.errorBusyWait can't be < 0"); + + public YggdrasilGameProfileRepository() + { LogHelper.debug("Patched GameProfileRepository created"); } - public YggdrasilGameProfileRepository(YggdrasilAuthenticationService authenticationService) { + public YggdrasilGameProfileRepository(YggdrasilAuthenticationService authenticationService) + { this(); } + private static void busyWait(long ms) + { + try + { + Thread.sleep(ms); + } + catch (InterruptedException e) + { + LogHelper.error(e); + } + } + @Override - public void findProfilesByNames(String[] usernames, Agent agent, ProfileLookupCallback callback) { + public void findProfilesByNames(String[] usernames, Agent agent, ProfileLookupCallback callback) + { int offset = 0; - while (offset < usernames.length) { + while (offset < usernames.length) + { String[] sliceUsernames = Arrays.copyOfRange(usernames, offset, Math.min(offset + BatchProfileByUsernameRequest.MAX_BATCH_SIZE, usernames.length)); offset += BatchProfileByUsernameRequest.MAX_BATCH_SIZE; // Batch Username-To-UUID request PlayerProfile[] sliceProfiles; - try { + try + { sliceProfiles = new BatchProfileByUsernameRequest(sliceUsernames).request(); - } catch (Throwable exc) { - for (String username : sliceUsernames) { + } + catch (Throwable exc) + { + for (String username : sliceUsernames) + { LogHelper.debug("Couldn't find profile '%s': %s", username, exc); callback.onProfileLookupFailed(new GameProfile((UUID) null, username), exc instanceof Exception ? (Exception) exc : new Exception(exc)); } @@ -51,9 +72,11 @@ } // Request succeeded! - for (int i = 0; i < sliceProfiles.length; i++) { + for (int i = 0; i < sliceProfiles.length; i++) + { PlayerProfile pp = sliceProfiles[i]; - if (pp == null) { + if (pp == null) + { String username = sliceUsernames[i]; LogHelper.debug("Couldn't find profile '%s'", username); callback.onProfileLookupFailed(new GameProfile((UUID) null, username), new ProfileNotFoundException("Server did not find the requested profile")); @@ -69,12 +92,4 @@ busyWait(BUSY_WAIT_MS); } } - - private static void busyWait(long ms) { - try { - Thread.sleep(ms); - } catch (InterruptedException e) { - LogHelper.error(e); - } - } } diff --git a/Launcher/source-authlib/yggdrasil/YggdrasilMinecraftSessionService.java b/Launcher/source-authlib/yggdrasil/YggdrasilMinecraftSessionService.java index 29e30d5..c417b54 100644 --- a/Launcher/source-authlib/yggdrasil/YggdrasilMinecraftSessionService.java +++ b/Launcher/source-authlib/yggdrasil/YggdrasilMinecraftSessionService.java @@ -1,11 +1,5 @@ package com.mojang.authlib.yggdrasil; -import java.net.InetAddress; -import java.util.Base64; -import java.util.EnumMap; -import java.util.Map; -import java.util.UUID; - import com.google.common.collect.Iterables; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -28,39 +22,122 @@ import launcher.request.auth.JoinServerRequest; import launcher.request.uuid.ProfileByUUIDRequest; -public class YggdrasilMinecraftSessionService extends BaseMinecraftSessionService { +import java.net.InetAddress; +import java.util.Base64; +import java.util.EnumMap; +import java.util.Map; +import java.util.UUID; + +public class YggdrasilMinecraftSessionService extends BaseMinecraftSessionService +{ public static final JsonParser JSON_PARSER = new JsonParser(); public static final boolean NO_TEXTURES = Boolean.parseBoolean("launcher.authlib.noTextures"); - public YggdrasilMinecraftSessionService(AuthenticationService service) { + public YggdrasilMinecraftSessionService(AuthenticationService service) + { super(service); LogHelper.debug("Patched MinecraftSessionService created"); } - public YggdrasilMinecraftSessionService(YggdrasilAuthenticationService service) { + public YggdrasilMinecraftSessionService(YggdrasilAuthenticationService service) + { this((AuthenticationService) service); } + public static void fillTextureProperties(GameProfile profile, PlayerProfile pp) + { + LogHelper.debug("fillTextureProperties, Username: '%s'", profile.getName()); + if (NO_TEXTURES) + { + return; + } + + // Fill textures map + PropertyMap properties = profile.getProperties(); + if (pp.skin != null) + { + properties.put(ClientLauncher.SKIN_URL_PROPERTY, new Property(ClientLauncher.SKIN_URL_PROPERTY, pp.skin.url, "")); + properties.put(ClientLauncher.SKIN_DIGEST_PROPERTY, new Property(ClientLauncher.SKIN_DIGEST_PROPERTY, SecurityHelper.toHex(pp.skin.digest), "")); + LogHelper.debug("fillTextureProperties, Has skin texture for username '%s'", profile.getName()); + } + if (pp.cloak != null) + { + properties.put(ClientLauncher.CLOAK_URL_PROPERTY, new Property(ClientLauncher.CLOAK_URL_PROPERTY, pp.cloak.url, "")); + properties.put(ClientLauncher.CLOAK_DIGEST_PROPERTY, new Property(ClientLauncher.CLOAK_DIGEST_PROPERTY, SecurityHelper.toHex(pp.cloak.digest), "")); + LogHelper.debug("fillTextureProperties, Has cloak texture for username '%s'", profile.getName()); + } + } + + public static GameProfile toGameProfile(PlayerProfile pp) + { + GameProfile profile = new GameProfile(pp.uuid, pp.username); + fillTextureProperties(profile, pp); + return profile; + } + + private static void getTexturesMojang(Map textures, String texturesBase64, GameProfile profile) + { + // Decode textures payload + JsonObject texturesJSON; + try + { + byte[] asBytes = Base64.getDecoder().decode(texturesBase64); + String asString = new String(asBytes, IOHelper.UNICODE_CHARSET); + texturesJSON = JSON_PARSER.parse(asString).getAsJsonObject().getAsJsonObject("textures"); + } + catch (Throwable ignored) + { + LogHelper.error("Could not decode textures payload, Username: '%s', UUID: '%s'", profile.getName(), profile.getUUID()); + return; + } + + // Fetch textures from textures JSON + for (Type type : MinecraftProfileTexture.PROFILE_TEXTURE_TYPES) + { + if (textures.containsKey(type)) + { + continue; // Overriden by launcher + } + + // Get texture from JSON + JsonElement textureJSON = texturesJSON.get(type.name()); + if (textureJSON != null && textureJSON.isJsonObject()) + { + JsonElement urlValue = textureJSON.getAsJsonObject().get("url"); + if (urlValue.isJsonPrimitive()) + { + textures.put(type, new MinecraftProfileTexture(urlValue.getAsString())); + } + } + } + } + @Override - public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) { + public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) + { // Verify has UUID UUID uuid = profile.getUUID(); LogHelper.debug("fillProfileProperties, UUID: %s", uuid); - if (uuid == null) { + if (uuid == null) + { return profile; } // Make profile request PlayerProfile pp; - try { + try + { pp = new ProfileByUUIDRequest(uuid).request(); - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.debug("Couldn't fetch profile properties for '%s': %s", profile, exc); return profile; } // Verify is found - if (pp == null) { + if (pp == null) + { LogHelper.debug("Couldn't fetch profile properties for '%s' as the profile does not exist", profile); return profile; } @@ -72,30 +149,36 @@ } @Override - public Map getTextures(GameProfile profile, boolean requireSecure) { + public Map getTextures(GameProfile profile, boolean requireSecure) + { LogHelper.debug("getTextures, Username: '%s', UUID: '%s'", profile.getName(), profile.getUUID()); Map textures = new EnumMap<>(Type.class); // Add textures - if (!NO_TEXTURES) { + if (!NO_TEXTURES) + { // Add skin URL to textures map Property skinURL = Iterables.getFirst(profile.getProperties().get(ClientLauncher.SKIN_URL_PROPERTY), null); Property skinDigest = Iterables.getFirst(profile.getProperties().get(ClientLauncher.SKIN_DIGEST_PROPERTY), null); - if (skinURL != null && skinDigest != null) { + if (skinURL != null && skinDigest != null) + { textures.put(Type.SKIN, new MinecraftProfileTexture(skinURL.getValue(), skinDigest.getValue())); } // Add cloak URL to textures map Property cloakURL = Iterables.getFirst(profile.getProperties().get(ClientLauncher.CLOAK_URL_PROPERTY), null); Property cloakDigest = Iterables.getFirst(profile.getProperties().get(ClientLauncher.CLOAK_DIGEST_PROPERTY), null); - if (cloakURL != null && cloakDigest != null) { + if (cloakURL != null && cloakDigest != null) + { textures.put(Type.CAPE, new MinecraftProfileTexture(cloakURL.getValue(), cloakDigest.getValue())); } // Try to find missing textures in textures payload (now always true because launcher is not passing elytra skins) - if (textures.size() != MinecraftProfileTexture.PROFILE_TEXTURE_COUNT) { + if (textures.size() != MinecraftProfileTexture.PROFILE_TEXTURE_COUNT) + { Property texturesMojang = Iterables.getFirst(profile.getProperties().get("textures"), null); - if (texturesMojang != null) { + if (texturesMojang != null) + { getTexturesMojang(textures, texturesMojang.getValue(), profile); } } @@ -106,15 +189,19 @@ } @Override - public GameProfile hasJoinedServer(GameProfile profile, String serverID) throws AuthenticationUnavailableException { + public GameProfile hasJoinedServer(GameProfile profile, String serverID) throws AuthenticationUnavailableException + { String username = profile.getName(); LogHelper.debug("checkServer, Username: '%s', Server ID: %s", username, serverID); // Make checkServer request PlayerProfile pp; - try { + try + { pp = new CheckServerRequest(username, serverID).request(); - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.error(exc); throw new AuthenticationUnavailableException(exc); } @@ -124,13 +211,16 @@ } @Override - public GameProfile hasJoinedServer(GameProfile profile, String serverID, InetAddress address) throws AuthenticationUnavailableException { + public GameProfile hasJoinedServer(GameProfile profile, String serverID, InetAddress address) throws AuthenticationUnavailableException + { return hasJoinedServer(profile, serverID); } @Override - public void joinServer(GameProfile profile, String accessToken, String serverID) throws AuthenticationException { - if (!ClientLauncher.isLaunched()) { + public void joinServer(GameProfile profile, String accessToken, String serverID) throws AuthenticationException + { + if (!ClientLauncher.isLaunched()) + { throw new AuthenticationException("Bad Login (Cheater)"); } @@ -140,70 +230,19 @@ // Make joinServer request boolean success; - try { + try + { success = new JoinServerRequest(username, accessToken, serverID).request(); - } catch (Throwable exc) { + } + catch (Throwable exc) + { throw new AuthenticationUnavailableException(exc); } // Verify is success - if (!success) { + if (!success) + { throw new AuthenticationException("Bad Login (Clientside)"); } } - - public static void fillTextureProperties(GameProfile profile, PlayerProfile pp) { - LogHelper.debug("fillTextureProperties, Username: '%s'", profile.getName()); - if (NO_TEXTURES) { - return; - } - - // Fill textures map - PropertyMap properties = profile.getProperties(); - if (pp.skin != null) { - properties.put(ClientLauncher.SKIN_URL_PROPERTY, new Property(ClientLauncher.SKIN_URL_PROPERTY, pp.skin.url, "")); - properties.put(ClientLauncher.SKIN_DIGEST_PROPERTY, new Property(ClientLauncher.SKIN_DIGEST_PROPERTY, SecurityHelper.toHex(pp.skin.digest), "")); - LogHelper.debug("fillTextureProperties, Has skin texture for username '%s'", profile.getName()); - } - if (pp.cloak != null) { - properties.put(ClientLauncher.CLOAK_URL_PROPERTY, new Property(ClientLauncher.CLOAK_URL_PROPERTY, pp.cloak.url, "")); - properties.put(ClientLauncher.CLOAK_DIGEST_PROPERTY, new Property(ClientLauncher.CLOAK_DIGEST_PROPERTY, SecurityHelper.toHex(pp.cloak.digest), "")); - LogHelper.debug("fillTextureProperties, Has cloak texture for username '%s'", profile.getName()); - } - } - - public static GameProfile toGameProfile(PlayerProfile pp) { - GameProfile profile = new GameProfile(pp.uuid, pp.username); - fillTextureProperties(profile, pp); - return profile; - } - - private static void getTexturesMojang(Map textures, String texturesBase64, GameProfile profile) { - // Decode textures payload - JsonObject texturesJSON; - try { - byte[] asBytes = Base64.getDecoder().decode(texturesBase64); - String asString = new String(asBytes, IOHelper.UNICODE_CHARSET); - texturesJSON = JSON_PARSER.parse(asString).getAsJsonObject().getAsJsonObject("textures"); - } catch (Throwable ignored) { - LogHelper.error("Could not decode textures payload, Username: '%s', UUID: '%s'", profile.getName(), profile.getUUID()); - return; - } - - // Fetch textures from textures JSON - for (Type type : MinecraftProfileTexture.PROFILE_TEXTURE_TYPES) { - if (textures.containsKey(type)) { - continue; // Overriden by launcher - } - - // Get texture from JSON - JsonElement textureJSON = texturesJSON.get(type.name()); - if (textureJSON != null && textureJSON.isJsonObject()) { - JsonElement urlValue = textureJSON.getAsJsonObject().get("url"); - if (urlValue.isJsonPrimitive()) { - textures.put(type, new MinecraftProfileTexture(urlValue.getAsString())); - } - } - } - } } diff --git a/Launcher/source-testing/LauncherWrap.java b/Launcher/source-testing/LauncherWrap.java index d63b905..2d7d8ac 100644 --- a/Launcher/source-testing/LauncherWrap.java +++ b/Launcher/source-testing/LauncherWrap.java @@ -1,10 +1,13 @@ package launcher; -public final class LauncherWrap { - private LauncherWrap() { +public final class LauncherWrap +{ + private LauncherWrap() + { } - public static void main(String... args) throws Throwable { + public static void main(String... args) throws Throwable + { Launcher.main(args); // Just for test runtime } } diff --git a/Launcher/source/Launcher.java b/Launcher/source/Launcher.java index 0c85275..876446e 100644 --- a/Launcher/source/Launcher.java +++ b/Launcher/source/Launcher.java @@ -1,28 +1,6 @@ package launcher; -import java.io.BufferedReader; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.URL; -import java.nio.file.NoSuchFileException; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import javax.script.Bindings; -import javax.script.Invocable; -import javax.script.ScriptContext; -import javax.script.ScriptEngine; -import javax.script.ScriptException; import javafx.application.Application; - import launcher.client.ClientLauncher; import launcher.client.ClientLauncher.Params; import launcher.client.ClientProfile; @@ -34,15 +12,10 @@ import launcher.hasher.HashedDir; import launcher.hasher.HashedEntry; import launcher.hasher.HashedFile; -import launcher.helper.CommonHelper; -import launcher.helper.IOHelper; -import launcher.helper.JVMHelper; +import launcher.helper.*; import launcher.helper.JVMHelper.OS; -import launcher.helper.LogHelper; import launcher.helper.LogHelper.Output; -import launcher.helper.SecurityHelper; import launcher.helper.SecurityHelper.DigestAlgorithm; -import launcher.helper.VerifyHelper; import launcher.helper.js.JSApplication; import launcher.request.CustomRequest; import launcher.request.PingRequest; @@ -62,70 +35,55 @@ import launcher.serialize.config.ConfigObject.Adapter; import launcher.serialize.config.TextConfigReader; import launcher.serialize.config.TextConfigWriter; -import launcher.serialize.config.entry.BlockConfigEntry; -import launcher.serialize.config.entry.BooleanConfigEntry; +import launcher.serialize.config.entry.*; import launcher.serialize.config.entry.ConfigEntry.Type; -import launcher.serialize.config.entry.IntegerConfigEntry; -import launcher.serialize.config.entry.ListConfigEntry; -import launcher.serialize.config.entry.StringConfigEntry; import launcher.serialize.signed.SignedBytesHolder; import launcher.serialize.signed.SignedObjectHolder; import launcher.serialize.stream.EnumSerializer; import launcher.serialize.stream.StreamObject; -public final class Launcher { - private static final AtomicReference CONFIG = new AtomicReference<>(); +import javax.script.*; +import java.io.BufferedReader; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URL; +import java.nio.file.NoSuchFileException; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +public final class Launcher +{ // Version info - @LauncherAPI public static final String VERSION = "1.4.2"; - @LauncherAPI public static final String BUILD = readBuildNumber(); - @LauncherAPI public static final int PROTOCOL_MAGIC = 0x724724_00 + 23; - + @LauncherAPI + public static final String VERSION = "1.4.2"; + @LauncherAPI + public static final String BUILD = readBuildNumber(); + @LauncherAPI + public static final int PROTOCOL_MAGIC = 0x724724_00 + 23; // Constants - @LauncherAPI public static final String RUNTIME_DIR = "runtime"; - @LauncherAPI public static final String CONFIG_FILE = "config.bin"; - @LauncherAPI public static final String INIT_SCRIPT_FILE = "init.js"; - + @LauncherAPI + public static final String RUNTIME_DIR = "runtime"; + @LauncherAPI + public static final String CONFIG_FILE = "config.bin"; + @LauncherAPI + public static final String INIT_SCRIPT_FILE = "init.js"; + private static final AtomicReference CONFIG = new AtomicReference<>(); // Instance private final AtomicBoolean started = new AtomicBoolean(false); private final ScriptEngine engine = CommonHelper.newScriptEngine(); - private Launcher() { + private Launcher() + { setScriptBindings(); } @LauncherAPI - public Object loadScript(URL url) throws IOException, ScriptException { - LogHelper.debug("Loading script: '%s'", url); - try (BufferedReader reader = IOHelper.newReader(url)) { - return engine.eval(reader); - } - } - - @LauncherAPI - public void start(String... args) throws Throwable { - Objects.requireNonNull(args, "args"); - if (started.getAndSet(true)) { - throw new IllegalStateException("Launcher has been already started"); - } - - // Load init.js script - loadScript(getResourceURL(INIT_SCRIPT_FILE)); - LogHelper.info("Invoking start() function"); - ((Invocable) engine).invokeFunction("start", (Object) args); - } - - private void setScriptBindings() { - LogHelper.info("Setting up script engine bindings"); - Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); - bindings.put("launcher", this); - - // Add launcher class bindings - addLauncherClassBindings(engine, bindings); - } - - @LauncherAPI - public static void addLauncherClassBindings(ScriptEngine engine, Map bindings) { + public static void addLauncherClassBindings(ScriptEngine engine, Map bindings) + { addClassBinding(engine, bindings, "Launcher", Launcher.class); addClassBinding(engine, bindings, "Config", Config.class); @@ -192,31 +150,43 @@ addClassBinding(engine, bindings, "VerifyHelper", VerifyHelper.class); // Load JS API if available - try { + try + { addClassBinding(engine, bindings, "Application", Application.class); addClassBinding(engine, bindings, "JSApplication", JSApplication.class); - } catch (Throwable ignored) { + } + catch (Throwable ignored) + { LogHelper.warning("JavaFX API isn't available"); } } @LauncherAPI - public static void addClassBinding(ScriptEngine engine, Map bindings, String name, Class clazz) { + public static void addClassBinding(ScriptEngine engine, Map bindings, String name, Class clazz) + { bindings.put(name + "Class", clazz); // Backwards-compatibility - try { + try + { engine.eval("var " + name + " = " + name + "Class.static;"); - } catch (ScriptException e) { + } + catch (ScriptException e) + { throw new AssertionError(e); } } @LauncherAPI - public static Config getConfig() { + public static Config getConfig() + { Config config = CONFIG.get(); - if (config == null) { - try (HInput input = new HInput(IOHelper.newInput(IOHelper.getResourceURL(CONFIG_FILE)))) { + if (config == null) + { + try (HInput input = new HInput(IOHelper.newInput(IOHelper.getResourceURL(CONFIG_FILE)))) + { config = new Config(input); - } catch (IOException | InvalidKeySpecException e) { + } + catch (IOException | InvalidKeySpecException e) + { throw new SecurityException(e); } CONFIG.set(config); @@ -225,16 +195,19 @@ } @LauncherAPI - public static URL getResourceURL(String name) throws IOException { + public static URL getResourceURL(String name) throws IOException + { Config config = getConfig(); byte[] validDigest = config.runtime.get(name); - if (validDigest == null) { // No such resource digest + if (validDigest == null) + { // No such resource digest throw new NoSuchFileException(name); } // Resolve URL and verify digest URL url = IOHelper.getResourceURL(RUNTIME_DIR + '/' + name); - if (!Arrays.equals(validDigest, SecurityHelper.digest(DigestAlgorithm.MD5, url))) { + if (!Arrays.equals(validDigest, SecurityHelper.digest(DigestAlgorithm.MD5, url))) + { throw new NoSuchFileException(name); // Digest mismatch } @@ -243,21 +216,26 @@ } @LauncherAPI - @SuppressWarnings({ "SameReturnValue", "MethodReturnAlwaysConstant" }) - public static String getVersion() { + @SuppressWarnings({"SameReturnValue", "MethodReturnAlwaysConstant"}) + public static String getVersion() + { return VERSION; // Because Java constants are known at compile-time } - public static void main(String... args) throws Throwable { + public static void main(String... args) throws Throwable + { SecurityHelper.verifyCertificates(Launcher.class); JVMHelper.verifySystemProperties(Launcher.class, true); LogHelper.printVersion("Launcher"); // Start Launcher long start = System.currentTimeMillis(); - try { + try + { new Launcher().start(args); - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.error(exc); return; } @@ -265,15 +243,55 @@ LogHelper.debug("Launcher started in %dms", end - start); } - private static String readBuildNumber() { - try { + private static String readBuildNumber() + { + try + { return IOHelper.request(IOHelper.getResourceURL("buildnumber")); - } catch (IOException ignored) { + } + catch (IOException ignored) + { return "dev"; // Maybe dev env? } } - public static final class Config extends StreamObject { + @LauncherAPI + public Object loadScript(URL url) throws IOException, ScriptException + { + LogHelper.debug("Loading script: '%s'", url); + try (BufferedReader reader = IOHelper.newReader(url)) + { + return engine.eval(reader); + } + } + + @LauncherAPI + public void start(String... args) throws Throwable + { + Objects.requireNonNull(args, "args"); + if (started.getAndSet(true)) + { + throw new IllegalStateException("Launcher has been already started"); + } + + // Load init.js script + loadScript(getResourceURL(INIT_SCRIPT_FILE)); + LogHelper.info("Invoking start() function"); + ((Invocable) engine).invokeFunction("start", (Object) args); + } + + private void setScriptBindings() + { + LogHelper.info("Setting up script engine bindings"); + Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE); + bindings.put("launcher", this); + + // Add launcher class bindings + addLauncherClassBindings(engine, bindings); + } + + public static final class Config extends StreamObject + { @LauncherAPI public static final String ADDRESS_OVERRIDE_PROPERTY = "launcher.addressOverride"; @LauncherAPI @@ -289,38 +307,43 @@ @LauncherAPI @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter") - public Config(String address, int port, RSAPublicKey publicKey, Map runtime) { + public Config(String address, int port, RSAPublicKey publicKey, Map runtime) + { this.address = InetSocketAddress.createUnresolved(address, port); this.publicKey = Objects.requireNonNull(publicKey, "publicKey"); this.runtime = Collections.unmodifiableMap(new HashMap<>(runtime)); } @LauncherAPI - public Config(HInput input) throws IOException, InvalidKeySpecException { + public Config(HInput input) throws IOException, InvalidKeySpecException + { String localAddress = input.readASCII(255); address = InetSocketAddress.createUnresolved( - ADDRESS_OVERRIDE == null ? localAddress : ADDRESS_OVERRIDE, input.readLength(65535)); + ADDRESS_OVERRIDE == null ? localAddress : ADDRESS_OVERRIDE, input.readLength(65535)); publicKey = SecurityHelper.toPublicRSAKey(input.readByteArray(SecurityHelper.CRYPTO_MAX_LENGTH)); // Read signed runtime int count = input.readLength(0); Map localResources = new HashMap<>(count); - for (int i = 0; i < count; i++) { + for (int i = 0; i < count; i++) + { String name = input.readString(255); VerifyHelper.putIfAbsent(localResources, name, - input.readByteArray(SecurityHelper.CRYPTO_MAX_LENGTH), - String.format("Duplicate runtime resource: '%s'", name)); + input.readByteArray(SecurityHelper.CRYPTO_MAX_LENGTH), + String.format("Duplicate runtime resource: '%s'", name)); } runtime = Collections.unmodifiableMap(localResources); // Print warning if address override is enabled - if (ADDRESS_OVERRIDE != null) { + if (ADDRESS_OVERRIDE != null) + { LogHelper.warning("Address override is enabled: '%s'", ADDRESS_OVERRIDE); } } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { output.writeASCII(address.getHostString(), 255); output.writeLength(address.getPort(), 65535); output.writeByteArray(publicKey.getEncoded(), SecurityHelper.CRYPTO_MAX_LENGTH); @@ -328,7 +351,8 @@ // Write signed runtime Set> entrySet = runtime.entrySet(); output.writeLength(entrySet.size(), 0); - for (Entry entry : runtime.entrySet()) { + for (Entry entry : runtime.entrySet()) + { output.writeString(entry.getKey(), 255); output.writeByteArray(entry.getValue(), SecurityHelper.CRYPTO_MAX_LENGTH); } diff --git a/Launcher/source/LauncherAPI.java b/Launcher/source/LauncherAPI.java index 6978944..4c192a8 100644 --- a/Launcher/source/LauncherAPI.java +++ b/Launcher/source/LauncherAPI.java @@ -6,7 +6,8 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.CLASS) -@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD }) -public @interface LauncherAPI { +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) +public @interface LauncherAPI +{ /* This annotation implies that method/field/class should not be renamed or obfuscated */ } diff --git a/Launcher/source/client/ClientLauncher.java b/Launcher/source/client/ClientLauncher.java index a3ff490..13fa915 100644 --- a/Launcher/source/client/ClientLauncher.java +++ b/Launcher/source/client/ClientLauncher.java @@ -1,29 +1,5 @@ package launcher.client; -import java.io.IOException; -import java.lang.ProcessBuilder.Redirect; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodType; -import java.net.URL; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.PosixFilePermission; -import java.security.interfaces.RSAPublicKey; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.regex.Pattern; - import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonObject; import com.eclipsesource.json.WriterConfig; @@ -34,62 +10,77 @@ import launcher.hasher.DirWatcher; import launcher.hasher.FileNameMatcher; import launcher.hasher.HashedDir; -import launcher.helper.CommonHelper; -import launcher.helper.IOHelper; -import launcher.helper.JVMHelper; +import launcher.helper.*; import launcher.helper.JVMHelper.OS; -import launcher.helper.LogHelper; -import launcher.helper.SecurityHelper; -import launcher.helper.VerifyHelper; import launcher.request.update.LauncherRequest; import launcher.serialize.HInput; import launcher.serialize.HOutput; import launcher.serialize.signed.SignedObjectHolder; import launcher.serialize.stream.StreamObject; -public final class ClientLauncher { +import java.io.IOException; +import java.lang.ProcessBuilder.Redirect; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.net.URL; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.security.interfaces.RSAPublicKey; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.regex.Pattern; + +public final class ClientLauncher +{ + // Authlib constants + @LauncherAPI + public static final String SKIN_URL_PROPERTY = "skinURL"; + @LauncherAPI + public static final String SKIN_DIGEST_PROPERTY = "skinDigest"; + @LauncherAPI + public static final String CLOAK_URL_PROPERTY = "cloakURL"; + @LauncherAPI + public static final String CLOAK_DIGEST_PROPERTY = "cloakDigest"; private static final String[] EMPTY_ARRAY = new String[0]; private static final String MAGICAL_INTEL_OPTION = "-XX:HeapDumpPath=ThisTricksIntelDriversForPerformance_javaw.exe_minecraft.exe.heapdump"; private static final Set BIN_POSIX_PERMISSIONS = Collections.unmodifiableSet(EnumSet.of( - PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE, // Owner - PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_EXECUTE, // Group - PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_EXECUTE // Others + PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE, // Owner + PosixFilePermission.GROUP_READ, PosixFilePermission.GROUP_EXECUTE, // Group + PosixFilePermission.OTHERS_READ, PosixFilePermission.OTHERS_EXECUTE // Others )); - // Constants private static final Path NATIVES_DIR = IOHelper.toPath("natives"); private static final Path RESOURCEPACKS_DIR = IOHelper.toPath("resourcepacks"); private static final Pattern UUID_PATTERN = Pattern.compile("-", Pattern.LITERAL); - - // Authlib constants - @LauncherAPI public static final String SKIN_URL_PROPERTY = "skinURL"; - @LauncherAPI public static final String SKIN_DIGEST_PROPERTY = "skinDigest"; - @LauncherAPI public static final String CLOAK_URL_PROPERTY = "cloakURL"; - @LauncherAPI public static final String CLOAK_DIGEST_PROPERTY = "cloakDigest"; - // Used to determine from clientside is launched from launcher private static final AtomicBoolean LAUNCHED = new AtomicBoolean(false); - private ClientLauncher() { + private ClientLauncher() + { } @LauncherAPI - public static boolean isLaunched() { + public static boolean isLaunched() + { return LAUNCHED.get(); } - public static String jvmProperty(String name, String value) { + public static String jvmProperty(String name, String value) + { return String.format("-D%s=%s", name, value); } @LauncherAPI public static Process launch(Path jvmDir, SignedObjectHolder jvmHDir, - SignedObjectHolder assetHDir, SignedObjectHolder clientHDir, - SignedObjectHolder profile, Params params, boolean pipeOutput) throws Throwable { + SignedObjectHolder assetHDir, SignedObjectHolder clientHDir, + SignedObjectHolder profile, Params params, boolean pipeOutput) throws Throwable + { // Write params file (instead of CLI; Mustdie32 API can't handle command line > 32767 chars) LogHelper.debug("Writing ClientLauncher params file"); Path paramsFile = Files.createTempFile("ClientLauncherParams", ".bin"); - try (HOutput output = new HOutput(IOHelper.newOutput(paramsFile))) { + try (HOutput output = new HOutput(IOHelper.newOutput(paramsFile))) + { params.write(output); profile.write(output); @@ -102,7 +93,8 @@ // Resolve java bin and set permissions LogHelper.debug("Resolving JVM binary"); Path javaBin = IOHelper.resolveJavaBin(jvmDir); - if (IOHelper.POSIX) { + if (IOHelper.POSIX) + { Files.setPosixFilePermissions(javaBin, BIN_POSIX_PERMISSIONS); } @@ -110,15 +102,18 @@ List args = new LinkedList<>(); args.add(javaBin.toString()); args.add(MAGICAL_INTEL_OPTION); - if (params.ram > 0 && params.ram <= JVMHelper.RAM) { + 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) { + 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.")) { + 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")); args.add(jvmProperty("os.version", "10.0")); @@ -142,7 +137,8 @@ env.put("_JAVA_OPTIONS", ""); env.put("JAVA_OPTS", ""); env.put("JAVA_OPTIONS", ""); - if (pipeOutput) { + if (pipeOutput) + { builder.redirectErrorStream(true); builder.redirectOutput(Redirect.PIPE); } @@ -152,7 +148,8 @@ } @LauncherAPI - public static void main(String... args) throws Throwable { + public static void main(String... args) throws Throwable + { SecurityHelper.verifyCertificates(ClientLauncher.class); JVMHelper.verifySystemProperties(ClientLauncher.class, true); LogHelper.printVersion("Client Launcher"); @@ -167,7 +164,8 @@ SignedObjectHolder profile; SignedObjectHolder jvmHDir, assetHDir, clientHDir; RSAPublicKey publicKey = Launcher.getConfig().publicKey; - try (HInput input = new HInput(IOHelper.newInput(paramsFile))) { + try (HInput input = new HInput(IOHelper.newInput(paramsFile))) + { params = new Params(input); profile = new SignedObjectHolder<>(input, publicKey, ClientProfile.RO_ADAPTER); @@ -175,7 +173,9 @@ jvmHDir = new SignedObjectHolder<>(input, publicKey, HashedDir::new); assetHDir = new SignedObjectHolder<>(input, publicKey, HashedDir::new); clientHDir = new SignedObjectHolder<>(input, publicKey, HashedDir::new); - } finally { + } + finally + { Files.delete(paramsFile); } @@ -183,9 +183,11 @@ LogHelper.debug("Verifying ClientLauncher sign and classpath"); SecurityHelper.verifySign(LauncherRequest.BINARY_PATH, params.launcherSign, publicKey); URL[] classpath = JVMHelper.getClassPath(); - for (URL classpathURL : classpath) { + for (URL classpathURL : classpath) + { Path file = Paths.get(classpathURL.toURI()); - if (!file.startsWith(IOHelper.JVM_DIR) && !file.equals(LauncherRequest.BINARY_PATH)) { + if (!file.startsWith(IOHelper.JVM_DIR) && !file.equals(LauncherRequest.BINARY_PATH)) + { throw new SecurityException(String.format("Forbidden classpath entry: '%s'", file)); } } @@ -196,8 +198,9 @@ FileNameMatcher assetMatcher = profile.object.getAssetUpdateMatcher(); FileNameMatcher clientMatcher = profile.object.getClientUpdateMatcher(); try (DirWatcher jvmWatcher = new DirWatcher(IOHelper.JVM_DIR, jvmHDir.object, null, digest); // JVM Watcher - DirWatcher assetWatcher = new DirWatcher(params.assetDir, assetHDir.object, assetMatcher, digest); - DirWatcher clientWatcher = new DirWatcher(params.clientDir, clientHDir.object, clientMatcher, digest)) { + DirWatcher assetWatcher = new DirWatcher(params.assetDir, assetHDir.object, assetMatcher, digest); + DirWatcher clientWatcher = new DirWatcher(params.clientDir, clientHDir.object, clientMatcher, digest)) + { // Verify current state of all dirs verifyHDir(IOHelper.JVM_DIR, jvmHDir.object, null, digest); verifyHDir(params.assetDir, assetHDir.object, assetMatcher, digest); @@ -212,43 +215,52 @@ } @LauncherAPI - public static String toHash(UUID uuid) { + public static String toHash(UUID uuid) + { return UUID_PATTERN.matcher(uuid.toString()).replaceAll(""); } @LauncherAPI - public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException { - if (matcher != null) { + public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException + { + if (matcher != null) + { matcher = matcher.verifyOnly(); } // Hash directory and compare (ignore update-only matcher entries, it will break offline-mode) HashedDir currentHDir = new HashedDir(dir, matcher, false, digest); - if (!hdir.diff(currentHDir, matcher).isSame()) { + if (!hdir.diff(currentHDir, matcher).isSame()) + { throw new SecurityException(String.format("Forbidden modification: '%s'", IOHelper.getFileName(dir))); } } - private static void addClientArgs(Collection args, ClientProfile profile, Params params) { + private static void addClientArgs(Collection args, ClientProfile profile, Params params) + { PlayerProfile pp = params.pp; // Add version-dependent args Version version = profile.getVersion(); Collections.addAll(args, "--username", pp.username); - if (version.compareTo(Version.MC172) >= 0) { + if (version.compareTo(Version.MC172) >= 0) + { Collections.addAll(args, "--uuid", toHash(pp.uuid)); Collections.addAll(args, "--accessToken", params.accessToken); // Add 1.7.10+ args (user properties, asset index) - if (version.compareTo(Version.MC1710) >= 0) { + if (version.compareTo(Version.MC1710) >= 0) + { // Add user properties Collections.addAll(args, "--userType", "mojang"); JsonObject properties = Json.object(); - if (pp.skin != null) { + if (pp.skin != null) + { properties.add(SKIN_URL_PROPERTY, Json.array(pp.skin.url)); properties.add(SKIN_DIGEST_PROPERTY, Json.array(SecurityHelper.toHex(pp.skin.digest))); } - if (pp.cloak != null) { + if (pp.cloak != null) + { properties.add(CLOAK_URL_PROPERTY, Json.array(pp.cloak.url)); properties.add(CLOAK_DIGEST_PROPERTY, Json.array(SecurityHelper.toHex(pp.cloak.digest))); } @@ -257,7 +269,9 @@ // Add asset index Collections.addAll(args, "--assetIndex", profile.getAssetIndex()); } - } else { + } + else + { Collections.addAll(args, "--session", params.accessToken); } @@ -266,27 +280,32 @@ Collections.addAll(args, "--gameDir", params.clientDir.toString()); Collections.addAll(args, "--assetsDir", params.assetDir.toString()); Collections.addAll(args, "--resourcePackDir", params.clientDir.resolve(RESOURCEPACKS_DIR).toString()); - if (version.compareTo(Version.MC194) >= 0) { // Just to show it in debug screen + if (version.compareTo(Version.MC194) >= 0) + { // Just to show it in debug screen Collections.addAll(args, "--versionType", "KJ-Launcher v" + Launcher.VERSION); } // Add server args - if (params.autoEnter) { + if (params.autoEnter) + { Collections.addAll(args, "--server", profile.getServerAddress()); Collections.addAll(args, "--port", Integer.toString(profile.getServerPort())); } // Add window size args - if (params.fullScreen) { + if (params.fullScreen) + { Collections.addAll(args, "--fullscreen", Boolean.toString(true)); } - if (params.width > 0 && params.height > 0) { + if (params.width > 0 && params.height > 0) + { Collections.addAll(args, "--width", Integer.toString(params.width)); Collections.addAll(args, "--height", Integer.toString(params.height)); } } - private static void addClientLegacyArgs(Collection args, ClientProfile profile, Params params) { + private static void addClientLegacyArgs(Collection args, ClientProfile profile, Params params) + { args.add(params.pp.username); args.add(params.accessToken); @@ -296,15 +315,19 @@ Collections.addAll(args, "--assetsDir", params.assetDir.toString()); } - private static void launch(ClientProfile profile, Params params) throws Throwable { + private static void launch(ClientProfile profile, Params params) throws Throwable + { // Add natives path JVMHelper.addNativePath(params.clientDir.resolve(NATIVES_DIR)); // Add client args Collection args = new LinkedList<>(); - if (profile.getVersion().compareTo(Version.MC164) >= 0) { + if (profile.getVersion().compareTo(Version.MC164) >= 0) + { addClientArgs(args, profile, params); - } else { + } + else + { addClientLegacyArgs(args, profile, params); } Collections.addAll(args, profile.getClientArgs()); @@ -312,14 +335,15 @@ // Add client classpath URL[] classPath = resolveClassPath(params.clientDir, profile.getClassPath()); - for (URL url : classPath) { + for (URL url : classPath) + { JVMHelper.addClassPath(url); } // Resolve main class and method Class mainClass = Class.forName(profile.getMainClass()); MethodHandle mainMethod = JVMHelper.LOOKUP.findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)) - .asFixedArity(); + .asFixedArity(); // Invoke main method with exception wrapping LAUNCHED.set(true); @@ -330,11 +354,14 @@ mainMethod.invoke((Object) args.toArray(EMPTY_ARRAY)); } - private static URL[] resolveClassPath(Path clientDir, String... classPath) throws IOException { + private static URL[] resolveClassPath(Path clientDir, String... classPath) throws IOException + { Collection result = new LinkedList<>(); - for (String classPathEntry : classPath) { + for (String classPathEntry : classPath) + { Path path = clientDir.resolve(IOHelper.toPath(classPathEntry)); - if (IOHelper.isDir(path)) { // Recursive walking and adding + if (IOHelper.isDir(path)) + { // Recursive walking and adding IOHelper.walk(path, new ClassPathFileVisitor(result), false); continue; } @@ -343,24 +370,35 @@ return result.stream().map(IOHelper::toURL).toArray(URL[]::new); } - public static final class Params extends StreamObject { + public static final class Params extends StreamObject + { // Client paths - @LauncherAPI public final Path assetDir; - @LauncherAPI public final Path clientDir; + @LauncherAPI + public final Path assetDir; + @LauncherAPI + public final Path clientDir; // Client params - @LauncherAPI public final PlayerProfile pp; - @LauncherAPI public final String accessToken; - @LauncherAPI public final boolean autoEnter; - @LauncherAPI public final boolean fullScreen; - @LauncherAPI public final int ram; - @LauncherAPI public final int width; - @LauncherAPI public final int height; + @LauncherAPI + public final PlayerProfile pp; + @LauncherAPI + public final String accessToken; + @LauncherAPI + public final boolean autoEnter; + @LauncherAPI + public final boolean fullScreen; + @LauncherAPI + public final int ram; + @LauncherAPI + public final int width; + @LauncherAPI + public final int height; private final byte[] launcherSign; @LauncherAPI public Params(byte[] launcherSign, Path assetDir, Path clientDir, PlayerProfile pp, String accessToken, - boolean autoEnter, boolean fullScreen, int ram, int width, int height) { + boolean autoEnter, boolean fullScreen, int ram, int width, int height) + { this.launcherSign = launcherSign.clone(); // Client paths @@ -378,7 +416,8 @@ } @LauncherAPI - public Params(HInput input) throws IOException { + public Params(HInput input) throws IOException + { launcherSign = input.readByteArray(-SecurityHelper.RSA_KEY_LENGTH); // Client paths @@ -396,7 +435,8 @@ } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { output.writeByteArray(launcherSign, -SecurityHelper.RSA_KEY_LENGTH); // Client paths @@ -414,16 +454,20 @@ } } - private static final class ClassPathFileVisitor extends SimpleFileVisitor { + private static final class ClassPathFileVisitor extends SimpleFileVisitor + { private final Collection result; - private ClassPathFileVisitor(Collection result) { + private ClassPathFileVisitor(Collection result) + { this.result = result; } @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (IOHelper.hasExtension(file, "jar") || IOHelper.hasExtension(file, "zip")) { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException + { + if (IOHelper.hasExtension(file, "jar") || IOHelper.hasExtension(file, "zip")) + { result.add(file); } return super.visitFile(file, attrs); diff --git a/Launcher/source/client/ClientProfile.java b/Launcher/source/client/ClientProfile.java index 22df8d8..47ed39b 100644 --- a/Launcher/source/client/ClientProfile.java +++ b/Launcher/source/client/ClientProfile.java @@ -1,29 +1,27 @@ package launcher.client; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.util.HashMap; -import java.util.Map; - import launcher.LauncherAPI; import launcher.hasher.FileNameMatcher; import launcher.helper.IOHelper; import launcher.helper.VerifyHelper; 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.*; import launcher.serialize.config.entry.ConfigEntry.Type; -import launcher.serialize.config.entry.IntegerConfigEntry; -import launcher.serialize.config.entry.ListConfigEntry; -import launcher.serialize.config.entry.StringConfigEntry; import launcher.serialize.stream.StreamObject; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.Map; + @SuppressWarnings("ComparableImplementedButEqualsNotOverridden") -public final class ClientProfile extends ConfigObject implements Comparable { - @LauncherAPI public static final StreamObject.Adapter RO_ADAPTER = input -> new ClientProfile(input, true); +public final class ClientProfile extends ConfigObject implements Comparable +{ + @LauncherAPI + public static final StreamObject.Adapter RO_ADAPTER = input -> new ClientProfile(input, true); private static final FileNameMatcher ASSET_MATCHER = new FileNameMatcher( - new String[0], new String[] { "indexes", "objects" }, new String[0]); + new String[0], new String[]{"indexes", "objects"}, new String[0]); // Version private final StringConfigEntry version; @@ -48,7 +46,8 @@ private final ListConfigEntry clientArgs; @LauncherAPI - public ClientProfile(BlockConfigEntry block) { + public ClientProfile(BlockConfigEntry block) + { super(block); // Version @@ -75,42 +74,50 @@ } @LauncherAPI - public ClientProfile(HInput input, boolean ro) throws IOException { + public ClientProfile(HInput input, boolean ro) throws IOException + { this(new BlockConfigEntry(input, ro)); } @Override - public int compareTo(ClientProfile o) { + public int compareTo(ClientProfile o) + { return Integer.compare(getSortIndex(), o.getSortIndex()); } @Override - public String toString() { + public String toString() + { return title.getValue(); } @LauncherAPI - public String getAssetIndex() { + public String getAssetIndex() + { return assetIndex.getValue(); } @LauncherAPI - public FileNameMatcher getAssetUpdateMatcher() { + public FileNameMatcher getAssetUpdateMatcher() + { return getVersion().compareTo(Version.MC1710) >= 0 ? ASSET_MATCHER : null; } @LauncherAPI - public String[] getClassPath() { + public String[] getClassPath() + { return classPath.stream(StringConfigEntry.class).toArray(String[]::new); } @LauncherAPI - public String[] getClientArgs() { + public String[] getClientArgs() + { return clientArgs.stream(StringConfigEntry.class).toArray(String[]::new); } @LauncherAPI - public FileNameMatcher getClientUpdateMatcher() { + public FileNameMatcher getClientUpdateMatcher() + { String[] updateArray = update.stream(StringConfigEntry.class).toArray(String[]::new); String[] verifyArray = updateVerify.stream(StringConfigEntry.class).toArray(String[]::new); String[] exclusionsArray = updateExclusions.stream(StringConfigEntry.class).toArray(String[]::new); @@ -118,62 +125,74 @@ } @LauncherAPI - public String[] getJvmArgs() { + public String[] getJvmArgs() + { return jvmArgs.stream(StringConfigEntry.class).toArray(String[]::new); } @LauncherAPI - public String getMainClass() { + public String getMainClass() + { return mainClass.getValue(); } @LauncherAPI - public String getServerAddress() { + public String getServerAddress() + { return serverAddress.getValue(); } @LauncherAPI - public int getServerPort() { + public int getServerPort() + { return serverPort.getValue(); } @LauncherAPI - public InetSocketAddress getServerSocketAddress() { + public InetSocketAddress getServerSocketAddress() + { return InetSocketAddress.createUnresolved(getServerAddress(), getServerPort()); } @LauncherAPI - public int getSortIndex() { + public int getSortIndex() + { return sortIndex.getValue(); } @LauncherAPI - public String getTitle() { + public String getTitle() + { return title.getValue(); } @LauncherAPI - public void setTitle(String title) { + public void setTitle(String title) + { this.title.setValue(title); } @LauncherAPI - public Version getVersion() { + public Version getVersion() + { return Version.byName(version.getValue()); } @LauncherAPI - public void setVersion(Version version) { + public void setVersion(Version version) + { this.version.setValue(version.name); } @LauncherAPI - public boolean isUpdateFastCheck() { + public boolean isUpdateFastCheck() + { return updateFastCheck.getValue(); } @LauncherAPI - public void verify() { + public void verify() + { // Version getVersion(); IOHelper.verifyFileName(getAssetIndex()); @@ -196,7 +215,8 @@ } @LauncherAPI - public enum Version { + public enum Version + { MC147("1.4.7", 51), MC152("1.5.2", 61), MC164("1.6.4", 78), @@ -213,29 +233,35 @@ MC1160("1.16", 735), MC1161("1.16.1", 736); // Столько шума... private static final Map VERSIONS; + + static + { + Version[] versionsValues = values(); + VERSIONS = new HashMap<>(versionsValues.length); + for (Version version : versionsValues) + { + VERSIONS.put(version.name, version); + } + } + public final String name; public final int protocol; - Version(String name, int protocol) { + Version(String name, int protocol) + { this.name = name; this.protocol = protocol; } - @Override - public String toString() { - return "Minecraft " + name; - } - - public static Version byName(String name) { + public static Version byName(String name) + { return VerifyHelper.getMapValue(VERSIONS, name, String.format("Unknown client version: '%s'", name)); } - static { - Version[] versionsValues = values(); - VERSIONS = new HashMap<>(versionsValues.length); - for (Version version : versionsValues) { - VERSIONS.put(version.name, version); - } + @Override + public String toString() + { + return "Minecraft " + name; } } } diff --git a/Launcher/source/client/PlayerProfile.java b/Launcher/source/client/PlayerProfile.java index b6629f9..1d6d2dc 100644 --- a/Launcher/source/client/PlayerProfile.java +++ b/Launcher/source/client/PlayerProfile.java @@ -1,12 +1,5 @@ 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; - import launcher.LauncherAPI; import launcher.helper.IOHelper; import launcher.helper.SecurityHelper; @@ -16,13 +9,25 @@ import launcher.serialize.HOutput; import launcher.serialize.stream.StreamObject; -public final class PlayerProfile extends StreamObject { - @LauncherAPI public final UUID uuid; - @LauncherAPI public final String username; - @LauncherAPI public final Texture skin, cloak; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Objects; +import java.util.UUID; + +public final class PlayerProfile extends StreamObject +{ + @LauncherAPI + public final UUID uuid; + @LauncherAPI + public final String username; + @LauncherAPI + public final Texture skin, cloak; @LauncherAPI - public PlayerProfile(HInput input) throws IOException { + public PlayerProfile(HInput input) throws IOException + { uuid = input.readUUID(); username = VerifyHelper.verifyUsername(input.readString(64)); skin = input.readBoolean() ? new Texture(input) : null; @@ -30,62 +35,75 @@ } @LauncherAPI - public PlayerProfile(UUID uuid, String username, Texture skin, Texture cloak) { + public PlayerProfile(UUID uuid, String username, Texture skin, Texture cloak) + { this.uuid = Objects.requireNonNull(uuid, "uuid"); this.username = VerifyHelper.verifyUsername(username); this.skin = skin; this.cloak = cloak; } + @LauncherAPI + public static PlayerProfile newOfflineProfile(String username) + { + return new PlayerProfile(offlineUUID(username), username, null, null); + } + + @LauncherAPI + public static UUID offlineUUID(String username) + { + return UUID.nameUUIDFromBytes(IOHelper.encodeASCII("OfflinePlayer:" + username)); + } + @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { output.writeUUID(uuid); output.writeString(username, 64); // Write textures output.writeBoolean(skin != null); - if (skin != null) { + if (skin != null) + { skin.write(output); } output.writeBoolean(cloak != null); - if (cloak != null) { + if (cloak != null) + { cloak.write(output); } } - @LauncherAPI - public static PlayerProfile newOfflineProfile(String username) { - return new PlayerProfile(offlineUUID(username), username, null, null); - } - - @LauncherAPI - public static UUID offlineUUID(String username) { - return UUID.nameUUIDFromBytes(IOHelper.encodeASCII("OfflinePlayer:" + username)); - } - - public static final class Texture extends StreamObject { + 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; + @LauncherAPI + public final String url; + @LauncherAPI + public final byte[] digest; @LauncherAPI - public Texture(String url, byte[] digest) { + public Texture(String url, byte[] digest) + { this.url = IOHelper.verifyURL(url); this.digest = Objects.requireNonNull(digest, "digest"); } @LauncherAPI - public Texture(String url, boolean cloak) throws IOException { + public Texture(String url, boolean cloak) throws IOException + { this.url = IOHelper.verifyURL(url); // Fetch texture byte[] texture; - try (InputStream input = IOHelper.newInput(new URL(url))) { + try (InputStream input = IOHelper.newInput(new URL(url))) + { texture = IOHelper.read(input); } - try (ByteArrayInputStream input = new ByteArrayInputStream(texture)) { + try (ByteArrayInputStream input = new ByteArrayInputStream(texture)) + { IOHelper.readTexture(input, cloak); // Verify texture } @@ -94,13 +112,15 @@ } @LauncherAPI - public Texture(HInput input) throws IOException { + public Texture(HInput input) throws IOException + { url = IOHelper.verifyURL(input.readASCII(2048)); digest = input.readByteArray(-DIGEST_ALGO.bytes); } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { output.writeASCII(url, 2048); output.writeByteArray(digest, -DIGEST_ALGO.bytes); } diff --git a/Launcher/source/client/ServerPinger.java b/Launcher/source/client/ServerPinger.java index 99d4d50..0eb27de 100644 --- a/Launcher/source/client/ServerPinger.java +++ b/Launcher/source/client/ServerPinger.java @@ -1,13 +1,5 @@ package launcher.client; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.util.Objects; -import java.util.regex.Pattern; - import com.eclipsesource.json.Json; import com.eclipsesource.json.JsonObject; import launcher.LauncherAPI; @@ -19,7 +11,16 @@ import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class ServerPinger { +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.regex.Pattern; + +public final class ServerPinger +{ // Constants private static final String LEGACY_PING_HOST_MAGIC = "§1"; private static final String LEGACY_PING_HOST_CHANNEL = "MC|PingHost"; @@ -37,29 +38,52 @@ private long cacheUntil = Long.MIN_VALUE; @LauncherAPI - public ServerPinger(InetSocketAddress address, Version version) { + public ServerPinger(InetSocketAddress address, Version version) + { this.address = Objects.requireNonNull(address, "address"); this.version = Objects.requireNonNull(version, "version"); } + private static String readUTF16String(HInput input) throws IOException + { + int length = input.readUnsignedShort() << 1; + byte[] encoded = input.readByteArray(-length); + return new String(encoded, StandardCharsets.UTF_16BE); + } + + private static void writeUTF16String(HOutput output, String s) throws IOException + { + output.writeShort((short) s.length()); + output.stream.write(s.getBytes(StandardCharsets.UTF_16BE)); + } + @LauncherAPI - public Result ping() { - synchronized (cacheLock) { + public Result ping() + { + synchronized (cacheLock) + { // Update ping cache - if (System.currentTimeMillis() >= cacheUntil) { - try { + if (System.currentTimeMillis() >= cacheUntil) + { + try + { cache = doPing(); cacheError = null; - } catch (Throwable exc) { + } + catch (Throwable exc) + { cache = null; cacheError = exc; - } finally { + } + finally + { cacheUntil = System.currentTimeMillis() + IOHelper.SOCKET_TIMEOUT; } } // Verify is result available - if (cacheError != null) { + if (cacheError != null) + { JVMHelper.UNSAFE.throwException(cacheError); } @@ -68,27 +92,34 @@ } } - private Result doPing() throws IOException { - try (Socket socket = IOHelper.newSocket()) { + private Result doPing() throws IOException + { + try (Socket socket = IOHelper.newSocket()) + { socket.connect(IOHelper.resolve(address), IOHelper.SOCKET_TIMEOUT); try (HInput input = new HInput(socket.getInputStream()); - HOutput output = new HOutput(socket.getOutputStream())) { + HOutput output = new HOutput(socket.getOutputStream())) + { return version.compareTo(Version.MC172) >= 0 ? modernPing(input, output) : legacyPing(input, output, version.compareTo(Version.MC164) >= 0); } } } - private Result legacyPing(HInput input, HOutput output, boolean mc16) throws IOException { + private Result legacyPing(HInput input, HOutput output, boolean mc16) throws IOException + { output.writeUnsignedByte(0xFE); // 254 packet ID, Server list ping output.writeUnsignedByte(0x01); // Server ping payload - if (mc16) { + if (mc16) + { output.writeUnsignedByte(0xFA); // 250 packet ID, Custom payload writeUTF16String(output, LEGACY_PING_HOST_CHANNEL); // Custom payload name // Prepare custom payload packet byte[] customPayloadPacket; - try (ByteArrayOutputStream packetArray = IOHelper.newByteArrayOutput()) { - try (HOutput packetOutput = new HOutput(packetArray)) { + try (ByteArrayOutputStream packetArray = IOHelper.newByteArrayOutput()) + { + try (HOutput packetOutput = new HOutput(packetArray)) + { packetOutput.writeUnsignedByte(version.protocol); // Protocol version writeUTF16String(packetOutput, address.getHostString()); // Server address packetOutput.writeInt(address.getPort()); // Server port @@ -104,7 +135,8 @@ // Raed kick (response) packet int kickPacketID = input.readUnsignedByte(); - if (kickPacketID != 0xFF) { + if (kickPacketID != 0xFF) + { throw new IOException("Illegal kick packet ID: " + kickPacketID); } @@ -112,37 +144,44 @@ String response = readUTF16String(input); LogHelper.debug("Ping response (legacy): '%s'", response); String[] splitted = LEGACY_PING_HOST_DELIMETER.split(response); - if (splitted.length != 6) { + if (splitted.length != 6) + { throw new IOException("Tokens count mismatch"); } // Verify all parts String magic = splitted[0]; - if (!magic.equals(LEGACY_PING_HOST_MAGIC)) { + if (!magic.equals(LEGACY_PING_HOST_MAGIC)) + { throw new IOException("Magic string mismatch: " + magic); } int protocol = Integer.parseInt(splitted[1]); - if (protocol != version.protocol) { + if (protocol != version.protocol) + { throw new IOException("Protocol mismatch: " + protocol); } String clientVersion = splitted[2]; - if (!clientVersion.equals(version.name)) { + if (!clientVersion.equals(version.name)) + { throw new IOException(String.format("Version mismatch: '%s'", clientVersion)); } int onlinePlayers = VerifyHelper.verifyInt(Integer.parseInt(splitted[4]), - VerifyHelper.NOT_NEGATIVE, "onlinePlayers can't be < 0"); + VerifyHelper.NOT_NEGATIVE, "onlinePlayers can't be < 0"); int maxPlayers = VerifyHelper.verifyInt(Integer.parseInt(splitted[5]), - VerifyHelper.NOT_NEGATIVE, "maxPlayers can't be < 0"); + VerifyHelper.NOT_NEGATIVE, "maxPlayers can't be < 0"); // Return ping status return new Result(onlinePlayers, maxPlayers, response); } - private Result modernPing(HInput input, HOutput output) throws IOException { + private Result modernPing(HInput input, HOutput output) throws IOException + { // Prepare handshake packet byte[] handshakePacket; - try (ByteArrayOutputStream packetArray = IOHelper.newByteArrayOutput()) { - try (HOutput packetOutput = new HOutput(packetArray)) { + try (ByteArrayOutputStream packetArray = IOHelper.newByteArrayOutput()) + { + try (HOutput packetOutput = new HOutput(packetArray)) + { packetOutput.writeVarInt(0x0); // Handshake packet ID packetOutput.writeVarInt(version.protocol); // Protocol version packetOutput.writeString(address.getHostString(), 0); // Server address @@ -162,16 +201,19 @@ // ab is a dirty fix for some servers (noticed KCauldron 1.7.10) int ab = 0; - while (ab <= 0) { + while (ab <= 0) + { ab = IOHelper.verifyLength(input.readVarInt(), PACKET_LENGTH); } // Read outer status response packet ID String response; byte[] statusPacket = input.readByteArray(-ab); - try (HInput packetInput = new HInput(statusPacket)) { + try (HInput packetInput = new HInput(statusPacket)) + { int statusPacketID = packetInput.readVarInt(); - if (statusPacketID != 0x0) { + if (statusPacketID != 0x0) + { throw new IOException("Illegal status packet ID: " + statusPacketID); } response = packetInput.readString(PACKET_LENGTH); @@ -188,32 +230,27 @@ return new Result(online, max, response); } - private static String readUTF16String(HInput input) throws IOException { - int length = input.readUnsignedShort() << 1; - byte[] encoded = input.readByteArray(-length); - return new String(encoded, StandardCharsets.UTF_16BE); - } + public static final class Result + { + @LauncherAPI + public final int onlinePlayers; + @LauncherAPI + public final int maxPlayers; + @LauncherAPI + public final String raw; - private static void writeUTF16String(HOutput output, String s) throws IOException { - output.writeShort((short) s.length()); - output.stream.write(s.getBytes(StandardCharsets.UTF_16BE)); - } - - public static final class Result { - @LauncherAPI public final int onlinePlayers; - @LauncherAPI public final int maxPlayers; - @LauncherAPI public final String raw; - - public Result(int onlinePlayers, int maxPlayers, String raw) { + public Result(int onlinePlayers, int maxPlayers, String raw) + { this.onlinePlayers = VerifyHelper.verifyInt(onlinePlayers, - VerifyHelper.NOT_NEGATIVE, "onlinePlayers can't be < 0"); + VerifyHelper.NOT_NEGATIVE, "onlinePlayers can't be < 0"); this.maxPlayers = VerifyHelper.verifyInt(maxPlayers, - VerifyHelper.NOT_NEGATIVE, "maxPlayers can't be < 0"); + VerifyHelper.NOT_NEGATIVE, "maxPlayers can't be < 0"); this.raw = raw; } @LauncherAPI - public boolean isOverfilled() { + public boolean isOverfilled() + { return onlinePlayers >= maxPlayers; } } diff --git a/Launcher/source/hasher/DirWatcher.java b/Launcher/source/hasher/DirWatcher.java index ef0d9e5..fea22ce 100644 --- a/Launcher/source/hasher/DirWatcher.java +++ b/Launcher/source/hasher/DirWatcher.java @@ -1,21 +1,5 @@ package launcher.hasher; -import java.io.IOException; -import java.nio.file.ClosedWatchServiceException; -import java.nio.file.FileVisitResult; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardWatchEventKinds; -import java.nio.file.WatchEvent; -import java.nio.file.WatchEvent.Kind; -import java.nio.file.WatchEvent.Modifier; -import java.nio.file.WatchKey; -import java.nio.file.WatchService; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Deque; -import java.util.LinkedList; -import java.util.Objects; - import com.sun.nio.file.ExtendedWatchEventModifier; import com.sun.nio.file.SensitivityWatchEventModifier; import launcher.LauncherAPI; @@ -25,18 +9,28 @@ import launcher.helper.JVMHelper.OS; import launcher.helper.LogHelper; -public final class DirWatcher implements Runnable, AutoCloseable { +import java.io.IOException; +import java.nio.file.*; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchEvent.Modifier; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Deque; +import java.util.LinkedList; +import java.util.Objects; + +public final class DirWatcher implements Runnable, AutoCloseable +{ private static final boolean FILE_TREE_SUPPORTED = JVMHelper.OS_TYPE == OS.MUSTDIE; // Constants private static final Modifier[] MODIFIERS = { - SensitivityWatchEventModifier.HIGH + SensitivityWatchEventModifier.HIGH }; private static final Modifier[] FILE_TREE_MODIFIERS = { - ExtendedWatchEventModifier.FILE_TREE, SensitivityWatchEventModifier.HIGH + ExtendedWatchEventModifier.FILE_TREE, SensitivityWatchEventModifier.HIGH }; private static final Kind[] KINDS = { - StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE + StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE }; // Instance @@ -47,7 +41,8 @@ private final boolean digest; @LauncherAPI - public DirWatcher(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException { + public DirWatcher(Path dir, HashedDir hdir, FileNameMatcher matcher, boolean digest) throws IOException + { this.dir = Objects.requireNonNull(dir, "dir"); this.hdir = Objects.requireNonNull(hdir, "hdir"); this.matcher = matcher; @@ -55,7 +50,8 @@ service = dir.getFileSystem().newWatchService(); // Use FILE_TREE if supported - if (FILE_TREE_SUPPORTED) { + if (FILE_TREE_SUPPORTED) + { dir.register(service, KINDS, FILE_TREE_MODIFIERS); return; } @@ -64,30 +60,57 @@ IOHelper.walk(dir, new RegisterFileVisitor(), true); } + private static void handleError(Throwable e) + { + LogHelper.error(e); + JVMHelper.halt0(0x0BADFEE1); + } + + private static Deque toPath(Iterable path) + { + Deque result = new LinkedList<>(); + for (Path pe : path) + { + result.add(pe.toString()); + } + return result; + } + @Override @LauncherAPI - public void close() throws IOException { + public void close() throws IOException + { service.close(); } @Override @LauncherAPI - public void run() { - try { + public void run() + { + try + { processLoop(); - } catch (InterruptedException | ClosedWatchServiceException ignored) { + } + catch (InterruptedException | ClosedWatchServiceException ignored) + { // Do nothing (closed etc) - } catch (Throwable exc) { + } + catch (Throwable exc) + { handleError(exc); } } - private void processKey(WatchKey key) throws IOException { + private void processKey(WatchKey key) throws IOException + { Path watchDir = (Path) key.watchable(); - for (WatchEvent event : key.pollEvents()) { + for (WatchEvent event : key.pollEvents()) + { Kind kind = event.kind(); - if (kind.equals(StandardWatchEventKinds.OVERFLOW)) { - if (Boolean.getBoolean("launcher.dirwatcher.ignoreOverflows")) { + if (kind.equals(StandardWatchEventKinds.OVERFLOW)) + { + if (Boolean.getBoolean("launcher.dirwatcher.ignoreOverflows")) + { continue; // Sometimes it's better to ignore than interrupt fair playing } throw new IOException("Overflow"); @@ -96,14 +119,17 @@ // Resolve paths and verify is not exclusion Path path = watchDir.resolve((Path) event.context()); Deque stringPath = toPath(dir.relativize(path)); - if (matcher != null && !matcher.shouldVerify(stringPath)) { + if (matcher != null && !matcher.shouldVerify(stringPath)) + { continue; // Exclusion; should not be verified } // Verify is REALLY modified (not just attributes) - if (kind.equals(StandardWatchEventKinds.ENTRY_MODIFY)) { + if (kind.equals(StandardWatchEventKinds.ENTRY_MODIFY)) + { HashedEntry entry = hdir.resolve(stringPath); - if (entry != null && (entry.getType() != Type.FILE || ((HashedFile) entry).isSame(path, digest))) { + if (entry != null && (entry.getType() != Type.FILE || ((HashedFile) entry).isSame(path, digest))) + { continue; // Modified attributes, not need to worry :D } } @@ -114,48 +140,43 @@ key.reset(); } - private void processLoop() throws IOException, InterruptedException { - while (!Thread.interrupted()) { + private void processLoop() throws IOException, InterruptedException + { + while (!Thread.interrupted()) + { processKey(service.take()); } } - private static void handleError(Throwable e) { - LogHelper.error(e); - JVMHelper.halt0(0x0BADFEE1); - } - - private static Deque toPath(Iterable path) { - Deque result = new LinkedList<>(); - for (Path pe : path) { - result.add(pe.toString()); - } - return result; - } - - private final class RegisterFileVisitor extends SimpleFileVisitor { + private final class RegisterFileVisitor extends SimpleFileVisitor + { private final Deque path = new LinkedList<>(); @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException + { FileVisitResult result = super.postVisitDirectory(dir, exc); - if (!DirWatcher.this.dir.equals(dir)) { + if (!DirWatcher.this.dir.equals(dir)) + { path.removeLast(); } return result; } @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException + { FileVisitResult result = super.preVisitDirectory(dir, attrs); - if (DirWatcher.this.dir.equals(dir)) { + if (DirWatcher.this.dir.equals(dir)) + { dir.register(service, KINDS, MODIFIERS); return result; } // Maybe it's unnecessary to go deeper path.add(IOHelper.getFileName(dir)); - if (matcher != null && !matcher.shouldVerify(path)) { + if (matcher != null && !matcher.shouldVerify(path)) + { return FileVisitResult.SKIP_SUBTREE; } diff --git a/Launcher/source/hasher/FileNameMatcher.java b/Launcher/source/hasher/FileNameMatcher.java index f0a2db4..92f11d8 100644 --- a/Launcher/source/hasher/FileNameMatcher.java +++ b/Launcher/source/hasher/FileNameMatcher.java @@ -1,14 +1,15 @@ package launcher.hasher; +import launcher.LauncherAPI; +import launcher.helper.IOHelper; + import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.regex.Pattern; -import launcher.LauncherAPI; -import launcher.helper.IOHelper; - -public final class FileNameMatcher { +public final class FileNameMatcher +{ private static final Entry[] NO_ENTRIES = new Entry[0]; // Instance @@ -17,59 +18,72 @@ private final Entry[] exclusions; @LauncherAPI - public FileNameMatcher(String[] update, String[] verify, String[] exclusions) { + public FileNameMatcher(String[] update, String[] verify, String[] exclusions) + { this.update = toEntries(update); this.verify = toEntries(verify); this.exclusions = toEntries(exclusions); } - private FileNameMatcher(Entry[] update, Entry[] verify, Entry[] exclusions) { + private FileNameMatcher(Entry[] update, Entry[] verify, Entry[] exclusions) + { this.update = update; this.verify = verify; this.exclusions = exclusions; } + private static boolean anyMatch(Entry[] entries, Collection path) + { + return Arrays.stream(entries).anyMatch(e -> e.matches(path)); + } + + private static Entry[] toEntries(String... entries) + { + return Arrays.stream(entries).map(Entry::new).toArray(Entry[]::new); + } + @LauncherAPI - public boolean shouldUpdate(Collection path) { + public boolean shouldUpdate(Collection path) + { return (anyMatch(update, path) || anyMatch(verify, path)) && !anyMatch(exclusions, path); } @LauncherAPI - public boolean shouldVerify(Collection path) { + public boolean shouldVerify(Collection path) + { return anyMatch(verify, path) && !anyMatch(exclusions, path); } @LauncherAPI - public FileNameMatcher verifyOnly() { + public FileNameMatcher verifyOnly() + { return new FileNameMatcher(NO_ENTRIES, verify, exclusions); } - private static boolean anyMatch(Entry[] entries, Collection path) { - return Arrays.stream(entries).anyMatch(e -> e.matches(path)); - } - - private static Entry[] toEntries(String... entries) { - return Arrays.stream(entries).map(Entry::new).toArray(Entry[]::new); - } - - private static final class Entry { + private static final class Entry + { private static final Pattern SPLITTER = Pattern.compile(Pattern.quote(IOHelper.CROSS_SEPARATOR) + '+'); private final Pattern[] parts; - private Entry(CharSequence exclusion) { + private Entry(CharSequence exclusion) + { parts = SPLITTER.splitAsStream(exclusion).map(Pattern::compile).toArray(Pattern[]::new); } - private boolean matches(Collection path) { - if (parts.length > path.size()) { + private boolean matches(Collection path) + { + if (parts.length > path.size()) + { return false; } // Verify path parts Iterator iterator = path.iterator(); - for (Pattern patternPart : parts) { + for (Pattern patternPart : parts) + { String pathPart = iterator.next(); - if (!patternPart.matcher(pathPart).matches()) { + if (!patternPart.matcher(pathPart).matches()) + { return false; } } diff --git a/Launcher/source/hasher/HashedDir.java b/Launcher/source/hasher/HashedDir.java index db47235..14e62b5 100644 --- a/Launcher/source/hasher/HashedDir.java +++ b/Launcher/source/hasher/HashedDir.java @@ -1,18 +1,5 @@ package launcher.hasher; -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - import launcher.LauncherAPI; import launcher.helper.IOHelper; import launcher.helper.VerifyHelper; @@ -20,28 +7,42 @@ import launcher.serialize.HOutput; import launcher.serialize.stream.EnumSerializer; -public final class HashedDir extends HashedEntry { +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; +import java.util.Map.Entry; + +public final class HashedDir extends HashedEntry +{ private final Map map = new HashMap<>(32); @LauncherAPI - public HashedDir() { + public HashedDir() + { } @LauncherAPI - public HashedDir(Path dir, FileNameMatcher matcher, boolean allowSymlinks, boolean digest) throws IOException { + public HashedDir(Path dir, FileNameMatcher matcher, boolean allowSymlinks, boolean digest) throws IOException + { IOHelper.walk(dir, new HashFileVisitor(dir, matcher, allowSymlinks, digest), true); } @LauncherAPI - public HashedDir(HInput input) throws IOException { + public HashedDir(HInput input) throws IOException + { int entriesCount = input.readLength(0); - for (int i = 0; i < entriesCount; i++) { + for (int i = 0; i < entriesCount; i++) + { String name = IOHelper.verifyFileName(input.readString(255)); // Read entry HashedEntry entry; Type type = Type.read(input); - switch (type) { + switch (type) + { case FILE: entry = new HashedFile(input); break; @@ -58,20 +59,24 @@ } @Override - public Type getType() { + public Type getType() + { return Type.DIR; } @Override - public long size() { + public long size() + { return map.values().stream().mapToLong(HashedEntry::size).sum(); } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { Set> entries = map.entrySet(); output.writeLength(entries.size(), 0); - for (Entry mapEntry : entries) { + for (Entry mapEntry : entries) + { output.writeString(mapEntry.getKey(), 255); // Write hashed entry @@ -82,32 +87,39 @@ } @LauncherAPI - public Diff diff(HashedDir other, FileNameMatcher matcher) { + public Diff diff(HashedDir other, FileNameMatcher matcher) + { HashedDir mismatch = sideDiff(other, matcher, new LinkedList<>(), true); HashedDir extra = other.sideDiff(this, matcher, new LinkedList<>(), false); return new Diff(mismatch, extra); } @LauncherAPI - public HashedEntry getEntry(String name) { + public HashedEntry getEntry(String name) + { return map.get(name); } @LauncherAPI - public boolean isEmpty() { + public boolean isEmpty() + { return map.isEmpty(); } @LauncherAPI - public Map map() { + public Map map() + { return Collections.unmodifiableMap(map); } @LauncherAPI - public HashedEntry resolve(Iterable path) { + public HashedEntry resolve(Iterable path) + { HashedEntry current = this; - for (String pathEntry : path) { - if (current instanceof HashedDir) { + for (String pathEntry : path) + { + if (current instanceof HashedDir) + { current = ((HashedDir) current).map.get(pathEntry); continue; } @@ -116,9 +128,11 @@ return current; } - private HashedDir sideDiff(HashedDir other, FileNameMatcher matcher, Deque path, boolean mismatchList) { + private HashedDir sideDiff(HashedDir other, FileNameMatcher matcher, Deque path, boolean mismatchList) + { HashedDir diff = new HashedDir(); - for (Entry mapEntry : map.entrySet()) { + for (Entry mapEntry : map.entrySet()) + { String name = mapEntry.getKey(); HashedEntry entry = mapEntry.getValue(); path.add(name); @@ -129,12 +143,15 @@ // Not found or of different type Type type = entry.getType(); HashedEntry otherEntry = other.map.get(name); - if (otherEntry == null || otherEntry.getType() != type) { - if (shouldUpdate || mismatchList && otherEntry == null) { + if (otherEntry == null || otherEntry.getType() != type) + { + if (shouldUpdate || mismatchList && otherEntry == null) + { diff.map.put(name, entry); // Should be deleted! - if (!mismatchList) { + if (!mismatchList) + { entry.flag = true; } } @@ -143,20 +160,24 @@ } // Compare entries based on type - switch (type) { + switch (type) + { case FILE: HashedFile file = (HashedFile) entry; HashedFile otherFile = (HashedFile) otherEntry; - if (mismatchList && shouldUpdate && !file.isSame(otherFile)) { + if (mismatchList && shouldUpdate && !file.isSame(otherFile)) + { diff.map.put(name, entry); } break; case DIR: HashedDir dir = (HashedDir) entry; HashedDir otherDir = (HashedDir) otherEntry; - if (mismatchList || shouldUpdate) { // Maybe isn't need to go deeper? + if (mismatchList || shouldUpdate) + { // Maybe isn't need to go deeper? HashedDir mismatch = dir.sideDiff(otherDir, matcher, path, mismatchList); - if (!mismatch.isEmpty()) { + if (!mismatch.isEmpty()) + { diff.map.put(name, mismatch); } } @@ -171,28 +192,51 @@ return diff; } - private final class HashFileVisitor extends SimpleFileVisitor { + public static final class Diff + { + @LauncherAPI + public final HashedDir mismatch; + @LauncherAPI + public final HashedDir extra; + + private Diff(HashedDir mismatch, HashedDir extra) + { + this.mismatch = mismatch; + this.extra = extra; + } + + @LauncherAPI + public boolean isSame() + { + return mismatch.isEmpty() && extra.isEmpty(); + } + } + + private final class HashFileVisitor extends SimpleFileVisitor + { private final Path dir; private final FileNameMatcher matcher; private final boolean allowSymlinks; private final boolean digest; - - // State - private HashedDir current = HashedDir.this; private final Deque path = new LinkedList<>(); private final Deque stack = new LinkedList<>(); + // State + private HashedDir current = HashedDir.this; - private HashFileVisitor(Path dir, FileNameMatcher matcher, boolean allowSymlinks, boolean digest) { + private HashFileVisitor(Path dir, FileNameMatcher matcher, boolean allowSymlinks, boolean digest) + { this.dir = dir; this.matcher = matcher; this.allowSymlinks = allowSymlinks; - this.digest = digest; + this.digest = digest; } @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException + { FileVisitResult result = super.postVisitDirectory(dir, exc); - if (this.dir.equals(dir)) { + if (this.dir.equals(dir)) + { return result; } @@ -206,15 +250,18 @@ } @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException + { FileVisitResult result = super.preVisitDirectory(dir, attrs); - if (this.dir.equals(dir)) { + if (this.dir.equals(dir)) + { return result; } // Verify is not symlink // Symlinks was disallowed because modification of it's destination are ignored by DirWatcher - if (!allowSymlinks && attrs.isSymbolicLink()) { + if (!allowSymlinks && attrs.isSymbolicLink()) + { throw new SecurityException("Symlinks are not allowed"); } @@ -228,9 +275,11 @@ } @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException + { // Verify is not symlink - if (!allowSymlinks && attrs.isSymbolicLink()) { + if (!allowSymlinks && attrs.isSymbolicLink()) + { throw new SecurityException("Symlinks are not allowed"); } @@ -241,19 +290,4 @@ return super.visitFile(file, attrs); } } - - public static final class Diff { - @LauncherAPI public final HashedDir mismatch; - @LauncherAPI public final HashedDir extra; - - private Diff(HashedDir mismatch, HashedDir extra) { - this.mismatch = mismatch; - this.extra = extra; - } - - @LauncherAPI - public boolean isSame() { - return mismatch.isEmpty() && extra.isEmpty(); - } - } } diff --git a/Launcher/source/hasher/HashedEntry.java b/Launcher/source/hasher/HashedEntry.java index ddc7a88..0bba947 100644 --- a/Launcher/source/hasher/HashedEntry.java +++ b/Launcher/source/hasher/HashedEntry.java @@ -1,15 +1,17 @@ package launcher.hasher; -import java.io.IOException; - import launcher.LauncherAPI; import launcher.serialize.HInput; import launcher.serialize.stream.EnumSerializer; import launcher.serialize.stream.EnumSerializer.Itf; import launcher.serialize.stream.StreamObject; -public abstract class HashedEntry extends StreamObject { - @LauncherAPI public boolean flag; // For external usage +import java.io.IOException; + +public abstract class HashedEntry extends StreamObject +{ + @LauncherAPI + public boolean flag; // For external usage @LauncherAPI public abstract Type getType(); @@ -18,22 +20,26 @@ public abstract long size(); @LauncherAPI - public enum Type implements Itf { + public enum Type implements Itf + { DIR(1), FILE(2); private static final EnumSerializer SERIALIZER = new EnumSerializer<>(Type.class); private final int n; - Type(int n) { + Type(int n) + { this.n = n; } - @Override - public int getNumber() { - return n; + public static Type read(HInput input) throws IOException + { + return SERIALIZER.read(input); } - public static Type read(HInput input) throws IOException { - return SERIALIZER.read(input); + @Override + public int getNumber() + { + return n; } } } diff --git a/Launcher/source/hasher/HashedFile.java b/Launcher/source/hasher/HashedFile.java index 0becfe0..e1f5ce4 100644 --- a/Launcher/source/hasher/HashedFile.java +++ b/Launcher/source/hasher/HashedFile.java @@ -1,9 +1,5 @@ package launcher.hasher; -import java.io.IOException; -import java.nio.file.Path; -import java.util.Arrays; - import launcher.LauncherAPI; import launcher.helper.IOHelper; import launcher.helper.SecurityHelper; @@ -12,59 +8,76 @@ import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class HashedFile extends HashedEntry { +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; + +public final class HashedFile extends HashedEntry +{ public static final DigestAlgorithm DIGEST_ALGO = DigestAlgorithm.MD5; // Instance - @LauncherAPI public final long size; + @LauncherAPI + public final long size; private final byte[] digest; @LauncherAPI - public HashedFile(long size, byte[] digest) { + public HashedFile(long size, byte[] digest) + { this.size = VerifyHelper.verifyLong(size, VerifyHelper.L_NOT_NEGATIVE, "Illegal size: " + size); this.digest = digest == null ? null : DIGEST_ALGO.verify(digest).clone(); } @LauncherAPI - public HashedFile(Path file, long size, boolean digest) throws IOException { + public HashedFile(Path file, long size, boolean digest) throws IOException + { this(size, digest ? SecurityHelper.digest(DIGEST_ALGO, file) : null); } @LauncherAPI - public HashedFile(HInput input) throws IOException { + public HashedFile(HInput input) throws IOException + { this(input.readVarLong(), input.readBoolean() ? input.readByteArray(-DIGEST_ALGO.bytes) : null); } @Override - public Type getType() { + public Type getType() + { return Type.FILE; } @Override - public long size() { + public long size() + { return size; } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { output.writeVarLong(size); output.writeBoolean(digest != null); - if (digest != null) { + if (digest != null) + { output.writeByteArray(digest, -DIGEST_ALGO.bytes); } } @LauncherAPI - public boolean isSame(HashedFile o) { + public boolean isSame(HashedFile o) + { return size == o.size && (digest == null || o.digest == null || Arrays.equals(digest, o.digest)); } @LauncherAPI - public boolean isSame(Path file, boolean digest) throws IOException { - if (size != IOHelper.readAttributes(file).size()) { + public boolean isSame(Path file, boolean digest) throws IOException + { + if (size != IOHelper.readAttributes(file).size()) + { return false; } - if (!digest || this.digest == null) { + if (!digest || this.digest == null) + { return true; } @@ -74,7 +87,8 @@ } @LauncherAPI - public boolean isSameDigest(byte[] digest) { + public boolean isSameDigest(byte[] digest) + { return this.digest == null || digest == null || Arrays.equals(this.digest, digest); } } diff --git a/Launcher/source/helper/CommonHelper.java b/Launcher/source/helper/CommonHelper.java index e41e766..eb1dd87 100644 --- a/Launcher/source/helper/CommonHelper.java +++ b/Launcher/source/helper/CommonHelper.java @@ -1,40 +1,48 @@ package launcher.helper; -import java.util.Locale; -import javax.script.ScriptEngine; - import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import launcher.LauncherAPI; -public final class CommonHelper { - private static final String[] SCRIPT_ENGINE_ARGS = { "-strict", "--language=es6", "--optimistic-types=false" }; +import javax.script.ScriptEngine; +import java.util.Locale; - private CommonHelper() { +public final class CommonHelper +{ + private static final String[] SCRIPT_ENGINE_ARGS = {"-strict", "--language=es6", "--optimistic-types=false"}; + + private CommonHelper() + { } @LauncherAPI - public static String low(String s) { + public static String low(String s) + { return s.toLowerCase(Locale.US); } @LauncherAPI - public static ScriptEngine newScriptEngine() { + public static ScriptEngine newScriptEngine() + { return new NashornScriptEngineFactory().getScriptEngine(SCRIPT_ENGINE_ARGS); } @LauncherAPI - public static Thread newThread(String name, boolean daemon, Runnable runnable) { + public static Thread newThread(String name, boolean daemon, Runnable runnable) + { Thread thread = new Thread(runnable); thread.setDaemon(daemon); - if (name != null) { + if (name != null) + { thread.setName(name); } return thread; } @LauncherAPI - public static String replace(String source, String... params) { - for (int i = 0; i < params.length; i += 2) { + public static String replace(String source, String... params) + { + for (int i = 0; i < params.length; i += 2) + { source = source.replace('%' + params[i] + '%', params[i + 1]); } return source; diff --git a/Launcher/source/helper/IOHelper.java b/Launcher/source/helper/IOHelper.java index 60cfdeb..746d2ba 100644 --- a/Launcher/source/helper/IOHelper.java +++ b/Launcher/source/helper/IOHelper.java @@ -1,49 +1,17 @@ package launcher.helper; +import launcher.Launcher; +import launcher.LauncherAPI; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; import java.awt.image.BufferedImage; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.net.HttpURLConnection; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLDecoder; -import java.net.URLEncoder; +import java.io.*; +import java.net.*; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.nio.file.CopyOption; -import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.FileVisitOption; -import java.nio.file.FileVisitResult; -import java.nio.file.FileVisitor; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; +import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; import java.util.Set; @@ -53,211 +21,258 @@ import java.util.zip.Inflater; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import javax.imageio.ImageIO; -import javax.imageio.ImageReader; -import launcher.Launcher; -import launcher.LauncherAPI; - -public final class IOHelper { +public final class IOHelper +{ // Charset - @LauncherAPI public static final Charset UNICODE_CHARSET = StandardCharsets.UTF_8; - @LauncherAPI public static final Charset ASCII_CHARSET = StandardCharsets.US_ASCII; + @LauncherAPI + public static final Charset UNICODE_CHARSET = StandardCharsets.UTF_8; + @LauncherAPI + public static final Charset ASCII_CHARSET = StandardCharsets.US_ASCII; // Constants - @LauncherAPI public static final int SOCKET_TIMEOUT = VerifyHelper.verifyInt( - Integer.parseInt(System.getProperty("launcher.socketTimeout", Integer.toString(30000))), - VerifyHelper.POSITIVE, "launcher.socketTimeout can't be <= 0"); - @LauncherAPI public static final int HTTP_TIMEOUT = VerifyHelper.verifyInt( - Integer.parseInt(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.parseInt(System.getProperty("launcher.bufferSize", Integer.toString(4096))), - VerifyHelper.POSITIVE, "launcher.bufferSize can't be <= 0"); + @LauncherAPI + public static final int SOCKET_TIMEOUT = VerifyHelper.verifyInt( + Integer.parseInt(System.getProperty("launcher.socketTimeout", Integer.toString(30000))), + VerifyHelper.POSITIVE, "launcher.socketTimeout can't be <= 0"); + @LauncherAPI + public static final int HTTP_TIMEOUT = VerifyHelper.verifyInt( + Integer.parseInt(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.parseInt(System.getProperty("launcher.bufferSize", Integer.toString(4096))), + VerifyHelper.POSITIVE, "launcher.bufferSize can't be <= 0"); // Platform-dependent - @LauncherAPI public static final String CROSS_SEPARATOR = "/"; - @LauncherAPI public static final FileSystem FS = FileSystems.getDefault(); - @LauncherAPI public static final String PLATFORM_SEPARATOR = FS.getSeparator(); - @LauncherAPI public static final boolean POSIX = FS.supportedFileAttributeViews().contains("posix"); + @LauncherAPI + public static final String CROSS_SEPARATOR = "/"; + @LauncherAPI + public static final FileSystem FS = FileSystems.getDefault(); + @LauncherAPI + public static final String PLATFORM_SEPARATOR = FS.getSeparator(); + @LauncherAPI + public static final boolean POSIX = FS.supportedFileAttributeViews().contains("posix"); // Paths - @LauncherAPI public static final Path JVM_DIR = Paths.get(System.getProperty("java.home")); - @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")); + @LauncherAPI + public static final Path JVM_DIR = Paths.get(System.getProperty("java.home")); + @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")); // Open options - as arrays - private static final OpenOption[] READ_OPTIONS = { StandardOpenOption.READ }; - 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 }; + private static final OpenOption[] READ_OPTIONS = {StandardOpenOption.READ}; + 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 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() { + private IOHelper() + { } @LauncherAPI - public static void close(AutoCloseable closeable) { - try { + public static void close(AutoCloseable closeable) + { + try + { closeable.close(); - } catch (Throwable exc) { + } + catch (Throwable exc) + { LogHelper.error(exc); } } @LauncherAPI - public static void copy(Path source, Path target) throws IOException { + public static void copy(Path source, Path target) throws IOException + { createParentDirs(target); Files.copy(source, target, COPY_OPTIONS); } @LauncherAPI - public static void createParentDirs(Path path) throws IOException { + public static void createParentDirs(Path path) throws IOException + { Path parent = path.getParent(); - if (parent != null && !isDir(parent)) { + if (parent != null && !isDir(parent)) + { Files.createDirectories(parent); } } @LauncherAPI - public static String decode(byte[] bytes) { + public static String decode(byte[] bytes) + { return new String(bytes, UNICODE_CHARSET); } @LauncherAPI - public static String decodeASCII(byte[] bytes) { + public static String decodeASCII(byte[] bytes) + { return new String(bytes, ASCII_CHARSET); } @LauncherAPI - public static void deleteDir(Path dir, boolean self) throws IOException { + public static void deleteDir(Path dir, boolean self) throws IOException + { walk(dir, new DeleteDirVisitor(dir, self), true); } @LauncherAPI - public static byte[] encode(String s) { + public static byte[] encode(String s) + { return s.getBytes(UNICODE_CHARSET); } @LauncherAPI - public static byte[] encodeASCII(String s) { + public static byte[] encodeASCII(String s) + { return s.getBytes(ASCII_CHARSET); } @LauncherAPI - public static boolean exists(Path path) { + public static boolean exists(Path path) + { return Files.exists(path, LINK_OPTIONS); } @LauncherAPI - public static Path getCodeSource(Class clazz) { + public static Path getCodeSource(Class clazz) + { return Paths.get(toURI(clazz.getProtectionDomain().getCodeSource().getLocation())); } @LauncherAPI - public static String getFileName(Path path) { + public static String getFileName(Path path) + { return path.getFileName().toString(); } @LauncherAPI - public static String getIP(SocketAddress address) { + public static String getIP(SocketAddress address) + { return ((InetSocketAddress) address).getAddress().getHostAddress(); } @LauncherAPI - public static byte[] getResourceBytes(String name) throws IOException { + public static byte[] getResourceBytes(String name) throws IOException + { return read(getResourceURL(name)); } @LauncherAPI - public static URL getResourceURL(String name) throws NoSuchFileException { + public static URL getResourceURL(String name) throws NoSuchFileException + { URL url = Launcher.class.getResource('/' + name); - if (url == null) { + if (url == null) + { throw new NoSuchFileException(name); } return url; } @LauncherAPI - public static boolean hasExtension(Path file, String extension) { + public static boolean hasExtension(Path file, String extension) + { return getFileName(file).endsWith('.' + extension); } @LauncherAPI - public static boolean isDir(Path path) { + public static boolean isDir(Path path) + { return Files.isDirectory(path, LINK_OPTIONS); } @LauncherAPI - public static boolean isEmpty(Path dir) throws IOException { - try (DirectoryStream stream = Files.newDirectoryStream(dir)) { + public static boolean isEmpty(Path dir) throws IOException + { + try (DirectoryStream stream = Files.newDirectoryStream(dir)) + { return !stream.iterator().hasNext(); } } @LauncherAPI - public static boolean isFile(Path path) { + public static boolean isFile(Path path) + { return Files.isRegularFile(path, LINK_OPTIONS); } @LauncherAPI - public static boolean isValidFileName(String fileName) { + public static boolean isValidFileName(String fileName) + { return !fileName.equals(".") && !fileName.equals("..") && - fileName.chars().noneMatch(ch -> ch == '/' || ch == '\\') && isValidPath(fileName); + fileName.chars().noneMatch(ch -> ch == '/' || ch == '\\') && isValidPath(fileName); } @LauncherAPI - public static boolean isValidPath(String path) { - try { + public static boolean isValidPath(String path) + { + try + { toPath(path); return true; - } catch (InvalidPathException ignored) { + } + catch (InvalidPathException ignored) + { return false; } } @LauncherAPI - public static boolean isValidTextureBounds(int width, int height, boolean cloak) { + public static boolean isValidTextureBounds(int width, int height, boolean cloak) + { return width % 64 == 0 && (height << 1 == width || !cloak && height == width) && width <= 1024 || - cloak && width % 22 == 0 && height % 17 == 0 && width / 22 == height / 17; + cloak && width % 22 == 0 && height % 17 == 0 && width / 22 == height / 17; } @LauncherAPI - public static void move(Path source, Path target) throws IOException { + public static void move(Path source, Path target) throws IOException + { createParentDirs(target); Files.move(source, target, COPY_OPTIONS); } @LauncherAPI - public static byte[] newBuffer() { + public static byte[] newBuffer() + { return new byte[BUFFER_SIZE]; } @LauncherAPI - public static ByteArrayOutputStream newByteArrayOutput() { + public static ByteArrayOutputStream newByteArrayOutput() + { return new ByteArrayOutputStream(); } @LauncherAPI - public static char[] newCharBuffer() { + public static char[] newCharBuffer() + { return new char[BUFFER_SIZE]; } @LauncherAPI - public static URLConnection newConnection(URL url) throws IOException { + public static URLConnection newConnection(URL url) throws IOException + { URLConnection connection = url.openConnection(); - if (connection instanceof HttpURLConnection) { + if (connection instanceof HttpURLConnection) + { connection.setReadTimeout(HTTP_TIMEOUT); connection.setConnectTimeout(HTTP_TIMEOUT); connection.addRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"); // Fix for stupid servers - } else { + } + else + { connection.setUseCaches(false); } connection.setDoInput(true); @@ -266,7 +281,8 @@ } @LauncherAPI - public static HttpURLConnection newConnectionPost(URL url) throws IOException { + public static HttpURLConnection newConnectionPost(URL url) throws IOException + { HttpURLConnection connection = (HttpURLConnection) newConnection(url); connection.setDoOutput(true); connection.setRequestMethod("POST"); @@ -274,125 +290,148 @@ } @LauncherAPI - public static Deflater newDeflater() { + public static Deflater newDeflater() + { Deflater deflater = new Deflater(Deflater.DEFAULT_COMPRESSION, true); deflater.setStrategy(Deflater.DEFAULT_STRATEGY); return deflater; } @LauncherAPI - public static Inflater newInflater() { + public static Inflater newInflater() + { return new Inflater(true); } @LauncherAPI - public static InputStream newInput(URL url) throws IOException { + public static InputStream newInput(URL url) throws IOException + { return newConnection(url).getInputStream(); } @LauncherAPI - public static InputStream newInput(Path file) throws IOException { + public static InputStream newInput(Path file) throws IOException + { return Files.newInputStream(file, READ_OPTIONS); } @LauncherAPI - public static OutputStream newOutput(Path file) throws IOException { + public static OutputStream newOutput(Path file) throws IOException + { return newOutput(file, false); } @LauncherAPI - public static OutputStream newOutput(Path file, boolean append) throws IOException { + public static OutputStream newOutput(Path file, boolean append) throws IOException + { createParentDirs(file); return Files.newOutputStream(file, append ? APPEND_OPTIONS : WRITE_OPTIONS); } @LauncherAPI - public static BufferedReader newReader(InputStream input) { + public static BufferedReader newReader(InputStream input) + { return newReader(input, UNICODE_CHARSET); } @LauncherAPI - public static BufferedReader newReader(InputStream input, Charset charset) { + public static BufferedReader newReader(InputStream input, Charset charset) + { return new BufferedReader(new InputStreamReader(input, charset)); } @LauncherAPI - public static BufferedReader newReader(URL url) throws IOException { + public static BufferedReader newReader(URL url) throws IOException + { URLConnection connection = newConnection(url); String charset = connection.getContentEncoding(); return newReader(connection.getInputStream(), charset == null ? UNICODE_CHARSET : Charset.forName(charset)); } @LauncherAPI - public static BufferedReader newReader(Path file) throws IOException { + public static BufferedReader newReader(Path file) throws IOException + { return Files.newBufferedReader(file, UNICODE_CHARSET); } @LauncherAPI - public static Socket newSocket() throws SocketException { + public static Socket newSocket() throws SocketException + { Socket socket = new Socket(); setSocketFlags(socket); return socket; } @LauncherAPI - public static BufferedWriter newWriter(OutputStream output) { + public static BufferedWriter newWriter(OutputStream output) + { return new BufferedWriter(new OutputStreamWriter(output, UNICODE_CHARSET)); } @LauncherAPI - public static BufferedWriter newWriter(Path file) throws IOException { + public static BufferedWriter newWriter(Path file) throws IOException + { return newWriter(file, false); } @LauncherAPI - public static BufferedWriter newWriter(Path file, boolean append) throws IOException { + public static BufferedWriter newWriter(Path file, boolean append) throws IOException + { createParentDirs(file); return Files.newBufferedWriter(file, UNICODE_CHARSET, append ? APPEND_OPTIONS : WRITE_OPTIONS); } @LauncherAPI - public static BufferedWriter newWriter(FileDescriptor fd) { + public static BufferedWriter newWriter(FileDescriptor fd) + { return newWriter(new FileOutputStream(fd)); } @LauncherAPI - public static ZipEntry newZipEntry(String name) { + public static ZipEntry newZipEntry(String name) + { ZipEntry entry = new ZipEntry(name); entry.setTime(0); return entry; } @LauncherAPI - public static ZipEntry newZipEntry(ZipEntry entry) { + public static ZipEntry newZipEntry(ZipEntry entry) + { return newZipEntry(entry.getName()); } @LauncherAPI - public static ZipInputStream newZipInput(InputStream input) { + public static ZipInputStream newZipInput(InputStream input) + { return new ZipInputStream(input, UNICODE_CHARSET); } @LauncherAPI - public static ZipInputStream newZipInput(URL url) throws IOException { + public static ZipInputStream newZipInput(URL url) throws IOException + { return newZipInput(newInput(url)); } @LauncherAPI - public static ZipInputStream newZipInput(Path file) throws IOException { + public static ZipInputStream newZipInput(Path file) throws IOException + { return newZipInput(newInput(file)); } @LauncherAPI - public static byte[] read(Path file) throws IOException { + public static byte[] read(Path file) throws IOException + { long size = readAttributes(file).size(); - if (size > Integer.MAX_VALUE) { + if (size > Integer.MAX_VALUE) + { throw new IOException("File too big"); } // Read bytes from file byte[] bytes = new byte[(int) size]; - try (InputStream input = newInput(file)) { + try (InputStream input = newInput(file)) + { read(input, bytes); } @@ -401,18 +440,23 @@ } @LauncherAPI - public static byte[] read(URL url) throws IOException { - try (InputStream input = newInput(url)) { + public static byte[] read(URL url) throws IOException + { + try (InputStream input = newInput(url)) + { return read(input); } } @LauncherAPI - public static void read(InputStream input, byte[] bytes) throws IOException { + public static void read(InputStream input, byte[] bytes) throws IOException + { int offset = 0; - while (offset < bytes.length) { + while (offset < bytes.length) + { int length = input.read(bytes, offset, bytes.length - offset); - if (length < 0) { + if (length < 0) + { throw new EOFException(String.format("%d bytes remaining", bytes.length - offset)); } offset += length; @@ -420,63 +464,78 @@ } @LauncherAPI - public static byte[] read(InputStream input) throws IOException { - try (ByteArrayOutputStream output = newByteArrayOutput()) { + public static byte[] read(InputStream input) throws IOException + { + try (ByteArrayOutputStream output = newByteArrayOutput()) + { transfer(input, output); return output.toByteArray(); } } @LauncherAPI - public static BasicFileAttributes readAttributes(Path path) throws IOException { + public static BasicFileAttributes readAttributes(Path path) throws IOException + { return Files.readAttributes(path, BasicFileAttributes.class, LINK_OPTIONS); } @LauncherAPI - public static BufferedImage readTexture(Object input, boolean cloak) throws IOException { + public static BufferedImage readTexture(Object input, boolean cloak) throws IOException + { ImageReader reader = ImageIO.getImageReadersByMIMEType("image/png").next(); - try { + try + { reader.setInput(ImageIO.createImageInputStream(input), false, false); // Verify texture bounds int width = reader.getWidth(0); int height = reader.getHeight(0); - if (!isValidTextureBounds(width, height, cloak)) { + if (!isValidTextureBounds(width, height, cloak)) + { throw new IOException(String.format("Invalid texture bounds: %dx%d", width, height)); } // Read image return reader.read(0); - } finally { + } + finally + { reader.dispose(); } } @LauncherAPI - public static String request(URL url) throws IOException { + public static String request(URL url) throws IOException + { return decode(read(url)).trim(); } @LauncherAPI - public static InetSocketAddress resolve(InetSocketAddress address) { - if (address.isUnresolved()) { // Create resolved address + public static InetSocketAddress resolve(InetSocketAddress address) + { + if (address.isUnresolved()) + { // Create resolved address return new InetSocketAddress(address.getHostString(), address.getPort()); } return address; } @LauncherAPI - public static Path resolveIncremental(Path dir, String name, String extension) { + public static Path resolveIncremental(Path dir, String name, String extension) + { Path original = dir.resolve(name + '.' + extension); - if (!exists(original)) { // Not need to increment + if (!exists(original)) + { // Not need to increment return original; } // Incremental resolve int counter = 1; - while (true) { + while (true) + { Path path = dir.resolve(String.format("%s (%d).%s", name, counter, extension)); - if (exists(path)) { + if (exists(path)) + { counter++; continue; } @@ -485,27 +544,32 @@ } @LauncherAPI - public static Path resolveJavaBin(Path javaDir) { + public static Path resolveJavaBin(Path javaDir) + { // Get Java binaries path Path javaBinDir = (javaDir == null ? JVM_DIR : javaDir).resolve("bin"); // Verify has "javaw.exe" file - if (!LogHelper.isDebugEnabled()) { + if (!LogHelper.isDebugEnabled()) + { Path javawExe = javaBinDir.resolve("javaw.exe"); - if (isFile(javawExe)) { + if (isFile(javawExe)) + { return javawExe; } } // Verify has "java.exe" file Path javaExe = javaBinDir.resolve("java.exe"); - if (isFile(javaExe)) { + if (isFile(javaExe)) + { return javaExe; } // Verify has "java" file Path java = javaBinDir.resolve("java"); - if (isFile(java)) { + if (isFile(java)) + { return java; } @@ -514,7 +578,8 @@ } @LauncherAPI - public static void setSocketFlags(Socket socket) throws SocketException { + public static void setSocketFlags(Socket socket) throws SocketException + { // Set socket flags socket.setKeepAlive(false); // TODO To socket channels socket.setTcpNoDelay(false); @@ -530,38 +595,50 @@ } @LauncherAPI - public static Path toPath(String path) { + public static Path toPath(String path) + { return Paths.get(CROSS_SEPARATOR_PATTERN.matcher(path).replaceAll(Matcher.quoteReplacement(PLATFORM_SEPARATOR))); } @LauncherAPI - public static String toString(Path path) { + public static String toString(Path path) + { return PLATFORM_SEPARATOR_PATTERN.matcher(path.toString()).replaceAll(Matcher.quoteReplacement(CROSS_SEPARATOR)); } @LauncherAPI - public static URI toURI(URL url) { - try { + public static URI toURI(URL url) + { + try + { return url.toURI(); - } catch (URISyntaxException e) { + } + catch (URISyntaxException e) + { throw new IllegalArgumentException(e); } } @LauncherAPI - public static URL toURL(Path path) { - try { + public static URL toURL(Path path) + { + try + { return path.toUri().toURL(); - } catch (MalformedURLException e) { + } + catch (MalformedURLException e) + { throw new InternalError(e); } } @LauncherAPI - public static int transfer(InputStream input, OutputStream output) throws IOException { + public static int transfer(InputStream input, OutputStream output) throws IOException + { int transferred = 0; byte[] buffer = newBuffer(); - for (int length = input.read(buffer); length >= 0; length = input.read(buffer)) { + for (int length = input.read(buffer); length >= 0; length = input.read(buffer)) + { output.write(buffer, 0, length); transferred += length; } @@ -569,131 +646,165 @@ } @LauncherAPI - public static void transfer(Path file, OutputStream output) throws IOException { - try (InputStream input = newInput(file)) { + public static void transfer(Path file, OutputStream output) throws IOException + { + try (InputStream input = newInput(file)) + { transfer(input, output); } } @LauncherAPI - public static int transfer(InputStream input, Path file) throws IOException { + public static int transfer(InputStream input, Path file) throws IOException + { return transfer(input, file, false); } @LauncherAPI - public static int transfer(InputStream input, Path file, boolean append) throws IOException { - try (OutputStream output = newOutput(file, append)) { + public static int transfer(InputStream input, Path file, boolean append) throws IOException + { + try (OutputStream output = newOutput(file, append)) + { return transfer(input, output); } } @LauncherAPI - public static String urlDecode(String s) { - try { + public static String urlDecode(String s) + { + try + { return URLDecoder.decode(s, UNICODE_CHARSET.name()); - } catch (UnsupportedEncodingException e) { + } + catch (UnsupportedEncodingException e) + { throw new InternalError(e); } } @LauncherAPI - public static String urlEncode(String s) { - try { + public static String urlEncode(String s) + { + try + { return URLEncoder.encode(s, UNICODE_CHARSET.name()); - } catch (UnsupportedEncodingException e) { + } + catch (UnsupportedEncodingException e) + { throw new InternalError(e); } } @LauncherAPI - public static String verifyFileName(String fileName) { + public static String verifyFileName(String fileName) + { return VerifyHelper.verify(fileName, IOHelper::isValidFileName, String.format("Invalid file name: '%s'", fileName)); } @LauncherAPI - public static int verifyLength(int length, int max) throws IOException { - if (length < 0 || max < 0 && length != -max || max > 0 && length > max) { + public static int verifyLength(int length, int max) throws IOException + { + if (length < 0 || max < 0 && length != -max || max > 0 && length > max) + { throw new IOException("Illegal length: " + length); } return length; } @LauncherAPI - public static BufferedImage verifyTexture(BufferedImage skin, boolean cloak) { + public static BufferedImage verifyTexture(BufferedImage skin, boolean cloak) + { return VerifyHelper.verify(skin, i -> isValidTextureBounds(i.getWidth(), i.getHeight(), cloak), - String.format("Invalid texture bounds: %dx%d", skin.getWidth(), skin.getHeight())); + String.format("Invalid texture bounds: %dx%d", skin.getWidth(), skin.getHeight())); } @LauncherAPI - public static String verifyURL(String url) { - try { + public static String verifyURL(String url) + { + try + { new URL(url).toURI(); return url; - } catch (MalformedURLException | URISyntaxException e) { + } + catch (MalformedURLException | URISyntaxException e) + { throw new IllegalArgumentException("Invalid URL", e); } } @LauncherAPI - public static void walk(Path dir, FileVisitor visitor, boolean hidden) throws IOException { + public static void walk(Path dir, FileVisitor visitor, boolean hidden) throws IOException + { Files.walkFileTree(dir, WALK_OPTIONS, Integer.MAX_VALUE, hidden ? visitor : new SkipHiddenVisitor(visitor)); } @LauncherAPI - public static void write(Path file, byte[] bytes) throws IOException { + public static void write(Path file, byte[] bytes) throws IOException + { createParentDirs(file); Files.write(file, bytes, WRITE_OPTIONS); } - private static final class DeleteDirVisitor extends SimpleFileVisitor { + private static final class DeleteDirVisitor extends SimpleFileVisitor + { private final Path dir; private final boolean self; - private DeleteDirVisitor(Path dir, boolean self) { + private DeleteDirVisitor(Path dir, boolean self) + { this.dir = dir; this.self = self; } @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException + { FileVisitResult result = super.postVisitDirectory(dir, exc); - if (self || !this.dir.equals(dir)) { + if (self || !this.dir.equals(dir)) + { Files.delete(dir); } return result; } @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException + { Files.delete(file); return super.visitFile(file, attrs); } } - private static final class SkipHiddenVisitor implements FileVisitor { + private static final class SkipHiddenVisitor implements FileVisitor + { private final FileVisitor visitor; - private SkipHiddenVisitor(FileVisitor visitor) { + private SkipHiddenVisitor(FileVisitor visitor) + { this.visitor = visitor; } @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException + { return Files.isHidden(dir) ? FileVisitResult.CONTINUE : visitor.postVisitDirectory(dir, exc); } @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException + { return Files.isHidden(dir) ? FileVisitResult.SKIP_SUBTREE : visitor.preVisitDirectory(dir, attrs); } @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException + { return Files.isHidden(file) ? FileVisitResult.CONTINUE : visitor.visitFile(file, attrs); } @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException + { return visitor.visitFileFailed(file, exc); } } diff --git a/Launcher/source/helper/JVMHelper.java b/Launcher/source/helper/JVMHelper.java index 7bdc687..f7f1cc3 100644 --- a/Launcher/source/helper/JVMHelper.java +++ b/Launcher/source/helper/JVMHelper.java @@ -1,5 +1,9 @@ package launcher.helper; +import com.sun.management.OperatingSystemMXBean; +import launcher.LauncherAPI; +import sun.misc.Unsafe; + import java.io.File; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; @@ -14,28 +18,36 @@ import java.util.Arrays; import java.util.Locale; -import com.sun.management.OperatingSystemMXBean; -import launcher.LauncherAPI; -import sun.misc.Unsafe; - -public final class JVMHelper { +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(); + @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(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(); + @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(); // Public static fields - @LauncherAPI public static final Unsafe UNSAFE; - @LauncherAPI public static final Lookup LOOKUP; - @LauncherAPI public static final Runtime RUNTIME = Runtime.getRuntime(); - @LauncherAPI public static final ClassLoader LOADER = ClassLoader.getSystemClassLoader(); + @LauncherAPI + public static final Unsafe UNSAFE; + @LauncherAPI + public static final Lookup LOOKUP; + @LauncherAPI + public static final Runtime RUNTIME = Runtime.getRuntime(); + @LauncherAPI + public static final ClassLoader LOADER = ClassLoader.getSystemClassLoader(); // Useful internal fields and constants private static final String JAVA_LIBRARY_PATH = "java.library.path"; @@ -47,129 +59,10 @@ private static final MethodHandle MH_UCP_GETRESOURCE_METHOD; private static final MethodHandle MH_RESOURCE_GETCERTS_METHOD; - private JVMHelper() { - } - - @LauncherAPI - public static void addClassPath(URL url) { - try { - MH_UCP_ADDURL_METHOD.invoke(UCP, url); - } catch (Throwable exc) { - throw new InternalError(exc); - } - } - - @LauncherAPI - public static void addNativePath(Path path) { - String stringPath = path.toString(); - - // Add to library path - String libraryPath = System.getProperty(JAVA_LIBRARY_PATH); - if (libraryPath == null || libraryPath.isEmpty()) { - libraryPath = stringPath; - } else { - libraryPath += File.pathSeparatorChar + stringPath; - } - System.setProperty(JAVA_LIBRARY_PATH, libraryPath); - - // Reset usrPaths and sysPaths cache - try { - MH_SET_USR_PATHS.invoke((Object) null); - MH_SET_SYS_PATHS.invoke((Object) null); - } catch (Throwable exc) { - throw new InternalError(exc); - } - } - - @LauncherAPI - @SuppressWarnings("CallToSystemGC") - public static void fullGC() { - RUNTIME.gc(); - RUNTIME.runFinalization(); - LogHelper.debug("Used heap: %d MiB", RUNTIME.totalMemory() - RUNTIME.freeMemory() >> 20); - } - - @LauncherAPI - public static Certificate[] getCertificates(String resource) { - try { - Object resource0 = MH_UCP_GETRESOURCE_METHOD.invoke(UCP, resource); - return resource0 == null ? null : (Certificate[]) MH_RESOURCE_GETCERTS_METHOD.invoke(resource0); - } catch (Throwable exc) { - throw new InternalError(exc); - } - } - - @LauncherAPI - public static URL[] getClassPath() { - try { - return (URL[]) MH_UCP_GETURLS_METHOD.invoke(UCP); - } catch (Throwable exc) { - throw new InternalError(exc); - } - } - - @LauncherAPI - public static void halt0(int status) { - LogHelper.debug("Trying to halt JVM"); - try { - LOOKUP.findStatic(Class.forName("java.lang.Shutdown"), "halt0", MethodType.methodType(void.class, int.class)).invokeExact(status); - } catch (Throwable exc) { - throw new InternalError(exc); - } - } - - @LauncherAPI - public static boolean isJVMMatchesSystemArch() { - return JVM_BITS == OS_BITS; - } - - @LauncherAPI - public static void verifySystemProperties(Class mainClass, boolean requireSystem) { - Locale.setDefault(Locale.US); - - // Verify class loader - LogHelper.debug("Verifying class loader"); - if (requireSystem && !mainClass.getClassLoader().equals(LOADER)) { - throw new SecurityException("ClassLoader should be system"); - } - - // Verify system and java architecture - LogHelper.debug("Verifying JVM architecture"); - if (!isJVMMatchesSystemArch()) { - LogHelper.warning("Java and OS architecture mismatch"); - LogHelper.warning("It's recommended to download %d-bit JRE", OS_BITS); - } - } - - @SuppressWarnings("CallToSystemGetenv") - private static int getCorrectOSArch() { - // As always, mustdie must die - if (OS_TYPE == OS.MUSTDIE) { - return System.getenv("ProgramFiles(x86)") == null ? 32 : 64; - } - - // Or trust system property (maybe incorrect) - return System.getProperty("os.arch").contains("64") ? 64 : 32; - } - - private static int getRAMAmount() { - int physicalRam = (int) (OPERATING_SYSTEM_MXBEAN.getTotalPhysicalMemorySize() >> 20); - return Math.min(physicalRam, OS_BITS == 32 ? 1536 : 8192); // Limit 32-bit OS to 1536 MiB, and 64-bit OS to 8192 MiB (because it's enough) - } - - public static Class firstClass(String... names) throws ClassNotFoundException { - for (String name : names) { - try { - return Class.forName(name, false, LOADER); - } catch (ClassNotFoundException ignored) { - // Expected - } - } - throw new ClassNotFoundException(Arrays.toString(names)); - } - - static { - try { + static + { + try + { MethodHandles.publicLookup(); // Just to initialize class // Get unsafe to get trusted lookup @@ -192,28 +85,194 @@ MH_UCP_GETURLS_METHOD = LOOKUP.findVirtual(ucpClass, "getURLs", MethodType.methodType(URL[].class)); MH_UCP_GETRESOURCE_METHOD = LOOKUP.findVirtual(ucpClass, "getResource", MethodType.methodType(resourceClass, String.class)); MH_RESOURCE_GETCERTS_METHOD = LOOKUP.findVirtual(resourceClass, "getCertificates", MethodType.methodType(Certificate[].class)); - } catch (Throwable exc) { + } + catch (Throwable exc) + { + throw new InternalError(exc); + } + } + + private JVMHelper() + { + } + + @LauncherAPI + public static void addClassPath(URL url) + { + try + { + MH_UCP_ADDURL_METHOD.invoke(UCP, url); + } + catch (Throwable exc) + { throw new InternalError(exc); } } @LauncherAPI - public enum OS { + public static void addNativePath(Path path) + { + String stringPath = path.toString(); + + // Add to library path + String libraryPath = System.getProperty(JAVA_LIBRARY_PATH); + if (libraryPath == null || libraryPath.isEmpty()) + { + libraryPath = stringPath; + } + else + { + libraryPath += File.pathSeparatorChar + stringPath; + } + System.setProperty(JAVA_LIBRARY_PATH, libraryPath); + + // Reset usrPaths and sysPaths cache + try + { + MH_SET_USR_PATHS.invoke((Object) null); + MH_SET_SYS_PATHS.invoke((Object) null); + } + catch (Throwable exc) + { + throw new InternalError(exc); + } + } + + @LauncherAPI + @SuppressWarnings("CallToSystemGC") + public static void fullGC() + { + RUNTIME.gc(); + RUNTIME.runFinalization(); + LogHelper.debug("Used heap: %d MiB", RUNTIME.totalMemory() - RUNTIME.freeMemory() >> 20); + } + + @LauncherAPI + public static Certificate[] getCertificates(String resource) + { + try + { + Object resource0 = MH_UCP_GETRESOURCE_METHOD.invoke(UCP, resource); + return resource0 == null ? null : (Certificate[]) MH_RESOURCE_GETCERTS_METHOD.invoke(resource0); + } + catch (Throwable exc) + { + throw new InternalError(exc); + } + } + + @LauncherAPI + public static URL[] getClassPath() + { + try + { + return (URL[]) MH_UCP_GETURLS_METHOD.invoke(UCP); + } + catch (Throwable exc) + { + throw new InternalError(exc); + } + } + + @LauncherAPI + public static void halt0(int status) + { + LogHelper.debug("Trying to halt JVM"); + try + { + LOOKUP.findStatic(Class.forName("java.lang.Shutdown"), "halt0", MethodType.methodType(void.class, int.class)).invokeExact(status); + } + catch (Throwable exc) + { + throw new InternalError(exc); + } + } + + @LauncherAPI + public static boolean isJVMMatchesSystemArch() + { + return JVM_BITS == OS_BITS; + } + + @LauncherAPI + public static void verifySystemProperties(Class mainClass, boolean requireSystem) + { + Locale.setDefault(Locale.US); + + // Verify class loader + LogHelper.debug("Verifying class loader"); + if (requireSystem && !mainClass.getClassLoader().equals(LOADER)) + { + throw new SecurityException("ClassLoader should be system"); + } + + // Verify system and java architecture + LogHelper.debug("Verifying JVM architecture"); + if (!isJVMMatchesSystemArch()) + { + LogHelper.warning("Java and OS architecture mismatch"); + LogHelper.warning("It's recommended to download %d-bit JRE", OS_BITS); + } + } + + @SuppressWarnings("CallToSystemGetenv") + private static int getCorrectOSArch() + { + // As always, mustdie must die + if (OS_TYPE == OS.MUSTDIE) + { + return System.getenv("ProgramFiles(x86)") == null ? 32 : 64; + } + + // Or trust system property (maybe incorrect) + return System.getProperty("os.arch").contains("64") ? 64 : 32; + } + + private static int getRAMAmount() + { + int physicalRam = (int) (OPERATING_SYSTEM_MXBEAN.getTotalPhysicalMemorySize() >> 20); + return Math.min(physicalRam, OS_BITS == 32 ? 1536 : 8192); // Limit 32-bit OS to 1536 MiB, and 64-bit OS to 8192 MiB (because it's enough) + } + + public static Class firstClass(String... names) throws ClassNotFoundException + { + for (String name : names) + { + try + { + return Class.forName(name, false, LOADER); + } + catch (ClassNotFoundException ignored) + { + // Expected + } + } + throw new ClassNotFoundException(Arrays.toString(names)); + } + + @LauncherAPI + public enum OS + { MUSTDIE("mustdie"), LINUX("linux"), MACOSX("macosx"); public final String name; - OS(String name) { + OS(String name) + { this.name = name; } - public static OS byName(String name) { - if (name.startsWith("Windows")) { + public static OS byName(String name) + { + if (name.startsWith("Windows")) + { return MUSTDIE; } - if (name.startsWith("Linux")) { + if (name.startsWith("Linux")) + { return LINUX; } - if (name.startsWith("Mac OS X")) { + if (name.startsWith("Mac OS X")) + { return MACOSX; } throw new RuntimeException(String.format("This shit is not yet supported: '%s'", name)); diff --git a/Launcher/source/helper/LogHelper.java b/Launcher/source/helper/LogHelper.java index 169ff87..b9d9491 100644 --- a/Launcher/source/helper/LogHelper.java +++ b/Launcher/source/helper/LogHelper.java @@ -1,10 +1,14 @@ package launcher.helper; -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; +import launcher.Launcher; +import launcher.LauncherAPI; +import org.fusesource.jansi.Ansi; +import org.fusesource.jansi.Ansi.Attribute; +import org.fusesource.jansi.Ansi.Color; +import org.fusesource.jansi.AnsiConsole; +import org.fusesource.jansi.AnsiOutputStream; + +import java.io.*; import java.nio.file.Path; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -15,18 +19,14 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -import launcher.Launcher; -import launcher.LauncherAPI; -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.Ansi.Attribute; -import org.fusesource.jansi.Ansi.Color; -import org.fusesource.jansi.AnsiConsole; -import org.fusesource.jansi.AnsiOutputStream; - -public final class LogHelper { - @LauncherAPI public static final String DEBUG_PROPERTY = "launcher.debug"; - @LauncherAPI public static final String NO_JANSI_PROPERTY = "launcher.noJAnsi"; - @LauncherAPI public static final boolean JANSI; +public final class LogHelper +{ + @LauncherAPI + public static final String DEBUG_PROPERTY = "launcher.debug"; + @LauncherAPI + public static final String NO_JANSI_PROPERTY = "launcher.noJAnsi"; + @LauncherAPI + public static final boolean JANSI; // Output settings private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy.MM.dd HH:mm:ss", Locale.US); @@ -34,162 +34,243 @@ private static final Set OUTPUTS = Collections.newSetFromMap(new ConcurrentHashMap<>(2)); private static final Output STD_OUTPUT; - private LogHelper() { + static + { + // Use JAnsi if available + boolean jansi; + try + { + if (Boolean.getBoolean(NO_JANSI_PROPERTY)) + { + jansi = false; + } + else + { + Class.forName("org.fusesource.jansi.Ansi"); + AnsiConsole.systemInstall(); + jansi = true; + } + } + catch (ClassNotFoundException ignored) + { + jansi = false; + } + JANSI = jansi; + + // Add std writer + STD_OUTPUT = System.out::println; + addOutput(STD_OUTPUT); + + // Add file log writer + String logFile = System.getProperty("launcher.logFile"); + if (logFile != null) + { + try + { + addOutput(IOHelper.toPath(logFile)); + } + catch (IOException e) + { + error(e); + } + } + } + + private LogHelper() + { } @LauncherAPI - public static void addOutput(Output output) { + public static void addOutput(Output output) + { OUTPUTS.add(Objects.requireNonNull(output, "output")); } @LauncherAPI - public static void addOutput(Path file) throws IOException { - if (JANSI) { + public static void addOutput(Path file) throws IOException + { + if (JANSI) + { addOutput(new JAnsiOutput(IOHelper.newOutput(file, true))); - } else { + } + else + { addOutput(IOHelper.newWriter(file, true)); } } @LauncherAPI - public static void addOutput(Writer writer) throws IOException { + public static void addOutput(Writer writer) throws IOException + { addOutput(new WriterOutput(writer)); } @LauncherAPI - public static void debug(String message) { - if (isDebugEnabled()) { + public static void debug(String message) + { + if (isDebugEnabled()) + { log(Level.DEBUG, message, false); } } @LauncherAPI - public static void debug(String format, Object... args) { + public static void debug(String format, Object... args) + { debug(String.format(format, args)); } @LauncherAPI - public static void error(Throwable exc) { + public static void error(Throwable exc) + { error(isDebugEnabled() ? toString(exc) : exc.toString()); } @LauncherAPI - public static void error(String message) { + public static void error(String message) + { log(Level.ERROR, message, false); } @LauncherAPI - public static void error(String format, Object... args) { + public static void error(String format, Object... args) + { error(String.format(format, args)); } @LauncherAPI - public static void info(String message) { + public static void info(String message) + { log(Level.INFO, message, false); } @LauncherAPI - public static void info(String format, Object... args) { + public static void info(String format, Object... args) + { info(String.format(format, args)); } @LauncherAPI - public static boolean isDebugEnabled() { + public static boolean isDebugEnabled() + { return DEBUG_ENABLED.get(); } @LauncherAPI - public static void setDebugEnabled(boolean debugEnabled) { + public static void setDebugEnabled(boolean debugEnabled) + { DEBUG_ENABLED.set(debugEnabled); } @LauncherAPI - public static void log(Level level, String message, boolean sub) { + public static void log(Level level, String message, boolean sub) + { String dateTime = DATE_TIME_FORMATTER.format(LocalDateTime.now()); println(JANSI ? ansiFormatLog(level, dateTime, message, sub) : - formatLog(level, message, dateTime, sub)); + formatLog(level, message, dateTime, sub)); } @LauncherAPI - public static void printVersion(String product) { + public static void printVersion(String product) + { println(JANSI ? ansiFormatVersion(product) : formatVersion(product)); } @LauncherAPI - public static synchronized void println(String message) { - for (Output output : OUTPUTS) { + public static synchronized void println(String message) + { + for (Output output : OUTPUTS) + { output.println(message); } } @LauncherAPI - public static boolean removeOutput(Output output) { + public static boolean removeOutput(Output output) + { return OUTPUTS.remove(output); } @LauncherAPI - public static boolean removeStdOutput() { + public static boolean removeStdOutput() + { return removeOutput(STD_OUTPUT); } @LauncherAPI - public static void subDebug(String message) { - if (isDebugEnabled()) { + public static void subDebug(String message) + { + if (isDebugEnabled()) + { log(Level.DEBUG, message, true); } } @LauncherAPI - public static void subDebug(String format, Object... args) { + public static void subDebug(String format, Object... args) + { subDebug(String.format(format, args)); } @LauncherAPI - public static void subInfo(String message) { + public static void subInfo(String message) + { log(Level.INFO, message, true); } @LauncherAPI - public static void subInfo(String format, Object... args) { + public static void subInfo(String format, Object... args) + { subInfo(String.format(format, args)); } @LauncherAPI - public static void subWarning(String message) { + public static void subWarning(String message) + { log(Level.WARNING, message, true); } @LauncherAPI - public static void subWarning(String format, Object... args) { + public static void subWarning(String format, Object... args) + { subWarning(String.format(format, args)); } @LauncherAPI - public static String toString(Throwable exc) { - try (StringWriter sw = new StringWriter()) { - try (PrintWriter pw = new PrintWriter(sw)) { + public static String toString(Throwable exc) + { + try (StringWriter sw = new StringWriter()) + { + try (PrintWriter pw = new PrintWriter(sw)) + { exc.printStackTrace(pw); } return sw.toString(); - } catch (IOException e) { + } + catch (IOException e) + { throw new AssertionError(e); } } @LauncherAPI - public static void warning(String message) { + public static void warning(String message) + { log(Level.WARNING, message, false); } @LauncherAPI - public static void warning(String format, Object... args) { + public static void warning(String format, Object... args) + { warning(String.format(format, args)); } - private static String ansiFormatLog(Level level, String dateTime, String message, boolean sub) { + private static String ansiFormatLog(Level level, String dateTime, String message, boolean sub) + { Color levelColor; boolean bright = level != Level.DEBUG; - switch (level) { + switch (level) + { case WARNING: levelColor = Color.YELLOW; break; @@ -207,20 +288,27 @@ // Level ansi.fgBright(Color.WHITE).a(" [").bold(); - if (bright) { + if (bright) + { ansi.fgBright(levelColor); - } else { + } + else + { ansi.fg(levelColor); } ansi.a(level).boldOff().fgBright(Color.WHITE).a("] "); // Message - if (bright) { + if (bright) + { ansi.fgBright(levelColor); - } else { + } + else + { ansi.fg(levelColor); } - if (sub) { + if (sub) + { ansi.a(' ').a(Attribute.ITALIC); } ansi.a(message); @@ -229,102 +317,88 @@ return ansi.reset().toString(); } - private static String ansiFormatVersion(String product) { + private static String ansiFormatVersion(String product) + { return new Ansi().bold(). // Setup - fgBright(Color.MAGENTA).a("KeeperJerry's "). // Autor mirror - fgBright(Color.CYAN).a(product). // Product - fgBright(Color.WHITE).a(" v").fgBright(Color.BLUE).a(Launcher.VERSION). // Version - fgBright(Color.WHITE).a(" (build #").fgBright(Color.RED).a(Launcher.BUILD).fgBright(Color.WHITE).a(')'). // Build# - reset().toString(); // To string + fgBright(Color.MAGENTA).a("KeeperJerry's "). // Autor mirror + fgBright(Color.CYAN).a(product). // Product + fgBright(Color.WHITE).a(" v").fgBright(Color.BLUE).a(Launcher.VERSION). // Version + fgBright(Color.WHITE).a(" (build #").fgBright(Color.RED).a(Launcher.BUILD).fgBright(Color.WHITE).a(')'). // Build# + reset().toString(); // To string } - private static String formatLog(Level level, String message, String dateTime, boolean sub) { - if (sub) { + private static String formatLog(Level level, String message, String dateTime, boolean sub) + { + if (sub) + { message = ' ' + message; } return dateTime + " [" + level.name + "] " + message; } - private static String formatVersion(String product) { + private static String formatVersion(String product) + { return String.format("KeeperJerry's %s v%s (build #%s)", product, Launcher.VERSION, Launcher.BUILD); } - static { - // Use JAnsi if available - boolean jansi; - try { - if (Boolean.getBoolean(NO_JANSI_PROPERTY)) { - jansi = false; - } else { - Class.forName("org.fusesource.jansi.Ansi"); - AnsiConsole.systemInstall(); - jansi = true; - } - } catch (ClassNotFoundException ignored) { - jansi = false; + @LauncherAPI + public enum Level + { + DEBUG("DEBUG"), INFO("INFO"), WARNING("WARN"), ERROR("ERROR"); + public final String name; + + Level(String name) + { + this.name = name; } - JANSI = jansi; - // Add std writer - STD_OUTPUT = System.out::println; - addOutput(STD_OUTPUT); - - // Add file log writer - String logFile = System.getProperty("launcher.logFile"); - if (logFile != null) { - try { - addOutput(IOHelper.toPath(logFile)); - } catch (IOException e) { - error(e); - } + @Override + public String toString() + { + return name; } } @LauncherAPI @FunctionalInterface - public interface Output { + public interface Output + { void println(String message); } - @LauncherAPI - public enum Level { - DEBUG("DEBUG"), INFO("INFO"), WARNING("WARN"), ERROR("ERROR"); - public final String name; - - Level(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - } - - private static final class JAnsiOutput extends WriterOutput { - private JAnsiOutput(OutputStream output) throws IOException { + private static final class JAnsiOutput extends WriterOutput + { + private JAnsiOutput(OutputStream output) throws IOException + { super(IOHelper.newWriter(new AnsiOutputStream(output))); } } - private static class WriterOutput implements Output, AutoCloseable { + private static class WriterOutput implements Output, AutoCloseable + { private final Writer writer; - private WriterOutput(Writer writer) { + private WriterOutput(Writer writer) + { this.writer = writer; } @Override - public void close() throws IOException { + public void close() throws IOException + { writer.close(); } @Override - public void println(String message) { - try { + public void println(String message) + { + try + { writer.write(message + System.lineSeparator()); writer.flush(); - } catch (IOException ignored) { + } + catch (IOException ignored) + { // Do nothing? } } diff --git a/Launcher/source/helper/SecurityHelper.java b/Launcher/source/helper/SecurityHelper.java index 2313ae4..c724e6d 100644 --- a/Launcher/source/helper/SecurityHelper.java +++ b/Launcher/source/helper/SecurityHelper.java @@ -1,20 +1,14 @@ package launcher.helper; +import launcher.LauncherAPI; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Path; -import java.security.CodeSource; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.Signature; -import java.security.SignatureException; +import java.security.*; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.interfaces.RSAKey; @@ -27,104 +21,132 @@ import java.util.Map; import java.util.Random; import java.util.jar.JarFile; -import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; -import launcher.LauncherAPI; - -public final class SecurityHelper { +public final class SecurityHelper +{ // Algorithm constants - @LauncherAPI public static final String RSA_ALGO = "RSA"; - @LauncherAPI public static final String RSA_SIGN_ALGO = "SHA256withRSA"; - @LauncherAPI public static final String RSA_CIPHER_ALGO = "RSA/ECB/PKCS1Padding"; + @LauncherAPI + public static final String RSA_ALGO = "RSA"; + @LauncherAPI + public static final String RSA_SIGN_ALGO = "SHA256withRSA"; + @LauncherAPI + public static final String RSA_CIPHER_ALGO = "RSA/ECB/PKCS1Padding"; // Algorithm size constants - @LauncherAPI public static final int TOKEN_LENGTH = 16; - @LauncherAPI public static final int TOKEN_STRING_LENGTH = TOKEN_LENGTH << 1; - @LauncherAPI public static final int RSA_KEY_LENGTH_BITS = 2048; - @LauncherAPI public static final int RSA_KEY_LENGTH = RSA_KEY_LENGTH_BITS / Byte.SIZE; - @LauncherAPI public static final int CRYPTO_MAX_LENGTH = 2048; + @LauncherAPI + public static final int TOKEN_LENGTH = 16; + @LauncherAPI + public static final int TOKEN_STRING_LENGTH = TOKEN_LENGTH << 1; + @LauncherAPI + public static final int RSA_KEY_LENGTH_BITS = 2048; + @LauncherAPI + public static final int RSA_KEY_LENGTH = RSA_KEY_LENGTH_BITS / Byte.SIZE; + @LauncherAPI + public static final int CRYPTO_MAX_LENGTH = 2048; // Certificate constants - @LauncherAPI public static final String CERTIFICATE_DIGEST = "b87c079e3bf6e709860e05e283678c857b6a27916c2ba280a212f78f1a2ec20a"; - @LauncherAPI public static final String HEX = "0123456789abcdef"; + @LauncherAPI + public static final String CERTIFICATE_DIGEST = "b87c079e3bf6e709860e05e283678c857b6a27916c2ba280a212f78f1a2ec20a"; + @LauncherAPI + public static final String HEX = "0123456789abcdef"; // Random generator constants - private static final char[] VOWELS = { 'e', 'u', 'i', 'o', 'a' }; - private static final char[] CONS = { 'r', 't', 'p', 's', 'd', 'f', 'g', 'h', 'k', 'l', 'c', 'v', 'b', 'n', 'm' }; + private static final char[] VOWELS = {'e', 'u', 'i', 'o', 'a'}; + private static final char[] CONS = {'r', 't', 'p', 's', 'd', 'f', 'g', 'h', 'k', 'l', 'c', 'v', 'b', 'n', 'm'}; - private SecurityHelper() { + private SecurityHelper() + { } @LauncherAPI - public static byte[] digest(DigestAlgorithm algo, String s) { + public static byte[] digest(DigestAlgorithm algo, String s) + { return digest(algo, IOHelper.encode(s)); } @LauncherAPI - public static byte[] digest(DigestAlgorithm algo, URL url) throws IOException { - try (InputStream input = IOHelper.newInput(url)) { + public static byte[] digest(DigestAlgorithm algo, URL url) throws IOException + { + try (InputStream input = IOHelper.newInput(url)) + { return digest(algo, input); } } @LauncherAPI - public static byte[] digest(DigestAlgorithm algo, Path file) throws IOException { - try (InputStream input = IOHelper.newInput(file)) { + public static byte[] digest(DigestAlgorithm algo, Path file) throws IOException + { + try (InputStream input = IOHelper.newInput(file)) + { return digest(algo, input); } } @LauncherAPI - public static byte[] digest(DigestAlgorithm algo, byte[] bytes) { + public static byte[] digest(DigestAlgorithm algo, byte[] bytes) + { return newDigest(algo).digest(bytes); } @LauncherAPI - public static byte[] digest(DigestAlgorithm algo, InputStream input) throws IOException { + public static byte[] digest(DigestAlgorithm algo, InputStream input) throws IOException + { byte[] buffer = IOHelper.newBuffer(); MessageDigest digest = newDigest(algo); - for (int length = input.read(buffer); length != -1; length = input.read(buffer)) { + for (int length = input.read(buffer); length != -1; length = input.read(buffer)) + { digest.update(buffer, 0, length); } return digest.digest(); } @LauncherAPI - public static KeyPair genRSAKeyPair(SecureRandom random) { - try { + public static KeyPair genRSAKeyPair(SecureRandom random) + { + try + { KeyPairGenerator generator = KeyPairGenerator.getInstance(RSA_ALGO); generator.initialize(RSA_KEY_LENGTH_BITS, random); return generator.genKeyPair(); - } catch (NoSuchAlgorithmException e) { + } + catch (NoSuchAlgorithmException e) + { throw new InternalError(e); } } @LauncherAPI - public static KeyPair genRSAKeyPair() { + public static KeyPair genRSAKeyPair() + { return genRSAKeyPair(newRandom()); } @LauncherAPI - public static boolean isValidCertificate(Certificate cert) { - try { + public static boolean isValidCertificate(Certificate cert) + { + try + { return toHex(digest(DigestAlgorithm.SHA256, cert.getEncoded())).equals(CERTIFICATE_DIGEST); - } catch (CertificateEncodingException e) { + } + catch (CertificateEncodingException e) + { throw new InternalError(e); } } @LauncherAPI - public static boolean isValidCertificates(Certificate... certs) { + public static boolean isValidCertificates(Certificate... certs) + { return certs != null && certs.length == 1 && isValidCertificate(certs[0]); } @LauncherAPI - public static boolean isValidCertificates(Class clazz) { + public static boolean isValidCertificates(Class clazz) + { // Verify META-INF/MANIFEST.MF certificate Certificate[] certificates = JVMHelper.getCertificates(JarFile.MANIFEST_NAME); - if (certificates == null || !isValidCertificates(certificates)) { + if (certificates == null || !isValidCertificates(certificates)) + { return false; } @@ -134,148 +156,190 @@ } @LauncherAPI - public static boolean isValidSign(Path path, byte[] sign, RSAPublicKey publicKey) throws IOException, SignatureException { - try (InputStream input = IOHelper.newInput(path)) { + public static boolean isValidSign(Path path, byte[] sign, RSAPublicKey publicKey) throws IOException, SignatureException + { + try (InputStream input = IOHelper.newInput(path)) + { return isValidSign(input, sign, publicKey); } } @LauncherAPI - public static boolean isValidSign(byte[] bytes, byte[] sign, RSAPublicKey publicKey) throws SignatureException { + public static boolean isValidSign(byte[] bytes, byte[] sign, RSAPublicKey publicKey) throws SignatureException + { Signature signature = newRSAVerifySignature(publicKey); - try { + try + { signature.update(bytes); - } catch (SignatureException e) { + } + catch (SignatureException e) + { throw new InternalError(e); } return signature.verify(sign); } @LauncherAPI - public static boolean isValidSign(InputStream input, byte[] sign, RSAPublicKey publicKey) throws IOException, SignatureException { + public static boolean isValidSign(InputStream input, byte[] sign, RSAPublicKey publicKey) throws IOException, SignatureException + { Signature signature = newRSAVerifySignature(publicKey); updateSignature(input, signature); return signature.verify(sign); } @LauncherAPI - public static boolean isValidSign(URL url, byte[] sign, RSAPublicKey publicKey) throws IOException, SignatureException { - try (InputStream input = IOHelper.newInput(url)) { + public static boolean isValidSign(URL url, byte[] sign, RSAPublicKey publicKey) throws IOException, SignatureException + { + try (InputStream input = IOHelper.newInput(url)) + { return isValidSign(input, sign, publicKey); } } @LauncherAPI - public static boolean isValidToken(CharSequence token) { + public static boolean isValidToken(CharSequence token) + { return token.length() == TOKEN_STRING_LENGTH && token.chars().allMatch(ch -> HEX.indexOf(ch) >= 0); } @LauncherAPI - public static MessageDigest newDigest(DigestAlgorithm algo) { + public static MessageDigest newDigest(DigestAlgorithm algo) + { VerifyHelper.verify(algo, a -> a != DigestAlgorithm.PLAIN, "PLAIN digest"); - try { + try + { return MessageDigest.getInstance(algo.name); - } catch (NoSuchAlgorithmException e) { + } + catch (NoSuchAlgorithmException e) + { throw new InternalError(e); } } @LauncherAPI - public static Cipher newRSADecryptCipher(RSAPrivateKey key) { + public static Cipher newRSADecryptCipher(RSAPrivateKey key) + { return newRSACipher(Cipher.DECRYPT_MODE, key); } @LauncherAPI - public static Cipher newRSAEncryptCipher(RSAPublicKey key) { + public static Cipher newRSAEncryptCipher(RSAPublicKey key) + { return newRSACipher(Cipher.ENCRYPT_MODE, key); } @LauncherAPI - public static Signature newRSASignSignature(RSAPrivateKey key) { + public static Signature newRSASignSignature(RSAPrivateKey key) + { Signature signature = newRSASignature(); - try { + try + { signature.initSign(key); - } catch (InvalidKeyException e) { + } + catch (InvalidKeyException e) + { throw new InternalError(e); } return signature; } @LauncherAPI - public static Signature newRSAVerifySignature(RSAPublicKey key) { + public static Signature newRSAVerifySignature(RSAPublicKey key) + { Signature signature = newRSASignature(); - try { + try + { signature.initVerify(key); - } catch (InvalidKeyException e) { + } + catch (InvalidKeyException e) + { throw new InternalError(e); } return signature; } @LauncherAPI - public static SecureRandom newRandom() { + public static SecureRandom newRandom() + { return new SecureRandom(); } @LauncherAPI - public static byte[] randomBytes(Random random, int length) { + public static byte[] randomBytes(Random random, int length) + { byte[] bytes = new byte[length]; random.nextBytes(bytes); return bytes; } @LauncherAPI - public static byte[] randomBytes(int length) { + public static byte[] randomBytes(int length) + { return randomBytes(newRandom(), length); } @LauncherAPI - public static String randomStringToken(Random random) { + public static String randomStringToken(Random random) + { return toHex(randomToken(random)); } @LauncherAPI - public static String randomStringToken() { + public static String randomStringToken() + { return randomStringToken(newRandom()); } @LauncherAPI - public static byte[] randomToken(Random random) { + public static byte[] randomToken(Random random) + { return randomBytes(random, TOKEN_LENGTH); } @LauncherAPI - public static byte[] randomToken() { + public static byte[] randomToken() + { return randomToken(newRandom()); } @LauncherAPI - public static String randomUsername(Random random) { + public static String randomUsername(Random random) + { int usernameLength = 3 + random.nextInt(7); // 3-9 // Choose prefix String prefix; int prefixType = random.nextInt(7); - if (usernameLength >= 5 && prefixType == 6) { // (6) 2-char + if (usernameLength >= 5 && prefixType == 6) + { // (6) 2-char prefix = random.nextBoolean() ? "Mr" : "Dr"; usernameLength -= 2; - } else if (usernameLength >= 6 && prefixType == 5) { // (5) 3-char + } + else if (usernameLength >= 6 && prefixType == 5) + { // (5) 3-char prefix = "Mrs"; usernameLength -= 3; - } else { + } + else + { prefix = ""; } // Choose suffix String suffix; int suffixType = random.nextInt(7); // 0-6, 7 values - if (usernameLength >= 5 && suffixType == 6) { // (6) 10-99 + if (usernameLength >= 5 && suffixType == 6) + { // (6) 10-99 suffix = String.valueOf(10 + random.nextInt(90)); usernameLength -= 2; - } else if (usernameLength >= 7 && suffixType == 5) { // (5) 1990-2015 + } + else if (usernameLength >= 7 && suffixType == 5) + { // (5) 1990-2015 suffix = String.valueOf(1990 + random.nextInt(26)); usernameLength -= 4; - } else { + } + else + { suffix = ""; } @@ -283,16 +347,21 @@ int consRepeat = 0; boolean consPrev = random.nextBoolean(); char[] chars = new char[usernameLength]; - for (int i = 0; i < chars.length; i++) { - if (i > 1 && consPrev && random.nextInt(10) == 0) { // Doubled + for (int i = 0; i < chars.length; i++) + { + if (i > 1 && consPrev && random.nextInt(10) == 0) + { // Doubled chars[i] = chars[i - 1]; continue; } // Choose next char - if (consRepeat < 1 && random.nextInt() == 5) { + if (consRepeat < 1 && random.nextInt() == 5) + { consRepeat++; - } else { + } + else + { consRepeat = 0; consPrev ^= true; } @@ -303,7 +372,8 @@ } // Make first letter uppercase - if (!prefix.isEmpty() || random.nextBoolean()) { + if (!prefix.isEmpty() || random.nextBoolean()) + { chars[0] = Character.toUpperCase(chars[0]); } @@ -312,44 +382,57 @@ } @LauncherAPI - public static String randomUsername() { + public static String randomUsername() + { return randomUsername(newRandom()); } @LauncherAPI - public static byte[] sign(InputStream input, RSAPrivateKey privateKey) throws IOException { + public static byte[] sign(InputStream input, RSAPrivateKey privateKey) throws IOException + { Signature signature = newRSASignSignature(privateKey); updateSignature(input, signature); - try { + try + { return signature.sign(); - } catch (SignatureException e) { + } + catch (SignatureException e) + { throw new InternalError(e); } } @LauncherAPI - public static byte[] sign(byte[] bytes, RSAPrivateKey privateKey) { + public static byte[] sign(byte[] bytes, RSAPrivateKey privateKey) + { Signature signature = newRSASignSignature(privateKey); - try { + try + { signature.update(bytes); return signature.sign(); - } catch (SignatureException e) { + } + catch (SignatureException e) + { throw new InternalError(e); } } @LauncherAPI - public static byte[] sign(Path path, RSAPrivateKey privateKey) throws IOException { - try (InputStream input = IOHelper.newInput(path)) { + public static byte[] sign(Path path, RSAPrivateKey privateKey) throws IOException + { + try (InputStream input = IOHelper.newInput(path)) + { return sign(input, privateKey); } } @LauncherAPI - public static String toHex(byte[] bytes) { + public static String toHex(byte[] bytes) + { int offset = 0; char[] hex = new char[bytes.length << 1]; - for (byte currentByte : bytes) { + for (byte currentByte : bytes) + { int ub = Byte.toUnsignedInt(currentByte); hex[offset] = HEX.charAt(ub >>> 4); offset++; @@ -360,114 +443,162 @@ } @LauncherAPI - public static RSAPrivateKey toPrivateRSAKey(byte[] bytes) throws InvalidKeySpecException { + public static RSAPrivateKey toPrivateRSAKey(byte[] bytes) throws InvalidKeySpecException + { return (RSAPrivateKey) newRSAKeyFactory().generatePrivate(new PKCS8EncodedKeySpec(bytes)); } @LauncherAPI - public static RSAPublicKey toPublicRSAKey(byte[] bytes) throws InvalidKeySpecException { + public static RSAPublicKey toPublicRSAKey(byte[] bytes) throws InvalidKeySpecException + { return (RSAPublicKey) newRSAKeyFactory().generatePublic(new X509EncodedKeySpec(bytes)); } @LauncherAPI - public static void verifyCertificates(Class clazz) { - if (!isValidCertificates(clazz)) { + public static void verifyCertificates(Class clazz) + { + if (!isValidCertificates(clazz)) + { throw new SecurityException("Invalid certificates"); } } @LauncherAPI - public static void verifySign(byte[] bytes, byte[] sign, RSAPublicKey publicKey) throws SignatureException { - if (!isValidSign(bytes, sign, publicKey)) { + public static void verifySign(byte[] bytes, byte[] sign, RSAPublicKey publicKey) throws SignatureException + { + if (!isValidSign(bytes, sign, publicKey)) + { throw new SignatureException("Invalid sign"); } } @LauncherAPI - public static void verifySign(InputStream input, byte[] sign, RSAPublicKey publicKey) throws SignatureException, IOException { - if (!isValidSign(input, sign, publicKey)) { + public static void verifySign(InputStream input, byte[] sign, RSAPublicKey publicKey) throws SignatureException, IOException + { + if (!isValidSign(input, sign, publicKey)) + { throw new SignatureException("Invalid stream sign"); } } @LauncherAPI - public static void verifySign(Path path, byte[] sign, RSAPublicKey publicKey) throws SignatureException, IOException { - if (!isValidSign(path, sign, publicKey)) { + public static void verifySign(Path path, byte[] sign, RSAPublicKey publicKey) throws SignatureException, IOException + { + if (!isValidSign(path, sign, publicKey)) + { throw new SignatureException(String.format("Invalid file sign: '%s'", path)); } } @LauncherAPI - public static void verifySign(URL url, byte[] sign, RSAPublicKey publicKey) throws SignatureException, IOException { - if (!isValidSign(url, sign, publicKey)) { + public static void verifySign(URL url, byte[] sign, RSAPublicKey publicKey) throws SignatureException, IOException + { + if (!isValidSign(url, sign, publicKey)) + { throw new SignatureException(String.format("Invalid URL sign: '%s'", url)); } } @LauncherAPI - public static String verifyToken(String token) { + public static String verifyToken(String token) + { return VerifyHelper.verify(token, SecurityHelper::isValidToken, String.format("Invalid token: '%s'", token)); } - private static Cipher newCipher(String algo) { + private static Cipher newCipher(String algo) + { // IDK Why, but collapsing catch blocks makes ProGuard generate invalid stackmap - try { + try + { return Cipher.getInstance(algo); - } catch (NoSuchAlgorithmException e) { + } + catch (NoSuchAlgorithmException e) + { throw new InternalError(e); - } catch (NoSuchPaddingException e) { + } + catch (NoSuchPaddingException e) + { throw new InternalError(e); } } - private static Cipher newRSACipher(int mode, RSAKey key) { + private static Cipher newRSACipher(int mode, RSAKey key) + { Cipher cipher = newCipher(RSA_CIPHER_ALGO); - try { + try + { cipher.init(mode, (Key) key); - } catch (InvalidKeyException e) { + } + catch (InvalidKeyException e) + { throw new InternalError(e); } return cipher; } - private static KeyFactory newRSAKeyFactory() { - try { + private static KeyFactory newRSAKeyFactory() + { + try + { return KeyFactory.getInstance(RSA_ALGO); - } catch (NoSuchAlgorithmException e) { + } + catch (NoSuchAlgorithmException e) + { throw new InternalError(e); } } - private static Signature newRSASignature() { - try { + private static Signature newRSASignature() + { + try + { return Signature.getInstance(RSA_SIGN_ALGO); - } catch (NoSuchAlgorithmException e) { + } + catch (NoSuchAlgorithmException e) + { throw new InternalError(e); } } - private static void updateSignature(InputStream input, Signature signature) throws IOException { + private static void updateSignature(InputStream input, Signature signature) throws IOException + { byte[] buffer = IOHelper.newBuffer(); - for (int length = input.read(buffer); length >= 0; length = input.read(buffer)) { - try { + for (int length = input.read(buffer); length >= 0; length = input.read(buffer)) + { + try + { signature.update(buffer, 0, length); - } catch (SignatureException e) { + } + catch (SignatureException e) + { throw new InternalError(e); } } } @LauncherAPI - public enum DigestAlgorithm { + public enum DigestAlgorithm + { 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; + static + { + DigestAlgorithm[] algorithmsValues = values(); + ALGORITHMS = new HashMap<>(algorithmsValues.length); + for (DigestAlgorithm algorithm : algorithmsValues) + { + ALGORITHMS.put(algorithm.name, algorithm); + } + } + // Instance public final String name; public final int bits; public final int bytes; - DigestAlgorithm(String name, int bits) { + DigestAlgorithm(String name, int bits) + { this.name = name; this.bits = bits; @@ -476,28 +607,24 @@ assert bits % Byte.SIZE == 0; } - @Override - public String toString() { - 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) { + public static DigestAlgorithm byName(String name) + { return VerifyHelper.getMapValue(ALGORITHMS, name, String.format("Unknown digest algorithm: '%s'", name)); } - static { - DigestAlgorithm[] algorithmsValues = values(); - ALGORITHMS = new HashMap<>(algorithmsValues.length); - for (DigestAlgorithm algorithm : algorithmsValues) { - ALGORITHMS.put(algorithm.name, algorithm); + @Override + public String toString() + { + return name; + } + + public byte[] verify(byte[] digest) + { + if (digest.length != bytes) + { + throw new IllegalArgumentException("Invalid digest length: " + digest.length); } + return digest; } } } diff --git a/Launcher/source/helper/VerifyHelper.java b/Launcher/source/helper/VerifyHelper.java index 3f424ac..f721505 100644 --- a/Launcher/source/helper/VerifyHelper.java +++ b/Launcher/source/helper/VerifyHelper.java @@ -1,5 +1,7 @@ package launcher.helper; +import launcher.LauncherAPI; + import java.util.Map; import java.util.Objects; import java.util.function.DoublePredicate; @@ -8,88 +10,110 @@ import java.util.function.Predicate; import java.util.regex.Pattern; -import launcher.LauncherAPI; +public final class VerifyHelper +{ + @LauncherAPI + public static final IntPredicate POSITIVE = i -> i > 0; + @LauncherAPI + public static final IntPredicate NOT_NEGATIVE = i -> i >= 0; + @LauncherAPI + public static final LongPredicate L_POSITIVE = l -> l > 0; + @LauncherAPI + public static final LongPredicate L_NOT_NEGATIVE = l -> l >= 0; + @LauncherAPI + public static final Predicate NOT_EMPTY = s -> !s.isEmpty(); + @LauncherAPI + public static final Pattern USERNAME_PATTERN = Pattern.compile("[a-zA-Zа-яА-Я0-9_.\\-]{1,16}"); -public final class VerifyHelper { - @LauncherAPI public static final IntPredicate POSITIVE = i -> i > 0; - @LauncherAPI public static final IntPredicate NOT_NEGATIVE = i -> i >= 0; - @LauncherAPI public static final LongPredicate L_POSITIVE = l -> l > 0; - @LauncherAPI public static final LongPredicate L_NOT_NEGATIVE = l -> l >= 0; - @LauncherAPI public static final Predicate NOT_EMPTY = s -> !s.isEmpty(); - @LauncherAPI public static final Pattern USERNAME_PATTERN = Pattern.compile("[a-zA-Zа-яА-Я0-9_.\\-]{1,16}"); - - private VerifyHelper() { + private VerifyHelper() + { } @LauncherAPI - public static V getMapValue(Map map, K key, String error) { + public static V getMapValue(Map map, K key, String error) + { return verify(map.get(key), Objects::nonNull, error); } @LauncherAPI - public static boolean isValidIDName(String name) { + public static boolean isValidIDName(String name) + { return !name.isEmpty() && name.length() <= 255 && name.chars().allMatch(VerifyHelper::isValidIDNameChar); } @LauncherAPI - public static boolean isValidIDNameChar(int ch) { + public static boolean isValidIDNameChar(int ch) + { return ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9' || ch == '-' || ch == '_'; } @LauncherAPI - public static boolean isValidUsername(CharSequence username) { + public static boolean isValidUsername(CharSequence username) + { return USERNAME_PATTERN.matcher(username).matches(); } @LauncherAPI - public static void putIfAbsent(Map map, K key, V value, String error) { + public static void putIfAbsent(Map map, K key, V value, String error) + { verify(map.putIfAbsent(key, value), Objects::isNull, error); } @LauncherAPI - public static IntPredicate range(int min, int max) { + public static IntPredicate range(int min, int max) + { return i -> i >= min && i <= max; } @LauncherAPI - public static T verify(T object, Predicate predicate, String error) { - if (predicate.test(object)) { + public static T verify(T object, Predicate predicate, String error) + { + if (predicate.test(object)) + { return object; } throw new IllegalArgumentException(error); } @LauncherAPI - public static double verifyDouble(double d, DoublePredicate predicate, String error) { - if (predicate.test(d)) { + public static double verifyDouble(double d, DoublePredicate predicate, String error) + { + if (predicate.test(d)) + { return d; } throw new IllegalArgumentException(error); } @LauncherAPI - public static String verifyIDName(String name) { + public static String verifyIDName(String name) + { return verify(name, VerifyHelper::isValidIDName, String.format("Invalid name: '%s'", name)); } @LauncherAPI - public static int verifyInt(int i, IntPredicate predicate, String error) { - if (predicate.test(i)) { + public static int verifyInt(int i, IntPredicate predicate, String error) + { + if (predicate.test(i)) + { return i; } throw new IllegalArgumentException(error); } @LauncherAPI - public static long verifyLong(long l, LongPredicate predicate, String error) { - if (predicate.test(l)) { + public static long verifyLong(long l, LongPredicate predicate, String error) + { + if (predicate.test(l)) + { return l; } throw new IllegalArgumentException(error); } @LauncherAPI - public static String verifyUsername(String username) { + public static String verifyUsername(String username) + { return verify(username, VerifyHelper::isValidUsername, String.format("Invalid username: '%s'", username)); } } diff --git a/Launcher/source/helper/js/JSApplication.java b/Launcher/source/helper/js/JSApplication.java index eed22e6..e41a812 100644 --- a/Launcher/source/helper/js/JSApplication.java +++ b/Launcher/source/helper/js/JSApplication.java @@ -1,21 +1,24 @@ package launcher.helper.js; -import java.util.concurrent.atomic.AtomicReference; import javafx.application.Application; - import launcher.LauncherAPI; +import java.util.concurrent.atomic.AtomicReference; + @LauncherAPI @SuppressWarnings("AbstractClassNeverImplemented") -public abstract class JSApplication extends Application { +public abstract class JSApplication extends Application +{ private static final AtomicReference INSTANCE = new AtomicReference<>(); @SuppressWarnings("ConstructorNotProtectedInAbstractClass") - public JSApplication() { + public JSApplication() + { INSTANCE.set(this); } - public static JSApplication getInstance() { + public static JSApplication getInstance() + { return INSTANCE.get(); } } diff --git a/Launcher/source/request/CustomRequest.java b/Launcher/source/request/CustomRequest.java index 61e675d..90f053f 100644 --- a/Launcher/source/request/CustomRequest.java +++ b/Launcher/source/request/CustomRequest.java @@ -1,30 +1,34 @@ package launcher.request; -import launcher.Launcher; import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.helper.VerifyHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; -public abstract class CustomRequest extends Request { +public abstract class CustomRequest extends Request +{ @LauncherAPI - public CustomRequest(Config config) { + public CustomRequest(Config config) + { super(config); } @LauncherAPI - public CustomRequest() { + public CustomRequest() + { this(null); } @Override - public final Type getType() { + public final Type getType() + { return Type.CUSTOM; } @Override - protected final T requestDo(HInput input, HOutput output) throws Throwable { + protected final T requestDo(HInput input, HOutput output) throws Throwable + { output.writeASCII(VerifyHelper.verifyIDName(getName()), 255); output.flush(); diff --git a/Launcher/source/request/PingRequest.java b/Launcher/source/request/PingRequest.java index 9e7f99b..708b88e 100644 --- a/Launcher/source/request/PingRequest.java +++ b/Launcher/source/request/PingRequest.java @@ -1,35 +1,41 @@ package launcher.request; -import java.io.IOException; - -import launcher.Launcher; import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class PingRequest extends Request { - @LauncherAPI public static final byte EXPECTED_BYTE = 0b01010101; +import java.io.IOException; + +public final class PingRequest extends Request +{ + @LauncherAPI + public static final byte EXPECTED_BYTE = 0b01010101; @LauncherAPI - public PingRequest(Config config) { + public PingRequest(Config config) + { super(config); } @LauncherAPI - public PingRequest() { + public PingRequest() + { this(null); } @Override - public Type getType() { + public Type getType() + { return Type.PING; } @Override - protected Void requestDo(HInput input, HOutput output) throws IOException { + protected Void requestDo(HInput input, HOutput output) throws IOException + { byte pong = (byte) input.readUnsignedByte(); - if (pong != EXPECTED_BYTE) { + if (pong != EXPECTED_BYTE) + { throw new IOException("Illegal ping response: " + pong); } return null; diff --git a/Launcher/source/request/Request.java b/Launcher/source/request/Request.java index 4118e2c..8fa726c 100644 --- a/Launcher/source/request/Request.java +++ b/Launcher/source/request/Request.java @@ -1,9 +1,5 @@ package launcher.request; -import java.io.IOException; -import java.net.Socket; -import java.util.concurrent.atomic.AtomicBoolean; - import launcher.Launcher; import launcher.Launcher.Config; import launcher.LauncherAPI; @@ -14,21 +10,35 @@ import launcher.serialize.stream.EnumSerializer; import launcher.serialize.stream.EnumSerializer.Itf; -public abstract class Request { - @LauncherAPI protected final Config config; +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.atomic.AtomicBoolean; + +public abstract class Request +{ + @LauncherAPI + protected final Config config; private final AtomicBoolean started = new AtomicBoolean(false); @LauncherAPI - protected Request(Config config) { + protected Request(Config config) + { this.config = config == null ? Launcher.getConfig() : config; } @LauncherAPI - protected Request() { + protected Request() + { this(null); } @LauncherAPI + public static void requestError(String message) throws RequestException + { + throw new RequestException(message); + } + + @LauncherAPI public abstract Type getType(); @LauncherAPI @@ -36,16 +46,20 @@ @LauncherAPI @SuppressWarnings("DesignForExtension") - public R request() throws Throwable { - if (!started.compareAndSet(false, true)) { + public R request() throws Throwable + { + if (!started.compareAndSet(false, true)) + { throw new IllegalStateException("Request already started"); } // Make request to LaunchServer - try (Socket socket = IOHelper.newSocket()) { + try (Socket socket = IOHelper.newSocket()) + { socket.connect(IOHelper.resolve(config.address)); try (HInput input = new HInput(socket.getInputStream()); - HOutput output = new HOutput(socket.getOutputStream())) { + HOutput output = new HOutput(socket.getOutputStream())) + { writeHandshake(input, output); return requestDo(input, output); } @@ -53,14 +67,17 @@ } @LauncherAPI - protected final void readError(HInput input) throws IOException { + protected final void readError(HInput input) throws IOException + { String error = input.readString(0); - if (!error.isEmpty()) { + if (!error.isEmpty()) + { requestError(error); } } - private void writeHandshake(HInput input, HOutput output) throws IOException { + private void writeHandshake(HInput input, HOutput output) throws IOException + { // Write handshake output.writeInt(Launcher.PROTOCOL_MAGIC); output.writeBigInteger(config.publicKey.getModulus(), SecurityHelper.RSA_KEY_LENGTH + 1); @@ -68,18 +85,15 @@ output.flush(); // Verify is accepted - if (!input.readBoolean()) { + if (!input.readBoolean()) + { requestError("Serverside not accepted this connection"); } } @LauncherAPI - public static void requestError(String message) throws RequestException { - throw new RequestException(message); - } - - @LauncherAPI - public enum Type implements Itf { + public enum Type implements Itf + { PING(0), // Ping request LAUNCHER(1), UPDATE(2), UPDATE_LIST(3), // Update requests AUTH(4), JOIN_SERVER(5), CHECK_SERVER(6), // Auth requests @@ -88,18 +102,21 @@ private static final EnumSerializer SERIALIZER = new EnumSerializer<>(Type.class); private final int n; - Type(int n) { + Type(int n) + { this.n = n; } - @Override - public int getNumber() { - return n; + @LauncherAPI + public static Type read(HInput input) throws IOException + { + return SERIALIZER.read(input); } - @LauncherAPI - public static Type read(HInput input) throws IOException { - return SERIALIZER.read(input); + @Override + public int getNumber() + { + return n; } } } diff --git a/Launcher/source/request/RequestException.java b/Launcher/source/request/RequestException.java index c0802ce..8df7e93 100644 --- a/Launcher/source/request/RequestException.java +++ b/Launcher/source/request/RequestException.java @@ -1,29 +1,34 @@ package launcher.request; -import java.io.IOException; - import launcher.LauncherAPI; -public final class RequestException extends IOException { +import java.io.IOException; + +public final class RequestException extends IOException +{ private static final long serialVersionUID = 7558237657082664821L; @LauncherAPI - public RequestException(String message) { + public RequestException(String message) + { super(message); } @LauncherAPI - public RequestException(Throwable exc) { + public RequestException(Throwable exc) + { super(exc); } @LauncherAPI - public RequestException(String message, Throwable exc) { + public RequestException(String message, Throwable exc) + { super(message, exc); } @Override - public String toString() { + public String toString() + { return getMessage(); } } diff --git a/Launcher/source/request/auth/AuthRequest.java b/Launcher/source/request/auth/AuthRequest.java index e3ba2c7..eaacd73 100644 --- a/Launcher/source/request/auth/AuthRequest.java +++ b/Launcher/source/request/auth/AuthRequest.java @@ -1,7 +1,5 @@ package launcher.request.auth; -import java.io.IOException; - import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.client.PlayerProfile; @@ -12,29 +10,36 @@ import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class AuthRequest extends Request { +import java.io.IOException; + +public final class AuthRequest extends Request +{ private final String login; private final byte[] encryptedPassword; @LauncherAPI - public AuthRequest(Config config, String login, byte[] encryptedPassword) { + public AuthRequest(Config config, String login, byte[] encryptedPassword) + { super(config); this.login = VerifyHelper.verify(login, VerifyHelper.NOT_EMPTY, "Login can't be empty"); this.encryptedPassword = encryptedPassword.clone(); } @LauncherAPI - public AuthRequest(String login, byte[] encryptedPassword) { + public AuthRequest(String login, byte[] encryptedPassword) + { this(null, login, encryptedPassword); } @Override - public Type getType() { + public Type getType() + { return Type.AUTH; } @Override - protected Result requestDo(HInput input, HOutput output) throws IOException { + protected Result requestDo(HInput input, HOutput output) throws IOException + { output.writeString(login, 255); output.writeByteArray(encryptedPassword, SecurityHelper.CRYPTO_MAX_LENGTH); output.flush(); @@ -46,11 +51,15 @@ return new Result(pp, accessToken); } - public static final class Result { - @LauncherAPI public final PlayerProfile pp; - @LauncherAPI public final String accessToken; + public static final class Result + { + @LauncherAPI + public final PlayerProfile pp; + @LauncherAPI + public final String accessToken; - private Result(PlayerProfile pp, String accessToken) { + private Result(PlayerProfile pp, String accessToken) + { this.pp = pp; this.accessToken = accessToken; } diff --git a/Launcher/source/request/auth/CheckServerRequest.java b/Launcher/source/request/auth/CheckServerRequest.java index c8db341..5aab406 100644 --- a/Launcher/source/request/auth/CheckServerRequest.java +++ b/Launcher/source/request/auth/CheckServerRequest.java @@ -1,7 +1,5 @@ package launcher.request.auth; -import java.io.IOException; - import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.client.PlayerProfile; @@ -10,29 +8,36 @@ import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class CheckServerRequest extends Request { +import java.io.IOException; + +public final class CheckServerRequest extends Request +{ private final String username; private final String serverID; @LauncherAPI - public CheckServerRequest(Config config, String username, String serverID) { + public CheckServerRequest(Config config, String username, String serverID) + { super(config); this.username = VerifyHelper.verifyUsername(username); this.serverID = JoinServerRequest.verifyServerID(serverID); } @LauncherAPI - public CheckServerRequest(String username, String serverID) { + public CheckServerRequest(String username, String serverID) + { this(null, username, serverID); } @Override - public Type getType() { + public Type getType() + { return Type.CHECK_SERVER; } @Override - protected PlayerProfile requestDo(HInput input, HOutput output) throws IOException { + protected PlayerProfile requestDo(HInput input, HOutput output) throws IOException + { output.writeString(username, 64); output.writeASCII(serverID, 41); // 1 char for minus sign output.flush(); diff --git a/Launcher/source/request/auth/JoinServerRequest.java b/Launcher/source/request/auth/JoinServerRequest.java index ad59f31..9b9d130 100644 --- a/Launcher/source/request/auth/JoinServerRequest.java +++ b/Launcher/source/request/auth/JoinServerRequest.java @@ -1,8 +1,5 @@ package launcher.request.auth; -import java.io.IOException; -import java.util.regex.Pattern; - import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.helper.SecurityHelper; @@ -11,7 +8,11 @@ import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class JoinServerRequest extends Request { +import java.io.IOException; +import java.util.regex.Pattern; + +public final class JoinServerRequest extends Request +{ private static final Pattern SERVERID_PATTERN = Pattern.compile("-?[0-9a-f]{1,40}"); // Instance @@ -20,7 +21,8 @@ private final String serverID; @LauncherAPI - public JoinServerRequest(Config config, String username, String accessToken, String serverID) { + public JoinServerRequest(Config config, String username, String accessToken, String serverID) + { super(config); this.username = VerifyHelper.verifyUsername(username); this.accessToken = SecurityHelper.verifyToken(accessToken); @@ -28,17 +30,33 @@ } @LauncherAPI - public JoinServerRequest(String username, String accessToken, String serverID) { + public JoinServerRequest(String username, String accessToken, String serverID) + { this(null, username, accessToken, serverID); } + @LauncherAPI + public static boolean isValidServerID(CharSequence serverID) + { + return SERVERID_PATTERN.matcher(serverID).matches(); + } + + @LauncherAPI + public static String verifyServerID(String serverID) + { + return VerifyHelper.verify(serverID, JoinServerRequest::isValidServerID, + String.format("Invalid server ID: '%s'", serverID)); + } + @Override - public Type getType() { + public Type getType() + { return Type.JOIN_SERVER; } @Override - protected Boolean requestDo(HInput input, HOutput output) throws IOException { + protected Boolean requestDo(HInput input, HOutput output) throws IOException + { output.writeString(username, 64); output.writeASCII(accessToken, -SecurityHelper.TOKEN_STRING_LENGTH); output.writeASCII(serverID, 41); // 1 char for minus sign @@ -48,15 +66,4 @@ readError(input); return input.readBoolean(); } - - @LauncherAPI - public static boolean isValidServerID(CharSequence serverID) { - return SERVERID_PATTERN.matcher(serverID).matches(); - } - - @LauncherAPI - public static String verifyServerID(String serverID) { - return VerifyHelper.verify(serverID, JoinServerRequest::isValidServerID, - String.format("Invalid server ID: '%s'", serverID)); - } } diff --git a/Launcher/source/request/update/LauncherRequest.java b/Launcher/source/request/update/LauncherRequest.java index c085bb4..4aa13c1 100644 --- a/Launcher/source/request/update/LauncherRequest.java +++ b/Launcher/source/request/update/LauncherRequest.java @@ -1,13 +1,5 @@ package launcher.request.update; -import java.io.IOException; -import java.nio.file.Path; -import java.security.SignatureException; -import java.security.interfaces.RSAPublicKey; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import launcher.Launcher; import launcher.Launcher.Config; import launcher.LauncherAPI; @@ -23,68 +15,47 @@ import launcher.serialize.HOutput; import launcher.serialize.signed.SignedObjectHolder; -public final class LauncherRequest extends Request { - @LauncherAPI public static final Path BINARY_PATH = IOHelper.getCodeSource(Launcher.class); - @LauncherAPI public static final boolean EXE_BINARY = IOHelper.hasExtension(BINARY_PATH, "exe"); +import java.io.IOException; +import java.nio.file.Path; +import java.security.SignatureException; +import java.security.interfaces.RSAPublicKey; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class LauncherRequest extends Request +{ + @LauncherAPI + public static final Path BINARY_PATH = IOHelper.getCodeSource(Launcher.class); + @LauncherAPI + public static final boolean EXE_BINARY = IOHelper.hasExtension(BINARY_PATH, "exe"); @LauncherAPI - public LauncherRequest(Config config) { + public LauncherRequest(Config config) + { super(config); } @LauncherAPI - public LauncherRequest() { + public LauncherRequest() + { this(null); } - @Override - public Type getType() { - return Type.LAUNCHER; - } - - @Override - @SuppressWarnings("CallToSystemExit") - protected Result requestDo(HInput input, HOutput output) throws Throwable { - output.writeBoolean(EXE_BINARY); - output.flush(); - readError(input); - - // Verify launcher sign - RSAPublicKey publicKey = config.publicKey; - byte[] sign = input.readByteArray(-SecurityHelper.RSA_KEY_LENGTH); - boolean shouldUpdate = !SecurityHelper.isValidSign(BINARY_PATH, sign, publicKey); - - // Update launcher if need - output.writeBoolean(shouldUpdate); - output.flush(); - if (shouldUpdate) { - byte[] binary = input.readByteArray(0); - SecurityHelper.verifySign(binary, sign, config.publicKey); - return new Result(binary, sign, Collections.emptyList()); - } - - // Read clients profiles list - int count = input.readLength(0); - List> profiles = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - profiles.add(new SignedObjectHolder<>(input, publicKey, ClientProfile.RO_ADAPTER)); - } - - // Return request result - return new Result(null, sign, profiles); - } - @LauncherAPI - public static void update(Config config, Result result) throws SignatureException, IOException { + public static void update(Config config, Result result) throws SignatureException, IOException + { SecurityHelper.verifySign(result.binary, result.sign, config.publicKey); // Prepare process builder to start new instance (java -jar works for Launch4J's EXE too) List args = new ArrayList<>(8); args.add(IOHelper.resolveJavaBin(null).toString()); - if (LogHelper.isDebugEnabled()) { + if (LogHelper.isDebugEnabled()) + { args.add(ClientLauncher.jvmProperty(LogHelper.DEBUG_PROPERTY, Boolean.toString(LogHelper.isDebugEnabled()))); } - if (Config.ADDRESS_OVERRIDE != null) { + if (Config.ADDRESS_OVERRIDE != null) + { args.add(ClientLauncher.jvmProperty(Config.ADDRESS_OVERRIDE_PROPERTY, Config.ADDRESS_OVERRIDE)); } args.add("-jar"); @@ -101,24 +72,70 @@ throw new AssertionError("Why Launcher wasn't restarted?!"); } - public static final class Result { - @LauncherAPI public final List> profiles; + @Override + public Type getType() + { + return Type.LAUNCHER; + } + + @Override + @SuppressWarnings("CallToSystemExit") + protected Result requestDo(HInput input, HOutput output) throws Throwable + { + output.writeBoolean(EXE_BINARY); + output.flush(); + readError(input); + + // Verify launcher sign + RSAPublicKey publicKey = config.publicKey; + byte[] sign = input.readByteArray(-SecurityHelper.RSA_KEY_LENGTH); + boolean shouldUpdate = !SecurityHelper.isValidSign(BINARY_PATH, sign, publicKey); + + // Update launcher if need + output.writeBoolean(shouldUpdate); + output.flush(); + if (shouldUpdate) + { + byte[] binary = input.readByteArray(0); + SecurityHelper.verifySign(binary, sign, config.publicKey); + return new Result(binary, sign, Collections.emptyList()); + } + + // Read clients profiles list + int count = input.readLength(0); + List> profiles = new ArrayList<>(count); + for (int i = 0; i < count; i++) + { + profiles.add(new SignedObjectHolder<>(input, publicKey, ClientProfile.RO_ADAPTER)); + } + + // Return request result + return new Result(null, sign, profiles); + } + + public static final class Result + { + @LauncherAPI + public final List> profiles; private final byte[] binary; private final byte[] sign; - private Result(byte[] binary, byte[] sign, List> profiles) { + private Result(byte[] binary, byte[] sign, List> profiles) + { this.binary = binary == null ? null : binary.clone(); this.sign = sign.clone(); this.profiles = Collections.unmodifiableList(profiles); } @LauncherAPI - public byte[] getBinary() { + public byte[] getBinary() + { return binary == null ? null : binary.clone(); } @LauncherAPI - public byte[] getSign() { + public byte[] getSign() + { return sign.clone(); } } diff --git a/Launcher/source/request/update/UpdateListRequest.java b/Launcher/source/request/update/UpdateListRequest.java index 52272dd..2e26e59 100644 --- a/Launcher/source/request/update/UpdateListRequest.java +++ b/Launcher/source/request/update/UpdateListRequest.java @@ -1,11 +1,5 @@ package launcher.request.update; -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import launcher.Launcher; import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.helper.IOHelper; @@ -13,29 +7,40 @@ import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class UpdateListRequest extends Request> { +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public final class UpdateListRequest extends Request> +{ @LauncherAPI - public UpdateListRequest(Config config) { + public UpdateListRequest(Config config) + { super(config); } @LauncherAPI - public UpdateListRequest() { + public UpdateListRequest() + { this(null); } @Override - public Type getType() { + public Type getType() + { return Type.UPDATE_LIST; } @Override - protected Set requestDo(HInput input, HOutput output) throws IOException { + protected Set requestDo(HInput input, HOutput output) throws IOException + { int count = input.readLength(0); // Read all update dirs names Set result = new HashSet<>(count); - for (int i = 0; i < count; i++) { + for (int i = 0; i < count; i++) + { result.add(IOHelper.verifyFileName(input.readString(255))); } diff --git a/Launcher/source/request/update/UpdateRequest.java b/Launcher/source/request/update/UpdateRequest.java index 665548e..c7375da 100644 --- a/Launcher/source/request/update/UpdateRequest.java +++ b/Launcher/source/request/update/UpdateRequest.java @@ -1,21 +1,5 @@ package launcher.request.update; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.SignatureException; -import java.time.Duration; -import java.time.Instant; -import java.util.LinkedList; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Queue; -import java.util.zip.InflaterInputStream; - import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.hasher.FileNameMatcher; @@ -35,8 +19,26 @@ import launcher.serialize.stream.EnumSerializer.Itf; import launcher.serialize.stream.StreamObject; -public final class UpdateRequest extends Request> { - @LauncherAPI public static final int MAX_QUEUE_SIZE = 128; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.SignatureException; +import java.time.Duration; +import java.time.Instant; +import java.util.LinkedList; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Queue; +import java.util.zip.InflaterInputStream; + +public final class UpdateRequest extends Request> +{ + @LauncherAPI + public static final int MAX_QUEUE_SIZE = 128; // Instance private final String dirName; @@ -52,7 +54,8 @@ private Instant startTime; @LauncherAPI - public UpdateRequest(Config config, String dirName, Path dir, FileNameMatcher matcher, boolean digest) { + public UpdateRequest(Config config, String dirName, Path dir, FileNameMatcher matcher, boolean digest) + { super(config); this.dirName = IOHelper.verifyFileName(dirName); this.dir = Objects.requireNonNull(dir, "dir"); @@ -61,17 +64,43 @@ } @LauncherAPI - public UpdateRequest(String dirName, Path dir, FileNameMatcher matcher, boolean digest) { + public UpdateRequest(String dirName, Path dir, FileNameMatcher matcher, boolean digest) + { this(null, dirName, dir, matcher, digest); } + private static void fillActionsQueue(Queue queue, HashedDir mismatch) + { + for (Entry mapEntry : mismatch.map().entrySet()) + { + String name = mapEntry.getKey(); + HashedEntry entry = mapEntry.getValue(); + HashedEntry.Type entryType = entry.getType(); + switch (entryType) + { + case DIR: // cd - get - cd .. + queue.add(new Action(Action.Type.CD, name, entry)); + fillActionsQueue(queue, (HashedDir) entry); + queue.add(Action.CD_BACK); + break; + case FILE: // get + queue.add(new Action(Action.Type.GET, name, entry)); + break; + default: + throw new AssertionError("Unsupported hashed entry type: " + entryType.name()); + } + } + } + @Override - public Type getType() { + public Type getType() + { return Type.UPDATE; } @Override - public SignedObjectHolder request() throws Throwable { + public SignedObjectHolder request() throws Throwable + { Files.createDirectories(dir); localDir = new HashedDir(dir, matcher, false, digest); @@ -80,7 +109,8 @@ } @Override - protected SignedObjectHolder requestDo(HInput input, HOutput output) throws IOException, SignatureException { + protected SignedObjectHolder requestDo(HInput input, HOutput output) throws IOException, SignatureException + { // Write update dir name output.writeString(dirName, 255); output.flush(); @@ -105,12 +135,14 @@ startTime = Instant.now(); Path currentDir = dir; Action[] actionsSlice = new Action[MAX_QUEUE_SIZE]; - while (!queue.isEmpty()) { + while (!queue.isEmpty()) + { int length = Math.min(queue.size(), MAX_QUEUE_SIZE); // Write actions slice output.writeLength(length, MAX_QUEUE_SIZE); - for (int i = 0; i < length; i++) { + for (int i = 0; i < length; i++) + { Action action = queue.remove(); actionsSlice[i] = action; action.write(output); @@ -118,16 +150,19 @@ output.flush(); // Perform actions - for (int i = 0; i < length; i++) { + for (int i = 0; i < length; i++) + { Action action = actionsSlice[i]; - switch (action.type) { + switch (action.type) + { case CD: currentDir = currentDir.resolve(action.name); Files.createDirectories(currentDir); break; case GET: Path targetFile = currentDir.resolve(action.name); - if (fileInput.read() != 0xFF) { + if (fileInput.read() != 0xFF) + { throw new IOException("Serverside cached size mismath for file " + action.name); } downloadFile(targetFile, (HashedFile) action.entry, fileInput); @@ -149,19 +184,23 @@ } @LauncherAPI - public void setStateCallback(Callback callback) { + public void setStateCallback(Callback callback) + { stateCallback = callback; } - private void deleteExtraDir(Path subDir, HashedDir subHDir, boolean flag) throws IOException { - for (Entry mapEntry : subHDir.map().entrySet()) { + private void deleteExtraDir(Path subDir, HashedDir subHDir, boolean flag) throws IOException + { + for (Entry mapEntry : subHDir.map().entrySet()) + { String name = mapEntry.getKey(); Path path = subDir.resolve(name); // Delete files and dirs based on type HashedEntry entry = mapEntry.getValue(); HashedEntry.Type entryType = entry.getType(); - switch (entryType) { + switch (entryType) + { case FILE: updateState(IOHelper.toString(path), 0, 0); Files.delete(path); @@ -175,33 +214,39 @@ } // Delete! - if (flag) { + if (flag) + { updateState(IOHelper.toString(subDir), 0, 0); Files.delete(subDir); } } - private void downloadFile(Path file, HashedFile hFile, InputStream input) throws IOException { + private void downloadFile(Path file, HashedFile hFile, InputStream input) throws IOException + { String filePath = IOHelper.toString(dir.relativize(file)); updateState(filePath, 0L, hFile.size); // Start file update MessageDigest digest = this.digest ? SecurityHelper.newDigest(DigestAlgorithm.MD5) : null; - try (OutputStream fileOutput = IOHelper.newOutput(file)) { + try (OutputStream fileOutput = IOHelper.newOutput(file)) + { long downloaded = 0L; // Download with digest update byte[] bytes = IOHelper.newBuffer(); - while (downloaded < hFile.size) { + while (downloaded < hFile.size) + { int remaining = (int) Math.min(hFile.size - downloaded, bytes.length); int length = input.read(bytes, 0, remaining); - if (length < 0) { + if (length < 0) + { throw new EOFException(String.format("%d bytes remaining", hFile.size - downloaded)); } // Update file fileOutput.write(bytes, 0, length); - if (digest != null) { + if (digest != null) + { digest.update(bytes, 0, length); } @@ -213,42 +258,27 @@ } // Verify digest - if (digest != null) { + if (digest != null) + { byte[] digestBytes = digest.digest(); - if (!hFile.isSameDigest(digestBytes)) { + if (!hFile.isSameDigest(digestBytes)) + { throw new SecurityException(String.format("File digest mismatch: '%s'", filePath)); } } } - private static void fillActionsQueue(Queue queue, HashedDir mismatch) { - for (Entry mapEntry : mismatch.map().entrySet()) { - String name = mapEntry.getKey(); - HashedEntry entry = mapEntry.getValue(); - HashedEntry.Type entryType = entry.getType(); - switch (entryType) { - case DIR: // cd - get - cd .. - queue.add(new Action(Action.Type.CD, name, entry)); - fillActionsQueue(queue, (HashedDir) entry); - queue.add(Action.CD_BACK); - break; - case FILE: // get - queue.add(new Action(Action.Type.GET, name, entry)); - break; - default: - throw new AssertionError("Unsupported hashed entry type: " + entryType.name()); - } - } - } - - private void updateState(String filePath, long fileDownloaded, long fileSize) { - if (stateCallback != null) { + private void updateState(String filePath, long fileDownloaded, long fileSize) + { + if (stateCallback != null) + { stateCallback.call(new State(filePath, fileDownloaded, fileSize, - totalDownloaded, totalSize, Duration.between(startTime, Instant.now()))); + totalDownloaded, totalSize, Duration.between(startTime, Instant.now()))); } } - public static final class Action extends StreamObject { + public static final class Action extends StreamObject + { public static final Action CD_BACK = new Action(Type.CD_BACK, null, null); public static final Action FINISH = new Action(Type.FINISH, null, null); @@ -257,55 +287,71 @@ public final String name; public final HashedEntry entry; - public Action(Type type, String name, HashedEntry entry) { + public Action(Type type, String name, HashedEntry entry) + { this.type = type; this.name = name; this.entry = entry; } - public Action(HInput input) throws IOException { + public Action(HInput input) throws IOException + { type = Type.read(input); name = type == Type.CD || type == Type.GET ? IOHelper.verifyFileName(input.readString(255)) : null; entry = null; } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { EnumSerializer.write(output, type); - if (type == Type.CD || type == Type.GET) { + if (type == Type.CD || type == Type.GET) + { output.writeString(name, 255); } } - public enum Type implements Itf { + public enum Type implements Itf + { CD(1), CD_BACK(2), GET(3), FINISH(255); private static final EnumSerializer SERIALIZER = new EnumSerializer<>(Type.class); private final int n; - Type(int n) { + Type(int n) + { this.n = n; } - @Override - public int getNumber() { - return n; + public static Type read(HInput input) throws IOException + { + return SERIALIZER.read(input); } - public static Type read(HInput input) throws IOException { - return SERIALIZER.read(input); + @Override + public int getNumber() + { + return n; } } } - public static final class State { - @LauncherAPI public final long fileDownloaded; - @LauncherAPI public final long fileSize; - @LauncherAPI public final long totalDownloaded; - @LauncherAPI public final long totalSize; - @LauncherAPI public final String filePath; - @LauncherAPI public final Duration duration; + public static final class State + { + @LauncherAPI + public final long fileDownloaded; + @LauncherAPI + public final long fileSize; + @LauncherAPI + public final long totalDownloaded; + @LauncherAPI + public final long totalSize; + @LauncherAPI + public final String filePath; + @LauncherAPI + public final Duration duration; - public State(String filePath, long fileDownloaded, long fileSize, long totalDownloaded, long totalSize, Duration duration) { + public State(String filePath, long fileDownloaded, long fileSize, long totalDownloaded, long totalSize, Duration duration) + { this.filePath = filePath; this.fileDownloaded = fileDownloaded; this.fileSize = fileSize; @@ -317,111 +363,134 @@ } @LauncherAPI - public double getBps() { + public double getBps() + { long seconds = duration.getSeconds(); - if (seconds == 0) { + if (seconds == 0) + { return -1.0D; // Otherwise will throw /0 exception } return totalDownloaded / (double) seconds; } @LauncherAPI - public Duration getEstimatedTime() { + public Duration getEstimatedTime() + { double bps = getBps(); - if (bps <= 0.0D) { + if (bps <= 0.0D) + { return null; // Otherwise will throw /0 exception } return Duration.ofSeconds((long) (getTotalRemaining() / bps)); } @LauncherAPI - public double getFileDownloadedKiB() { + public double getFileDownloadedKiB() + { return fileDownloaded / 1024.0D; } @LauncherAPI - public double getFileDownloadedMiB() { + public double getFileDownloadedMiB() + { return getFileDownloadedKiB() / 1024.0D; } @LauncherAPI - public double getFileDownloadedPart() { - if (fileSize == 0) { + public double getFileDownloadedPart() + { + if (fileSize == 0) + { return 0.0D; } return (double) fileDownloaded / fileSize; } @LauncherAPI - public long getFileRemaining() { + public long getFileRemaining() + { return fileSize - fileDownloaded; } @LauncherAPI - public double getFileRemainingKiB() { + public double getFileRemainingKiB() + { return getFileRemaining() / 1024.0D; } @LauncherAPI - public double getFileRemainingMiB() { + public double getFileRemainingMiB() + { return getFileRemainingKiB() / 1024.0D; } @LauncherAPI - public double getFileSizeKiB() { + public double getFileSizeKiB() + { return fileSize / 1024.0D; } @LauncherAPI - public double getFileSizeMiB() { + public double getFileSizeMiB() + { return getFileSizeKiB() / 1024.0D; } @LauncherAPI - public double getTotalDownloadedKiB() { + public double getTotalDownloadedKiB() + { return totalDownloaded / 1024.0D; } @LauncherAPI - public double getTotalDownloadedMiB() { + public double getTotalDownloadedMiB() + { return getTotalDownloadedKiB() / 1024.0D; } @LauncherAPI - public double getTotalDownloadedPart() { - if (totalSize == 0) { + public double getTotalDownloadedPart() + { + if (totalSize == 0) + { return 0.0D; } return (double) totalDownloaded / totalSize; } @LauncherAPI - public long getTotalRemaining() { + public long getTotalRemaining() + { return totalSize - totalDownloaded; } @LauncherAPI - public double getTotalRemainingKiB() { + public double getTotalRemainingKiB() + { return getTotalRemaining() / 1024.0D; } @LauncherAPI - public double getTotalRemainingMiB() { + public double getTotalRemainingMiB() + { return getTotalRemainingKiB() / 1024.0D; } @LauncherAPI - public double getTotalSizeKiB() { + public double getTotalSizeKiB() + { return totalSize / 1024.0D; } @LauncherAPI - public double getTotalSizeMiB() { + public double getTotalSizeMiB() + { return getTotalSizeKiB() / 1024.0D; } @FunctionalInterface - public interface Callback { + public interface Callback + { void call(State state); } } diff --git a/Launcher/source/request/uuid/BatchProfileByUsernameRequest.java b/Launcher/source/request/uuid/BatchProfileByUsernameRequest.java index 6d06707..15ceb80 100644 --- a/Launcher/source/request/uuid/BatchProfileByUsernameRequest.java +++ b/Launcher/source/request/uuid/BatchProfileByUsernameRequest.java @@ -1,7 +1,5 @@ package launcher.request.uuid; -import java.io.IOException; - import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.client.PlayerProfile; @@ -11,41 +9,52 @@ import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class BatchProfileByUsernameRequest extends Request { - @LauncherAPI public static final int MAX_BATCH_SIZE = 128; +import java.io.IOException; + +public final class BatchProfileByUsernameRequest extends Request +{ + @LauncherAPI + public static final int MAX_BATCH_SIZE = 128; private final String[] usernames; @LauncherAPI - public BatchProfileByUsernameRequest(Config config, String... usernames) throws IOException { + public BatchProfileByUsernameRequest(Config config, String... usernames) throws IOException + { super(config); this.usernames = usernames.clone(); IOHelper.verifyLength(this.usernames.length, MAX_BATCH_SIZE); - for (String username : this.usernames) { + for (String username : this.usernames) + { VerifyHelper.verifyUsername(username); } } @LauncherAPI - public BatchProfileByUsernameRequest(String... usernames) throws IOException { + public BatchProfileByUsernameRequest(String... usernames) throws IOException + { this(null, usernames); } @Override - public Type getType() { + public Type getType() + { return Type.BATCH_PROFILE_BY_USERNAME; } @Override - protected PlayerProfile[] requestDo(HInput input, HOutput output) throws IOException { + protected PlayerProfile[] requestDo(HInput input, HOutput output) throws IOException + { output.writeLength(usernames.length, MAX_BATCH_SIZE); - for (String username : usernames) { + for (String username : usernames) + { output.writeString(username, 64); } output.flush(); // Read profiles response PlayerProfile[] profiles = new PlayerProfile[usernames.length]; - for (int i = 0; i < profiles.length; i++) { + for (int i = 0; i < profiles.length; i++) + { profiles[i] = input.readBoolean() ? new PlayerProfile(input) : null; } diff --git a/Launcher/source/request/uuid/ProfileByUUIDRequest.java b/Launcher/source/request/uuid/ProfileByUUIDRequest.java index 56d3679..473e2ae 100644 --- a/Launcher/source/request/uuid/ProfileByUUIDRequest.java +++ b/Launcher/source/request/uuid/ProfileByUUIDRequest.java @@ -1,10 +1,5 @@ package launcher.request.uuid; -import java.io.IOException; -import java.util.Objects; -import java.util.UUID; - -import launcher.Launcher; import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.client.PlayerProfile; @@ -12,27 +7,36 @@ import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class ProfileByUUIDRequest extends Request { +import java.io.IOException; +import java.util.Objects; +import java.util.UUID; + +public final class ProfileByUUIDRequest extends Request +{ private final UUID uuid; @LauncherAPI - public ProfileByUUIDRequest(Config config, UUID uuid) { + public ProfileByUUIDRequest(Config config, UUID uuid) + { super(config); this.uuid = Objects.requireNonNull(uuid, "uuid"); } @LauncherAPI - public ProfileByUUIDRequest(UUID uuid) { + public ProfileByUUIDRequest(UUID uuid) + { this(null, uuid); } @Override - public Type getType() { + public Type getType() + { return Type.PROFILE_BY_UUID; } @Override - protected PlayerProfile requestDo(HInput input, HOutput output) throws IOException { + protected PlayerProfile requestDo(HInput input, HOutput output) throws IOException + { output.writeUUID(uuid); output.flush(); diff --git a/Launcher/source/request/uuid/ProfileByUsernameRequest.java b/Launcher/source/request/uuid/ProfileByUsernameRequest.java index c485e67..338e62c 100644 --- a/Launcher/source/request/uuid/ProfileByUsernameRequest.java +++ b/Launcher/source/request/uuid/ProfileByUsernameRequest.java @@ -1,7 +1,5 @@ package launcher.request.uuid; -import java.io.IOException; - import launcher.Launcher.Config; import launcher.LauncherAPI; import launcher.client.PlayerProfile; @@ -10,27 +8,34 @@ import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class ProfileByUsernameRequest extends Request { +import java.io.IOException; + +public final class ProfileByUsernameRequest extends Request +{ private final String username; @LauncherAPI - public ProfileByUsernameRequest(Config config, String username) { + public ProfileByUsernameRequest(Config config, String username) + { super(config); this.username = VerifyHelper.verifyUsername(username); } @LauncherAPI - public ProfileByUsernameRequest(String username) { + public ProfileByUsernameRequest(String username) + { this(null, username); } @Override - public Type getType() { + public Type getType() + { return Type.PROFILE_BY_USERNAME; } @Override - protected PlayerProfile requestDo(HInput input, HOutput output) throws IOException { + protected PlayerProfile requestDo(HInput input, HOutput output) throws IOException + { output.writeString(username, 64); output.flush(); diff --git a/Launcher/source/serialize/HInput.java b/Launcher/source/serialize/HInput.java index 1c8d868..b540760 100644 --- a/Launcher/source/serialize/HInput.java +++ b/Launcher/source/serialize/HInput.java @@ -1,5 +1,8 @@ package launcher.serialize; +import launcher.LauncherAPI; +import launcher.helper.IOHelper; + import java.io.ByteArrayInputStream; import java.io.EOFException; import java.io.IOException; @@ -8,41 +11,47 @@ import java.util.Objects; import java.util.UUID; -import launcher.LauncherAPI; -import launcher.helper.IOHelper; - -public final class HInput implements AutoCloseable { - @LauncherAPI public final InputStream stream; +public final class HInput implements AutoCloseable +{ + @LauncherAPI + public final InputStream stream; @LauncherAPI - public HInput(InputStream stream) { + public HInput(InputStream stream) + { this.stream = Objects.requireNonNull(stream, "stream"); } @LauncherAPI - public HInput(byte[] bytes) { + public HInput(byte[] bytes) + { stream = new ByteArrayInputStream(bytes); } @Override - public void close() throws IOException { + public void close() throws IOException + { stream.close(); } @LauncherAPI - public String readASCII(int maxBytes) throws IOException { + public String readASCII(int maxBytes) throws IOException + { return IOHelper.decodeASCII(readByteArray(maxBytes)); } @LauncherAPI - public BigInteger readBigInteger(int maxBytes) throws IOException { + public BigInteger readBigInteger(int maxBytes) throws IOException + { return new BigInteger(readByteArray(maxBytes)); } @LauncherAPI - public boolean readBoolean() throws IOException { + public boolean readBoolean() throws IOException + { int b = readUnsignedByte(); - switch (b) { + switch (b) + { case 0b0: return false; case 0b1: @@ -53,67 +62,81 @@ } @LauncherAPI - public byte[] readByteArray(int max) throws IOException { + public byte[] readByteArray(int max) throws IOException + { byte[] bytes = new byte[readLength(max)]; IOHelper.read(stream, bytes); return bytes; } @LauncherAPI - public int readInt() throws IOException { + public int readInt() throws IOException + { return (readUnsignedByte() << 24) + (readUnsignedByte() << 16) + (readUnsignedByte() << 8) + readUnsignedByte(); } @LauncherAPI - public int readLength(int max) throws IOException { - if (max < 0) { + public int readLength(int max) throws IOException + { + if (max < 0) + { return -max; } return IOHelper.verifyLength(readVarInt(), max); } @LauncherAPI - public long readLong() throws IOException { + public long readLong() throws IOException + { return (long) readInt() << 32 | readInt() & 0xFFFFFFFFL; } @LauncherAPI - public short readShort() throws IOException { + public short readShort() throws IOException + { return (short) ((readUnsignedByte() << 8) + readUnsignedByte()); } @LauncherAPI - public String readString(int maxBytes) throws IOException { + public String readString(int maxBytes) throws IOException + { return IOHelper.decode(readByteArray(maxBytes)); } @LauncherAPI - public UUID readUUID() throws IOException { + public UUID readUUID() throws IOException + { return new UUID(readLong(), readLong()); } @LauncherAPI - public int readUnsignedByte() throws IOException { + public int readUnsignedByte() throws IOException + { int b = stream.read(); - if (b < 0) { + if (b < 0) + { throw new EOFException("readUnsignedByte"); } return b; } @LauncherAPI - public int readUnsignedShort() throws IOException { + public int readUnsignedShort() throws IOException + { return Short.toUnsignedInt(readShort()); } @LauncherAPI - public int readVarInt() throws IOException { + public int readVarInt() throws IOException + { int shift = 0; int result = 0; - while (shift < Integer.SIZE) { + while (shift < Integer.SIZE) + { int b = readUnsignedByte(); result |= (b & 0x7F) << shift; - if ((b & 0x80) == 0) { + if ((b & 0x80) == 0) + { return result; } shift += 7; @@ -122,13 +145,16 @@ } @LauncherAPI - public long readVarLong() throws IOException { + public long readVarLong() throws IOException + { int shift = 0; long result = 0; - while (shift < Long.SIZE) { + while (shift < Long.SIZE) + { int b = readUnsignedByte(); result |= (long) (b & 0x7F) << shift; - if ((b & 0x80) == 0) { + if ((b & 0x80) == 0) + { return result; } shift += 7; diff --git a/Launcher/source/serialize/HOutput.java b/Launcher/source/serialize/HOutput.java index 813315a..87298ca 100644 --- a/Launcher/source/serialize/HOutput.java +++ b/Launcher/source/serialize/HOutput.java @@ -1,5 +1,8 @@ package launcher.serialize; +import launcher.LauncherAPI; +import launcher.helper.IOHelper; + import java.io.Flushable; import java.io.IOException; import java.io.OutputStream; @@ -7,50 +10,57 @@ import java.util.Objects; import java.util.UUID; -import launcher.LauncherAPI; -import launcher.helper.IOHelper; - -public final class HOutput implements AutoCloseable, Flushable { - @LauncherAPI public final OutputStream stream; +public final class HOutput implements AutoCloseable, Flushable +{ + @LauncherAPI + public final OutputStream stream; @LauncherAPI - public HOutput(OutputStream stream) { + public HOutput(OutputStream stream) + { this.stream = Objects.requireNonNull(stream, "stream"); } @Override - public void close() throws IOException { + public void close() throws IOException + { stream.close(); } @Override - public void flush() throws IOException { + public void flush() throws IOException + { stream.flush(); } @LauncherAPI - public void writeASCII(String s, int maxBytes) throws IOException { + public void writeASCII(String s, int maxBytes) throws IOException + { writeByteArray(IOHelper.encodeASCII(s), maxBytes); } @LauncherAPI - public void writeBigInteger(BigInteger bi, int max) throws IOException { + public void writeBigInteger(BigInteger bi, int max) throws IOException + { writeByteArray(bi.toByteArray(), max); } @LauncherAPI - public void writeBoolean(boolean b) throws IOException { + public void writeBoolean(boolean b) throws IOException + { writeUnsignedByte(b ? 0b1 : 0b0); } @LauncherAPI - public void writeByteArray(byte[] bytes, int max) throws IOException { + public void writeByteArray(byte[] bytes, int max) throws IOException + { writeLength(bytes.length, max); stream.write(bytes); } @LauncherAPI - public void writeInt(int i) throws IOException { + public void writeInt(int i) throws IOException + { writeUnsignedByte(i >>> 24 & 0xFF); writeUnsignedByte(i >>> 16 & 0xFF); writeUnsignedByte(i >>> 8 & 0xFF); @@ -58,44 +68,53 @@ } @LauncherAPI - public void writeLength(int length, int max) throws IOException { + public void writeLength(int length, int max) throws IOException + { IOHelper.verifyLength(length, max); - if (max >= 0) { + if (max >= 0) + { writeVarInt(length); } } @LauncherAPI - public void writeLong(long l) throws IOException { + public void writeLong(long l) throws IOException + { writeInt((int) (l >> 32)); writeInt((int) l); } @LauncherAPI - public void writeShort(short s) throws IOException { + public void writeShort(short s) throws IOException + { writeUnsignedByte(s >>> 8 & 0xFF); writeUnsignedByte(s & 0xFF); } @LauncherAPI - public void writeString(String s, int maxBytes) throws IOException { + public void writeString(String s, int maxBytes) throws IOException + { writeByteArray(IOHelper.encode(s), maxBytes); } @LauncherAPI - public void writeUUID(UUID uuid) throws IOException { + public void writeUUID(UUID uuid) throws IOException + { writeLong(uuid.getMostSignificantBits()); writeLong(uuid.getLeastSignificantBits()); } @LauncherAPI - public void writeUnsignedByte(int b) throws IOException { + public void writeUnsignedByte(int b) throws IOException + { stream.write(b); } @LauncherAPI - public void writeVarInt(int i) throws IOException { - while ((i & ~0x7FL) != 0) { + public void writeVarInt(int i) throws IOException + { + while ((i & ~0x7FL) != 0) + { writeUnsignedByte(i & 0x7F | 0x80); i >>>= 7; } @@ -103,8 +122,10 @@ } @LauncherAPI - public void writeVarLong(long l) throws IOException { - while ((l & ~0x7FL) != 0) { + public void writeVarLong(long l) throws IOException + { + while ((l & ~0x7FL) != 0) + { writeUnsignedByte((int) l & 0x7F | 0x80); l >>>= 7; } diff --git a/Launcher/source/serialize/config/ConfigObject.java b/Launcher/source/serialize/config/ConfigObject.java index 9d7d902..183c2aa 100644 --- a/Launcher/source/serialize/config/ConfigObject.java +++ b/Launcher/source/serialize/config/ConfigObject.java @@ -1,28 +1,33 @@ package launcher.serialize.config; -import java.io.IOException; -import java.util.Objects; - import launcher.LauncherAPI; import launcher.serialize.HOutput; import launcher.serialize.config.entry.BlockConfigEntry; import launcher.serialize.stream.StreamObject; -public abstract class ConfigObject extends StreamObject { - @LauncherAPI public final BlockConfigEntry block; +import java.io.IOException; +import java.util.Objects; + +public abstract class ConfigObject extends StreamObject +{ + @LauncherAPI + public final BlockConfigEntry block; @LauncherAPI - protected ConfigObject(BlockConfigEntry block) { + protected ConfigObject(BlockConfigEntry block) + { this.block = Objects.requireNonNull(block, "block"); } @Override - public final void write(HOutput output) throws IOException { + public final void write(HOutput output) throws IOException + { block.write(output); } @FunctionalInterface - public interface Adapter { + public interface Adapter + { @LauncherAPI O convert(BlockConfigEntry entry); } diff --git a/Launcher/source/serialize/config/TextConfigReader.java b/Launcher/source/serialize/config/TextConfigReader.java index 2696e9d..214780e 100644 --- a/Launcher/source/serialize/config/TextConfigReader.java +++ b/Launcher/source/serialize/config/TextConfigReader.java @@ -1,5 +1,9 @@ package launcher.serialize.config; +import launcher.LauncherAPI; +import launcher.helper.VerifyHelper; +import launcher.serialize.config.entry.*; + import java.io.IOException; import java.io.LineNumberReader; import java.io.Reader; @@ -8,55 +12,61 @@ import java.util.List; import java.util.Map; -import launcher.LauncherAPI; -import launcher.helper.VerifyHelper; -import launcher.serialize.config.entry.BlockConfigEntry; -import launcher.serialize.config.entry.BooleanConfigEntry; -import launcher.serialize.config.entry.ConfigEntry; -import launcher.serialize.config.entry.IntegerConfigEntry; -import launcher.serialize.config.entry.ListConfigEntry; -import launcher.serialize.config.entry.StringConfigEntry; - -public final class TextConfigReader { +public final class TextConfigReader +{ private final LineNumberReader reader; private final boolean ro; private String skipped; private int ch = -1; - private TextConfigReader(Reader reader, boolean ro) { + private TextConfigReader(Reader reader, boolean ro) + { this.reader = new LineNumberReader(reader); this.reader.setLineNumber(1); this.ro = ro; } - private IOException newIOException(String message) { + @LauncherAPI + public static BlockConfigEntry read(Reader reader, boolean ro) throws IOException + { + return new TextConfigReader(reader, ro).readBlock(0); + } + + private IOException newIOException(String message) + { return new IOException(message + " (line " + reader.getLineNumber() + ')'); } - private int nextChar(boolean eof) throws IOException { + private int nextChar(boolean eof) throws IOException + { ch = reader.read(); - if (eof && ch < 0) { + if (eof && ch < 0) + { throw newIOException("Unexpected end of config"); } return ch; } - private int nextClean(boolean eof) throws IOException { + private int nextClean(boolean eof) throws IOException + { nextChar(eof); return skipWhitespace(eof); } - private BlockConfigEntry readBlock(int cc) throws IOException { + private BlockConfigEntry readBlock(int cc) throws IOException + { Map> map = new LinkedHashMap<>(16); // Read block entries boolean brackets = ch == '{'; - while (nextClean(brackets) >= 0 && (!brackets || ch != '}')) { + while (nextClean(brackets) >= 0 && (!brackets || ch != '}')) + { String preNameComment = skipped; // Read entry name String name = readToken(); - if (skipWhitespace(true) != ':') { + if (skipWhitespace(true) != ':') + { throw newIOException("Value start expected"); } String postNameComment = skipped; @@ -65,7 +75,8 @@ nextClean(true); String preValueComment = skipped; ConfigEntry entry = readEntry(4); - if (skipWhitespace(true) != ';') { + if (skipWhitespace(true) != ';') + { throw newIOException("Value end expected"); } @@ -76,7 +87,8 @@ entry.setComment(3, skipped); // Try add entry to map - if (map.put(name, entry) != null) { + if (map.put(name, entry) != null) + { throw newIOException(String.format("Duplicate config entry: '%s'", name)); } } @@ -88,9 +100,11 @@ return block; } - private ConfigEntry readEntry(int cc) throws IOException { + private ConfigEntry readEntry(int cc) throws IOException + { // Try detect type by first char - switch (ch) { + switch (ch) + { case '"': // String return readString(cc); case '[': // List @@ -102,13 +116,15 @@ } // Possibly integer value - if (ch == '-' || ch >= '0' && ch <= '9') { + if (ch == '-' || ch >= '0' && ch <= '9') + { return readInteger(cc); } // Statement? String statement = readToken(); - switch (statement) { + switch (statement) + { case "true": return new BooleanConfigEntry(Boolean.TRUE, ro, cc); case "false": @@ -118,17 +134,20 @@ } } - private ConfigEntry readInteger(int cc) throws IOException { + private ConfigEntry readInteger(int cc) throws IOException + { return new IntegerConfigEntry(Integer.parseInt(readToken()), ro, cc); } - private ConfigEntry>> readList(int cc) throws IOException { + private ConfigEntry>> readList(int cc) throws IOException + { List> listValue = new ArrayList<>(16); // Read list elements boolean hasNextElement = nextClean(true) != ']'; String preValueComment = skipped; - while (hasNextElement) { + while (hasNextElement) + { ConfigEntry element = readEntry(2); hasNextElement = skipWhitespace(true) != ']'; element.setComment(0, preValueComment); @@ -136,8 +155,10 @@ listValue.add(element); // Prepare for next element read - if (hasNextElement) { - if (ch != ',') { + if (hasNextElement) + { + if (ch != ',') + { throw newIOException("Comma expected"); } nextClean(true); @@ -148,7 +169,8 @@ // Set in-list comment (if no elements) boolean additional = listValue.isEmpty(); ConfigEntry>> list = new ListConfigEntry(listValue, ro, additional ? cc + 1 : cc); - if (additional) { + if (additional) + { list.setComment(cc, skipped); } @@ -157,18 +179,22 @@ return list; } - private ConfigEntry readString(int cc) throws IOException { + private ConfigEntry readString(int cc) throws IOException + { StringBuilder builder = new StringBuilder(); // Read string chars - while (nextChar(true) != '"') { - switch (ch) { + while (nextChar(true) != '"') + { + switch (ch) + { case '\r': case '\n': // String termination throw newIOException("String termination"); case '\\': int next = nextChar(true); - switch (next) { + switch (next) + { case 't': builder.append('\t'); break; @@ -203,33 +229,41 @@ return new StringConfigEntry(builder.toString(), ro, cc); } - private String readToken() throws IOException { + private String readToken() throws IOException + { // Read token StringBuilder builder = new StringBuilder(); - while (VerifyHelper.isValidIDNameChar(ch)) { + while (VerifyHelper.isValidIDNameChar(ch)) + { builder.append((char) ch); nextChar(false); } // Return token as string String token = builder.toString(); - if (token.isEmpty()) { + if (token.isEmpty()) + { throw newIOException("Not a token"); } return token; } - private void skipComment(StringBuilder skippedBuilder, boolean eof) throws IOException { - while (ch >= 0 && ch != '\r' && ch != '\n') { + private void skipComment(StringBuilder skippedBuilder, boolean eof) throws IOException + { + while (ch >= 0 && ch != '\r' && ch != '\n') + { skippedBuilder.append((char) ch); nextChar(eof); } } - private int skipWhitespace(boolean eof) throws IOException { + private int skipWhitespace(boolean eof) throws IOException + { StringBuilder skippedBuilder = new StringBuilder(); - while (Character.isWhitespace(ch) || ch == '#') { - if (ch == '#') { + while (Character.isWhitespace(ch) || ch == '#') + { + if (ch == '#') + { skipComment(skippedBuilder, eof); continue; } @@ -239,9 +273,4 @@ skipped = skippedBuilder.toString(); return ch; } - - @LauncherAPI - public static BlockConfigEntry read(Reader reader, boolean ro) throws IOException { - return new TextConfigReader(reader, ro).readBlock(0); - } } diff --git a/Launcher/source/serialize/config/TextConfigWriter.java b/Launcher/source/serialize/config/TextConfigWriter.java index 324ee48..bc23216 100644 --- a/Launcher/source/serialize/config/TextConfigWriter.java +++ b/Launcher/source/serialize/config/TextConfigWriter.java @@ -1,38 +1,44 @@ package launcher.serialize.config; +import launcher.LauncherAPI; +import launcher.serialize.config.entry.*; +import launcher.serialize.config.entry.ConfigEntry.Type; + import java.io.IOException; import java.io.Writer; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import launcher.LauncherAPI; -import launcher.serialize.config.entry.BlockConfigEntry; -import launcher.serialize.config.entry.BooleanConfigEntry; -import launcher.serialize.config.entry.ConfigEntry; -import launcher.serialize.config.entry.ConfigEntry.Type; -import launcher.serialize.config.entry.IntegerConfigEntry; -import launcher.serialize.config.entry.ListConfigEntry; -import launcher.serialize.config.entry.StringConfigEntry; - -public final class TextConfigWriter { +public final class TextConfigWriter +{ private final Writer writer; private final boolean comments; - private TextConfigWriter(Writer writer, boolean comments) { + private TextConfigWriter(Writer writer, boolean comments) + { this.writer = writer; this.comments = comments; } - private void writeBlock(BlockConfigEntry block, boolean brackets) throws IOException { + @LauncherAPI + public static void write(BlockConfigEntry block, Writer writer, boolean comments) throws IOException + { + new TextConfigWriter(writer, comments).writeBlock(block, false); + } + + private void writeBlock(BlockConfigEntry block, boolean brackets) throws IOException + { // Write start bracket - if (brackets) { + if (brackets) + { writer.write('{'); } // Write block entries Map> map = block.getValue(); - for (Entry> mapEntry : map.entrySet()) { + for (Entry> mapEntry : map.entrySet()) + { String name = mapEntry.getKey(); ConfigEntry entry = mapEntry.getValue(); @@ -51,24 +57,30 @@ writeComment(block.getComment(-1)); // Write end bracket - if (brackets) { + if (brackets) + { writer.write('}'); } } - private void writeBoolean(BooleanConfigEntry entry) throws IOException { + private void writeBoolean(BooleanConfigEntry entry) throws IOException + { writer.write(entry.getValue().toString()); } - private void writeComment(String comment) throws IOException { - if (comments && comment != null) { + private void writeComment(String comment) throws IOException + { + if (comments && comment != null) + { writer.write(comment); } } - private void writeEntry(ConfigEntry entry) throws IOException { + private void writeEntry(ConfigEntry entry) throws IOException + { Type type = entry.getType(); - switch (type) { + switch (type) + { case BLOCK: writeBlock((BlockConfigEntry) entry, true); break; @@ -89,17 +101,21 @@ } } - private void writeInteger(IntegerConfigEntry entry) throws IOException { + private void writeInteger(IntegerConfigEntry entry) throws IOException + { writer.write(Integer.toString(entry.getValue())); } - private void writeList(ListConfigEntry entry) throws IOException { + private void writeList(ListConfigEntry entry) throws IOException + { writer.write('['); // Write list elements List> value = entry.getValue(); - for (int i = 0; i < value.size(); i++) { - if (i > 0) { + for (int i = 0; i < value.size(); i++) + { + if (i > 0) + { writer.write(','); } @@ -115,14 +131,17 @@ writer.write(']'); } - private void writeString(StringConfigEntry entry) throws IOException { + private void writeString(StringConfigEntry entry) throws IOException + { writer.write('"'); // Quote string String s = entry.getValue(); - for (int i = 0; i < s.length(); i++) { + for (int i = 0; i < s.length(); i++) + { char ch = s.charAt(i); - switch (ch) { + switch (ch) + { case '\t': writer.write("\\t"); break; @@ -152,9 +171,4 @@ // Write end quote writer.write('"'); } - - @LauncherAPI - public static void write(BlockConfigEntry block, Writer writer, boolean comments) throws IOException { - new TextConfigWriter(writer, comments).writeBlock(block, false); - } } diff --git a/Launcher/source/serialize/config/entry/BlockConfigEntry.java b/Launcher/source/serialize/config/entry/BlockConfigEntry.java index c6d4857..8ab925f 100644 --- a/Launcher/source/serialize/config/entry/BlockConfigEntry.java +++ b/Launcher/source/serialize/config/entry/BlockConfigEntry.java @@ -1,47 +1,65 @@ package launcher.serialize.config.entry; -import java.io.IOException; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NoSuchElementException; -import java.util.Set; - import launcher.LauncherAPI; import launcher.helper.VerifyHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class BlockConfigEntry extends ConfigEntry>> { +import java.io.IOException; +import java.util.*; +import java.util.Map.Entry; + +public final class BlockConfigEntry extends ConfigEntry>> +{ @LauncherAPI - public BlockConfigEntry(Map> map, boolean ro, int cc) { + public BlockConfigEntry(Map> map, boolean ro, int cc) + { super(map, ro, cc); } @LauncherAPI - public BlockConfigEntry(int cc) { + public BlockConfigEntry(int cc) + { super(Collections.emptyMap(), false, cc); } @LauncherAPI - public BlockConfigEntry(HInput input, boolean ro) throws IOException { + public BlockConfigEntry(HInput input, boolean ro) throws IOException + { super(readMap(input, ro), ro, 0); } + private static Map> readMap(HInput input, boolean ro) throws IOException + { + int entriesCount = input.readLength(0); + Map> map = new LinkedHashMap<>(entriesCount); + for (int i = 0; i < entriesCount; i++) + { + String name = VerifyHelper.verifyIDName(input.readString(255)); + ConfigEntry entry = readEntry(input, ro); + + // Try add entry to map + VerifyHelper.putIfAbsent(map, name, entry, String.format("Duplicate config entry: '%s'", name)); + } + return map; + } + @Override - public Type getType() { + public Type getType() + { return Type.BLOCK; } @Override - public Map> getValue() { + public Map> getValue() + { Map> value = super.getValue(); return ro ? value : Collections.unmodifiableMap(value); // Already RO } @Override - protected void uncheckedSetValue(Map> value) { + protected void uncheckedSetValue(Map> value) + { Map> newValue = new LinkedHashMap<>(value); newValue.keySet().stream().forEach(VerifyHelper::verifyIDName); @@ -50,60 +68,56 @@ } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { Set>> entries = getValue().entrySet(); output.writeLength(entries.size(), 0); - for (Entry> mapEntry : entries) { + for (Entry> mapEntry : entries) + { output.writeString(mapEntry.getKey(), 255); writeEntry(mapEntry.getValue(), output); } } @LauncherAPI - public void clear() { + public void clear() + { super.getValue().clear(); } @LauncherAPI - public > E getEntry(String name, Class clazz) { + public > E getEntry(String name, Class clazz) + { Map> map = super.getValue(); ConfigEntry value = map.get(name); - if (!clazz.isInstance(value)) { + if (!clazz.isInstance(value)) + { throw new NoSuchElementException(name); } return clazz.cast(value); } @LauncherAPI - public > V getEntryValue(String name, Class clazz) { + public > V getEntryValue(String name, Class clazz) + { return getEntry(name, clazz).getValue(); } @LauncherAPI - public boolean hasEntry(String name) { + public boolean hasEntry(String name) + { return getValue().containsKey(name); } @LauncherAPI - public void remove(String name) { + public void remove(String name) + { super.getValue().remove(name); } @LauncherAPI - public void setEntry(String name, ConfigEntry entry) { + public void setEntry(String name, ConfigEntry entry) + { super.getValue().put(VerifyHelper.verifyIDName(name), entry); } - - private static Map> readMap(HInput input, boolean ro) throws IOException { - int entriesCount = input.readLength(0); - Map> map = new LinkedHashMap<>(entriesCount); - for (int i = 0; i < entriesCount; i++) { - String name = VerifyHelper.verifyIDName(input.readString(255)); - ConfigEntry entry = readEntry(input, ro); - - // Try add entry to map - VerifyHelper.putIfAbsent(map, name, entry, String.format("Duplicate config entry: '%s'", name)); - } - return map; - } } diff --git a/Launcher/source/serialize/config/entry/BooleanConfigEntry.java b/Launcher/source/serialize/config/entry/BooleanConfigEntry.java index a7a044d..bb69615 100644 --- a/Launcher/source/serialize/config/entry/BooleanConfigEntry.java +++ b/Launcher/source/serialize/config/entry/BooleanConfigEntry.java @@ -1,29 +1,34 @@ package launcher.serialize.config.entry; -import java.io.IOException; - import launcher.LauncherAPI; import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class BooleanConfigEntry extends ConfigEntry { +import java.io.IOException; + +public final class BooleanConfigEntry extends ConfigEntry +{ @LauncherAPI - public BooleanConfigEntry(boolean value, boolean ro, int cc) { + public BooleanConfigEntry(boolean value, boolean ro, int cc) + { super(value, ro, cc); } @LauncherAPI - public BooleanConfigEntry(HInput input, boolean ro) throws IOException { + public BooleanConfigEntry(HInput input, boolean ro) throws IOException + { this(input.readBoolean(), ro, 0); } @Override - public Type getType() { + public Type getType() + { return Type.BOOLEAN; } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { output.writeBoolean(getValue()); } } diff --git a/Launcher/source/serialize/config/entry/ConfigEntry.java b/Launcher/source/serialize/config/entry/ConfigEntry.java index c8e8de6..3cdbf59 100644 --- a/Launcher/source/serialize/config/entry/ConfigEntry.java +++ b/Launcher/source/serialize/config/entry/ConfigEntry.java @@ -1,8 +1,5 @@ package launcher.serialize.config.entry; -import java.io.IOException; -import java.util.Objects; - import launcher.LauncherAPI; import launcher.serialize.HInput; import launcher.serialize.HOutput; @@ -10,59 +7,28 @@ import launcher.serialize.stream.EnumSerializer.Itf; import launcher.serialize.stream.StreamObject; -public abstract class ConfigEntry extends StreamObject { - @LauncherAPI public final boolean ro; +import java.io.IOException; +import java.util.Objects; + +public abstract class ConfigEntry extends StreamObject +{ + @LauncherAPI + public final boolean ro; private final String[] comments; private V value; - protected ConfigEntry(V value, boolean ro, int cc) { + protected ConfigEntry(V value, boolean ro, int cc) + { this.ro = ro; comments = new String[cc]; uncheckedSetValue(value); } - @LauncherAPI - public abstract Type getType(); - - @LauncherAPI - public final String getComment(int i) { - if (i < 0) { - i += comments.length; - } - return i >= comments.length ? null : comments[i]; - } - - @LauncherAPI - @SuppressWarnings("DesignForExtension") - public V getValue() { - return value; - } - - @LauncherAPI - public final void setValue(V value) { - ensureWritable(); - uncheckedSetValue(value); - } - - @LauncherAPI - public final void setComment(int i, String comment) { - comments[i] = comment; - } - - protected final void ensureWritable() { - if (ro) { - throw new UnsupportedOperationException("Read-only"); - } - } - - @SuppressWarnings("DesignForExtension") - protected void uncheckedSetValue(V value) { - this.value = Objects.requireNonNull(value, "value"); - } - - protected static ConfigEntry readEntry(HInput input, boolean ro) throws IOException { + protected static ConfigEntry readEntry(HInput input, boolean ro) throws IOException + { Type type = Type.read(input); - switch (type) { + switch (type) + { case BOOLEAN: return new BooleanConfigEntry(input, ro); case INTEGER: @@ -78,28 +44,80 @@ } } - protected static void writeEntry(ConfigEntry entry, HOutput output) throws IOException { + protected static void writeEntry(ConfigEntry entry, HOutput output) throws IOException + { EnumSerializer.write(output, entry.getType()); entry.write(output); } @LauncherAPI - public enum Type implements Itf { + public abstract Type getType(); + + @LauncherAPI + public final String getComment(int i) + { + if (i < 0) + { + i += comments.length; + } + return i >= comments.length ? null : comments[i]; + } + + @LauncherAPI + @SuppressWarnings("DesignForExtension") + public V getValue() + { + return value; + } + + @LauncherAPI + public final void setValue(V value) + { + ensureWritable(); + uncheckedSetValue(value); + } + + @LauncherAPI + public final void setComment(int i, String comment) + { + comments[i] = comment; + } + + protected final void ensureWritable() + { + if (ro) + { + throw new UnsupportedOperationException("Read-only"); + } + } + + @SuppressWarnings("DesignForExtension") + protected void uncheckedSetValue(V value) + { + this.value = Objects.requireNonNull(value, "value"); + } + + @LauncherAPI + public enum Type implements Itf + { BLOCK(1), BOOLEAN(2), INTEGER(3), STRING(4), LIST(5); private static final EnumSerializer SERIALIZER = new EnumSerializer<>(Type.class); private final int n; - Type(int n) { + Type(int n) + { this.n = n; } - @Override - public int getNumber() { - return n; + public static Type read(HInput input) throws IOException + { + return SERIALIZER.read(input); } - public static Type read(HInput input) throws IOException { - return SERIALIZER.read(input); + @Override + public int getNumber() + { + return n; } } } diff --git a/Launcher/source/serialize/config/entry/IntegerConfigEntry.java b/Launcher/source/serialize/config/entry/IntegerConfigEntry.java index 210aa6a..7d5e27d 100644 --- a/Launcher/source/serialize/config/entry/IntegerConfigEntry.java +++ b/Launcher/source/serialize/config/entry/IntegerConfigEntry.java @@ -1,29 +1,34 @@ package launcher.serialize.config.entry; -import java.io.IOException; - import launcher.LauncherAPI; import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class IntegerConfigEntry extends ConfigEntry { +import java.io.IOException; + +public final class IntegerConfigEntry extends ConfigEntry +{ @LauncherAPI - public IntegerConfigEntry(int value, boolean ro, int cc) { + public IntegerConfigEntry(int value, boolean ro, int cc) + { super(value, ro, cc); } @LauncherAPI - public IntegerConfigEntry(HInput input, boolean ro) throws IOException { + public IntegerConfigEntry(HInput input, boolean ro) throws IOException + { this(input.readVarInt(), ro, 0); } @Override - public Type getType() { + public Type getType() + { return Type.INTEGER; } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { output.writeVarInt(getValue()); } } diff --git a/Launcher/source/serialize/config/entry/ListConfigEntry.java b/Launcher/source/serialize/config/entry/ListConfigEntry.java index 8b0b9db..0874f7f 100644 --- a/Launcher/source/serialize/config/entry/ListConfigEntry.java +++ b/Launcher/source/serialize/config/entry/ListConfigEntry.java @@ -1,64 +1,76 @@ package launcher.serialize.config.entry; +import launcher.LauncherAPI; +import launcher.serialize.HInput; +import launcher.serialize.HOutput; + import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.stream.Stream; -import launcher.LauncherAPI; -import launcher.serialize.HInput; -import launcher.serialize.HOutput; - -public final class ListConfigEntry extends ConfigEntry>> { +public final class ListConfigEntry extends ConfigEntry>> +{ @LauncherAPI - public ListConfigEntry(List> value, boolean ro, int cc) { + public ListConfigEntry(List> value, boolean ro, int cc) + { super(value, ro, cc); } @LauncherAPI - public ListConfigEntry(HInput input, boolean ro) throws IOException { + public ListConfigEntry(HInput input, boolean ro) throws IOException + { super(readList(input, ro), ro, 0); } + private static List> readList(HInput input, boolean ro) throws IOException + { + int elementsCount = input.readLength(0); + List> list = new ArrayList<>(elementsCount); + for (int i = 0; i < elementsCount; i++) + { + list.add(readEntry(input, ro)); + } + return list; + } + @Override - public Type getType() { + public Type getType() + { return Type.LIST; } @Override - protected void uncheckedSetValue(List> value) { + protected void uncheckedSetValue(List> value) + { List> list = new ArrayList<>(value); super.uncheckedSetValue(ro ? Collections.unmodifiableList(list) : list); } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { List> value = getValue(); output.writeLength(value.size(), 0); - for (ConfigEntry element : value) { + for (ConfigEntry element : value) + { writeEntry(element, output); } } @LauncherAPI - public > Stream stream(Class clazz) { + public > Stream stream(Class clazz) + { return getValue().stream().map(clazz::cast).map(ConfigEntry::getValue); } @LauncherAPI - public void verifyOfType(Type type) { - if (getValue().stream().anyMatch(e -> e.getType() != type)) { + public void verifyOfType(Type type) + { + if (getValue().stream().anyMatch(e -> e.getType() != type)) + { throw new IllegalArgumentException("List type mismatch: " + type.name()); } } - - private static List> readList(HInput input, boolean ro) throws IOException { - int elementsCount = input.readLength(0); - List> list = new ArrayList<>(elementsCount); - for (int i = 0; i < elementsCount; i++) { - list.add(readEntry(input, ro)); - } - return list; - } } diff --git a/Launcher/source/serialize/config/entry/StringConfigEntry.java b/Launcher/source/serialize/config/entry/StringConfigEntry.java index 9afa8e1..ed6e259 100644 --- a/Launcher/source/serialize/config/entry/StringConfigEntry.java +++ b/Launcher/source/serialize/config/entry/StringConfigEntry.java @@ -1,34 +1,40 @@ package launcher.serialize.config.entry; -import java.io.IOException; - import launcher.LauncherAPI; import launcher.serialize.HInput; import launcher.serialize.HOutput; -public final class StringConfigEntry extends ConfigEntry { +import java.io.IOException; + +public final class StringConfigEntry extends ConfigEntry +{ @LauncherAPI - public StringConfigEntry(String value, boolean ro, int cc) { + public StringConfigEntry(String value, boolean ro, int cc) + { super(value, ro, cc); } @LauncherAPI - public StringConfigEntry(HInput input, boolean ro) throws IOException { + public StringConfigEntry(HInput input, boolean ro) throws IOException + { this(input.readString(0), ro, 0); } @Override - public Type getType() { + public Type getType() + { return Type.STRING; } @Override - protected void uncheckedSetValue(String value) { + protected void uncheckedSetValue(String value) + { super.uncheckedSetValue(value); } @Override - public void write(HOutput output) throws IOException { + public void write(HOutput output) throws IOException + { output.writeString(getValue(), 0); } } diff --git a/Launcher/source/serialize/signed/SignedBytesHolder.java b/Launcher/source/serialize/signed/SignedBytesHolder.java index dec3594..8538ad4 100644 --- a/Launcher/source/serialize/signed/SignedBytesHolder.java +++ b/Launcher/source/serialize/signed/SignedBytesHolder.java @@ -1,51 +1,58 @@ package launcher.serialize.signed; -import java.io.IOException; -import java.security.SignatureException; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; - import launcher.LauncherAPI; import launcher.helper.SecurityHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; import launcher.serialize.stream.StreamObject; -public class SignedBytesHolder extends StreamObject { +import java.io.IOException; +import java.security.SignatureException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; + +public class SignedBytesHolder extends StreamObject +{ protected final byte[] bytes; private final byte[] sign; @LauncherAPI - public SignedBytesHolder(HInput input, RSAPublicKey publicKey) throws IOException, SignatureException { + public SignedBytesHolder(HInput input, RSAPublicKey publicKey) throws IOException, SignatureException + { this(input.readByteArray(0), input.readByteArray(-SecurityHelper.RSA_KEY_LENGTH), publicKey); } @LauncherAPI - public SignedBytesHolder(byte[] bytes, byte[] sign, RSAPublicKey publicKey) throws SignatureException { + public SignedBytesHolder(byte[] bytes, byte[] sign, RSAPublicKey publicKey) throws SignatureException + { SecurityHelper.verifySign(bytes, sign, publicKey); this.bytes = bytes.clone(); this.sign = sign.clone(); } @LauncherAPI - public SignedBytesHolder(byte[] bytes, RSAPrivateKey privateKey) { + public SignedBytesHolder(byte[] bytes, RSAPrivateKey privateKey) + { this.bytes = bytes.clone(); sign = SecurityHelper.sign(bytes, privateKey); } @Override - public final void write(HOutput output) throws IOException { + public final void write(HOutput output) throws IOException + { output.writeByteArray(bytes, 0); output.writeByteArray(sign, -SecurityHelper.RSA_KEY_LENGTH); } @LauncherAPI - public final byte[] getBytes() { + public final byte[] getBytes() + { return bytes.clone(); } @LauncherAPI - public final byte[] getSign() { + public final byte[] getSign() + { return sign.clone(); } } diff --git a/Launcher/source/serialize/signed/SignedObjectHolder.java b/Launcher/source/serialize/signed/SignedObjectHolder.java index 5acd2de..e2a5874 100644 --- a/Launcher/source/serialize/signed/SignedObjectHolder.java +++ b/Launcher/source/serialize/signed/SignedObjectHolder.java @@ -1,47 +1,56 @@ package launcher.serialize.signed; +import launcher.LauncherAPI; +import launcher.serialize.HInput; +import launcher.serialize.stream.StreamObject; + import java.io.IOException; import java.security.SignatureException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; -import launcher.LauncherAPI; -import launcher.serialize.HInput; -import launcher.serialize.stream.StreamObject; - -public final class SignedObjectHolder extends SignedBytesHolder { - @LauncherAPI public final O object; +public final class SignedObjectHolder extends SignedBytesHolder +{ + @LauncherAPI + public final O object; @LauncherAPI - public SignedObjectHolder(HInput input, RSAPublicKey publicKey, Adapter adapter) throws IOException, SignatureException { + public SignedObjectHolder(HInput input, RSAPublicKey publicKey, Adapter adapter) throws IOException, SignatureException + { super(input, publicKey); object = newInstance(adapter); } @LauncherAPI - public SignedObjectHolder(O object, RSAPrivateKey privateKey) throws IOException { + public SignedObjectHolder(O object, RSAPrivateKey privateKey) throws IOException + { super(object.write(), privateKey); this.object = object; } @Override - public boolean equals(Object obj) { + public boolean equals(Object obj) + { return obj instanceof SignedObjectHolder && object.equals(((SignedObjectHolder) obj).object); } @Override - public int hashCode() { + public int hashCode() + { return object.hashCode(); } @Override - public String toString() { + public String toString() + { return object.toString(); } @LauncherAPI - public O newInstance(Adapter adapter) throws IOException { - try (HInput input = new HInput(bytes)) { + public O newInstance(Adapter adapter) throws IOException + { + try (HInput input = new HInput(bytes)) + { return adapter.convert(input); } } diff --git a/Launcher/source/serialize/stream/EnumSerializer.java b/Launcher/source/serialize/stream/EnumSerializer.java index f6b2de2..1640f26 100644 --- a/Launcher/source/serialize/stream/EnumSerializer.java +++ b/Launcher/source/serialize/stream/EnumSerializer.java @@ -1,38 +1,44 @@ package launcher.serialize.stream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - import launcher.LauncherAPI; import launcher.helper.VerifyHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; import launcher.serialize.stream.EnumSerializer.Itf; -public final class EnumSerializer & Itf> { +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public final class EnumSerializer & Itf> +{ private final Map map = new HashMap<>(16); @LauncherAPI - public EnumSerializer(Class clazz) { - for (E e : clazz.getEnumConstants()) { + public EnumSerializer(Class clazz) + { + for (E e : clazz.getEnumConstants()) + { VerifyHelper.putIfAbsent(map, e.getNumber(), e, "Duplicate number for enum constant " + e.name()); } } @LauncherAPI - public E read(HInput input) throws IOException { + public static void write(HOutput output, Itf itf) throws IOException + { + output.writeVarInt(itf.getNumber()); + } + + @LauncherAPI + public E read(HInput input) throws IOException + { int n = input.readVarInt(); return VerifyHelper.getMapValue(map, n, "Unknown enum number: " + n); } - @LauncherAPI - public static void write(HOutput output, Itf itf) throws IOException { - output.writeVarInt(itf.getNumber()); - } - @FunctionalInterface - public interface Itf { + public interface Itf + { @LauncherAPI int getNumber(); } diff --git a/Launcher/source/serialize/stream/StreamObject.java b/Launcher/source/serialize/stream/StreamObject.java index 7a81980..7124075 100644 --- a/Launcher/source/serialize/stream/StreamObject.java +++ b/Launcher/source/serialize/stream/StreamObject.java @@ -1,23 +1,27 @@ package launcher.serialize.stream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - import launcher.LauncherAPI; import launcher.helper.IOHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; -public abstract class StreamObject { +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public abstract class StreamObject +{ /* public StreamObject(HInput input) */ @LauncherAPI public abstract void write(HOutput output) throws IOException; @LauncherAPI - public final byte[] write() throws IOException { - try (ByteArrayOutputStream array = IOHelper.newByteArrayOutput()) { - try (HOutput output = new HOutput(array)) { + public final byte[] write() throws IOException + { + try (ByteArrayOutputStream array = IOHelper.newByteArrayOutput()) + { + try (HOutput output = new HOutput(array)) + { write(output); } return array.toByteArray(); @@ -25,7 +29,8 @@ } @FunctionalInterface - public interface Adapter { + public interface Adapter + { @LauncherAPI O convert(HInput input) throws IOException; }