diff --git a/core/src/main/java/com/volmit/iris/util/misc/SlimJar.java b/core/src/main/java/com/volmit/iris/util/misc/SlimJar.java index fa79b849d..e4c882b74 100644 --- a/core/src/main/java/com/volmit/iris/util/misc/SlimJar.java +++ b/core/src/main/java/com/volmit/iris/util/misc/SlimJar.java @@ -1,20 +1,35 @@ package com.volmit.iris.util.misc; +import com.volmit.iris.Iris; +import com.volmit.iris.core.nms.container.Pair; import io.github.slimjar.app.builder.ApplicationBuilder; +import io.github.slimjar.exceptions.InjectorException; +import io.github.slimjar.injector.loader.Injectable; +import io.github.slimjar.injector.loader.InjectableFactory; +import io.github.slimjar.injector.loader.IsolatedInjectableClassLoader; import io.github.slimjar.logging.ProcessLogger; +import io.github.slimjar.resolver.data.Repository; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; public class SlimJar { - private static final Logger LOGGER = Logger.getLogger("Iris"); + private static final String NAME = "Iris"; + private static final Logger LOGGER = Logger.getLogger(NAME); private static final ReentrantLock lock = new ReentrantLock(); private static final AtomicBoolean loaded = new AtomicBoolean(); + private static final boolean DISABLE_REMAPPER = Boolean.getBoolean("iris.disable-remapper"); public static void debug(boolean debug) { LOGGER.setLevel(debug ? Level.FINE : Level.INFO); @@ -31,28 +46,99 @@ public class SlimJar { } LOGGER.info("Loading libraries..."); - ApplicationBuilder.appending("Iris") - .downloadDirectoryPath(localRepository.toPath()) - .logger(new ProcessLogger() { - @Override - public void info(@NotNull String message, @Nullable Object... args) { - LOGGER.fine(message.formatted(args)); - } + load(localRepository.toPath(), new ProcessLogger() { + @Override + public void info(@NotNull String message, @Nullable Object... args) { + LOGGER.fine(message.formatted(args)); + } - @Override - public void error(@NotNull String message, @Nullable Object... args) { - LOGGER.severe(message.formatted(args)); - } + @Override + public void error(@NotNull String message, @Nullable Object... args) { + LOGGER.severe(message.formatted(args)); + } - @Override - public void debug(@NotNull String message, @Nullable Object... args) { - LOGGER.fine(message.formatted(args)); - } - }) - .build(); + @Override + public void debug(@NotNull String message, @Nullable Object... args) { + LOGGER.fine(message.formatted(args)); + } + }); LOGGER.info("Libraries loaded successfully!"); } finally { lock.unlock(); } } + + private static void load(Path downloadPath, ProcessLogger logger) { + try { + loadSpigot(downloadPath, logger); + } catch (Throwable e) { + Iris.warn("Failed to inject the library loader, falling back to application builder"); + ApplicationBuilder.appending(NAME) + .downloadDirectoryPath(downloadPath) + .logger(logger) + .build(); + } + } + + private static void loadSpigot(Path downloadPath, ProcessLogger logger) throws Throwable { + var current = SlimJar.class.getClassLoader(); + var libraryLoader = current.getClass().getDeclaredField("libraryLoader"); + libraryLoader.setAccessible(true); + if (!ClassLoader.class.isAssignableFrom(libraryLoader.getType())) throw new IllegalStateException("Failed to find library loader"); + + final var pair = findRemapper(); + final var remapper = pair.getA(); + final var factory = pair.getB(); + + final var libraries = factory.apply(new URL[0], current.getParent()); + final var injecting = InjectableFactory.create(downloadPath, List.of(Repository.central()), libraries); + + ApplicationBuilder.injecting(NAME, new Injectable() { + @Override + public void inject(@NotNull URL url) throws InjectorException { + try { + final List mapped; + synchronized (remapper) { + mapped = remapper.apply(List.of(Path.of(url.toURI()))); + } + + for (final Path path : mapped) { + injecting.inject(path.toUri().toURL()); + } + } catch (Throwable e) { + throw new InjectorException("Failed to inject " + url, e); + } + } + + @Override + public boolean isThreadSafe() { + return injecting.isThreadSafe(); + } + }) + .downloadDirectoryPath(downloadPath) + .logger(logger) + .build(); + + libraryLoader.set(current, libraries); + } + + private static Pair, List>, BiFunction> findRemapper() { + Function, List> mapper = null; + BiFunction factory = null; + if (!DISABLE_REMAPPER) { + try { + var libraryLoader = Class.forName("org.bukkit.plugin.java.LibraryLoader"); + var mapperField = libraryLoader.getDeclaredField("REMAPPER"); + var factoryField = libraryLoader.getDeclaredField("LIBRARY_LOADER_FACTORY"); + mapperField.setAccessible(true); + factoryField.setAccessible(true); + mapper = (Function, List>) mapperField.get(null); + factory = (BiFunction) factoryField.get(null); + } catch (Throwable ignored) {} + } + + if (mapper == null) mapper = Function.identity(); + if (factory == null) factory = (urls, parent) -> new IsolatedInjectableClassLoader(urls, List.of(), parent); + return new Pair<>(mapper, factory); + } }