package de.geolykt.starloader; import java.io.File; import java.lang.reflect.Field; import java.nio.file.Path; import java.util.List; import org.jetbrains.annotations.ApiStatus.Internal; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import net.minestom.server.extras.selfmodification.MinestomRootClassLoader; import de.geolykt.starloader.launcher.Utils; import de.geolykt.starloader.mod.DirectoryExtensionPrototypeList; import de.geolykt.starloader.mod.Extension; import de.geolykt.starloader.mod.ExtensionManager; import de.geolykt.starloader.mod.ExtensionPrototype; import de.geolykt.starloader.util.JavaInterop; public final class Starloader { private static final Logger LOGGER = LoggerFactory.getLogger(Starloader.class); private static Starloader instance; private final ExtensionManager extensions; @NotNull private final List<@NotNull ? extends ExtensionPrototype> extensionSource; @NotNull private final Path modDirectory; private Starloader(@NotNull DirectoryExtensionPrototypeList modSource) { this.extensionSource = modSource; this.modDirectory = modSource.getFolder().toPath(); this.extensions = new ExtensionManager(this.modDirectory.resolve(".picoresolve-cache")); } private Starloader(@NotNull List<@NotNull ? extends ExtensionPrototype> extensionSource, ExtensionManager extensions, @NotNull Path modDir) { this.extensionSource = extensionSource; this.extensions = extensions; this.modDirectory = modDir; } private void start() { this.extensions.loadExtensions(this.extensionSource); Starloader.LOGGER.info("From {} prototypes, {} extensions were loaded.", this.extensionSource.size(), this.extensions.getExtensions().size()); long start = System.currentTimeMillis(); Starloader.LOGGER.info("Initializing extension: preinit"); this.extensions.getExtensions().forEach(Extension::preInitialize); Starloader.LOGGER.info("Initializing extension: init"); this.extensions.getExtensions().forEach(extension -> { extension.initialize(); Starloader.LOGGER.info("Initialized extension {}.", extension.getDescription().getName()); }); Starloader.LOGGER.info("Initializing extension: postinit"); this.extensions.getExtensions().forEach(Extension::postInitialize); Starloader.LOGGER.info("All Extensions initialized within {}ms", (System.currentTimeMillis() - start)); // FIXME don't use shutdown hooks and/or have them deadlock-proof. Runtime.getRuntime().addShutdownHook(new Thread(this.extensions::shutdown, "ExtensionsShutdownThread")); } @Internal public static void start(@NotNull List<@NotNull ExtensionPrototype> extensionSource, @NotNull Path modDir) { if (Starloader.instance != null) { throw new IllegalStateException("Starloader initialized twice!"); } Starloader.LOGGER.info("Java version: {}. JavaInterop J9: {}", System.getProperty("java.version"), JavaInterop.isJava9()); Starloader.LOGGER.info("Storing log files in {}", Utils.getLogDirectory().toAbsolutePath().toString()); Starloader.instance = new Starloader(extensionSource, new ExtensionManager(modDir.resolve(".picoresolve-cache")), modDir); Starloader.instance.start(); } @Internal public static void start(@NotNull DirectoryExtensionPrototypeList modSource) { if (Starloader.instance != null) { throw new IllegalStateException("Starloader initialized twice!"); } Starloader.LOGGER.info("Java version: {}. JavaInterop J9: {}", System.getProperty("java.version"), JavaInterop.isJava9()); Starloader.LOGGER.info("Storing log files in {}", Utils.getLogDirectory().toAbsolutePath().toString()); Starloader.instance = new Starloader(modSource); Starloader.instance.start(); } public static ExtensionManager getExtensionManager() { return Starloader.instance.extensions; } @NotNull @Deprecated public static File getExtensionDir() { return Starloader.instance.getModDirectory().toFile(); } @NotNull public Path getModDirectory() { return this.modDirectory; } public static Starloader getInstance() { return Starloader.instance; } static { // Usually the Starloader class should be loaded by the root classloader, but in certain // cases this does not happen. To fix this, this class delegates all but the #start methods // to the actual instance loaded by the root classloader label001: if (Starloader.class.getClassLoader().getClass() != MinestomRootClassLoader.class) { MinestomRootClassLoader rootCl = MinestomRootClassLoader.getInstance(); try { Class slClass = Class.forName("de.geolykt.starloader.Starloader", false, rootCl); if (slClass == Starloader.class) { break label001; // Everything is happening as intended. The root classloader is likely delegating it's calls to some other classloader as the other classloader has the class loaded. } Field instanceField = slClass.getDeclaredField("instance"); Field srcField = slClass.getDeclaredField("extensionSource"); Field extField = slClass.getDeclaredField("extensions"); Field modDirField = slClass.getDeclaredField("modDirectory"); srcField.setAccessible(true); extField.setAccessible(true); modDirField.setAccessible(true); instanceField.setAccessible(true); Object instance = instanceField.get(null); if (instance == null) { throw new IllegalStateException("Unable to find instance of actual the Starloader class (Did it start yet?);" + " This Class instance was loaded by " + Starloader.class.getClassLoader() + ", where as it should've been " + slClass.getClassLoader()); } @SuppressWarnings({ "unchecked", "null" }) List<@NotNull ExtensionPrototype> extensionSource = (List) srcField.get(instance); ExtensionManager extensions = (ExtensionManager) extField.get(instance); Path modDir = (Path) modDirField.get(instance); assert extensionSource != null && modDir != null; Starloader.instance = new Starloader(extensionSource, extensions, modDir); } catch (Exception e) { throw new IllegalStateException("This class should be loaded by the root classloader!", e); } } } }