From c4cd9812e0443c2fe639643da06458965abf95f2 Mon Sep 17 00:00:00 2001 From: Zaxar163 <35835496+Zaxar163@users.noreply.github.com> Date: Sun, 28 Apr 2019 07:07:01 +0200 Subject: [PATCH] =?UTF-8?q?[FIX]=20=D0=9E=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D0=BF=D0=B0=D1=82=D1=82=D0=B5=D1=80=D0=BD=D0=B0=D1=85?= =?UTF-8?q?=20ServerWrapper;=20=D0=B1=D0=B0=D0=B3=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20RAM.=20(#232)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [FIX] Убраны лишние классы из libLauncher. * [FIX] Получение RAM. * [FIX] Ещё фиксы такового с RAM. * [REFACTOR] Удалён лишний код. * [FEATURE] Теперь прикрутить sentry можно просто добавив библиотеку в libraries и дописав 3 строчки в runtime. * [FIX] Ошибка в паттернах ServerWrapper, порождает java.util.IllegalFormatConversionException --- .../dialog/overlay/settings/settings.js | 2 +- .../java/cpw/mods/fml/SafeExitJVMLegacy.java | 0 .../net/minecraftforge/fml/SafeExitJVM.java | 0 .../ru/gravit/launcher/LauncherAgent.java | 3 +- .../ru/gravit/launcher/LauncherEngine.java | 3 +- .../launcher/client/ClientLauncher.java | 8 +- .../launcher/client/FunctionalBridge.java | 12 +- .../launcher/hwid/OshiHWIDProvider.java | 2 +- .../ru/gravit/launcher/utils}/DirWatcher.java | 7 +- .../gravit/launcher}/utils/NativeJVMHalt.java | 2 +- .../gravit/launcher/server/ServerWrapper.java | 2 +- .../gravit/utils/downloader/Downloader.java | 213 ------------------ .../ru/gravit/utils/helper/JVMHelper.java | 17 -- .../ru/gravit/utils/helper/LogHelper.java | 8 + 14 files changed, 27 insertions(+), 252 deletions(-) rename {libLauncher => Launcher}/src/main/java/cpw/mods/fml/SafeExitJVMLegacy.java (100%) rename {libLauncher => Launcher}/src/main/java/net/minecraftforge/fml/SafeExitJVM.java (100%) rename {libLauncher/src/main/java/ru/gravit/launcher/hasher => Launcher/src/main/java/ru/gravit/launcher/utils}/DirWatcher.java (95%) rename {libLauncher/src/main/java/ru/gravit => Launcher/src/main/java/ru/gravit/launcher}/utils/NativeJVMHalt.java (96%) delete mode 100644 libLauncher/src/main/java/ru/gravit/utils/downloader/Downloader.java diff --git a/Launcher/runtime/dialog/overlay/settings/settings.js b/Launcher/runtime/dialog/overlay/settings/settings.js index 8429fd46..590dea8b 100644 --- a/Launcher/runtime/dialog/overlay/settings/settings.js +++ b/Launcher/runtime/dialog/overlay/settings/settings.js @@ -42,7 +42,7 @@ var settingsOverlay = { settingsOverlay.updateRAMLabel(); var ramSlider = holder.lookup("#ramSlider"); - ramSlider.setMax(JVMHelper.RAM); + ramSlider.setMax(FunctionalBridge.getJVMTotalMemory()); ramSlider.setSnapToTicks(true); ramSlider.setShowTickMarks(true); ramSlider.setShowTickLabels(true); diff --git a/libLauncher/src/main/java/cpw/mods/fml/SafeExitJVMLegacy.java b/Launcher/src/main/java/cpw/mods/fml/SafeExitJVMLegacy.java similarity index 100% rename from libLauncher/src/main/java/cpw/mods/fml/SafeExitJVMLegacy.java rename to Launcher/src/main/java/cpw/mods/fml/SafeExitJVMLegacy.java diff --git a/libLauncher/src/main/java/net/minecraftforge/fml/SafeExitJVM.java b/Launcher/src/main/java/net/minecraftforge/fml/SafeExitJVM.java similarity index 100% rename from libLauncher/src/main/java/net/minecraftforge/fml/SafeExitJVM.java rename to Launcher/src/main/java/net/minecraftforge/fml/SafeExitJVM.java diff --git a/Launcher/src/main/java/ru/gravit/launcher/LauncherAgent.java b/Launcher/src/main/java/ru/gravit/launcher/LauncherAgent.java index f42740fc..cb3f0ca4 100644 --- a/Launcher/src/main/java/ru/gravit/launcher/LauncherAgent.java +++ b/Launcher/src/main/java/ru/gravit/launcher/LauncherAgent.java @@ -7,7 +7,8 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodNode; -import ru.gravit.utils.NativeJVMHalt; + +import ru.gravit.launcher.utils.NativeJVMHalt; import ru.gravit.utils.helper.LogHelper; import java.io.ByteArrayOutputStream; diff --git a/Launcher/src/main/java/ru/gravit/launcher/LauncherEngine.java b/Launcher/src/main/java/ru/gravit/launcher/LauncherEngine.java index 85f82be1..52c4d22b 100644 --- a/Launcher/src/main/java/ru/gravit/launcher/LauncherEngine.java +++ b/Launcher/src/main/java/ru/gravit/launcher/LauncherEngine.java @@ -9,7 +9,6 @@ import ru.gravit.launcher.managers.ClientGsonManager; import ru.gravit.launcher.managers.ConsoleManager; import ru.gravit.launcher.request.Request; -import ru.gravit.launcher.request.websockets.ClientWebSocketService; import ru.gravit.launcher.request.websockets.StandartClientWebSocketService; import ru.gravit.utils.helper.CommonHelper; import ru.gravit.utils.helper.EnvHelper; @@ -25,7 +24,7 @@ public static void main(String... args) throws Throwable { JVMHelper.checkStackTrace(LauncherEngine.class); JVMHelper.verifySystemProperties(Launcher.class, true); EnvHelper.checkDangerousParams(); - //if(!LauncherAgent.isStarted()) throw new SecurityException("JavaAgent not set"); + if(!LauncherAgent.isStarted()) throw new SecurityException("JavaAgent not set"); LogHelper.printVersion("Launcher"); LogHelper.printLicense("Launcher"); // Start Launcher diff --git a/Launcher/src/main/java/ru/gravit/launcher/client/ClientLauncher.java b/Launcher/src/main/java/ru/gravit/launcher/client/ClientLauncher.java index 3609fe38..3d8d118a 100644 --- a/Launcher/src/main/java/ru/gravit/launcher/client/ClientLauncher.java +++ b/Launcher/src/main/java/ru/gravit/launcher/client/ClientLauncher.java @@ -3,10 +3,8 @@ import ru.gravit.launcher.*; import ru.gravit.launcher.guard.LauncherGuardManager; import ru.gravit.launcher.gui.JSRuntimeProvider; -import ru.gravit.launcher.hasher.DirWatcher; import ru.gravit.launcher.hasher.FileNameMatcher; import ru.gravit.launcher.hasher.HashedDir; -import ru.gravit.launcher.hasher.HashedEntry; import ru.gravit.launcher.managers.ClientGsonManager; import ru.gravit.launcher.profiles.ClientProfile; import ru.gravit.launcher.profiles.PlayerProfile; @@ -15,12 +13,12 @@ import ru.gravit.launcher.serialize.HInput; import ru.gravit.launcher.serialize.HOutput; import ru.gravit.launcher.serialize.stream.StreamObject; +import ru.gravit.launcher.utils.DirWatcher; import ru.gravit.utils.PublicURLClassLoader; import ru.gravit.utils.helper.*; import ru.gravit.utils.helper.JVMHelper.OS; import javax.swing.*; -import java.io.File; import java.io.IOException; import java.lang.ProcessBuilder.Redirect; import java.lang.invoke.MethodHandle; @@ -36,10 +34,8 @@ import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.PosixFilePermission; import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; public final class ClientLauncher { - private static final class ClassPathFileVisitor extends SimpleFileVisitor { private final Collection result; @@ -331,7 +327,7 @@ public static Process launch( context.playerProfile = params.pp; context.args.add(javaBin.toString()); context.args.add(MAGICAL_INTEL_OPTION); - if (params.ram > 0 && params.ram <= JVMHelper.RAM) { + if (params.ram > 0 && params.ram <= FunctionalBridge.getJVMTotalMemory()) { context.args.add("-Xms" + params.ram + 'M'); context.args.add("-Xmx" + params.ram + 'M'); } diff --git a/Launcher/src/main/java/ru/gravit/launcher/client/FunctionalBridge.java b/Launcher/src/main/java/ru/gravit/launcher/client/FunctionalBridge.java index 6639966d..a530b0d7 100644 --- a/Launcher/src/main/java/ru/gravit/launcher/client/FunctionalBridge.java +++ b/Launcher/src/main/java/ru/gravit/launcher/client/FunctionalBridge.java @@ -11,7 +11,6 @@ import ru.gravit.launcher.managers.HasherManager; import ru.gravit.launcher.managers.HasherStore; import ru.gravit.launcher.request.Request; -import ru.gravit.launcher.request.websockets.RequestInterface; import ru.gravit.launcher.serialize.signed.SignedObjectHolder; import ru.gravit.utils.helper.LogHelper; @@ -30,6 +29,8 @@ public class FunctionalBridge { public static AtomicReference hwid = new AtomicReference<>(); @LauncherAPI public static Thread getHWID = null; + + private static long cachedMemorySize = -1; @LauncherAPI public static HashedDirRunnable offlineUpdateRequest(String dirName, Path dir, SignedObjectHolder hdir, FileNameMatcher matcher, boolean digest) { @@ -42,11 +43,6 @@ public static HashedDirRunnable offlineUpdateRequest(String dirName, Path dir, S }; } - @LauncherAPI - public static void makeJsonRequest(RequestInterface request, Runnable callback) { - - } - @LauncherAPI public static void startTask(Runnable task) { threadPool.execute(task); @@ -61,7 +57,8 @@ public static HWID getHWID() { @LauncherAPI public static long getTotalMemory() { - return hwidProvider.getTotalMemory() >> 20; + if (cachedMemorySize > 0) return cachedMemorySize; + return cachedMemorySize = hwidProvider.getTotalMemory() >> 20; } @LauncherAPI @@ -106,6 +103,7 @@ public static void setAuthParams(AuthRequestEvent event) { public interface HashedDirRunnable { SignedObjectHolder run() throws Exception; } + @LauncherAPI public static void evalCommand(String cmd) { diff --git a/Launcher/src/main/java/ru/gravit/launcher/hwid/OshiHWIDProvider.java b/Launcher/src/main/java/ru/gravit/launcher/hwid/OshiHWIDProvider.java index 43003f1c..5e75d632 100644 --- a/Launcher/src/main/java/ru/gravit/launcher/hwid/OshiHWIDProvider.java +++ b/Launcher/src/main/java/ru/gravit/launcher/hwid/OshiHWIDProvider.java @@ -83,7 +83,7 @@ public String getMacAddr() { } public long getTotalMemory() { - if (noHWID) return -1; + if (noHWID) return 1024<<20; if (hardware == null) hardware = systemInfo.getHardware(); return hardware.getMemory().getTotal(); } diff --git a/libLauncher/src/main/java/ru/gravit/launcher/hasher/DirWatcher.java b/Launcher/src/main/java/ru/gravit/launcher/utils/DirWatcher.java similarity index 95% rename from libLauncher/src/main/java/ru/gravit/launcher/hasher/DirWatcher.java rename to Launcher/src/main/java/ru/gravit/launcher/utils/DirWatcher.java index cc0aaba8..9c0e30bf 100644 --- a/libLauncher/src/main/java/ru/gravit/launcher/hasher/DirWatcher.java +++ b/Launcher/src/main/java/ru/gravit/launcher/utils/DirWatcher.java @@ -1,8 +1,11 @@ -package ru.gravit.launcher.hasher; +package ru.gravit.launcher.utils; import ru.gravit.launcher.LauncherAPI; +import ru.gravit.launcher.hasher.FileNameMatcher; +import ru.gravit.launcher.hasher.HashedDir; +import ru.gravit.launcher.hasher.HashedEntry; +import ru.gravit.launcher.hasher.HashedFile; import ru.gravit.launcher.hasher.HashedEntry.Type; -import ru.gravit.utils.NativeJVMHalt; import ru.gravit.utils.helper.IOHelper; import ru.gravit.utils.helper.JVMHelper; import ru.gravit.utils.helper.JVMHelper.OS; diff --git a/libLauncher/src/main/java/ru/gravit/utils/NativeJVMHalt.java b/Launcher/src/main/java/ru/gravit/launcher/utils/NativeJVMHalt.java similarity index 96% rename from libLauncher/src/main/java/ru/gravit/utils/NativeJVMHalt.java rename to Launcher/src/main/java/ru/gravit/launcher/utils/NativeJVMHalt.java index 5a154c50..4401ceb1 100644 --- a/libLauncher/src/main/java/ru/gravit/utils/NativeJVMHalt.java +++ b/Launcher/src/main/java/ru/gravit/launcher/utils/NativeJVMHalt.java @@ -1,4 +1,4 @@ -package ru.gravit.utils; +package ru.gravit.launcher.utils; import cpw.mods.fml.SafeExitJVMLegacy; import net.minecraftforge.fml.SafeExitJVM; diff --git a/ServerWrapper/src/main/java/ru/gravit/launcher/server/ServerWrapper.java b/ServerWrapper/src/main/java/ru/gravit/launcher/server/ServerWrapper.java index 56b946ce..6a2948f3 100644 --- a/ServerWrapper/src/main/java/ru/gravit/launcher/server/ServerWrapper.java +++ b/ServerWrapper/src/main/java/ru/gravit/launcher/server/ServerWrapper.java @@ -1,4 +1,4 @@ -package ru.gravit.launcher.server; + package ru.gravit.launcher.server; import ru.gravit.launcher.ClientPermissions; import ru.gravit.launcher.Launcher; diff --git a/libLauncher/src/main/java/ru/gravit/utils/downloader/Downloader.java b/libLauncher/src/main/java/ru/gravit/utils/downloader/Downloader.java deleted file mode 100644 index dd128305..00000000 --- a/libLauncher/src/main/java/ru/gravit/utils/downloader/Downloader.java +++ /dev/null @@ -1,213 +0,0 @@ -package ru.gravit.utils.downloader; - -import ru.gravit.utils.helper.IOHelper; -import ru.gravit.utils.helper.LogHelper; - -import javax.net.ssl.HttpsURLConnection; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; -import java.security.cert.Certificate; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -public class Downloader implements Runnable { - @FunctionalInterface - public interface Handler { - void check(Certificate[] certs) throws IOException; - } - - public static final Map requestClient = Collections.singletonMap("User-Agent", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11"); - public static final int INTERVAL = 300; - - private final File file; - private final URL url; - private final String method; - public final Map requestProps; - public AtomicInteger writed = new AtomicInteger(0); - public final AtomicBoolean interrupt = new AtomicBoolean(false); - public final AtomicBoolean interrupted = new AtomicBoolean(false); - public AtomicReference ex = new AtomicReference<>(null); - private final int skip; - private final Handler handler; - - private HttpURLConnection connect = null; - - public Downloader(URL url, File file) { - this.requestProps = new HashMap<>(requestClient); - this.file = file; - this.url = url; - this.skip = 0; - this.handler = null; - this.method = null; - } - - public Downloader(URL url, File file, int skip) { - this.requestProps = new HashMap<>(requestClient); - this.file = file; - this.url = url; - this.skip = skip; - this.handler = null; - this.method = null; - } - - public Downloader(URL url, File file, Handler handler) { - this.requestProps = new HashMap<>(requestClient); - this.file = file; - this.url = url; - this.skip = 0; - this.handler = handler; - this.method = null; - } - - public Downloader(URL url, File file, int skip, Handler handler) { - this.requestProps = new HashMap<>(requestClient); - this.file = file; - this.url = url; - this.skip = skip; - this.handler = handler; - this.method = null; - } - - public Downloader(URL url, File file, int skip, Handler handler, Map requestProps) { - this.requestProps = new HashMap<>(requestProps); - this.file = file; - this.url = url; - this.skip = skip; - this.handler = handler; - this.method = null; - } - - public Downloader(URL url, File file, int skip, Handler handler, Map requestProps, String method) { - this.requestProps = new HashMap<>(requestProps); - this.file = file; - this.url = url; - this.skip = skip; - this.handler = handler; - this.method = method; - } - - public Downloader(URL url, File file, int skip, Handler handler, String method) { - this.requestProps = new HashMap<>(requestClient); - this.file = file; - this.url = url; - this.skip = skip; - this.handler = handler; - this.method = method; - } - - public Map getProps() { - return requestProps; - } - - public void addProp(String key, String value) { - requestProps.put(key, value); - } - - public File getFile() { - return file; - } - - public String getMethod() { - return method; - } - - public Handler getHandler() { - return handler; - } - - public void downloadFile() throws IOException { - if (!(url.getProtocol().equalsIgnoreCase("http") || url.getProtocol().equalsIgnoreCase("https"))) - throw new IOException("Invalid protocol."); - interrupted.set(false); - if (url.getProtocol().equalsIgnoreCase("http")) { - HttpURLConnection connect = (HttpURLConnection) (url).openConnection(); - this.connect = connect; - if (method != null) connect.setRequestMethod(method); - for (Map.Entry ent : requestProps.entrySet()) { - connect.setRequestProperty(ent.getKey(), ent.getValue()); - } - connect.setInstanceFollowRedirects(true); - if (!(connect.getResponseCode() >= 200 && connect.getResponseCode() < 300)) - throw new IOException(String.format("Invalid response of http server %d.", connect.getResponseCode())); - try (BufferedInputStream in = new BufferedInputStream(connect.getInputStream(), IOHelper.BUFFER_SIZE); - FileOutputStream fout = new FileOutputStream(file, skip != 0)) { - byte data[] = new byte[IOHelper.BUFFER_SIZE]; - int count = -1; - long timestamp = System.currentTimeMillis(); - int writed_local = 0; - in.skip(skip); - while ((count = in.read(data)) != -1) { - fout.write(data, 0, count); - writed_local += count; - if (System.currentTimeMillis() - timestamp > INTERVAL) { - writed.set(writed_local); - LogHelper.debug("Downloaded %d", writed_local); - if (interrupt.get()) { - break; - } - } - } - LogHelper.debug("Downloaded %d", writed_local); - writed.set(writed_local); - } - } else { - HttpsURLConnection connect = (HttpsURLConnection) (url).openConnection(); - this.connect = connect; - if (method != null) connect.setRequestMethod(method); - for (Map.Entry ent : requestProps.entrySet()) { - connect.setRequestProperty(ent.getKey(), ent.getValue()); - } - connect.setInstanceFollowRedirects(true); - if (handler != null) - handler.check(connect.getServerCertificates()); - if (!(connect.getResponseCode() >= 200 && connect.getResponseCode() < 300)) - throw new IOException(String.format("Invalid response of http server %d.", connect.getResponseCode())); - try (BufferedInputStream in = new BufferedInputStream(connect.getInputStream(), IOHelper.BUFFER_SIZE); - FileOutputStream fout = new FileOutputStream(file, skip != 0)) { - byte data[] = new byte[IOHelper.BUFFER_SIZE]; - int count = -1; - long timestamp = System.currentTimeMillis(); - int writed_local = 0; - in.skip(skip); - while ((count = in.read(data)) != -1) { - fout.write(data, 0, count); - writed_local += count; - if (System.currentTimeMillis() - timestamp > INTERVAL) { - writed.set(writed_local); - LogHelper.debug("Downloaded %d", writed_local); - if (interrupt.get()) { - break; - } - } - } - LogHelper.debug("Downloaded %d", writed_local); - writed.set(writed_local); - } - } - interrupted.set(true); - } - - @Override - public void run() { - try { - downloadFile(); - } catch (Throwable ex) { - this.ex.set(ex); - LogHelper.error(ex); - } - if (connect != null) - try { - connect.disconnect(); - } catch (Throwable ignored) { - } - } -} diff --git a/libLauncher/src/main/java/ru/gravit/utils/helper/JVMHelper.java b/libLauncher/src/main/java/ru/gravit/utils/helper/JVMHelper.java index e4e2510b..f0351c92 100644 --- a/libLauncher/src/main/java/ru/gravit/utils/helper/JVMHelper.java +++ b/libLauncher/src/main/java/ru/gravit/utils/helper/JVMHelper.java @@ -7,8 +7,6 @@ import java.lang.management.ManagementFactory; import java.lang.management.OperatingSystemMXBean; import java.lang.management.RuntimeMXBean; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.Arrays; @@ -52,8 +50,6 @@ public static OS byName(String name) { public static final int OS_BITS = getCorrectOSArch(); @LauncherAPI public static final int JVM_BITS = Integer.parseInt(System.getProperty("sun.arch.data.model")); - @LauncherAPI - public static final int RAM = getRAMAmount(); @LauncherAPI public static final SecurityManager SECURITY_MANAGER = System.getSecurityManager(); @@ -137,19 +133,6 @@ public static String getEnvPropertyCaseSensitive(String name) { return System.getenv().get(name); } - private static int getRAMAmount() { - int physicalRam = 1024; - try { - final Method getTotalPhysicalMemorySize = OPERATING_SYSTEM_MXBEAN.getClass().getDeclaredMethod("getTotalPhysicalMemorySize"); - getTotalPhysicalMemorySize.setAccessible(true); - physicalRam = (int) ((long)getTotalPhysicalMemorySize.invoke(OPERATING_SYSTEM_MXBEAN) >> 20); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException - | SecurityException e) { - throw new Error(e); - } - return Math.min(physicalRam, OS_BITS == 32 ? 1536 : 32768); // Limit 32-bit OS to 1536 MiB, and 64-bit OS to 32768 MiB (because it's enough) - } - @LauncherAPI public static boolean isJVMMatchesSystemArch() { return JVM_BITS == OS_BITS; diff --git a/libLauncher/src/main/java/ru/gravit/utils/helper/LogHelper.java b/libLauncher/src/main/java/ru/gravit/utils/helper/LogHelper.java index 6ff72f84..90898237 100644 --- a/libLauncher/src/main/java/ru/gravit/utils/helper/LogHelper.java +++ b/libLauncher/src/main/java/ru/gravit/utils/helper/LogHelper.java @@ -15,6 +15,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import java.util.function.Supplier; public final class LogHelper { @@ -50,6 +51,7 @@ public enum OutputTypes { } private static final Set OUTPUTS = Collections.newSetFromMap(new ConcurrentHashMap<>(2)); + private static final Set> EXCEPTIONS_CALLBACKS = Collections.newSetFromMap(new ConcurrentHashMap<>(2)); private static final OutputEnity STD_OUTPUT; private LogHelper() { @@ -60,6 +62,11 @@ public static void addOutput(OutputEnity output) { OUTPUTS.add(Objects.requireNonNull(output, "output")); } + @LauncherAPI + public static void addExcCallback(Consumer output) { + EXCEPTIONS_CALLBACKS.add(Objects.requireNonNull(output, "output")); + } + @LauncherAPI public static void addOutput(Output output, OutputTypes type) { OUTPUTS.add(new OutputEnity(Objects.requireNonNull(output, "output"), type)); @@ -105,6 +112,7 @@ public static void dev(String format, Object... args) { @LauncherAPI public static void error(Throwable exc) { + EXCEPTIONS_CALLBACKS.forEach(e -> e.accept(exc)); error(isStacktraceEnabled() ? toString(exc) : exc.toString()); }