diff --git a/gradle.properties b/gradle.properties index bb8d01d80..5dfed98d6 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.28.0-beta.2 +quilt_loader = 0.28.0-beta.3 # Fabric & Quilt Libraries asm = 9.7.1 @@ -15,7 +15,7 @@ tiny_remapper = 0.10.4 access_widener = 2.1.0 mapping_io = 0.6.1 quilt_json5 = 1.0.4+final -quilt_parsers = 0.2.0 +quilt_parsers = 0.3.1 quilt_loader_sat4j = 2.3.5.1 quilt_config = 1.3.1 quilt_chasm = 0.1.0-20230126.045734-27 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 bb90f340c..e2961c79e 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 @@ -35,8 +35,8 @@ import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -import org.quiltmc.json5.JsonReader; -import org.quiltmc.json5.JsonToken; +import org.quiltmc.parsers.json.JsonReader; +import org.quiltmc.parsers.json.JsonToken; import org.quiltmc.loader.api.Version; import org.quiltmc.loader.impl.QuiltLoaderImpl; import org.quiltmc.loader.impl.util.ExceptionUtil; 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 1546a8569..e97fa8dd4 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 @@ -163,7 +163,7 @@ public ModDependencyIdentifier id() { }); } List paths = new ArrayList<>(gameJars); - paths.addAll(miscGameLibraries); +// paths.addAll(miscGameLibraries); return Collections.singletonList(new BuiltinMod(paths, metadata.build())); } diff --git a/minecraft/src/test/java/net/fabricmc/test/McVersionLookupTest.java b/minecraft/src/test/java/net/fabricmc/test/McVersionLookupTest.java index 9515fef91..79b0d407c 100644 --- a/minecraft/src/test/java/net/fabricmc/test/McVersionLookupTest.java +++ b/minecraft/src/test/java/net/fabricmc/test/McVersionLookupTest.java @@ -31,7 +31,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import org.quiltmc.json5.JsonReader; +import org.quiltmc.parsers.json.JsonReader; import org.quiltmc.loader.impl.game.minecraft.McVersion; import org.quiltmc.loader.impl.game.minecraft.McVersionLookup; diff --git a/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java b/src/main/java/org/quiltmc/loader/impl/QuiltLoaderImpl.java index 4c38e586a..df7da69a3 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.28.0-beta.2"; + public static final String VERSION = "0.28.0-beta.3"; public static final String MOD_ID = "quilt_loader"; public static final String DEFAULT_MODS_DIR = "mods"; public static final String DEFAULT_CACHE_DIR = ".cache"; diff --git a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltClassPath.java b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltClassPath.java index d9b944a54..7aef10536 100644 --- a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltClassPath.java +++ b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltClassPath.java @@ -50,9 +50,11 @@ import org.quiltmc.loader.impl.util.log.LogCategory; /** Essentially a {@link QuiltJoinedFileSystem} but which caches all paths in advance. Not exposed as a filesystem since - * this is a bit more dynamic than that. */ + * this is a bit more dynamic than that. + * + * @param Optional extra data that may be stored alongside paths. */ @QuiltLoaderInternal(QuiltLoaderInternalType.LEGACY_EXPOSED) -public class QuiltClassPath { +public class QuiltClassPath { private static final boolean VALIDATE = SystemProperties.getBoolean( SystemProperties.VALIDATE_QUILT_CLASS_PATH, SystemProperties.VALIDATION_LEVEL > 0 @@ -66,37 +68,114 @@ public class QuiltClassPath { * by int hash) */ private static final boolean USE_CUSTOM_TABLE = !Boolean.getBoolean(SystemProperties.DISABLE_QUILT_CLASS_PATH_CUSTOM_TABLE); - private final List allRoots = VALIDATE ? new CopyOnWriteArrayList<>() : null; - private final AtomicReference roots = new AtomicReference<>(new Path[0]); + private final Class dataClass; + private final List allRoots = VALIDATE ? new CopyOnWriteArrayList<>() : null; + private final AtomicReference roots = new AtomicReference<>(new PathData[0]); private final FileMap files = USE_CUSTOM_TABLE ? new HashTableFileMap() : new StandardFileMap(); /** Set if {@link #VALIDATE} finds a problem. */ private static boolean printFullDetail = false; + @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) + public static final class PathResult { + public final Path path; + + /** The root which the data was associated with. May be null if the path was added with no data. */ + public final Path root; + + /** The data associated with the root path. May be null if no data was added for the root path. */ + public final D data; + + PathResult(Path path, Path root, D data) { + this.path = path; + this.root = root; + this.data = data; + } + + @Override + public String toString() { + return "PathResult{path=\"" + path + "\", root=\"" + root + "\", data=" + data + "}"; + } + + @Override + public int hashCode() { + return Objects.hash(path, System.identityHashCode(root), System.identityHashCode(data)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof PathResult)) { + return false; + } + PathResult other = (PathResult) obj; + return Objects.equals(path, other.path) // + && root == other.root && data == other.data; + } + } + + private static final class PathData { + final Path path; + final Object data; + + PathData(Path path, Object data) { + this.path = path; + this.data = data; + } + } + + public QuiltClassPath() { + this(null); + } + + public QuiltClassPath(Class dataClass) { + this.dataClass = dataClass; + } + public void addRoot(Path root) { - if (VALIDATE) { - allRoots.add(root); + addRoot(root, null); + } + + public void addRoot(Path root, D data) { + if (dataClass == null) { + if (data != null) { + throw new IllegalArgumentException("Cannot add extra data if this doesn't have a data class!"); + } + } else if (data != null) { + // We might as well validate this + dataClass.cast(data); } if (root instanceof QuiltJoinedPath) { QuiltJoinedFileSystem fs = ((QuiltJoinedPath) root).fs; for (Path from : fs.from) { - addRoot(from); + addRoot(from, data); } - } else if (root instanceof QuiltMemoryPath) { + return; + } + + if (VALIDATE) { + allRoots.add(new PathData(root, data)); + } + + if (root instanceof QuiltMemoryPath) { QuiltMemoryFileSystem fs = ((QuiltMemoryPath) root).fs; if (fs instanceof QuiltMemoryFileSystem.ReadWrite) { Log.warn(LogCategory.GENERAL, "Adding read/write FS root to the classpath, this may slow down class loading: " + fs.name); - addRootToInternalArray(root); + addRootToInternalArray(root, data); } else { files.ensureCapacityFor(fs.getEntryCount()); + ExtraData extraData = data == null ? null : new ExtraData(root, data); + for (Path key : fs.getEntryPathIterator()) { - putQuickFile(key.toString(), key); + putQuickFile(key.toString(), key, extraData); } } @@ -105,8 +184,10 @@ public void addRoot(Path root) { files.ensureCapacityFor(fs.getEntryCount()); + ExtraData extraData = data == null ? null : new ExtraData(root, data); + for (Path key : fs.getEntryPathIterator()) { - putQuickFile(key.toString(), key); + putQuickFile(key.toString(), key, extraData); } } else { @@ -115,8 +196,9 @@ public void addRoot(Path root) { if ("jar".equals(fs.provider().getScheme())) { // Assume it's read-only for speed - addRootToInternalArray(root); - beginScanning(root); + addRootToInternalArray(root, data); + ExtraData extraData = data == null ? null : new ExtraData(root, data); + beginScanning(root, extraData); return; } @@ -124,25 +206,25 @@ public void addRoot(Path root) { Log.warn(LogCategory.GENERAL, "Adding unknown root to the classpath, this may slow down class loading: " + root.getFileSystem() + " " + root); } - addRootToInternalArray(root); + addRootToInternalArray(root, data); } } - private void addRootToInternalArray(Path root) { + private void addRootToInternalArray(Path root, D data) { roots.updateAndGet(array -> { - Path[] array2 = Arrays.copyOf(array, array.length + 1); - array2[array.length] = root; + PathData[] array2 = Arrays.copyOf(array, array.length + 1); + array2[array.length] = new PathData(root, data); return array2; }); } - private void putQuickFile(String fileName, Path file) { - files.put(file); + private void putQuickFile(String fileName, Path file, ExtraData extraData) { + files.put(file, extraData); } - private void beginScanning(Path zipRoot) { + private void beginScanning(Path zipRoot, ExtraData extraData) { synchronized (QuiltClassPath.class) { - SCAN_TASKS.add(() -> scanZip(zipRoot)); + SCAN_TASKS.add(() -> scanZip(zipRoot, extraData)); int scannerCount = ACTIVE_SCANNERS.size(); if (scannerCount < 4 && scannerCount < SCAN_TASKS.size()) { Thread scanner = new Thread("QuiltClassPath ZipScanner#" + ZIP_SCANNER_COUNT.incrementAndGet()) { @@ -168,7 +250,7 @@ public void run() { } } - private void scanZip(Path zipRoot) { + private void scanZip(Path zipRoot, ExtraData extraData) { try { long start = System.nanoTime(); Files.walkFileTree(zipRoot, new SimpleFileVisitor() { @@ -200,7 +282,7 @@ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) th stack.addLast("/"); } foldersRead++; - putQuickFile(dir.toString(), dir); + putQuickFile(dir.toString(), dir, extraData); return FileVisitResult.CONTINUE; } @@ -223,16 +305,16 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } filesRead++; relativeString.append(file.getFileName().toString()); - putQuickFile(relativeString.toString(), file); + putQuickFile(relativeString.toString(), file, extraData); return FileVisitResult.CONTINUE; } }); roots.updateAndGet(array -> { - Path[] array2 = new Path[array.length - 1]; + PathData[] array2 = new PathData[array.length - 1]; int output = 0; for (int i = 0; i < array.length; i++) { - Path old = array[i]; - if (old != zipRoot) { + PathData old = array[i]; + if (old.path != zipRoot) { array2[output++] = old; } } @@ -246,21 +328,26 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO } public Path findResource(String path) { - Path[] rootsCopy0 = roots.get(); - Path quick = quickFindResource(path); + PathResult result = findResourceData(path); + return result != null ? result.path : null; + } + + public PathResult findResourceData(String path) { + PathData[] rootsCopy0 = roots.get(); + PathResult quick = quickFindResource(path); if (VALIDATE) { - Path slow = findResourceIn(allRoots.toArray(new Path[0]), path); + PathResult slow = findResourceIn(allRoots.toArray(new PathData[0]), path); if (!Objects.equals(slow, quick)) { - Path quick2 = quickFindResource(path); - Path[] rootsCopy1 = roots.get(); + PathResult quick2 = quickFindResource(path); + PathData[] rootsCopy1 = roots.get(); IllegalStateException ex = new IllegalStateException( "quickFindResource( " + path + " ) returned a different path to the slow find resource!" + "\nquick 1 = " + describePath(quick) + "\nslow = " + describePath(slow) + "\nquick 2 = " + describePath(quick2) - + "\nroots 1 = " + describePaths(Arrays.asList(rootsCopy0)) - + "\nroots 2 = " + describePaths(Arrays.asList(rootsCopy1)) - + "\nall_roots = " + describePaths(allRoots) + + "\nroots 1 = " + describePathDatas(Arrays.asList(rootsCopy0)) + + "\nroots 2 = " + describePathDatas(Arrays.asList(rootsCopy1)) + + "\nall_roots = " + describePathDatas(allRoots) ); ex.printStackTrace(); printFullDetail = true; @@ -271,6 +358,31 @@ public Path findResource(String path) { return quick; } + private D castData(Object data) { + if (dataClass == null) { + if (data != null) { + throw new IllegalStateException("Expected no data, but got " + data.getClass()); + } + return null; + } else { + return dataClass.cast(data); + } + } + + private static String describePath(PathData path) { + if (path == null) { + return "null"; + } + return describePath(path.path); + } + + private static String describePath(PathResult path) { + if (path == null) { + return "null"; + } + return describePath(path.path); + } + private static String describePath(Path path) { if (path == null) { return "null"; @@ -281,10 +393,13 @@ private static String describePath(Path path) { if (path instanceof OverlappingPath) { return ((OverlappingPath) path).describe(); } + if (path instanceof ExtraDataPath) { + return ((ExtraDataPath) path).describe(); + } return "'" + path + "'.fs:" + path.getFileSystem(); } - private Path quickFindResource(String path) { + private PathResult quickFindResource(String path) { String absolutePath = path; if (!path.startsWith("/")) { absolutePath = "/" + path; @@ -304,7 +419,7 @@ private Path quickFindResource(String path) { */ // Grabbing a copy of the roots array before we check in files ensures we never miss a path // This fix is also applied to quickGetResources - Path[] fullArray = roots.get(); + PathData[] fullArray = roots.get(); Path quick = files.get(absolutePath); if (printFullDetail) { @@ -320,36 +435,55 @@ private Path quickFindResource(String path) { if (quick != null) { if (quick instanceof OverlappingPath) { - return ((OverlappingPath) quick).getFirst(); + quick = ((OverlappingPath) quick).getFirst(); + } + + if (quick instanceof ExtraDataPath) { + ExtraDataPath dataPath = (ExtraDataPath) quick; + return new PathResult<>(dataPath.path, dataPath.data.root, castData(dataPath.data.data)); + } else { + return new PathResult<>(quick, null, null); } - return quick; } return findResourceIn(fullArray, path); } - private static Path findResourceIn(Path[] array, String path) { - for (Path root : array) { - Path ext = root.resolve(path); + private PathResult findResourceIn(PathData[] array, String path) { + for (PathData root : array) { + Path ext = root.path.resolve(path); if (FasterFiles.exists(ext)) { - return ext; + if (root.data == null) { + return new PathResult<>(ext, null, null); + } else { + return new PathResult<>(ext, root.path, castData(root.data)); + } } } return null; } public List getResources(String path) { - List quick = quickGetResources(path); + List> fullResult = getAllResourceData(path); + List list = new ArrayList<>(fullResult.size()); + for (PathResult result : fullResult) { + list.add(result.path); + } + return list; + } + + public List> getAllResourceData(String path) { + List> quick = quickGetResources(path); if (VALIDATE) { - List slow = new ArrayList<>(); - getResourcesIn(allRoots.toArray(new Path[0]), path, slow); + List> slow = new ArrayList<>(); + getResourcesIn(allRoots.toArray(new PathData[0]), path, slow); if (!quick.equals(slow)) { - List quick2 = quickGetResources(path); + List> quick2 = quickGetResources(path); IllegalStateException ex = new IllegalStateException( "quickGetResources( " + path + " ) returned a different list of paths to the slow get resources!" - + "\nquick 1 = " + describePaths(quick) - + "\nslow = " + describePaths(slow) - + "\nquick 2 = " + describePaths(quick2) + + "\nquick 1 = " + describePathResults(quick) + + "\nslow = " + describePathResults(slow) + + "\nquick 2 = " + describePathResults(quick2) ); ex.printStackTrace(); printFullDetail = true; @@ -360,6 +494,30 @@ public List getResources(String path) { return quick; } + private static String describePathDatas(List paths) { + StringBuilder sb = new StringBuilder("["); + for (PathData p : paths) { + if (sb.length() > 1) { + sb.append(", "); + } + sb.append(describePath(p)); + } + sb.append(" ]"); + return sb.toString(); + } + + private static String describePathResults(List> paths) { + StringBuilder sb = new StringBuilder("["); + for (PathResult p : paths) { + if (sb.length() > 1) { + sb.append(", "); + } + sb.append(describePath(p)); + } + sb.append(" ]"); + return sb.toString(); + } + private static String describePaths(List paths) { StringBuilder sb = new StringBuilder("["); for (Path p : paths) { @@ -372,7 +530,7 @@ private static String describePaths(List paths) { return sb.toString(); } - private List quickGetResources(String path) { + private List> quickGetResources(String path) { String absolutePath = path; if (!path.startsWith("/")) { absolutePath = "/" + path; @@ -380,19 +538,29 @@ private List quickGetResources(String path) { // Thread race condition fix // see "quickFindResource" for details - Path[] rootsArray = roots.get(); + PathData[] rootsArray = roots.get(); Path quick = files.get(absolutePath); if (quick instanceof HashCollisionPath) { quick = ((HashCollisionPath) quick).get(absolutePath); } - List paths = new ArrayList<>(); + List> paths = new ArrayList<>(); if (quick != null) { if (quick instanceof OverlappingPath) { - Collections.addAll(paths, ((OverlappingPath) quick).paths); + for (Path real : ((OverlappingPath) quick).paths) { + if (real instanceof ExtraDataPath) { + ExtraDataPath dataPath = (ExtraDataPath) real; + paths.add(new PathResult<>(dataPath.path, dataPath.data.root, castData(dataPath.data.data))); + } else { + paths.add(new PathResult<>(real, null, null)); + } + } + } else if (quick instanceof ExtraDataPath) { + ExtraDataPath dataPath = (ExtraDataPath) quick; + paths.add(new PathResult<>(dataPath.path, dataPath.data.root, castData(dataPath.data.data))); } else { - paths.add(quick); + paths.add(new PathResult<>(quick, null, null)); } } @@ -400,11 +568,15 @@ private List quickGetResources(String path) { return Collections.unmodifiableList(paths); } - private static void getResourcesIn(Path[] src, String path, List dst) { - for (Path root : src) { - Path ext = root.resolve(path); + private void getResourcesIn(PathData[] src, String path, List> dst) { + for (PathData root : src) { + Path ext = root.path.resolve(path); if (FasterFiles.exists(ext)) { - dst.add(ext); + if (root.data == null) { + dst.add(new PathResult<>(ext, null, null)); + } else { + dst.add(new PathResult<>(ext, root.path, castData(root.data))); + } } } } @@ -413,9 +585,15 @@ private static boolean isEqualPath(Path in, Path value) { if (in instanceof OverlappingPath) { in = ((OverlappingPath) in).paths[0]; } + if (in instanceof ExtraDataPath) { + in = ((ExtraDataPath) in).path; + } if (value instanceof OverlappingPath) { value = ((OverlappingPath) value).paths[0]; } + if (value instanceof ExtraDataPath) { + value = ((ExtraDataPath) value).path; + } if (in instanceof QuiltBasePath) { return ((QuiltBasePath) in).isToStringEqual(value); } @@ -436,6 +614,10 @@ private static boolean isEqualPath(Path in, Path value) { private static boolean isEqual(String key, Path value) { + if (value instanceof ExtraDataPath) { + value = ((ExtraDataPath) value).path; + } + boolean should = VALIDATE && key.equals(value.toString()); int offset = key.length(); @@ -501,28 +683,28 @@ final Path get(String key) { abstract void ensureCapacityFor(int newPathCount); - abstract void put(Path newPath); + abstract void put(Path newPath, ExtraData extraData); - protected static Path computeNewPath(Path current, Path file) { + protected static Path computeNewPath(Path current, Path file, ExtraData extraData) { if (current == null) { - return file; + return ExtraDataPath.wrap(file, extraData); } else if (current instanceof HashCollisionPath) { HashCollisionPath collision = (HashCollisionPath) current; int equalIndex = collision.getEqualPathIndex(file); if (equalIndex < 0) { Path[] newArray = new Path[collision.values.length + 1]; System.arraycopy(collision.values, 0, newArray, 0, collision.values.length); - newArray[collision.values.length] = file; + newArray[collision.values.length] = ExtraDataPath.wrap(file, extraData); collision.values = newArray; } else { Path equal = collision.values[equalIndex]; if (equal instanceof OverlappingPath) { OverlappingPath multi = (OverlappingPath) equal; - multi.addPath(file); + multi.addPath(file, extraData); multi.data &= ~OverlappingPath.FLAG_HAS_WARNED; } else { OverlappingPath multi = new OverlappingPath(); - multi.paths = new Path[] { equal, file }; + multi.paths = new Path[] { equal, ExtraDataPath.wrap(file, extraData) }; collision.values[equalIndex] = multi; } } @@ -530,19 +712,19 @@ protected static Path computeNewPath(Path current, Path file) { } else if (current instanceof OverlappingPath) { if (isEqualPath(file, ((OverlappingPath) current).paths[0])) { OverlappingPath multi = (OverlappingPath) current; - multi.addPath(file); + multi.addPath(file, extraData); multi.data &= ~OverlappingPath.FLAG_HAS_WARNED; return multi; } else { - return new HashCollisionPath(current, file); + return new HashCollisionPath(current, ExtraDataPath.wrap(file, extraData)); } } else { if (isEqualPath(file, current)) { OverlappingPath multi = new OverlappingPath(); - multi.paths = new Path[] { current, file }; + multi.paths = new Path[] { current, ExtraDataPath.wrap(file, extraData) }; return multi; } else { - return new HashCollisionPath(current, file); + return new HashCollisionPath(current, ExtraDataPath.wrap(file, extraData)); } } } @@ -560,8 +742,8 @@ void ensureCapacityFor(int newPathCount) { } @Override - void put(Path path) { - files.compute(path.toString().hashCode(), (a, current) -> computeNewPath(current, path)); + void put(Path path, ExtraData extraData) { + files.compute(path.toString().hashCode(), (a, current) -> computeNewPath(current, path, extraData)); } @Override @@ -605,13 +787,13 @@ synchronized void ensureCapacityFor(int newPathCount) { } @Override - synchronized void put(Path newPath) { + synchronized void put(Path newPath, ExtraData extraData) { entryCount++; if (table.length * FILL_PERCENT < entryCount) { rehash(table.length * 2); } int index = hashCode(newPath) & table.length - 1; - table[index] = computeNewPath(table[index], newPath); + table[index] = computeNewPath(table[index], newPath, extraData); } private static int hashCode(Path path) { @@ -640,14 +822,28 @@ private void rehash(int newSize) { } for (Path sub2 : subIter) { - final Path hashPath; + + Path hashPath; if (sub2 instanceof OverlappingPath) { hashPath = ((OverlappingPath) sub2).paths[0]; } else { hashPath = sub2; } + + final ExtraData extraData; + final Path toAdd; + if (hashPath instanceof ExtraDataPath) { + ExtraDataPath dataPath = (ExtraDataPath) hashPath; + extraData = dataPath.data; + hashPath = dataPath.path; + toAdd = hashPath; + } else { + extraData = null; + toAdd = sub2; + } + int index = hashCode(hashPath) & newTable.length - 1; - newTable[index] = computeNewPath(newTable[index], sub2); + newTable[index] = computeNewPath(newTable[index], toAdd, extraData); } } table = newTable; @@ -730,9 +926,9 @@ public OverlappingPath(int fullHash) { public OverlappingPath() {} - public void addPath(Path file) { + public void addPath(Path file, ExtraData extraData) { paths = Arrays.copyOf(paths, paths.length + 1); - paths[paths.length - 1] = file; + paths[paths.length - 1] = ExtraDataPath.wrap(file, extraData); file.getNameCount(); } @@ -782,4 +978,55 @@ public Path getFirst() { return paths[0]; } } + + /** Optional extra information about how a path should be handled. */ + @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) + private static final class ExtraData { + + final Path root; + /** Corresponds to the "D" parameter of {@link QuiltClassPath} */ + final Object data; + + ExtraData(Path root, Object data) { + this.root = root; + this.data = data; + } + } + + /** Used to store {@link ExtraData} about a real Path.*/ + @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) + private static final class ExtraDataPath extends NullPath { + final Path path; + final ExtraData data; + + ExtraDataPath(Path path, ExtraData data) { + // Make sure the given path: + // 1: Is not null + // 2: Is not a NullPath + path.getNameCount(); + this.path = path; + this.data = data; + } + + public static Path wrap(Path file, ExtraData extraData) { + if (extraData != null) { + return new ExtraDataPath(file, extraData); + } else { + return file; + } + } + + @Override + protected IllegalStateException illegal() { + IllegalStateException ex = new IllegalStateException( + "QuiltClassPath must NEVER return an ExtraDataPath - something has gone very wrong!" + ); + ex.printStackTrace(); + throw ex; + } + + private String describe() { + return "ExtraDataPath " + describePath(path); + } + } } diff --git a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltZipFileSystem.java b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltZipFileSystem.java index ecc6a239c..f53d46289 100644 --- a/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltZipFileSystem.java +++ b/src/main/java/org/quiltmc/loader/impl/filesystem/QuiltZipFileSystem.java @@ -30,6 +30,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; import java.nio.file.Files; +import java.nio.file.NotLinkException; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.StandardOpenOption; @@ -50,10 +51,12 @@ import java.util.zip.ZipInputStream; import org.jetbrains.annotations.Nullable; +import org.quiltmc.loader.api.ExtendedFileSystem; import org.quiltmc.loader.impl.filesystem.QuiltUnifiedEntry.QuiltUnifiedFile; import org.quiltmc.loader.impl.filesystem.QuiltUnifiedEntry.QuiltUnifiedFolder; import org.quiltmc.loader.impl.filesystem.QuiltUnifiedEntry.QuiltUnifiedFolderReadOnly; import org.quiltmc.loader.impl.filesystem.QuiltUnifiedEntry.QuiltUnifiedFolderWriteable; +import org.quiltmc.loader.impl.filesystem.QuiltUnifiedEntry.QuiltUnifiedMountedFile; import org.quiltmc.loader.impl.util.DisconnectableByteChannel; import org.quiltmc.loader.impl.util.ExposedByteArrayOutputStream; import org.quiltmc.loader.impl.util.FileUtil; @@ -73,7 +76,7 @@ * this if it's not read-only, or the "compress" constructor argument is false). */ @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) public class QuiltZipFileSystem extends QuiltMapFileSystem - implements ReadOnlyFileSystem { + implements ReadOnlyFileSystem, ExtendedFileSystem { static final boolean DEBUG_TEST_READING = false; @@ -189,11 +192,11 @@ private void setupMultiReleaseJar() throws IOException { continue; } - copyMultiReleaseEntry(exactVersionPath, root); + linkMultiReleaseEntry(exactVersionPath, root); } } - private void copyMultiReleaseEntry(Path from, QuiltZipPath to) throws IOException { + private void linkMultiReleaseEntry(Path from, QuiltZipPath to) throws IOException { QuiltUnifiedEntry entry = getEntry(from); if (entry instanceof QuiltUnifiedFolder) { QuiltUnifiedFolder folder = (QuiltUnifiedFolder) entry; @@ -207,12 +210,11 @@ private void copyMultiReleaseEntry(Path from, QuiltZipPath to) throws IOExceptio } for (Path child : folder.getChildren()) { - copyMultiReleaseEntry(child, to.resolve(child.getFileName())); + linkMultiReleaseEntry(child, to.resolve(child.getFileName())); } } else { - QuiltUnifiedFile file = (QuiltUnifiedFile) entry; removeEntry(to, false); - addEntryRequiringParent(file.createMovedTo(to)); + addEntryRequiringParent(new QuiltUnifiedMountedFile(to, from, true)); } } @@ -308,6 +310,11 @@ private void addFolder(QuiltZipPath src, QuiltZipPath dst) { } else if (entryFrom instanceof QuiltZipFile) { QuiltZipFile from = (QuiltZipFile) entryFrom; addEntryWithoutParentsUnsafe(new QuiltZipFile(dst, source, from.offset, from.compressedSize, from.uncompressedSize, from.isCompressed)); + } else if (entryFrom instanceof QuiltUnifiedMountedFile) { + // Used for Multi-Release jars + // This isn't ideal, as it will continue to point to the original file system + QuiltUnifiedMountedFile from = (QuiltUnifiedMountedFile) entryFrom; + addEntryWithoutParentsUnsafe(new QuiltUnifiedMountedFile(dst, from.to, true)); } else { // This isn't meant to happen, it means something got constructed badly throw new IllegalArgumentException("Unknown source entry " + entryFrom); @@ -357,6 +364,27 @@ public boolean isExecutable(Path path) { return exists(path); } + // ExtendedFileSystem + + // These are supported due to multi-release jars + + @Override + public boolean isMountedFile(Path file) { + return getEntry(file) instanceof QuiltUnifiedMountedFile; + } + + // Copy-on-write is unsupported + + @Override + public Path readMountTarget(Path file) throws IOException { + QuiltUnifiedEntry entry = getEntry(file); + if (entry instanceof QuiltUnifiedMountedFile) { + return ((QuiltUnifiedMountedFile) entry).to; + } else { + throw new NotLinkException(file.toString() + " is not a mounted file!"); + } + } + // Custom classes to grab the real offset while reading the zip static final class CountingInputStream extends InputStream { diff --git a/src/main/java/org/quiltmc/loader/impl/game/LoaderLibrary.java b/src/main/java/org/quiltmc/loader/impl/game/LoaderLibrary.java index a9ba9df8e..2be98285b 100644 --- a/src/main/java/org/quiltmc/loader/impl/game/LoaderLibrary.java +++ b/src/main/java/org/quiltmc/loader/impl/game/LoaderLibrary.java @@ -27,7 +27,6 @@ import org.objectweb.asm.tree.analysis.Analyzer; import org.objectweb.asm.util.CheckClassAdapter; import org.quiltmc.config.api.Config; -import org.quiltmc.json5.JsonReader; import org.quiltmc.loader.impl.util.UrlConversionException; import org.quiltmc.loader.impl.util.UrlUtil; import org.spongepowered.asm.launch.MixinBootstrap; @@ -47,7 +46,11 @@ enum LoaderLibrary { TINY_REMAPPER(TinyRemapper.class), MAPPING_IO(MappingTreeView.class), ACCESS_WIDENER(AccessWidener.class), - QUILT_JSON5(JsonReader.class), + // This is an older library, so there's nothing we can do about this deprecation warning + // However it shouldn't be replaced with the newer version, since we want to target this + // library exactly + @SuppressWarnings("deprecation") + QUILT_JSON5(org.quiltmc.json5.JsonReader.class), QUILT_CONFIG(Config.class), diff --git a/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassLoader.java b/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassLoader.java index 702e68bf0..02931f5cb 100644 --- a/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassLoader.java +++ b/src/main/java/org/quiltmc/loader/impl/launch/knot/KnotClassLoader.java @@ -19,10 +19,13 @@ import net.fabricmc.api.EnvType; +import org.quiltmc.loader.api.ExtendedFiles; import org.quiltmc.loader.api.ModContainer; import org.quiltmc.loader.impl.filesystem.QuiltClassPath; +import org.quiltmc.loader.impl.filesystem.QuiltClassPath.PathResult; import org.quiltmc.loader.impl.filesystem.QuiltZipFileSystem; import org.quiltmc.loader.impl.filesystem.QuiltZipFileSystem.ZipHandling; +import org.quiltmc.loader.impl.filesystem.QuiltZipPath; import org.quiltmc.loader.impl.game.GameProvider; import org.quiltmc.loader.impl.util.DeferredInputStream; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; @@ -62,7 +65,7 @@ public void addURL(URL url) { } } - private final QuiltClassPath paths = new QuiltClassPath(); + private final QuiltClassPath paths = new QuiltClassPath<>(PathCustomUrl.class); private final DynamicURLClassLoader fakeLoader; private final DynamicURLClassLoader minimalLoader; private final ClassLoader originalLoader; @@ -117,18 +120,25 @@ public URL getResource(String name, boolean allowFromParent) { public URL findResource(String name) { Objects.requireNonNull(name); - Path path = paths.findResource(name); + PathResult path = paths.findResourceData(name); if (path != null) { - try { - return UrlUtil.asUrl(path); - } catch (MalformedURLException e) { - throw new Error(e); - } + return toUrl(path); } return minimalLoader.getResource(name); } + private static URL toUrl(PathResult path) { + try { + if (path.data != null) { + return path.data.constructUrl(path.path, path.root); + } + return UrlUtil.asUrl(path.path); + } catch (MalformedURLException e) { + throw new Error("Failed to turn the path " + path.path.getClass() + " " + path + " into a url!", e); + } + } + @Override public InputStream getResourceAsStream(String name) { Objects.requireNonNull(name); @@ -166,11 +176,11 @@ private InputStream getResourceAsStream0(String name) { public Enumeration getResources(String name) throws IOException { Objects.requireNonNull(name); - List fromPaths = paths.getResources(name); + List> fromPaths = paths.getAllResourceData(name); Enumeration first = minimalLoader.getResources(name); Enumeration second = originalLoader.getResources(name); return new Enumeration() { - Iterator iterator = fromPaths.iterator(); + Iterator> iterator = fromPaths.iterator(); Enumeration current = first; @Override @@ -197,12 +207,7 @@ public boolean hasMoreElements() { @Override public URL nextElement() { if (iterator.hasNext()) { - Path path = iterator.next(); - try { - return UrlUtil.asUrl(path); - } catch (MalformedURLException e) { - throw new IllegalStateException("Failed to turn the path " + path.getClass() + " " + path + " into a url!", e); - } + return toUrl(iterator.next()); } if (current == null) { return null; @@ -290,7 +295,8 @@ public void addPath(Path root, ModContainer mod, URL origin) { Log.warn(LogCategory.GENERAL, "Failed to open the file " + root + ", adding it via the slow method instead!", io); } if (zipRoot != null) { - paths.addRoot(zipRoot); + String urlBase = "jar:" + asUrl + "!/"; + paths.addRoot(zipRoot, new PathCustomUrl(urlBase)); } else { minimalLoader.addURL(asUrl); } @@ -338,4 +344,33 @@ public String toString() { static { registerAsParallelCapable(); } + + private static final class PathCustomUrl { + final String urlBase; + + PathCustomUrl(String urlBase) { + this.urlBase = urlBase; + } + + URL constructUrl(Path path, Path root) throws MalformedURLException { + Path realPath = null; + if (ExtendedFiles.isMountedFile(path)) { + // Used for multi-release jars + // We 'mount' multi-versioned classes, so they will really be somewhere else + try { + realPath = ExtendedFiles.readMountTarget(path); + if (realPath.getFileSystem() == root.getFileSystem()) { + path = realPath; + } + } catch (IOException e) { + // In theory this should never happen? + // At least for quilt-loader's own filesystems + // Which this must be (as we constructed a QuiltZipFileSystem) + throw new Error("Failed to read mount target? ", e); + } + } + + return new URL(urlBase + root.relativize(path)); + } + } } diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderFactoryImpl.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderFactoryImpl.java index 677b8645c..094808049 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderFactoryImpl.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderFactoryImpl.java @@ -29,8 +29,8 @@ import java.util.HashMap; import java.util.Map; -import org.quiltmc.json5.JsonReader; -import org.quiltmc.json5.JsonWriter; +import org.quiltmc.parsers.json.JsonReader; +import org.quiltmc.parsers.json.JsonWriter; import org.quiltmc.loader.api.LoaderValue; import org.quiltmc.loader.api.plugin.LoaderValueFactory; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderValue.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderValue.java index 6beefa52e..6e53131ae 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderValue.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderValue.java @@ -30,11 +30,11 @@ import java.util.stream.Stream; import org.jetbrains.annotations.Nullable; -import org.quiltmc.json5.JsonReader; -import org.quiltmc.json5.JsonToken; -import org.quiltmc.json5.JsonWriter; -import org.quiltmc.json5.exception.MalformedSyntaxException; -import org.quiltmc.json5.exception.ParseException; +import org.quiltmc.parsers.json.JsonReader; +import org.quiltmc.parsers.json.JsonToken; +import org.quiltmc.parsers.json.JsonWriter; +import org.quiltmc.parsers.json.MalformedSyntaxException; +import org.quiltmc.parsers.json.ParseException; import org.quiltmc.loader.api.LoaderValue; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/ModLicenseImpl.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/ModLicenseImpl.java index 527ab44f8..2a0cb219c 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/ModLicenseImpl.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/ModLicenseImpl.java @@ -17,7 +17,7 @@ package org.quiltmc.loader.impl.metadata.qmj; import org.jetbrains.annotations.Nullable; -import org.quiltmc.json5.JsonReader; +import org.quiltmc.parsers.json.JsonReader; import org.quiltmc.loader.api.LoaderValue; import org.quiltmc.loader.api.ModLicense; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/ModMetadataReader.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/ModMetadataReader.java index dab9a3f3c..e0ef7f154 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/ModMetadataReader.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/ModMetadataReader.java @@ -24,9 +24,10 @@ import java.nio.file.Path; import org.jetbrains.annotations.Nullable; -import org.quiltmc.json5.JsonReader; -import org.quiltmc.json5.JsonToken; -import org.quiltmc.json5.exception.ParseException; +import org.quiltmc.parsers.json.JsonFormat; +import org.quiltmc.parsers.json.JsonReader; +import org.quiltmc.parsers.json.JsonToken; +import org.quiltmc.parsers.json.ParseException; import org.quiltmc.loader.api.LoaderValue; import org.quiltmc.loader.api.plugin.QuiltPluginManager; import org.quiltmc.loader.api.plugin.gui.PluginGuiTreeNode; @@ -71,12 +72,14 @@ public static InternalModMetadata read(InputStream json) throws IOException, Par public static InternalModMetadata read(InputStream json, Path path, QuiltPluginManager manager, PluginGuiTreeNode warningNode) throws IOException, ParseException { JsonLoaderValue value; - try (JsonReader reader = JsonReader.json5(new InputStreamReader(json, StandardCharsets.UTF_8))) { - // Only use the reader as a JSON5 one if we're dealing with a JSON5 file - if (!path.toString().endsWith(".json5")) { - reader.setStrictJson(); - } + final JsonFormat format; + if (path.getFileName().toString().endsWith(".json5")) { + format = JsonFormat.JSON5; + } else { + format = JsonFormat.JSON; + } + try (JsonReader reader = JsonReader.create(new InputStreamReader(json, StandardCharsets.UTF_8), format)) { // Root must be an object if (reader.peek() != JsonToken.BEGIN_OBJECT) { throw new ParseException(reader, "A quilt.mod.json must have an object at the root"); diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/QuiltOverrides.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/QuiltOverrides.java index ab7696e9d..98a80b96a 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/QuiltOverrides.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/QuiltOverrides.java @@ -25,8 +25,8 @@ import java.util.Map; import java.util.regex.Pattern; -import org.quiltmc.json5.JsonReader; -import org.quiltmc.json5.exception.ParseException; +import org.quiltmc.parsers.json.JsonReader; +import org.quiltmc.parsers.json.ParseException; import org.quiltmc.loader.api.LoaderValue; import org.quiltmc.loader.api.LoaderValue.LArray; import org.quiltmc.loader.api.LoaderValue.LObject; diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataImpl.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataImpl.java index 11f7fc0df..ae8cdc5dd 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataImpl.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataImpl.java @@ -23,9 +23,8 @@ import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; -import org.quiltmc.json5.exception.ParseException; +import org.quiltmc.parsers.json.ParseException; import org.quiltmc.loader.api.LoaderValue; -import org.quiltmc.loader.api.ModContainer; import org.quiltmc.loader.api.ModContributor; import org.quiltmc.loader.api.ModDependency; import org.quiltmc.loader.api.ModLicense; diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataReader.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataReader.java index 52c88f574..e476ad646 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataReader.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataReader.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; @@ -37,7 +36,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.quiltmc.json5.exception.ParseException; +import org.quiltmc.parsers.json.ParseException; import org.quiltmc.loader.api.LoaderValue; import org.quiltmc.loader.api.LoaderValue.LType; import org.quiltmc.loader.api.ModDependency; diff --git a/src/main/java/org/quiltmc/loader/impl/plugin/quilt/StandardQuiltPlugin.java b/src/main/java/org/quiltmc/loader/impl/plugin/quilt/StandardQuiltPlugin.java index 4cea7cb9a..f16e076d7 100644 --- a/src/main/java/org/quiltmc/loader/impl/plugin/quilt/StandardQuiltPlugin.java +++ b/src/main/java/org/quiltmc/loader/impl/plugin/quilt/StandardQuiltPlugin.java @@ -33,7 +33,7 @@ import java.util.Objects; import java.util.regex.Pattern; -import org.quiltmc.json5.exception.ParseException; +import org.quiltmc.parsers.json.ParseException; import org.quiltmc.loader.api.FasterFiles; import org.quiltmc.loader.api.LoaderValue; import org.quiltmc.loader.api.ModDependency; diff --git a/src/main/resources/changelog/0.28.0.txt b/src/main/resources/changelog/0.28.0.txt index eaf42b5db..5b3b6884e 100644 --- a/src/main/resources/changelog/0.28.0.txt +++ b/src/main/resources/changelog/0.28.0.txt @@ -1,6 +1,6 @@ Bug Fixes: -- Fixed most minecraft libraries not being added to Knot's classpath (allowing them to be modified via mixin or overriden via mods) +- Fixed most minecraft libraries not being added to Knot's classpath (allowing them to be modified via mixin or overridden via mods) - This should fix lots of classloader compatibility issues with various mods. - (TODO list them!) - Fixed server libraries not being identified properly with newer quilt-server-launcher.