package de.geolykt.starloader.launcher; import java.io.IOException; import java.io.Reader; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.security.CodeSource; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.LoggerFactory; import org.stianloader.micromixin.transform.api.MixinTransformer; import org.stianloader.micromixin.transform.api.supertypes.ClassWrapperPool; import net.minestom.server.extras.selfmodification.HierarchyClassLoader; import net.minestom.server.extras.selfmodification.MinestomRootClassLoader; import de.geolykt.starloader.mod.DirectoryExtensionPrototypeList; import de.geolykt.starloader.mod.ExtensionPrototype; import de.geolykt.starloader.mod.NamedExtensionPrototype; import de.geolykt.starloader.transformers.StarplaneAnnotationsInlineTransformer; import de.geolykt.starloader.util.JavaInterop; /** * An entrypoint that is meant for debugging SLL mods within an IDE. * As such it heavily relies on system properties in order to work. The following properties * are used by this class: * * * * @since 4.0.0 */ public class IDELauncher { public static void main(String[] args) { String mainClass = System.getProperty("de.geolykt.starloader.launcher.CLILauncher.mainClass"); if (mainClass == null) { LoggerFactory.getLogger(IDELauncher.class).warn("Main class not set! Falling back to com.example.Main"); mainClass = "com.example.Main"; } String modDirectory = System.getProperty("de.geolykt.starloader.launcher.IDELauncher.modDirectory"); String modURLs = System.getProperty("de.geolykt.starloader.launcher.IDELauncher.modURLs"); if (modURLs == null) { if (modDirectory == null) { LoggerFactory.getLogger(IDELauncher.class).error("Unable to find the URLs of mods. Cannot proceed!"); } else { LoggerFactory.getLogger(IDELauncher.class).warn("Unable to find the URLs of mods."); } } else if (modDirectory == null) { LoggerFactory.getLogger(IDELauncher.class).warn("Extension directory undefined."); } String bootURLs = System.getProperty("de.geolykt.starloader.launcher.IDELauncher.bootURLs"); if (bootURLs == null) { LoggerFactory.getLogger(IDELauncher.class).error("Unable to find the URLs that need to be added to the root classloader. Cannot proceed!"); } if ((modURLs == null && modDirectory == null) || bootURLs == null) { throw new IllegalStateException("The modURLs and/or the bootURLs system property is not set."); } boolean inlineSPAnnotations = Boolean.getBoolean("de.geolykt.starloader.launcher.IDELauncher.inlineStarplaneAnnotations"); List bootPaths = new ArrayList<>(); List> mods = new ArrayList<>(); Map expansionProperties = null; readExpansionProperties: { String expansionPropertiesPath = System.getProperty("org.stianloader.sll.IDELauncher.propertyExpansionSource"); if (expansionPropertiesPath == null) { break readExpansionProperties; } Path propertiesPath = Paths.get(expansionPropertiesPath); if (Files.notExists(propertiesPath)) { LoggerFactory.getLogger(IDELauncher.class).error("The propertyExpansionSource system property points to a non-existent file: '{}'", propertiesPath); break readExpansionProperties; } Properties properties = new Properties(); try (Reader reader = Files.newBufferedReader(propertiesPath, StandardCharsets.UTF_8)) { properties.load(reader); } catch (IOException e) { LoggerFactory.getLogger(IDELauncher.class).error("Cannot read properties clared by the propertyExpansionSource system property defined as path '{}'", propertiesPath, e); break readExpansionProperties; } expansionProperties = new HashMap<>(); for (Map.Entry entry : properties.entrySet()) { String value = entry.getValue() == null ? null : entry.getValue().toString(); expansionProperties.put(entry.getKey().toString(), value); } } Map smapURIAliases = new HashMap<>(); readSmapURIAliases: { String smapURIAliasesJSONString = System.getProperty("org.stianloader.sll.IDELauncher.smapURIAliases"); if (smapURIAliasesJSONString == null) { break readSmapURIAliases; } JSONObject jsonObject; try { jsonObject = new JSONObject(smapURIAliasesJSONString); } catch (JSONException e) { LoggerFactory.getLogger(IDELauncher.class).warn("Unable to interpret property 'org.stianloader.sll.IDELauncher.smapURIAliases': JSON decode error.", e); break readSmapURIAliases; } for (String key : jsonObject.keySet()) { if (key == null) { continue; } try { new URI(key); } catch (URISyntaxException e) { LoggerFactory.getLogger(IDELauncher.class).warn("Stumbled on potentially erroneous key '{}' while interpreting property 'org.stianloader.sll.IDELauncher.smapURIAliases'. Ensure that the key is a valid URI.", key, e); } smapURIAliases.put(key, jsonObject.getString(key)); } } try { for (Object o : new JSONArray(bootURLs)) { if (!(o instanceof String)) { throw new IllegalStateException("Encountered invalid object in boot URL property: " + o); } try { bootPaths.add(new URL((String) o)); } catch (MalformedURLException e) { LoggerFactory.getLogger(IDELauncher.class).error("Invalid URL " + o, e); throw new IllegalStateException("Encountered invalid URL in boot URL property: " + o, e); } } } catch (JSONException e) { LoggerFactory.getLogger(IDELauncher.class).error("Invalid bootURLs system property: {}", bootURLs, e); } if (modURLs != null) { for (Object o0 : new JSONArray(modURLs)) { if (!(o0 instanceof JSONArray)) { LoggerFactory.getLogger(IDELauncher.class).error("Invalid mod {}", o0); throw new IllegalStateException("Encountered invalid object in mods URL property (it should be an array): " + o0); } List mod = new ArrayList<>(); for (Object o : (JSONArray) o0) { if (!(o instanceof String)) { LoggerFactory.getLogger(IDELauncher.class).error("Invalid URL {}", o); throw new IllegalStateException("Invalid URL: " + o); } try { mod.add(new URL((String) o)); } catch (MalformedURLException e) { LoggerFactory.getLogger(IDELauncher.class).error("Invalid URL {}", o); throw new IllegalStateException("Encountered invalid URL in boot URL property: " + o, e); } } mods.add(mod); } } List prototypes = new ArrayList<>(); for (List mod : mods) { prototypes.add(new ExtensionPrototype(mod, true, expansionProperties)); } Path modDirectoryPath = Paths.get(modDirectory == null ? "mods" : modDirectory); if (!Files.notExists(modDirectoryPath)) { prototypes.addAll(new DirectoryExtensionPrototypeList(modDirectoryPath.toFile())); } LoggerFactory.getLogger(IDELauncher.class).info("Using prototypes from following sources:"); prototypes.forEach((prototype) -> { prototype.enabled = true; if (prototype instanceof NamedExtensionPrototype) { NamedExtensionPrototype namedPrototype = (NamedExtensionPrototype) prototype; LoggerFactory.getLogger(IDELauncher.class).info("- {} v{} (loaded from {})", namedPrototype.name, namedPrototype.version, namedPrototype.originURLs); } else { LoggerFactory.getLogger(IDELauncher.class).info("- {}", prototype.originURLs); } }); MinestomRootClassLoader cl = MinestomRootClassLoader.getInstance(); bootPaths.forEach(cl::addURL); if (inlineSPAnnotations) { LoggerFactory.getLogger(IDELauncher.class).info("Making use of the StarplaneAnnotationsInlineTransformer."); cl.addASMTransformer(new StarplaneAnnotationsInlineTransformer()); } // Start mixins & load extensions MixinBytecodeProvider provider = new MixinBytecodeProvider(smapURIAliases); ClassWrapperPool cwPool = new ClassWrapperPool(); cwPool.addProvider(provider); MixinTransformer transformer = new MixinTransformer<>(provider, cwPool); transformer.setLogger(new SLF4JLoggingFacade()); cl.addASMTransformer(new ASMMixinTransformer(transformer)); // ensure extensions are loaded when starting the server try { Class slClass = cl.loadClass("de.geolykt.starloader.Starloader"); MethodHandles.lookup().findStatic(slClass, "start", MethodType.methodType(void.class, List.class, Path.class)).invokeExact(prototypes, modDirectoryPath.toAbsolutePath()); } catch (Throwable t) { LoggerFactory.getLogger(IDELauncher.class).error("Unable to start up SLL. Quitting.", t); return; } LoggerFactory.getLogger(IDELauncher.class).info("Starting main class " + mainClass + " with arguments " + Arrays.toString(args)); try { Class mainClassInstance = cl.loadClass(mainClass); if (mainClassInstance.getClassLoader() != cl) { LoggerFactory.getLogger(IDELauncher.class).warn("Main class '{}' loaded by wrong Classloader '{}', expected it to be loaded by '{}'. Some runtime anomalies are to be expected; Did you set up the classpaths correctly?", mainClass, JavaInterop.getClassloaderName(mainClassInstance.getClassLoader()), JavaInterop.getClassloaderName(cl)); } Utils.startMain(mainClassInstance, args); } catch (Throwable t) { t.printStackTrace(); } } }