diff --git a/gradle.properties b/gradle.properties index f704aed81..c478d5de3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ group = org.quiltmc description = The mod loading component of Quilt url = https://github.com/quiltmc/quilt-loader # Don't forget to change this in QuiltLoaderImpl as well -quilt_loader = 0.26.0-beta.2 +quilt_loader = 0.26.4-beta.1 # Fabric & Quilt Libraries asm = 9.6 diff --git a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/McVersionLookup.java index fd937532b..8f5c9510b 100644 --- a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/McVersionLookup.java @@ -284,7 +284,9 @@ protected static String getRelease(String version) { int year = Integer.parseInt(matcher.group(1)); int week = Integer.parseInt(matcher.group(2)); - if (year == 23 && week >= 51 || year >= 24) { + if (year >= 24 && week >= 18) { + return "1.21"; + } else if (year == 23 && week >= 51 || year == 24 && week <= 14) { return "1.20.5"; } else if (year == 23 && week >= 40 && week <= 46) { return "1.20.3"; diff --git a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/MinecraftGameProvider.java index 100056c06..e73760bb7 100644 --- a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -50,6 +50,7 @@ import org.quiltmc.loader.impl.game.minecraft.patch.EntrypointPatch; import org.quiltmc.loader.impl.game.minecraft.patch.TinyFDPatch; import org.quiltmc.loader.impl.launch.common.QuiltLauncher; +import org.quiltmc.loader.impl.launch.common.QuiltLauncherBase; import org.quiltmc.loader.impl.metadata.qmj.V1ModMetadataBuilder; import org.quiltmc.loader.impl.util.Arguments; import org.quiltmc.loader.impl.util.ExceptionUtil; @@ -189,6 +190,11 @@ public boolean isObfuscated() { return true; // generally yes... } + @Override + public String getNamespace() { + return QuiltLauncherBase.getLauncher().isDevelopment() ? "named" : "intermediary"; + } + @Override public boolean requiresUrlClassLoader() { return hasModLoader; @@ -431,7 +437,7 @@ public void initialize(QuiltLauncher launcher) { setupLogHandler(launcher, true); - transformer.locateEntrypoints(launcher, gameJars); + transformer.locateEntrypoints(launcher, getNamespace(), gameJars); } private void setupLogHandler(QuiltLauncher launcher, boolean useTargetCl) { diff --git a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/BrandingPatch.java b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/BrandingPatch.java index d5b153a6b..617e0128a 100644 --- a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/BrandingPatch.java +++ b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/BrandingPatch.java @@ -30,7 +30,7 @@ public final class BrandingPatch extends GamePatch { @Override - public void process(QuiltLauncher launcher, GamePatchContext context) { + public void process(QuiltLauncher launcher, String namespace, GamePatchContext context) { for (String brandClassName : new String[] { "net.minecraft.client.ClientBrandRetriever", "net.minecraft.server.MinecraftServer" diff --git a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/EntrypointPatch.java b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/EntrypointPatch.java index 199eee925..e00d16420 100644 --- a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/EntrypointPatch.java +++ b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/EntrypointPatch.java @@ -75,7 +75,7 @@ private void finishEntrypoint(EnvType type, ListIterator it) { } @Override - public void process(QuiltLauncher launcher, GamePatchContext context) { + public void process(QuiltLauncher launcher, String namespace, GamePatchContext context) { EnvType type = launcher.getEnvironmentType(); String entrypoint = launcher.getEntrypoint(); Version gameVersion = getGameVersion(); diff --git a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/TinyFDPatch.java b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/TinyFDPatch.java index c114d7aea..81230f4d9 100644 --- a/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/TinyFDPatch.java +++ b/minecraft/src/main/java/org/quiltmc/loader/impl/game/minecraft/patch/TinyFDPatch.java @@ -45,7 +45,7 @@ public final class TinyFDPatch extends GamePatch { private static final String DIALOG_TITLE = "Select settings file (.json)"; @Override - public void process(QuiltLauncher launcher, GamePatchContext context) { + public void process(QuiltLauncher launcher, String namespace, GamePatchContext context) { if (launcher.getEnvironmentType() != EnvType.CLIENT) { // Fix should only be applied to clients. return; diff --git a/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java b/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java index 48e47e3d5..0dd71764e 100644 --- a/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java +++ b/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java @@ -131,7 +131,7 @@ public final class QuiltLoaderImpl { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.26.0-beta.2"; + public static final String VERSION = "0.26.4-beta.1"; public static final String MOD_ID = "quilt_loader"; public static final String DEFAULT_MODS_DIR = "mods"; public static final String DEFAULT_CACHE_DIR = ".cache"; @@ -603,6 +603,8 @@ private ModSolveResult runPlugins() { temporarySourcePaths.put(mod.from(), plugins.convertToSourcePaths(mod.from())); } + QuiltLauncherBase.getLauncher().setPluginPackages(plugins.getPluginPackages()); + if (displayedMessage) { return result; } diff --git a/src/main/java/org/quiltmc/loader/impl/entrypoint/GamePatch.java b/src/main/java/org/quiltmc/loader/impl/entrypoint/GamePatch.java index 65b1b60fa..bfef73b9b 100644 --- a/src/main/java/org/quiltmc/loader/impl/entrypoint/GamePatch.java +++ b/src/main/java/org/quiltmc/loader/impl/entrypoint/GamePatch.java @@ -18,6 +18,7 @@ package org.quiltmc.loader.impl.entrypoint; import org.objectweb.asm.ClassReader; +import org.quiltmc.loader.impl.QuiltLoaderImpl; import org.quiltmc.loader.impl.launch.common.QuiltLauncher; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; @@ -128,12 +129,18 @@ protected boolean isPublicInstance(int access) { return ((access & 0x0F) == (Opcodes.ACC_PUBLIC | 0 /* non-static */)); } - public void process(QuiltLauncher launcher, Function classSource, Consumer classEmitter) { + public void process(QuiltLauncher launcher, String namespace, Function classSource, Consumer classEmitter) { throw new AbstractMethodError(getClass() + " must override one of the 'process' methods!"); } + public void process(QuiltLauncher launcher, Function classSource, Consumer classEmitter) { + process(launcher, launcher.getTargetNamespace(), classSource, classEmitter); + } + public void process(QuiltLauncher launcher, String namespace, GamePatchContext context) { + process(launcher, namespace, context::getClassSourceReader, context::addPatchedClass); + } public void process(QuiltLauncher launcher, GamePatchContext context) { - process(launcher, context::getClassSourceReader, context::addPatchedClass); + process(launcher, launcher.getTargetNamespace(), context); } } diff --git a/src/main/java/org/quiltmc/loader/impl/entrypoint/GameTransformer.java b/src/main/java/org/quiltmc/loader/impl/entrypoint/GameTransformer.java index e007ecd4a..258abcd07 100644 --- a/src/main/java/org/quiltmc/loader/impl/entrypoint/GameTransformer.java +++ b/src/main/java/org/quiltmc/loader/impl/entrypoint/GameTransformer.java @@ -67,6 +67,9 @@ private void addPatchedClass(ClassNode node) { } public void locateEntrypoints(QuiltLauncher launcher, List gameJars) { + this.locateEntrypoints(launcher, null, gameJars); + } + public void locateEntrypoints(QuiltLauncher launcher, String namespace, List gameJars) { if (entrypointsLocated) { return; } @@ -129,7 +132,10 @@ public void addPatchedClass(ClassNode node) { }; for (GamePatch patch : patches) { - patch.process(launcher, context); + if (namespace == null) + patch.process(launcher, context); + else + patch.process(launcher, namespace, context); } for (ClassNode node : addedClassNodes.values()) { diff --git a/src/main/java/org/quiltmc/loader/impl/game/GameProvider.java b/src/main/java/org/quiltmc/loader/impl/game/GameProvider.java index c0cb54c46..2bfe78908 100644 --- a/src/main/java/org/quiltmc/loader/impl/game/GameProvider.java +++ b/src/main/java/org/quiltmc/loader/impl/game/GameProvider.java @@ -44,6 +44,9 @@ public interface GameProvider { String getEntrypoint(); Path getLaunchDirectory(); boolean isObfuscated(); + default String getNamespace() { + return isObfuscated()? "intermediary": "named"; + }; boolean requiresUrlClassLoader(); boolean isEnabled(); diff --git a/src/main/java/org/quiltmc/loader/impl/gui/AbstractWindow.java b/src/main/java/org/quiltmc/loader/impl/gui/AbstractWindow.java index e98c1bde1..35f78b8e9 100644 --- a/src/main/java/org/quiltmc/loader/impl/gui/AbstractWindow.java +++ b/src/main/java/org/quiltmc/loader/impl/gui/AbstractWindow.java @@ -24,6 +24,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.function.Supplier; import org.quiltmc.loader.api.LoaderValue; import org.quiltmc.loader.api.LoaderValue.LObject; @@ -126,7 +127,7 @@ void onClosed() { sendSignal("closed"); } - void waitUntilClosed() throws LoaderGuiException { + void waitUntilClosed(Supplier errorChecker) throws LoaderGuiException { try { while (true) { try { @@ -134,7 +135,14 @@ void waitUntilClosed() throws LoaderGuiException { break; } catch (TimeoutException e) { if (QuiltForkComms.getCurrentComms() == null) { - throw new LoaderGuiException("Forked communication failure; check the log for details!", e); + Error error = errorChecker.get(); + if (error == null) { + throw new LoaderGuiException("Forked communication failure; check the log for details!", e); + } else { + LoaderGuiException ex = new LoaderGuiException("Forked communication failure!", error); + ex.addSuppressed(e); + throw ex; + } } } } diff --git a/src/main/java/org/quiltmc/loader/impl/gui/GuiManagerImpl.java b/src/main/java/org/quiltmc/loader/impl/gui/GuiManagerImpl.java index 96e771bef..a11607a73 100644 --- a/src/main/java/org/quiltmc/loader/impl/gui/GuiManagerImpl.java +++ b/src/main/java/org/quiltmc/loader/impl/gui/GuiManagerImpl.java @@ -97,9 +97,7 @@ public static QuiltLoaderIcon allocateIcons(Map imageSiz } public static QuiltLoaderIcon allocateIcons(byte[][] imageBytes) { - PluginIconImpl icon = new PluginIconImpl(imageBytes); - icon.send(); - return icon; + return new PluginIconImpl(imageBytes); } public static QuiltLoaderIcon getModIcon(ModContainer mod) { diff --git a/src/main/java/org/quiltmc/loader/impl/gui/QuiltFork.java b/src/main/java/org/quiltmc/loader/impl/gui/QuiltFork.java index d7f4f7957..6a9735eb2 100644 --- a/src/main/java/org/quiltmc/loader/impl/gui/QuiltFork.java +++ b/src/main/java/org/quiltmc/loader/impl/gui/QuiltFork.java @@ -16,6 +16,7 @@ package org.quiltmc.loader.impl.gui; +import java.awt.*; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; @@ -56,10 +57,11 @@ public class QuiltFork { private static final QuiltForkComms COMMS; private static final LoaderValueHelper HELPER = LoaderValueHelper.IO_EXCEPTION; private static final IOException FORK_EXCEPTION; + private static Error previousServerError; static { GameProvider provider = QuiltLoaderImpl.INSTANCE.getGameProvider(); - if (Boolean.getBoolean(SystemProperties.DISABLE_FORKED_GUIS) || !provider.canOpenGui()) { + if (Boolean.getBoolean(SystemProperties.DISABLE_FORKED_GUIS) || !provider.canOpenGui() || GraphicsEnvironment.isHeadless()) { COMMS = null; FORK_EXCEPTION = null; } else { @@ -130,13 +132,18 @@ public static void open(QuiltLoaderWindow window, boolean shouldWait) throws return; } + if (previousServerError != null) { + // Gui NOT disabled, but it previously crashed. + throw new LoaderGuiException("The gui server encountered an error!", previousServerError); + } + AbstractWindow realWindow = (AbstractWindow) window; realWindow.send(); realWindow.open(); if (shouldWait) { - realWindow.waitUntilClosed(); + realWindow.waitUntilClosed(() -> previousServerError); } } @@ -187,10 +194,15 @@ private static void handleMessageFromServer0(LoaderValue msg) throws IOException switch (type) { case ForkCommNames.ID_EXCEPTION: { // The server encountered an exception - // We should really store the exception, but for now just exit + LoaderValue detail = packet.get("detail"); + if (detail == null || detail.type() != LType.STRING) { + Log.error(LogCategory.COMMS, "The gui-server encountered an unknown error!"); + previousServerError = new Error("[Unknown error: the gui server didn't send a detail trace]"); + } else { + Log.error(LogCategory.COMMS, "The gui-server encountered an error:\n" + detail.asString()); + previousServerError = new Error("Previous error stacktrace:\n" + detail.asString()); + } COMMS.close(); - Log.error(LogCategory.COMMS, "The gui-server encountered an error!"); - System.exit(1); return; } case ForkCommNames.ID_GUI_OBJECT_UPDATE: { diff --git a/src/main/java/org/quiltmc/loader/impl/gui/QuiltForkComms.java b/src/main/java/org/quiltmc/loader/impl/gui/QuiltForkComms.java index 04c0cc8d3..1c42b2571 100644 --- a/src/main/java/org/quiltmc/loader/impl/gui/QuiltForkComms.java +++ b/src/main/java/org/quiltmc/loader/impl/gui/QuiltForkComms.java @@ -23,6 +23,9 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.ProcessBuilder.Redirect; import java.net.InetAddress; import java.net.Socket; @@ -47,11 +50,15 @@ import org.quiltmc.loader.impl.util.LimitedInputStream; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; +import org.quiltmc.loader.impl.util.SystemProperties; +import org.quiltmc.loader.impl.util.log.Log; +import org.quiltmc.loader.impl.util.log.LogCategory; @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) public class QuiltForkComms { private static final String SYS_PROP = "quiltmc.loader.fork.comms_port"; + private static final boolean PRINT_NET_PACKETS = Boolean.getBoolean(SystemProperties.DEBUG_GUI_PACKETS); private static ForkSide side; private static final AtomicReference currentComms = new AtomicReference<>(); @@ -323,6 +330,12 @@ private void runWriter() { try { BlockingQueue queue = writerQueue; LoaderValue value = queue == null ? lvf().nul() : queue.take(); + if (PRINT_NET_PACKETS) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + LoaderValueFactory.getFactory().write(value, baos); + String json = new String(baos.toByteArray(), StandardCharsets.UTF_8); + Log.info(LogCategory.GUI, "Sending packet: " + json); + } ByteArrayOutputStream baos = new ByteArrayOutputStream(); lvf().write(value, baos); byte[] written = baos.toByteArray(); @@ -380,6 +393,14 @@ private void readMessage(LoaderValue value) { } catch (Throwable t) { Map map = new HashMap<>(); map.put("__TYPE", lvf().string(ForkCommNames.ID_EXCEPTION)); + + StringWriter sw = new StringWriter(); + try (PrintWriter printer = new PrintWriter(sw)) { + t.printStackTrace(printer); + printer.flush(); + } + map.put("detail", lvf().string(sw.toString())); + send(lvf().object(map)); throw t; } diff --git a/src/main/java/org/quiltmc/loader/impl/launch/common/MappingConfiguration.java b/src/main/java/org/quiltmc/loader/impl/launch/common/MappingConfiguration.java index d2919e27b..29bb857c7 100644 --- a/src/main/java/org/quiltmc/loader/impl/launch/common/MappingConfiguration.java +++ b/src/main/java/org/quiltmc/loader/impl/launch/common/MappingConfiguration.java @@ -39,6 +39,8 @@ import net.fabricmc.mappingio.format.tiny.Tiny2FileReader; import org.quiltmc.loader.api.QuiltLoader; +import org.quiltmc.loader.impl.QuiltLoaderImpl; +import org.quiltmc.loader.impl.game.GameProvider; import org.quiltmc.loader.impl.util.ManifestUtil; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; @@ -101,6 +103,11 @@ public List getNamespaces() { } public String getTargetNamespace() { + GameProvider gameProvider = QuiltLoaderImpl.INSTANCE.tryGetGameProvider(); + if (gameProvider != null) + return gameProvider.getNamespace(); + // else + // If the game provider doesn't exist yet, use the development flag to set the namespace return QuiltLauncherBase.getLauncher().isDevelopment() ? "named" : "intermediary"; } @@ -186,7 +193,7 @@ private void initialize() { String mojmapPath = System.getProperty(SystemProperties.MOJMAP_PATH); if (mojmapPath != null) { try (BufferedReader reader = Files.newBufferedReader(Paths.get(mojmapPath))) { - ProGuardFileReader.read(reader, "mojmap", "official", new MappingSourceNsSwitch(mappings, "official")); + ProGuardFileReader.read(reader, "mojang", "official", new MappingSourceNsSwitch(mappings, "official")); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/src/main/java/org/quiltmc/loader/impl/launch/common/QuiltLauncher.java b/src/main/java/org/quiltmc/loader/impl/launch/common/QuiltLauncher.java index 2845928ab..975cd2df1 100644 --- a/src/main/java/org/quiltmc/loader/impl/launch/common/QuiltLauncher.java +++ b/src/main/java/org/quiltmc/loader/impl/launch/common/QuiltLauncher.java @@ -42,6 +42,7 @@ public interface QuiltLauncher { void setTransformCache(URL insideTransformCache); void setHiddenClasses(Set classes); void setHiddenClasses(Map classes); + void setPluginPackages(Map hiddenClasses); void hideParentUrl(URL hidden); void hideParentPath(Path obf); void validateGameClassLoader(Object gameInstance); diff --git a/src/main/java/org/quiltmc/loader/impl/launch/knot/Knot.java b/src/main/java/org/quiltmc/loader/impl/launch/knot/Knot.java index 9053671cd..fef4f33f0 100644 --- a/src/main/java/org/quiltmc/loader/impl/launch/knot/Knot.java +++ b/src/main/java/org/quiltmc/loader/impl/launch/knot/Knot.java @@ -318,6 +318,11 @@ public void setHiddenClasses(Map hiddenClasses) { classLoader.getDelegate().setHiddenClasses(hiddenClasses); } + @Override + public void setPluginPackages(Map hiddenClasses) { + classLoader.getDelegate().setPluginPackages(hiddenClasses); + } + @Override public void hideParentUrl(URL parent) { classLoader.getDelegate().hideParentUrl(parent); diff --git a/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassDelegate.java index 46c79bd7c..06b30eccf 100644 --- a/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -136,6 +136,8 @@ public Optional getQuiltMod() { /** Map of package to the reason why it cannot be loaded. If the package can be loaded then the value is the empty string. */ private final Map packageLoadDenyCache = new ConcurrentHashMap<>(); + private Map pluginPackages = Collections.emptyMap(); + KnotClassDelegate(boolean isDevelopment, EnvType envType, KnotClassLoaderInterface itf, GameProvider provider) { this.isDevelopment = isDevelopment; this.envType = envType; @@ -244,11 +246,19 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound } } - if (!allowedPrefixes.isEmpty()) { + if (!allowedPrefixes.isEmpty() && url != null) { + String fileName = LoaderUtil.getClassFileName(name); + URL codeSource = null; + + try { + codeSource = UrlUtil.getSource(fileName, url); + } catch (UrlConversionException e) { + Log.warn(LogCategory.GENERAL, "Failed to get the code source URL for " + url); + } + String[] prefixes; - if (url != null - && (prefixes = allowedPrefixes.get(url.toString())) != null) { + if (codeSource != null && (prefixes = allowedPrefixes.get(codeSource.toString())) != null) { assert prefixes.length > 0; boolean found = false; @@ -260,7 +270,7 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound } if (!found) { - throw new ClassNotFoundException("class "+name+" is currently restricted from being loaded"); + throw new ClassNotFoundException("class " + name + " is currently restricted from being loaded"); } } } @@ -279,6 +289,11 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound } } + ClassLoader pluginCl = pluginPackages.get(pkgString); + if (pluginCl != null) { + return pluginCl.loadClass(name); + } + String hideReason = hiddenClasses.get(name); if (hideReason != null) { throw new RuntimeException("Cannot load " + name + " " + hideReason); @@ -350,7 +365,6 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound return c; } - private boolean shouldRerouteToParent(String name) { return name.startsWith("org.slf4j.") || name.startsWith("org.apache.logging.log4j."); } @@ -513,7 +527,7 @@ public byte[] getPreMixinClassByteArray(URL classFileURL, String name) { Log.info(LogCategory.GENERAL, "Loading " + name + " early", new Throwable()); } - if (name.startsWith("org.quiltmc.loader.impl.patch.")) { + if (name.startsWith("org.quiltmc.loader.impl.patch.PATCHED.")) { return PatchLoader.getNewPatchedClass(name); } @@ -567,6 +581,10 @@ void setHiddenClasses(Map hiddenClasses) { this.hiddenClasses = hiddenClasses; } + void setPluginPackages(Map map) { + pluginPackages = map; + } + void hideParentUrl(URL parentPath) { parentHiddenUrls.add(parentPath.toString()); } diff --git a/src/main/java/org/quiltmc/loader/impl/patch/PatchLoader.java b/src/main/java/org/quiltmc/loader/impl/patch/PatchLoader.java index a84e94517..9a6e5654f 100644 --- a/src/main/java/org/quiltmc/loader/impl/patch/PatchLoader.java +++ b/src/main/java/org/quiltmc/loader/impl/patch/PatchLoader.java @@ -16,12 +16,18 @@ package org.quiltmc.loader.impl.patch; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; +import org.quiltmc.loader.api.QuiltLoader; import org.quiltmc.loader.impl.patch.reflections.ReflectionsClassPatcher; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; +import org.quiltmc.loader.impl.util.SystemProperties; import org.quiltmc.loader.impl.util.log.Log; import org.quiltmc.loader.impl.util.log.LogCategory; @@ -31,6 +37,24 @@ public abstract class PatchLoader { public static void load() { ReflectionsClassPatcher.load(patchedClasses); + + if (Boolean.getBoolean(SystemProperties.DEBUG_DUMP_PATCHED_CLASSES)) { + Path root = QuiltLoader.getGameDir().resolve("quilt_loader_patched_classes"); + try { + Files.createDirectories(root); + Files.write(root.resolve("FILE_LIST.txt"), patchedClasses.keySet().stream().sorted().collect(Collectors.toList())); + for (Map.Entry entry : patchedClasses.entrySet()) { + Path file = root.resolve(entry.getKey().replace(".", root.getFileSystem().getSeparator()) + ".class"); + Files.createDirectories(file.getParent()); + Files.write(file, entry.getValue()); + } + } catch (IOException e) { + throw new Error( + "Failed to save patched classes! (If you don't need them then remove '-D" + + SystemProperties.DEBUG_DUMP_PATCHED_CLASSES + "=true from your VM options)", e + ); + } + } } public static byte[] getNewPatchedClass(String name) { diff --git a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsClassPatcher.java b/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsClassPatcher.java index 851371ca5..74bf0f5ed 100644 --- a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsClassPatcher.java +++ b/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsClassPatcher.java @@ -17,6 +17,12 @@ package org.quiltmc.loader.impl.patch.reflections; import java.io.IOException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; import java.util.Map; import org.objectweb.asm.ClassReader; @@ -24,9 +30,13 @@ import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.ClassRemapper; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.commons.SimpleRemapper; +import org.quiltmc.loader.api.FasterFileSystem; import org.quiltmc.loader.impl.QuiltLoaderImpl; import org.quiltmc.loader.impl.launch.common.QuiltLauncherBase; -import org.quiltmc.loader.impl.patch.PatchLoader; import org.quiltmc.loader.impl.util.FileUtil; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; @@ -34,110 +44,285 @@ import org.quiltmc.loader.impl.util.log.LogCategory; @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) -public class ReflectionsClassPatcher extends PatchLoader { +public class ReflectionsClassPatcher { - static final String INPUT_CLASS = "/org/quiltmc/loader/impl/patch/reflections/"; - static final String TARGET_PACKAGE = "org.quiltmc.loader.impl.patch.reflections."; + private static final String REF_PATCH_UTIL = Type.getInternalName(ReflectionsPatchUtils.class); + + private static final String INPUT_CLASS = "/org/quiltmc/loader/impl/patch/reflections/"; + + /** Quilt loader impl package to place the newly generated class in. This uses the dot separator '.' */ + private final String patchPackage; + + /** Quilt loader impl package to place the newly generated class in. This uses the slash separator '/' */ + private final String patchInternalPackage; + + /** Target package to call into. This uses the package separator '/', and starts with text and ends with a slash, + * like "org/reflections/place/" */ + private final String targetPackage; + + private final Remapper mappings; public static void load(Map patchedClasses) { - if (QuiltLauncherBase.getLauncher().getResourceURL("/org/reflections/vfs/Vfs.class") == null) { - return; + + List targets = new ArrayList<>(); + + targets.add(new ReflectionsClassPatcher("", "org/reflections/")); + targets.add(new ReflectionsClassPatcher("kubejsoffline.", "pie/ilikepiefoo/kubejsoffline/reflections/")); + + // TODO: Add system property based targets! + + for (ReflectionsClassPatcher target : targets) { + target.patch(patchedClasses); } + } - String urlType = TARGET_PACKAGE + "ReflectionsPathUrlType"; - try { - patchedClasses.put(urlType, patchUrlType()); - patchedClasses.put(TARGET_PACKAGE + "ReflectionsPathDir", patchDir()); - patchedClasses.put(TARGET_PACKAGE + "ReflectionsPathFile", patchFile()); - } catch (IOException e) { - throw new Error("Failed to patch a reflections class!", e); + /** @param patchPackage Sub-package to place this into. This will be prefixed with + * "org.quiltmc.loader.impl.patch.PATCHED.reflections." since it must go in there. */ + private ReflectionsClassPatcher(String patchPackage, String targetPackage) { + this.patchPackage = "org.quiltmc.loader.impl.patch.PATCHED.reflections." + patchPackage; + this.patchInternalPackage = this.patchPackage.replace('.', '/'); + this.targetPackage = targetPackage; + + if (targetPackage.startsWith("/")) { + throw new IllegalArgumentException(targetPackage + " starts with a '/'"); + } + + if (!targetPackage.endsWith("/")) { + throw new IllegalArgumentException(targetPackage + " doesn't end with a '/'"); + } + + Map map = new HashMap<>(); + String originalPackage = INPUT_CLASS.substring(1); + String newPackage = this.patchPackage.replace('.', '/'); + map.put(originalPackage + "ReflectionsPathUrlType", newPackage + "ReflectionsPathUrlType"); + map.put(originalPackage + "ReflectionsPathDir", newPackage + "ReflectionsPathDir"); + map.put(originalPackage + "ReflectionsPathFile", newPackage + "ReflectionsPathFile"); + this.mappings = new SimpleRemapper(map); + } + + private void patch(Map patchedClasses) { + + if (QuiltLauncherBase.getLauncher().getResourceURL("/" + targetPackage + "vfs/Vfs.class") == null) { + return; } + // TODO: Just generate the classes instead of using a remapper, since that way we can guarentee + // that they don't cause a VM crash + + String urlType = patchPackage + "ReflectionsPathUrlType"; + String dir = patchPackage + "ReflectionsPathDir"; + String file = patchPackage + "ReflectionsPathFile"; + genUrlType(urlType, patchedClasses); + genDir(dir, patchedClasses); + genFile(file, patchedClasses); + try { - Class.forName(urlType, true, QuiltLauncherBase.getLauncher().getTargetClassLoader()); - } catch (ClassNotFoundException e) { + ClassLoader targetClassLoader = QuiltLauncherBase.getLauncher().getTargetClassLoader(); + Class urlTypeCls = Class.forName(urlType, true, targetClassLoader); + Class.forName(dir, true, targetClassLoader); + Class.forName(file, true, targetClassLoader); + Class vfs = Class.forName(targetPackage.replace('/', '.') + "vfs.Vfs", false, targetClassLoader); + List list = (List) vfs.getMethod("getDefaultUrlTypes").invoke(null); + list.add(urlTypeCls.newInstance()); + } catch (ReflectiveOperationException e) { throw new Error(e); } - Log.info(LogCategory.GENERAL, "Successfully patched reflections to be able to handle quilt file systems."); + Log.info( + LogCategory.GENERAL, "Successfully patched " + targetPackage + " to be able to handle quilt file systems." + ); } - private static byte[] patchUrlType() throws IOException { - byte[] input = FileUtil.readAllBytes( - ReflectionsClassPatcher.class.getResourceAsStream(INPUT_CLASS + "ReflectionsPathUrlType.class") - ); - ClassReader reader = new ClassReader(input); - ClassWriter writer = new ClassWriter(reader, 0); - reader.accept(new ClassVisitor(QuiltLoaderImpl.ASM_VERSION, writer) { - String owner; - - @Override - public void visit(int version, int access, String name, String signature, String superName, - String[] interfaces) { - super.visit( - version, Opcodes.ACC_PUBLIC | access, owner = name, signature, superName, new String[] { - "org/reflections/vfs/Vfs$UrlType" } - ); - } - - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, - String[] exceptions) { - if (name.equals("createDir")) { - String dirType = "org/reflections/vfs/Vfs$Dir"; - MethodVisitor delegate = super.visitMethod( - access, name, "(Ljava/net/URL;)L" + dirType + ";", null, exceptions - ); - delegate.visitMaxs(2, 2); - delegate.visitCode(); - delegate.visitVarInsn(Opcodes.ALOAD, 0); - delegate.visitVarInsn(Opcodes.ALOAD, 1); - delegate.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, descriptor, false); - delegate.visitTypeInsn(Opcodes.CHECKCAST, dirType); - delegate.visitInsn(Opcodes.ARETURN); - delegate.visitEnd(); - } - return super.visitMethod(access, name, descriptor, signature, exceptions); - } - }, 0); - return writer.toByteArray(); + private void genUrlType(String newName, Map dst) { + ClassWriter writer = new ClassWriter(0); + String[] itfs = { targetPackage + "vfs/Vfs$UrlType" }; + String[] exception = { "java/lang/Exception" }; + String intName = newName.replace('.', '/'); + writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, intName, null, Type.getInternalName(Object.class), itfs); + { + MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitMaxs(1, 1); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(Opcodes.RETURN); + mv.visitEnd(); + } + { + MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "matches", "(Ljava/net/URL;)Z", null, exception); + mv.visitCode(); + mv.visitMaxs(3, 2); + /* url.toURI : (URL)URI Paths.get(URI) : (URI)Path path.getFileSystem() : (Path)FileSystem instanceof + * FasterFileSystem */ + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/URL", "toURI", "()Ljava/net/URI;", false); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, "java/nio/file/Paths", "get", "(Ljava/net/URI;)Ljava/nio/file/Path;", false + ); + mv.visitMethodInsn( + Opcodes.INVOKEINTERFACE, "java/nio/file/Path", "getFileSystem", "()Ljava/nio/file/FileSystem;", true + ); + mv.visitTypeInsn(Opcodes.INSTANCEOF, Type.getInternalName(FasterFileSystem.class)); + mv.visitInsn(Opcodes.IRETURN); + mv.visitEnd(); + } + { + String desc = "(Ljava/net/URL;)L" + targetPackage + "vfs/Vfs$Dir;"; + MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "createDir", desc, null, exception); + mv.visitCode(); + String dir = patchInternalPackage + "ReflectionsPathDir"; + mv.visitTypeInsn(Opcodes.NEW, dir); + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/net/URL", "toURI", "()Ljava/net/URI;", false); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, "java/nio/file/Paths", "get", "(Ljava/net/URI;)Ljava/nio/file/Path;", false + ); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, dir, "", "(Ljava/nio/file/Path;)V", false); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(3, 2); + mv.visitEnd(); + } + writer.visitEnd(); + dst.put(newName, writer.toByteArray()); } - private static byte[] patchDir() throws IOException { - byte[] input = FileUtil.readAllBytes( - ReflectionsClassPatcher.class.getResourceAsStream(INPUT_CLASS + "ReflectionsPathDir.class") - ); - ClassReader reader = new ClassReader(input); - ClassWriter writer = new ClassWriter(reader, 0); - reader.accept(new ClassVisitor(QuiltLoaderImpl.ASM_VERSION, writer) { - @Override - public void visit(int version, int access, String name, String signature, String superName, - String[] interfaces) { - super.visit( - version, Opcodes.ACC_PUBLIC | access, name, signature, superName, new String[] { - "org/reflections/vfs/Vfs$Dir" } - ); - } - }, 0); - return writer.toByteArray(); + private void genDir(String newName, Map dst) { + ClassWriter writer = new ClassWriter(0); + String dirInterface = Type.getInternalName(ReflectionsDir.class); + String[] itfs = { targetPackage + "vfs/Vfs$Dir", dirInterface }; + String[] exception = { "java/lang/Exception" }; + String pathDesc = Type.getDescriptor(Path.class); + String intName = newName.replace('.', '/'); + writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, intName, null, Type.getInternalName(Object.class), itfs); + writer.visitField(Opcodes.ACC_FINAL, "path", pathDesc, null, null); + { + MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "", "(Ljava/nio/file/Path;)V", null, null); + mv.visitCode(); + mv.visitMaxs(2, 2); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitFieldInsn(Opcodes.PUTFIELD, intName, "path", pathDesc); + mv.visitInsn(Opcodes.RETURN); + mv.visitEnd(); + } + { + MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "getPath", "()Ljava/lang/String;", null, null); + mv.visitCode(); + mv.visitMaxs(1, 1); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, intName, "path", pathDesc); + mv.visitMethodInsn( + Opcodes.INVOKESTATIC, REF_PATCH_UTIL, "dir_getPath", "(Ljava/nio/file/Path;)Ljava/lang/String;", false + ); + mv.visitInsn(Opcodes.ARETURN); + mv.visitEnd(); + } + { + // public Iterable getFiles() { + // return ReflectionsPatchUtils.dir_getFiles(this, this.path); + // } + MethodVisitor mv = writer.visitMethod( + Opcodes.ACC_PUBLIC, "getFiles", "()Ljava/lang/Iterable;", null, exception + ); + mv.visitCode(); + String dir = patchInternalPackage + "ReflectionsPathDir"; + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitTypeInsn(Opcodes.CHECKCAST, dirInterface); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, intName, "path", pathDesc); + String desc = "(L" + dirInterface + ";Ljava/nio/file/Path;)Ljava/lang/Iterable;"; + mv.visitMethodInsn(Opcodes.INVOKESTATIC, REF_PATCH_UTIL, "dir_getFiles", desc, false); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(2, 2); + mv.visitEnd(); + } + { + // public Object createFile(Path p) { + // return new ReflectionsPathFile(this, p); + // } + MethodVisitor mv = writer.visitMethod( + Opcodes.ACC_PUBLIC, "createFile", "(Ljava/nio/file/Path;)Ljava/lang/Object;", null, null + ); + mv.visitCode(); + String file = patchInternalPackage + "ReflectionsPathFile"; + mv.visitTypeInsn(Opcodes.NEW, file); + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 1); + String ctorDesc = "(L" + patchInternalPackage + "ReflectionsPathDir;Ljava/nio/file/Path;)V"; + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, file, "", ctorDesc, false); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(4, 2); + mv.visitEnd(); + } + writer.visitEnd(); + dst.put(newName, writer.toByteArray()); } - private static byte[] patchFile() throws IOException { - byte[] input = FileUtil.readAllBytes( - ReflectionsClassPatcher.class.getResourceAsStream(INPUT_CLASS + "ReflectionsPathFile.class") - ); - ClassReader reader = new ClassReader(input); - ClassWriter writer = new ClassWriter(reader, 0); - reader.accept(new ClassVisitor(QuiltLoaderImpl.ASM_VERSION, writer) { - @Override - public void visit(int version, int access, String name, String signature, String superName, - String[] interfaces) { - super.visit( - version, Opcodes.ACC_PUBLIC | access, name, signature, superName, new String[] { - "org/reflections/vfs/Vfs$File" } - ); - } - }, 0); - return writer.toByteArray(); + private void genFile(String newName, Map dst) { + ClassWriter writer = new ClassWriter(0); + String[] itfs = { targetPackage + "vfs/Vfs$File" }; + String[] exception = { "java/lang/Exception" }; + String dir = "L"+patchInternalPackage + "ReflectionsPathDir;"; + String intName = newName.replace('.', '/'); + writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, intName, null, Type.getInternalName(Object.class), itfs); + writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "dir", dir, null, null); + writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, "path", Type.getDescriptor(Path.class), null, null); + { + String ctorDesc = "(" + dir + "Ljava/nio/file/Path;)V"; + MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "", ctorDesc, null, null); + mv.visitCode(); + mv.visitMaxs(2, 3); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitFieldInsn(Opcodes.PUTFIELD, intName, "dir", dir); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitFieldInsn(Opcodes.PUTFIELD, intName, "path", "Ljava/nio/file/Path;"); + mv.visitInsn(Opcodes.RETURN); + mv.visitEnd(); + } + { + MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "getName", "()Ljava/lang/String;", null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, intName, "path", "Ljava/nio/file/Path;"); + mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/nio/file/Path", "getFileName", "()Ljava/nio/file/Path;", true); + mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/nio/file/Path", "toString", "()Ljava/lang/String;", true); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(2, 1); + mv.visitEnd(); + } + { + MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "getRelativePath", "()Ljava/lang/String;", null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, intName, "dir", dir); + mv.visitFieldInsn(Opcodes.GETFIELD, dir.substring(1, dir.length() - 1), "path", "Ljava/nio/file/Path;"); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, intName, "path", "Ljava/nio/file/Path;"); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, REF_PATCH_UTIL, "file_getRelativePath", "(Ljava/nio/file/Path;Ljava/nio/file/Path;)Ljava/lang/String;", false); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(3, 1); + mv.visitEnd(); + } + { + String[] io = { Type.getInternalName(IOException.class) }; + MethodVisitor mv = writer.visitMethod(Opcodes.ACC_PUBLIC, "openInputStream", "()Ljava/io/InputStream;", null, io); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, intName, "path", "Ljava/nio/file/Path;"); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, REF_PATCH_UTIL, "file_openInputStream", "(Ljava/nio/file/Path;)Ljava/io/InputStream;", false); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(2, 1); + mv.visitEnd(); + } + writer.visitEnd(); + dst.put(newName, writer.toByteArray()); } } diff --git a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsDir.java b/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsDir.java new file mode 100644 index 000000000..05d6689b5 --- /dev/null +++ b/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsDir.java @@ -0,0 +1,27 @@ +/* + * Copyright 2024 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.loader.impl.patch.reflections; + +import java.nio.file.Path; + +import org.quiltmc.loader.impl.util.QuiltLoaderInternal; +import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; + +@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) +public interface ReflectionsDir { + Object createFile(Path in); +} diff --git a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPatchUtils.java b/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPatchUtils.java new file mode 100644 index 000000000..74df31918 --- /dev/null +++ b/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPatchUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright 2024 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.loader.impl.patch.reflections; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; + +import org.quiltmc.loader.api.FasterFiles; +import org.quiltmc.loader.impl.util.QuiltLoaderInternal; +import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; + +/** Used for various method implementations of {@link ReflectionsClassPatcher} */ +@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) +public class ReflectionsPatchUtils { + + // TODO: Use these! + + public static String dir_getPath(Path path) { + return path.toString().replace(path.getFileSystem().getSeparator(), "/"); + } + + public static Iterable dir_getFiles(ReflectionsDir dir, Path in) { + if (!FasterFiles.isDirectory(in)) { + return Collections.emptyList(); + } + return () -> { + try { + return Files.walk(in).filter(Files::isRegularFile).map(dir::createFile).iterator(); + } catch (IOException e) { + throw new RuntimeException("Could not get files for " + in, e); + } + }; + } + + public static String file_getRelativePath(Path root, Path path) { + return root.relativize(path).toString().replace(path.getFileSystem().getSeparator(), "/"); + } + + public static InputStream file_openInputStream(Path path) throws IOException { + return Files.newInputStream(path); + } +} diff --git a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPathDir.java b/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPathDir.java deleted file mode 100644 index 8dbf1d0df..000000000 --- a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPathDir.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2023 QuiltMC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.quiltmc.loader.impl.patch.reflections; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collections; - -import org.quiltmc.loader.api.FasterFiles; -import org.quiltmc.loader.impl.util.QuiltLoaderInternal; -import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; - -/** Patch class that is transformed by {@link ReflectionsClassPatcher} to implement "org.reflections.vfs.Vfs.Dir"*/ -@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) -class ReflectionsPathDir { - final Path path; - - public ReflectionsPathDir(Path path) { - this.path = path; - } - - public String getPath() { - return path.toString().replace(path.getFileSystem().getSeparator(), "/"); - } - - public Iterable getFiles() { - if (!FasterFiles.isDirectory(path)) { - return Collections.emptyList(); - } - return () -> { - try { - return Files.walk(path) - .filter(Files::isRegularFile) - .map(p -> (Object) new ReflectionsPathFile(ReflectionsPathDir.this, p)) - .iterator(); - } catch (IOException e) { - throw new RuntimeException("could not get files for " + path, e); - } - }; - } -} diff --git a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPathFile.java b/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPathFile.java deleted file mode 100644 index e5f2f70da..000000000 --- a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPathFile.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023 QuiltMC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.quiltmc.loader.impl.patch.reflections; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; - -import org.quiltmc.loader.impl.util.QuiltLoaderInternal; -import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; - -/** Patch class that is transformed by {@link ReflectionsClassPatcher} to implement "org.reflections.vfs.Vfs.File" */ -@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) -class ReflectionsPathFile { - - private final ReflectionsPathDir root; - private final Path path; - - public ReflectionsPathFile(ReflectionsPathDir root, Path path) { - this.root = root; - this.path = path; - } - - public String getName() { - return path.getFileName().toString(); - } - - public String getRelativePath() { - return root.path.relativize(path).toString().replace(path.getFileSystem().getSeparator(), "/"); - } - - public InputStream openInputStream() throws IOException { - return Files.newInputStream(path); - } - - @Override - public String toString() { - return path.toString(); - } -} diff --git a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPathUrlType.java b/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPathUrlType.java deleted file mode 100644 index ad957d1b7..000000000 --- a/src/main/java/org/quiltmc/loader/impl/patch/reflections/ReflectionsPathUrlType.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023 QuiltMC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.quiltmc.loader.impl.patch.reflections; - -import java.lang.reflect.Field; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; - -import org.quiltmc.loader.api.FasterFileSystem; -import org.quiltmc.loader.impl.util.QuiltLoaderInternal; -import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; - -/** Patch class that is transformed by {@link ReflectionsClassPatcher} to implement "org.reflections.vfs.Vfs.UrlType" */ -@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) -class ReflectionsPathUrlType { - - static { - try { - Class cls = Class.forName("org.reflections.vfs.Vfs"); - Field field = cls.getDeclaredField("defaultUrlTypes"); - field.setAccessible(true); - List list = (List) field.get(null); - list.add(new ReflectionsPathUrlType()); - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Failed to inject into reflections!", e); - } - } - - public boolean matches(URL url) throws Exception { - // Basically only handle quilt file systems - Path path = Paths.get(url.toURI()); - return path.getFileSystem() instanceof FasterFileSystem; - } - - public ReflectionsPathDir createDir(URL url) throws Exception { - return new ReflectionsPathDir(Paths.get(url.toURI())); - } -} diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginManagerImpl.java b/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginManagerImpl.java index 7184ea645..512c940ae 100644 --- a/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginManagerImpl.java +++ b/src/main/java/org/quiltmc/loader/impl/plugin/QuiltPluginManagerImpl.java @@ -736,6 +736,12 @@ public List getErrors() { return Collections.unmodifiableList(errors); } + public Map getPluginPackages() { + Map map = new HashMap<>(); + map.putAll(pluginsByPackage); + return map; + } + Class findClass(String name, String pkg) throws ClassNotFoundException { if (pkg == null) { return null; @@ -871,6 +877,11 @@ private void populateModsGuiTab(ModSolveResultImpl result) { } } + if (SystemProperties.getBoolean(SystemProperties.IGNORE_UNSUPPORTED_MODS, false)) { + /* skip unsupported mod checking */ + return; + } + Map> filesList = new HashMap<>(); for (PathLoadState loadState : modPaths.values()) { diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/SolverErrorHelper.java b/src/main/java/org/quiltmc/loader/impl/plugin/SolverErrorHelper.java index bed4984b8..2b9919036 100644 --- a/src/main/java/org/quiltmc/loader/impl/plugin/SolverErrorHelper.java +++ b/src/main/java/org/quiltmc/loader/impl/plugin/SolverErrorHelper.java @@ -16,7 +16,6 @@ package org.quiltmc.loader.impl.plugin; -import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; @@ -34,8 +33,6 @@ import java.util.Optional; import java.util.Set; -import javax.imageio.ImageIO; - import org.quiltmc.loader.api.ModDependencyIdentifier; import org.quiltmc.loader.api.ModMetadata.ProvidedMod; import org.quiltmc.loader.api.VersionRange; @@ -46,7 +43,6 @@ import org.quiltmc.loader.api.plugin.solver.LoadOption; import org.quiltmc.loader.api.plugin.solver.ModLoadOption; import org.quiltmc.loader.api.plugin.solver.Rule; -import org.quiltmc.loader.api.plugin.solver.RuleContext; import org.quiltmc.loader.impl.plugin.quilt.DisabledModIdDefinition; import org.quiltmc.loader.impl.plugin.quilt.MandatoryModIdDefinition; import org.quiltmc.loader.impl.plugin.quilt.OptionalModIdDefintion; diff --git a/src/main/java/org/quiltmc/loader/impl/report/QuiltReport.java b/src/main/java/org/quiltmc/loader/impl/report/QuiltReport.java index 91f7b47c7..7e85d1cb1 100644 --- a/src/main/java/org/quiltmc/loader/impl/report/QuiltReport.java +++ b/src/main/java/org/quiltmc/loader/impl/report/QuiltReport.java @@ -25,6 +25,7 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collections; @@ -85,8 +86,8 @@ public void write(PrintWriter to) { private void writeInternal(PrintWriter to, boolean toLog) { to.println("---- " + header + " ----"); - LocalDateTime now = LocalDateTime.now(); - to.println("Date/Time: " + now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd kk:mm:ss.SSSS"))); + ZonedDateTime now = ZonedDateTime.now(); + to.println("Date/Time: " + now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSSS '['Z']'"))); for (String line : overview) { to.println(line); @@ -114,12 +115,15 @@ private void writeInternal(PrintWriter to, boolean toLog) { } public void writeToLog() { - PrintWriter writer = new PrintWriter(System.out); + StringWriter sw = new StringWriter(); + sw.append("Partial Report:\n"); + PrintWriter writer = new PrintWriter(sw); try { writeInternal(writer, true); } finally { writer.flush(); } + Log.error(LogCategory.GENERAL, sw.toString()); } /** Makes a best-effort attempt to write the crash-report somewhere (either to a new crash-report file or System @@ -136,7 +140,7 @@ public Path writeInDirectory(Path gameDirectory) throws CrashReportSaveFailed { try { StringBuilder sb = new StringBuilder("crash-"); - sb.append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_kk.mm.ss.SSSS"))); + sb.append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH.mm.ss.SSSS"))); sb.append("-quilt_loader.txt"); Path crashReportFile = crashReportDir.resolve(sb.toString()); write(crashReportFile); diff --git a/src/main/java/org/quiltmc/loader/impl/util/SystemProperties.java b/src/main/java/org/quiltmc/loader/impl/util/SystemProperties.java index 534fea073..37b0d9340 100644 --- a/src/main/java/org/quiltmc/loader/impl/util/SystemProperties.java +++ b/src/main/java/org/quiltmc/loader/impl/util/SystemProperties.java @@ -99,11 +99,19 @@ private SystemProperties() {} public static final String ALWAYS_DEFER_FILESYSTEM_OPERATIONS = "loader.workaround.defer_all_filesystem_operations"; public static final String DISABLE_QUILT_CLASS_PATH_CUSTOM_TABLE = "loader.quilt_class_path.disable_custom_table"; public static final String DISABLE_BUILTIN_MIXIN_EXTRAS = "loader.disable_builtin_mixin_extras"; + /** whether the loader should display unsupported mods with the GUI or ignore and continue starting up the game. */ + public static final String IGNORE_UNSUPPORTED_MODS = "loader.ignore_unsupported_mods"; /** Disables loader from registering its {@link URLStreamHandlerFactory} with * {@link URL#setURLStreamHandlerFactory(URLStreamHandlerFactory)}. This */ public static final String DISABLE_URL_STREAM_FACTORY = "loader.disable_url_stream_factory"; + /** Enables printing all gui packets that are sent and received. */ + public static final String DEBUG_GUI_PACKETS = "loader.debug.gui_packets"; + + /** Enables saving of all patched classes for debug purposes. */ + public static final String DEBUG_DUMP_PATCHED_CLASSES = "loader.debug.dump_patched_classes"; + // ############## // # Validation # // ############## diff --git a/src/main/java/org/quiltmc/loader/impl/util/UrlUtil.java b/src/main/java/org/quiltmc/loader/impl/util/UrlUtil.java index b932d05bb..ff05e01d7 100644 --- a/src/main/java/org/quiltmc/loader/impl/util/UrlUtil.java +++ b/src/main/java/org/quiltmc/loader/impl/util/UrlUtil.java @@ -77,7 +77,7 @@ public static URL asUrl(File file) throws MalformedURLException { } public static URL asUrl(Path path) throws MalformedURLException { - return path.toUri().toURL(); + return LoaderUtil.normalizePath(path).toUri().toURL(); } public static Path getCodeSource(URL url, String localPath) throws UrlConversionException { diff --git a/src/main/resources/changelog/0.26.0.txt b/src/main/resources/changelog/0.26.0.txt index 51443568d..6fb203599 100644 --- a/src/main/resources/changelog/0.26.0.txt +++ b/src/main/resources/changelog/0.26.0.txt @@ -5,6 +5,13 @@ Features: - This doesn't affect minecraft development environments, and is instead useful when depending on loader without using loom. - [#423] Updated Quilt Config to 1.3.1 - Add a system property ("loader.disable_url_stream_factory") to disable DelegatingUrlStreamHandlerFactory. +- [#428] Allow the GameProvider to provide the runtime mapping namespace +- [#434] Added a system property to disable unsupported mod warnings + - Add `-Dloader.ignore_unsupported_mods=true` to your JVM arguments to disable them. +- Tweaks to generated reports (usually crash reports): + - Include the zone offset (raw time number like +0100) in the actual crash report file. + This should help when assisting people in other timezones - as we can figure out if a crash report was generated just now, or is an older one. + - Change from CLOCK_HOUR_OF_DAY to HOUR_OF_DAY. This means we'll get digital time at 15 minutes past midnight (00:15) rather than analog time (24:15) Bug Fixes: @@ -12,9 +19,10 @@ Bug Fixes: - [#429] Fix global cache being in the wrong location - [#426] Preserve order of contributors in the quilt.mod.json - [#427] Fix an edge case where the same person listed as author and contributor to a Fabric mod was listed twice in the Quilt contributors. +- [#430] Fix the Knot classloader's allowlist + - Resolves an issue where Mixins were unable to apply on Minecraft versions between 1.7 and 1.8.9 - -Changes from updating Fabric Loader from 0.15.7 to 0.15.10: +Changes from updating Fabric Loader from 0.15.7 to 0.15.11: - Updated Mixin to 0.13.3 -- Added version parsing support for 24w14potato +- Added version parsing support for 24w14potato and 1.21 diff --git a/src/main/resources/changelog/0.26.1.txt b/src/main/resources/changelog/0.26.1.txt new file mode 100644 index 000000000..d64a6865d --- /dev/null +++ b/src/main/resources/changelog/0.26.1.txt @@ -0,0 +1,4 @@ +Bug Fixes: + +- Fixed crash reports being printed to system out, rather than the log. (This is a problem for launchers which don't replay sysout, like Modrinth) +- Fixed the gui system closing the whole game if the forked gui process encountered an error, rather than returning the error to the original process. diff --git a/src/main/resources/changelog/0.26.2.txt b/src/main/resources/changelog/0.26.2.txt new file mode 100644 index 000000000..900ba167a --- /dev/null +++ b/src/main/resources/changelog/0.26.2.txt @@ -0,0 +1,4 @@ +Changes from updating Fabric Loader from 0.15.11 to 0.16.0: + +- Updated Mixin to 0.15.0 +- Updated MixinExtras to 0.4.0 \ No newline at end of file diff --git a/src/main/resources/changelog/0.26.3.txt b/src/main/resources/changelog/0.26.3.txt new file mode 100644 index 000000000..53a9ee0e0 --- /dev/null +++ b/src/main/resources/changelog/0.26.3.txt @@ -0,0 +1 @@ +Reverted all changes from 0.26.2 \ No newline at end of file diff --git a/src/main/resources/changelog/0.26.4.txt b/src/main/resources/changelog/0.26.4.txt new file mode 100644 index 000000000..f1af64ab5 --- /dev/null +++ b/src/main/resources/changelog/0.26.4.txt @@ -0,0 +1,3 @@ +Bug fixes: + +- Fixed an oversight where Loader can try to open a GUI in a headless environment \ No newline at end of file diff --git a/src/main/resources/quilt.mod.json b/src/main/resources/quilt.mod.json index 4997f6f31..f3f3b9e3b 100644 --- a/src/main/resources/quilt.mod.json +++ b/src/main/resources/quilt.mod.json @@ -22,7 +22,7 @@ "provides": [ { "id": "fabricloader", - "version": "0.15.10" + "version": "0.15.11" } ], "depends": [