diff --git a/.idea/misc.xml b/.idea/misc.xml index 73c86c0..54495bf 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -5,8 +5,10 @@ - + + + diff --git a/LaunchServer/LaunchServer.iml b/LaunchServer/LaunchServer.iml index 6c20146..a862a9c 100644 --- a/LaunchServer/LaunchServer.iml +++ b/LaunchServer/LaunchServer.iml @@ -1,5 +1,15 @@ + + + + + SPIGOT + BUNGEECORD + + + + @@ -21,6 +31,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/LaunchServer/resources/bungee.yml b/LaunchServer/resources/bungee.yml new file mode 100644 index 0000000..3950a5e --- /dev/null +++ b/LaunchServer/resources/bungee.yml @@ -0,0 +1,8 @@ +name: LaunchServer +description: "sashok724's LaunchServer" +version: 1.0 + +author: "sashok724 LLC" +website: 'https://sashok724.net/' + +main: launchserver.plugin.bungee.LaunchServerPluginBungee diff --git a/LaunchServer/resources/plugin.yml b/LaunchServer/resources/plugin.yml new file mode 100644 index 0000000..6bc0e8a --- /dev/null +++ b/LaunchServer/resources/plugin.yml @@ -0,0 +1,14 @@ +name: LaunchServer +description: "sashok724's LaunchServer" +version: 1.0 + +author: "sashok724 LLC" +website: 'https://sashok724.net/' + +main: launchserver.plugin.bukkit.LaunchServerPluginBukkit + +commands: + launchserver: + aliases: [launcher,ls,l] + description: "Primary LaunchServer command" + usage: '/ [args...]' diff --git a/LaunchServer/source/LaunchServer.java b/LaunchServer/source/LaunchServer.java index 247207b..6ab8d46 100644 --- a/LaunchServer/source/LaunchServer.java +++ b/LaunchServer/source/LaunchServer.java @@ -20,6 +20,7 @@ import java.time.Instant; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -29,6 +30,7 @@ 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; @@ -72,21 +74,23 @@ import launchserver.response.ServerSocketHandler.Listener; import launchserver.texture.TextureProvider; -public final class LaunchServer implements Runnable { +public final class LaunchServer implements Runnable, AutoCloseable { // Constant paths - @LauncherAPI public static final Path CONFIG_FILE = IOHelper.WORKING_DIR.resolve("LaunchServer.cfg"); - @LauncherAPI public static final Path PUBLIC_KEY_FILE = IOHelper.WORKING_DIR.resolve("public.key"); - @LauncherAPI public static final Path PRIVATE_KEY_FILE = IOHelper.WORKING_DIR.resolve("private.key"); - @LauncherAPI public static final Path UPDATES_DIR = IOHelper.WORKING_DIR.resolve("updates"); - @LauncherAPI public static final Path PROFILES_DIR = IOHelper.WORKING_DIR.resolve("profiles"); + @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; // Server config @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 = new JARLauncherBinary(this); + @LauncherAPI public final LauncherBinary launcherBinary; @LauncherAPI public final LauncherBinary launcherEXEBinary; // Server @@ -99,28 +103,41 @@ private volatile List> profilesList; private volatile Map> updatesDirMap; - private LaunchServer() throws IOException, InvalidKeySpecException { + public LaunchServer(Path dir, boolean portable) throws IOException, InvalidKeySpecException { setScriptBindings(); + this.portable = portable; + + // Setup config locations + this.dir = dir; + configFile = dir.resolve("LaunchServer.cfg"); + publicKeyFile = dir.resolve("public.key"); + privateKeyFile = dir.resolve("private.key"); + updatesDir = dir.resolve("updates"); + profilesDir = dir.resolve("profiles"); // Set command handler CommandHandler localCommandHandler; - try { - Class.forName("jline.Terminal"); + if (portable) { + localCommandHandler = new StdCommandHandler(this, false); + } else { + try { + Class.forName("jline.Terminal"); - // JLine2 available - localCommandHandler = new JLineCommandHandler(this); - LogHelper.info("JLine2 terminal enabled"); - } catch (ClassNotFoundException ignored) { - localCommandHandler = new StdCommandHandler(this); - LogHelper.warning("JLine2 isn't in classpath, using std"); + // JLine2 available + localCommandHandler = new JLineCommandHandler(this); + LogHelper.info("JLine2 terminal enabled"); + } catch (ClassNotFoundException ignored) { + localCommandHandler = new StdCommandHandler(this, true); + LogHelper.warning("JLine2 isn't in classpath, using std"); + } } commandHandler = localCommandHandler; // Set key pair - if (IOHelper.isFile(PUBLIC_KEY_FILE) && IOHelper.isFile(PRIVATE_KEY_FILE)) { + if (IOHelper.isFile(publicKeyFile) && IOHelper.isFile(privateKeyFile)) { LogHelper.info("Reading RSA keypair"); - publicKey = SecurityHelper.toPublicRSAKey(IOHelper.read(PUBLIC_KEY_FILE)); - privateKey = SecurityHelper.toPrivateRSAKey(IOHelper.read(PRIVATE_KEY_FILE)); + publicKey = SecurityHelper.toPublicRSAKey(IOHelper.read(publicKeyFile)); + privateKey = SecurityHelper.toPrivateRSAKey(IOHelper.read(privateKeyFile)); if (!publicKey.getModulus().equals(privateKey.getModulus())) { throw new IOException("Private and public key modulus mismatch"); } @@ -132,8 +149,8 @@ // Write key pair files LogHelper.info("Writing RSA keypair files"); - IOHelper.write(PUBLIC_KEY_FILE, publicKey.getEncoded()); - IOHelper.write(PRIVATE_KEY_FILE, privateKey.getEncoded()); + IOHelper.write(publicKeyFile, publicKey.getEncoded()); + IOHelper.write(privateKeyFile, privateKey.getEncoded()); } // Print keypair fingerprints @@ -144,24 +161,25 @@ // Read LaunchServer config generateConfigIfNotExists(); LogHelper.info("Reading LaunchServer config file"); - try (BufferedReader reader = IOHelper.newReader(CONFIG_FILE)) { + try (BufferedReader reader = IOHelper.newReader(configFile)) { config = new Config(TextConfigReader.read(reader, true)); } config.verify(); // Set launcher EXE binary + launcherBinary = new JARLauncherBinary(this); launcherEXEBinary = config.launch4J ? new EXEL4JLauncherBinary(this) : new EXELauncherBinary(this); syncLauncherBinaries(); // Sync updates dir - if (!IOHelper.isDir(UPDATES_DIR)) { - Files.createDirectory(UPDATES_DIR); + if (!IOHelper.isDir(updatesDir)) { + Files.createDirectory(updatesDir); } syncUpdatesDir(null); // Sync profiles dir - if (!IOHelper.isDir(PROFILES_DIR)) { - Files.createDirectory(PROFILES_DIR); + if (!IOHelper.isDir(profilesDir)) { + Files.createDirectory(profilesDir); } syncProfilesDir(); @@ -170,13 +188,47 @@ } @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 (Exception e) { + LogHelper.error(e); + } + + // 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 = IOHelper.WORKING_DIR.resolve("plugin.js"); + Path scriptFile = dir.resolve("plugin.js"); if (IOHelper.isFile(scriptFile)) { LogHelper.info("Loading plugin.js script"); try { @@ -187,8 +239,10 @@ } // Add shutdown hook, then start LaunchServer - JVMHelper.RUNTIME.addShutdownHook(CommonHelper.newThread(null, false, this::shutdownHook)); - CommonHelper.newThread("Command Thread", true, commandHandler).start(); + if (!portable) { + JVMHelper.RUNTIME.addShutdownHook(CommonHelper.newThread(null, false, this::close)); + CommonHelper.newThread("Command Thread", true, commandHandler).start(); + } rebindServerSocket(); } @@ -249,10 +303,10 @@ public void syncProfilesDir() throws IOException { LogHelper.info("Syncing profiles dir"); List> newProfies = new LinkedList<>(); - IOHelper.walk(PROFILES_DIR, new ProfilesFileVisitor(newProfies), false); + IOHelper.walk(profilesDir, new ProfilesFileVisitor(newProfies), false); // Sort and set new profiles - Collections.sort(newProfies, (a, b) -> a.object.compareTo(b.object)); + newProfies.sort(Comparator.comparing(a -> a.object)); profilesList = Collections.unmodifiableList(newProfies); } @@ -260,7 +314,7 @@ public void syncUpdatesDir(Collection dirs) throws IOException { LogHelper.info("Syncing updates dir"); Map> newUpdatesDirMap = new HashMap<>(16); - try (DirectoryStream dirStream = Files.newDirectoryStream(UPDATES_DIR)) { + try (DirectoryStream dirStream = Files.newDirectoryStream(updatesDir)) { for (Path updateDir : dirStream) { if (Files.isHidden(updateDir)) { continue; // Skip hidden @@ -292,7 +346,7 @@ } private void generateConfigIfNotExists() throws IOException { - if (IOHelper.isFile(CONFIG_FILE)) { + if (IOHelper.isFile(configFile)) { return; } @@ -304,12 +358,17 @@ } // Set server address - LogHelper.println("LaunchServer address: "); - newConfig.setAddress(commandHandler.readLine()); + 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(CONFIG_FILE)) { + try (BufferedWriter writer = IOHelper.newWriter(configFile)) { TextConfigWriter.write(newConfig.block, writer, true); } } @@ -324,42 +383,18 @@ addLaunchServerClassBindings(bindings); } - private void shutdownHook() { - 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); - } - - // Print last message before death :( - LogHelper.info("LaunchServer stopped"); - } - public static void main(String... args) throws Throwable { - JVMHelper.verifySystemProperties(LaunchServer.class); SecurityHelper.verifyCertificates(LaunchServer.class); + JVMHelper.verifySystemProperties(LaunchServer.class, true); LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log")); LogHelper.printVersion("LaunchServer"); // Start LaunchServer Instant start = Instant.now(); try { - new LaunchServer().run(); - } catch (Exception e) { - LogHelper.error(e); + new LaunchServer(IOHelper.WORKING_DIR, false).run(); + } catch (Throwable exc) { + LogHelper.error(exc); return; } Instant end = Instant.now(); diff --git a/LaunchServer/source/auth/provider/FileAuthProvider.java b/LaunchServer/source/auth/provider/FileAuthProvider.java index 1910638..4f46378 100644 --- a/LaunchServer/source/auth/provider/FileAuthProvider.java +++ b/LaunchServer/source/auth/provider/FileAuthProvider.java @@ -23,7 +23,7 @@ private final Path file; // Cache - private final Map entries = new HashMap<>(); + private final Map entries = new HashMap<>(256); private final Object cacheLock = new Object(); private FileTime cacheLastModified; diff --git a/LaunchServer/source/auth/provider/NullAuthProvider.java b/LaunchServer/source/auth/provider/NullAuthProvider.java index 83ec3ef..6701085 100644 --- a/LaunchServer/source/auth/provider/NullAuthProvider.java +++ b/LaunchServer/source/auth/provider/NullAuthProvider.java @@ -1,6 +1,7 @@ package launchserver.auth.provider; import java.io.IOException; +import java.util.Objects; import launcher.LauncherAPI; import launcher.helper.VerifyHelper; @@ -32,6 +33,6 @@ } private AuthProvider getProvider() { - return VerifyHelper.verify(provider, a -> a != null, "Backend auth provider wasn't set"); + return VerifyHelper.verify(provider, Objects::nonNull, "Backend auth provider wasn't set"); } } diff --git a/LaunchServer/source/binary/EXEL4JLauncherBinary.java b/LaunchServer/source/binary/EXEL4JLauncherBinary.java index fe7977c..d84e130 100644 --- a/LaunchServer/source/binary/EXEL4JLauncherBinary.java +++ b/LaunchServer/source/binary/EXEL4JLauncherBinary.java @@ -22,12 +22,13 @@ private static final String DOWNLOAD_URL = "http://www.oracle.com/technetwork/java/javase/downloads/jre8-downloads-2133155.html"; // Oracle JRE 8 // File constants - private static final Path EXE_BINARY_FILE = IOHelper.WORKING_DIR.resolve(EXELauncherBinary.EXE_BINARY_FILE); - private static final Path FAVICON_FILE = IOHelper.WORKING_DIR.resolve("favicon.ico"); + private final Path faviconFile; @LauncherAPI public EXEL4JLauncherBinary(LaunchServer server) { - super(server, EXE_BINARY_FILE); + super(server, server.dir.resolve(EXELauncherBinary.EXE_BINARY_FILE)); + faviconFile = server.dir.resolve("favicon.ico"); + setConfig(); } @Override @@ -36,7 +37,7 @@ // Set favicon path Config config = ConfigPersister.getInstance().getConfig(); - if (IOHelper.isFile(FAVICON_FILE)) { + if (IOHelper.isFile(faviconFile)) { config.setIcon(new File("favicon.ico")); } else { config.setIcon(null); @@ -52,7 +53,7 @@ } } - static { + private void setConfig() { Config config = new Config(); // Set string options @@ -74,7 +75,7 @@ config.setJre(jre); // Prepare version info (product) - VersionInfo info =new VersionInfo(); + VersionInfo info = new VersionInfo(); info.setProductName("sashok724's Launcher v3"); info.setProductVersion("1.0.0.0"); info.setTxtProductVersion(Launcher.VERSION + ", build " + Launcher.BUILD); @@ -83,7 +84,7 @@ info.setFileDescription("sashok724's Launcher v3"); info.setFileVersion("1.0.0.0"); info.setTxtFileVersion(Launcher.VERSION + ", build " + Launcher.BUILD); - info.setOriginalFilename(EXE_BINARY_FILE.getFileName().toString()); + info.setOriginalFilename(binaryFile.getFileName().toString()); // Prepare version info (misc) info.setInternalName("Launcher"); @@ -94,8 +95,8 @@ // Set JAR wrapping options config.setDontWrapJar(false); - config.setJar(JARLauncherBinary.JAR_BINARY_FILE.toFile()); - config.setOutfile(EXE_BINARY_FILE.toFile()); + config.setJar(server.launcherBinary.binaryFile.toFile()); + config.setOutfile(binaryFile.toFile()); // Return prepared config ConfigPersister.getInstance().setAntConfig(config, null); diff --git a/LaunchServer/source/binary/EXELauncherBinary.java b/LaunchServer/source/binary/EXELauncherBinary.java index 7e5818a..630938a 100644 --- a/LaunchServer/source/binary/EXELauncherBinary.java +++ b/LaunchServer/source/binary/EXELauncherBinary.java @@ -14,7 +14,7 @@ @LauncherAPI public EXELauncherBinary(LaunchServer server) { - super(server, IOHelper.WORKING_DIR.resolve(EXE_BINARY_FILE)); + super(server, server.dir.resolve(EXE_BINARY_FILE)); } @Override diff --git a/LaunchServer/source/binary/JARLauncherBinary.java b/LaunchServer/source/binary/JARLauncherBinary.java index 06923f0..98870d5 100644 --- a/LaunchServer/source/binary/JARLauncherBinary.java +++ b/LaunchServer/source/binary/JARLauncherBinary.java @@ -29,20 +29,24 @@ import launchserver.LaunchServer; public final class JARLauncherBinary extends LauncherBinary { - @LauncherAPI public static final Path RUNTIME_DIR = IOHelper.WORKING_DIR.resolve(Launcher.RUNTIME_DIR); - @LauncherAPI public static final Path INIT_SCRIPT_FILE = RUNTIME_DIR.resolve(Launcher.INIT_SCRIPT_FILE); - @LauncherAPI public static final Path JAR_BINARY_FILE = IOHelper.WORKING_DIR.resolve("Launcher.jar"); + @LauncherAPI public final Path runtimeDir; + @LauncherAPI public final Path initScriptFile; @LauncherAPI public JARLauncherBinary(LaunchServer server) throws IOException { - super(server, JAR_BINARY_FILE); + super(server, server.dir.resolve("Launcher.jar")); + runtimeDir = server.dir.resolve(Launcher.RUNTIME_DIR); + initScriptFile = runtimeDir.resolve(Launcher.INIT_SCRIPT_FILE); tryUnpackRuntime(); } @Override public void build() throws IOException { + tryUnpackRuntime(); + + // Build launcher binary LogHelper.info("Building launcher binary file"); - try (JarOutputStream output = new JarOutputStream(IOHelper.newOutput(JAR_BINARY_FILE))) { + 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")))) { @@ -50,20 +54,19 @@ } // Verify has init script file - if (!IOHelper.isFile(INIT_SCRIPT_FILE)) { + if (!IOHelper.isFile(initScriptFile)) { throw new IOException(String.format("Missing init script file ('%s')", Launcher.INIT_SCRIPT_FILE)); } // Write launcher runtime dir - Map runtime = new HashMap<>(); - IOHelper.walk(RUNTIME_DIR, new RuntimeDirVisitor(output, runtime), false); + Map runtime = new HashMap<>(256); + IOHelper.walk(runtimeDir, new RuntimeDirVisitor(output, runtime), false); // Create launcher config file byte[] launcherConfigBytes; try (ByteArrayOutputStream configArray = IOHelper.newByteArrayOutput()) { try (HOutput configOutput = new HOutput(configArray)) { - new Config(server.config.getAddress(), server.config.port, - server.publicKey, runtime).write(configOutput); + new Config(server.config.getAddress(), server.config.port, server.publicKey, runtime).write(configOutput); } launcherConfigBytes = configArray.toByteArray(); } @@ -77,12 +80,12 @@ @LauncherAPI public void tryUnpackRuntime() throws IOException { // Verify is runtime dir unpacked - if (IOHelper.isDir(RUNTIME_DIR)) { + if (IOHelper.isDir(runtimeDir)) { return; // Already unpacked } // Unpack launcher runtime files - Files.createDirectory(RUNTIME_DIR); + 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()) { @@ -91,12 +94,16 @@ } // Unpack runtime file - IOHelper.transfer(input, RUNTIME_DIR.resolve(IOHelper.toPath(entry.getName()))); + IOHelper.transfer(input, runtimeDir.resolve(IOHelper.toPath(entry.getName()))); } } } - private static final class RuntimeDirVisitor extends SimpleFileVisitor { + private static ZipEntry newEntry(String fileName) { + return IOHelper.newZipEntry(Launcher.RUNTIME_DIR + IOHelper.CROSS_SEPARATOR + fileName); + } + + private final class RuntimeDirVisitor extends SimpleFileVisitor { private final ZipOutputStream output; private final Map runtime; @@ -107,14 +114,14 @@ @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - String dirName = IOHelper.toString(RUNTIME_DIR.relativize(dir)); + 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 { - String fileName = IOHelper.toString(RUNTIME_DIR.relativize(file)); + String fileName = IOHelper.toString(runtimeDir.relativize(file)); runtime.put(fileName, SecurityHelper.digest(DigestAlgorithm.MD5, file)); // Create zip entry and transfer contents @@ -124,9 +131,5 @@ // Return result return super.visitFile(file, attrs); } - - private static ZipEntry newEntry(String fileName) { - return IOHelper.newZipEntry(Launcher.RUNTIME_DIR + IOHelper.CROSS_SEPARATOR + fileName); - } } } diff --git a/LaunchServer/source/command/handler/CommandHandler.java b/LaunchServer/source/command/handler/CommandHandler.java index 2c96322..f3fb0fe 100644 --- a/LaunchServer/source/command/handler/CommandHandler.java +++ b/LaunchServer/source/command/handler/CommandHandler.java @@ -92,15 +92,30 @@ @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 (Exception e) { + LogHelper.error(e); + 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 Instant startTime = Instant.now(); try { - String[] args = parse(line); - if (args.length == 0) { - return; - } - - // Invoke command - LogHelper.info("Command '%s'", line); lookup(args[0]).invoke(Arrays.copyOfRange(args, 1, args.length)); } catch (Exception e) { LogHelper.error(e); @@ -145,10 +160,10 @@ // Read line char by char Collection result = new LinkedList<>(); - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < line.length() + 1; i++) { + StringBuilder builder = new StringBuilder(100); + for (int i = 0; i <= line.length(); i++) { boolean end = i >= line.length(); - char ch = end ? 0 : line.charAt(i); + char ch = end ? '\0' : line.charAt(i); // Maybe we should read next argument? if (end || !quoted && Character.isWhitespace(ch)) { @@ -174,6 +189,9 @@ wasQuoted = true; break; case '\\': // All escapes, including spaces etc + if (i + 1 >= line.length()) { + throw new CommandException("Escape character is not specified"); + } char next = line.charAt(i + 1); builder.append(next); i++; diff --git a/LaunchServer/source/command/handler/StdCommandHandler.java b/LaunchServer/source/command/handler/StdCommandHandler.java index 7d175b0..488b9c7 100644 --- a/LaunchServer/source/command/handler/StdCommandHandler.java +++ b/LaunchServer/source/command/handler/StdCommandHandler.java @@ -9,9 +9,9 @@ public final class StdCommandHandler extends CommandHandler { private final BufferedReader reader; - public StdCommandHandler(LaunchServer server) { + public StdCommandHandler(LaunchServer server, boolean readCommands) { super(server); - reader = IOHelper.newReader(System.in); + reader = readCommands ? IOHelper.newReader(System.in) : null; } @Override @@ -26,6 +26,6 @@ @Override public String readLine() throws IOException { - return reader.readLine(); + return reader == null ? null : reader.readLine(); } } diff --git a/LaunchServer/source/command/hash/DownloadAssetCommand.java b/LaunchServer/source/command/hash/DownloadAssetCommand.java index a1dc8bb..4320773 100644 --- a/LaunchServer/source/command/hash/DownloadAssetCommand.java +++ b/LaunchServer/source/command/hash/DownloadAssetCommand.java @@ -8,7 +8,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; -import launcher.client.ClientProfile; import launcher.client.ClientProfile.Version; import launcher.helper.IOHelper; import launcher.helper.LogHelper; @@ -37,7 +36,7 @@ verifyArgs(args, 2); Version version = Version.byName(args[0]); String dirName = IOHelper.verifyFileName(args[1]); - Path assetDir = LaunchServer.UPDATES_DIR.resolve(dirName); + Path assetDir = server.updatesDir.resolve(dirName); // Create asset dir LogHelper.subInfo("Creating asset dir: '%s'", dirName); diff --git a/LaunchServer/source/command/hash/DownloadClientCommand.java b/LaunchServer/source/command/hash/DownloadClientCommand.java index 656b5a5..e670710 100644 --- a/LaunchServer/source/command/hash/DownloadClientCommand.java +++ b/LaunchServer/source/command/hash/DownloadClientCommand.java @@ -41,7 +41,7 @@ verifyArgs(args, 2); Version version = Version.byName(args[0]); String dirName = IOHelper.verifyFileName(args[1]); - Path clientDir = LaunchServer.UPDATES_DIR.resolve(args[1]); + Path clientDir = server.updatesDir.resolve(args[1]); // Create client dir LogHelper.subInfo("Creating client dir: '%s'", dirName); @@ -61,7 +61,7 @@ } client.setTitle(dirName); client.block.getEntry("dir", StringConfigEntry.class).setValue(dirName); - try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(LaunchServer.PROFILES_DIR, + try (BufferedWriter writer = IOHelper.newWriter(IOHelper.resolveIncremental(server.profilesDir, 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 201cf68..408066a 100644 --- a/LaunchServer/source/command/hash/IndexAssetCommand.java +++ b/LaunchServer/source/command/hash/IndexAssetCommand.java @@ -46,8 +46,8 @@ String inputAssetDirName = IOHelper.verifyFileName(args[0]); String indexFileName = IOHelper.verifyFileName(args[1]); String outputAssetDirName = IOHelper.verifyFileName(args[2]); - Path inputAssetDir = LaunchServer.UPDATES_DIR.resolve(inputAssetDirName); - Path outputAssetDir = LaunchServer.UPDATES_DIR.resolve(outputAssetDirName); + Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName); + Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName); if (outputAssetDir.equals(inputAssetDir)) { throw new CommandException("Unindexed and indexed asset dirs can't be same"); } diff --git a/LaunchServer/source/command/hash/UnindexAssetCommand.java b/LaunchServer/source/command/hash/UnindexAssetCommand.java index 8cc2bbf..590657a 100644 --- a/LaunchServer/source/command/hash/UnindexAssetCommand.java +++ b/LaunchServer/source/command/hash/UnindexAssetCommand.java @@ -35,8 +35,8 @@ String inputAssetDirName = IOHelper.verifyFileName(args[0]); String indexFileName = IOHelper.verifyFileName(args[1]); String outputAssetDirName = IOHelper.verifyFileName(args[2]); - Path inputAssetDir = LaunchServer.UPDATES_DIR.resolve(inputAssetDirName); - Path outputAssetDir = LaunchServer.UPDATES_DIR.resolve(outputAssetDirName); + Path inputAssetDir = server.updatesDir.resolve(inputAssetDirName); + Path outputAssetDir = server.updatesDir.resolve(outputAssetDirName); if (outputAssetDir.equals(inputAssetDir)) { throw new CommandException("Indexed and unindexed asset dirs can't be same"); } diff --git a/LaunchServer/source/plugin/LaunchServerPluginBridge.java b/LaunchServer/source/plugin/LaunchServerPluginBridge.java new file mode 100644 index 0000000..952af16 --- /dev/null +++ b/LaunchServer/source/plugin/LaunchServerPluginBridge.java @@ -0,0 +1,48 @@ +package launchserver.plugin; + +import java.nio.file.Path; +import java.time.Duration; +import java.time.Instant; + +import launcher.helper.JVMHelper; +import launcher.helper.LogHelper; +import launchserver.LaunchServer; + +public final class LaunchServerPluginBridge implements Runnable, AutoCloseable { + private final LaunchServer server; + + public LaunchServerPluginBridge(Path dir) throws Throwable { + LogHelper.addOutput(dir.resolve("LaunchServer.log")); + LogHelper.printVersion("LaunchServer"); + + // Create new LaunchServer + Instant start = Instant.now(); + try { + server = new LaunchServer(dir, true); + } catch (Throwable exc) { + LogHelper.error(exc); + throw exc; + } + Instant end = Instant.now(); + LogHelper.debug("LaunchServer started in %dms", Duration.between(start, end).toMillis()); + } + + @Override + public void close() { + server.close(); + } + + @Override + public void run() { + server.run(); + } + + 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 new file mode 100644 index 0000000..2a465c3 --- /dev/null +++ b/LaunchServer/source/plugin/bukkit/LaunchServerCommandBukkit.java @@ -0,0 +1,34 @@ +package launchserver.plugin.bukkit; + +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; + +public final class LaunchServerCommandBukkit implements CommandExecutor { + public final 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)) { + sender.sendMessage(ChatColor.RED + "Эту команду можно использовать только из консоли"); + return true; + } + + // Eval command + LaunchServerPluginBridge bridge = plugin.bridge; + if (bridge == null) { + sender.sendMessage(ChatColor.RED + "Лаунчсервер не был полностью загружен"); + } else { + bridge.eval(args); + } + return true; + } +} diff --git a/LaunchServer/source/plugin/bukkit/LaunchServerPluginBukkit.java b/LaunchServer/source/plugin/bukkit/LaunchServerPluginBukkit.java new file mode 100644 index 0000000..aa913fe --- /dev/null +++ b/LaunchServer/source/plugin/bukkit/LaunchServerPluginBukkit.java @@ -0,0 +1,32 @@ +package launchserver.plugin.bukkit; + +import launchserver.plugin.LaunchServerPluginBridge; +import org.bukkit.plugin.java.JavaPlugin; + +public final class LaunchServerPluginBukkit extends JavaPlugin { + public volatile LaunchServerPluginBridge bridge = null; + + @Override + public void onDisable() { + super.onDisable(); + if (bridge != null) { + bridge.close(); + bridge = null; + } + } + + @Override + public void onEnable() { + super.onEnable(); + + // Initialize LaunchServer + try { + bridge = new LaunchServerPluginBridge(getDataFolder().toPath()); + } catch (Throwable exc) { + exc.printStackTrace(); + } + + // Register command + getCommand("launchserver").setExecutor(new LaunchServerCommandBukkit(this)); + } +} diff --git a/LaunchServer/source/plugin/bungee/LaunchServerCommandBungee.java b/LaunchServer/source/plugin/bungee/LaunchServerCommandBungee.java new file mode 100644 index 0000000..24d12ed --- /dev/null +++ b/LaunchServer/source/plugin/bungee/LaunchServerCommandBungee.java @@ -0,0 +1,38 @@ +package launchserver.plugin.bungee; + +import launchserver.plugin.LaunchServerPluginBridge; +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.command.ConsoleCommandSender; + +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) { + super("launchserver", null, "launcher", "ls", "l"); + this.plugin = plugin; + } + + @Override + 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) { + sender.sendMessage(NOT_INITIALIZED_MESSAGE); + } else { + bridge.eval(args); + } + } +} diff --git a/LaunchServer/source/plugin/bungee/LaunchServerPluginBungee.java b/LaunchServer/source/plugin/bungee/LaunchServerPluginBungee.java new file mode 100644 index 0000000..6d875fd --- /dev/null +++ b/LaunchServer/source/plugin/bungee/LaunchServerPluginBungee.java @@ -0,0 +1,32 @@ +package launchserver.plugin.bungee; + +import launchserver.plugin.LaunchServerPluginBridge; +import net.md_5.bungee.api.plugin.Plugin; + +public final class LaunchServerPluginBungee extends Plugin { + public volatile LaunchServerPluginBridge bridge = null; + + @Override + public void onDisable() { + super.onDisable(); + if (bridge != null) { + bridge.close(); + bridge = null; + } + } + + @Override + public void onEnable() { + super.onEnable(); + + // Initialize LaunchServer + try { + bridge = new LaunchServerPluginBridge(getDataFolder().toPath()); + } catch (Throwable exc) { + exc.printStackTrace(); + } + + // Register command + getProxy().getPluginManager().registerCommand(this, new LaunchServerCommandBungee(this)); + } +} diff --git a/LaunchServer/source/response/update/UpdateResponse.java b/LaunchServer/source/response/update/UpdateResponse.java index 8aa72bb..fe7aa58 100644 --- a/LaunchServer/source/response/update/UpdateResponse.java +++ b/LaunchServer/source/response/update/UpdateResponse.java @@ -43,7 +43,7 @@ output.flush(); // Prepare variables for actions queue - Path dir = LaunchServer.UPDATES_DIR.resolve(updateDirName); + Path dir = server.updatesDir.resolve(updateDirName); Deque dirStack = new LinkedList<>(); dirStack.add(hdir.object); diff --git a/LaunchServer/source/texture/VoidTextureProvider.java b/LaunchServer/source/texture/VoidTextureProvider.java index b525f33..b3e823a 100644 --- a/LaunchServer/source/texture/VoidTextureProvider.java +++ b/LaunchServer/source/texture/VoidTextureProvider.java @@ -2,7 +2,6 @@ import java.util.UUID; -import launcher.client.PlayerProfile; import launcher.client.PlayerProfile.Texture; import launcher.serialize.config.entry.BlockConfigEntry; diff --git a/Launcher/source/Launcher.java b/Launcher/source/Launcher.java index 80461ba..7c7d558 100644 --- a/Launcher/source/Launcher.java +++ b/Launcher/source/Launcher.java @@ -239,8 +239,8 @@ } public static void main(String... args) throws Throwable { - JVMHelper.verifySystemProperties(Launcher.class); SecurityHelper.verifyCertificates(Launcher.class); + JVMHelper.verifySystemProperties(Launcher.class, true); LogHelper.printVersion("Launcher"); // Start Launcher diff --git a/Launcher/source/client/ClientLauncher.java b/Launcher/source/client/ClientLauncher.java index 498bc39..15f098c 100644 --- a/Launcher/source/client/ClientLauncher.java +++ b/Launcher/source/client/ClientLauncher.java @@ -146,8 +146,8 @@ @LauncherAPI public static void main(String... args) throws Throwable { - JVMHelper.verifySystemProperties(ClientLauncher.class); SecurityHelper.verifyCertificates(ClientLauncher.class); + JVMHelper.verifySystemProperties(ClientLauncher.class, true); LogHelper.printVersion("Client Launcher"); // Resolve params file diff --git a/Launcher/source/helper/IOHelper.java b/Launcher/source/helper/IOHelper.java index 6aa6103..d39be4f 100644 --- a/Launcher/source/helper/IOHelper.java +++ b/Launcher/source/helper/IOHelper.java @@ -56,6 +56,7 @@ import javax.imageio.ImageIO; import javax.imageio.ImageReader; +import launcher.Launcher; import launcher.LauncherAPI; public final class IOHelper { @@ -177,7 +178,7 @@ @LauncherAPI public static URL getResourceURL(String name) throws NoSuchFileException { - URL url = ClassLoader.getSystemResource(name); + URL url = Launcher.class.getResource(name); if (url == null) { throw new NoSuchFileException(name); } @@ -256,6 +257,8 @@ 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 { + connection.setUseCaches(false); } connection.setDoInput(true); connection.setDoOutput(false); diff --git a/Launcher/source/helper/JVMHelper.java b/Launcher/source/helper/JVMHelper.java index 9cb3107..abde106 100644 --- a/Launcher/source/helper/JVMHelper.java +++ b/Launcher/source/helper/JVMHelper.java @@ -86,12 +86,12 @@ } @LauncherAPI - public static void verifySystemProperties(Class mainClass) { + public static void verifySystemProperties(Class mainClass, boolean requireSystem) { Locale.setDefault(Locale.US); // Verify class loader LogHelper.debug("Verifying class loader"); - if (!mainClass.getClassLoader().equals(LOADER)) { + if (requireSystem && !mainClass.getClassLoader().equals(LOADER)) { throw new SecurityException("ClassLoader should be system"); }