diff --git a/build.gradle b/build.gradle index ce9fdac3b..d12981197 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ plugins { id 'eclipse' id 'maven-publish' id 'org.quiltmc.gradle.licenser' version '1.1.+' - id 'org.quiltmc.loom' version '1.1.+' apply false + id 'org.quiltmc.loom' version '1.3.+' apply false id 'com.github.johnrengelman.shadow' version '8.1.1' } diff --git a/src/main/java/org/quiltmc/loader/api/plugin/solver/ModLoadOption.java b/src/main/java/org/quiltmc/loader/api/plugin/solver/ModLoadOption.java index 4100b21ce..430553582 100644 --- a/src/main/java/org/quiltmc/loader/api/plugin/solver/ModLoadOption.java +++ b/src/main/java/org/quiltmc/loader/api/plugin/solver/ModLoadOption.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.nio.file.FileSystems; -import java.nio.file.Files; import java.nio.file.Path; import org.jetbrains.annotations.Nullable; @@ -68,7 +67,7 @@ public abstract class ModLoadOption extends LoadOption { @Nullable public abstract String namespaceMappingFrom(); - public abstract boolean needsChasmTransforming(); + public abstract boolean needsTransforming(); /** @return A hash of the origin files used for the mod. This is used to cache class transformations (like remapping * and chasm) between launches. This may be called off-thread. */ diff --git a/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java b/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java index 2aa10fe9b..33c512fa6 100644 --- a/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java +++ b/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java @@ -18,7 +18,6 @@ package org.quiltmc.loader.impl; import java.awt.GraphicsEnvironment; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; @@ -103,7 +102,7 @@ import org.quiltmc.loader.impl.report.QuiltReport.CrashReportSaveFailed; import org.quiltmc.loader.impl.report.QuiltReportedError; import org.quiltmc.loader.impl.solver.ModSolveResultImpl; -import org.quiltmc.loader.impl.transformer.TransformCache; +import org.quiltmc.loader.impl.transformer.TransformCacheManager; import org.quiltmc.loader.impl.transformer.TransformCacheResult; import org.quiltmc.loader.impl.util.Arguments; import org.quiltmc.loader.impl.util.AsciiTableGenerator; @@ -121,8 +120,6 @@ import net.fabricmc.loader.api.ObjectShare; -import net.fabricmc.accesswidener.AccessWidener; -import net.fabricmc.accesswidener.AccessWidenerReader; import net.fabricmc.api.EnvType; @QuiltLoaderInternal(value = QuiltLoaderInternalType.LEGACY_EXPOSED, replacements = QuiltLoader.class) @@ -155,7 +152,6 @@ public final class QuiltLoaderImpl { private final Map adapterMap = new HashMap<>(); private final EntrypointStorage entrypointStorage = new EntrypointStorage(); - private final AccessWidener accessWidener = new AccessWidener(); private final ObjectShare objectShare = new ObjectShareImpl(); @@ -392,13 +388,14 @@ private void setup() throws ModResolutionException { } Path transformCacheFolder = getCacheDir().resolve(CACHE_DIR_NAME).resolve("transform-cache-" + suffix); - TransformCacheResult cacheResult = TransformCache.populateTransformBundle(transformCacheFolder, modList, modOriginHash, result); + TransformCacheResult cacheResult = TransformCacheManager.populateTransformBundle(transformCacheFolder, modList, modOriginHash, result); QuiltZipPath transformedModBundle = cacheResult.transformCacheRoot; long zipEnd = System.nanoTime(); try { QuiltLauncherBase.getLauncher().setTransformCache(transformedModBundle.toUri().toURL()); + QuiltLauncherBase.getLauncher().setHiddenClasses(cacheResult.hiddenClasses); } catch (MalformedURLException e) { throw new RuntimeException(e); } @@ -418,7 +415,7 @@ private void setup() throws ModResolutionException { for (ModLoadOption modOption : modList) { Path resourceRoot; - if (!modOption.needsChasmTransforming() && modOption.namespaceMappingFrom() == null) { + if (!modOption.needsTransforming() && modOption.namespaceMappingFrom() == null) { resourceRoot = modOption.resourceRoot(); } else { String modid = modOption.id(); @@ -1159,26 +1156,7 @@ private void setupMods() { } } - public void loadAccessWideners() { - AccessWidenerReader accessWidenerReader = new AccessWidenerReader(accessWidener); - for (ModContainerExt mod : mods) { - for (String accessWidener : mod.metadata().accessWideners()) { - - Path path = mod.getPath(accessWidener); - - if (!FasterFiles.isRegularFile(path)) { - throw new RuntimeException("Failed to find accessWidener file from mod " + mod.metadata().id() + " '" + accessWidener + "'"); - } - - try (BufferedReader reader = Files.newBufferedReader(path)) { - accessWidenerReader.read(reader, getMappingResolver().getCurrentRuntimeNamespace()); - } catch (Exception e) { - throw new RuntimeException("Failed to read accessWidener file from mod " + mod.metadata().id(), e); - } - } - } - } public void prepareModInit(Path newRunDir, Object gameInstance) { if (!frozen) { @@ -1215,10 +1193,6 @@ public void invokePreLaunch() { } } - public AccessWidener getAccessWidener() { - return accessWidener; - } - /** * Sets the game instance. This is only used in 20w22a+ by the dedicated server and should not be called by anything else. */ diff --git a/src/main/java/org/quiltmc/loader/impl/discovery/ClasspathModCandidateFinder.java b/src/main/java/org/quiltmc/loader/impl/discovery/ClasspathModCandidateFinder.java index 2fe65c384..0fc787722 100644 --- a/src/main/java/org/quiltmc/loader/impl/discovery/ClasspathModCandidateFinder.java +++ b/src/main/java/org/quiltmc/loader/impl/discovery/ClasspathModCandidateFinder.java @@ -55,43 +55,9 @@ public interface ModAdder { public static void findCandidatesStatic(ModAdder out) { if (QuiltLauncherBase.getLauncher().isDevelopment()) { Map> pathGroups = getPathGroups(); - - // Search for URLs which point to 'fabric.mod.json' entries, to be considered as mods. try { - Enumeration fabricMods = QuiltLauncherBase.getLauncher().getTargetClassLoader().getResources("fabric.mod.json"); - Enumeration quiltMods = QuiltLauncherBase.getLauncher().getTargetClassLoader().getResources("quilt.mod.json"); - while (quiltMods.hasMoreElements()) { - URL url = quiltMods.nextElement(); - - try { - Path path = LoaderUtil.normalizeExistingPath(UrlUtil.getCodeSource(url, "quilt.mod.json")); - List paths = pathGroups.get(path); - - if (paths == null) { - out.addMod(Collections.singletonList(path)); - } else { - out.addMod(paths); - } - } catch (UrlConversionException e) { - Log.debug(LogCategory.DISCOVERY, "Error determining location for quilt.mod.json from %s", url, e); - } - } - while (fabricMods.hasMoreElements()) { - URL url = fabricMods.nextElement(); - - try { - Path path = LoaderUtil.normalizeExistingPath(UrlUtil.getCodeSource(url, "fabric.mod.json")); - List paths = pathGroups.get(path); - - if (paths == null) { - out.addMod(Collections.singletonList(path)); - } else { - out.addMod(paths); - } - } catch (UrlConversionException e) { - Log.debug(LogCategory.DISCOVERY, "Error determining location for fabric.mod.json from %s", url, e); - } - } + findCandidates("quilt.mod.json", pathGroups, out); + findCandidates("fabric.mod.json", pathGroups, out); } catch (IOException e) { throw new RuntimeException(e); } @@ -104,6 +70,37 @@ public static void findCandidatesStatic(ModAdder out) { } } + private static void findCandidates(String metadataName, Map> pathGroups, ModAdder out) throws IOException { + Enumeration quiltMods = QuiltLauncherBase.getLauncher().getTargetClassLoader().getResources(metadataName); + while (quiltMods.hasMoreElements()) { + URL url = quiltMods.nextElement(); + + try { + Path path = LoaderUtil.normalizeExistingPath(UrlUtil.getCodeSource(url, metadataName)); + List paths = pathGroups.get(path); + + if (paths == null) { + if (!url.getProtocol().equals("jar")) { + if (!pathGroups.isEmpty()) { + // path groups are enabled, warn for misconfiguration + Log.error(LogCategory.DISCOVERY,"Mod at path " + url + " lacks a class path group! Make sure the loom 'mods' block " + + "is configured correctly!"); + } + + Log.error(LogCategory.DISCOVERY, "Mod at path %s is not included in the loom 'mods' block! " + + "This will be an error in a future version of Loader!", path); + } + + out.addMod(Collections.singletonList(path)); + } else { + out.addMod(paths); + } + } catch (UrlConversionException e) { + Log.debug(LogCategory.DISCOVERY, "Error determining location for %s from %s", metadataName, url, e); + } + } + } + /** * Parse fabric.classPathGroups system property into a path group lookup map. * diff --git a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltMapFileSystem.java b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltMapFileSystem.java index b22a13e47..2b2695155 100644 --- a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltMapFileSystem.java +++ b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltMapFileSystem.java @@ -237,9 +237,7 @@ private P addEntryWithoutParents0(QuiltUnifiedEntry newEnt protected boolean removeEntry(P path, boolean throwIfMissing) throws IOException { path = path.toAbsolutePath().normalize(); - if ("/quilt_tags/quilt_tags.accesswidener".equals(path.toString())) { - System.out.println("Removing " + path); - } + QuiltUnifiedEntry current = getEntry(path); if (current == null) { if (throwIfMissing) { diff --git a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltMapFileSystemProvider.java b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltMapFileSystemProvider.java index ebf86d121..3d747e2ff 100644 --- a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltMapFileSystemProvider.java +++ b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltMapFileSystemProvider.java @@ -433,7 +433,7 @@ private void copy0(P src, P dst, boolean isMove, CopyOption[] options) throws IO } if (canExist) { - delete(dst); + deleteIfExists(dst); } else if (dstEntry != null) { throw new FileAlreadyExistsException(dst.toString()); } diff --git a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltUnifiedFileSystem.java b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltUnifiedFileSystem.java index 6d35e31ba..a18d7ee10 100644 --- a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltUnifiedFileSystem.java +++ b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltUnifiedFileSystem.java @@ -114,7 +114,7 @@ public Path copyOnWrite(Path source, Path target, CopyOption... options) throws } if (canExist) { - provider().delete(dst); + provider().deleteIfExists(dst); } else if (dstEntry != null) { throw new FileAlreadyExistsException(dst.toString()); } @@ -158,7 +158,7 @@ public Path mount(Path source, Path target, MountOption... options) throws IOExc if (canExist) { - provider().delete(dst); + provider().deleteIfExists(dst); } else if (dstEntry != null) { throw new FileAlreadyExistsException(dst.toString()); } 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 acb758f22..e3f54fa05 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 @@ -22,11 +22,12 @@ import java.io.InputStream; import java.net.URL; import java.nio.file.Path; -import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.jar.Manifest; import org.quiltmc.loader.api.ModContainer; +import org.quiltmc.loader.impl.entrypoint.GameTransformer; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; @@ -38,6 +39,7 @@ public interface QuiltLauncher { void addToClassPath(Path path, ModContainer mod, URL origin, String... allowedPrefixes); void setAllowedPrefixes(Path path, String... prefixes); void setTransformCache(URL insideTransformCache); + void setHiddenClasses(Set classes); void hideParentUrl(URL hidden); void hideParentPath(Path obf); void validateGameClassLoader(Object gameInstance); @@ -76,4 +78,6 @@ public interface QuiltLauncher { String getTargetNamespace(); List getClassPath(); + + GameTransformer getEntrypointTransformer(); } 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 7d6d6eca6..6f78ef0df 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 @@ -24,6 +24,7 @@ import org.quiltmc.loader.impl.QuiltLoaderImpl; import org.quiltmc.loader.impl.config.QuiltConfigImpl; import org.quiltmc.loader.impl.entrypoint.EntrypointUtils; +import org.quiltmc.loader.impl.entrypoint.GameTransformer; import org.quiltmc.loader.impl.game.GameProvider; import org.quiltmc.loader.impl.launch.common.QuiltLauncherBase; import org.quiltmc.loader.impl.launch.common.QuiltMixinBootstrap; @@ -52,6 +53,7 @@ import java.util.Locale; import java.util.Map; import java.util.ServiceLoader; +import java.util.Set; import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.zip.ZipEntry; @@ -147,8 +149,6 @@ public ClassLoader init(String[] args) { loader.load(); loader.freeze(); - QuiltLoaderImpl.INSTANCE.loadAccessWideners(); - MixinBootstrap.init(); QuiltMixinBootstrap.init(getEnvironmentType(), loader); QuiltLauncherBase.finishMixinBootstrapping(); @@ -270,6 +270,11 @@ public List getClassPath() { return classPath; } + @Override + public GameTransformer getEntrypointTransformer() { + return provider.getEntrypointTransformer(); + } + @Override public void addToClassPath(Path path, String... allowedPrefixes) { addToClassPath(path, null, null, allowedPrefixes); @@ -303,6 +308,11 @@ public void setTransformCache(URL insideTransformCache) { classLoader.getDelegate().setTransformCache(insideTransformCache); } + @Override + public void setHiddenClasses(Set hiddenClasses) { + classLoader.getDelegate().setHiddenClasses(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 b8cdf3e43..a656c5cdf 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 @@ -21,16 +21,12 @@ import org.objectweb.asm.ClassReader; import org.quiltmc.loader.api.ModContainer; import org.quiltmc.loader.api.QuiltLoader; -import org.quiltmc.loader.api.ModContainer.BasicSourceType; -import org.quiltmc.loader.api.minecraft.ClientOnly; -import org.quiltmc.loader.api.minecraft.DedicatedServerOnly; import org.quiltmc.loader.impl.QuiltLoaderImpl; import org.quiltmc.loader.impl.game.GameProvider; import org.quiltmc.loader.impl.launch.common.QuiltCodeSource; import org.quiltmc.loader.impl.launch.common.QuiltLauncherBase; import org.quiltmc.loader.impl.patch.PatchLoader; import org.quiltmc.loader.impl.transformer.PackageEnvironmentStrippingData; -import org.quiltmc.loader.impl.transformer.QuiltTransformer; import org.quiltmc.loader.impl.util.FileSystemUtil; import org.quiltmc.loader.impl.util.FileUtil; import org.quiltmc.loader.impl.util.ManifestUtil; @@ -43,7 +39,6 @@ import org.quiltmc.loader.impl.util.log.LogCategory; import org.spongepowered.asm.mixin.transformer.IMixinTransformer; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Constructor; @@ -56,6 +51,7 @@ import java.security.CodeSource; import java.security.cert.Certificate; import java.util.Collections; +import java.util.HashSet; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -101,6 +97,7 @@ public Optional getQuiltMod() { private IMixinTransformer mixinTransformer; private boolean transformInitialized = false; private boolean transformFinishedLoading = false; + private Set hiddenClasses = Collections.emptySet(); private String transformCacheUrl; private final Map allowedPrefixes = new ConcurrentHashMap<>(); private final Set parentSourcedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); @@ -489,29 +486,11 @@ public byte[] getPreMixinClassByteArray(URL classFileURL, String name) { return PatchLoader.getNewPatchedClass(name); } - if (!transformInitialized || !canTransformClass(name)) { - try { - return getRawClassByteArray(classFileURL, name); - } catch (IOException e) { - throw new RuntimeException("Failed to load class file for '" + name + "'!", e); - } - } - - byte[] input = provider.getEntrypointTransformer().transform(name); - - if (input == null) { - try { - input = getRawClassByteArray(classFileURL, name); - } catch (IOException e) { - throw new RuntimeException("Failed to load class file for '" + name + "'!", e); - } - } - - if (input != null) { - return QuiltTransformer.transform(isDevelopment, envType, name, input); + try { + return getRawClassByteArray(classFileURL, name); + } catch (IOException e) { + throw new RuntimeException("Failed to load class file for '" + name + "'!", e); } - - return null; } private static boolean canTransformClass(String name) { @@ -525,6 +504,10 @@ public byte[] getRawClassByteArray(String name, boolean allowFromParent) throws } public byte[] getRawClassByteArray(URL url, String name) throws IOException { + if (hiddenClasses.contains(name)) { + return null; + } + try (InputStream inputStream = (url != null ? url.openStream() : null)) { if (inputStream == null) { return null; @@ -545,6 +528,10 @@ void setTransformCache(URL insideTransformCache) { transformCacheUrl = insideTransformCache.toString(); } + void setHiddenClasses(Set hiddenClasses) { + this.hiddenClasses = hiddenClasses; + } + void hideParentUrl(URL parentPath) { parentHiddenUrls.add(parentPath.toString()); } diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/base/InternalModOptionBase.java b/src/main/java/org/quiltmc/loader/impl/plugin/base/InternalModOptionBase.java index a11f1ea56..2c3819883 100644 --- a/src/main/java/org/quiltmc/loader/impl/plugin/base/InternalModOptionBase.java +++ b/src/main/java/org/quiltmc/loader/impl/plugin/base/InternalModOptionBase.java @@ -121,7 +121,7 @@ public QuiltLoaderText describe() { } @Override - public boolean needsChasmTransforming() { + public boolean needsTransforming() { return true; } diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/quilt/BuiltinModOption.java b/src/main/java/org/quiltmc/loader/impl/plugin/quilt/BuiltinModOption.java index cefa9ce57..31061036a 100644 --- a/src/main/java/org/quiltmc/loader/impl/plugin/quilt/BuiltinModOption.java +++ b/src/main/java/org/quiltmc/loader/impl/plugin/quilt/BuiltinModOption.java @@ -59,6 +59,6 @@ public ModContainerExt convertToMod(Path transformedResourceRoot) { throw new RuntimeException(e); } } - return new BuiltinModContainer(pluginContext, metadata, from, transformedResourceRoot, needsChasmTransforming()); + return new BuiltinModContainer(pluginContext, metadata, from, transformedResourceRoot, needsTransforming()); } } diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/quilt/ProvidedModOption.java b/src/main/java/org/quiltmc/loader/impl/plugin/quilt/ProvidedModOption.java index f5fa554a8..dbfd019f2 100644 --- a/src/main/java/org/quiltmc/loader/impl/plugin/quilt/ProvidedModOption.java +++ b/src/main/java/org/quiltmc/loader/impl/plugin/quilt/ProvidedModOption.java @@ -137,7 +137,7 @@ public void populateModsTabInfo(PluginGuiTreeNode guiNode) { } @Override - public boolean needsChasmTransforming() { + public boolean needsTransforming() { // The providing mod will get transformed - not this alias. return false; } diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/quilt/SystemModOption.java b/src/main/java/org/quiltmc/loader/impl/plugin/quilt/SystemModOption.java index be03da6a2..1cc0f4a90 100644 --- a/src/main/java/org/quiltmc/loader/impl/plugin/quilt/SystemModOption.java +++ b/src/main/java/org/quiltmc/loader/impl/plugin/quilt/SystemModOption.java @@ -47,7 +47,7 @@ public byte[] computeOriginHash() throws IOException { } @Override - public boolean needsChasmTransforming() { + public boolean needsTransforming() { return false; } } diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/ChasmInvoker.java b/src/main/java/org/quiltmc/loader/impl/transformer/ChasmInvoker.java index 2a42d83a0..6db35bbb1 100644 --- a/src/main/java/org/quiltmc/loader/impl/transformer/ChasmInvoker.java +++ b/src/main/java/org/quiltmc/loader/impl/transformer/ChasmInvoker.java @@ -30,10 +30,8 @@ import java.util.List; import java.util.Map; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.ClassReader; -import org.objectweb.asm.Opcodes; import org.quiltmc.chasm.api.ChasmProcessor; import org.quiltmc.chasm.api.ClassResult; import org.quiltmc.chasm.api.Transformer; @@ -47,8 +45,6 @@ import org.quiltmc.loader.api.LoaderValue.LArray; import org.quiltmc.loader.api.LoaderValue.LType; import org.quiltmc.loader.api.plugin.solver.ModLoadOption; -import org.quiltmc.loader.api.plugin.solver.ModSolveResult; -import org.quiltmc.loader.impl.discovery.ModResolutionException; import org.quiltmc.loader.impl.util.FileUtil; import org.quiltmc.loader.impl.util.LoaderUtil; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; @@ -59,22 +55,22 @@ @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) class ChasmInvoker { - static void applyChasm(Path root, List modList, ModSolveResult result) - throws ModResolutionException { + static void applyChasm(TransformCache cache) + throws ChasmTransformException { try { - applyChasm0(root, modList, result); - } catch (Exception e) { + applyChasm0(cache); + } catch (Throwable e) { throw new ChasmTransformException("Failed to apply chasm!", e); } } - static void applyChasm0(Path root, List modList, ModSolveResult solveResult) throws IOException { - Map package2mod = new HashMap<>(); + static void applyChasm0(TransformCache cache) throws IOException { + Map package2mod = new HashMap<>(); Map inputClassCache = new HashMap<>(); // TODO: Move chasm searching to here! - for (ModLoadOption mod : modList) { - Path path2 = root.resolve(mod.id()); + for (ModLoadOption mod : cache.getMods()) { + Path path2 = cache.getRoot(mod); if (!FasterFiles.isDirectory(path2)) { continue; } @@ -82,7 +78,7 @@ static void applyChasm0(Path root, List modList, ModSolveResult s @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.getFileName().toString().endsWith(".class")) { - package2mod.put(path2.relativize(file.getParent()).toString(), mod.id()); + package2mod.put(path2.relativize(file.getParent()).toString(), mod); inputClassCache.put(path2.relativize(file).toString(), Files.readAllBytes(file)); } return FileVisitResult.CONTINUE; @@ -112,8 +108,8 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } }); - for (ModLoadOption mod : modList) { - Path modPath = root.resolve(mod.id()); + for (ModLoadOption mod : cache.getMods()) { + Path modPath = cache.getRoot(mod); if (!FasterFiles.exists(modPath)) { continue; } @@ -121,6 +117,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO // QMJ spec: "experimental_chasm_transformers" // either a string, or a list of strings // each string is a folder which will be recursively searched for chasm transformers. + // TODO: duplicated in QuiltTransformers LoaderValue value = mod.metadata().value("experimental_chasm_transformers"); final String[] paths; @@ -163,7 +160,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO if (file.getFileName().toString().endsWith(".class")) { byte[] bytes = Files.readAllBytes(file); Metadata meta = new Metadata(); - meta.put(QuiltMetadata.class, new QuiltMetadata(mod)); + meta.put(QuiltMetadata.class, new QuiltMetadata(mod, LoaderUtil.getClassNameFromTransformCache(file.toString()))); chasm.addClass(bytes, meta); } else if (file.getFileName().toString().endsWith(".chasm")) { for (Path chasmRoot : chasmRoots) { @@ -197,14 +194,13 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO String className = cr.getClassName(); final Path rootTo; if (qm != null) { - rootTo = root.resolve(qm.from.id()); + rootTo = cache.getRoot(qm.from); } else { - String mod = package2mod.get(className.substring(0, className.lastIndexOf('/'))); + ModLoadOption mod = package2mod.get(className.substring(0, className.lastIndexOf('/'))); if (mod == null) { - mod = TransformCache.TRANSFORM_CACHE_NONMOD_CLASSLOADABLE; throw new AbstractMethodError("// TODO: Support classloading from unknown mods!"); } - rootTo = root.resolve(mod); + rootTo = cache.getRoot(mod); } Path to = rootTo.resolve(LoaderUtil.getClassFileName(className)); Files.createDirectories(to.getParent()); @@ -215,12 +211,13 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO break; } case REMOVED: { - // We need to prevent resource loading from accessing this file. - - // Deleting the file from the transform cache isn't enough. - // QuiltLoaderImpl#setup() is missing functionality - // and so are the file systems? - throw new AbstractMethodError("// TODO: Support REMOVED files in the path system!"); + QuiltMetadata qm = this_value_is_actually_nullable(result.getMetadata().get(QuiltMetadata.class)); + if (qm != null) { + cache.hideClass(LoaderUtil.getClassNameFromTransformCache(qm.name)); + } else { + throw new UnsupportedOperationException("Cannot remove unknown class"); + } + break; } default: { throw new UnsupportedChasmException( @@ -241,9 +238,12 @@ private static T this_value_is_actually_nullable(T in) { static class QuiltMetadata { final ModLoadOption from; + // the dot name of this class + final String name; - public QuiltMetadata(ModLoadOption from) { + public QuiltMetadata(ModLoadOption from, String name) { this.from = from; + this.name = name; } } } diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/QuiltTransformer.java b/src/main/java/org/quiltmc/loader/impl/transformer/QuiltTransformer.java index 7b12bfce4..1cdb1d745 100644 --- a/src/main/java/org/quiltmc/loader/impl/transformer/QuiltTransformer.java +++ b/src/main/java/org/quiltmc/loader/impl/transformer/QuiltTransformer.java @@ -19,7 +19,11 @@ import java.util.Collection; import java.util.HashSet; +import net.fabricmc.accesswidener.AccessWidener; import net.fabricmc.api.EnvType; + +import org.jetbrains.annotations.Nullable; +import org.quiltmc.loader.api.plugin.solver.ModLoadOption; import org.quiltmc.loader.impl.QuiltLoaderImpl; import org.quiltmc.loader.impl.launch.common.QuiltLauncherBase; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; @@ -27,22 +31,16 @@ import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.MethodVisitor; -import org.quiltmc.loader.impl.QuiltLoaderImpl; - -import net.fabricmc.loader.launch.common.FabricLauncherBase; import net.fabricmc.accesswidener.AccessWidenerClassVisitor; -import net.fabricmc.api.EnvType; @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) -public final class QuiltTransformer { - public static byte[] transform(boolean isDevelopment, EnvType envType, String name, byte[] bytes) { - // FIXME: Could use a better way to detect this... - boolean isMinecraftClass = name.startsWith("net.minecraft.") || name.startsWith("com.mojang.blaze3d.") || name.indexOf('.') < 0; +final class QuiltTransformer { + public static byte @Nullable [] transform(boolean isDevelopment, EnvType envType, TransformCache cache, AccessWidener accessWidener, String name, ModLoadOption mod, byte[] bytes) { + boolean isMinecraftClass = mod.id().equals("minecraft"); boolean transformAccess = isMinecraftClass && QuiltLauncherBase.getLauncher().getMappingConfiguration().requiresPackageAccessHack(); boolean environmentStrip = !isMinecraftClass || isDevelopment; - boolean applyAccessWidener = isMinecraftClass && QuiltLoaderImpl.INSTANCE.getAccessWidener().getTargets().contains(name); + boolean applyAccessWidener = isMinecraftClass && accessWidener.getTargets().contains(name); if (!transformAccess && !environmentStrip && !applyAccessWidener) { return bytes; @@ -58,7 +56,8 @@ public static byte[] transform(boolean isDevelopment, EnvType envType, String na classReader.accept(stripData, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); if (stripData.stripEntireClass()) { - throw new RuntimeException("Cannot load class " + name + " in environment type " + envType); + cache.hideClass(name); + return null; } Collection stripMethods = stripData.getStripMethods(); @@ -102,7 +101,7 @@ public static byte[] transform(boolean isDevelopment, EnvType envType, String na } if (applyAccessWidener) { - visitor = AccessWidenerClassVisitor.createClassVisitor(QuiltLoaderImpl.ASM_VERSION, visitor, QuiltLoaderImpl.INSTANCE.getAccessWidener()); + visitor = AccessWidenerClassVisitor.createClassVisitor(QuiltLoaderImpl.ASM_VERSION, visitor, accessWidener); visitorCount++; } @@ -112,7 +111,7 @@ public static byte[] transform(boolean isDevelopment, EnvType envType, String na } if (visitorCount <= 0) { - return bytes; + return null; } classReader.accept(visitor, 0); diff --git a/src/main/java/org/quiltmc/loader/impl/discovery/RuntimeModRemapper.java b/src/main/java/org/quiltmc/loader/impl/transformer/RuntimeModRemapper.java similarity index 73% rename from src/main/java/org/quiltmc/loader/impl/discovery/RuntimeModRemapper.java rename to src/main/java/org/quiltmc/loader/impl/transformer/RuntimeModRemapper.java index 91b5e5f67..40ba76172 100644 --- a/src/main/java/org/quiltmc/loader/impl/discovery/RuntimeModRemapper.java +++ b/src/main/java/org/quiltmc/loader/impl/transformer/RuntimeModRemapper.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package org.quiltmc.loader.impl.discovery; +package org.quiltmc.loader.impl.transformer; import java.io.File; import java.io.IOException; @@ -32,7 +32,6 @@ import org.objectweb.asm.commons.Remapper; import org.quiltmc.loader.api.ExtendedFiles; import org.quiltmc.loader.api.FasterFiles; -import org.quiltmc.loader.api.MountOption; import org.quiltmc.loader.api.plugin.solver.ModLoadOption; import org.quiltmc.loader.impl.QuiltLoaderImpl; import org.quiltmc.loader.impl.launch.common.QuiltLauncher; @@ -50,57 +49,16 @@ import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.TinyRemapper; -@QuiltLoaderInternal(QuiltLoaderInternalType.LEGACY_EXPOSED) -public final class RuntimeModRemapper { +@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) +final class RuntimeModRemapper { static final boolean COPY_ON_WRITE = true; - public static void remap(Path cache, List modList) { - List modsToRemap = modList.stream() + public static void remap(TransformCache cache) { + List modsToRemap = cache.getMods().stream() .filter(modLoadOption -> modLoadOption.namespaceMappingFrom() != null) .collect(Collectors.toList()); - // Copy everything that's not in the modsToRemap list - for (ModLoadOption mod : modList) { - if (mod.namespaceMappingFrom() == null && mod.needsChasmTransforming() && !QuiltLoaderImpl.MOD_ID.equals(mod.id())) { - - final boolean onlyTranformableFiles = mod.couldResourcesChange(); - - Path modSrc = mod.resourceRoot(); - Path modDst = cache.resolve(mod.id()); - try { - Files.walk(modSrc).forEach(path -> { - if (!FasterFiles.isRegularFile(path)) { - // Only copy class files, since those files are the only files modified by chasm - return; - } - if (onlyTranformableFiles) { - String fileName = path.getFileName().toString(); - if (!fileName.endsWith(".class") && !fileName.endsWith(".chasm")) { - // Only copy class files, since those files are the only files modified by chasm - // (and chasm files, since they are read by chasm) - return; - } - } - Path sub = modSrc.relativize(path); - Path dst = modDst.resolve(sub.toString().replace(modSrc.getFileSystem().getSeparator(), modDst.getFileSystem().getSeparator())); - try { - FasterFiles.createDirectories(dst.getParent()); - if (COPY_ON_WRITE) { - ExtendedFiles.copyOnWrite(path, dst); - } else { - FasterFiles.copy(path, dst); - } - } catch (IOException e) { - throw new Error(e); - } - }); - } catch (IOException io) { - throw new Error(io); - } - } - } - if (modsToRemap.isEmpty()) { return; } @@ -133,11 +91,9 @@ public static void remap(Path cache, List modList) { //Done in a 2nd loop as we need to make sure all the inputs are present before remapping for (ModLoadOption mod : modsToRemap) { RemapInfo info = infoMap.get(mod); - info.outputPath = cache.resolve("/" + mod.id()); + info.outputPath = cache.getRoot(mod); OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(info.outputPath).build(); - outputConsumer.addNonClassFiles(mod.resourceRoot(), NonClassCopyMode.FIX_META_INF, remapper); - info.outputConsumerPath = outputConsumer; remapper.apply(outputConsumer, info.tag); diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/TransformCache.java b/src/main/java/org/quiltmc/loader/impl/transformer/TransformCache.java index 4c4d57e1c..39331d3a7 100644 --- a/src/main/java/org/quiltmc/loader/impl/transformer/TransformCache.java +++ b/src/main/java/org/quiltmc/loader/impl/transformer/TransformCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2022, 2023 QuiltMC + * 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. @@ -16,337 +16,195 @@ package org.quiltmc.loader.impl.transformer; -import java.io.BufferedReader; -import java.io.IOError; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; +import java.io.UncheckedIOException; +import java.nio.file.CopyOption; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; -import java.util.TreeMap; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.jetbrains.annotations.Nullable; +import org.quiltmc.loader.api.ExtendedFiles; import org.quiltmc.loader.api.FasterFiles; -import org.quiltmc.loader.api.QuiltLoader; +import org.quiltmc.loader.api.LoaderValue; import org.quiltmc.loader.api.plugin.solver.ModLoadOption; -import org.quiltmc.loader.api.plugin.solver.ModSolveResult; -import org.quiltmc.loader.impl.discovery.ModResolutionException; -import org.quiltmc.loader.impl.discovery.RuntimeModRemapper; -import org.quiltmc.loader.impl.filesystem.PartiallyWrittenIOException; -import org.quiltmc.loader.impl.filesystem.QuiltMapFileSystem; -import org.quiltmc.loader.impl.filesystem.QuiltUnifiedFileSystem; -import org.quiltmc.loader.impl.filesystem.QuiltUnifiedPath; -import org.quiltmc.loader.impl.filesystem.QuiltZipFileSystem; -import org.quiltmc.loader.impl.filesystem.QuiltZipPath; -import org.quiltmc.loader.impl.util.FilePreloadHelper; -import org.quiltmc.loader.impl.util.FileSystemUtil; -import org.quiltmc.loader.impl.util.HashUtil; +import org.quiltmc.loader.impl.QuiltLoaderImpl; +import org.quiltmc.loader.impl.launch.common.QuiltLauncherBase; +import org.quiltmc.loader.impl.util.LoaderUtil; 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; +import org.quiltmc.parsers.json.JsonReader; +/** + * A representation of the transform cache to be used by transformers when generating the cache for the first time. + */ @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) -public class TransformCache { - - static final boolean SHOW_KEY_DIFFERENCE = Boolean.getBoolean(SystemProperties.LOG_CACHE_KEY_CHANGES); - - /** Sub-folder for classes which are not associated with any mod in particular, but still need to be classloaded. */ - public static final String TRANSFORM_CACHE_NONMOD_CLASSLOADABLE = "Unknown Mod"; - - private static final String CACHE_FILE = "files.zip"; - private static final String FILE_TRANSFORM_COMPLETE = "__TRANSFORM_COMPLETE"; - - public static TransformCacheResult populateTransformBundle(Path transformCacheFolder, List modList, - Map modOriginHash, ModSolveResult result) throws ModResolutionException { - Map map = new TreeMap<>(); - // Mod order is important? For now, assume it is - int index = 0; - for (ModLoadOption mod : modList) { - map.put("mod#" + index++, mod.id()); - } - - for (Entry provided : result.providedMods().entrySet()) { - map.put("provided-mod:" + provided.getKey(), provided.getValue().metadata().id()); - } - - for (Entry mod : result.directMods().entrySet()) { - ModLoadOption modOption = mod.getValue(); - String name = modOption.from().getFileName().toString(); - map.put("mod:" + mod.getKey(), name + " " + modOriginHash.get(modOption.id())); - } - - boolean enableChasm = Boolean.getBoolean(SystemProperties.ENABLE_EXPERIMENTAL_CHASM); - map.put("system-property:" + SystemProperties.ENABLE_EXPERIMENTAL_CHASM, "" + enableChasm); - - try { - Files.createDirectories(transformCacheFolder.getParent()); - } catch (IOException e) { - throw new ModResolutionException("Failed to create parent directories of the transform cache file!", e); - } - - QuiltZipPath existing = checkTransformCache(transformCacheFolder, map); - boolean isNewlyGenerated = false; - if (existing == null) { - existing = createTransformCache(transformCacheFolder.resolve(CACHE_FILE), toString(map), modList, result); - isNewlyGenerated = true; - } else if (!Boolean.getBoolean(SystemProperties.DISABLE_PRELOAD_TRANSFORM_CACHE)) { - FilePreloadHelper.preLoad(transformCacheFolder.resolve(CACHE_FILE)); - } - return new TransformCacheResult(transformCacheFolder, isNewlyGenerated, existing); - } - - private static String toString(Map map) { - StringBuilder optionList = new StringBuilder(); - for (Entry entry : map.entrySet()) { - optionList.append(entry.getKey()); - optionList.append("="); - optionList.append(entry.getValue()); - optionList.append("\n"); - } - String options = optionList.toString(); - optionList = null; - return options; - } - - private static QuiltZipPath checkTransformCache(Path transformCacheFolder, Map options) - throws ModResolutionException { +class TransformCache { + private final Path root; + private final Map modRoots = new HashMap<>(); + private final List orderedMods; + private final Set hiddenClasses = new HashSet<>(); + private static final boolean COPY_ON_WRITE = true; + + public TransformCache(Path root, List orderedMods) { + this.root = root; + this.orderedMods = orderedMods.stream().filter(mod -> mod.needsTransforming() && !QuiltLoaderImpl.MOD_ID.equals(mod.id())).collect(Collectors.toList()); + + for (ModLoadOption mod : orderedMods) { + Path modSrc = mod.resourceRoot(); + Path modDst = root.resolve(mod.id()); + modRoots.put(mod, modDst); + + try { + // note: we could provide a folder to pass in more data (e.g. a /transformers dir with metadata) + // we could also provide meta-inf here: +// copyFile(modSrc.resolve("META-INF/MANIFEST.MF"), modSrc, modDst); + + // Copy mixin + AWs over + for (String mixin : mod.metadata().mixins(QuiltLauncherBase.getLauncher().getEnvironmentType())) { + copyFile(modSrc.resolve(mixin), modSrc, modDst); + // find the refmap and copy it too + String refmap = extractRefmap(modSrc.resolve(mixin)); + if (refmap != null) { + // multiple mixins can reference the same refmap + copyFile(modSrc.resolve(refmap), modSrc, modDst, StandardCopyOption.REPLACE_EXISTING); - Path cacheFile = transformCacheFolder.resolve(CACHE_FILE); - - if (!FasterFiles.exists(cacheFile)) { - Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it's missing"); - erasePreviousTransformCache(transformCacheFolder, cacheFile, null); - return null; - } - - if (QuiltLoader.isDevelopmentEnvironment()) { - Log.info(LogCategory.CACHE, "Not reusing previous transform cache since we're in a development environment"); - erasePreviousTransformCache(transformCacheFolder, cacheFile, null); - return null; - } - - try (QuiltZipFileSystem fs = new QuiltZipFileSystem("transform-cache", cacheFile, "")) { - QuiltZipPath inner = fs.getRoot(); - if (!FasterFiles.isRegularFile(inner.resolve(FILE_TRANSFORM_COMPLETE))) { - Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it's incomplete!"); - erasePreviousTransformCache(transformCacheFolder, cacheFile, null); - return null; - } - Path optionFile = inner.resolve("options.txt"); - - try (BufferedReader br = Files.newBufferedReader(optionFile, StandardCharsets.UTF_8)) { - String line; - Map oldOptions = new TreeMap<>(options); - Map newOptions = new TreeMap<>(); - Map differingOptions = new TreeMap<>(); - while ((line = br.readLine()) != null) { - if (line.isEmpty()) { - continue; - } - int eq = line.indexOf('='); - String key = line.substring(0, eq); - String value = line.substring(eq + 1); - String oldValue = oldOptions.remove(key); - if (oldValue != null) { - if (!value.equals(oldValue)) { - differingOptions.put(key, value); - } - } else { - newOptions.put(key, value); } } + for (String aw : mod.metadata().accessWideners()) { + copyFile(modSrc.resolve(aw), modSrc, modDst); + } - if (!oldOptions.isEmpty() || !newOptions.isEmpty() || !differingOptions.isEmpty()) { - if (SHOW_KEY_DIFFERENCE) { - Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it has different keys:"); - - for (Map.Entry old : oldOptions.entrySet()) { - Log.info(LogCategory.CACHE, " Missing: '" + old.getKey() + "': '" + old.getValue() + "'"); - } - - for (Map.Entry added : newOptions.entrySet()) { - Log.info(LogCategory.CACHE, " Included: '" + added.getKey() + "': '" + added.getValue() + "'"); - } - - for (Map.Entry diff : differingOptions.entrySet()) { - String key = diff.getKey(); - String oldValue = diff.getValue(); - String newValue = options.get(key); - Log.info( - LogCategory.CACHE, " Different: '" + key + "': '" + oldValue + "' -> '" + newValue + "'" - ); + LoaderValue value = mod.metadata().value("experimental_chasm_transformers"); + + // TODO: copied from ChasmInvoker + final String[] chasmPaths; + if (value == null) { + chasmPaths = new String[0]; + } else if (value.type() == LoaderValue.LType.STRING) { + chasmPaths = new String[]{value.asString()}; + } else if (value.type() == LoaderValue.LType.ARRAY) { + LoaderValue.LArray array = value.asArray(); + chasmPaths = new String[array.size()]; + for (int i = 0; i < array.size(); i++) { + LoaderValue entry = array.get(i); + if (entry.type() == LoaderValue.LType.STRING) { + chasmPaths[i] = entry.asString(); + } else { + Log.warn(LogCategory.CHASM, "Unknown value found for 'experimental_chasm_transformers[" + i + "]' in " + mod.id()); } - } else { - Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it has " - + (oldOptions.size() + newOptions.size() + differingOptions.size()) - + " different keys." - + " (Add '-Dloader.transform_cache.log_changed_keys=true' to see all changes)."); } - erasePreviousTransformCache(transformCacheFolder, cacheFile, null); - return null; + } else { + chasmPaths = new String[0]; + Log.warn(LogCategory.CHASM, "Unknown value found for 'experimental_chasm_transformers' in " + mod.id()); } - } - return inner; - } catch (IOException | IOError io) { - if (io instanceof PartiallyWrittenIOException) { - Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it's incomplete!"); - } else { - Log.info( - LogCategory.CACHE, - "Not reusing previous transform cache since something went wrong while reading it!" - ); - } - - erasePreviousTransformCache(transformCacheFolder, cacheFile, io); - - return null; - } - } - private static void erasePreviousTransformCache(Path transformCacheFolder, Path cacheFile, Throwable suppressed) - throws ModResolutionException { - - if (!Files.exists(transformCacheFolder)) { - return; - } + for (String chasmPath : chasmPaths) { + copyFile(modSrc.resolve(chasmPath), modSrc, modDst); + } - try { - Files.walkFileTree(transformCacheFolder, Collections.emptySet(), 1, new SimpleFileVisitor() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; + // copy classes for mods which don't need remapped + if (mod.namespaceMappingFrom() == null) { + try (Stream stream = Files.walk(modSrc)) { + stream + .filter(FasterFiles::isRegularFile) + .filter(p -> p.getFileName().toString().endsWith(".class") || p.getFileName().toString().endsWith(".chasm")) + .forEach(path -> copyFile(path, modSrc, modDst)); + } } - }); - } catch (IOException e) { - ModResolutionException ex = new ModResolutionException( - "Failed to read an older transform cache file " + cacheFile + " and then delete it!", e - ); - if (suppressed != null) { - ex.addSuppressed(suppressed); + } catch (IOException io) { + throw new UncheckedIOException(io); } - throw ex; + } + // Populate mods that need remapped + RuntimeModRemapper.remap(this); + for (ModLoadOption orderedMod : orderedMods) { + modRoots.put(orderedMod, root.resolve(orderedMod.id() + "/")); } } - static final boolean WRITE_CUSTOM = true; - - private static QuiltZipPath createTransformCache(Path transformCacheFile, String options, List< - ModLoadOption> modList, ModSolveResult result) throws ModResolutionException { + public Path getRoot(ModLoadOption mod) { + return modRoots.get(mod); + } - try { - Files.createDirectories(transformCacheFile.getParent()); - } catch (IOException e) { - throw new ModResolutionException("Failed to create the transform cache parent directory!", e); - } + public List getMods() { + return Collections.unmodifiableList(orderedMods); + } - if (!Boolean.getBoolean(SystemProperties.DISABLE_OPTIMIZED_COMPRESSED_TRANSFORM_CACHE)) { - try (QuiltUnifiedFileSystem fs = new QuiltUnifiedFileSystem("transform-cache", true)) { - QuiltUnifiedPath root = fs.getRoot(); - populateTransformCache(root, modList, result); - fs.dumpEntries("after-populate"); - Files.write(root.resolve("options.txt"), options.getBytes(StandardCharsets.UTF_8)); - Files.createFile(root.resolve(FILE_TRANSFORM_COMPLETE)); - QuiltZipFileSystem.writeQuiltCompressedFileSystem(root, transformCacheFile); + public Set getHiddenClasses() { + return Collections.unmodifiableSet(hiddenClasses); + } - return openCache(transformCacheFile); - } catch (IOException e) { - throw new ModResolutionException("Failed to create the transform bundle!", e); - } - } - - try (FileSystemUtil.FileSystemDelegate fs = FileSystemUtil.getJarFileSystem(transformCacheFile, true)) { - URI fileUri = transformCacheFile.toUri(); - URI zipUri = new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null); - - Path inner = fs.get().getPath("/"); - - populateTransformCache(inner, modList, result); - - Files.write(inner.resolve("options.txt"), options.getBytes(StandardCharsets.UTF_8)); - Files.createFile(inner.resolve(FILE_TRANSFORM_COMPLETE)); - - } catch (IOException e) { - throw new ModResolutionException("Failed to create the transform bundle!", e); - } catch (URISyntaxException e) { - throw new ModResolutionException(e); + public void forEachClassFile(ClassConsumer action) + throws IOException { + for (ModLoadOption mod : orderedMods) { + visitFolder(mod, getRoot(mod), action); } + } - return openCache(transformCacheFile); + public void hideClass(String className) { + hiddenClasses.add(className); } - private static QuiltZipPath openCache(Path transformCacheFile) throws ModResolutionException { + private static void copyFile(Path path, Path modSrc, Path modDst, CopyOption... copyOptions) { + if (!FasterFiles.exists(path)) { + return; + } + Path sub = modSrc.relativize(path); + Path dst = modDst.resolve(sub.toString().replace(modSrc.getFileSystem().getSeparator(), modDst.getFileSystem().getSeparator())); try { - QuiltZipPath path = new QuiltZipFileSystem("transform-cache", transformCacheFile, "").getRoot(); - return path; + FasterFiles.createDirectories(dst.getParent()); + if (COPY_ON_WRITE) { + ExtendedFiles.copyOnWrite(path, dst, copyOptions); + } else { + FasterFiles.copy(path, dst, copyOptions); + } } catch (IOException e) { - // TODO: Better error message for the gui! - throw new ModResolutionException("Failed to read the newly written transform cache!", e); + throw new UncheckedIOException(e); } } - private static void populateTransformCache(Path root, List modList, ModSolveResult solveResult) - throws ModResolutionException, IOException { - - RuntimeModRemapper.remap(root, modList); - - QuiltMapFileSystem.dumpEntries(root.getFileSystem(), "after-remap"); - - if (Boolean.getBoolean(SystemProperties.ENABLE_EXPERIMENTAL_CHASM)) { - ChasmInvoker.applyChasm(root, modList, solveResult); - } - - InternalsHiderTransform internalsHider = new InternalsHiderTransform(InternalsHiderTransform.Target.MOD); - Map classes = new HashMap<>(); - - // the double read is necessary to avoid storing all classes in memory at once, and thus having memory complexity - // proportional to mod count - - forEachClassFile(root, modList, (mod, file) -> { - byte[] classBytes = Files.readAllBytes(file); - classes.put(file, mod); - internalsHider.scanClass(mod, file, classBytes); - return null; - }); - - for (Map.Entry entry : classes.entrySet()) { - byte[] classBytes = Files.readAllBytes(entry.getKey()); - byte[] newBytes = internalsHider.run(entry.getValue(), classBytes); - if (newBytes != null) { - Files.write(entry.getKey(), newBytes); + @Nullable + private static String extractRefmap(Path mixin) { + try (JsonReader reader = JsonReader.json(mixin)) { + // if this crashes because the structure sometimes doesn't look like this, forward complaints to glitch + reader.beginObject(); + while (reader.hasNext()) { + if (reader.nextName().equals("refmap")) { + return reader.nextString(); + } else { + reader.skipValue(); + } } + } catch (IOException e) { + throw new UncheckedIOException(e); } - internalsHider.finish(); + return null; } - private static void forEachClassFile(Path root, List modList, ClassConsumer action) - throws IOException { - for (ModLoadOption mod : modList) { - visitFolder(mod, root.resolve(mod.id()), action); - } - visitFolder(null, root.resolve(TRANSFORM_CACHE_NONMOD_CLASSLOADABLE), action); - } - - private static void visitFolder(ModLoadOption mod, Path root, ClassConsumer action) throws IOException { + private void visitFolder(ModLoadOption mod, Path root, ClassConsumer action) throws IOException { if (!Files.isDirectory(root)) { return; } Files.walkFileTree(root, new SimpleFileVisitor() { @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) { String folderName = Objects.toString(dir.getFileName()); if (folderName != null && !couldBeJavaElement(folderName, false)) { return FileVisitResult.SKIP_SUBTREE; @@ -358,9 +216,12 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { String fileName = file.getFileName().toString(); if (fileName.endsWith(".class") && couldBeJavaElement(fileName, true)) { - byte[] result = action.run(mod, file); - if (result != null) { - Files.write(file, result); + String name = LoaderUtil.getClassNameFromTransformCache(file.toString()); + if (!hiddenClasses.contains(name)) { + byte[] result = action.run(mod, name, file); + if (result != null) { + Files.write(file, result); + } } } return FileVisitResult.CONTINUE; @@ -382,7 +243,14 @@ private boolean couldBeJavaElement(String name, boolean ignoreClassSuffix) { } @FunctionalInterface - interface ClassConsumer { - byte[] run(ModLoadOption mod, Path file) throws IOException; + public interface ClassConsumer { + /** + * Consume a class and potentially transform it. + * + * @param mod the mod which "owns" this class file + * @param className the name of the class in dot form (e.g. {@code net.minecraft.client.MinecraftClient$1} + * @return the transformed bytes, or null if nothing was changed + */ + byte @Nullable [] run(ModLoadOption mod, String className, Path file) throws IOException; } } diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheGenerator.java b/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheGenerator.java new file mode 100644 index 000000000..a2148a094 --- /dev/null +++ b/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheGenerator.java @@ -0,0 +1,124 @@ +/* + * 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.transformer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.quiltmc.loader.api.FasterFiles; +import org.quiltmc.loader.api.QuiltLoader; +import org.quiltmc.loader.api.plugin.solver.ModLoadOption; +import org.quiltmc.loader.impl.discovery.ModResolutionException; +import org.quiltmc.loader.impl.filesystem.QuiltMapFileSystem; +import org.quiltmc.loader.impl.launch.common.QuiltLauncherBase; +import org.quiltmc.loader.impl.util.QuiltLoaderInternal; +import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; +import org.quiltmc.loader.impl.util.SystemProperties; + +import net.fabricmc.accesswidener.AccessWidener; +import net.fabricmc.accesswidener.AccessWidenerReader; + +@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) +final class TransformCacheGenerator { + + + static TransformCache generate(Path root, List modList) throws ModResolutionException, IOException { + TransformCache cache = new TransformCache(root, modList); + QuiltMapFileSystem.dumpEntries(root.getFileSystem(), "after-copy"); + + // Transform time! + // Load AWs + AccessWidener accessWidener = loadAccessWideners(cache); + // game provider transformer and QuiltTransformer + cache.forEachClassFile((mod, name, file) -> { + + byte[] classBytes = QuiltLauncherBase.getLauncher().getEntrypointTransformer().transform(name); + + if (classBytes == null) { + classBytes = Files.readAllBytes(file); + } + + return QuiltTransformer.transform( + QuiltLoader.isDevelopmentEnvironment(), + QuiltLauncherBase.getLauncher().getEnvironmentType(), + cache, + accessWidener, + name, + mod, + classBytes + ); + }); + + // chasm + if (Boolean.getBoolean(SystemProperties.ENABLE_EXPERIMENTAL_CHASM)) { + ChasmInvoker.applyChasm(cache); + } + InternalsHiderTransform internalsHider = new InternalsHiderTransform(InternalsHiderTransform.Target.MOD); + Map classes = new HashMap<>(); + + // internals hider + // the double read is necessary to avoid storing all classes in memory at once, and thus having memory complexity + // proportional to mod count + cache.forEachClassFile((mod, name, file) -> { + byte[] classBytes = Files.readAllBytes(file); + classes.put(file, mod); + internalsHider.scanClass(mod, file, classBytes); + return null; + }); + + for (Map.Entry entry : classes.entrySet()) { + byte[] classBytes = Files.readAllBytes(entry.getKey()); + byte[] newBytes = internalsHider.run(entry.getValue(), classBytes); + if (newBytes != null) { + Files.write(entry.getKey(), newBytes); + } + } + + internalsHider.finish(); + + return cache; + } + + private static AccessWidener loadAccessWideners(TransformCache cache) { + AccessWidener ret = new AccessWidener(); + AccessWidenerReader accessWidenerReader = new AccessWidenerReader(ret); + + for (ModLoadOption mod : cache.getMods()) { + for (String accessWidener : mod.metadata().accessWideners()) { + + Path path = cache.getRoot(mod).resolve(accessWidener); + + if (!FasterFiles.isRegularFile(path)) { + throw new RuntimeException("Failed to find accessWidener file from mod " + mod.metadata().id() + " '" + accessWidener + "'"); + } + + try (BufferedReader reader = Files.newBufferedReader(path)) { + accessWidenerReader.read(reader, QuiltLoader.getMappingResolver().getCurrentRuntimeNamespace()); + } catch (Exception e) { + throw new RuntimeException("Failed to read accessWidener file from mod " + mod.metadata().id(), e); + } + } + } + + return ret; + } +} diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheManager.java b/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheManager.java new file mode 100644 index 000000000..4b7dc13a7 --- /dev/null +++ b/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheManager.java @@ -0,0 +1,301 @@ +/* + * Copyright 2022, 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.transformer; + +import java.io.BufferedReader; +import java.io.IOError; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +import org.quiltmc.loader.api.FasterFiles; +import org.quiltmc.loader.api.QuiltLoader; +import org.quiltmc.loader.api.plugin.solver.ModLoadOption; +import org.quiltmc.loader.api.plugin.solver.ModSolveResult; +import org.quiltmc.loader.impl.discovery.ModResolutionException; +import org.quiltmc.loader.impl.filesystem.PartiallyWrittenIOException; +import org.quiltmc.loader.impl.filesystem.QuiltUnifiedFileSystem; +import org.quiltmc.loader.impl.filesystem.QuiltUnifiedPath; +import org.quiltmc.loader.impl.filesystem.QuiltZipFileSystem; +import org.quiltmc.loader.impl.filesystem.QuiltZipPath; +import org.quiltmc.loader.impl.util.FilePreloadHelper; +import org.quiltmc.loader.impl.util.FileSystemUtil; +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 TransformCacheManager { + + static final boolean SHOW_KEY_DIFFERENCE = Boolean.getBoolean(SystemProperties.LOG_CACHE_KEY_CHANGES); + + /** Sub-folder for classes which are not associated with any mod in particular, but still need to be classloaded. */ + public static final String TRANSFORM_CACHE_NONMOD_CLASSLOADABLE = "Unknown Mod"; + + private static final String CACHE_FILE = "files.zip"; + + private static final String FILE_TRANSFORM_COMPLETE = "__TRANSFORM_COMPLETE"; + private static final String HIDDEN_CLASSES_PATH = "hidden_classes.txt"; + + public static TransformCacheResult populateTransformBundle(Path transformCacheFolder, List modList, + Map modOriginHash, ModSolveResult result) throws ModResolutionException { + Map map = new TreeMap<>(); + // Mod order is important? For now, assume it is + int index = 0; + for (ModLoadOption mod : modList) { + map.put("mod#" + index++, mod.id()); + } + + for (Entry provided : result.providedMods().entrySet()) { + map.put("provided-mod:" + provided.getKey(), provided.getValue().metadata().id()); + } + + for (Entry mod : result.directMods().entrySet()) { + ModLoadOption modOption = mod.getValue(); + String name = modOption.from().getFileName().toString(); + map.put("mod:" + mod.getKey(), name + " " + modOriginHash.get(modOption.id())); + } + + boolean enableChasm = Boolean.getBoolean(SystemProperties.ENABLE_EXPERIMENTAL_CHASM); + map.put("system-property:" + SystemProperties.ENABLE_EXPERIMENTAL_CHASM, "" + enableChasm); + + try { + Files.createDirectories(transformCacheFolder.getParent()); + } catch (IOException e) { + throw new ModResolutionException("Failed to create parent directories of the transform cache file!", e); + } + + QuiltZipPath existing = checkTransformCache(transformCacheFolder, map); + boolean isNewlyGenerated = false; + if (existing == null) { + existing = createTransformCache(transformCacheFolder.resolve(CACHE_FILE), toString(map), modList); + isNewlyGenerated = true; + } else if (!Boolean.getBoolean(SystemProperties.DISABLE_PRELOAD_TRANSFORM_CACHE)) { + FilePreloadHelper.preLoad(transformCacheFolder.resolve(CACHE_FILE)); + } + try { + return new TransformCacheResult(existing, isNewlyGenerated, new HashSet<>(Files.readAllLines(existing.resolve(HIDDEN_CLASSES_PATH)))); + } catch (IOException e) { + throw new ModResolutionException("Failed to read hidden classes in the transform cache file!", e); + } + } + + private static String toString(Map map) { + StringBuilder optionList = new StringBuilder(); + for (Entry entry : map.entrySet()) { + optionList.append(entry.getKey()); + optionList.append("="); + optionList.append(entry.getValue()); + optionList.append("\n"); + } + String options = optionList.toString(); + optionList = null; + return options; + } + + private static QuiltZipPath checkTransformCache(Path transformCacheFolder, Map options) + throws ModResolutionException { + + Path cacheFile = transformCacheFolder.resolve(CACHE_FILE); + + if (!FasterFiles.exists(cacheFile)) { + Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it's missing"); + erasePreviousTransformCache(transformCacheFolder, cacheFile, null); + return null; + } + + if (QuiltLoader.isDevelopmentEnvironment()) { + Log.info(LogCategory.CACHE, "Not reusing previous transform cache since we're in a development environment"); + erasePreviousTransformCache(transformCacheFolder, cacheFile, null); + return null; + } + + try (QuiltZipFileSystem fs = new QuiltZipFileSystem("transform-cache", cacheFile, "")) { + QuiltZipPath inner = fs.getRoot(); + if (!FasterFiles.isRegularFile(inner.resolve(FILE_TRANSFORM_COMPLETE))) { + Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it's incomplete!"); + erasePreviousTransformCache(transformCacheFolder, cacheFile, null); + return null; + } + Path optionFile = inner.resolve("options.txt"); + + try (BufferedReader br = Files.newBufferedReader(optionFile, StandardCharsets.UTF_8)) { + String line; + Map oldOptions = new TreeMap<>(options); + Map newOptions = new TreeMap<>(); + Map differingOptions = new TreeMap<>(); + while ((line = br.readLine()) != null) { + if (line.isEmpty()) { + continue; + } + int eq = line.indexOf('='); + String key = line.substring(0, eq); + String value = line.substring(eq + 1); + String oldValue = oldOptions.remove(key); + if (oldValue != null) { + if (!value.equals(oldValue)) { + differingOptions.put(key, value); + } + } else { + newOptions.put(key, value); + } + } + + if (!oldOptions.isEmpty() || !newOptions.isEmpty() || !differingOptions.isEmpty()) { + if (SHOW_KEY_DIFFERENCE) { + Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it has different keys:"); + + for (Map.Entry old : oldOptions.entrySet()) { + Log.info(LogCategory.CACHE, " Missing: '" + old.getKey() + "': '" + old.getValue() + "'"); + } + + for (Map.Entry added : newOptions.entrySet()) { + Log.info(LogCategory.CACHE, " Included: '" + added.getKey() + "': '" + added.getValue() + "'"); + } + + for (Map.Entry diff : differingOptions.entrySet()) { + String key = diff.getKey(); + String oldValue = diff.getValue(); + String newValue = options.get(key); + Log.info( + LogCategory.CACHE, " Different: '" + key + "': '" + oldValue + "' -> '" + newValue + "'" + ); + } + } else { + Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it has " + + (oldOptions.size() + newOptions.size() + differingOptions.size()) + + " different keys." + + " (Add '-Dloader.transform_cache.log_changed_keys=true' to see all changes)."); + } + erasePreviousTransformCache(transformCacheFolder, cacheFile, null); + return null; + } + } + return inner; + } catch (IOException | IOError io) { + if (io instanceof PartiallyWrittenIOException) { + Log.info(LogCategory.CACHE, "Not reusing previous transform cache since it's incomplete!"); + } else { + Log.info( + LogCategory.CACHE, + "Not reusing previous transform cache since something went wrong while reading it!" + ); + } + + erasePreviousTransformCache(transformCacheFolder, cacheFile, io); + + return null; + } + } + + private static void erasePreviousTransformCache(Path transformCacheFolder, Path cacheFile, Throwable suppressed) + throws ModResolutionException { + + if (!Files.exists(transformCacheFolder)) { + return; + } + + try { + Files.walkFileTree(transformCacheFolder, Collections.emptySet(), 1, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + ModResolutionException ex = new ModResolutionException( + "Failed to read an older transform cache file " + cacheFile + " and then delete it!", e + ); + if (suppressed != null) { + ex.addSuppressed(suppressed); + } + throw ex; + } + } + + static final boolean WRITE_CUSTOM = true; + + private static QuiltZipPath createTransformCache(Path transformCacheFile, String options, List< + ModLoadOption> modList) throws ModResolutionException { + + try { + Files.createDirectories(transformCacheFile.getParent()); + } catch (IOException e) { + throw new ModResolutionException("Failed to create the transform cache parent directory!", e); + } + + if (!Boolean.getBoolean(SystemProperties.DISABLE_OPTIMIZED_COMPRESSED_TRANSFORM_CACHE)) { + try (QuiltUnifiedFileSystem fs = new QuiltUnifiedFileSystem("transform-cache", true)) { + QuiltUnifiedPath root = fs.getRoot(); + TransformCache cache = TransformCacheGenerator.generate(root, modList); + fs.dumpEntries("after-populate"); + Files.write(root.resolve("options.txt"), options.getBytes(StandardCharsets.UTF_8)); + Files.write(root.resolve(HIDDEN_CLASSES_PATH), cache.getHiddenClasses()); + Files.createFile(root.resolve(FILE_TRANSFORM_COMPLETE)); + QuiltZipFileSystem.writeQuiltCompressedFileSystem(root, transformCacheFile); + + return openCache(transformCacheFile); + } catch (IOException e) { + throw new ModResolutionException("Failed to create the transform bundle!", e); + } + } + + try (FileSystemUtil.FileSystemDelegate fs = FileSystemUtil.getJarFileSystem(transformCacheFile, true)) { + URI fileUri = transformCacheFile.toUri(); + URI zipUri = new URI("jar:" + fileUri.getScheme(), fileUri.getPath(), null); + + Path inner = fs.get().getPath("/"); + + TransformCacheGenerator.generate(inner, modList); + + Files.write(inner.resolve("options.txt"), options.getBytes(StandardCharsets.UTF_8)); + Files.createFile(inner.resolve(FILE_TRANSFORM_COMPLETE)); + + } catch (IOException e) { + throw new ModResolutionException("Failed to create the transform bundle!", e); + } catch (URISyntaxException e) { + throw new ModResolutionException(e); + } + + return openCache(transformCacheFile); + } + + private static QuiltZipPath openCache(Path transformCacheFile) throws ModResolutionException { + try { + QuiltZipPath path = new QuiltZipFileSystem("transform-cache", transformCacheFile, "").getRoot(); + return path; + } catch (IOException e) { + // TODO: Better error message for the gui! + throw new ModResolutionException("Failed to read the newly written transform cache!", e); + } + } +} diff --git a/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheResult.java b/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheResult.java index 38ed49e82..02c02b180 100644 --- a/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheResult.java +++ b/src/main/java/org/quiltmc/loader/impl/transformer/TransformCacheResult.java @@ -16,7 +16,7 @@ package org.quiltmc.loader.impl.transformer; -import java.nio.file.Path; +import java.util.Set; import org.quiltmc.loader.impl.filesystem.QuiltZipPath; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; @@ -24,13 +24,13 @@ @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) public class TransformCacheResult { - public final Path transformCacheFolder; - public final boolean isNewlyGenerated; public final QuiltZipPath transformCacheRoot; + public final boolean isNewlyGenerated; + public final Set hiddenClasses; - TransformCacheResult(Path transformCacheFolder, boolean isNewlyGenerated, QuiltZipPath transformCacheRoot) { - this.transformCacheFolder = transformCacheFolder; + TransformCacheResult(QuiltZipPath transformCacheRoot, boolean isNewlyGenerated, Set hiddenClasses) { this.isNewlyGenerated = isNewlyGenerated; this.transformCacheRoot = transformCacheRoot; + this.hiddenClasses = hiddenClasses; } } diff --git a/src/main/java/org/quiltmc/loader/impl/util/LoaderUtil.java b/src/main/java/org/quiltmc/loader/impl/util/LoaderUtil.java index 58b169fbc..a7b1836bb 100644 --- a/src/main/java/org/quiltmc/loader/impl/util/LoaderUtil.java +++ b/src/main/java/org/quiltmc/loader/impl/util/LoaderUtil.java @@ -28,6 +28,12 @@ public static String getClassFileName(String className) { return className.replace('.', '/').concat(".class"); } + public static String getClassNameFromTransformCache(String name) { + name = name.substring(name.indexOf('/', 1) + 1); // remove /mod_id/ + name = name.replace('/', '.'); + return name.substring(0, name.length() - 6); // remove .class + } + public static Path normalizePath(Path path) { if (path == null) { return null;