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");
}