diff --git a/LaunchServer/resources/launchserver/defaults/config.cfg b/LaunchServer/resources/launchserver/defaults/config.cfg index dd9fd33..b0d14cb 100644 --- a/LaunchServer/resources/launchserver/defaults/config.cfg +++ b/LaunchServer/resources/launchserver/defaults/config.cfg @@ -16,10 +16,9 @@ }; # Texture provider -textureProvider: "request"; +textureProvider: "mojang"; textureProviderConfig: { - skinsURL: "http://skins.minecraft.net/MinecraftSkins/%username%.png"; - cloaksURL: "http://skins.minecraft.net/MinecraftCloaks/%username%.png"; + # No config needed for mojang texture provider }; # Launch4J EXE binary building diff --git a/LaunchServer/runtime/apiServer.js b/LaunchServer/runtime/apiServer.js deleted file mode 100644 index cce81ce..0000000 --- a/LaunchServer/runtime/apiServer.js +++ /dev/null @@ -1,22 +0,0 @@ -server.loadScript(IOHelperClass.static.toURL(java.nio.file.Paths.get("api.js"))); -var LaunchServer = LaunchServerClass.static; - -// Auth class API imports -var AuthHandler = AuthHandlerClass.static; -var FileAuthHandler = FileAuthHandlerClass.static; -var CachedAuthHandler = CachedAuthHandlerClass.static; -var AuthProvider = AuthProviderClass.static; -var DigestAuthProvider = DigestAuthProviderClass.static; -var MySQLSourceConfig = MySQLSourceConfigClass.static; -var AuthException = AuthExceptionClass.static; -var TextureProvider = TextureProviderClass.static; - -// Command class API imports -var Command = CommandClass.static; -var CommandHandler = CommandHandlerClass.static; -var CommandException = CommandExceptionClass.static; - -// Response class API imports -var Response = ResponseClass.static; -var ResponseFactory = ResponseFactoryClass.static; -var ServerSocketHandlerListener = ServerSocketHandlerListenerClass.static; diff --git a/LaunchServer/runtime/plugin.js b/LaunchServer/runtime/plugin.js index a100255..842abc7 100644 --- a/LaunchServer/runtime/plugin.js +++ b/LaunchServer/runtime/plugin.js @@ -1,4 +1,4 @@ -server.loadScript(IOHelperClass.static.toURL(java.nio.file.Paths.get("apiServer.js"))); +server.loadScript(IOHelperClass.static.toURL(java.nio.file.Paths.get("api.js"))); // Print test message LogHelper.info("[plugin.js] Test message"); diff --git a/LaunchServer/source/LaunchServer.java b/LaunchServer/source/LaunchServer.java index 2fa025c..a311327 100644 --- a/LaunchServer/source/LaunchServer.java +++ b/LaunchServer/source/LaunchServer.java @@ -16,8 +16,6 @@ import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; -import java.time.Duration; -import java.time.Instant; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -59,6 +57,7 @@ import launchserver.auth.handler.CachedAuthHandler; import launchserver.auth.handler.FileAuthHandler; import launchserver.auth.provider.AuthProvider; +import launchserver.auth.provider.DigestAuthProvider; import launchserver.binary.EXEL4JLauncherBinary; import launchserver.binary.EXELauncherBinary; import launchserver.binary.JARLauncherBinary; @@ -96,8 +95,8 @@ // Server @LauncherAPI public final CommandHandler commandHandler; @LauncherAPI public final ServerSocketHandler serverSocketHandler; + @LauncherAPI public final ScriptEngine engine = CommonHelper.newScriptEngine(); private final AtomicBoolean started = new AtomicBoolean(false); - private final ScriptEngine engine = CommonHelper.newScriptEngine(); // Updates and profiles private volatile List> profilesList; @@ -213,8 +212,8 @@ ((Invocable) engine).invokeFunction("close"); } catch (NoSuchMethodException ignored) { // Do nothing if method simply doesn't exist - } catch (Exception e) { - LogHelper.error(e); + } catch (Throwable exc) { + LogHelper.error(exc); } // Print last message before death :( @@ -233,8 +232,8 @@ LogHelper.info("Loading plugin.js script"); try { loadScript(IOHelper.toURL(scriptFile)); - } catch (Exception e) { - throw new RuntimeException("Error while loading plugin.js", e); + } catch (Throwable exc) { + throw new RuntimeException("Error while loading plugin.js", exc); } } @@ -379,8 +378,8 @@ bindings.put("server", this); // Add launcher and launchserver class bindings - Launcher.addLauncherClassBindings(bindings); - addLaunchServerClassBindings(bindings); + Launcher.addLauncherClassBindings(engine, bindings); + addLaunchServerClassBindings(engine, bindings); } public static void main(String... args) throws Throwable { @@ -390,39 +389,39 @@ LogHelper.printVersion("LaunchServer"); // Start LaunchServer - Instant start = Instant.now(); + long start = System.currentTimeMillis(); try { new LaunchServer(IOHelper.WORKING_DIR, false).run(); } catch (Throwable exc) { LogHelper.error(exc); return; } - Instant end = Instant.now(); - LogHelper.debug("LaunchServer started in %dms", Duration.between(start, end).toMillis()); + long end = System.currentTimeMillis(); + LogHelper.debug("LaunchServer started in %dms", end - start); } - private static void addLaunchServerClassBindings(Map bindings) { - bindings.put("LaunchServerClass", LaunchServer.class); + public static void addLaunchServerClassBindings(ScriptEngine engine, Map bindings) { + Launcher.addClassBinding(engine, bindings, "LaunchServer", LaunchServer.class); // Set auth class bindings - bindings.put("AuthHandlerClass", AuthHandler.class); - bindings.put("FileAuthHandlerClass", FileAuthHandler.class); - bindings.put("CachedAuthHandlerClass", CachedAuthHandler.class); - bindings.put("AuthProviderClass", AuthProvider.class); - bindings.put("DigestAuthProviderClass", AuthProvider.class); - bindings.put("MySQLSourceConfigClass", MySQLSourceConfig.class); - bindings.put("AuthExceptionClass", AuthException.class); - bindings.put("TextureProviderClass", TextureProvider.class); + Launcher.addClassBinding(engine, bindings, "AuthHandler", AuthHandler.class); + Launcher.addClassBinding(engine, bindings, "FileAuthHandler", FileAuthHandler.class); + Launcher.addClassBinding(engine, bindings, "CachedAuthHandler", CachedAuthHandler.class); + Launcher.addClassBinding(engine, bindings, "AuthProvider", AuthProvider.class); + Launcher.addClassBinding(engine, bindings, "DigestAuthProvider", DigestAuthProvider.class); + Launcher.addClassBinding(engine, bindings, "MySQLSourceConfig", MySQLSourceConfig.class); + Launcher.addClassBinding(engine, bindings, "AuthException", AuthException.class); + Launcher.addClassBinding(engine, bindings, "TextureProvider", TextureProvider.class); // Set command class bindings - bindings.put("CommandClass", Command.class); - bindings.put("CommandHandlerClass", CommandHandler.class); - bindings.put("CommandExceptionClass", CommandException.class); + Launcher.addClassBinding(engine, bindings, "Command", Command.class); + Launcher.addClassBinding(engine, bindings, "CommandHandler", CommandHandler.class); + Launcher.addClassBinding(engine, bindings, "CommandException", CommandException.class); // Set response class bindings - bindings.put("ResponseClass", Response.class); - bindings.put("ResponseFactoryClass", Factory.class); - bindings.put("ServerSocketHandlerListenerClass", Listener.class); + Launcher.addClassBinding(engine, bindings, "Response", Response.class); + Launcher.addClassBinding(engine, bindings, "ResponseFactory", Factory.class); + Launcher.addClassBinding(engine, bindings, "ServerSocketHandlerListener", Listener.class); } private final class ProfilesFileVisitor extends SimpleFileVisitor { diff --git a/LaunchServer/source/auth/MySQLSourceConfig.java b/LaunchServer/source/auth/MySQLSourceConfig.java index c8ed5f7..e662212 100644 --- a/LaunchServer/source/auth/MySQLSourceConfig.java +++ b/LaunchServer/source/auth/MySQLSourceConfig.java @@ -16,10 +16,10 @@ public final class MySQLSourceConfig extends ConfigObject implements AutoCloseable { @LauncherAPI public static final int TIMEOUT = VerifyHelper.verifyInt( - Integer.parseUnsignedInt(System.getProperty("launcher.mysql.idleTimeout", Integer.toString(5000))), + 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.parseUnsignedInt(System.getProperty("launcher.mysql.maxPoolSize", Integer.toString(3))), + Integer.parseInt(System.getProperty("launcher.mysql.maxPoolSize", Integer.toString(3))), VerifyHelper.POSITIVE, "launcher.mysql.maxPoolSize can't be <= 0"); // Instance diff --git a/LaunchServer/source/auth/handler/AuthHandler.java b/LaunchServer/source/auth/handler/AuthHandler.java index 364e9c8..94f9313 100644 --- a/LaunchServer/source/auth/handler/AuthHandler.java +++ b/LaunchServer/source/auth/handler/AuthHandler.java @@ -59,8 +59,8 @@ } static { - registerHandler("null", NullAuthHandler::new); registerHandler("memory", MemoryAuthHandler::new); + registerHandler("delegate", DelegateAuthHandler::new); // Auth handler that doesn't do nothing :D registerHandler("binaryFile", BinaryFileAuthHandler::new); diff --git a/LaunchServer/source/auth/handler/DelegateAuthHandler.java b/LaunchServer/source/auth/handler/DelegateAuthHandler.java new file mode 100644 index 0000000..6c6548b --- /dev/null +++ b/LaunchServer/source/auth/handler/DelegateAuthHandler.java @@ -0,0 +1,60 @@ +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 { + private volatile AuthHandler delegate; + + public DelegateAuthHandler(BlockConfigEntry block) { + super(block); + } + + @Override + public UUID auth(AuthProviderResult authResult) throws IOException { + return getDelegate().auth(authResult); + } + + @Override + public UUID checkServer(String username, String serverID) throws IOException { + return getDelegate().checkServer(username, serverID); + } + + @Override + public void close() throws IOException { + AuthHandler delegate = this.delegate; + if (delegate != null) { + delegate.close(); + } + } + + @Override + 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 { + return getDelegate().usernameToUUID(username); + } + + @Override + 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"); + } +} diff --git a/LaunchServer/source/auth/handler/NullAuthHandler.java b/LaunchServer/source/auth/handler/NullAuthHandler.java deleted file mode 100644 index a12a3f7..0000000 --- a/LaunchServer/source/auth/handler/NullAuthHandler.java +++ /dev/null @@ -1,60 +0,0 @@ -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 final class NullAuthHandler extends AuthHandler { - private volatile AuthHandler handler; - - public NullAuthHandler(BlockConfigEntry block) { - super(block); - } - - @Override - public UUID auth(AuthProviderResult authResult) throws IOException { - return getHandler().auth(authResult); - } - - @Override - public UUID checkServer(String username, String serverID) throws IOException { - return getHandler().checkServer(username, serverID); - } - - @Override - public void close() throws IOException { - AuthHandler handler = this.handler; - if (handler != null) { - handler.close(); - } - } - - @Override - public boolean joinServer(String username, String accessToken, String serverID) throws IOException { - return getHandler().joinServer(username, accessToken, serverID); - } - - @Override - public UUID usernameToUUID(String username) throws IOException { - return getHandler().usernameToUUID(username); - } - - @Override - public String uuidToUsername(UUID uuid) throws IOException { - return getHandler().uuidToUsername(uuid); - } - - @LauncherAPI - public void setBackend(AuthHandler handler) { - this.handler = handler; - } - - private AuthHandler getHandler() { - return VerifyHelper.verify(handler, Objects::nonNull, "Backend auth handler wasn't set"); - } -} diff --git a/LaunchServer/source/auth/provider/AuthProvider.java b/LaunchServer/source/auth/provider/AuthProvider.java index 2fdbf0d..7f0da4b 100644 --- a/LaunchServer/source/auth/provider/AuthProvider.java +++ b/LaunchServer/source/auth/provider/AuthProvider.java @@ -23,7 +23,7 @@ public abstract void close() throws IOException; @LauncherAPI - public abstract AuthProviderResult auth(String login, String password, String ip) throws Exception; + public abstract AuthProviderResult auth(String login, String password, String ip) throws Throwable; @LauncherAPI public static AuthProviderResult authError(String message) throws AuthException { @@ -45,14 +45,14 @@ } static { - registerProvider("null", NullAuthProvider::new); registerProvider("accept", AcceptAuthProvider::new); registerProvider("reject", RejectAuthProvider::new); + registerProvider("delegate", DelegateAuthProvider::new); // Auth providers that doesn't do nothing :D + registerProvider("file", FileAuthProvider::new); registerProvider("mojang", MojangAuthProvider::new); registerProvider("mysql", MySQLAuthProvider::new); - registerProvider("file", FileAuthProvider::new); registerProvider("request", RequestAuthProvider::new); } } diff --git a/LaunchServer/source/auth/provider/DelegateAuthProvider.java b/LaunchServer/source/auth/provider/DelegateAuthProvider.java new file mode 100644 index 0000000..f705063 --- /dev/null +++ b/LaunchServer/source/auth/provider/DelegateAuthProvider.java @@ -0,0 +1,38 @@ +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 { + private volatile AuthProvider delegate; + + public DelegateAuthProvider(BlockConfigEntry block) { + super(block); + } + + @Override + public AuthProviderResult auth(String login, String password, String ip) throws Throwable { + return getDelegate().auth(login, password, ip); + } + + @Override + public void close() throws IOException { + AuthProvider delegate = this.delegate; + 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"); + } +} diff --git a/LaunchServer/source/auth/provider/MojangAuthProvider.java b/LaunchServer/source/auth/provider/MojangAuthProvider.java index c4d577c..4fe97a2 100644 --- a/LaunchServer/source/auth/provider/MojangAuthProvider.java +++ b/LaunchServer/source/auth/provider/MojangAuthProvider.java @@ -28,13 +28,13 @@ } @Override - public AuthProviderResult auth(String login, String password, String ip) throws Exception { + 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); // Verify there's no error - JsonObject response = makeJSONRequest(URL, request); + JsonObject response = makeMojangRequest(URL, request); if (response == null) { authError("Empty mojang response"); } @@ -59,12 +59,17 @@ // Do nothing } - public static JsonObject makeJSONRequest(URL url, JsonObject request) throws IOException { - // Make authentication request - HttpURLConnection connection = IOHelper.newConnectionPost(url); - connection.setRequestProperty("Content-Type", "application/json"); - try (OutputStream output = connection.getOutputStream()) { - output.write(request.toString(WriterConfig.MINIMAL).getBytes(StandardCharsets.UTF_8)); + 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 diff --git a/LaunchServer/source/auth/provider/NullAuthProvider.java b/LaunchServer/source/auth/provider/NullAuthProvider.java deleted file mode 100644 index baf9edc..0000000 --- a/LaunchServer/source/auth/provider/NullAuthProvider.java +++ /dev/null @@ -1,38 +0,0 @@ -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 final class NullAuthProvider extends AuthProvider { - private volatile AuthProvider provider; - - public NullAuthProvider(BlockConfigEntry block) { - super(block); - } - - @Override - public AuthProviderResult auth(String login, String password, String ip) throws Exception { - return getProvider().auth(login, password, ip); - } - - @Override - public void close() throws IOException { - AuthProvider provider = this.provider; - if (provider != null) { - provider.close(); - } - } - - @LauncherAPI - public void setBackend(AuthProvider provider) { - this.provider = provider; - } - - private AuthProvider getProvider() { - return VerifyHelper.verify(provider, Objects::nonNull, "Backend auth provider wasn't set"); - } -} diff --git a/LaunchServer/source/command/Command.java b/LaunchServer/source/command/Command.java index d2bd0dd..9cf458f 100644 --- a/LaunchServer/source/command/Command.java +++ b/LaunchServer/source/command/Command.java @@ -21,7 +21,7 @@ public abstract String getUsageDescription(); @LauncherAPI - public abstract void invoke(String... args) throws Exception; + public abstract void invoke(String... args) throws Throwable; @LauncherAPI protected final void verifyArgs(String[] args, int min) throws CommandException { diff --git a/LaunchServer/source/command/auth/AuthCommand.java b/LaunchServer/source/command/auth/AuthCommand.java index aeaf10c..08c22b5 100644 --- a/LaunchServer/source/command/auth/AuthCommand.java +++ b/LaunchServer/source/command/auth/AuthCommand.java @@ -23,7 +23,7 @@ } @Override - public void invoke(String... args) throws Exception { + 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 2231885..8d6d59c 100644 --- a/LaunchServer/source/command/auth/CheckServerCommand.java +++ b/LaunchServer/source/command/auth/CheckServerCommand.java @@ -22,7 +22,7 @@ } @Override - public void invoke(String... args) throws Exception { + 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 0c66a34..a94ed88 100644 --- a/LaunchServer/source/command/auth/JoinServerCommand.java +++ b/LaunchServer/source/command/auth/JoinServerCommand.java @@ -20,7 +20,7 @@ } @Override - public void invoke(String... args) throws Exception { + public void invoke(String... args) throws Throwable { verifyArgs(args, 3); String username = args[0]; String accessToken = args[1]; diff --git a/LaunchServer/source/command/basic/BuildCommand.java b/LaunchServer/source/command/basic/BuildCommand.java index 2ecedb8..366b591 100644 --- a/LaunchServer/source/command/basic/BuildCommand.java +++ b/LaunchServer/source/command/basic/BuildCommand.java @@ -19,7 +19,7 @@ } @Override - public void invoke(String... args) throws Exception { + 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 2ab2671..1ce0d6e 100644 --- a/LaunchServer/source/command/basic/ClearCommand.java +++ b/LaunchServer/source/command/basic/ClearCommand.java @@ -20,7 +20,7 @@ } @Override - public void invoke(String... args) throws Exception { + public void invoke(String... args) throws Throwable { server.commandHandler.clear(); LogHelper.subInfo("Terminal cleared"); } diff --git a/LaunchServer/source/command/basic/EvalCommand.java b/LaunchServer/source/command/basic/EvalCommand.java new file mode 100644 index 0000000..4284c4d --- /dev/null +++ b/LaunchServer/source/command/basic/EvalCommand.java @@ -0,0 +1,26 @@ +package launchserver.command.basic; + +import launcher.helper.LogHelper; +import launchserver.LaunchServer; +import launchserver.command.Command; + +public final class EvalCommand extends Command { + public EvalCommand(LaunchServer server) { + super(server); + } + + @Override + public String getArgsDescription() { + return ""; + } + + @Override + public String getUsageDescription() { + return "Evaluate JavaScript code snippet"; + } + + @Override + 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 3f2424b..278baed 100644 --- a/LaunchServer/source/command/basic/GCCommand.java +++ b/LaunchServer/source/command/basic/GCCommand.java @@ -21,7 +21,7 @@ } @Override - public void invoke(String... args) throws Exception { + public void invoke(String... args) throws Throwable { LogHelper.subInfo("Performing full GC"); JVMHelper.fullGC(); diff --git a/LaunchServer/source/command/basic/RebindCommand.java b/LaunchServer/source/command/basic/RebindCommand.java index 03e24df..d298a1b 100644 --- a/LaunchServer/source/command/basic/RebindCommand.java +++ b/LaunchServer/source/command/basic/RebindCommand.java @@ -19,7 +19,7 @@ } @Override - public void invoke(String... args) throws Exception { + public void invoke(String... args) { server.rebindServerSocket(); } } diff --git a/LaunchServer/source/command/basic/VersionCommand.java b/LaunchServer/source/command/basic/VersionCommand.java index 24302af..4c6c4cf 100644 --- a/LaunchServer/source/command/basic/VersionCommand.java +++ b/LaunchServer/source/command/basic/VersionCommand.java @@ -21,7 +21,7 @@ } @Override - public void invoke(String... args) throws Exception { + 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 16af2a9..d031c53 100644 --- a/LaunchServer/source/command/handler/CommandHandler.java +++ b/LaunchServer/source/command/handler/CommandHandler.java @@ -1,8 +1,6 @@ package launchserver.command.handler; import java.io.IOException; -import java.time.Duration; -import java.time.Instant; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -25,6 +23,7 @@ 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; @@ -52,6 +51,7 @@ registerCommand("rebind", new RebindCommand(server)); registerCommand("debug", new DebugCommand(server)); registerCommand("clear", new ClearCommand(server)); + registerCommand("eval", new EvalCommand(server)); registerCommand("gc", new GCCommand(server)); registerCommand("logConnections", new LogConnectionsCommand(server)); @@ -106,8 +106,8 @@ String[] args; try { args = parse(line); - } catch (Exception e) { - LogHelper.error(e); + } catch (Throwable exc) { + LogHelper.error(exc); return; } @@ -122,16 +122,16 @@ } // Measure start time and invoke command - Instant startTime = Instant.now(); + long start = System.currentTimeMillis(); try { lookup(args[0]).invoke(Arrays.copyOfRange(args, 1, args.length)); - } catch (Exception e) { - LogHelper.error(e); + } catch (Throwable exc) { + LogHelper.error(exc); } // Bell if invocation took > 1s - Instant endTime = Instant.now(); - if (bell && Duration.between(startTime, endTime).getSeconds() >= 5) { + long end = System.currentTimeMillis(); + if (bell && end - start >= 5_000L) { try { bell(); } catch (IOException e) { diff --git a/LaunchServer/source/command/hash/DownloadAssetCommand.java b/LaunchServer/source/command/hash/DownloadAssetCommand.java index 4320773..276d4eb 100644 --- a/LaunchServer/source/command/hash/DownloadAssetCommand.java +++ b/LaunchServer/source/command/hash/DownloadAssetCommand.java @@ -32,7 +32,7 @@ } @Override - public void invoke(String... args) throws Exception { + public void invoke(String... args) throws Throwable { verifyArgs(args, 2); Version version = Version.byName(args[0]); String dirName = IOHelper.verifyFileName(args[1]); diff --git a/LaunchServer/source/command/hash/DownloadClientCommand.java b/LaunchServer/source/command/hash/DownloadClientCommand.java index e670710..3c20651 100644 --- a/LaunchServer/source/command/hash/DownloadClientCommand.java +++ b/LaunchServer/source/command/hash/DownloadClientCommand.java @@ -37,7 +37,7 @@ } @Override - public void invoke(String... args) throws IOException, CommandException { + public void invoke(String... args) throws Throwable { verifyArgs(args, 2); Version version = Version.byName(args[0]); String dirName = IOHelper.verifyFileName(args[1]); diff --git a/LaunchServer/source/command/hash/IndexAssetCommand.java b/LaunchServer/source/command/hash/IndexAssetCommand.java index 408066a..40b7103 100644 --- a/LaunchServer/source/command/hash/IndexAssetCommand.java +++ b/LaunchServer/source/command/hash/IndexAssetCommand.java @@ -41,7 +41,7 @@ } @Override - public void invoke(String... args) throws Exception { + public void invoke(String... args) throws Throwable { verifyArgs(args, 3); String inputAssetDirName = IOHelper.verifyFileName(args[0]); String indexFileName = IOHelper.verifyFileName(args[1]); diff --git a/LaunchServer/source/command/hash/UnindexAssetCommand.java b/LaunchServer/source/command/hash/UnindexAssetCommand.java index 590657a..d4e986a 100644 --- a/LaunchServer/source/command/hash/UnindexAssetCommand.java +++ b/LaunchServer/source/command/hash/UnindexAssetCommand.java @@ -30,7 +30,7 @@ } @Override - public void invoke(String... args) throws Exception { + public void invoke(String... args) throws Throwable { verifyArgs(args, 3); String inputAssetDirName = IOHelper.verifyFileName(args[0]); String indexFileName = IOHelper.verifyFileName(args[1]); diff --git a/LaunchServer/source/plugin/LaunchServerPluginBridge.java b/LaunchServer/source/plugin/LaunchServerPluginBridge.java index 952af16..d10dabe 100644 --- a/LaunchServer/source/plugin/LaunchServerPluginBridge.java +++ b/LaunchServer/source/plugin/LaunchServerPluginBridge.java @@ -1,8 +1,6 @@ package launchserver.plugin; import java.nio.file.Path; -import java.time.Duration; -import java.time.Instant; import launcher.helper.JVMHelper; import launcher.helper.LogHelper; @@ -16,15 +14,15 @@ LogHelper.printVersion("LaunchServer"); // Create new LaunchServer - Instant start = Instant.now(); + long start = System.currentTimeMillis(); 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()); + long end = System.currentTimeMillis(); + LogHelper.debug("LaunchServer started in %dms", end - start); } @Override diff --git a/LaunchServer/source/response/Response.java b/LaunchServer/source/response/Response.java index 8be6b89..fb83ada 100644 --- a/LaunchServer/source/response/Response.java +++ b/LaunchServer/source/response/Response.java @@ -24,7 +24,7 @@ } @LauncherAPI - public abstract void reply() throws Exception; + public abstract void reply() throws Throwable; @LauncherAPI protected final void debug(String message) { diff --git a/LaunchServer/source/response/ResponseThread.java b/LaunchServer/source/response/ResponseThread.java index 691d2e8..645c71f 100644 --- a/LaunchServer/source/response/ResponseThread.java +++ b/LaunchServer/source/response/ResponseThread.java @@ -47,7 +47,7 @@ // Process connection boolean cancelled = false; - Exception savedError = null; + Throwable savedError = null; try (HInput input = new HInput(socket.getInputStream()); HOutput output = new HOutput(socket.getOutputStream())) { Type type = readHandshake(input, output); @@ -63,9 +63,9 @@ LogHelper.subDebug(String.format("#%d Request error: %s", id, e.getMessage())); output.writeString(e.getMessage(), 0); } - } catch (Exception e) { - savedError = e; - LogHelper.error(e); + } catch (Throwable exc) { + savedError = exc; + LogHelper.error(exc); } finally { IOHelper.close(socket); if (!cancelled) { @@ -111,7 +111,7 @@ return type; } - private void respond(Type type, HInput input, HOutput output) throws Exception { + 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 { diff --git a/LaunchServer/source/response/ServerSocketHandler.java b/LaunchServer/source/response/ServerSocketHandler.java index a1516aa..4858a4f 100644 --- a/LaunchServer/source/response/ServerSocketHandler.java +++ b/LaunchServer/source/response/ServerSocketHandler.java @@ -109,9 +109,9 @@ this.listener = listener; } - /*package*/ void onDisconnect(long id, Exception e) { + /*package*/ void onDisconnect(long id, Throwable exc) { if (listener != null) { - listener.onDisconnect(id, e); + listener.onDisconnect(id, exc); } } @@ -124,7 +124,7 @@ boolean onConnect(long id, InetAddress address); @LauncherAPI - void onDisconnect(long id, Exception e); + void onDisconnect(long id, Throwable exc); @LauncherAPI boolean onHandshake(long id, Type type); diff --git a/LaunchServer/source/response/auth/AuthResponse.java b/LaunchServer/source/response/auth/AuthResponse.java index 5d65754..36c16d5 100644 --- a/LaunchServer/source/response/auth/AuthResponse.java +++ b/LaunchServer/source/response/auth/AuthResponse.java @@ -27,7 +27,7 @@ } @Override - public void reply() throws Exception { + public void reply() throws Throwable { String login = input.readString(255); byte[] encryptedPassword = input.readByteArray(SecurityHelper.CRYPTO_MAX_LENGTH); @@ -53,8 +53,8 @@ } catch (AuthException e) { requestError(e.getMessage()); return; - } catch (Exception e) { - LogHelper.error(e); + } catch (Throwable exc) { + LogHelper.error(exc); requestError("Internal auth provider error"); return; } @@ -67,8 +67,8 @@ } catch (AuthException e) { requestError(e.getMessage()); return; - } catch (Exception e) { - LogHelper.error(e); + } catch (Throwable exc) { + LogHelper.error(exc); requestError("Internal auth handler error"); return; } diff --git a/LaunchServer/source/response/auth/CheckServerResponse.java b/LaunchServer/source/response/auth/CheckServerResponse.java index a71d99c..3681632 100644 --- a/LaunchServer/source/response/auth/CheckServerResponse.java +++ b/LaunchServer/source/response/auth/CheckServerResponse.java @@ -31,8 +31,8 @@ } catch (AuthException e) { requestError(e.getMessage()); return; - } catch (Exception e) { - LogHelper.error(e); + } catch (Throwable exc) { + LogHelper.error(exc); requestError("Internal auth handler error"); return; } diff --git a/LaunchServer/source/response/auth/JoinServerResponse.java b/LaunchServer/source/response/auth/JoinServerResponse.java index 5b39635..fec21e6 100644 --- a/LaunchServer/source/response/auth/JoinServerResponse.java +++ b/LaunchServer/source/response/auth/JoinServerResponse.java @@ -31,8 +31,8 @@ } catch (AuthException e) { requestError(e.getMessage()); return; - } catch (Exception e) { - LogHelper.error(e); + } catch (Throwable exc) { + LogHelper.error(exc); requestError("Internal auth handler error"); return; } diff --git a/LaunchServer/source/response/profile/ProfileByUUIDResponse.java b/LaunchServer/source/response/profile/ProfileByUUIDResponse.java index e955341..f75601e 100644 --- a/LaunchServer/source/response/profile/ProfileByUUIDResponse.java +++ b/LaunchServer/source/response/profile/ProfileByUUIDResponse.java @@ -38,8 +38,8 @@ Texture skin; try { skin = server.config.textureProvider.getSkinTexture(uuid, username); - } catch (IOException e) { - LogHelper.error(new IOException(String.format("Can't get skin texture: '%s'", username), e)); + } catch (Throwable exc) { + LogHelper.error(new IOException(String.format("Can't get skin texture: '%s'", username), exc)); skin = null; } @@ -47,8 +47,8 @@ Texture cloak; try { cloak = server.config.textureProvider.getCloakTexture(uuid, username); - } catch (IOException e) { - LogHelper.error(new IOException(String.format("Can't get cloak texture: '%s'", username), e)); + } catch (Throwable exc) { + LogHelper.error(new IOException(String.format("Can't get cloak texture: '%s'", username), exc)); cloak = null; } diff --git a/LaunchServer/source/response/update/UpdateListResponse.java b/LaunchServer/source/response/update/UpdateListResponse.java index 5f1c44b..39b0242 100644 --- a/LaunchServer/source/response/update/UpdateListResponse.java +++ b/LaunchServer/source/response/update/UpdateListResponse.java @@ -16,7 +16,7 @@ } @Override - public void reply() throws Exception { + public void reply() throws Throwable { Set>> updateDirs = server.getUpdateDirs(); // Write all update dirs names diff --git a/LaunchServer/source/texture/DelegateTextureProvider.java b/LaunchServer/source/texture/DelegateTextureProvider.java new file mode 100644 index 0000000..95e470c --- /dev/null +++ b/LaunchServer/source/texture/DelegateTextureProvider.java @@ -0,0 +1,45 @@ +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 { + private volatile TextureProvider delegate; + + public DelegateTextureProvider(BlockConfigEntry block) { + super(block); + } + + @Override + public void close() throws IOException { + TextureProvider delegate = this.delegate; + if (delegate != null) { + delegate.close(); + } + } + + @Override + public Texture getCloakTexture(UUID uuid, String username) throws IOException { + return getDelegate().getCloakTexture(uuid, username); + } + + @Override + 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"); + } +} diff --git a/LaunchServer/source/texture/MojangTextureProvider.java b/LaunchServer/source/texture/MojangTextureProvider.java new file mode 100644 index 0000000..e0400a1 --- /dev/null +++ b/LaunchServer/source/texture/MojangTextureProvider.java @@ -0,0 +1,144 @@ +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; +import com.eclipsesource.json.JsonValue; +import launcher.LauncherAPI; +import launcher.client.PlayerProfile.Texture; +import launcher.helper.IOHelper; +import launcher.helper.JVMHelper; +import launcher.helper.LogHelper; +import launcher.helper.VerifyHelper; +import launcher.serialize.config.entry.BlockConfigEntry; +import launchserver.auth.provider.MojangAuthProvider; + +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; + + // Instance + private final Map cache = new HashMap<>(1024); + + public MojangTextureProvider(BlockConfigEntry block) { + super(block); + } + + @Override + public void close() { + // Do nothing + } + + @Override + public synchronized Texture getCloakTexture(UUID uuid, String username) { + return getCached(uuid, username).skin; + } + + @Override + public synchronized Texture getSkinTexture(UUID uuid, String username) { + return getCached(uuid, username).cloak; + } + + 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) { + JVMHelper.UNSAFE.throwException(result.exc); + } + return result; + } + + 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) { + throw new IllegalArgumentException("Empty UUID response"); + } + String uuidResolved = uuidResponse.get("id").asString(); + + // Obtain player profile + URL profileURL = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuidResolved); + JsonObject profileResponse = MojangAuthProvider.makeMojangRequest(profileURL, null); + if (profileResponse == null) { + throw new IllegalArgumentException("Empty Mojang response"); + } + JsonArray properties = (JsonArray) profileResponse.get("properties"); + if (properties == null) { + LogHelper.subDebug("No properties"); + return cache(username, null, null, null); + } + + // Find textures property + JsonObject texturesProperty = null; + for (JsonValue property : properties) { + JsonObject property0 = property.asObject(); + 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) { + LogHelper.subDebug("No textures property"); + return cache(username, null, null, null); + } + + // Extract skin&cloak texture + texturesProperty = (JsonObject) texturesProperty.get("textures"); + JsonObject skinProperty = (JsonObject) texturesProperty.get("SKIN"); + Texture skinTexture = skinProperty == null ? null : new Texture(skinProperty.get("url").asString(), false); + JsonObject cloakProperty = (JsonObject) texturesProperty.get("CAPE"); + Texture cloakTexture = cloakProperty == null ? null : new Texture(cloakProperty.get("url").asString(), true); + + // We're done + return cache(username, skinTexture, cloakTexture, null); + } catch (Throwable exc) { + cache(username, null, null, exc); + JVMHelper.UNSAFE.throwException(exc); + } + + // We're dones + return result; + } + + 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) { + cache.put(username, data); + } + return data; + } + + 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) { + this.skin = skin; + this.cloak = cloak; + this.until = until; + exc = null; + } + + private CacheData(Throwable exc, long until) { + this.exc = exc; + this.until = until; + skin = cloak = null; + } + } +} diff --git a/LaunchServer/source/texture/NullTextureProvider.java b/LaunchServer/source/texture/NullTextureProvider.java deleted file mode 100644 index 8ed2c58..0000000 --- a/LaunchServer/source/texture/NullTextureProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -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 final class NullTextureProvider extends TextureProvider { - private volatile TextureProvider provider; - - public NullTextureProvider(BlockConfigEntry block) { - super(block); - } - - @Override - public void close() throws IOException { - TextureProvider provider = this.provider; - if (provider != null) { - provider.close(); - } - } - - @Override - public Texture getCloakTexture(UUID uuid, String username) throws IOException { - return getProvider().getCloakTexture(uuid, username); - } - - @Override - public Texture getSkinTexture(UUID uuid, String username) throws IOException { - return getProvider().getSkinTexture(uuid, username); - } - - @LauncherAPI - public void setBackend(TextureProvider provider) { - this.provider = provider; - } - - private TextureProvider getProvider() { - return VerifyHelper.verify(provider, Objects::nonNull, "Backend texture provider wasn't set"); - } -} diff --git a/LaunchServer/source/texture/TextureProvider.java b/LaunchServer/source/texture/TextureProvider.java index d4d5f4c..471221d 100644 --- a/LaunchServer/source/texture/TextureProvider.java +++ b/LaunchServer/source/texture/TextureProvider.java @@ -44,10 +44,11 @@ } static { - registerProvider("null", NullTextureProvider::new); 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/Launcher/runtime/engine/api.js b/Launcher/runtime/engine/api.js index e116f75..1ecb21c 100644 --- a/Launcher/runtime/engine/api.js +++ b/Launcher/runtime/engine/api.js @@ -1,75 +1,3 @@ -var Launcher = LauncherClass.static; -var LauncherConfig = LauncherConfigClass.static; - -// Hasher class API imports -var PlayerProfile = PlayerProfileClass.static; -var PlayerProfileTexture = PlayerProfileTextureClass.static; -var ClientProfile = ClientProfileClass.static; -var ClientProfileVersion = ClientProfileVersionClass.static; -var ClientLauncher = ClientLauncherClass.static; -var ClientLauncherParams = ClientLauncherParamsClass.static; -var ServerPinger = ServerPingerClass.static; - -// Request class API imports -var Request = RequestClass.static; -var RequestType = RequestTypeClass.static; -var RequestException = RequestExceptionClass.static; -var CustomRequest = CustomRequestClass.static; -var PingRequest = PingRequestClass.static; -var AuthRequest = AuthRequestClass.static; -var JoinServerRequest = JoinServerRequestClass.static; -var CheckServerRequest = CheckServerRequestClass.static; -var UpdateRequest = UpdateRequestClass.static; -var LauncherRequest = LauncherRequestClass.static; -var ProfileByUsernameRequest = ProfileByUsernameRequestClass.static; -var ProfileByUUIDRequest = ProfileByUUIDRequestClass.static; -var BatchProfileByUsernameRequest = BatchProfileByUsernameRequestClass.static; - -// Hasher class API imports -var FileNameMatcher = FileNameMatcherClass.static; -var HashedDir = HashedDirClass.static; -var HashedFile = HashedFileClass.static; -var HashedEntryType = HashedEntryTypeClass.static; - -// Serialization class API imports -var HInput = HInputClass.static; -var HOutput = HOutputClass.static; -var StreamObject = StreamObjectClass.static; -var StreamObjectAdapter = StreamObjectAdapterClass.static; -var SignedBytesHolder = SignedBytesHolderClass.static; -var SignedObjectHolder = SignedObjectHolderClass.static; -var EnumSerializer = EnumSerializerClass.static; - -// Config serialization class bindings -var ConfigObject = ConfigObjectClass.static; -var ConfigObjectAdapter = ConfigObjectAdapterClass.static; -var BlockConfigEntry = BlockConfigEntryClass.static; -var BooleanConfigEntry = BooleanConfigEntryClass.static; -var IntegerConfigEntry = IntegerConfigEntryClass.static; -var ListConfigEntry = ListConfigEntryClass.static; -var StringConfigEntry = StringConfigEntryClass.static; -var ConfigEntryType = ConfigEntryTypeClass.static; -var TextConfigReader = TextConfigReaderClass.static; -var TextConfigWriter = TextConfigWriterClass.static; - -// Helper class API imports -var CommonHelper = CommonHelperClass.static; -var IOHelper = IOHelperClass.static; -var JVMHelper = JVMHelperClass.static; -var JVMHelperOS = JVMHelperOSClass.static; -var LogHelper = LogHelperClass.static; -var LogHelperOutput = LogHelperOutputClass.static; -var SecurityHelper = SecurityHelperClass.static; -var DigestAlgorithm = DigestAlgorithmClass.static; -var VerifyHelper = VerifyHelperClass.static; - -// Helper JS class API imports -var JSApplication = null; -if (typeof JSApplicationClass !== 'undefined') { - JSApplication = JSApplicationClass.static; -} - -// API wrapper function tryWithResources(closeable, f) { try { f(closeable); diff --git a/Launcher/source-authlib/yggdrasil/CompatBridge.java b/Launcher/source-authlib/yggdrasil/CompatBridge.java index 225f00a..c8c30d7 100644 --- a/Launcher/source-authlib/yggdrasil/CompatBridge.java +++ b/Launcher/source-authlib/yggdrasil/CompatBridge.java @@ -21,13 +21,13 @@ } @SuppressWarnings("unused") - public static CompatProfile checkServer(String username, String serverID) throws Exception { + 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 Exception { + public static boolean joinServer(String username, String accessToken, String serverID) throws Throwable { if (!ClientLauncher.isLaunched()) { throw new IllegalStateException("Bad Login (Cheater)"); } @@ -38,17 +38,17 @@ } @SuppressWarnings("unused") - public static CompatProfile profileByUUID(UUID uuid) throws Exception { + public static CompatProfile profileByUUID(UUID uuid) throws Throwable { return CompatProfile.fromPlayerProfile(new ProfileByUUIDRequest(uuid).request()); } @SuppressWarnings("unused") - public static CompatProfile profileByUsername(String username) throws Exception { + public static CompatProfile profileByUsername(String username) throws Throwable { return CompatProfile.fromPlayerProfile(new ProfileByUsernameRequest(username).request()); } @SuppressWarnings("unused") - public static CompatProfile[] profilesByUsername(String... usernames) throws Exception { + public static CompatProfile[] profilesByUsername(String... usernames) throws Throwable { PlayerProfile[] profiles = new BatchProfileByUsernameRequest(usernames).request(); // Convert profiles diff --git a/Launcher/source-authlib/yggdrasil/LegacyBridge.java b/Launcher/source-authlib/yggdrasil/LegacyBridge.java index 7cb5747..05e42aa 100644 --- a/Launcher/source-authlib/yggdrasil/LegacyBridge.java +++ b/Launcher/source-authlib/yggdrasil/LegacyBridge.java @@ -15,7 +15,7 @@ } @SuppressWarnings("unused") - public static boolean checkServer(String username, String serverID) throws Exception { + 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; } @@ -44,8 +44,8 @@ LogHelper.debug("LegacyBridge.joinServer, Username: '%s', Access token: %s, Server ID: %s", username, accessToken, serverID); try { return new JoinServerRequest(username, accessToken, serverID).request() ? "OK" : "Bad Login (Clientside)"; - } catch (Exception e) { - return e.toString(); + } catch (Throwable exc) { + return exc.toString(); } } } diff --git a/Launcher/source-authlib/yggdrasil/YggdrasilGameProfileRepository.java b/Launcher/source-authlib/yggdrasil/YggdrasilGameProfileRepository.java index 8b8081f..e6c1f5e 100644 --- a/Launcher/source-authlib/yggdrasil/YggdrasilGameProfileRepository.java +++ b/Launcher/source-authlib/yggdrasil/YggdrasilGameProfileRepository.java @@ -39,10 +39,10 @@ PlayerProfile[] sliceProfiles; try { sliceProfiles = new BatchProfileByUsernameRequest(sliceUsernames).request(); - } catch (Exception e) { + } catch (Throwable exc) { for (String username : sliceUsernames) { - LogHelper.debug("Couldn't find profile '%s': %s", username, e); - callback.onProfileLookupFailed(new GameProfile((UUID) null, username), e); + 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)); } // Busy wait, like in standard authlib diff --git a/Launcher/source-authlib/yggdrasil/YggdrasilMinecraftSessionService.java b/Launcher/source-authlib/yggdrasil/YggdrasilMinecraftSessionService.java index 1bee302..29e30d5 100644 --- a/Launcher/source-authlib/yggdrasil/YggdrasilMinecraftSessionService.java +++ b/Launcher/source-authlib/yggdrasil/YggdrasilMinecraftSessionService.java @@ -54,8 +54,8 @@ PlayerProfile pp; try { pp = new ProfileByUUIDRequest(uuid).request(); - } catch (Exception e) { - LogHelper.debug("Couldn't fetch profile properties for '%s': %s", profile, e); + } catch (Throwable exc) { + LogHelper.debug("Couldn't fetch profile properties for '%s': %s", profile, exc); return profile; } @@ -114,9 +114,9 @@ PlayerProfile pp; try { pp = new CheckServerRequest(username, serverID).request(); - } catch (Exception e) { - LogHelper.error(e); - throw new AuthenticationUnavailableException(e); + } catch (Throwable exc) { + LogHelper.error(exc); + throw new AuthenticationUnavailableException(exc); } // Return profile if found @@ -142,8 +142,8 @@ boolean success; try { success = new JoinServerRequest(username, accessToken, serverID).request(); - } catch (Exception e) { - throw new AuthenticationUnavailableException(e); + } catch (Throwable exc) { + throw new AuthenticationUnavailableException(exc); } // Verify is success @@ -182,9 +182,10 @@ // Decode textures payload JsonObject texturesJSON; try { - byte[] decoded = Base64.getDecoder().decode(texturesBase64); - texturesJSON = JSON_PARSER.parse(new String(decoded, IOHelper.UNICODE_CHARSET)).getAsJsonObject().getAsJsonObject("textures"); - } catch (Exception ignored) { + 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; } diff --git a/Launcher/source/Launcher.java b/Launcher/source/Launcher.java index 21f0dc3..1fac671 100644 --- a/Launcher/source/Launcher.java +++ b/Launcher/source/Launcher.java @@ -7,8 +7,6 @@ import java.nio.file.NoSuchFileException; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; -import java.time.Duration; -import java.time.Instant; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -23,6 +21,7 @@ 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; @@ -78,14 +77,20 @@ private static final AtomicReference CONFIG = new AtomicReference<>(); // Version info - @LauncherAPI public static final String VERSION = "15.4"; - @LauncherAPI public static final String BUILD = readBuildNumber(); - @LauncherAPI public static final int PROTOCOL_MAGIC = 0x724724_00 + 23; + @LauncherAPI + public static final String VERSION = "15.4"; + @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"; // Instance private final AtomicBoolean started = new AtomicBoolean(false); @@ -122,86 +127,96 @@ bindings.put("launcher", this); // Add launcher class bindings - addLauncherClassBindings(bindings); + addLauncherClassBindings(engine, bindings); } @LauncherAPI - public static void addLauncherClassBindings(Map bindings) { - bindings.put("LauncherClass", Launcher.class); - bindings.put("LauncherConfigClass", Config.class); + public static void addLauncherClassBindings(ScriptEngine engine, Map bindings) { + addClassBinding(engine, bindings, "Launcher", Launcher.class); + addClassBinding(engine, bindings, "Config", Config.class); // Set client class bindings - bindings.put("PlayerProfileClass", PlayerProfile.class); - bindings.put("PlayerProfileTextureClass", Texture.class); - bindings.put("ClientProfileClass", ClientProfile.class); - bindings.put("ClientProfileVersionClass", Version.class); - bindings.put("ClientLauncherClass", ClientLauncher.class); - bindings.put("ClientLauncherParamsClass", Params.class); - bindings.put("ServerPingerClass", ServerPinger.class); + addClassBinding(engine, bindings, "PlayerProfile", PlayerProfile.class); + addClassBinding(engine, bindings, "PlayerProfileTexture", Texture.class); + addClassBinding(engine, bindings, "ClientProfile", ClientProfile.class); + addClassBinding(engine, bindings, "ClientProfileVersion", Version.class); + addClassBinding(engine, bindings, "ClientLauncher", ClientLauncher.class); + addClassBinding(engine, bindings, "ClientLauncherParams", Params.class); + addClassBinding(engine, bindings, "ServerPinger", ServerPinger.class); // Set request class bindings - bindings.put("RequestClass", Request.class); - bindings.put("RequestTypeClass", Request.Type.class); - bindings.put("RequestExceptionClass", RequestException.class); - bindings.put("CustomRequestClass", CustomRequest.class); - bindings.put("PingRequestClass", PingRequest.class); - bindings.put("AuthRequestClass", AuthRequest.class); - bindings.put("JoinServerRequestClass", JoinServerRequest.class); - bindings.put("CheckServerRequestClass", CheckServerRequest.class); - bindings.put("UpdateRequestClass", UpdateRequest.class); - bindings.put("LauncherRequestClass", LauncherRequest.class); - bindings.put("ProfileByUsernameRequestClass", ProfileByUsernameRequest.class); - bindings.put("ProfileByUUIDRequestClass", ProfileByUUIDRequest.class); - bindings.put("BatchProfileByUsernameRequestClass", BatchProfileByUsernameRequest.class); + addClassBinding(engine, bindings, "Request", Request.class); + addClassBinding(engine, bindings, "RequestType", Request.Type.class); + addClassBinding(engine, bindings, "RequestException", RequestException.class); + addClassBinding(engine, bindings, "CustomRequest", CustomRequest.class); + addClassBinding(engine, bindings, "PingRequest", PingRequest.class); + addClassBinding(engine, bindings, "AuthRequest", AuthRequest.class); + addClassBinding(engine, bindings, "JoinServerRequest", JoinServerRequest.class); + addClassBinding(engine, bindings, "CheckServerRequest", CheckServerRequest.class); + addClassBinding(engine, bindings, "UpdateRequest", UpdateRequest.class); + addClassBinding(engine, bindings, "LauncherRequest", LauncherRequest.class); + addClassBinding(engine, bindings, "ProfileByUsernameRequest", ProfileByUsernameRequest.class); + addClassBinding(engine, bindings, "ProfileByUUIDRequest", ProfileByUUIDRequest.class); + addClassBinding(engine, bindings, "BatchProfileByUsernameRequest", BatchProfileByUsernameRequest.class); // Set hasher class bindings - bindings.put("FileNameMatcherClass", FileNameMatcher.class); - bindings.put("HashedDirClass", HashedDir.class); - bindings.put("HashedFileClass", HashedFile.class); - bindings.put("HashedEntryTypeClass", HashedEntry.Type.class); + addClassBinding(engine, bindings, "FileNameMatcher", FileNameMatcher.class); + addClassBinding(engine, bindings, "HashedDir", HashedDir.class); + addClassBinding(engine, bindings, "HashedFile", HashedFile.class); + addClassBinding(engine, bindings, "HashedEntryType", HashedEntry.Type.class); // Set serialization class bindings - bindings.put("HInputClass", HInput.class); - bindings.put("HOutputClass", HOutput.class); - bindings.put("StreamObjectClass", StreamObject.class); - bindings.put("StreamObjectAdapterClass", StreamObject.Adapter.class); - bindings.put("SignedBytesHolderClass", SignedBytesHolder.class); - bindings.put("SignedObjectHolderClass", SignedObjectHolder.class); - bindings.put("EnumSerializerClass", EnumSerializer.class); + addClassBinding(engine, bindings, "HInput", HInput.class); + addClassBinding(engine, bindings, "HOutput", HOutput.class); + addClassBinding(engine, bindings, "StreamObject", StreamObject.class); + addClassBinding(engine, bindings, "StreamObjectAdapter", StreamObject.Adapter.class); + addClassBinding(engine, bindings, "SignedBytesHolder", SignedBytesHolder.class); + addClassBinding(engine, bindings, "SignedObjectHolder", SignedObjectHolder.class); + addClassBinding(engine, bindings, "EnumSerializer", EnumSerializer.class); // Set config serialization class bindings - bindings.put("ConfigObjectClass", ConfigObject.class); - bindings.put("ConfigObjectAdapterClass", Adapter.class); - bindings.put("BlockConfigEntryClass", BlockConfigEntry.class); - bindings.put("BooleanConfigEntryClass", BooleanConfigEntry.class); - bindings.put("IntegerConfigEntryClass", IntegerConfigEntry.class); - bindings.put("ListConfigEntryClass", ListConfigEntry.class); - bindings.put("StringConfigEntryClass", StringConfigEntry.class); - bindings.put("ConfigEntryTypeClass", Type.class); - bindings.put("TextConfigReaderClass", TextConfigReader.class); - bindings.put("TextConfigWriterClass", TextConfigWriter.class); + addClassBinding(engine, bindings, "ConfigObject", ConfigObject.class); + addClassBinding(engine, bindings, "ConfigObjectAdapter", Adapter.class); + addClassBinding(engine, bindings, "BlockConfigEntry", BlockConfigEntry.class); + addClassBinding(engine, bindings, "BooleanConfigEntry", BooleanConfigEntry.class); + addClassBinding(engine, bindings, "IntegerConfigEntry", IntegerConfigEntry.class); + addClassBinding(engine, bindings, "ListConfigEntry", ListConfigEntry.class); + addClassBinding(engine, bindings, "StringConfigEntry", StringConfigEntry.class); + addClassBinding(engine, bindings, "ConfigEntryType", Type.class); + addClassBinding(engine, bindings, "TextConfigReader", TextConfigReader.class); + addClassBinding(engine, bindings, "TextConfigWriter", TextConfigWriter.class); // Set helper class bindings - bindings.put("CommonHelperClass", CommonHelper.class); - bindings.put("IOHelperClass", IOHelper.class); - bindings.put("JVMHelperClass", JVMHelper.class); - bindings.put("JVMHelperOSClass", OS.class); - bindings.put("LogHelperClass", LogHelper.class); - bindings.put("LogHelperOutputClass", Output.class); - bindings.put("SecurityHelperClass", SecurityHelper.class); - bindings.put("DigestAlgorithmClass", DigestAlgorithm.class); - bindings.put("VerifyHelperClass", VerifyHelper.class); + addClassBinding(engine, bindings, "CommonHelper", CommonHelper.class); + addClassBinding(engine, bindings, "IOHelper", IOHelper.class); + addClassBinding(engine, bindings, "JVMHelper", JVMHelper.class); + addClassBinding(engine, bindings, "JVMHelperOS", OS.class); + addClassBinding(engine, bindings, "LogHelper", LogHelper.class); + addClassBinding(engine, bindings, "LogHelperOutput", Output.class); + addClassBinding(engine, bindings, "SecurityHelper", SecurityHelper.class); + addClassBinding(engine, bindings, "DigestAlgorithm", DigestAlgorithm.class); + addClassBinding(engine, bindings, "VerifyHelper", VerifyHelper.class); // Load JS API if available try { - Class.forName("javafx.application.Application"); - bindings.put("JSApplicationClass", JSApplication.class); - } catch (ClassNotFoundException ignored) { + addClassBinding(engine, bindings, "Application", Application.class); + addClassBinding(engine, bindings, "JSApplication", JSApplication.class); + } catch (Throwable ignored) { LogHelper.warning("JavaFX API isn't available"); } } @LauncherAPI + public static void addClassBinding(ScriptEngine engine, Map bindings, String name, Class clazz) { + bindings.put(name + "Class", clazz); // Backwards-compatibility + try { + engine.eval("var " + name + " = " + name + "Class.static;"); + } catch (ScriptException e) { + throw new AssertionError(e); + } + } + + @LauncherAPI public static Config getConfig() { Config config = CONFIG.get(); if (config == null) { @@ -245,15 +260,15 @@ LogHelper.printVersion("Launcher"); // Start Launcher - Instant start = Instant.now(); + long start = System.currentTimeMillis(); try { new Launcher().start(args); - } catch (Exception e) { - LogHelper.error(e); + } catch (Throwable exc) { + LogHelper.error(exc); return; } - Instant end = Instant.now(); - LogHelper.debug("Launcher started in %dms", Duration.between(start, end).toMillis()); + long end = System.currentTimeMillis(); + LogHelper.debug("Launcher started in %dms", end - start); } private static String readBuildNumber() { @@ -265,13 +280,18 @@ } public static final class Config extends StreamObject { - @LauncherAPI public static final String ADDRESS_OVERRIDE_PROPERTY = "launcher.addressOverride"; - @LauncherAPI public static final String ADDRESS_OVERRIDE = System.getProperty(ADDRESS_OVERRIDE_PROPERTY, null); + @LauncherAPI + public static final String ADDRESS_OVERRIDE_PROPERTY = "launcher.addressOverride"; + @LauncherAPI + public static final String ADDRESS_OVERRIDE = System.getProperty(ADDRESS_OVERRIDE_PROPERTY, null); // Instance - @LauncherAPI public final InetSocketAddress address; - @LauncherAPI public final RSAPublicKey publicKey; - @LauncherAPI public final Map runtime; + @LauncherAPI + public final InetSocketAddress address; + @LauncherAPI + public final RSAPublicKey publicKey; + @LauncherAPI + public final Map runtime; @LauncherAPI @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter") diff --git a/Launcher/source/client/ClientLauncher.java b/Launcher/source/client/ClientLauncher.java index d26da3c..309475b 100644 --- a/Launcher/source/client/ClientLauncher.java +++ b/Launcher/source/client/ClientLauncher.java @@ -312,7 +312,8 @@ // Resolve main class and method Class mainClass = Class.forName(profile.getMainClass()); - MethodHandle mainMethod = JVMHelper.LOOKUP.findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)); + MethodHandle mainMethod = JVMHelper.LOOKUP.findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)) + .asFixedArity(); // Invoke main method with exception wrapping LAUNCHED.set(true); @@ -424,7 +425,7 @@ } } -// It's here since first commit, there's no any reasons to remove :D +// She's here since first commit, there's no any reasons to remove :D // ++oyyysssssssssssooooooo++++++++/////:::::-------------................----:::----::+osssso+///+++++ooys/:/+ssssyyssyooooooo+++////:::::::::-::///++ossyhdddddddhhys/----::::::::::::::/://////////// // ++oyyssssssssssoooooooo++++++++//////:::::--------------------------------:::::-:::/+oo+//://+oo+//syysssssyyyyyhyyyssssssoo+++///::--:----:--:://++osyyhdddmddmdhys/------::::::::::::::://///////// // ++syyssssssssssoooooooo++++++++///////:::::::::::::::-----------------------::--::/++++/:--::/+++//osysshhhhyhhdyyyyyssyssoo++//::-------------::/+++oyhyhdddmddmdhy+--------:::::::::::::::///////// diff --git a/Launcher/source/client/ServerPinger.java b/Launcher/source/client/ServerPinger.java index 4a47ae4..99d4d50 100644 --- a/Launcher/source/client/ServerPinger.java +++ b/Launcher/source/client/ServerPinger.java @@ -5,8 +5,6 @@ import java.net.InetSocketAddress; import java.net.Socket; import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.Instant; import java.util.Objects; import java.util.regex.Pattern; @@ -15,6 +13,7 @@ import launcher.LauncherAPI; import launcher.client.ClientProfile.Version; import launcher.helper.IOHelper; +import launcher.helper.JVMHelper; import launcher.helper.LogHelper; import launcher.helper.VerifyHelper; import launcher.serialize.HInput; @@ -34,8 +33,8 @@ // Cache private final Object cacheLock = new Object(); private Result cache = null; - private Exception cacheException = null; - private Instant cacheTime = null; + private Throwable cacheError = null; + private long cacheUntil = Long.MIN_VALUE; @LauncherAPI public ServerPinger(InetSocketAddress address, Version version) { @@ -44,31 +43,24 @@ } @LauncherAPI - public Result ping() throws IOException { - Instant now = Instant.now(); + public Result ping() { synchronized (cacheLock) { // Update ping cache - if (cacheTime == null || Duration.between(now, cacheTime).toMillis() >= IOHelper.SOCKET_TIMEOUT) { - cacheTime = now; + if (System.currentTimeMillis() >= cacheUntil) { try { cache = doPing(); - cacheException = null; - } catch (IOException | IllegalArgumentException /* Protocol error */ e) { + cacheError = null; + } catch (Throwable exc) { cache = null; - cacheException = e; + cacheError = exc; + } finally { + cacheUntil = System.currentTimeMillis() + IOHelper.SOCKET_TIMEOUT; } } // Verify is result available - if (cache == null) { - if (cacheException instanceof IOException) { - throw (IOException) cacheException; - } - if (cacheException instanceof IllegalArgumentException) { - throw (IllegalArgumentException) cacheException; - } - cacheException = new IOException("Unavailable"); - throw (IOException) cacheException; + if (cacheError != null) { + JVMHelper.UNSAFE.throwException(cacheError); } // We're done diff --git a/Launcher/source/helper/IOHelper.java b/Launcher/source/helper/IOHelper.java index e18c1d9..60cfdeb 100644 --- a/Launcher/source/helper/IOHelper.java +++ b/Launcher/source/helper/IOHelper.java @@ -66,13 +66,13 @@ // Constants @LauncherAPI public static final int SOCKET_TIMEOUT = VerifyHelper.verifyInt( - Integer.parseUnsignedInt(System.getProperty("launcher.socketTimeout", Integer.toString(30000))), + 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.parseUnsignedInt(System.getProperty("launcher.httpTimeout", Integer.toString(5000))), + 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.parseUnsignedInt(System.getProperty("launcher.bufferSize", Integer.toString(4096))), + Integer.parseInt(System.getProperty("launcher.bufferSize", Integer.toString(4096))), VerifyHelper.POSITIVE, "launcher.bufferSize can't be <= 0"); // Platform-dependent @@ -107,7 +107,7 @@ public static void close(AutoCloseable closeable) { try { closeable.close(); - } catch (Exception exc) { + } catch (Throwable exc) { LogHelper.error(exc); } } diff --git a/Launcher/source/helper/LogHelper.java b/Launcher/source/helper/LogHelper.java index 00fc37d..975beb7 100644 --- a/Launcher/source/helper/LogHelper.java +++ b/Launcher/source/helper/LogHelper.java @@ -172,7 +172,7 @@ } return sw.toString(); } catch (IOException e) { - throw new InternalError(e); + throw new AssertionError(e); } } diff --git a/Launcher/source/request/CustomRequest.java b/Launcher/source/request/CustomRequest.java index 100e291..61e675d 100644 --- a/Launcher/source/request/CustomRequest.java +++ b/Launcher/source/request/CustomRequest.java @@ -24,7 +24,7 @@ } @Override - protected final T requestDo(HInput input, HOutput output) throws Exception { + protected final T requestDo(HInput input, HOutput output) throws Throwable { output.writeASCII(VerifyHelper.verifyIDName(getName()), 255); output.flush(); diff --git a/Launcher/source/request/Request.java b/Launcher/source/request/Request.java index 32c9b86..4118e2c 100644 --- a/Launcher/source/request/Request.java +++ b/Launcher/source/request/Request.java @@ -32,11 +32,11 @@ public abstract Type getType(); @LauncherAPI - protected abstract R requestDo(HInput input, HOutput output) throws Exception; + protected abstract R requestDo(HInput input, HOutput output) throws Throwable; @LauncherAPI @SuppressWarnings("DesignForExtension") - public R request() throws Exception { + public R request() throws Throwable { if (!started.compareAndSet(false, true)) { throw new IllegalStateException("Request already started"); } diff --git a/Launcher/source/request/update/LauncherRequest.java b/Launcher/source/request/update/LauncherRequest.java index 78f116b..c085bb4 100644 --- a/Launcher/source/request/update/LauncherRequest.java +++ b/Launcher/source/request/update/LauncherRequest.java @@ -44,7 +44,7 @@ @Override @SuppressWarnings("CallToSystemExit") - protected Result requestDo(HInput input, HOutput output) throws Exception { + protected Result requestDo(HInput input, HOutput output) throws Throwable { output.writeBoolean(EXE_BINARY); output.flush(); readError(input); diff --git a/Launcher/source/request/update/UpdateRequest.java b/Launcher/source/request/update/UpdateRequest.java index aa2182d..665548e 100644 --- a/Launcher/source/request/update/UpdateRequest.java +++ b/Launcher/source/request/update/UpdateRequest.java @@ -71,7 +71,7 @@ } @Override - public SignedObjectHolder request() throws Exception { + public SignedObjectHolder request() throws Throwable { Files.createDirectories(dir); localDir = new HashedDir(dir, matcher, false, digest);