From 2c87d1149c46cc41b3cabdce91382b139870a5c8 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sun, 20 Feb 2022 13:59:28 +0000 Subject: [PATCH 01/80] Better support for experimental versions. --- .../impl/game/minecraft/McVersionLookup.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java index 068963ea9..aab79fb91 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java @@ -55,12 +55,14 @@ public final class McVersionLookup { + "(Alpha|Beta) v?\\d+\\.\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?|" // long alpha/beta names: Alpha v1.2.3_45 + "Inf?dev (0\\.31 )?\\d+(-\\d+)?|" // long indev/infdev names: Infdev 12345678-9 + "(rd|inf)-\\d+|" // early rd-123, inf-123 - + "1\\.RV-Pre1|3D Shareware v1\\.34" // odd exceptions + + "1\\.RV-Pre1|3D Shareware v1\\.34|" // odd exceptions + + "(.*[Ee]xperimental [Ss]napshot )(\\d+)" // Experimental versions. ); private static final Pattern RELEASE_PATTERN = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?"); private static final Pattern PRE_RELEASE_PATTERN = Pattern.compile(".+(?:-pre| Pre-[Rr]elease )(\\d+)"); private static final Pattern RELEASE_CANDIDATE_PATTERN = Pattern.compile(".+(?:-rc| [Rr]elease Candidate )(\\d+)"); private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("(?:Snapshot )?(\\d+)w0?(0|[1-9]\\d*)([a-z])"); + private static final Pattern EXPERIMENTAL_PATTERN = Pattern.compile("(?:.*[Ee]xperimental [Ss]napshot )(\\d+)"); private static final Pattern BETA_PATTERN = Pattern.compile("(?:b|Beta v?)1\\.(\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?)"); private static final Pattern ALPHA_PATTERN = Pattern.compile("(?:a|Alpha v?)1\\.(\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?)"); private static final Pattern INDEV_PATTERN = Pattern.compile("(?:inf-|Inf?dev )(?:0\\.31 )?(\\d+(-\\d+)?)"); @@ -346,7 +348,9 @@ protected static String normalizeVersion(String name, String release) { Matcher matcher; - if (name.startsWith(release)) { + if ((matcher = EXPERIMENTAL_PATTERN.matcher(name)).matches()) { + return String.format("%s-Experimental.%s", release, matcher.group(1)); + } else if (name.startsWith(release)) { matcher = RELEASE_CANDIDATE_PATTERN.matcher(name); if (matcher.matches()) { @@ -534,17 +538,6 @@ private static String normalizeSpecialVersion(String version) { // The ninth Combat Test 8c, forked from 1.16.2 return "1.16.3-combat.8.c"; - case "1.18 Experimental Snapshot 1": - case "1.18 experimental snapshot 2": - case "1.18 experimental snapshot 3": - case "1.18 experimental snapshot 4": - case "1.18 experimental snapshot 5": - case "1.18 experimental snapshot 6": - case "1.18 experimental snapshot 7": - // Pre-snapshot snapshots for 1.18 before the first (21w37a) - // Characters are compared lexically, so E(xperimental) < a(lpha) - return "1.18-Experimental.".concat(version.substring(27)); - default: return null; //Don't recognise the version } From 7d08574a969a29b73b037c84f4ed1ceb1285f588 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Mon, 21 Feb 2022 12:23:55 +0000 Subject: [PATCH 02/80] Bump version. --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 3a261ff49..4cfdec896 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -71,7 +71,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.13.2"; + public static final String VERSION = "0.13.3"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From ecfb7b8ebe3c45e305141f7a97a53efcb8b0beb1 Mon Sep 17 00:00:00 2001 From: Player Date: Tue, 22 Feb 2022 10:49:45 +0100 Subject: [PATCH 03/80] Allow arbitrary +build contents in semver parser, warn earlier about non-semver and only indev, refactoring --- .../loader/impl/FabricLoaderImpl.java | 26 +-- .../loader/impl/discovery/ModCandidate.java | 10 +- .../loader/impl/discovery/ModDiscoverer.java | 39 +++-- .../loader/impl/discovery/ResultAnalyzer.java | 2 +- .../impl/metadata/DependencyOverrides.java | 9 +- .../impl/metadata/MetadataVerifier.java | 150 ++++++++++++++++++ .../impl/metadata/ModMetadataParser.java | 91 +---------- .../impl/metadata/ParseMetadataException.java | 2 +- .../impl/metadata/VersionOverrides.java | 12 +- .../util/version/SemanticVersionImpl.java | 13 +- .../fabricmc/test/V1ModJsonParsingTests.java | 5 +- 11 files changed, 217 insertions(+), 142 deletions(-) create mode 100644 src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 4cfdec896..a55beeb41 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -42,7 +42,6 @@ import net.fabricmc.loader.api.MappingResolver; import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.ObjectShare; -import net.fabricmc.loader.api.SemanticVersion; import net.fabricmc.loader.api.entrypoint.EntrypointContainer; import net.fabricmc.loader.impl.discovery.ArgumentModCandidateFinder; import net.fabricmc.loader.impl.discovery.ClasspathModCandidateFinder; @@ -196,10 +195,12 @@ public void load() { private void setup() throws ModResolutionException { boolean remapRegularMods = isDevelopmentEnvironment(); + VersionOverrides versionOverrides = new VersionOverrides(); + DependencyOverrides depOverrides = new DependencyOverrides(configDir); // discover mods - ModDiscoverer discoverer = new ModDiscoverer(); + ModDiscoverer discoverer = new ModDiscoverer(versionOverrides, depOverrides); discoverer.addCandidateFinder(new ClasspathModCandidateFinder()); discoverer.addCandidateFinder(new DirectoryModCandidateFinder(gameDir.resolve("mods"), remapRegularMods)); discoverer.addCandidateFinder(new ArgumentModCandidateFinder(remapRegularMods)); @@ -207,13 +208,7 @@ private void setup() throws ModResolutionException { Map> envDisabledMods = new HashMap<>(); modCandidates = discoverer.discoverMods(this, envDisabledMods); - // apply version and dependency overrides - - VersionOverrides versionOverrides = new VersionOverrides(); - versionOverrides.apply(modCandidates); - - DependencyOverrides depOverrides = new DependencyOverrides(configDir); - depOverrides.apply(modCandidates); + // dump version and dependency overrides info if (!versionOverrides.getAffectedModIds().isEmpty()) { Log.info(LogCategory.GENERAL, "Versions overridden for %s", String.join(", ", versionOverrides.getAffectedModIds())); @@ -340,7 +335,6 @@ private void finishModLoading() { } } - postprocessModMetadata(); setupLanguageAdapters(); setupMods(); } @@ -420,18 +414,6 @@ private void addMod(ModCandidate candidate) throws ModResolutionException { } } - protected void postprocessModMetadata() { - for (ModContainerImpl mod : mods) { - if (!(mod.getInfo().getVersion() instanceof SemanticVersion)) { - Log.warn(LogCategory.METADATA, "Mod `%s` (%s) does not respect SemVer - comparison support is limited.", - mod.getInfo().getId(), mod.getInfo().getVersion().getFriendlyString()); - } else if (((SemanticVersion) mod.getInfo().getVersion()).getVersionComponentCount() >= 4) { - Log.warn(LogCategory.METADATA, "Mod `%s` (%s) uses more dot-separated version components than SemVer allows; support for this is currently not guaranteed.", - mod.getInfo().getId(), mod.getInfo().getVersion().getFriendlyString()); - } - } - } - private void setupLanguageAdapters() { adapterMap.put("default", DefaultLanguageAdapter.INSTANCE); diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ModCandidate.java b/src/main/java/net/fabricmc/loader/impl/discovery/ModCandidate.java index 4408e8676..4bc5a52cd 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ModCandidate.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ModCandidate.java @@ -37,7 +37,9 @@ import net.fabricmc.loader.api.metadata.ModDependency; import net.fabricmc.loader.impl.game.GameProvider.BuiltinMod; import net.fabricmc.loader.impl.metadata.AbstractModMetadata; +import net.fabricmc.loader.impl.metadata.DependencyOverrides; import net.fabricmc.loader.impl.metadata.LoaderModMetadata; +import net.fabricmc.loader.impl.metadata.VersionOverrides; public final class ModCandidate implements DomainObject.Mod { static final Comparator ID_VERSION_COMPARATOR = new Comparator() { @@ -60,8 +62,12 @@ public int compare(ModCandidate a, ModCandidate b) { private int minNestLevel; private SoftReference dataRef; - static ModCandidate createBuiltin(BuiltinMod mod) { - return new ModCandidate(mod.paths, null, -1, new BuiltinMetadataWrapper(mod.metadata), false, Collections.emptyList()); + static ModCandidate createBuiltin(BuiltinMod mod, VersionOverrides versionOverrides, DependencyOverrides depOverrides) { + LoaderModMetadata metadata = new BuiltinMetadataWrapper(mod.metadata); + versionOverrides.apply(metadata); + depOverrides.apply(metadata); + + return new ModCandidate(mod.paths, null, -1, metadata, false, Collections.emptyList()); } static ModCandidate createPlain(List paths, LoaderModMetadata metadata, boolean requiresRemap, Collection nestedMods) { diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java index 6e3d93239..c8ede4f87 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java @@ -46,26 +46,37 @@ import java.util.zip.ZipInputStream; import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.metadata.ModMetadata; import net.fabricmc.loader.impl.FabricLoaderImpl; import net.fabricmc.loader.impl.FormattedException; import net.fabricmc.loader.impl.discovery.ModCandidateFinder.ModCandidateConsumer; import net.fabricmc.loader.impl.game.GameProvider.BuiltinMod; import net.fabricmc.loader.impl.metadata.BuiltinModMetadata; +import net.fabricmc.loader.impl.metadata.DependencyOverrides; import net.fabricmc.loader.impl.metadata.LoaderModMetadata; +import net.fabricmc.loader.impl.metadata.MetadataVerifier; import net.fabricmc.loader.impl.metadata.ModMetadataParser; import net.fabricmc.loader.impl.metadata.NestedJarEntry; import net.fabricmc.loader.impl.metadata.ParseMetadataException; +import net.fabricmc.loader.impl.metadata.VersionOverrides; import net.fabricmc.loader.impl.util.ExceptionUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; public final class ModDiscoverer { + private final VersionOverrides versionOverrides; + private final DependencyOverrides depOverrides; private final List candidateFinders = new ArrayList<>(); private final EnvType envType = FabricLoaderImpl.INSTANCE.getEnvironmentType(); private final Map jijDedupMap = new ConcurrentHashMap<>(); // avoids reading the same jar twice private final List nestedModInitDatas = Collections.synchronizedList(new ArrayList<>()); // breaks potential cycles from deduplication + public ModDiscoverer(VersionOverrides versionOverrides, DependencyOverrides depOverrides) { + this.versionOverrides = versionOverrides; + this.depOverrides = depOverrides; + } + public void addCandidateFinder(ModCandidateFinder f) { candidateFinders.add(f); } @@ -105,15 +116,12 @@ public List discoverMods(FabricLoaderImpl loader, Map discoverMods(FabricLoaderImpl loader, Map(ret); } + private ModCandidate createJavaMod() { + ModMetadata metadata = new BuiltinModMetadata.Builder("java", System.getProperty("java.specification.version").replaceFirst("^1\\.", "")) + .setName(System.getProperty("java.vm.name")) + .build(); + BuiltinMod builtinMod = new BuiltinMod(Collections.singletonList(Paths.get(System.getProperty("java.home"))), metadata); + + return ModCandidate.createBuiltin(builtinMod, versionOverrides, depOverrides); + } + @SuppressWarnings("serial") final class ModScanTask extends RecursiveTask { private final List paths; @@ -247,7 +264,7 @@ private ModCandidate computeDir() throws IOException, ParseMetadataException { LoaderModMetadata metadata; try (InputStream is = Files.newInputStream(modJson)) { - metadata = ModMetadataParser.parseMetadata(is, path.toString(), parentPaths); + metadata = parseMetadata(is, path.toString()); } return ModCandidate.createPlain(paths, metadata, requiresRemap, Collections.emptyList()); @@ -266,7 +283,7 @@ private ModCandidate computeJarFile() throws IOException, ParseMetadataException LoaderModMetadata metadata; try (InputStream is = zf.getInputStream(entry)) { - metadata = ModMetadataParser.parseMetadata(is, localPath, parentPaths); + metadata = parseMetadata(is, localPath); } if (!metadata.loadsInEnvironment(envType)) { @@ -334,7 +351,7 @@ private ModCandidate computeJarStream() throws IOException, ParseMetadataExcepti try (ZipInputStream zis = new ZipInputStream(is)) { while ((entry = zis.getNextEntry()) != null) { if (entry.getName().equals("fabric.mod.json")) { - metadata = ModMetadataParser.parseMetadata(zis, localPath, parentPaths); + metadata = parseMetadata(zis, localPath); break; } } @@ -442,6 +459,10 @@ private List computeNestedMods(ZipEntrySource entrySource) throws I return tasks; } + + private LoaderModMetadata parseMetadata(InputStream is, String localPath) throws ParseMetadataException { + return ModMetadataParser.parseMetadata(is, localPath, parentPaths, versionOverrides, depOverrides); + } } private static boolean isValidNestedJarEntry(ZipEntry entry) { diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java b/src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java index 7559fbe42..b966f0487 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java @@ -480,7 +480,7 @@ private static boolean isWildcard(VersionInterval interval, int incrementedCompo return false; } - for (int i = incrementedComponent + 1; i < 3; i++) { + for (int i = incrementedComponent + 1, m = Math.max(min.getVersionComponentCount(), max.getVersionComponentCount()); i < m; i++) { // all following components need to be 0 if (min.getVersionComponent(i) != 0 || max.getVersionComponent(i) != 0) { return false; diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/DependencyOverrides.java b/src/main/java/net/fabricmc/loader/impl/metadata/DependencyOverrides.java index 6334cea1e..01c5c4752 100644 --- a/src/main/java/net/fabricmc/loader/impl/metadata/DependencyOverrides.java +++ b/src/main/java/net/fabricmc/loader/impl/metadata/DependencyOverrides.java @@ -35,7 +35,6 @@ import net.fabricmc.loader.api.VersionParsingException; import net.fabricmc.loader.api.metadata.ModDependency; import net.fabricmc.loader.impl.FormattedException; -import net.fabricmc.loader.impl.discovery.ModCandidate; import net.fabricmc.loader.impl.lib.gson.JsonReader; import net.fabricmc.loader.impl.lib.gson.JsonToken; @@ -202,15 +201,9 @@ private static List readDependencies(JsonReader reader, ModDepend return ret; } - public void apply(Collection mods) { + public void apply(LoaderModMetadata metadata) { if (dependencyOverrides.isEmpty()) return; - for (ModCandidate mod : mods) { - apply(mod.getMetadata()); - } - } - - private void apply(LoaderModMetadata metadata) { List modOverrides = dependencyOverrides.get(metadata.getId()); if (modOverrides == null) return; diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java b/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java new file mode 100644 index 000000000..3ab34e75f --- /dev/null +++ b/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java @@ -0,0 +1,150 @@ +/* + * Copyright 2016 FabricMC + * + * 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 net.fabricmc.loader.impl.metadata; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.SemanticVersion; +import net.fabricmc.loader.api.VersionParsingException; +import net.fabricmc.loader.impl.FabricLoaderImpl; +import net.fabricmc.loader.impl.discovery.ModCandidate; +import net.fabricmc.loader.impl.util.log.Log; +import net.fabricmc.loader.impl.util.log.LogCategory; + +public final class MetadataVerifier { + private static final Pattern MOD_ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{1,63}"); + + public static ModCandidate verifyIndev(ModCandidate mod) { + if (FabricLoaderImpl.INSTANCE.isDevelopmentEnvironment()) { + try { + MetadataVerifier.verify(mod.getMetadata()); + } catch (ParseMetadataException e) { + e.setModPaths(mod.getLocalPath(), Collections.emptyList()); + throw new RuntimeException("Invalid mod metadata", e); + } + } + + return mod; + } + + static void verify(LoaderModMetadata metadata) throws ParseMetadataException { + checkModId(metadata.getId(), "mod id"); + + for (String providesDecl : metadata.getProvides()) { + checkModId(providesDecl, "provides declaration"); + } + + // TODO: verify mod id and version decls in deps + + if (FabricLoader.getInstance().isDevelopmentEnvironment()) { + if (metadata.getSchemaVersion() < ModMetadataParser.LATEST_VERSION) { + Log.warn(LogCategory.METADATA, "Mod %s uses an outdated schema version: %d < %d", metadata.getId(), metadata.getSchemaVersion(), ModMetadataParser.LATEST_VERSION); + } + + if (!(metadata.getVersion() instanceof SemanticVersion)) { + String version = metadata.getVersion().getFriendlyString(); + VersionParsingException exc; + + try { + SemanticVersion.parse(version); + exc = null; + } catch (VersionParsingException e) { + exc = e; + } + + if (exc != null) { + Log.warn(LogCategory.METADATA, "Mod %s uses the version %s which isn't compatible with Loader's extended semantic version format (%s), SemVer is recommended for reliable dependency comparisons", + metadata.getId(), version, exc.getMessage()); + } + } + + metadata.emitFormatWarnings(); + } + } + + private static void checkModId(String id, String name) throws ParseMetadataException { + if (MOD_ID_PATTERN.matcher(id).matches()) return; + + List errorList = new ArrayList<>(); + + // A more useful error list for MOD_ID_PATTERN + if (id.isEmpty()) { + errorList.add("is empty!"); + } else { + if (id.length() == 1) { + errorList.add("is only a single character! (It must be at least 2 characters long)!"); + } else if (id.length() > 64) { + errorList.add("has more than 64 characters!"); + } + + char first = id.charAt(0); + + if (first < 'a' || first > 'z') { + errorList.add("starts with an invalid character '" + first + "' (it must be a lowercase a-z - uppercase isn't allowed anywhere in the ID)"); + } + + Set invalidChars = null; + + for (int i = 1; i < id.length(); i++) { + char c = id.charAt(i); + + if (c == '-' || c == '_' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z')) { + continue; + } + + if (invalidChars == null) { + invalidChars = new HashSet<>(); + } + + invalidChars.add(c); + } + + if (invalidChars != null) { + StringBuilder error = new StringBuilder("contains invalid characters: "); + error.append(invalidChars.stream().map(value -> "'" + value + "'").collect(Collectors.joining(", "))); + errorList.add(error.append("!").toString()); + } + } + + assert !errorList.isEmpty(); + + StringWriter sw = new StringWriter(); + + try (PrintWriter pw = new PrintWriter(sw)) { + pw.printf("Invalid %s %s:", name, id); + + if (errorList.size() == 1) { + pw.printf(" It %s", errorList.get(0)); + } else { + for (String error : errorList) { + pw.printf("\n\t- It %s", error); + } + } + } + + throw new ParseMetadataException(sw.toString()); + } +} diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java b/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java index 59bcc228d..9cb2ef49f 100644 --- a/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java +++ b/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java @@ -19,16 +19,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.io.StringWriter; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.regex.Pattern; -import java.util.stream.Collectors; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.impl.lib.gson.JsonReader; @@ -43,29 +37,17 @@ public final class ModMetadataParser { */ public static final Set IGNORED_KEYS = Collections.singleton("$schema"); - private static final Pattern MOD_ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{1,63}"); - // Per the ECMA-404 (www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf), the JSON spec does not prohibit duplicate keys. // For all intents and purposes of replicating the logic of Gson's fromJson before we have migrated to JsonReader, duplicate keys will replace previous entries. - public static LoaderModMetadata parseMetadata(InputStream is, String modPath, List modParentPaths) throws ParseMetadataException { + public static LoaderModMetadata parseMetadata(InputStream is, String modPath, List modParentPaths, + VersionOverrides versionOverrides, DependencyOverrides depOverrides) throws ParseMetadataException { try { LoaderModMetadata ret = readModMetadata(is); - checkModId(ret.getId(), "mod id"); - - for (String providesDecl : ret.getProvides()) { - checkModId(providesDecl, "provides declaration"); - } + versionOverrides.apply(ret); + depOverrides.apply(ret); - // TODO: verify mod id decls in deps - - if (FabricLoader.getInstance().isDevelopmentEnvironment()) { - if (ret.getSchemaVersion() < LATEST_VERSION) { - Log.warn(LogCategory.METADATA, "Mod ID %s uses outdated schema version: %d < %d", ret.getId(), ret.getSchemaVersion(), ModMetadataParser.LATEST_VERSION); - } - - ret.emitFormatWarnings(); - } + MetadataVerifier.verify(ret); return ret; } catch (ParseMetadataException e) { @@ -170,69 +152,6 @@ private static LoaderModMetadata readModMetadata(JsonReader reader, int schemaVe } } - private static void checkModId(String id, String name) throws ParseMetadataException { - if (MOD_ID_PATTERN.matcher(id).matches()) return; - - List errorList = new ArrayList<>(); - - // A more useful error list for MOD_ID_PATTERN - if (id.isEmpty()) { - errorList.add("is empty!"); - } else { - if (id.length() == 1) { - errorList.add("is only a single character! (It must be at least 2 characters long)!"); - } else if (id.length() > 64) { - errorList.add("has more than 64 characters!"); - } - - char first = id.charAt(0); - - if (first < 'a' || first > 'z') { - errorList.add("starts with an invalid character '" + first + "' (it must be a lowercase a-z - uppercase isn't allowed anywhere in the ID)"); - } - - Set invalidChars = null; - - for (int i = 1; i < id.length(); i++) { - char c = id.charAt(i); - - if (c == '-' || c == '_' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z')) { - continue; - } - - if (invalidChars == null) { - invalidChars = new HashSet<>(); - } - - invalidChars.add(c); - } - - if (invalidChars != null) { - StringBuilder error = new StringBuilder("contains invalid characters: "); - error.append(invalidChars.stream().map(value -> "'" + value + "'").collect(Collectors.joining(", "))); - errorList.add(error.append("!").toString()); - } - } - - assert !errorList.isEmpty(); - - StringWriter sw = new StringWriter(); - - try (PrintWriter pw = new PrintWriter(sw)) { - pw.printf("Invalid %s %s:", name, id); - - if (errorList.size() == 1) { - pw.printf(" It %s", errorList.get(0)); - } else { - for (String error : errorList) { - pw.printf("\n\t- It %s", error); - } - } - } - - throw new ParseMetadataException(sw.toString()); - } - static void logWarningMessages(String id, List warnings) { if (warnings.isEmpty()) return; diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/ParseMetadataException.java b/src/main/java/net/fabricmc/loader/impl/metadata/ParseMetadataException.java index 67653b7de..e4d114abb 100644 --- a/src/main/java/net/fabricmc/loader/impl/metadata/ParseMetadataException.java +++ b/src/main/java/net/fabricmc/loader/impl/metadata/ParseMetadataException.java @@ -41,7 +41,7 @@ public ParseMetadataException(Throwable t) { super(t); } - void setModPaths(String modPath, List modParentPaths) { + public void setModPaths(String modPath, List modParentPaths) { modPaths = new ArrayList<>(modParentPaths); modPaths.add(modPath); } diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/VersionOverrides.java b/src/main/java/net/fabricmc/loader/impl/metadata/VersionOverrides.java index ad796ec76..1651398e6 100644 --- a/src/main/java/net/fabricmc/loader/impl/metadata/VersionOverrides.java +++ b/src/main/java/net/fabricmc/loader/impl/metadata/VersionOverrides.java @@ -18,12 +18,10 @@ import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; import net.fabricmc.loader.api.Version; import net.fabricmc.loader.api.VersionParsingException; -import net.fabricmc.loader.impl.discovery.ModCandidate; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.version.VersionParser; @@ -52,15 +50,13 @@ public VersionOverrides() { } } - public void apply(List mods) { + public void apply(LoaderModMetadata metadata) { if (replacements.isEmpty()) return; - for (ModCandidate mod : mods) { - Version replacement = replacements.get(mod.getId()); + Version replacement = replacements.get(metadata.getId()); - if (replacement != null) { - mod.getMetadata().setVersion(replacement); - } + if (replacement != null) { + metadata.setVersion(replacement); } } diff --git a/src/main/java/net/fabricmc/loader/impl/util/version/SemanticVersionImpl.java b/src/main/java/net/fabricmc/loader/impl/util/version/SemanticVersionImpl.java index d1ee2d850..048c450af 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/version/SemanticVersionImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/util/version/SemanticVersionImpl.java @@ -26,6 +26,15 @@ import net.fabricmc.loader.api.Version; import net.fabricmc.loader.api.VersionParsingException; +/** + * Parser for a superset of the semantic version format described at semver.org. + * + *

This superset allows additionally + *

  • Arbitrary number of {@code } components, but at least 1 + *
  • {@code x}, {@code X} or {@code *} for the last {@code } component with {@code storeX} if not the first + *
  • Arbitrary {@code } contents + *
+ */ @SuppressWarnings("deprecation") public class SemanticVersionImpl extends net.fabricmc.loader.util.version.SemanticVersionImpl implements SemanticVersion { private static final Pattern DOT_SEPARATED_ID = Pattern.compile("|[-0-9A-Za-z]+(\\.[-0-9A-Za-z]+)*"); @@ -58,10 +67,6 @@ public SemanticVersionImpl(String version, boolean storeX) throws VersionParsing throw new VersionParsingException("Invalid prerelease string '" + prerelease + "'!"); } - if (build != null && !DOT_SEPARATED_ID.matcher(build).matches()) { - throw new VersionParsingException("Invalid build string '" + build + "'!"); - } - if (version.endsWith(".")) { throw new VersionParsingException("Negative version number component found!"); } else if (version.startsWith(".")) { diff --git a/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java b/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java index 6d568f6c4..fd520dab8 100644 --- a/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java +++ b/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java @@ -26,6 +26,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collections; import java.util.Map; @@ -36,9 +37,11 @@ import net.fabricmc.loader.api.SemanticVersion; import net.fabricmc.loader.api.metadata.CustomValue; +import net.fabricmc.loader.impl.metadata.DependencyOverrides; import net.fabricmc.loader.impl.metadata.LoaderModMetadata; import net.fabricmc.loader.impl.metadata.ModMetadataParser; import net.fabricmc.loader.impl.metadata.ParseMetadataException; +import net.fabricmc.loader.impl.metadata.VersionOverrides; @Disabled // TODO needs fixing. final class V1ModJsonParsingTests { @@ -185,7 +188,7 @@ public void testWarnings() { } private static LoaderModMetadata parseMetadata(Path path) throws IOException, ParseMetadataException { try (InputStream is = Files.newInputStream(path)) { - return ModMetadataParser.parseMetadata(null, "dummy", Collections.emptyList()); + return ModMetadataParser.parseMetadata(null, "dummy", Collections.emptyList(), new VersionOverrides(), new DependencyOverrides(Paths.get("randomMissing"))); } } } From 8f9e772a6181f90211cbe4f9e41a6847f289f572 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Tue, 22 Mar 2022 22:24:05 +0000 Subject: [PATCH 04/80] Jar and PGP sign maven artifacts. (#625) --- .github/workflows/release.yml | 5 ++++- build.gradle | 27 +++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b0e606a6..accce31a5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,4 +27,7 @@ jobs: MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} CHANGELOG: ${{ steps.changelog.outputs.changelog }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SIGNING_SERVER: ${{ secrets.SIGNING_SERVER }} + SIGNING_PGP_KEY: ${{ secrets.SIGNING_PGP_KEY }} + SIGNING_JAR_KEY: ${{ secrets.SIGNING_JAR_KEY }} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 201b50688..f3ee237d7 100644 --- a/build.gradle +++ b/build.gradle @@ -14,6 +14,7 @@ plugins { id 'org.cadixdev.licenser' version '0.6.1' id 'fabric-loom' version '0.11-SNAPSHOT' apply false id 'com.github.johnrengelman.shadow' version '7.1.2' + id "me.modmuss50.remotesign" version "0.2.4" } sourceCompatibility = JavaVersion.VERSION_1_8 @@ -274,12 +275,34 @@ tasks.withType(GenerateModuleMetadata) { enabled = false } +def signingEnabled = ENV.SIGNING_SERVER +File proguardFileSigned = null + +if (signingEnabled) { + proguardFileSigned = file("build/libs/fabric-loader-${version}-signed.jar") + + remoteSign { + requestUrl = ENV.SIGNING_SERVER + pgpAuthKey = ENV.SIGNING_PGP_KEY + jarAuthKey = ENV.SIGNING_JAR_KEY + + sign(proguardFile, proguardFileSigned, "proguard").configure { + dependsOn proguardJar + } + + afterEvaluate { + sign publishing.publications.mavenJava + } + } +} + publishing { publications { mavenJava(MavenPublication) { // add all the jars that should be included when publishing to maven - artifact(proguardFile) { - builtBy proguardJar + artifact(signingEnabled ? proguardFileSigned : proguardFile) { + builtBy(signingEnabled ? signProguard : proguardJar) + classifier = null } artifact(sourcesJar) artifact javadocJar From ec63637e969988f05f051f217dae1c997478fb26 Mon Sep 17 00:00:00 2001 From: js6pak Date: Sun, 27 Mar 2022 21:10:18 +0200 Subject: [PATCH 05/80] Add support for pre 1.6 arguments (cherry picked from commit afa674c831fb27ebd364db5f9a72506f3a97d9ca) --- .../loader/impl/game/minecraft/applet/AppletFrame.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/applet/AppletFrame.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/applet/AppletFrame.java index 26ffc4f81..4169a110f 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/applet/AppletFrame.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/applet/AppletFrame.java @@ -73,6 +73,9 @@ public void launch(String[] args) { if (arguments.containsKey("session") /* 1.6 */) { sessionid = arguments.get("session"); + } else if (arguments.getExtraArgs().size() == 2 /* pre 1.6 */) { + username = arguments.getExtraArgs().get(0); + sessionid = arguments.getExtraArgs().get(1); } else /* fallback */ { sessionid = ""; } From 29b38f730e03fbb726113aea09cd58492ec78429 Mon Sep 17 00:00:00 2001 From: js6pak Date: Tue, 29 Mar 2022 03:55:09 +0200 Subject: [PATCH 06/80] Fix old server version parsing (#633) --- .../impl/game/minecraft/McVersionLookup.java | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java index aab79fb91..cd2f610d8 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java @@ -64,7 +64,7 @@ public final class McVersionLookup { private static final Pattern SNAPSHOT_PATTERN = Pattern.compile("(?:Snapshot )?(\\d+)w0?(0|[1-9]\\d*)([a-z])"); private static final Pattern EXPERIMENTAL_PATTERN = Pattern.compile("(?:.*[Ee]xperimental [Ss]napshot )(\\d+)"); private static final Pattern BETA_PATTERN = Pattern.compile("(?:b|Beta v?)1\\.(\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?)"); - private static final Pattern ALPHA_PATTERN = Pattern.compile("(?:a|Alpha v?)1\\.(\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?)"); + private static final Pattern ALPHA_PATTERN = Pattern.compile("(?:a|Alpha v?)[01]\\.(\\d+(\\.\\d+)?[a-z]?(_\\d+)?[a-z]?)"); private static final Pattern INDEV_PATTERN = Pattern.compile("(?:inf-|Inf?dev )(?:0\\.31 )?(\\d+(-\\d+)?)"); private static final String STRING_DESC = "Ljava/lang/String;"; @@ -732,6 +732,8 @@ protected void visitAnyInsn() { } private static final class MethodConstantVisitor extends ClassVisitor implements Analyzer { + private static final String STARTING_MESSAGE = "Starting minecraft server version "; + MethodConstantVisitor(String methodNameHint) { super(FabricLoaderImpl.ASM_VERSION); @@ -754,13 +756,29 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str return new MethodVisitor(FabricLoaderImpl.ASM_VERSION) { @Override public void visitLdcInsn(Object value) { - String str; + if ((result == null || !foundInMethodHint && isRequestedMethod) && value instanceof String) { + String str = (String) value; + + // a0.1.0 - 1.2.5 have a startup message including the version, extract it from there + // Examples: + // release 1.0.0 - Starting minecraft server version 1.0.0 + // beta 1.7.3 - Starting minecraft server version Beta 1.7.3 + // alpha 0.2.8 - Starting minecraft server version 0.2.8 + if (str.startsWith(STARTING_MESSAGE)) { + str = str.substring(STARTING_MESSAGE.length()); + + // Alpha servers don't have any prefix, but they all have 0 as the major + if (!str.startsWith("Beta") && str.startsWith("0.")) { + str = "Alpha " + str; + } + } - if ((result == null || !foundInMethodHint && isRequestedMethod) - && value instanceof String - && isProbableVersion(str = (String) value)) { - result = str; - foundInMethodHint = isRequestedMethod; + // 1.0.0 - 1.13.2 have an obfuscated method that just returns the version, so we can use that + + if (isProbableVersion(str)) { + result = str; + foundInMethodHint = isRequestedMethod; + } } } }; From ccacc836e96887c534e26731eba6bd04bc358a11 Mon Sep 17 00:00:00 2001 From: Cat Core <34719527+arthurbambou@users.noreply.github.com> Date: Tue, 29 Mar 2022 17:46:43 +0200 Subject: [PATCH 07/80] Parse mc 2.0 versions correctly (#634) --- .../loader/impl/game/minecraft/McVersionLookup.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java index cd2f610d8..2033aaa36 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java @@ -538,6 +538,18 @@ private static String normalizeSpecialVersion(String version) { // The ninth Combat Test 8c, forked from 1.16.2 return "1.16.3-combat.8.c"; + case "2point0_red": + // 2.0 update version red, forked from 1.5.1 + return "1.5.2-red"; + + case "2point0_purple": + // 2.0 update version purple, forked from 1.5.1 + return "1.5.2-purple"; + + case "2point0_blue": + // 2.0 update version blue, forked from 1.5.1 + return "1.5.2-blue"; + default: return null; //Don't recognise the version } From 36ed420f8d5d9b65653e1a88a424724811a3dc53 Mon Sep 17 00:00:00 2001 From: Player Date: Sat, 2 Apr 2022 18:42:25 +0000 Subject: [PATCH 08/80] Improve class loader isolation (#630) * Refactor LibClassifier to be usable by other game providers * Multi-gameJar and full CL transfer prototype * Ignore parent CL accesses to URLs in the Knot CL, refactor class loading * Address review comments * Prevent bogus parent CL delegation cases, add missing logging related libs * Swap QueueLogAppender for UUIDTypeAdapter in log4j plugin lib detection, the former is no longer in use * Update Mixin, use its direct service specification * Optimize Loader library handling --- gradle.properties | 2 +- .../impl/game/minecraft/BundlerProcessor.java | 11 +- .../impl/game/minecraft/LibClassifier.java | 181 ------------- .../loader/impl/game/minecraft/McLibrary.java | 67 +++++ .../impl/game/minecraft/McVersionLookup.java | 53 ++-- .../game/minecraft/MinecraftGameProvider.java | 162 +++++++----- .../launchwrapper/FabricTweaker.java | 6 + .../loader/impl/FabricLoaderImpl.java | 6 +- .../ClasspathModCandidateFinder.java | 11 +- .../loader/impl/game/GameProviderHelper.java | 21 ++ .../loader/impl/game/LibClassifier.java | 205 ++++++++++++++ .../loader/impl/game/LoaderLibrary.java | 60 +++++ .../impl/game/patch/GameTransformer.java | 27 +- .../loader/impl/gui/FabricGuiEntry.java | 7 +- .../loader/impl/launch/FabricLauncher.java | 2 + .../impl/launch/FabricLauncherBase.java | 12 +- .../impl/launch/FabricMixinBootstrap.java | 9 +- .../loader/impl/launch/knot/Knot.java | 52 ++-- .../impl/launch/knot/KnotClassDelegate.java | 249 +++++++++++++++--- .../impl/launch/knot/KnotClassLoader.java | 87 ++---- .../launch/knot/KnotClassLoaderInterface.java | 35 ++- .../knot/KnotCompatibilityClassLoader.java | 87 ++---- .../impl/launch/knot/MixinServiceKnot.java | 8 +- .../loader/impl/util/SimpleClassPath.java | 143 ++++++++++ .../loader/impl/util/SystemProperties.java | 7 + .../fabricmc/loader/impl/util/UrlUtil.java | 14 +- src/main/resources/fabric-installer.json | 2 +- .../fabric-installer.launchwrapper.json | 2 +- 28 files changed, 1025 insertions(+), 503 deletions(-) delete mode 100644 minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/LibClassifier.java create mode 100644 minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McLibrary.java create mode 100644 src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java create mode 100644 src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java create mode 100644 src/main/java/net/fabricmc/loader/impl/util/SimpleClassPath.java diff --git a/gradle.properties b/gradle.properties index 55cf747de..27046f70c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ description = The mod loading component of Fabric url = https://github.com/FabricMC/fabric-loader asm_version = 9.2 -mixin_version = 0.11.2+mixin.0.8.5 +mixin_version = 0.11.3+mixin.0.8.5 diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/BundlerProcessor.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/BundlerProcessor.java index 243b5e06c..41c115845 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/BundlerProcessor.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/BundlerProcessor.java @@ -25,15 +25,14 @@ import java.util.Arrays; import java.util.concurrent.TimeUnit; -import net.fabricmc.api.EnvType; -import net.fabricmc.loader.impl.game.minecraft.LibClassifier.Lib; +import net.fabricmc.loader.impl.game.LibClassifier; import net.fabricmc.loader.impl.util.LoaderUtil; final class BundlerProcessor { private static final String MAIN_CLASS_PROPERTY = "bundlerMainClass"; - static void process(LibClassifier classifier) throws IOException { - Path bundlerOrigin = classifier.getOrigin(Lib.MC_BUNDLER); + static void process(LibClassifier classifier) throws IOException { + Path bundlerOrigin = classifier.getOrigin(McLibrary.MC_BUNDLER); // determine urls by running the bundler and extracting them from the context class loader @@ -83,7 +82,7 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE } } }) { - Class cls = Class.forName(classifier.getClassName(Lib.MC_BUNDLER), true, bundlerCl); + Class cls = Class.forName(classifier.getClassName(McLibrary.MC_BUNDLER), true, bundlerCl); Method method = cls.getMethod("main", String[].class); // save + restore the system property and context class loader just in case @@ -118,7 +117,7 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE classifier.remove(bundlerOrigin); for (URL url : urls) { - classifier.process(url, EnvType.SERVER); + classifier.process(url); } } } diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/LibClassifier.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/LibClassifier.java deleted file mode 100644 index 37c2eddc7..000000000 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/LibClassifier.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2016 FabricMC - * - * 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 net.fabricmc.loader.impl.game.minecraft; - -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.EnumMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipEntry; -import java.util.zip.ZipError; -import java.util.zip.ZipFile; - -import net.fabricmc.api.EnvType; -import net.fabricmc.loader.impl.util.UrlUtil; - -final class LibClassifier { - private final Map matches = new EnumMap<>(Lib.class); - private final Map localPaths = new EnumMap<>(Lib.class); - private final List unmatchedOrigins = new ArrayList<>(); - - public void process(URL url, EnvType env) throws IOException { - try { - process(UrlUtil.asPath(url), env); - } catch (URISyntaxException e) { - throw new RuntimeException("invalid url: "+url); - } - } - - public void process(Iterable paths, EnvType env) throws IOException { - for (Path path : paths) { - process(path, env); - } - } - - public void process(Path path, EnvType env) throws IOException { - boolean matched = false; - - if (Files.isDirectory(path)) { - for (Lib lib : Lib.VALUES) { - if (!lib.isInEnv(env) || matches.containsKey(lib)) continue; - - for (String p : lib.paths) { - if (Files.exists(path.resolve(p))) { - matched = true; - matches.put(lib, path); - localPaths.put(lib, p); - break; - } - } - } - } else { - try (ZipFile zf = new ZipFile(path.toFile())) { - for (Lib lib : Lib.VALUES) { - if (!lib.isInEnv(env) || matches.containsKey(lib)) continue; - - for (String p : lib.paths) { - ZipEntry entry = zf.getEntry(p); - - if (entry != null) { - matched = true; - matches.put(lib, path); - localPaths.put(lib, p); - break; - } - } - } - } catch (ZipError | IOException e) { - throw new IOException("error reading "+path.toAbsolutePath(), e); - } - } - - if (!matched) unmatchedOrigins.add(path); - } - - public boolean is(Path path, Lib... libs) { - for (Lib lib : libs) { - if (path.equals(matches.get(lib))) return true; - } - - return false; - } - - public boolean has(Lib lib) { - return matches.containsKey(lib); - } - - public Path getOrigin(Lib lib) { - return matches.get(lib); - } - - public String getLocalPath(Lib lib) { - return localPaths.get(lib); - } - - public String getClassName(Lib lib) { - String localPath = localPaths.get(lib); - if (localPath == null || !localPath.endsWith(".class")) return null; - - return localPath.substring(0, localPath.length() - 6).replace('/', '.'); - } - - public List getUnmatchedOrigins() { - return unmatchedOrigins; - } - - public boolean remove(Path path) { - if (unmatchedOrigins.remove(path)) return true; - - boolean ret = false; - - for (Iterator> it = matches.entrySet().iterator(); it.hasNext(); ) { - Map.Entry entry = it.next(); - - if (entry.getValue().equals(path)) { - localPaths.remove(entry.getKey()); - it.remove(); - - ret = true; - } - } - - return ret; - } - - enum Lib { - MC_CLIENT(EnvType.CLIENT, "net/minecraft/client/main/Main.class", "net/minecraft/client/MinecraftApplet.class", "com/mojang/minecraft/MinecraftApplet.class"), - MC_SERVER(EnvType.SERVER, "net/minecraft/server/Main.class", "net/minecraft/server/MinecraftServer.class", "com/mojang/minecraft/server/MinecraftServer.class"), - MC_BUNDLER(EnvType.SERVER, "net/minecraft/bundler/Main.class"), - REALMS(EnvType.CLIENT, "realmsVersion"), - MODLOADER("ModLoader"), - LOG4J_API("org/apache/logging/log4j/LogManager.class"), - LOG4J_CORE("META-INF/services/org.apache.logging.log4j.spi.Provider", "META-INF/log4j-provider.properties"), - LOG4J_CONFIG("log4j2.xml"), - LOG4J_PLUGIN("com/mojang/util/QueueLogAppender.class"), - SLF4J_API("org/slf4j/Logger.class"), - SLF4J_CORE("META-INF/services/org.slf4j.spi.SLF4JServiceProvider"); - - static final Lib[] VALUES = values(); - static final Lib[] LOGGING = { LOG4J_API, LOG4J_CORE, LOG4J_CONFIG, LOG4J_PLUGIN, SLF4J_API, SLF4J_CORE }; - - final String[] paths; - final EnvType env; - - Lib(String path) { - this(null, new String[] { path }); - } - - Lib(String... paths) { - this(null, paths); - } - - Lib(EnvType env, String... paths) { - this.paths = paths; - this.env = env; - } - - boolean isInEnv(EnvType env) { - return this.env == null || this.env == env; - } - } -} diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McLibrary.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McLibrary.java new file mode 100644 index 000000000..b88710cf7 --- /dev/null +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McLibrary.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016 FabricMC + * + * 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 net.fabricmc.loader.impl.game.minecraft; + +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.impl.game.LibClassifier.LibraryType; + +enum McLibrary implements LibraryType { + MC_CLIENT(EnvType.CLIENT, "net/minecraft/client/main/Main.class", "net/minecraft/client/MinecraftApplet.class", "com/mojang/minecraft/MinecraftApplet.class"), + MC_SERVER(EnvType.SERVER, "net/minecraft/server/Main.class", "net/minecraft/server/MinecraftServer.class", "com/mojang/minecraft/server/MinecraftServer.class"), + MC_COMMON("net/minecraft/server/MinecraftServer.class"), + MC_BUNDLER(EnvType.SERVER, "net/minecraft/bundler/Main.class"), + REALMS(EnvType.CLIENT, "realmsVersion"), + MODLOADER("ModLoader"), + LOG4J_API("org/apache/logging/log4j/LogManager.class"), + LOG4J_CORE("META-INF/services/org.apache.logging.log4j.spi.Provider", "META-INF/log4j-provider.properties"), + LOG4J_CONFIG("log4j2.xml"), + LOG4J_PLUGIN("com/mojang/util/UUIDTypeAdapter.class"), // in authlib + LOG4J_PLUGIN_2("com/mojang/patchy/LegacyXMLLayout.class"), // in patchy + LOG4J_PLUGIN_3("net/minecrell/terminalconsole/util/LoggerNamePatternSelector.class"), // in terminalconsoleappender, used by loom's log4j config + GSON("com/google/gson/TypeAdapter.class"), // used by log4j plugins + SLF4J_API("org/slf4j/Logger.class"), + SLF4J_CORE("META-INF/services/org.slf4j.spi.SLF4JServiceProvider"); + + static final McLibrary[] GAME = { MC_CLIENT, MC_SERVER, MC_BUNDLER }; + static final McLibrary[] LOGGING = { LOG4J_API, LOG4J_CORE, LOG4J_CONFIG, LOG4J_PLUGIN, LOG4J_PLUGIN_2, LOG4J_PLUGIN_3, GSON, SLF4J_API, SLF4J_CORE }; + + private final EnvType env; + private final String[] paths; + + McLibrary(String path) { + this(null, new String[] { path }); + } + + McLibrary(String... paths) { + this(null, paths); + } + + McLibrary(EnvType env, String... paths) { + this.paths = paths; + this.env = env; + } + + @Override + public boolean isApplicable(EnvType env) { + return this.env == null || this.env == env; + } + + @Override + public String[] getPaths() { + return paths; + } +} diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java index 2033aaa36..2f4fb40e4 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java @@ -21,9 +21,9 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,8 +40,9 @@ import net.fabricmc.loader.impl.lib.gson.JsonReader; import net.fabricmc.loader.impl.lib.gson.JsonToken; import net.fabricmc.loader.impl.util.ExceptionUtil; -import net.fabricmc.loader.impl.util.FileSystemUtil; import net.fabricmc.loader.impl.util.LoaderUtil; +import net.fabricmc.loader.impl.util.SimpleClassPath; +import net.fabricmc.loader.impl.util.SimpleClassPath.CpEntry; import net.fabricmc.loader.impl.util.version.SemanticVersionImpl; import net.fabricmc.loader.impl.util.version.VersionPredicateParser; @@ -68,33 +69,29 @@ public final class McVersionLookup { private static final Pattern INDEV_PATTERN = Pattern.compile("(?:inf-|Inf?dev )(?:0\\.31 )?(\\d+(-\\d+)?)"); private static final String STRING_DESC = "Ljava/lang/String;"; - public static McVersion getVersion(Path gameJar, String entrypointClass, String versionName) { + public static McVersion getVersion(List gameJars, String entrypointClass, String versionName) { McVersion.Builder builder = new McVersion.Builder(); if (versionName != null) { builder.setNameAndRelease(versionName); } - try (FileSystemUtil.FileSystemDelegate jarFs = FileSystemUtil.getJarFileSystem(gameJar, false)) { - FileSystem fs = jarFs.get(); - + try (SimpleClassPath cp = new SimpleClassPath(gameJars)) { // Determine class version if (entrypointClass != null) { - Path file = fs.getPath(LoaderUtil.getClassFileName(entrypointClass)); + try (InputStream is = cp.getInputStream(LoaderUtil.getClassFileName(entrypointClass))) { + DataInputStream dis = new DataInputStream(is); - if (Files.isRegularFile(file)) { - try (DataInputStream is = new DataInputStream(Files.newInputStream(file))) { - if (is.readInt() == 0xCAFEBABE) { - is.readUnsignedShort(); - builder.setClassVersion(is.readUnsignedShort()); - } + if (dis.readInt() == 0xCAFEBABE) { + dis.readUnsignedShort(); + builder.setClassVersion(dis.readUnsignedShort()); } } } // Check various known files for version information if unknown if (versionName == null) { - fillVersionFromJar(gameJar, fs, builder); + fillVersionFromJar(cp, builder); } } catch (IOException e) { throw ExceptionUtil.wrap(e); @@ -106,8 +103,8 @@ public static McVersion getVersion(Path gameJar, String entrypointClass, String public static McVersion getVersionExceptClassVersion(Path gameJar) { McVersion.Builder builder = new McVersion.Builder(); - try (FileSystemUtil.FileSystemDelegate jarFs = FileSystemUtil.getJarFileSystem(gameJar, false)) { - fillVersionFromJar(gameJar, jarFs.get(), builder); + try (SimpleClassPath cp = new SimpleClassPath(Collections.singletonList(gameJar))) { + fillVersionFromJar(cp, builder); } catch (IOException e) { throw ExceptionUtil.wrap(e); } @@ -115,38 +112,40 @@ public static McVersion getVersionExceptClassVersion(Path gameJar) { return builder.build(); } - public static void fillVersionFromJar(Path gameJar, FileSystem fs, McVersion.Builder builder) { + public static void fillVersionFromJar(SimpleClassPath cp, McVersion.Builder builder) { try { - Path file; + InputStream is; // version.json - contains version and target release for 18w47b+ - if (Files.isRegularFile(file = fs.getPath("version.json")) && fromVersionJson(Files.newInputStream(file), builder)) { + if ((is = cp.getInputStream("version.json")) != null && fromVersionJson(is, builder)) { return; } // constant field RealmsSharedConstants.VERSION_STRING - if (Files.isRegularFile(file = fs.getPath("net/minecraft/realms/RealmsSharedConstants.class")) && fromAnalyzer(Files.newInputStream(file), new FieldStringConstantVisitor("VERSION_STRING"), builder)) { + if ((is = cp.getInputStream("net/minecraft/realms/RealmsSharedConstants.class")) != null && fromAnalyzer(is, new FieldStringConstantVisitor("VERSION_STRING"), builder)) { return; } // constant return value of RealmsBridge.getVersionString (presumably inlined+dead code eliminated VERSION_STRING) - if (Files.isRegularFile(file = fs.getPath("net/minecraft/realms/RealmsBridge.class")) && fromAnalyzer(Files.newInputStream(file), new MethodConstantRetVisitor("getVersionString"), builder)) { + if ((is = cp.getInputStream("net/minecraft/realms/RealmsBridge.class")) != null && fromAnalyzer(is, new MethodConstantRetVisitor("getVersionString"), builder)) { return; } // version-like String constant used in MinecraftServer.run or another MinecraftServer method - if (Files.isRegularFile(file = fs.getPath("net/minecraft/server/MinecraftServer.class")) && fromAnalyzer(Files.newInputStream(file), new MethodConstantVisitor("run"), builder)) { + if ((is = cp.getInputStream("net/minecraft/server/MinecraftServer.class")) != null && fromAnalyzer(is, new MethodConstantVisitor("run"), builder)) { return; } - if (Files.isRegularFile(file = fs.getPath("net/minecraft/client/Minecraft.class"))) { + CpEntry entry = cp.getEntry("net/minecraft/client/Minecraft.class"); + + if (entry != null) { // version-like constant return value of a Minecraft method (obfuscated/unknown name) - if (fromAnalyzer(Files.newInputStream(file), new MethodConstantRetVisitor(null), builder)) { + if (fromAnalyzer(entry.getInputStream(), new MethodConstantRetVisitor(null), builder)) { return; } // version-like constant passed into Display.setTitle in a Minecraft method (obfuscated/unknown name) - if (fromAnalyzer(Files.newInputStream(file), new MethodStringConstantContainsVisitor("org/lwjgl/opengl/Display", "setTitle"), builder)) { + if (fromAnalyzer(entry.getInputStream(), new MethodStringConstantContainsVisitor("org/lwjgl/opengl/Display", "setTitle"), builder)) { return; } } @@ -154,7 +153,7 @@ public static void fillVersionFromJar(Path gameJar, FileSystem fs, McVersion.Bui e.printStackTrace(); } - builder.setFromFileName(gameJar.getFileName().toString()); + builder.setFromFileName(cp.getPaths().get(0).getFileName().toString()); } private static boolean fromVersionJson(InputStream is, McVersion.Builder builder) { diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index 2ed168f1f..7b0af4ded 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -41,7 +40,7 @@ import net.fabricmc.loader.impl.FormattedException; import net.fabricmc.loader.impl.game.GameProvider; import net.fabricmc.loader.impl.game.GameProviderHelper; -import net.fabricmc.loader.impl.game.minecraft.LibClassifier.Lib; +import net.fabricmc.loader.impl.game.LibClassifier; import net.fabricmc.loader.impl.game.minecraft.patch.BrandingPatch; import net.fabricmc.loader.impl.game.minecraft.patch.EntrypointPatch; import net.fabricmc.loader.impl.game.minecraft.patch.EntrypointPatchFML125; @@ -54,6 +53,7 @@ import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.log.Log; +import net.fabricmc.loader.impl.util.log.LogCategory; import net.fabricmc.loader.impl.util.log.LogHandler; public class MinecraftGameProvider implements GameProvider { @@ -74,13 +74,14 @@ public class MinecraftGameProvider implements GameProvider { private EnvType envType; private String entrypoint; private Arguments arguments; - private Path gameJar, realmsJar; + private final List gameJars = new ArrayList<>(2); // env game jar and potentially common game jar + private Path realmsJar; private final Set logJars = new HashSet<>(); private boolean log4jAvailable; private boolean slf4jAvailable; private final List miscGameLibraries = new ArrayList<>(); // libraries not relevant for loader's uses + private Collection validParentClassPath; // computed parent class path restriction (loader+deps) private McVersion versionData; - private boolean useGameJarForLogging; private boolean hasModLoader = false; private static final GameTransformer TRANSFORMER = new GameTransformer( @@ -123,11 +124,11 @@ public Collection getBuiltinMods() { } } - return Collections.singletonList(new BuiltinMod(Collections.singletonList(gameJar), metadata.build())); + return Collections.singletonList(new BuiltinMod(gameJars, metadata.build())); } public Path getGameJar() { - return gameJar; + return gameJars.get(0); } @Override @@ -166,65 +167,76 @@ public boolean locateGame(FabricLauncher launcher, String[] args) { arguments.parse(args); try { - String gameJarProperty = System.getProperty(SystemProperties.GAME_JAR_PATH); - List lookupPaths; - - if (gameJarProperty != null) { - Path path = Paths.get(gameJarProperty).toAbsolutePath().normalize(); - if (!Files.exists(path)) throw new RuntimeException("Game jar "+path+" configured through "+SystemProperties.GAME_JAR_PATH+" system property doesn't exist"); + LibClassifier classifier = new LibClassifier<>(McLibrary.class, envType, this); + McLibrary envGameLib = envType == EnvType.CLIENT ? McLibrary.MC_CLIENT : McLibrary.MC_SERVER; + Path commonGameJar = GameProviderHelper.getCommonGameJar(); + Path envGameJar = GameProviderHelper.getEnvGameJar(envType); + boolean commonGameJarDeclared = commonGameJar != null; + + if (commonGameJarDeclared) { + if (envGameJar != null) { + classifier.process(envGameJar, McLibrary.MC_COMMON); + } - lookupPaths = new ArrayList<>(); - lookupPaths.add(path); - lookupPaths.addAll(launcher.getClassPath()); - } else { - lookupPaths = launcher.getClassPath(); + classifier.process(commonGameJar); + } else if (envGameJar != null) { + classifier.process(envGameJar); } - LibClassifier classifier = new LibClassifier(); - classifier.process(lookupPaths, envType); + classifier.process(launcher.getClassPath()); - if (classifier.has(Lib.MC_BUNDLER)) { + if (classifier.has(McLibrary.MC_BUNDLER)) { BundlerProcessor.process(classifier); } - Lib gameLib = envType == EnvType.CLIENT ? Lib.MC_CLIENT : Lib.MC_SERVER; - gameJar = classifier.getOrigin(gameLib); - if (gameJar == null) return false; + envGameJar = classifier.getOrigin(envGameLib); + if (envGameJar == null) return false; + + commonGameJar = classifier.getOrigin(McLibrary.MC_COMMON); + + if (commonGameJarDeclared && commonGameJar == null) { + Log.warn(LogCategory.GAME_PROVIDER, "The declared common game jar didn't contain any of the expected classes!"); + } + + gameJars.add(envGameJar); - entrypoint = classifier.getClassName(gameLib); - realmsJar = classifier.getOrigin(Lib.REALMS); - useGameJarForLogging = classifier.is(gameJar, Lib.LOGGING); - hasModLoader = classifier.has(Lib.MODLOADER); - log4jAvailable = classifier.has(Lib.LOG4J_API) && classifier.has(Lib.LOG4J_CORE); - slf4jAvailable = classifier.has(Lib.SLF4J_API) && classifier.has(Lib.SLF4J_CORE); + if (commonGameJar != null && !commonGameJar.equals(envGameJar)) { + gameJars.add(commonGameJar); + } + + entrypoint = classifier.getClassName(envGameLib); + realmsJar = classifier.getOrigin(McLibrary.REALMS); + hasModLoader = classifier.has(McLibrary.MODLOADER); + log4jAvailable = classifier.has(McLibrary.LOG4J_API) && classifier.has(McLibrary.LOG4J_CORE); + slf4jAvailable = classifier.has(McLibrary.SLF4J_API) && classifier.has(McLibrary.SLF4J_CORE); - for (Lib lib : Lib.LOGGING) { + for (McLibrary lib : McLibrary.LOGGING) { Path path = classifier.getOrigin(lib); - if (path != null && !path.equals(gameJar) && !lookupPaths.contains(path)) { - logJars.add(path); + if (path != null) { + if (log4jAvailable || slf4jAvailable) { + logJars.add(path); + } else if (!gameJars.contains(path)) { + miscGameLibraries.add(path); + } } } - for (Path path : classifier.getUnmatchedOrigins()) { - if (!lookupPaths.contains(path)) miscGameLibraries.add(path); - } + miscGameLibraries.addAll(classifier.getUnmatchedOrigins()); + validParentClassPath = classifier.getLoaderOrigins(); } catch (IOException e) { throw ExceptionUtil.wrap(e); } - if (!useGameJarForLogging && logJars.isEmpty()) { // use Log4J/SLF4J log handler directly if it is not shaded into the game jar, otherwise delay it to initialize() after deobfuscation - setupLogHandler(launcher, false); - } - // expose obfuscated jar locations for mods to more easily remap code from obfuscated to intermediary ObjectShare share = FabricLoaderImpl.INSTANCE.getObjectShare(); - share.put("fabric-loader:inputGameJar", gameJar); + share.put("fabric-loader:inputGameJar", gameJars.get(0)); // deprecated + share.put("fabric-loader:inputGameJars", gameJars); if (realmsJar != null) share.put("fabric-loader:inputRealmsJar", realmsJar); String version = arguments.remove(Arguments.GAME_VERSION); if (version == null) version = System.getProperty(SystemProperties.GAME_VERSION); - versionData = McVersionLookup.getVersion(gameJar, entrypoint, version); + versionData = McVersionLookup.getVersion(gameJars, entrypoint, version); processArgumentMap(arguments, envType); @@ -269,39 +281,59 @@ private static Path getLaunchDirectory(Arguments argMap) { @Override public void initialize(FabricLauncher launcher) { - Map gameJars = new HashMap<>(2); - String name = envType.name().toLowerCase(Locale.ENGLISH); - gameJars.put(name, gameJar); - - if (realmsJar != null) { - gameJars.put("realms", realmsJar); - } + launcher.setValidParentClassPath(validParentClassPath); if (isObfuscated()) { - gameJars = GameProviderHelper.deobfuscate(gameJars, + Map obfJars = new HashMap<>(3); + String[] names = new String[gameJars.size()]; + + for (int i = 0; i < gameJars.size(); i++) { + String name; + + if (i == 0) { + name = envType.name().toLowerCase(Locale.ENGLISH); + } else if (i == 1) { + name = "common"; + } else { + name = String.format("extra-%d", i - 2); + } + + obfJars.put(name, gameJars.get(i)); + names[i] = name; + } + + if (realmsJar != null) { + obfJars.put("realms", realmsJar); + } + + obfJars = GameProviderHelper.deobfuscate(obfJars, getGameId(), getNormalizedGameVersion(), getLaunchDirectory(), launcher); - gameJar = gameJars.get(name); - realmsJar = gameJars.get("realms"); - } + for (int i = 0; i < gameJars.size(); i++) { + Path newJar = obfJars.get(names[i]); + Path oldJar = gameJars.set(i, newJar); - if (useGameJarForLogging || !logJars.isEmpty()) { - if (useGameJarForLogging) { - launcher.addToClassPath(gameJar, ALLOWED_EARLY_CLASS_PREFIXES); + if (logJars.remove(oldJar)) logJars.add(newJar); } - if (!logJars.isEmpty()) { - for (Path jar : logJars) { + realmsJar = obfJars.get("realms"); + } + + if (!logJars.isEmpty()) { + for (Path jar : logJars) { + if (gameJars.contains(jar)) { + launcher.addToClassPath(jar, ALLOWED_EARLY_CLASS_PREFIXES); + } else { launcher.addToClassPath(jar); } } - - setupLogHandler(launcher, true); } - TRANSFORMER.locateEntrypoints(launcher, gameJar); + setupLogHandler(launcher, true); + + TRANSFORMER.locateEntrypoints(launcher, gameJars); } private void setupLogHandler(FabricLauncher launcher, boolean useTargetCl) { @@ -389,10 +421,12 @@ public boolean hasAwtSupport() { @Override public void unlockClassPath(FabricLauncher launcher) { - if (useGameJarForLogging) { - launcher.setAllowedPrefixes(gameJar); - } else { - launcher.addToClassPath(gameJar); + for (Path gameJar : gameJars) { + if (logJars.contains(gameJar)) { + launcher.setAllowedPrefixes(gameJar); + } else { + launcher.addToClassPath(gameJar); + } } if (realmsJar != null) launcher.addToClassPath(realmsJar); diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java index 34df585a8..0778fdc12 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java @@ -28,6 +28,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; @@ -185,6 +186,11 @@ public void setAllowedPrefixes(Path path, String... prefixes) { // not implemented (no-op) } + @Override + public void setValidParentClassPath(Collection paths) { + // not implemented (no-op) + } + @Override public List getClassPath() { return classPath; diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index a55beeb41..5f4dcc766 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -61,6 +61,7 @@ import net.fabricmc.loader.impl.metadata.VersionOverrides; import net.fabricmc.loader.impl.util.DefaultLanguageAdapter; import net.fabricmc.loader.impl.util.SystemProperties; +import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; @@ -147,6 +148,8 @@ public EnvType getEnvironmentType() { */ @Override public Path getGameDir() { + if (gameDir == null) throw new IllegalStateException("invoked too early?"); + return gameDir; } @@ -321,8 +324,7 @@ private void finishModLoading() { } // suppress fabric loader explicitly in case its fabric.mod.json is in a different folder from the classes - Path fabricLoaderPath = ClasspathModCandidateFinder.getFabricLoaderPath(); - if (fabricLoaderPath != null) knownModPaths.add(fabricLoaderPath.toAbsolutePath().normalize()); + knownModPaths.add(UrlUtil.LOADER_CODE_SOURCE.toAbsolutePath().normalize()); for (String pathName : System.getProperty("java.class.path", "").split(File.pathSeparator)) { if (pathName.isEmpty() || pathName.endsWith("*")) continue; diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java index 1d6bcda8e..bd1ae5070 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java @@ -69,7 +69,7 @@ public void findCandidates(ModCandidateConsumer out) { } } else { // production, add loader as a mod try { - out.accept(getFabricLoaderPath(), false); + out.accept(UrlUtil.LOADER_CODE_SOURCE, false); } catch (Throwable t) { Log.debug(LogCategory.DISCOVERY, "Could not retrieve launcher code source!", t); } @@ -117,13 +117,4 @@ private static Map> getPathGroups() { return ret; } - - public static Path getFabricLoaderPath() { - try { - return UrlUtil.asPath(FabricLauncherBase.getLauncher().getClass().getProtectionDomain().getCodeSource().getLocation()); - } catch (Throwable t) { - Log.debug(LogCategory.DISCOVERY, "Could not retrieve launcher code source!", t); - return null; - } - } } diff --git a/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java b/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java index 8547d1ce4..1604cf068 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java +++ b/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java @@ -22,6 +22,7 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; @@ -34,11 +35,13 @@ import java.util.jar.JarFile; import java.util.zip.ZipFile; +import net.fabricmc.api.EnvType; import net.fabricmc.loader.impl.FabricLoaderImpl; import net.fabricmc.loader.impl.FormattedException; import net.fabricmc.loader.impl.launch.FabricLauncher; import net.fabricmc.loader.impl.launch.MappingConfiguration; import net.fabricmc.loader.impl.util.LoaderUtil; +import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.UrlConversionException; import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.loader.impl.util.log.Log; @@ -53,6 +56,24 @@ public final class GameProviderHelper { private GameProviderHelper() { } + public static Path getCommonGameJar() { + return getGameJar(SystemProperties.GAME_JAR_PATH); + } + + public static Path getEnvGameJar(EnvType env) { + return getGameJar(env == EnvType.CLIENT ? SystemProperties.GAME_JAR_PATH_CLIENT : SystemProperties.GAME_JAR_PATH_SERVER); + } + + private static Path getGameJar(String property) { + String val = System.getProperty(property); + if (val == null) return null; + + Path path = Paths.get(val).toAbsolutePath().normalize(); + if (!Files.exists(path)) throw new RuntimeException("Game jar "+path+" configured through "+property+" system property doesn't exist"); + + return path; + } + public static Optional getSource(ClassLoader loader, String filename) { URL url; diff --git a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java new file mode 100644 index 000000000..2abe10f37 --- /dev/null +++ b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java @@ -0,0 +1,205 @@ +/* + * Copyright 2016 FabricMC + * + * 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 net.fabricmc.loader.impl.game; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumMap; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.ZipError; +import java.util.zip.ZipFile; + +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.impl.game.LibClassifier.LibraryType; +import net.fabricmc.loader.impl.util.UrlUtil; + +public final class LibClassifier & LibraryType> { + private final List libs; + private final Map origins; + private final Map localPaths; + private final Set loaderOrigins = new HashSet<>(); + private final List unmatchedOrigins = new ArrayList<>(); + + public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) { + L[] libs = cls.getEnumConstants(); + + this.libs = new ArrayList<>(libs.length); + this.origins = new EnumMap<>(cls); + this.localPaths = new EnumMap<>(cls); + + for (L lib : libs) { + if (lib.isApplicable(env)) { + this.libs.add(lib); + } + } + + for (LoaderLibrary lib : LoaderLibrary.values()) { + loaderOrigins.add(lib.path); + } + + loaderOrigins.add(UrlUtil.getCodeSource(gameProvider.getClass())); + } + + public void process(URL url) throws IOException { + try { + process(UrlUtil.asPath(url)); + } catch (URISyntaxException e) { + throw new RuntimeException("invalid url: "+url); + } + } + + @SafeVarargs + public final void process(Iterable paths, L... excludedLibs) throws IOException { + Set excluded = makeSet(excludedLibs); + + for (Path path : paths) { + process(path, excluded); + } + } + + @SafeVarargs + public final void process(Path path, L... excludedLibs) throws IOException { + process(path, makeSet(excludedLibs)); + } + + private static > Set makeSet(L[] libs) { + if (libs.length == 0) return Collections.emptySet(); + + Set ret = EnumSet.of(libs[0]); + + for (int i = 1; i < libs.length; i++) { + ret.add(libs[i]); + } + + return ret; + } + + private void process(Path path, Set excludedLibs) throws IOException { + if (loaderOrigins.contains(path)) return; + + boolean matched = false; + + if (Files.isDirectory(path)) { + for (L lib : libs) { + if (excludedLibs.contains(lib) || origins.containsKey(lib)) continue; + + for (String p : lib.getPaths()) { + if (Files.exists(path.resolve(p))) { + matched = true; + addLibrary(lib, path, p); + break; + } + } + } + } else { + try (ZipFile zf = new ZipFile(path.toFile())) { + for (L lib : libs) { + if (excludedLibs.contains(lib) || origins.containsKey(lib)) continue; + + for (String p : lib.getPaths()) { + if (zf.getEntry(p) != null) { + matched = true; + addLibrary(lib, path, p); + break; + } + } + } + } catch (ZipError | IOException e) { + throw new IOException("error reading "+path.toAbsolutePath(), e); + } + } + + if (!matched) unmatchedOrigins.add(path); + } + + private void addLibrary(L lib, Path originPath, String localPath) { + Path prev = origins.put(lib, originPath); + if (prev != null) throw new IllegalStateException("lib "+lib+" was already added"); + localPaths.put(lib, localPath); + } + + @SafeVarargs + public final boolean is(Path path, L... libs) { + for (L lib : libs) { + if (path.equals(origins.get(lib))) return true; + } + + return false; + } + + public boolean has(L lib) { + return origins.containsKey(lib); + } + + public Path getOrigin(L lib) { + return origins.get(lib); + } + + public String getLocalPath(L lib) { + return localPaths.get(lib); + } + + public String getClassName(L lib) { + String localPath = localPaths.get(lib); + if (localPath == null || !localPath.endsWith(".class")) return null; + + return localPath.substring(0, localPath.length() - 6).replace('/', '.'); + } + + public List getUnmatchedOrigins() { + return unmatchedOrigins; + } + + public Collection getLoaderOrigins() { + return loaderOrigins; + } + + public boolean remove(Path path) { + if (unmatchedOrigins.remove(path)) return true; + + boolean ret = false; + + for (Iterator> it = origins.entrySet().iterator(); it.hasNext(); ) { + Map.Entry entry = it.next(); + + if (entry.getValue().equals(path)) { + localPaths.remove(entry.getKey()); + it.remove(); + + ret = true; + } + } + + return ret; + } + + public interface LibraryType { + boolean isApplicable(EnvType env); + String[] getPaths(); + } +} diff --git a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java new file mode 100644 index 000000000..a25f2b5ce --- /dev/null +++ b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016 FabricMC + * + * 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 net.fabricmc.loader.impl.game; + +import java.nio.file.Path; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.util.CheckClassAdapter; +import org.sat4j.pb.SolverFactory; +import org.sat4j.specs.ContradictionException; +import org.spongepowered.asm.launch.MixinBootstrap; + +import net.fabricmc.accesswidener.AccessWidener; +import net.fabricmc.loader.impl.util.UrlUtil; +import net.fabricmc.mapping.tree.TinyMappingFactory; +import net.fabricmc.tinyremapper.TinyRemapper; + +enum LoaderLibrary { + FABRIC_LOADER(UrlUtil.LOADER_CODE_SOURCE), + TINY_MAPPINGS_PARSER(TinyMappingFactory.class), + SPONGE_MIXIN(MixinBootstrap.class), + TINY_REMAPPER(TinyRemapper.class), + ACCESS_WIDENER(AccessWidener.class), + ASM(ClassReader.class), + ASM_ANALYSIS(Analyzer.class), + ASM_COMMONS(Remapper.class), + ASM_TREE(ClassNode.class), + ASM_UTIL(CheckClassAdapter.class), + SAT4J_CORE(ContradictionException.class), + SAT4J_PB(SolverFactory.class); + + final Path path; + + LoaderLibrary(Class cls) { + this(UrlUtil.getCodeSource(cls)); + } + + LoaderLibrary(Path path) { + if (path == null) throw new RuntimeException("missing loader library "+name()); + + this.path = path; + } +} diff --git a/src/main/java/net/fabricmc/loader/impl/game/patch/GameTransformer.java b/src/main/java/net/fabricmc/loader/impl/game/patch/GameTransformer.java index 057a86b1c..f4433c1a0 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/patch/GameTransformer.java +++ b/src/main/java/net/fabricmc/loader/impl/game/patch/GameTransformer.java @@ -18,22 +18,23 @@ import java.io.IOException; import java.io.InputStream; -import java.io.UncheckedIOException; import java.nio.file.Path; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; +import java.util.zip.ZipError; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; import net.fabricmc.loader.impl.launch.FabricLauncher; +import net.fabricmc.loader.impl.util.ExceptionUtil; import net.fabricmc.loader.impl.util.LoaderUtil; +import net.fabricmc.loader.impl.util.SimpleClassPath; +import net.fabricmc.loader.impl.util.SimpleClassPath.CpEntry; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; @@ -58,14 +59,14 @@ private void addPatchedClass(ClassNode node) { patchedClasses.put(key, writer.toByteArray()); } - public void locateEntrypoints(FabricLauncher launcher, Path gameJar) { + public void locateEntrypoints(FabricLauncher launcher, List gameJars) { if (entrypointsLocated) { return; } patchedClasses = new HashMap<>(); - try (ZipFile zf = new ZipFile(gameJar.toFile())) { + try (SimpleClassPath cp = new SimpleClassPath(gameJars)) { Function classSource = name -> { byte[] data = patchedClasses.get(name); @@ -73,13 +74,17 @@ public void locateEntrypoints(FabricLauncher launcher, Path gameJar) { return new ClassReader(data); } - ZipEntry entry = zf.getEntry(LoaderUtil.getClassFileName(name)); - if (entry == null) return null; + try { + CpEntry entry = cp.getEntry(LoaderUtil.getClassFileName(name)); + if (entry == null) return null; - try (InputStream is = zf.getInputStream(entry)) { - return new ClassReader(is); + try (InputStream is = entry.getInputStream()) { + return new ClassReader(is); + } catch (IOException | ZipError e) { + throw new RuntimeException(String.format("error reading %s in %s: %s", name, entry.getOrigin().toAbsolutePath(), e), e); + } } catch (IOException e) { - throw new UncheckedIOException(String.format("error reading %s in %s: %s", name, gameJar.toAbsolutePath(), e), e); + throw ExceptionUtil.wrap(e); } }; @@ -87,7 +92,7 @@ public void locateEntrypoints(FabricLauncher launcher, Path gameJar) { patch.process(launcher, classSource, this::addPatchedClass); } } catch (IOException e) { - throw new UncheckedIOException(String.format("error reading %s: %s", gameJar.toAbsolutePath(), e), e); + throw ExceptionUtil.wrap(e); } Log.debug(LogCategory.GAME_PATCH, "Patched %d class%s", patchedClasses.size(), patchedClasses.size() != 1 ? "s" : ""); diff --git a/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java b/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java index b534b92f5..9ab1ff302 100644 --- a/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java +++ b/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java @@ -28,12 +28,12 @@ import java.util.function.Consumer; import net.fabricmc.loader.impl.FabricLoaderImpl; -import net.fabricmc.loader.impl.discovery.ClasspathModCandidateFinder; import net.fabricmc.loader.impl.game.GameProvider; import net.fabricmc.loader.impl.gui.FabricStatusTree.FabricBasicButtonType; import net.fabricmc.loader.impl.gui.FabricStatusTree.FabricStatusTab; import net.fabricmc.loader.impl.gui.FabricStatusTree.FabricTreeWarningLevel; import net.fabricmc.loader.impl.util.LoaderUtil; +import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; @@ -69,10 +69,7 @@ private static void openForked(FabricStatusTree tree) throws IOException, Interr if (javaPath == null) throw new RuntimeException("can't find java executable in "+javaBinDir); - Path loaderPath = ClasspathModCandidateFinder.getFabricLoaderPath(); - if (loaderPath == null) throw new RuntimeException("can't determine Fabric Loader path"); - - Process process = new ProcessBuilder(javaPath.toString(), "-Xmx100M", "-cp", loaderPath.toString(), FabricGuiEntry.class.getName()) + Process process = new ProcessBuilder(javaPath.toString(), "-Xmx100M", "-cp", UrlUtil.LOADER_CODE_SOURCE.toString(), FabricGuiEntry.class.getName()) .redirectOutput(ProcessBuilder.Redirect.INHERIT) .redirectError(ProcessBuilder.Redirect.INHERIT) .start(); diff --git a/src/main/java/net/fabricmc/loader/impl/launch/FabricLauncher.java b/src/main/java/net/fabricmc/loader/impl/launch/FabricLauncher.java index 097340057..9566c4dda 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/FabricLauncher.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/FabricLauncher.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; +import java.util.Collection; import java.util.List; import java.util.jar.Manifest; @@ -29,6 +30,7 @@ public interface FabricLauncher { void addToClassPath(Path path, String... allowedPrefixes); void setAllowedPrefixes(Path path, String... prefixes); + void setValidParentClassPath(Collection paths); EnvType getEnvironmentType(); diff --git a/src/main/java/net/fabricmc/loader/impl/launch/FabricLauncherBase.java b/src/main/java/net/fabricmc/loader/impl/launch/FabricLauncherBase.java index 564307cf2..856595331 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/FabricLauncherBase.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/FabricLauncherBase.java @@ -16,6 +16,9 @@ package net.fabricmc.loader.impl.launch; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.PrintWriter; import java.lang.reflect.Method; import java.util.Map; @@ -107,7 +110,14 @@ public void uncaughtException(Thread t, Throwable e) { } } catch (Throwable e2) { // just in case e.addSuppressed(e2); - e.printStackTrace(); + + try { + e.printStackTrace(); + } catch (Throwable e3) { + PrintWriter pw = new PrintWriter(new FileOutputStream(FileDescriptor.err)); + e.printStackTrace(pw); + pw.flush(); + } } } }); diff --git a/src/main/java/net/fabricmc/loader/impl/launch/FabricMixinBootstrap.java b/src/main/java/net/fabricmc/loader/impl/launch/FabricMixinBootstrap.java index 3856685d5..a9cc9c4c9 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/FabricMixinBootstrap.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/FabricMixinBootstrap.java @@ -38,6 +38,8 @@ import net.fabricmc.loader.api.metadata.version.VersionInterval; import net.fabricmc.loader.impl.FabricLoaderImpl; import net.fabricmc.loader.impl.ModContainerImpl; +import net.fabricmc.loader.impl.launch.knot.MixinServiceKnot; +import net.fabricmc.loader.impl.launch.knot.MixinServiceKnotBootstrap; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; import net.fabricmc.loader.impl.util.mappings.MixinIntermediaryDevRemapper; @@ -53,6 +55,11 @@ public static void init(EnvType side, FabricLoaderImpl loader) { throw new RuntimeException("FabricMixinBootstrap has already been initialized!"); } + System.setProperty("mixin.bootstrapService", MixinServiceKnotBootstrap.class.getName()); + System.setProperty("mixin.service", MixinServiceKnot.class.getName()); + + MixinBootstrap.init(); + if (FabricLauncherBase.getLauncher().isDevelopment()) { MappingConfiguration mappingConfiguration = FabricLauncherBase.getLauncher().getMappingConfiguration(); TinyTree mappings = mappingConfiguration.getMappings(); @@ -75,8 +82,6 @@ public static void init(EnvType side, FabricLoaderImpl loader) { } } - MixinBootstrap.init(); - Map configToModMap = new HashMap<>(); for (ModContainerImpl mod : loader.getModsInternal()) { diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java index bc860bf9d..777ab21d6 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java @@ -20,7 +20,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -28,6 +27,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -38,8 +38,6 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import org.spongepowered.asm.launch.MixinBootstrap; - import net.fabricmc.api.EnvType; import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint; import net.fabricmc.loader.impl.FabricLoaderImpl; @@ -130,8 +128,8 @@ protected ClassLoader init(String[] args) { // Setup classloader // TODO: Provide KnotCompatibilityClassLoader in non-exclusive-Fabric pre-1.13 environments? boolean useCompatibility = provider.requiresUrlClassLoader() || Boolean.parseBoolean(System.getProperty("fabric.loader.useCompatibilityClassLoader", "false")); - classLoader = useCompatibility ? new KnotCompatibilityClassLoader(isDevelopment(), envType, provider) : new KnotClassLoader(isDevelopment(), envType, provider); - ClassLoader cl = (ClassLoader) classLoader; + classLoader = KnotClassLoaderInterface.create(useCompatibility, isDevelopment(), envType, provider); + ClassLoader cl = classLoader.getClassLoader(); provider.initialize(this); @@ -144,11 +142,10 @@ protected ClassLoader init(String[] args) { FabricLoaderImpl.INSTANCE.loadAccessWideners(); - MixinBootstrap.init(); FabricMixinBootstrap.init(getEnvironmentType(), loader); FabricLauncherBase.finishMixinBootstrapping(); - classLoader.getDelegate().initializeTransformers(); + classLoader.initializeTransformers(); provider.unlockClassPath(this); unlocked = true; @@ -216,8 +213,8 @@ private GameProvider createGameProvider(String[] args) { */ private static GameProvider findEmbedddedGameProvider() { try { - Path flPath = UrlUtil.asPath(Knot.class.getProtectionDomain().getCodeSource().getLocation()); - if (!flPath.getFileName().toString().endsWith(".jar")) return null; // not a jar + Path flPath = UrlUtil.getCodeSource(Knot.class); + if (flPath == null || !flPath.getFileName().toString().endsWith(".jar")) return null; // not a jar try (ZipFile zf = new ZipFile(flPath.toFile())) { ZipEntry entry = zf.getEntry("META-INF/services/net.fabricmc.loader.impl.game.GameProvider"); // same file as used by service loader @@ -246,7 +243,7 @@ private static GameProvider findEmbedddedGameProvider() { } return null; - } catch (IOException | URISyntaxException | ReflectiveOperationException e) { + } catch (IOException | ReflectiveOperationException e) { throw new RuntimeException(e); } } @@ -268,8 +265,8 @@ public void addToClassPath(Path path, String... allowedPrefixes) { try { URL url = UrlUtil.asUrl(path); - classLoader.getDelegate().setAllowedPrefixes(url, allowedPrefixes); - classLoader.addURL(url); + classLoader.setAllowedPrefixes(url, allowedPrefixes); + classLoader.addUrl(url); } catch (MalformedURLException e) { throw new RuntimeException(e); } @@ -278,12 +275,27 @@ public void addToClassPath(Path path, String... allowedPrefixes) { @Override public void setAllowedPrefixes(Path path, String... prefixes) { try { - classLoader.getDelegate().setAllowedPrefixes(UrlUtil.asUrl(path), prefixes); + classLoader.setAllowedPrefixes(UrlUtil.asUrl(path), prefixes); } catch (MalformedURLException e) { throw new RuntimeException(e); } } + @Override + public void setValidParentClassPath(Collection paths) { + List urls = new ArrayList<>(paths.size()); + + try { + for (Path path : paths) { + urls.add(UrlUtil.asUrl(path)); + } + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + + classLoader.setValidParentClassPath(urls); + } + @Override public EnvType getEnvironmentType() { return envType; @@ -301,16 +313,12 @@ public Class loadIntoTarget(String name) throws ClassNotFoundException { @Override public InputStream getResourceAsStream(String name) { - try { - return classLoader.getResourceAsStream(name, false); - } catch (IOException e) { - throw new RuntimeException("Failed to read file '" + name + "'!", e); - } + return classLoader.getClassLoader().getResourceAsStream(name); } @Override public ClassLoader getTargetClassLoader() { - return (ClassLoader) classLoader; + return classLoader.getClassLoader(); } @Override @@ -318,16 +326,16 @@ public byte[] getClassByteArray(String name, boolean runTransformers) throws IOE if (!unlocked) throw new IllegalStateException("early getClassByteArray access"); if (runTransformers) { - return classLoader.getDelegate().getPreMixinClassByteArray(name, true); + return classLoader.getPreMixinClassBytes(name); } else { - return classLoader.getDelegate().getRawClassByteArray(name, true); + return classLoader.getRawClassBytes(name); } } @Override public Manifest getManifest(Path originPath) { try { - return classLoader.getDelegate().getMetadata(UrlUtil.asUrl(originPath)).manifest; + return classLoader.getManifest(UrlUtil.asUrl(originPath)); } catch (MalformedURLException e) { throw new RuntimeException(e); } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index e45152a78..5d0bbf63e 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -29,7 +29,9 @@ import java.nio.file.Path; import java.security.CodeSource; import java.security.cert.Certificate; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -40,6 +42,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.loader.impl.game.GameProvider; import net.fabricmc.loader.impl.launch.FabricLauncherBase; +import net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.ClassLoaderAccess; import net.fabricmc.loader.impl.transformer.FabricTransformer; import net.fabricmc.loader.impl.util.FileSystemUtil; import net.fabricmc.loader.impl.util.LoaderUtil; @@ -50,10 +53,12 @@ import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; -final class KnotClassDelegate { +final class KnotClassDelegate implements KnotClassLoaderInterface { + private static final boolean LOG_CLASS_LOAD_ERRORS = System.getProperty(SystemProperties.DEBUG_LOG_CLASS_LOAD_ERRORS) != null; private static final boolean LOG_TRANSFORM_ERRORS = System.getProperty(SystemProperties.DEBUG_LOG_TRANSFORM_ERRORS) != null; + private static final boolean DISABLE_ISOLATION = System.getProperty(SystemProperties.DEBUG_DISABLE_CLASS_PATH_ISOLATION) != null; - static class Metadata { + static final class Metadata { static final Metadata EMPTY = new Metadata(null, null); final Manifest manifest; @@ -65,23 +70,35 @@ static class Metadata { } } + private static final ClassLoader PLATFORM_CLASS_LOADER = getPlatformClassLoader(); + private final Map metadataCache = new ConcurrentHashMap<>(); - private final KnotClassLoaderInterface itf; + private final T classLoader; + private final ClassLoader parentClassLoader; private final GameProvider provider; private final boolean isDevelopment; private final EnvType envType; private IMixinTransformer mixinTransformer; private boolean transformInitialized = false; + private volatile Set urls = Collections.emptySet(); + private volatile Set validParentUrls = Collections.emptySet(); private final Map allowedPrefixes = new ConcurrentHashMap<>(); private final Set parentSourcedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); - KnotClassDelegate(boolean isDevelopment, EnvType envType, KnotClassLoaderInterface itf, GameProvider provider) { + KnotClassDelegate(boolean isDevelopment, EnvType envType, T classLoader, ClassLoader parentClassLoader, GameProvider provider) { this.isDevelopment = isDevelopment; this.envType = envType; - this.itf = itf; + this.classLoader = classLoader; + this.parentClassLoader = parentClassLoader; this.provider = provider; } + @Override + public ClassLoader getClassLoader() { + return classLoader; + } + + @Override public void initializeTransformers() { if (transformInitialized) throw new IllegalStateException("Cannot initialize KnotClassDelegate twice!"); @@ -109,13 +126,146 @@ private IMixinTransformer getMixinTransformer() { return mixinTransformer; } + @Override + public void addUrl(URL url) { + String urlStr = url.toString(); + + synchronized (this) { + Set urls = this.urls; + if (urls.contains(urlStr)) return; + + Set newUrls = new HashSet<>(urls.size() + 1, 1); + newUrls.addAll(urls); + newUrls.add(urlStr); + + this.urls = newUrls; + } + + classLoader.addUrlFwd(url); + } + + @Override + public void setAllowedPrefixes(URL url, String... prefixes) { + if (prefixes.length == 0) { + allowedPrefixes.remove(url); + } else { + allowedPrefixes.put(url, prefixes); + } + } + + @Override + public void setValidParentClassPath(Collection urls) { + Set urlStrs = new HashSet<>(urls.size(), 1); + + for (URL url : urls) { + urlStrs.add(url.toString()); + } + + this.validParentUrls = urlStrs; + } + + @Override + public Manifest getManifest(URL url) { + return getMetadata(url).manifest; + } + + @Override + public boolean isClassLoaded(String name) { + synchronized (classLoader.getClassLoadingLockFwd(name)) { + return classLoader.findLoadedClassFwd(name) != null; + } + } + + @Override + public Class loadIntoTarget(String name) throws ClassNotFoundException { + synchronized (classLoader.getClassLoadingLockFwd(name)) { + Class c = classLoader.findLoadedClassFwd(name); + + if (c == null) { + c = tryLoadClass(name, true); + + if (c == null) { + throw new ClassNotFoundException("can't find class "+name); + } + } + + classLoader.resolveClassFwd(c); + + return c; + } + } + + Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + synchronized (classLoader.getClassLoadingLockFwd(name)) { + Class c = classLoader.findLoadedClassFwd(name); + + if (c == null) { + if (name.startsWith("java.")) { // fast path for java.** (can only be loaded by the platform CL anyway) + c = PLATFORM_CLASS_LOADER.loadClass(name); + } else { + c = tryLoadClass(name, false); // try local load + + if (c == null) { // not available locally, try system class loader + String fileName = LoaderUtil.getClassFileName(name); + URL url = parentClassLoader.getResource(fileName); + + if (url == null) { // no .class file + String msg = "can't find class "+name; + if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, msg); + throw new ClassNotFoundException(msg); + } else if (!isValidParentUrl(url, fileName)) { // available, but restricted + // The class would technically be available, but the game provider restricted it from being + // loaded by setting validParentUrls and not including "url". Typical causes are: + // - accessing classes too early (game libs shouldn't be used until Loader is ready) + // - using jars that are only transient (deobfuscation input or pass-through installers) + String msg = "can't load class "+name+" as it hasn't been exposed to the game (yet?)"; + if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, msg); + throw new ClassNotFoundException(msg); + } else { // load from system cl + c = parentClassLoader.loadClass(name); + } + } + } + } + + if (resolve) { + classLoader.resolveClassFwd(c); + } + + return c; + } + } + + /** + * Check if an url is loadable by the parent class loader. + * + *

This handles explicit parent url whitelisting by {@link #validParentUrls} or shadowing by {@link #urls} + */ + private boolean isValidParentUrl(URL url, String fileName) { + if (url == null) return false; + if (DISABLE_ISOLATION) return true; + + try { + String srcUrl = UrlUtil.getSource(fileName, url).toString(); + Set validParentUrls = this.validParentUrls; + + if (validParentUrls != null) { // explicit whitelist (in addition to platform cl classes) + return validParentUrls.contains(srcUrl) || PLATFORM_CLASS_LOADER.getResource(fileName) != null; + } else { // reject urls shadowed by this cl + return !urls.contains(srcUrl); + } + } catch (UrlConversionException e) { + throw new RuntimeException(e); + } + } + Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFoundException { if (name.startsWith("java.")) { return null; } - if (!allowedPrefixes.isEmpty()) { - URL url = itf.getResource(LoaderUtil.getClassFileName(name)); + if (!allowedPrefixes.isEmpty() && !DISABLE_ISOLATION) { // check prefix restrictions (allows exposing libraries partially during startup) + URL url = classLoader.getResource(LoaderUtil.getClassFileName(name)); String[] prefixes; if (url != null @@ -131,12 +281,14 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound } if (!found) { - throw new ClassNotFoundException("class "+name+" is currently restricted from being loaded"); + String msg = "class "+name+" is currently restricted from being loaded"; + if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, msg); + throw new ClassNotFoundException(msg); } } } - if (!allowFromParent && !parentSourcedClasses.isEmpty()) { + if (!allowFromParent && !parentSourcedClasses.isEmpty()) { // propagate loadIntoTarget behavior to its nested classes int pos = name.length(); while ((pos = name.lastIndexOf('$', pos - 1)) > 0) { @@ -154,7 +306,7 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound parentSourcedClasses.add(name); } - KnotClassDelegate.Metadata metadata = getMetadata(name, itf.getResource(LoaderUtil.getClassFileName(name))); + KnotClassDelegate.Metadata metadata = getMetadata(name, classLoader.getResource(LoaderUtil.getClassFileName(name))); int pkgDelimiterPos = name.lastIndexOf('.'); @@ -162,16 +314,16 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound // TODO: package definition stub String pkgString = name.substring(0, pkgDelimiterPos); - if (itf.getPackage(pkgString) == null) { + if (classLoader.getPackageFwd(pkgString) == null) { try { - itf.definePackage(pkgString, null, null, null, null, null, null, null); + classLoader.definePackageFwd(pkgString, null, null, null, null, null, null, null); } catch (IllegalArgumentException e) { // presumably concurrent package definition - if (itf.getPackage(pkgString) == null) throw e; // still not defined? + if (classLoader.getPackageFwd(pkgString) == null) throw e; // still not defined? } } } - return itf.defineClassFwd(name, input, 0, input.length, metadata.codeSource); + return classLoader.defineClassFwd(name, input, 0, input.length, metadata.codeSource); } Metadata getMetadata(String name, URL resourceURL) { @@ -236,7 +388,7 @@ Metadata getMetadata(URL codeSourceUrl) { }); } - public byte[] getPostMixinClassByteArray(String name, boolean allowFromParent) { + private byte[] getPostMixinClassByteArray(String name, boolean allowFromParent) { byte[] transformedClassArray = getPreMixinClassByteArray(name, allowFromParent); if (!transformInitialized || !canTransformClass(name)) { @@ -253,10 +405,15 @@ public byte[] getPostMixinClassByteArray(String name, boolean allowFromParent) { } } + @Override + public byte[] getPreMixinClassBytes(String name) { + return getPreMixinClassByteArray(name, true); + } + /** * Runs all the class transformers except mixin. */ - public byte[] getPreMixinClassByteArray(String name, boolean allowFromParent) { + private byte[] getPreMixinClassByteArray(String name, boolean allowFromParent) { // some of the transformers rely on dot notation name = name.replace('/', '.'); @@ -291,28 +448,56 @@ private static boolean canTransformClass(String name) { return /* !"net.fabricmc.api.EnvType".equals(name) && !name.startsWith("net.fabricmc.loader.") && */ !name.startsWith("org.apache.logging.log4j"); } - public byte[] getRawClassByteArray(String name, boolean allowFromParent) throws IOException { - InputStream inputStream = itf.getResourceAsStream(LoaderUtil.getClassFileName(name), allowFromParent); - if (inputStream == null) return null; + @Override + public byte[] getRawClassBytes(String name) throws IOException { + return getRawClassByteArray(name, true); + } + + private byte[] getRawClassByteArray(String name, boolean allowFromParent) throws IOException { + name = LoaderUtil.getClassFileName(name); + URL url = classLoader.findResourceFwd(name); - int a = inputStream.available(); - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(a < 32 ? 32768 : a); - byte[] buffer = new byte[8192]; - int len; + if (url == null) { + if (!allowFromParent) return null; - while ((len = inputStream.read(buffer)) > 0) { - outputStream.write(buffer, 0, len); + url = parentClassLoader.getResource(name); + if (!isValidParentUrl(url, name)) return null; } - inputStream.close(); - return outputStream.toByteArray(); + try (InputStream inputStream = url.openStream()) { + int a = inputStream.available(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(a < 32 ? 32768 : a); + byte[] buffer = new byte[8192]; + int len; + + while ((len = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, len); + } + + return outputStream.toByteArray(); + } } - void setAllowedPrefixes(URL url, String... prefixes) { - if (prefixes.length == 0) { - allowedPrefixes.remove(url); - } else { - allowedPrefixes.put(url, prefixes); + private static ClassLoader getPlatformClassLoader() { + try { + return (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null); // Java 9+ only + } catch (NoSuchMethodException e) { + return new ClassLoader(null) { }; // fall back to boot cl + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); } } + + interface ClassLoaderAccess { + void addUrlFwd(URL url); + URL findResourceFwd(String name); + + Package getPackageFwd(String name); + Package definePackageFwd(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException; + + Object getClassLoadingLockFwd(String name); + Class findLoadedClassFwd(String name); + Class defineClassFwd(String name, byte[] b, int off, int len, CodeSource cs); + void resolveClassFwd(Class cls); + } } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java index 99a7782d8..ebaa1088d 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java @@ -27,9 +27,10 @@ import net.fabricmc.api.EnvType; import net.fabricmc.loader.impl.game.GameProvider; +import net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.ClassLoaderAccess; -final class KnotClassLoader extends SecureClassLoader implements KnotClassLoaderInterface { - private static class DynamicURLClassLoader extends URLClassLoader { +final class KnotClassLoader extends SecureClassLoader implements ClassLoaderAccess { + private static final class DynamicURLClassLoader extends URLClassLoader { private DynamicURLClassLoader(URL[] urls) { super(urls, new DummyClassLoader()); } @@ -46,27 +47,19 @@ public void addURL(URL url) { private final DynamicURLClassLoader urlLoader; private final ClassLoader originalLoader; - private final KnotClassDelegate delegate; + private final KnotClassDelegate delegate; KnotClassLoader(boolean isDevelopment, EnvType envType, GameProvider provider) { super(new DynamicURLClassLoader(new URL[0])); this.originalLoader = getClass().getClassLoader(); this.urlLoader = (DynamicURLClassLoader) getParent(); - this.delegate = new KnotClassDelegate(isDevelopment, envType, this, provider); + this.delegate = new KnotClassDelegate<>(isDevelopment, envType, this, originalLoader, provider); } - @Override - public KnotClassDelegate getDelegate() { + KnotClassDelegate getDelegate() { return delegate; } - @Override - public boolean isClassLoaded(String name) { - synchronized (getClassLoadingLock(name)) { - return findLoadedClass(name) != null; - } - } - @Override public URL getResource(String name) { Objects.requireNonNull(name); @@ -148,23 +141,7 @@ public URL nextElement() { @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - synchronized (getClassLoadingLock(name)) { - Class c = findLoadedClass(name); - - if (c == null) { - c = delegate.tryLoadClass(name, false); - - if (c == null) { - c = originalLoader.loadClass(name); - } - } - - if (resolve) { - resolveClass(c); - } - - return c; - } + return delegate.loadClass(name, resolve); } @Override @@ -173,56 +150,46 @@ protected Class findClass(String name) throws ClassNotFoundException { } @Override - public Class loadIntoTarget(String name) throws ClassNotFoundException { - synchronized (getClassLoadingLock(name)) { - Class c = findLoadedClass(name); - - if (c == null) { - c = delegate.tryLoadClass(name, true); - - if (c == null) { - throw new ClassNotFoundException("can't find class "+name); - } - } - - resolveClass(c); - - return c; - } - } - - @Override - public void addURL(URL url) { + public void addUrlFwd(URL url) { urlLoader.addURL(url); } @Override - public InputStream getResourceAsStream(String classFile, boolean allowFromParent) throws IOException { - InputStream inputStream = urlLoader.getResourceAsStream(classFile); - - if (inputStream == null && allowFromParent) { - inputStream = originalLoader.getResourceAsStream(classFile); - } - - return inputStream; + public URL findResourceFwd(String name) { + return urlLoader.findResource(name); } @Override - public Package getPackage(String name) { + public Package getPackageFwd(String name) { return super.getPackage(name); } @Override - public Package definePackage(String name, String specTitle, String specVersion, String specVendor, + public Package definePackageFwd(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException { return super.definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); } + @Override + public Object getClassLoadingLockFwd(String name) { + return super.getClassLoadingLock(name); + } + + @Override + public Class findLoadedClassFwd(String name) { + return super.findLoadedClass(name); + } + @Override public Class defineClassFwd(String name, byte[] b, int off, int len, CodeSource cs) { return super.defineClass(name, b, off, len, cs); } + @Override + public void resolveClassFwd(Class cls) { + super.resolveClass(cls); + } + static { registerAsParallelCapable(); } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoaderInterface.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoaderInterface.java index ce8366415..174767f41 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoaderInterface.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoaderInterface.java @@ -17,19 +17,36 @@ package net.fabricmc.loader.impl.launch.knot; import java.io.IOException; -import java.io.InputStream; import java.net.URL; -import java.security.CodeSource; +import java.util.Collection; +import java.util.jar.Manifest; + +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.impl.game.GameProvider; interface KnotClassLoaderInterface { - KnotClassDelegate getDelegate(); + @SuppressWarnings("resource") + static KnotClassLoaderInterface create(boolean useCompatibility, boolean isDevelopment, EnvType envType, GameProvider provider) { + if (useCompatibility) { + return new KnotCompatibilityClassLoader(isDevelopment, envType, provider).getDelegate(); + } else { + return new KnotClassLoader(isDevelopment, envType, provider).getDelegate(); + } + } + + void initializeTransformers(); + + ClassLoader getClassLoader(); + + void addUrl(URL url); + void setAllowedPrefixes(URL url, String... prefixes); + void setValidParentClassPath(Collection urls); + + Manifest getManifest(URL url); + boolean isClassLoaded(String name); Class loadIntoTarget(String name) throws ClassNotFoundException; - void addURL(URL url); - URL getResource(String name); - InputStream getResourceAsStream(String filename, boolean skipOriginalLoader) throws IOException; - Package getPackage(String name); - Package definePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException; - Class defineClassFwd(String name, byte[] b, int off, int len, CodeSource cs); + byte[] getRawClassBytes(String name) throws IOException; + byte[] getPreMixinClassBytes(String name); } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotCompatibilityClassLoader.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotCompatibilityClassLoader.java index f043d5a1d..31689970a 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotCompatibilityClassLoader.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotCompatibilityClassLoader.java @@ -16,54 +16,29 @@ package net.fabricmc.loader.impl.launch.knot; -import java.io.IOException; -import java.io.InputStream; import java.net.URL; import java.net.URLClassLoader; import java.security.CodeSource; import net.fabricmc.api.EnvType; import net.fabricmc.loader.impl.game.GameProvider; +import net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.ClassLoaderAccess; -class KnotCompatibilityClassLoader extends URLClassLoader implements KnotClassLoaderInterface { - private final KnotClassDelegate delegate; +class KnotCompatibilityClassLoader extends URLClassLoader implements ClassLoaderAccess { + private final KnotClassDelegate delegate; KnotCompatibilityClassLoader(boolean isDevelopment, EnvType envType, GameProvider provider) { super(new URL[0], KnotCompatibilityClassLoader.class.getClassLoader()); - this.delegate = new KnotClassDelegate(isDevelopment, envType, this, provider); + this.delegate = new KnotClassDelegate<>(isDevelopment, envType, this, getParent(), provider); } - @Override - public KnotClassDelegate getDelegate() { + KnotClassDelegate getDelegate() { return delegate; } - @Override - public boolean isClassLoaded(String name) { - synchronized (getClassLoadingLock(name)) { - return findLoadedClass(name) != null; - } - } - @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - synchronized (getClassLoadingLock(name)) { - Class c = findLoadedClass(name); - - if (c == null) { - c = delegate.tryLoadClass(name, false); - - if (c == null) { - c = getParent().loadClass(name); - } - } - - if (resolve) { - resolveClass(c); - } - - return c; - } + return delegate.loadClass(name, resolve); } @Override @@ -72,56 +47,46 @@ protected Class findClass(String name) throws ClassNotFoundException { } @Override - public Class loadIntoTarget(String name) throws ClassNotFoundException { - synchronized (getClassLoadingLock(name)) { - Class c = findLoadedClass(name); - - if (c == null) { - c = delegate.tryLoadClass(name, true); - - if (c == null) { - throw new ClassNotFoundException("can't find class "+name); - } - } - - resolveClass(c); - - return c; - } - } - - @Override - public void addURL(URL url) { + public void addUrlFwd(URL url) { super.addURL(url); } @Override - public InputStream getResourceAsStream(String classFile, boolean allowFromParent) throws IOException { - if (!allowFromParent) { - if (findResource(classFile) == null) { - return null; - } - } - - return super.getResourceAsStream(classFile); + public URL findResourceFwd(String name) { + return findResource(name); } @Override - public Package getPackage(String name) { + public Package getPackageFwd(String name) { return super.getPackage(name); } @Override - public Package definePackage(String name, String specTitle, String specVersion, String specVendor, + public Package definePackageFwd(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException { return super.definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); } + @Override + public Object getClassLoadingLockFwd(String name) { + return super.getClassLoadingLock(name); + } + + @Override + public Class findLoadedClassFwd(String name) { + return super.findLoadedClass(name); + } + @Override public Class defineClassFwd(String name, byte[] b, int off, int len, CodeSource cs) { return super.defineClass(name, b, off, len, cs); } + @Override + public void resolveClassFwd(Class cls) { + super.resolveClass(cls); + } + static { registerAsParallelCapable(); } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/MixinServiceKnot.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/MixinServiceKnot.java index 071b6ee2c..8f230d9ae 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/MixinServiceKnot.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/MixinServiceKnot.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.io.InputStream; -import java.net.URISyntaxException; import java.net.URL; import java.util.Collection; import java.util.Collections; @@ -42,6 +41,7 @@ import org.spongepowered.asm.util.ReEntranceLock; import net.fabricmc.loader.impl.launch.FabricLauncherBase; +import net.fabricmc.loader.impl.util.UrlUtil; public class MixinServiceKnot implements IMixinService, IClassProvider, IClassBytecodeProvider, ITransformerProvider, IClassTracker { static IMixinTransformer transformer; @@ -173,11 +173,7 @@ public Collection getPlatformAgents() { @Override public IContainerHandle getPrimaryContainer() { - try { - return new ContainerHandleURI(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } + return new ContainerHandleURI(UrlUtil.LOADER_CODE_SOURCE.toUri()); } @Override diff --git a/src/main/java/net/fabricmc/loader/impl/util/SimpleClassPath.java b/src/main/java/net/fabricmc/loader/impl/util/SimpleClassPath.java new file mode 100644 index 000000000..dbdd3b973 --- /dev/null +++ b/src/main/java/net/fabricmc/loader/impl/util/SimpleClassPath.java @@ -0,0 +1,143 @@ +/* + * Copyright 2016 FabricMC + * + * 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 net.fabricmc.loader.impl.util; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipError; +import java.util.zip.ZipFile; + +public final class SimpleClassPath implements Closeable { + public SimpleClassPath(List paths) { + this.paths = paths; + this.jarMarkers = new boolean[paths.size()]; + this.openJars = new ZipFile[paths.size()]; + + for (int i = 0; i < jarMarkers.length; i++) { + if (!Files.isDirectory(paths.get(i))) { + jarMarkers[i] = true; + } + } + } + + @Override + public void close() throws IOException { + IOException exc = null; + + for (int i = 0; i < openJars.length; i++) { + Closeable file = openJars[i]; + + try { + if (file != null) file.close(); + } catch (IOException e) { + if (exc == null) { + exc = e; + } else { + exc.addSuppressed(e); + } + } + + openJars[i] = null; + } + + if (exc != null) throw exc; + } + + public List getPaths() { + return paths; + } + + public CpEntry getEntry(String subPath) throws IOException { + for (int i = 0; i < jarMarkers.length; i++) { + if (jarMarkers[i]) { + ZipFile zf = openJars[i]; + + if (zf == null) { + Path path = paths.get(i); + + try { + openJars[i] = zf = new ZipFile(path.toFile()); + } catch (IOException | ZipError e) { + throw new IOException(String.format("error opening %s: %s", path.toAbsolutePath(), e), e); + } + } + + ZipEntry entry = zf.getEntry(subPath); + + if (entry != null) { + return new CpEntry(i, subPath, entry); + } + } else { + Path file = paths.get(i).resolve(subPath); + + if (Files.isRegularFile(file)) { + return new CpEntry(i, subPath, file); + } + } + } + + return null; + } + + public InputStream getInputStream(String subPath) throws IOException { + CpEntry entry = getEntry(subPath); + + return entry != null ? entry.getInputStream() : null; + } + + public final class CpEntry { + private CpEntry(int idx, String subPath, Object instance) { + this.idx = idx; + this.subPath = subPath; + this.instance = instance; + } + + public Path getOrigin() { + return paths.get(idx); + } + + public String getSubPath() { + return subPath; + } + + public InputStream getInputStream() throws IOException { + if (instance instanceof ZipEntry) { + return openJars[idx].getInputStream((ZipEntry) instance); + } else { + return Files.newInputStream((Path) instance); + } + } + + @Override + public String toString() { + return String.format("%s:%s", getOrigin(), subPath); + } + + private final int idx; + private final String subPath; + private final Object instance; + } + + private final List paths; + private final boolean[] jarMarkers; // whether the path is a jar (otherwise plain dir) + private final ZipFile[] openJars; +} diff --git a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java index 0f66ef067..d7e2c75e0 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java +++ b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java @@ -22,7 +22,10 @@ public final class SystemProperties { public static final String SIDE = "fabric.side"; // skips the embedded MC game provider, letting ServiceLoader-provided ones take over public static final String SKIP_MC_PROVIDER = "fabric.skipMcProvider"; + // game jar paths for common/client/server, replaces lookup from class path if present, env specific takes precedence public static final String GAME_JAR_PATH = "fabric.gameJarPath"; + public static final String GAME_JAR_PATH_CLIENT = "fabric.gameJarPath.client"; + public static final String GAME_JAR_PATH_SERVER = "fabric.gameJarPath.server"; // set the game version for the builtin game mod/dependencies, bypassing auto-detection public static final String GAME_VERSION = "fabric.gameVersion"; // fallback log file for the builtin log handler (dumped on exit if not replaced with another handler) @@ -37,8 +40,12 @@ public final class SystemProperties { public static final String PATH_GROUPS = "fabric.classPathGroups"; // throw exceptions from entrypoints, discovery etc. directly instead of gathering and attaching as suppressed public static final String DEBUG_THROW_DIRECTLY = "fabric.debug.throwDirectly"; + // logs class loading errors to uncover caught exceptions without adequate logging + public static final String DEBUG_LOG_CLASS_LOAD_ERRORS = "fabric.debug.logClassLoadErrors"; // logs class transformation errors to uncover caught exceptions without adequate logging public static final String DEBUG_LOG_TRANSFORM_ERRORS = "fabric.debug.logTransformErrors"; + // disables system class path isolation, allowing bogus lib accesses (too early, transient jars) + public static final String DEBUG_DISABLE_CLASS_PATH_ISOLATION = "fabric.debug.disableClassPathIsolation"; // disables mod load order shuffling to be the same in-dev as in production public static final String DEBUG_DISABLE_MOD_SHUFFLE = "fabric.debug.disableModShuffle"; // workaround for bad load order dependencies diff --git a/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java b/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java index a0b6b3260..85d74ac06 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java +++ b/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java @@ -24,9 +24,10 @@ import java.net.URLConnection; import java.nio.file.Path; import java.nio.file.Paths; +import java.security.CodeSource; public final class UrlUtil { - private UrlUtil() { } + public static final Path LOADER_CODE_SOURCE = getCodeSource(UrlUtil.class); public static URL getSource(String filename, URL resourceURL) throws UrlConversionException { URL codeSourceURL; @@ -71,4 +72,15 @@ public static URL asUrl(File file) throws MalformedURLException { public static URL asUrl(Path path) throws MalformedURLException { return path.toUri().toURL(); } + + public static Path getCodeSource(Class cls) { + CodeSource cs = cls.getProtectionDomain().getCodeSource(); + if (cs == null) return null; + + try { + return asPath(cs.getLocation()); + } catch (URISyntaxException e) { + throw ExceptionUtil.wrap(e); + } + } } diff --git a/src/main/resources/fabric-installer.json b/src/main/resources/fabric-installer.json index 93956a795..8360c6d1b 100644 --- a/src/main/resources/fabric-installer.json +++ b/src/main/resources/fabric-installer.json @@ -9,7 +9,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:sponge-mixin:0.11.2+mixin.0.8.5", + "name": "net.fabricmc:sponge-mixin:0.11.3+mixin.0.8.5", "url": "https://maven.fabricmc.net/" }, { diff --git a/src/main/resources/fabric-installer.launchwrapper.json b/src/main/resources/fabric-installer.launchwrapper.json index 9c09d03d6..d38c276e3 100644 --- a/src/main/resources/fabric-installer.launchwrapper.json +++ b/src/main/resources/fabric-installer.launchwrapper.json @@ -12,7 +12,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:sponge-mixin:0.11.2+mixin.0.8.5", + "name": "net.fabricmc:sponge-mixin:0.11.3+mixin.0.8.5", "url": "https://maven.fabricmc.net/" }, { From 775120e0606213e07df0f6c33a2e50d8ce3c8a5c Mon Sep 17 00:00:00 2001 From: Player Date: Mon, 11 Apr 2022 17:23:52 +0200 Subject: [PATCH 09/80] Fix Javadoc for ModOrigin.getParentSubLocation --- src/main/java/net/fabricmc/loader/api/metadata/ModOrigin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/api/metadata/ModOrigin.java b/src/main/java/net/fabricmc/loader/api/metadata/ModOrigin.java index 06a1668fc..bb1540069 100644 --- a/src/main/java/net/fabricmc/loader/api/metadata/ModOrigin.java +++ b/src/main/java/net/fabricmc/loader/api/metadata/ModOrigin.java @@ -51,9 +51,9 @@ public interface ModOrigin { String getParentModId(); /** - * Get the jar or folder paths for a {@link Kind#PATH} origin. + * Get the sub-location within the parent mod for a {@link Kind#NESTED} origin. * - * @return jar or folder paths + * @return sub-location * @throws UnsupportedOperationException for incompatible kinds */ String getParentSubLocation(); From 1bbb92acb23358083c96c6eb14bede03c341981c Mon Sep 17 00:00:00 2001 From: Player Date: Thu, 10 Mar 2022 17:59:12 +0100 Subject: [PATCH 10/80] Handle complex dependency failures more gracefully --- .../loader/impl/discovery/ResultAnalyzer.java | 25 ++++++++++++++++++- .../net/fabricmc/loader/Messages.properties | 8 ++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java b/src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java index b966f0487..b530cd32a 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ResultAnalyzer.java @@ -181,6 +181,8 @@ private static void formatFix(ModSolver.Fix fix, oldModsFormatted, formatVersionRequirements(newMod.getVersionIntervals()))); + boolean foundAny = false; + // check old deps against future mod set to highlight inconsistencies for (ModDependency dep : oldMod.getDependencies()) { if (dep.getKind().isSoft()) continue; @@ -192,6 +194,7 @@ private static void formatFix(ModSolver.Fix fix, pw.printf("\n\t\t - %s", Localization.format("resolution.solution.replaceModVersionDifferent.reqSupportedModVersion", mod.getId(), getVersion(mod))); + foundAny = true; } continue; @@ -202,10 +205,15 @@ private static void formatFix(ModSolver.Fix fix, pw.printf("\n\t\t - %s", Localization.format("resolution.solution.replaceModVersionDifferent.reqSupportedModVersions", addMod.getId(), formatVersionRequirements(addMod.getVersionIntervals()))); + foundAny = true; break; } } } + + if (!foundAny) { + pw.printf("\n\t\t - %s", Localization.format("resolution.solution.replaceModVersionDifferent.unknown")); + } } } } @@ -268,7 +276,22 @@ private static void addErrorToList(ModCandidate mod, ModDependency dep, List Date: Sat, 2 Apr 2022 21:31:22 +0200 Subject: [PATCH 11/80] Delay log output until it can be displayed properly, except when errors occur --- .../impl/game/minecraft/Log4jLogHandler.java | 4 +- .../game/minecraft/MinecraftGameProvider.java | 4 +- .../impl/game/minecraft/Slf4jLogHandler.java | 2 +- .../impl/util/log/BuiltinLogHandler.java | 59 +++++++++++++++---- .../impl/util/log/ConsoleLogHandler.java | 2 +- .../fabricmc/loader/impl/util/log/Log.java | 25 ++++++-- .../loader/impl/util/log/LogHandler.java | 2 +- 7 files changed, 73 insertions(+), 25 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Log4jLogHandler.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Log4jLogHandler.java index 380098ea7..cc3d520e4 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Log4jLogHandler.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Log4jLogHandler.java @@ -46,8 +46,8 @@ public boolean shouldLog(LogLevel level, LogCategory category) { } @Override - public void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean isReplayedBuiltin) { - // TODO: suppress console log output if isReplayedBuiltin is true to avoid duplicate output + public void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean fromReplay, boolean wasSuppressed) { + // TODO: suppress console log output if wasSuppressed is false to avoid duplicate output getLogger(category).log(translateLogLevel(level), msg, exc); } diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index 7b0af4ded..5266fe0cc 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -192,6 +192,8 @@ public boolean locateGame(FabricLauncher launcher, String[] args) { envGameJar = classifier.getOrigin(envGameLib); if (envGameJar == null) return false; + Log.enableBuiltinHandlerBuffering(true); + commonGameJar = classifier.getOrigin(McLibrary.MC_COMMON); if (commonGameJarDeclared && commonGameJar == null) { @@ -360,7 +362,7 @@ private void setupLogHandler(FabricLauncher launcher, boolean useTargetCl) { logHandlerCls = Class.forName(logHandlerClsName); } - Log.init((LogHandler) logHandlerCls.getConstructor().newInstance(), true); + Log.init((LogHandler) logHandlerCls.getConstructor().newInstance()); Thread.currentThread().setContextClassLoader(prevCl); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Slf4jLogHandler.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Slf4jLogHandler.java index bae9f4e67..fcaf66679 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Slf4jLogHandler.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Slf4jLogHandler.java @@ -41,7 +41,7 @@ public boolean shouldLog(LogLevel level, LogCategory category) { } @Override - public void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean isReplayedBuiltin) { + public void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean fromReplay, boolean wasSuppressed) { Logger logger = getLogger(category); if (msg == null) { diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java b/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java index 78d581eca..4effffabc 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java @@ -22,8 +22,8 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.ArrayDeque; -import java.util.Queue; +import java.util.ArrayList; +import java.util.List; import net.fabricmc.loader.impl.util.SystemProperties; @@ -39,7 +39,8 @@ final class BuiltinLogHandler extends ConsoleLogHandler { private static final String DEFAULT_LOG_FILE = "fabricloader.log"; - private final Queue replayBuffer = new ArrayDeque<>(); + private boolean suppressOutput; + private List buffer; private final Thread shutdownHook; BuiltinLogHandler() { @@ -48,12 +49,30 @@ final class BuiltinLogHandler extends ConsoleLogHandler { } @Override - public void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean isReplayedBuiltin) { - super.log(time, level, category, msg, exc, isReplayedBuiltin); + public void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean fromReplay, boolean wasSuppressed) { + boolean output; synchronized (this) { - replayBuffer.add(new ReplayEntry(time, level, category, msg, exc)); + if (!suppressOutput) { + output = true; + } else if (level.isLessThan(LogLevel.ERROR)) { + output = false; + } else { + for (int i = 0; i < buffer.size(); i++) { // index based loop to tolerate replay producing log output by itself + ReplayEntry entry = buffer.get(i); + super.log(entry.time, entry.level, entry.category, entry.msg, entry.exc, true, true); + } + + suppressOutput = false; + output = true; + } + + if (buffer != null) { + buffer.add(new ReplayEntry(time, level, category, msg, exc)); + } } + + if (output) super.log(time, level, category, msg, exc, fromReplay, wasSuppressed); } @Override @@ -69,11 +88,17 @@ public void close() { } } + synchronized void enableBuffering(boolean suppressOutput) { + if (buffer == null) buffer = new ArrayList<>(); + this.suppressOutput |= suppressOutput; + } + synchronized boolean replay(LogHandler target) { - ReplayEntry entry; + if (buffer == null || buffer.isEmpty()) return false; - while ((entry = replayBuffer.poll()) != null) { - target.log(entry.time, entry.level, entry.category, entry.msg, entry.exc, true); + for (int i = 0; i < buffer.size(); i++) { // index based loop to tolerate replay producing log output by itself + ReplayEntry entry = buffer.get(i); + target.log(entry.time, entry.level, entry.category, entry.msg, entry.exc, true, suppressOutput); } return true; @@ -103,7 +128,16 @@ private final class ShutdownHook extends Thread { @Override public void run() { synchronized (BuiltinLogHandler.this) { - if (replayBuffer.isEmpty()) return; + if (buffer == null || buffer.isEmpty()) return; + + if (suppressOutput) { + suppressOutput = false; + + for (int i = 0; i < buffer.size(); i++) { // index based loop to tolerate replay producing log output by itself + ReplayEntry entry = buffer.get(i); + BuiltinLogHandler.super.log(entry.time, entry.level, entry.category, entry.msg, entry.exc, true, true); + } + } String fileName = System.getProperty(SystemProperties.LOG_FILE, DEFAULT_LOG_FILE); if (fileName.isEmpty()) return; @@ -113,9 +147,8 @@ public void run() { Files.createDirectories(file.getParent()); try (Writer writer = Files.newBufferedWriter(file, StandardOpenOption.WRITE, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) { - ReplayEntry entry; - - while ((entry = replayBuffer.poll()) != null) { + for (int i = 0; i < buffer.size(); i++) { // index based loop to tolerate replay producing log output by itself + ReplayEntry entry = buffer.get(i); writer.write(formatLog(entry.time, entry.level, entry.category, entry.msg, entry.exc)); } } diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/ConsoleLogHandler.java b/src/main/java/net/fabricmc/loader/impl/util/log/ConsoleLogHandler.java index 5103a9872..62303c7c6 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/ConsoleLogHandler.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/ConsoleLogHandler.java @@ -24,7 +24,7 @@ public class ConsoleLogHandler implements LogHandler { private static final LogLevel MIN_STDOUT_LEVEL = LogLevel.getDefault(); @Override - public void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean isRelayedBuiltin) { + public void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean fromReplay, boolean wasSuppressed) { String formatted = formatLog(time, level, category, msg, exc); if (level.isLessThan(MIN_STDERR_LEVEL)) { diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/Log.java b/src/main/java/net/fabricmc/loader/impl/util/log/Log.java index 2cb11795f..8fcf147e0 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/Log.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/Log.java @@ -25,17 +25,30 @@ public final class Log { private static LogHandler handler = new BuiltinLogHandler(); - public static void init(LogHandler handler, boolean replayBuiltin) { + public static void init(LogHandler handler) { if (handler == null) throw new NullPointerException("null log handler"); - LogHandler oldhHandler = Log.handler; + LogHandler oldHandler = Log.handler; - if (oldhHandler instanceof BuiltinLogHandler && replayBuiltin) { - ((BuiltinLogHandler) oldhHandler).replay(handler); + if (oldHandler instanceof BuiltinLogHandler) { + ((BuiltinLogHandler) oldHandler).replay(handler); } Log.handler = handler; - oldhHandler.close(); + oldHandler.close(); + } + + /** + * Enable buffering builtin log output instead of emitting it. + * + * @param suppressOutput whether to suppress builtin log output until an ERROR-level log arrives + */ + public static void enableBuiltinHandlerBuffering(boolean suppressOutput) { + LogHandler handler = Log.handler; + + if (handler instanceof BuiltinLogHandler) { + ((BuiltinLogHandler) handler).enableBuffering(suppressOutput); + } } public static void error(LogCategory category, String format, Object... args) { @@ -191,7 +204,7 @@ private static int getRequiredArgs(String format) { } private static void log(LogHandler handler, LogLevel level, LogCategory category, String msg, Throwable exc) { - handler.log(System.currentTimeMillis(), level, category, msg.trim(), exc, false); + handler.log(System.currentTimeMillis(), level, category, msg.trim(), exc, false, false); } public static boolean shouldLog(LogLevel level, LogCategory category) { diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/LogHandler.java b/src/main/java/net/fabricmc/loader/impl/util/log/LogHandler.java index b6f5ddf20..63c04f571 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/LogHandler.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/LogHandler.java @@ -17,7 +17,7 @@ package net.fabricmc.loader.impl.util.log; public interface LogHandler { - void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean isReplayedBuiltin); + void log(long time, LogLevel level, LogCategory category, String msg, Throwable exc, boolean fromReplay, boolean wasSuppressed); boolean shouldLog(LogLevel level, LogCategory category); void close(); } From d04c6bf27ffc91c59f38bb76f330feb53e5f4ac3 Mon Sep 17 00:00:00 2001 From: Player Date: Tue, 19 Apr 2022 21:30:30 +0200 Subject: [PATCH 12/80] Always fall back to lexicographical comparison when StringVersion is involved, always show non-semver warning --- .../impl/metadata/MetadataVerifier.java | 26 +++++++++---------- .../util/version/SemanticVersionImpl.java | 2 +- .../impl/util/version/StringVersion.java | 5 ---- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java b/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java index 3ab34e75f..6af58ab33 100644 --- a/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java +++ b/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java @@ -63,22 +63,22 @@ static void verify(LoaderModMetadata metadata) throws ParseMetadataException { if (metadata.getSchemaVersion() < ModMetadataParser.LATEST_VERSION) { Log.warn(LogCategory.METADATA, "Mod %s uses an outdated schema version: %d < %d", metadata.getId(), metadata.getSchemaVersion(), ModMetadataParser.LATEST_VERSION); } + } - if (!(metadata.getVersion() instanceof SemanticVersion)) { - String version = metadata.getVersion().getFriendlyString(); - VersionParsingException exc; + if (!(metadata.getVersion() instanceof SemanticVersion)) { + String version = metadata.getVersion().getFriendlyString(); + VersionParsingException exc; - try { - SemanticVersion.parse(version); - exc = null; - } catch (VersionParsingException e) { - exc = e; - } + try { + SemanticVersion.parse(version); + exc = null; + } catch (VersionParsingException e) { + exc = e; + } - if (exc != null) { - Log.warn(LogCategory.METADATA, "Mod %s uses the version %s which isn't compatible with Loader's extended semantic version format (%s), SemVer is recommended for reliable dependency comparisons", - metadata.getId(), version, exc.getMessage()); - } + if (exc != null) { + Log.warn(LogCategory.METADATA, "Mod %s uses the version %s which isn't compatible with Loader's extended semantic version format (%s), SemVer is recommended for reliably evaluating dependencies and prioritizing newer version", + metadata.getId(), version, exc.getMessage()); } metadata.emitFormatWarnings(); diff --git a/src/main/java/net/fabricmc/loader/impl/util/version/SemanticVersionImpl.java b/src/main/java/net/fabricmc/loader/impl/util/version/SemanticVersionImpl.java index 048c450af..b8f2d2d59 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/version/SemanticVersionImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/util/version/SemanticVersionImpl.java @@ -252,7 +252,7 @@ public boolean equalsComponentsExactly(SemanticVersionImpl other) { @Override public int compareTo(Version other) { if (!(other instanceof SemanticVersion)) { - return 1; + return getFriendlyString().compareTo(other.getFriendlyString()); } SemanticVersion o = (SemanticVersion) other; diff --git a/src/main/java/net/fabricmc/loader/impl/util/version/StringVersion.java b/src/main/java/net/fabricmc/loader/impl/util/version/StringVersion.java index e32019ded..9914d7165 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/version/StringVersion.java +++ b/src/main/java/net/fabricmc/loader/impl/util/version/StringVersion.java @@ -16,7 +16,6 @@ package net.fabricmc.loader.impl.util.version; -import net.fabricmc.loader.api.SemanticVersion; import net.fabricmc.loader.api.Version; public class StringVersion implements Version { @@ -42,10 +41,6 @@ public boolean equals(Object obj) { @Override public int compareTo(Version o) { - if (o instanceof SemanticVersion) { - return -1; - } - return getFriendlyString().compareTo(o.getFriendlyString()); } From 640a7f0a4f3f49e9b8ef9853155a7d1b232c3a28 Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 20 Apr 2022 21:16:07 +0200 Subject: [PATCH 13/80] Update ASM, TR and bump version --- build.gradle | 2 +- gradle.properties | 2 +- .../net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- src/main/resources/fabric-installer.json | 12 ++++++------ .../resources/fabric-installer.launchwrapper.json | 12 ++++++------ 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index f3ee237d7..eab165844 100644 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,7 @@ dependencies { exclude module: 'guava' } api 'net.fabricmc:tiny-mappings-parser:0.3.0+build.17' - api 'net.fabricmc:tiny-remapper:0.8.1' + api 'net.fabricmc:tiny-remapper:0.8.2' api 'net.fabricmc:access-widener:2.1.0' include 'org.ow2.sat4j:org.ow2.sat4j.core:2.3.6' diff --git a/gradle.properties b/gradle.properties index 27046f70c..64d0f93b7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,5 +5,5 @@ group = net.fabricmc description = The mod loading component of Fabric url = https://github.com/FabricMC/fabric-loader -asm_version = 9.2 +asm_version = 9.3 mixin_version = 0.11.3+mixin.0.8.5 diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 5f4dcc766..dd6085018 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -71,7 +71,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.13.3"; + public static final String VERSION = "0.14.0"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir diff --git a/src/main/resources/fabric-installer.json b/src/main/resources/fabric-installer.json index 8360c6d1b..98170817d 100644 --- a/src/main/resources/fabric-installer.json +++ b/src/main/resources/fabric-installer.json @@ -13,7 +13,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:tiny-remapper:0.8.1", + "name": "net.fabricmc:tiny-remapper:0.8.2", "url": "https://maven.fabricmc.net/" }, { @@ -21,23 +21,23 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm:9.2", + "name": "org.ow2.asm:asm:9.3", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-analysis:9.2", + "name": "org.ow2.asm:asm-analysis:9.3", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-commons:9.2", + "name": "org.ow2.asm:asm-commons:9.3", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-tree:9.2", + "name": "org.ow2.asm:asm-tree:9.3", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-util:9.2", + "name": "org.ow2.asm:asm-util:9.3", "url": "https://maven.fabricmc.net/" } ], diff --git a/src/main/resources/fabric-installer.launchwrapper.json b/src/main/resources/fabric-installer.launchwrapper.json index d38c276e3..ce083583f 100644 --- a/src/main/resources/fabric-installer.launchwrapper.json +++ b/src/main/resources/fabric-installer.launchwrapper.json @@ -16,7 +16,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:tiny-remapper:0.8.1", + "name": "net.fabricmc:tiny-remapper:0.8.2", "url": "https://maven.fabricmc.net/" }, { @@ -24,23 +24,23 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm:9.2", + "name": "org.ow2.asm:asm:9.3", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-analysis:9.2", + "name": "org.ow2.asm:asm-analysis:9.3", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-commons:9.2", + "name": "org.ow2.asm:asm-commons:9.3", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-tree:9.2", + "name": "org.ow2.asm:asm-tree:9.3", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-util:9.2", + "name": "org.ow2.asm:asm-util:9.3", "url": "https://maven.fabricmc.net/" } ], From 2858857d07e10d2aa9788a5510ece1dea9e7a674 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 20 Apr 2022 20:35:03 +0100 Subject: [PATCH 14/80] Fix release.yml See: https://github.com/actions/checkout/issues/760 --- .github/workflows/release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index accce31a5..16c262bde 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,6 +12,7 @@ jobs: options: --user root steps: - run: apt update && apt install git -y && git --version + - run: git config --global --add safe.directory /__w/fabric-loader/fabric-loader - uses: actions/checkout@v2 with: fetch-depth: 0 From 1e356694e576d01e347cccac060aea46eb3ce068 Mon Sep 17 00:00:00 2001 From: Player Date: Fri, 22 Apr 2022 05:43:25 +0200 Subject: [PATCH 15/80] Remove theoretically superfluous in-dev class loader population --- .../loader/impl/FabricLoaderImpl.java | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index dd6085018..bd174b37d 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -21,12 +21,10 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -61,7 +59,6 @@ import net.fabricmc.loader.impl.metadata.VersionOverrides; import net.fabricmc.loader.impl.util.DefaultLanguageAdapter; import net.fabricmc.loader.impl.util.SystemProperties; -import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; @@ -309,34 +306,6 @@ private void finishModLoading() { } } - if (isDevelopmentEnvironment()) { - // Many development environments will provide classes and resources as separate directories to the classpath. - // As such, we're adding them to the classpath here and now. - // To avoid tripping loader-side checks, we also don't add URLs already in modsList. - // TODO: Perhaps a better solution would be to add the Sources of all parsed entrypoints. But this will do, for now. - - Set knownModPaths = new HashSet<>(); - - for (ModContainerImpl mod : mods) { - for (Path path : mod.getCodeSourcePaths()) { - knownModPaths.add(path.toAbsolutePath().normalize()); - } - } - - // suppress fabric loader explicitly in case its fabric.mod.json is in a different folder from the classes - knownModPaths.add(UrlUtil.LOADER_CODE_SOURCE.toAbsolutePath().normalize()); - - for (String pathName : System.getProperty("java.class.path", "").split(File.pathSeparator)) { - if (pathName.isEmpty() || pathName.endsWith("*")) continue; - - Path path = Paths.get(pathName).toAbsolutePath().normalize(); - - if (Files.isDirectory(path) && knownModPaths.add(path)) { - FabricLauncherBase.getLauncher().addToClassPath(path); - } - } - } - setupLanguageAdapters(); setupMods(); } From 3432be66479a6de92aeb9b215cce9a5e16db1659 Mon Sep 17 00:00:00 2001 From: Player Date: Fri, 22 Apr 2022 05:44:14 +0200 Subject: [PATCH 16/80] Rework URL handling to support special characters better --- .../impl/launch/knot/KnotClassDelegate.java | 67 ++++++++++--------- .../fabricmc/loader/impl/util/UrlUtil.java | 33 ++++++--- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index 5d0bbf63e..4cdedc88d 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.lang.reflect.Constructor; import java.net.JarURLConnection; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; @@ -72,7 +73,7 @@ static final class Metadata { private static final ClassLoader PLATFORM_CLASS_LOADER = getPlatformClassLoader(); - private final Map metadataCache = new ConcurrentHashMap<>(); + private final Map metadataCache = new ConcurrentHashMap<>(); private final T classLoader; private final ClassLoader parentClassLoader; private final GameProvider provider; @@ -80,9 +81,9 @@ static final class Metadata { private final EnvType envType; private IMixinTransformer mixinTransformer; private boolean transformInitialized = false; - private volatile Set urls = Collections.emptySet(); - private volatile Set validParentUrls = Collections.emptySet(); - private final Map allowedPrefixes = new ConcurrentHashMap<>(); + private volatile Set uris = Collections.emptySet(); + private volatile Set validParentUris = Collections.emptySet(); + private final Map allowedPrefixes = new ConcurrentHashMap<>(); private final Set parentSourcedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); KnotClassDelegate(boolean isDevelopment, EnvType envType, T classLoader, ClassLoader parentClassLoader, GameProvider provider) { @@ -128,17 +129,17 @@ private IMixinTransformer getMixinTransformer() { @Override public void addUrl(URL url) { - String urlStr = url.toString(); + URI uri = UrlUtil.asUri(url); synchronized (this) { - Set urls = this.urls; - if (urls.contains(urlStr)) return; + Set urls = this.uris; + if (urls.contains(uri)) return; - Set newUrls = new HashSet<>(urls.size() + 1, 1); + Set newUrls = new HashSet<>(urls.size() + 1, 1); newUrls.addAll(urls); - newUrls.add(urlStr); + newUrls.add(uri); - this.urls = newUrls; + this.uris = newUrls; } classLoader.addUrlFwd(url); @@ -146,27 +147,29 @@ public void addUrl(URL url) { @Override public void setAllowedPrefixes(URL url, String... prefixes) { + URI uri = UrlUtil.asUri(url); + if (prefixes.length == 0) { - allowedPrefixes.remove(url); + allowedPrefixes.remove(uri); } else { - allowedPrefixes.put(url, prefixes); + allowedPrefixes.put(uri, prefixes); } } @Override public void setValidParentClassPath(Collection urls) { - Set urlStrs = new HashSet<>(urls.size(), 1); + Set uris = new HashSet<>(urls.size(), 1); for (URL url : urls) { - urlStrs.add(url.toString()); + uris.add(UrlUtil.asUri(url)); } - this.validParentUrls = urlStrs; + this.validParentUris = uris; } @Override public Manifest getManifest(URL url) { - return getMetadata(url).manifest; + return getMetadata(UrlUtil.asUri(url)).manifest; } @Override @@ -239,20 +242,20 @@ Class loadClass(String name, boolean resolve) throws ClassNotFoundException { /** * Check if an url is loadable by the parent class loader. * - *

This handles explicit parent url whitelisting by {@link #validParentUrls} or shadowing by {@link #urls} + *

This handles explicit parent url whitelisting by {@link #validParentUris} or shadowing by {@link #uris} */ private boolean isValidParentUrl(URL url, String fileName) { if (url == null) return false; if (DISABLE_ISOLATION) return true; try { - String srcUrl = UrlUtil.getSource(fileName, url).toString(); - Set validParentUrls = this.validParentUrls; + URI srcUri = UrlUtil.getSource(fileName, url); + Set validParentUris = this.validParentUris; - if (validParentUrls != null) { // explicit whitelist (in addition to platform cl classes) - return validParentUrls.contains(srcUrl) || PLATFORM_CLASS_LOADER.getResource(fileName) != null; + if (validParentUris != null) { // explicit whitelist (in addition to platform cl classes) + return validParentUris.contains(srcUri) || PLATFORM_CLASS_LOADER.getResource(fileName) != null; } else { // reject urls shadowed by this cl - return !urls.contains(srcUrl); + return !uris.contains(srcUri); } } catch (UrlConversionException e) { throw new RuntimeException(e); @@ -269,7 +272,7 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound String[] prefixes; if (url != null - && (prefixes = allowedPrefixes.get(url)) != null) { + && (prefixes = allowedPrefixes.get(UrlUtil.asUri(url))) != null) { assert prefixes.length > 0; boolean found = false; @@ -329,32 +332,32 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound Metadata getMetadata(String name, URL resourceURL) { if (resourceURL == null) return Metadata.EMPTY; - URL codeSourceUrl = null; + URI codeSource = null; try { - codeSourceUrl = UrlUtil.getSource(LoaderUtil.getClassFileName(name), resourceURL); + codeSource = UrlUtil.getSource(LoaderUtil.getClassFileName(name), resourceURL); } catch (UrlConversionException e) { System.err.println("Could not find code source for " + resourceURL + ": " + e.getMessage()); } - if (codeSourceUrl == null) return Metadata.EMPTY; + if (codeSource == null) return Metadata.EMPTY; - return getMetadata(codeSourceUrl); + return getMetadata(codeSource); } - Metadata getMetadata(URL codeSourceUrl) { - return metadataCache.computeIfAbsent(codeSourceUrl.toString(), (codeSourceStr) -> { + Metadata getMetadata(URI codeSourceUri) { + return metadataCache.computeIfAbsent(codeSourceUri, (URI uri) -> { Manifest manifest = null; CodeSource codeSource = null; Certificate[] certificates = null; try { - Path path = UrlUtil.asPath(codeSourceUrl); + Path path = UrlUtil.asPath(uri); if (Files.isDirectory(path)) { manifest = ManifestUtil.readManifest(path); } else { - URLConnection connection = new URL("jar:" + codeSourceStr + "!/").openConnection(); + URLConnection connection = new URL("jar:" + uri.toString() + "!/").openConnection(); if (connection instanceof JarURLConnection) { manifest = ((JarURLConnection) connection).getManifest(); @@ -381,7 +384,7 @@ Metadata getMetadata(URL codeSourceUrl) { } if (codeSource == null) { - codeSource = new CodeSource(codeSourceUrl, certificates); + codeSource = new CodeSource(UrlUtil.asUrl(uri), certificates); } return new Metadata(manifest, codeSource); diff --git a/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java b/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java index 85d74ac06..69859a666 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java +++ b/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java @@ -19,6 +19,7 @@ import java.io.File; import java.net.JarURLConnection; import java.net.MalformedURLException; +import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; @@ -29,19 +30,17 @@ public final class UrlUtil { public static final Path LOADER_CODE_SOURCE = getCodeSource(UrlUtil.class); - public static URL getSource(String filename, URL resourceURL) throws UrlConversionException { - URL codeSourceURL; - + public static URI getSource(String filename, URL resourceURL) throws UrlConversionException { try { URLConnection connection = resourceURL.openConnection(); if (connection instanceof JarURLConnection) { - codeSourceURL = ((JarURLConnection) connection).getJarFileURL(); + return asUri(((JarURLConnection) connection).getJarFileURL()); } else { String path = resourceURL.getPath(); if (path.endsWith(filename)) { - codeSourceURL = new URL(resourceURL.getProtocol(), resourceURL.getHost(), resourceURL.getPort(), path.substring(0, path.length() - filename.length())); + return asUri(new URL(resourceURL.getProtocol(), resourceURL.getHost(), resourceURL.getPort(), path.substring(0, path.length() - filename.length()))); } else { throw new UrlConversionException("Could not figure out code source for file '" + filename + "' and URL '" + resourceURL + "'!"); } @@ -49,8 +48,6 @@ public static URL getSource(String filename, URL resourceURL) throws UrlConversi } catch (Exception e) { throw new UrlConversionException(e); } - - return codeSourceURL; } public static Path getSourcePath(String filename, URL resourceURL) throws UrlConversionException { @@ -61,8 +58,12 @@ public static Path getSourcePath(String filename, URL resourceURL) throws UrlCon } } + public static Path asPath(URI uri) throws URISyntaxException { + return Paths.get(uri); + } + public static Path asPath(URL url) throws URISyntaxException { - return Paths.get(url.toURI()); + return asPath(url.toURI()); } public static URL asUrl(File file) throws MalformedURLException { @@ -73,6 +74,22 @@ public static URL asUrl(Path path) throws MalformedURLException { return path.toUri().toURL(); } + public static URL asUrl(URI uri) { + try { + return uri.toURL(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException(e); + } + } + + public static URI asUri(URL url) { + try { + return url.toURI(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } + public static Path getCodeSource(Class cls) { CodeSource cs = cls.getProtectionDomain().getCodeSource(); if (cs == null) return null; From 3132c2d282356a834146b6da5639fa11f46c51ff Mon Sep 17 00:00:00 2001 From: Player Date: Fri, 22 Apr 2022 20:53:26 +0200 Subject: [PATCH 17/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index bd174b37d..36430f0d7 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -68,7 +68,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.0"; + public static final String VERSION = "0.14.1"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 103764a9a94e7222f33fe6e3842b4badaaaf15c5 Mon Sep 17 00:00:00 2001 From: Player Date: Sat, 23 Apr 2022 00:09:18 +0200 Subject: [PATCH 18/80] Fix server launchers leaking their class path to knot CL --- .../loader/impl/game/LibClassifier.java | 43 +++++++++++++++++-- .../loader/impl/game/LoaderLibrary.java | 20 ++++++++- .../impl/launch/knot/KnotClassDelegate.java | 17 +++++++- .../loader/impl/util/SystemProperties.java | 4 ++ .../loader/impl/util/log/LogCategory.java | 1 + 5 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java index 2abe10f37..bbb0d5f79 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java @@ -36,9 +36,14 @@ import net.fabricmc.api.EnvType; import net.fabricmc.loader.impl.game.LibClassifier.LibraryType; +import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.UrlUtil; +import net.fabricmc.loader.impl.util.log.Log; +import net.fabricmc.loader.impl.util.log.LogCategory; public final class LibClassifier & LibraryType> { + private static final boolean DEBUG = System.getProperty(SystemProperties.DEBUG_LOG_LIB_CLASSIFICATION) != null; + private final List libs; private final Map origins; private final Map localPaths; @@ -58,11 +63,34 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) { } } + StringBuilder sb = DEBUG ? new StringBuilder() : null; + for (LoaderLibrary lib : LoaderLibrary.values()) { - loaderOrigins.add(lib.path); + if (lib.env != null && lib.env != env) continue; + + if (lib.path != null) { + Path path = lib.path.toAbsolutePath().normalize(); + loaderOrigins.add(path); + + if (DEBUG) sb.append(String.format("✅ %s %s%n", lib.name(), path)); + } else { + if (DEBUG) sb.append(String.format("❎ %s%n", lib.name())); + } + } + + Path gameProviderPath = UrlUtil.getCodeSource(gameProvider.getClass()); + + if (gameProviderPath != null) { + gameProviderPath = gameProviderPath.toAbsolutePath().normalize(); + + if (loaderOrigins.add(gameProviderPath)) { + if (DEBUG) sb.append(String.format("✅ gameprovider %s%n", gameProviderPath)); + } + } else { + if (DEBUG) sb.append("❎ gameprovider"); } - loaderOrigins.add(UrlUtil.getCodeSource(gameProvider.getClass())); + if (DEBUG) Log.info(LogCategory.LIB_CLASSIFICATION, "Loader libraries:%n%s", sb); } public void process(URL url) throws IOException { @@ -100,6 +128,7 @@ private static > Set makeSet(L[] libs) { } private void process(Path path, Set excludedLibs) throws IOException { + path = path.toAbsolutePath().normalize(); if (loaderOrigins.contains(path)) return; boolean matched = false; @@ -130,17 +159,23 @@ private void process(Path path, Set excludedLibs) throws IOException { } } } catch (ZipError | IOException e) { - throw new IOException("error reading "+path.toAbsolutePath(), e); + throw new IOException("error reading "+path, e); } } - if (!matched) unmatchedOrigins.add(path); + if (!matched) { + unmatchedOrigins.add(path); + + if (DEBUG) Log.info(LogCategory.LIB_CLASSIFICATION, "unmatched %s", path); + } } private void addLibrary(L lib, Path originPath, String localPath) { Path prev = origins.put(lib, originPath); if (prev != null) throw new IllegalStateException("lib "+lib+" was already added"); localPaths.put(lib, localPath); + + if (DEBUG) Log.info(LogCategory.LIB_CLASSIFICATION, "%s %s (%s)", lib.name(), originPath, localPath); } @SafeVarargs diff --git a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java index a25f2b5ce..5a8c81031 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java @@ -16,6 +16,7 @@ package net.fabricmc.loader.impl.game; +import java.net.URL; import java.nio.file.Path; import org.objectweb.asm.ClassReader; @@ -28,6 +29,8 @@ import org.spongepowered.asm.launch.MixinBootstrap; import net.fabricmc.accesswidener.AccessWidener; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.impl.util.UrlConversionException; import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.mapping.tree.TinyMappingFactory; import net.fabricmc.tinyremapper.TinyRemapper; @@ -44,9 +47,12 @@ enum LoaderLibrary { ASM_TREE(ClassNode.class), ASM_UTIL(CheckClassAdapter.class), SAT4J_CORE(ContradictionException.class), - SAT4J_PB(SolverFactory.class); + SAT4J_PB(SolverFactory.class), + SERVER_LAUNCH("fabric-server-launch.properties", EnvType.SERVER), // installer generated jar to run setup loader's class path + SERVER_LAUNCHER("net/fabricmc/installer/ServerLauncher.class", EnvType.SERVER); // installer based launch-through method final Path path; + final EnvType env; LoaderLibrary(Class cls) { this(UrlUtil.getCodeSource(cls)); @@ -56,5 +62,17 @@ enum LoaderLibrary { if (path == null) throw new RuntimeException("missing loader library "+name()); this.path = path; + this.env = null; + } + + LoaderLibrary(String file, EnvType env) { + URL url = LoaderLibrary.class.getClassLoader().getResource(file); + + try { + this.path = url != null ? UrlUtil.getSourcePath(file, url) : null; + this.env = env; + } catch (UrlConversionException e) { + throw new RuntimeException(e); + } } } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index 4cdedc88d..e790e57e2 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -55,7 +55,8 @@ import net.fabricmc.loader.impl.util.log.LogCategory; final class KnotClassDelegate implements KnotClassLoaderInterface { - private static final boolean LOG_CLASS_LOAD_ERRORS = System.getProperty(SystemProperties.DEBUG_LOG_CLASS_LOAD_ERRORS) != null; + private static final boolean LOG_CLASS_LOAD = System.getProperty(SystemProperties.DEBUG_LOG_CLASS_LOAD) != null; + private static final boolean LOG_CLASS_LOAD_ERRORS = LOG_CLASS_LOAD || System.getProperty(SystemProperties.DEBUG_LOG_CLASS_LOAD_ERRORS) != null; private static final boolean LOG_TRANSFORM_ERRORS = System.getProperty(SystemProperties.DEBUG_LOG_TRANSFORM_ERRORS) != null; private static final boolean DISABLE_ISOLATION = System.getProperty(SystemProperties.DEBUG_DISABLE_CLASS_PATH_ISOLATION) != null; @@ -143,6 +144,8 @@ public void addUrl(URL url) { } classLoader.addUrlFwd(url); + + if (LOG_CLASS_LOAD_ERRORS) Log.info(LogCategory.KNOT, "added code source %s", url); } @Override @@ -189,6 +192,8 @@ public Class loadIntoTarget(String name) throws ClassNotFoundException { if (c == null) { throw new ClassNotFoundException("can't find class "+name); + } else if (LOG_CLASS_LOAD) { + Log.info(LogCategory.KNOT, "loaded class %s into target", name); } } @@ -225,8 +230,11 @@ Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, msg); throw new ClassNotFoundException(msg); } else { // load from system cl + if (LOG_CLASS_LOAD) Log.info(LogCategory.KNOT, "loading class %s using the parent class loader", name); c = parentClassLoader.loadClass(name); } + } else if (LOG_CLASS_LOAD) { + Log.info(LogCategory.KNOT, "loaded class %s", name); } } } @@ -464,7 +472,12 @@ private byte[] getRawClassByteArray(String name, boolean allowFromParent) throws if (!allowFromParent) return null; url = parentClassLoader.getResource(name); - if (!isValidParentUrl(url, name)) return null; + + if (!isValidParentUrl(url, name)) { + if (LOG_CLASS_LOAD) Log.info(LogCategory.KNOT, "refusing to load class %s from parent class loader", name); + + return null; + } } try (InputStream inputStream = url.openStream()) { diff --git a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java index d7e2c75e0..f2c18c128 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java +++ b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java @@ -40,6 +40,10 @@ public final class SystemProperties { public static final String PATH_GROUPS = "fabric.classPathGroups"; // throw exceptions from entrypoints, discovery etc. directly instead of gathering and attaching as suppressed public static final String DEBUG_THROW_DIRECTLY = "fabric.debug.throwDirectly"; + // logs library classification activity + public static final String DEBUG_LOG_LIB_CLASSIFICATION = "fabric.debug.logLibClassification"; + // logs class loading + public static final String DEBUG_LOG_CLASS_LOAD = "fabric.debug.logClassLoad"; // logs class loading errors to uncover caught exceptions without adequate logging public static final String DEBUG_LOG_CLASS_LOAD_ERRORS = "fabric.debug.logClassLoadErrors"; // logs class transformation errors to uncover caught exceptions without adequate logging diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/LogCategory.java b/src/main/java/net/fabricmc/loader/impl/util/log/LogCategory.java index 6bd9e13b8..c789a2f56 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/LogCategory.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/LogCategory.java @@ -24,6 +24,7 @@ public final class LogCategory { public static final LogCategory GAME_REMAP = new LogCategory("GameRemap"); public static final LogCategory GENERAL = new LogCategory(); public static final LogCategory KNOT = new LogCategory("Knot"); + public static final LogCategory LIB_CLASSIFICATION = new LogCategory("LibClassify"); public static final LogCategory LOG = new LogCategory("Log"); public static final LogCategory MAPPINGS = new LogCategory("Mappings"); public static final LogCategory METADATA = new LogCategory("Metadata"); From eacbf5aee4f6695ca0201dcc46d01bf5109e4d78 Mon Sep 17 00:00:00 2001 From: Player Date: Sat, 23 Apr 2022 02:01:11 +0200 Subject: [PATCH 19/80] Move everything to Path as URIs still don't compare with sufficient flexibility --- .../ClasspathModCandidateFinder.java | 2 +- .../loader/impl/game/GameProviderHelper.java | 4 +- .../loader/impl/game/LoaderLibrary.java | 2 +- .../loader/impl/launch/knot/Knot.java | 35 +--- .../impl/launch/knot/KnotClassDelegate.java | 163 ++++++++++-------- .../launch/knot/KnotClassLoaderInterface.java | 10 +- .../fabricmc/loader/impl/util/UrlUtil.java | 45 +---- .../impl/util/log/BuiltinLogHandler.java | 2 +- 8 files changed, 114 insertions(+), 149 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java index bd1ae5070..12d93982d 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java @@ -52,7 +52,7 @@ public void findCandidates(ModCandidateConsumer out) { URL url = mods.nextElement(); try { - Path path = UrlUtil.getSourcePath("fabric.mod.json", url).toAbsolutePath().normalize(); + Path path = UrlUtil.getCodeSource(url, "fabric.mod.json").toAbsolutePath().normalize(); List paths = pathGroups.get(path); if (paths == null) { diff --git a/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java b/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java index 1604cf068..045cb0aef 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java +++ b/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java @@ -79,7 +79,7 @@ public static Optional getSource(ClassLoader loader, String filename) { if ((url = loader.getResource(filename)) != null) { try { - return Optional.of(UrlUtil.getSourcePath(filename, url)); + return Optional.of(UrlUtil.getCodeSource(url, filename)); } catch (UrlConversionException e) { // TODO: Point to a logger e.printStackTrace(); @@ -98,7 +98,7 @@ public static List getSources(ClassLoader loader, String filename) { URL url = urls.nextElement(); try { - paths.add(UrlUtil.getSourcePath(filename, url)); + paths.add(UrlUtil.getCodeSource(url, filename)); } catch (UrlConversionException e) { // TODO: Point to a logger e.printStackTrace(); diff --git a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java index 5a8c81031..53a48bd7e 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java @@ -69,7 +69,7 @@ enum LoaderLibrary { URL url = LoaderLibrary.class.getClassLoader().getResource(file); try { - this.path = url != null ? UrlUtil.getSourcePath(file, url) : null; + this.path = url != null ? UrlUtil.getCodeSource(url, file) : null; this.env = env; } catch (UrlConversionException e) { throw new RuntimeException(e); diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java index 777ab21d6..ea591fedb 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java @@ -19,8 +19,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -263,37 +261,18 @@ public List getClassPath() { public void addToClassPath(Path path, String... allowedPrefixes) { Log.debug(LogCategory.KNOT, "Adding " + path + " to classpath."); - try { - URL url = UrlUtil.asUrl(path); - classLoader.setAllowedPrefixes(url, allowedPrefixes); - classLoader.addUrl(url); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } + classLoader.setAllowedPrefixes(path, allowedPrefixes); + classLoader.addCodeSource(path); } @Override public void setAllowedPrefixes(Path path, String... prefixes) { - try { - classLoader.setAllowedPrefixes(UrlUtil.asUrl(path), prefixes); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } + classLoader.setAllowedPrefixes(path, prefixes); } @Override public void setValidParentClassPath(Collection paths) { - List urls = new ArrayList<>(paths.size()); - - try { - for (Path path : paths) { - urls.add(UrlUtil.asUrl(path)); - } - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - - classLoader.setValidParentClassPath(urls); + classLoader.setValidParentClassPath(paths); } @Override @@ -334,11 +313,7 @@ public byte[] getClassByteArray(String name, boolean runTransformers) throws IOE @Override public Manifest getManifest(Path originPath) { - try { - return classLoader.getManifest(UrlUtil.asUrl(originPath)); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } + return classLoader.getManifest(originPath); } @Override diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index e790e57e2..839c5bd11 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -21,8 +21,7 @@ import java.io.InputStream; import java.lang.reflect.Constructor; import java.net.JarURLConnection; -import java.net.URI; -import java.net.URISyntaxException; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.nio.file.FileSystemNotFoundException; @@ -74,7 +73,7 @@ static final class Metadata { private static final ClassLoader PLATFORM_CLASS_LOADER = getPlatformClassLoader(); - private final Map metadataCache = new ConcurrentHashMap<>(); + private final Map metadataCache = new ConcurrentHashMap<>(); private final T classLoader; private final ClassLoader parentClassLoader; private final GameProvider provider; @@ -82,9 +81,9 @@ static final class Metadata { private final EnvType envType; private IMixinTransformer mixinTransformer; private boolean transformInitialized = false; - private volatile Set uris = Collections.emptySet(); - private volatile Set validParentUris = Collections.emptySet(); - private final Map allowedPrefixes = new ConcurrentHashMap<>(); + private volatile Set codeSources = Collections.emptySet(); + private volatile Set validParentCodeSources = Collections.emptySet(); + private final Map allowedPrefixes = new ConcurrentHashMap<>(); private final Set parentSourcedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>()); KnotClassDelegate(boolean isDevelopment, EnvType envType, T classLoader, ClassLoader parentClassLoader, GameProvider provider) { @@ -129,50 +128,54 @@ private IMixinTransformer getMixinTransformer() { } @Override - public void addUrl(URL url) { - URI uri = UrlUtil.asUri(url); + public void addCodeSource(Path path) { + path = path.toAbsolutePath().normalize(); synchronized (this) { - Set urls = this.uris; - if (urls.contains(uri)) return; + Set codeSources = this.codeSources; + if (codeSources.contains(path)) return; - Set newUrls = new HashSet<>(urls.size() + 1, 1); - newUrls.addAll(urls); - newUrls.add(uri); + Set newCodeSources = new HashSet<>(codeSources.size() + 1, 1); + newCodeSources.addAll(codeSources); + newCodeSources.add(path); - this.uris = newUrls; + this.codeSources = newCodeSources; } - classLoader.addUrlFwd(url); + try { + classLoader.addUrlFwd(UrlUtil.asUrl(path)); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } - if (LOG_CLASS_LOAD_ERRORS) Log.info(LogCategory.KNOT, "added code source %s", url); + if (LOG_CLASS_LOAD_ERRORS) Log.info(LogCategory.KNOT, "added code source %s", path); } @Override - public void setAllowedPrefixes(URL url, String... prefixes) { - URI uri = UrlUtil.asUri(url); + public void setAllowedPrefixes(Path codeSource, String... prefixes) { + codeSource = codeSource.toAbsolutePath().normalize(); if (prefixes.length == 0) { - allowedPrefixes.remove(uri); + allowedPrefixes.remove(codeSource); } else { - allowedPrefixes.put(uri, prefixes); + allowedPrefixes.put(codeSource, prefixes); } } @Override - public void setValidParentClassPath(Collection urls) { - Set uris = new HashSet<>(urls.size(), 1); + public void setValidParentClassPath(Collection paths) { + Set validPaths = new HashSet<>(paths.size(), 1); - for (URL url : urls) { - uris.add(UrlUtil.asUri(url)); + for (Path path : paths) { + validPaths.add(path.toAbsolutePath().normalize()); } - this.validParentUris = uris; + this.validParentCodeSources = validPaths; } @Override - public Manifest getManifest(URL url) { - return getMetadata(UrlUtil.asUri(url)).manifest; + public Manifest getManifest(Path codeSource) { + return getMetadata(codeSource).manifest; } @Override @@ -250,20 +253,21 @@ Class loadClass(String name, boolean resolve) throws ClassNotFoundException { /** * Check if an url is loadable by the parent class loader. * - *

This handles explicit parent url whitelisting by {@link #validParentUris} or shadowing by {@link #uris} + *

This handles explicit parent url whitelisting by {@link #validParentCodeSources} or shadowing by {@link #codeSources} */ private boolean isValidParentUrl(URL url, String fileName) { if (url == null) return false; if (DISABLE_ISOLATION) return true; + if (url.getProtocol().equals("jrt")) return true; try { - URI srcUri = UrlUtil.getSource(fileName, url); - Set validParentUris = this.validParentUris; + Path codeSource = UrlUtil.getCodeSource(url, fileName); + Set validParentCodeSources = this.validParentCodeSources; - if (validParentUris != null) { // explicit whitelist (in addition to platform cl classes) - return validParentUris.contains(srcUri) || PLATFORM_CLASS_LOADER.getResource(fileName) != null; + if (validParentCodeSources != null) { // explicit whitelist (in addition to platform cl classes) + return validParentCodeSources.contains(codeSource) || PLATFORM_CLASS_LOADER.getResource(fileName) != null; } else { // reject urls shadowed by this cl - return !uris.contains(srcUri); + return !codeSources.contains(codeSource); } } catch (UrlConversionException e) { throw new RuntimeException(e); @@ -276,25 +280,36 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound } if (!allowedPrefixes.isEmpty() && !DISABLE_ISOLATION) { // check prefix restrictions (allows exposing libraries partially during startup) - URL url = classLoader.getResource(LoaderUtil.getClassFileName(name)); - String[] prefixes; - - if (url != null - && (prefixes = allowedPrefixes.get(UrlUtil.asUri(url))) != null) { - assert prefixes.length > 0; - boolean found = false; - - for (String prefix : prefixes) { - if (name.startsWith(prefix)) { - found = true; - break; - } + String fileName = LoaderUtil.getClassFileName(name); + URL url = classLoader.getResource(fileName); + + if (url != null) { + Path codeSource; + + try { + codeSource = UrlUtil.getCodeSource(url, fileName); + } catch (UrlConversionException e) { + throw new RuntimeException(e); } - if (!found) { - String msg = "class "+name+" is currently restricted from being loaded"; - if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, msg); - throw new ClassNotFoundException(msg); + String[] prefixes = allowedPrefixes.get(codeSource); + + if (prefixes != null) { + assert prefixes.length > 0; + boolean found = false; + + for (String prefix : prefixes) { + if (name.startsWith(prefix)) { + found = true; + break; + } + } + + if (!found) { + String msg = "class "+name+" is currently restricted from being loaded"; + if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, msg); + throw new ClassNotFoundException(msg); + } } } } @@ -317,7 +332,7 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound parentSourcedClasses.add(name); } - KnotClassDelegate.Metadata metadata = getMetadata(name, classLoader.getResource(LoaderUtil.getClassFileName(name))); + KnotClassDelegate.Metadata metadata = getMetadata(name); int pkgDelimiterPos = name.lastIndexOf('.'); @@ -337,35 +352,35 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound return classLoader.defineClassFwd(name, input, 0, input.length, metadata.codeSource); } - Metadata getMetadata(String name, URL resourceURL) { - if (resourceURL == null) return Metadata.EMPTY; - - URI codeSource = null; + private Metadata getMetadata(String name) { + String fileName = LoaderUtil.getClassFileName(name); + URL url = classLoader.getResource(fileName); + if (url == null) return Metadata.EMPTY; try { - codeSource = UrlUtil.getSource(LoaderUtil.getClassFileName(name), resourceURL); + Path codeSource = UrlUtil.getCodeSource(url, fileName); + if (codeSource == null) return Metadata.EMPTY; + + return getMetadata(codeSource); } catch (UrlConversionException e) { - System.err.println("Could not find code source for " + resourceURL + ": " + e.getMessage()); + System.err.println("Could not find code source for " + url + ": " + e); + return Metadata.EMPTY; } - - if (codeSource == null) return Metadata.EMPTY; - - return getMetadata(codeSource); } - Metadata getMetadata(URI codeSourceUri) { - return metadataCache.computeIfAbsent(codeSourceUri, (URI uri) -> { + private Metadata getMetadata(Path codeSource) { + codeSource = codeSource.toAbsolutePath().normalize(); + + return metadataCache.computeIfAbsent(codeSource, (Path path) -> { Manifest manifest = null; - CodeSource codeSource = null; + CodeSource cs = null; Certificate[] certificates = null; try { - Path path = UrlUtil.asPath(uri); - if (Files.isDirectory(path)) { manifest = ManifestUtil.readManifest(path); } else { - URLConnection connection = new URL("jar:" + uri.toString() + "!/").openConnection(); + URLConnection connection = new URL("jar:" + path.toUri().toString() + "!/").openConnection(); if (connection instanceof JarURLConnection) { manifest = ((JarURLConnection) connection).getManifest(); @@ -382,20 +397,24 @@ Metadata getMetadata(URI codeSourceUri) { /* JarEntry codeEntry = codeSourceJar.getJarEntry(filename); if (codeEntry != null) { - codeSource = new CodeSource(codeSourceURL, codeEntry.getCodeSigners()); + cs = new CodeSource(codeSourceURL, codeEntry.getCodeSigners()); } */ } - } catch (IOException | FileSystemNotFoundException | URISyntaxException e) { + } catch (IOException | FileSystemNotFoundException e) { if (FabricLauncherBase.getLauncher().isDevelopment()) { Log.warn(LogCategory.KNOT, "Failed to load manifest", e); } } - if (codeSource == null) { - codeSource = new CodeSource(UrlUtil.asUrl(uri), certificates); + if (cs == null) { + try { + cs = new CodeSource(UrlUtil.asUrl(path), certificates); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } } - return new Metadata(manifest, codeSource); + return new Metadata(manifest, cs); }); } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoaderInterface.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoaderInterface.java index 174767f41..517ac7a29 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoaderInterface.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoaderInterface.java @@ -17,7 +17,7 @@ package net.fabricmc.loader.impl.launch.knot; import java.io.IOException; -import java.net.URL; +import java.nio.file.Path; import java.util.Collection; import java.util.jar.Manifest; @@ -38,11 +38,11 @@ static KnotClassLoaderInterface create(boolean useCompatibility, boolean isDevel ClassLoader getClassLoader(); - void addUrl(URL url); - void setAllowedPrefixes(URL url, String... prefixes); - void setValidParentClassPath(Collection urls); + void addCodeSource(Path path); + void setAllowedPrefixes(Path codeSource, String... prefixes); + void setValidParentClassPath(Collection codeSources); - Manifest getManifest(URL url); + Manifest getManifest(Path codeSource); boolean isClassLoaded(String name); Class loadIntoTarget(String name) throws ClassNotFoundException; diff --git a/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java b/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java index 69859a666..92ab63409 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java +++ b/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java @@ -19,7 +19,6 @@ import java.io.File; import java.net.JarURLConnection; import java.net.MalformedURLException; -import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; @@ -30,19 +29,19 @@ public final class UrlUtil { public static final Path LOADER_CODE_SOURCE = getCodeSource(UrlUtil.class); - public static URI getSource(String filename, URL resourceURL) throws UrlConversionException { + public static Path getCodeSource(URL url, String localPath) throws UrlConversionException { try { - URLConnection connection = resourceURL.openConnection(); + URLConnection connection = url.openConnection(); if (connection instanceof JarURLConnection) { - return asUri(((JarURLConnection) connection).getJarFileURL()); + return asPath(((JarURLConnection) connection).getJarFileURL()); } else { - String path = resourceURL.getPath(); + String path = url.getPath(); - if (path.endsWith(filename)) { - return asUri(new URL(resourceURL.getProtocol(), resourceURL.getHost(), resourceURL.getPort(), path.substring(0, path.length() - filename.length()))); + if (path.endsWith(localPath)) { + return asPath(new URL(url.getProtocol(), url.getHost(), url.getPort(), path.substring(0, path.length() - localPath.length()))); } else { - throw new UrlConversionException("Could not figure out code source for file '" + filename + "' and URL '" + resourceURL + "'!"); + throw new UrlConversionException("Could not figure out code source for file '" + localPath + "' in URL '" + url + "'!"); } } } catch (Exception e) { @@ -50,20 +49,8 @@ public static URI getSource(String filename, URL resourceURL) throws UrlConversi } } - public static Path getSourcePath(String filename, URL resourceURL) throws UrlConversionException { - try { - return asPath(getSource(filename, resourceURL)); - } catch (URISyntaxException e) { - throw new UrlConversionException(e); - } - } - - public static Path asPath(URI uri) throws URISyntaxException { - return Paths.get(uri); - } - public static Path asPath(URL url) throws URISyntaxException { - return asPath(url.toURI()); + return Paths.get(url.toURI()); } public static URL asUrl(File file) throws MalformedURLException { @@ -74,22 +61,6 @@ public static URL asUrl(Path path) throws MalformedURLException { return path.toUri().toURL(); } - public static URL asUrl(URI uri) { - try { - return uri.toURL(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException(e); - } - } - - public static URI asUri(URL url) { - try { - return url.toURI(); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - } - public static Path getCodeSource(Class cls) { CodeSource cs = cls.getProtectionDomain().getCodeSource(); if (cs == null) return null; diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java b/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java index 4effffabc..58faa79df 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java @@ -146,7 +146,7 @@ public void run() { Path file = Paths.get(fileName).toAbsolutePath().normalize(); Files.createDirectories(file.getParent()); - try (Writer writer = Files.newBufferedWriter(file, StandardOpenOption.WRITE, StandardOpenOption.APPEND, StandardOpenOption.CREATE)) { + try (Writer writer = Files.newBufferedWriter(file, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { for (int i = 0; i < buffer.size(); i++) { // index based loop to tolerate replay producing log output by itself ReplayEntry entry = buffer.get(i); writer.write(formatLog(entry.time, entry.level, entry.category, entry.msg, entry.exc)); From 3aea0b7bbca214dba85950cd451052ab9bffb084 Mon Sep 17 00:00:00 2001 From: Player Date: Sat, 23 Apr 2022 20:24:30 +0200 Subject: [PATCH 20/80] Fix log replay for very early messages --- .../game/minecraft/MinecraftGameProvider.java | 2 +- .../launchwrapper/FabricTweaker.java | 2 + .../loader/impl/launch/knot/Knot.java | 1 + .../impl/util/log/BuiltinLogHandler.java | 56 ++++++++++++++----- .../fabricmc/loader/impl/util/log/Log.java | 20 +++++-- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index 5266fe0cc..75be774e3 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -192,7 +192,7 @@ public boolean locateGame(FabricLauncher launcher, String[] args) { envGameJar = classifier.getOrigin(envGameLib); if (envGameJar == null) return false; - Log.enableBuiltinHandlerBuffering(true); + Log.configureBuiltin(true, false); commonGameJar = classifier.getOrigin(McLibrary.MC_COMMON); diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java index 0778fdc12..56bd44230 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java @@ -140,6 +140,8 @@ private void init() { throw new RuntimeException("Could not locate Minecraft: provider locate failed"); } + Log.finishBuiltinConfig(); + arguments = null; provider.initialize(this); diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java index ea591fedb..43ab2a9ce 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java @@ -119,6 +119,7 @@ protected ClassLoader init(String[] args) { } provider = createGameProvider(args); + Log.dropUnusedBuiltinReplayBuffer(); Log.info(LogCategory.GAME_PROVIDER, "Loading %s %s with Fabric Loader %s", provider.getGameName(), provider.getRawGameVersion(), FabricLoaderImpl.VERSION); isDevelopment = Boolean.parseBoolean(System.getProperty(SystemProperties.DEVELOPMENT, "false")); diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java b/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java index 58faa79df..68e42263b 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java @@ -39,8 +39,9 @@ final class BuiltinLogHandler extends ConsoleLogHandler { private static final String DEFAULT_LOG_FILE = "fabricloader.log"; - private boolean suppressOutput; - private List buffer; + private boolean configured; + private boolean enableOutput; + private List buffer = new ArrayList<>(); private final Thread shutdownHook; BuiltinLogHandler() { @@ -53,17 +54,12 @@ public void log(long time, LogLevel level, LogCategory category, String msg, Thr boolean output; synchronized (this) { - if (!suppressOutput) { + if (enableOutput) { output = true; } else if (level.isLessThan(LogLevel.ERROR)) { output = false; } else { - for (int i = 0; i < buffer.size(); i++) { // index based loop to tolerate replay producing log output by itself - ReplayEntry entry = buffer.get(i); - super.log(entry.time, entry.level, entry.category, entry.msg, entry.exc, true, true); - } - - suppressOutput = false; + startOutput(); output = true; } @@ -75,6 +71,19 @@ public void log(long time, LogLevel level, LogCategory category, String msg, Thr if (output) super.log(time, level, category, msg, exc, fromReplay, wasSuppressed); } + private void startOutput() { + if (enableOutput) return; + + if (buffer != null) { + for (int i = 0; i < buffer.size(); i++) { // index based loop to tolerate replay producing log output by itself + ReplayEntry entry = buffer.get(i); + super.log(entry.time, entry.level, entry.category, entry.msg, entry.exc, true, true); + } + } + + enableOutput = true; + } + @Override public void close() { Thread shutdownHook = this.shutdownHook; @@ -88,9 +97,26 @@ public void close() { } } - synchronized void enableBuffering(boolean suppressOutput) { - if (buffer == null) buffer = new ArrayList<>(); - this.suppressOutput |= suppressOutput; + synchronized void configure(boolean buffer, boolean output) { + if (!buffer && !output) throw new IllegalArgumentException("can't both disable buffering and the output"); + + if (output) { + startOutput(); + } else { + enableOutput = false; + } + + if (buffer) { + if (this.buffer == null) this.buffer = new ArrayList<>(); + } else { + this.buffer = null; + } + + configured = true; + } + + synchronized void finishConfig() { + if (!configured) configure(false, true); } synchronized boolean replay(LogHandler target) { @@ -98,7 +124,7 @@ synchronized boolean replay(LogHandler target) { for (int i = 0; i < buffer.size(); i++) { // index based loop to tolerate replay producing log output by itself ReplayEntry entry = buffer.get(i); - target.log(entry.time, entry.level, entry.category, entry.msg, entry.exc, true, suppressOutput); + target.log(entry.time, entry.level, entry.category, entry.msg, entry.exc, true, !enableOutput); } return true; @@ -130,8 +156,8 @@ public void run() { synchronized (BuiltinLogHandler.this) { if (buffer == null || buffer.isEmpty()) return; - if (suppressOutput) { - suppressOutput = false; + if (!enableOutput) { + enableOutput = true; for (int i = 0; i < buffer.size(); i++) { // index based loop to tolerate replay producing log output by itself ReplayEntry entry = buffer.get(i); diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/Log.java b/src/main/java/net/fabricmc/loader/impl/util/log/Log.java index 8fcf147e0..ec89fa8ce 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/Log.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/Log.java @@ -39,15 +39,27 @@ public static void init(LogHandler handler) { } /** - * Enable buffering builtin log output instead of emitting it. + * Configure builtin log handler. * - * @param suppressOutput whether to suppress builtin log output until an ERROR-level log arrives + * @param buffer whether to buffer log messages for later replaying + * @param output whether to output log messages directly */ - public static void enableBuiltinHandlerBuffering(boolean suppressOutput) { + public static void configureBuiltin(boolean buffer, boolean output) { LogHandler handler = Log.handler; if (handler instanceof BuiltinLogHandler) { - ((BuiltinLogHandler) handler).enableBuffering(suppressOutput); + ((BuiltinLogHandler) handler).configure(buffer, output); + } + } + + /** + * Finish configuring builtin log handler, using defaults if unconfigured. + */ + public static void finishBuiltinConfig() { + LogHandler handler = Log.handler; + + if (handler instanceof BuiltinLogHandler) { + ((BuiltinLogHandler) handler).finishConfig(); } } From d96da47bf0e8947ff6f7d658eb742198cfc396c8 Mon Sep 17 00:00:00 2001 From: Player Date: Sat, 23 Apr 2022 21:59:23 +0200 Subject: [PATCH 21/80] Group class path warnings --- .../net/fabricmc/loader/impl/launch/knot/Knot.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java index 43ab2a9ce..02cbdf257 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java @@ -102,24 +102,32 @@ protected ClassLoader init(String[] args) { classPath.clear(); + List missing = null; + List unsupported = null; + for (String cpEntry : System.getProperty("java.class.path").split(File.pathSeparator)) { if (cpEntry.equals("*") || cpEntry.endsWith(File.separator + "*")) { - Log.warn(LogCategory.KNOT, "Knot does not support wildcard classpath entries: %s - the game may not load properly!", cpEntry); + if (unsupported == null) unsupported = new ArrayList<>(); + unsupported.add(cpEntry); continue; } Path path = Paths.get(cpEntry); if (!Files.exists(path)) { - Log.warn(LogCategory.KNOT, "Class path entry %s doesn't exist!", cpEntry); + if (missing == null) missing = new ArrayList<>(); + missing.add(cpEntry); continue; } classPath.add(path); } + if (unsupported != null) Log.warn(LogCategory.KNOT, "Knot does not support wildcard class path entries: %s - the game may not load properly!", String.join(", ", unsupported)); + if (missing != null) Log.warn(LogCategory.KNOT, "Class path entries reference missing files: %s - the game may not load properly!", String.join(", ", missing)); + provider = createGameProvider(args); - Log.dropUnusedBuiltinReplayBuffer(); + Log.finishBuiltinConfig(); Log.info(LogCategory.GAME_PROVIDER, "Loading %s %s with Fabric Loader %s", provider.getGameName(), provider.getRawGameVersion(), FabricLoaderImpl.VERSION); isDevelopment = Boolean.parseBoolean(System.getProperty(SystemProperties.DEVELOPMENT, "false")); From 7c83b2604cd678bcb6dd29312e438cd09b8671ca Mon Sep 17 00:00:00 2001 From: Player Date: Sat, 23 Apr 2022 23:51:03 +0200 Subject: [PATCH 22/80] Verify Loader's class loader early --- .../java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 5 +++++ .../java/net/fabricmc/loader/impl/launch/knot/Knot.java | 9 ++++++++- .../loader/impl/launch/knot/KnotClassLoader.java | 1 + .../java/net/fabricmc/loader/impl/util/LoaderUtil.java | 9 +++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 36430f0d7..ef5988b11 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -58,6 +58,7 @@ import net.fabricmc.loader.impl.metadata.LoaderModMetadata; import net.fabricmc.loader.impl.metadata.VersionOverrides; import net.fabricmc.loader.impl.util.DefaultLanguageAdapter; +import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; @@ -533,4 +534,8 @@ public static FabricLoaderImpl get() { return instance; } } + + static { + LoaderUtil.verifyNotInTargetCl(FabricLoaderImpl.class); + } } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java index 02cbdf257..ff32d62b2 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java @@ -44,6 +44,7 @@ import net.fabricmc.loader.impl.game.GameProvider; import net.fabricmc.loader.impl.launch.FabricLauncherBase; import net.fabricmc.loader.impl.launch.FabricMixinBootstrap; +import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.loader.impl.util.log.Log; @@ -306,7 +307,9 @@ public InputStream getResourceAsStream(String name) { @Override public ClassLoader getTargetClassLoader() { - return classLoader.getClassLoader(); + KnotClassLoaderInterface classLoader = this.classLoader; + + return classLoader != null ? classLoader.getClassLoader() : null; } @Override @@ -338,4 +341,8 @@ public String getEntrypoint() { public static void main(String[] args) { new Knot(null).init(args); } + + static { + LoaderUtil.verifyNotInTargetCl(Knot.class); + } } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java index ebaa1088d..8d2bbaff2 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java @@ -29,6 +29,7 @@ import net.fabricmc.loader.impl.game.GameProvider; import net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.ClassLoaderAccess; +// class name referenced by string constant in net.fabricmc.loader.impl.util.LoaderUtil.verifyNotInTargetCl final class KnotClassLoader extends SecureClassLoader implements ClassLoaderAccess { private static final class DynamicURLClassLoader extends URLClassLoader { private DynamicURLClassLoader(URL[] urls) { diff --git a/src/main/java/net/fabricmc/loader/impl/util/LoaderUtil.java b/src/main/java/net/fabricmc/loader/impl/util/LoaderUtil.java index 7e6e0dd03..caafe7cfb 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/LoaderUtil.java +++ b/src/main/java/net/fabricmc/loader/impl/util/LoaderUtil.java @@ -23,6 +23,15 @@ public static String getClassFileName(String className) { return className.replace('.', '/').concat(".class"); } + public static void verifyNotInTargetCl(Class cls) { + if (cls.getClassLoader().getClass().getName().equals("net.fabricmc.loader.impl.launch.knot.KnotClassLoader")) { + // This usually happens when fabric loader has been added to the target class loader. This is a bad state. + // Such additions may be indirect, a JAR can use the Class-Path manifest attribute to drag additional + // libraries with it, likely recursively. + throw new IllegalStateException("trying to load "+cls.getName()+" from target class loader"); + } + } + public static boolean hasMacOs() { return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac"); } From f0318106b49f3a8f8dc6b1d865f9193010d7200c Mon Sep 17 00:00:00 2001 From: Player Date: Sun, 24 Apr 2022 05:49:00 +0200 Subject: [PATCH 23/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index ef5988b11..657a1ca51 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.1"; + public static final String VERSION = "0.14.2"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 2193264aa50045d9224504b07f3b5a68cd762855 Mon Sep 17 00:00:00 2001 From: Player Date: Sun, 24 Apr 2022 07:10:44 +0200 Subject: [PATCH 24/80] Normalize symlinks --- .../launchwrapper/FabricTweaker.java | 7 +-- .../patch/ModClassLoader_125_FML.java | 7 +-- .../discovery/ArgumentModCandidateFinder.java | 3 +- .../ClasspathModCandidateFinder.java | 7 ++- .../loader/impl/discovery/ModDiscoverer.java | 5 +- .../loader/impl/game/GameProviderHelper.java | 6 +- .../loader/impl/game/LibClassifier.java | 14 ++--- .../impl/game/patch/GameTransformer.java | 2 +- .../loader/impl/gui/FabricGuiEntry.java | 2 +- .../impl/launch/knot/KnotClassDelegate.java | 60 ++++++++----------- .../launch/server/FabricServerLauncher.java | 3 +- .../impl/metadata/DependencyOverrides.java | 5 +- .../fabricmc/loader/impl/util/LoaderUtil.java | 20 +++++++ .../loader/impl/util/SimpleClassPath.java | 2 +- .../fabricmc/loader/impl/util/UrlUtil.java | 14 ++--- .../impl/util/log/BuiltinLogHandler.java | 3 +- .../launch/common/FabricLauncherBase.java | 7 +-- .../net/fabricmc/loader/util/UrlUtil.java | 7 ++- 18 files changed, 85 insertions(+), 89 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java index 56bd44230..2326fb46d 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java @@ -23,7 +23,6 @@ import java.io.InputStream; import java.lang.reflect.Field; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -126,11 +125,7 @@ private void init() { classPath.clear(); for (URL url : launchClassLoader.getSources()) { - try { - classPath.add(UrlUtil.asPath(url)); - } catch (URISyntaxException e) { - e.printStackTrace(); - } + classPath.add(UrlUtil.asPath(url)); } GameProvider provider = new MinecraftGameProvider(); diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/ModClassLoader_125_FML.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/ModClassLoader_125_FML.java index 31aa596b9..ce7e2eb7e 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/ModClassLoader_125_FML.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/ModClassLoader_125_FML.java @@ -19,7 +19,6 @@ import java.io.File; import java.io.IOException; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; @@ -44,11 +43,7 @@ public ModClassLoader_125_FML() { @Override protected void addURL(URL url) { - try { - FabricLauncherBase.getLauncher().addToClassPath(UrlUtil.asPath(url)); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } + FabricLauncherBase.getLauncher().addToClassPath(UrlUtil.asPath(url)); URL[] newLocalUrls = new URL[localUrls.length + 1]; System.arraycopy(localUrls, 0, newLocalUrls, 0, localUrls.length); diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ArgumentModCandidateFinder.java b/src/main/java/net/fabricmc/loader/impl/discovery/ArgumentModCandidateFinder.java index 0c1eee7bd..5705d5b3b 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ArgumentModCandidateFinder.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ArgumentModCandidateFinder.java @@ -30,6 +30,7 @@ import net.fabricmc.loader.impl.FabricLoaderImpl; import net.fabricmc.loader.impl.util.Arguments; +import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; @@ -82,7 +83,7 @@ private void addMods(String list, String source, ModCandidateConsumer out) { } private void addMod(String pathStr, String source, ModCandidateConsumer out) { - Path path = Paths.get(pathStr).toAbsolutePath().normalize(); + Path path = LoaderUtil.normalizePath(Paths.get(pathStr)); if (!Files.exists(path)) { // missing Log.warn(LogCategory.DISCOVERY, "Skipping missing %s provided mod path %s", source, path); diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java index 12d93982d..1168041ec 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java @@ -32,6 +32,7 @@ import java.util.Set; import net.fabricmc.loader.impl.launch.FabricLauncherBase; +import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.UrlConversionException; import net.fabricmc.loader.impl.util.UrlUtil; @@ -52,7 +53,7 @@ public void findCandidates(ModCandidateConsumer out) { URL url = mods.nextElement(); try { - Path path = UrlUtil.getCodeSource(url, "fabric.mod.json").toAbsolutePath().normalize(); + Path path = LoaderUtil.normalizeExistingPath(UrlUtil.getCodeSource(url, "fabric.mod.json")); List paths = pathGroups.get(path); if (paths == null) { @@ -93,14 +94,14 @@ private static Map> getPathGroups() { for (String path : group.split(File.pathSeparator)) { if (path.isEmpty()) continue; - Path resolvedPath = Paths.get(path).toAbsolutePath().normalize(); + Path resolvedPath = Paths.get(path); if (!Files.exists(resolvedPath)) { Log.warn(LogCategory.DISCOVERY, "Skipping missing class path group entry %s", path); continue; } - paths.add(resolvedPath); + paths.add(LoaderUtil.normalizeExistingPath(resolvedPath)); } if (paths.size() < 2) { diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java index c8ede4f87..3071fdf8f 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java @@ -60,6 +60,7 @@ import net.fabricmc.loader.impl.metadata.ParseMetadataException; import net.fabricmc.loader.impl.metadata.VersionOverrides; import net.fabricmc.loader.impl.util.ExceptionUtil; +import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; @@ -89,7 +90,7 @@ public List discoverMods(FabricLoaderImpl loader, Map { if (paths.size() == 1) { - Path path = paths.get(0).toAbsolutePath().normalize(); + Path path = LoaderUtil.normalizeExistingPath(paths.get(0)); if (processedPaths.add(path)) { futures.add(pool.submit(new ModScanTask(Collections.singletonList(path), requiresRemap))); @@ -98,7 +99,7 @@ public List discoverMods(FabricLoaderImpl loader, Map normalizedPaths = new ArrayList<>(paths.size()); for (Path path : paths) { - normalizedPaths.add(path.toAbsolutePath().normalize()); + normalizedPaths.add(LoaderUtil.normalizeExistingPath(path)); } if (!processedPaths.containsAll(normalizedPaths)) { diff --git a/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java b/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java index 045cb0aef..17dc37957 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java +++ b/src/main/java/net/fabricmc/loader/impl/game/GameProviderHelper.java @@ -68,10 +68,10 @@ private static Path getGameJar(String property) { String val = System.getProperty(property); if (val == null) return null; - Path path = Paths.get(val).toAbsolutePath().normalize(); - if (!Files.exists(path)) throw new RuntimeException("Game jar "+path+" configured through "+property+" system property doesn't exist"); + Path path = Paths.get(val); + if (!Files.exists(path)) throw new RuntimeException("Game jar "+path+" ("+LoaderUtil.normalizePath(path)+") configured through "+property+" system property doesn't exist"); - return path; + return LoaderUtil.normalizeExistingPath(path); } public static Optional getSource(ClassLoader loader, String filename) { diff --git a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java index bbb0d5f79..c8882dd9d 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java @@ -17,7 +17,6 @@ package net.fabricmc.loader.impl.game; import java.io.IOException; -import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -36,6 +35,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.loader.impl.game.LibClassifier.LibraryType; +import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.loader.impl.util.log.Log; @@ -69,7 +69,7 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) { if (lib.env != null && lib.env != env) continue; if (lib.path != null) { - Path path = lib.path.toAbsolutePath().normalize(); + Path path = LoaderUtil.normalizeExistingPath(lib.path); loaderOrigins.add(path); if (DEBUG) sb.append(String.format("✅ %s %s%n", lib.name(), path)); @@ -81,7 +81,7 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) { Path gameProviderPath = UrlUtil.getCodeSource(gameProvider.getClass()); if (gameProviderPath != null) { - gameProviderPath = gameProviderPath.toAbsolutePath().normalize(); + gameProviderPath = LoaderUtil.normalizeExistingPath(gameProviderPath); if (loaderOrigins.add(gameProviderPath)) { if (DEBUG) sb.append(String.format("✅ gameprovider %s%n", gameProviderPath)); @@ -94,11 +94,7 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) { } public void process(URL url) throws IOException { - try { - process(UrlUtil.asPath(url)); - } catch (URISyntaxException e) { - throw new RuntimeException("invalid url: "+url); - } + process(UrlUtil.asPath(url)); } @SafeVarargs @@ -128,7 +124,7 @@ private static > Set makeSet(L[] libs) { } private void process(Path path, Set excludedLibs) throws IOException { - path = path.toAbsolutePath().normalize(); + path = LoaderUtil.normalizeExistingPath(path); if (loaderOrigins.contains(path)) return; boolean matched = false; diff --git a/src/main/java/net/fabricmc/loader/impl/game/patch/GameTransformer.java b/src/main/java/net/fabricmc/loader/impl/game/patch/GameTransformer.java index f4433c1a0..0a06710c6 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/patch/GameTransformer.java +++ b/src/main/java/net/fabricmc/loader/impl/game/patch/GameTransformer.java @@ -81,7 +81,7 @@ public void locateEntrypoints(FabricLauncher launcher, List gameJars) { try (InputStream is = entry.getInputStream()) { return new ClassReader(is); } catch (IOException | ZipError e) { - throw new RuntimeException(String.format("error reading %s in %s: %s", name, entry.getOrigin().toAbsolutePath(), e), e); + throw new RuntimeException(String.format("error reading %s in %s: %s", name, LoaderUtil.normalizePath(entry.getOrigin()), e), e); } } catch (IOException e) { throw ExceptionUtil.wrap(e); diff --git a/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java b/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java index 9ab1ff302..17f315dd9 100644 --- a/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java +++ b/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java @@ -54,7 +54,7 @@ public static void open(FabricStatusTree tree) throws Exception { } private static void openForked(FabricStatusTree tree) throws IOException, InterruptedException { - Path javaBinDir = Paths.get(System.getProperty("java.home"), "bin").toAbsolutePath(); + Path javaBinDir = LoaderUtil.normalizePath(Paths.get(System.getProperty("java.home"), "bin")); String[] executables = { "javaw.exe", "java.exe", "java" }; Path javaPath = null; diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index 839c5bd11..66431e658 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -44,6 +44,7 @@ import net.fabricmc.loader.impl.launch.FabricLauncherBase; import net.fabricmc.loader.impl.launch.knot.KnotClassDelegate.ClassLoaderAccess; import net.fabricmc.loader.impl.transformer.FabricTransformer; +import net.fabricmc.loader.impl.util.ExceptionUtil; import net.fabricmc.loader.impl.util.FileSystemUtil; import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.ManifestUtil; @@ -129,7 +130,7 @@ private IMixinTransformer getMixinTransformer() { @Override public void addCodeSource(Path path) { - path = path.toAbsolutePath().normalize(); + path = LoaderUtil.normalizeExistingPath(path); synchronized (this) { Set codeSources = this.codeSources; @@ -153,7 +154,7 @@ public void addCodeSource(Path path) { @Override public void setAllowedPrefixes(Path codeSource, String... prefixes) { - codeSource = codeSource.toAbsolutePath().normalize(); + codeSource = LoaderUtil.normalizeExistingPath(codeSource); if (prefixes.length == 0) { allowedPrefixes.remove(codeSource); @@ -167,7 +168,7 @@ public void setValidParentClassPath(Collection paths) { Set validPaths = new HashSet<>(paths.size(), 1); for (Path path : paths) { - validPaths.add(path.toAbsolutePath().normalize()); + validPaths.add(LoaderUtil.normalizeExistingPath(path)); } this.validParentCodeSources = validPaths; @@ -175,7 +176,7 @@ public void setValidParentClassPath(Collection paths) { @Override public Manifest getManifest(Path codeSource) { - return getMetadata(codeSource).manifest; + return getMetadata(LoaderUtil.normalizeExistingPath(codeSource)).manifest; } @Override @@ -229,7 +230,7 @@ Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // loaded by setting validParentUrls and not including "url". Typical causes are: // - accessing classes too early (game libs shouldn't be used until Loader is ready) // - using jars that are only transient (deobfuscation input or pass-through installers) - String msg = "can't load class "+name+" as it hasn't been exposed to the game (yet?)"; + String msg = String.format("can't load class %s at %s as it hasn't been exposed to the game (yet?)", name, getCodeSource(url, fileName)); if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, msg); throw new ClassNotFoundException(msg); } else { // load from system cl @@ -260,17 +261,13 @@ private boolean isValidParentUrl(URL url, String fileName) { if (DISABLE_ISOLATION) return true; if (url.getProtocol().equals("jrt")) return true; - try { - Path codeSource = UrlUtil.getCodeSource(url, fileName); - Set validParentCodeSources = this.validParentCodeSources; + Path codeSource = getCodeSource(url, fileName); + Set validParentCodeSources = this.validParentCodeSources; - if (validParentCodeSources != null) { // explicit whitelist (in addition to platform cl classes) - return validParentCodeSources.contains(codeSource) || PLATFORM_CLASS_LOADER.getResource(fileName) != null; - } else { // reject urls shadowed by this cl - return !codeSources.contains(codeSource); - } - } catch (UrlConversionException e) { - throw new RuntimeException(e); + if (validParentCodeSources != null) { // explicit whitelist (in addition to platform cl classes) + return validParentCodeSources.contains(codeSource) || PLATFORM_CLASS_LOADER.getResource(fileName) != null; + } else { // reject urls shadowed by this cl + return !codeSources.contains(codeSource); } } @@ -283,15 +280,8 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound String fileName = LoaderUtil.getClassFileName(name); URL url = classLoader.getResource(fileName); - if (url != null) { - Path codeSource; - - try { - codeSource = UrlUtil.getCodeSource(url, fileName); - } catch (UrlConversionException e) { - throw new RuntimeException(e); - } - + if (url != null && !url.getProtocol().equals("jrt")) { + Path codeSource = getCodeSource(url, fileName); String[] prefixes = allowedPrefixes.get(codeSource); if (prefixes != null) { @@ -357,20 +347,10 @@ private Metadata getMetadata(String name) { URL url = classLoader.getResource(fileName); if (url == null) return Metadata.EMPTY; - try { - Path codeSource = UrlUtil.getCodeSource(url, fileName); - if (codeSource == null) return Metadata.EMPTY; - - return getMetadata(codeSource); - } catch (UrlConversionException e) { - System.err.println("Could not find code source for " + url + ": " + e); - return Metadata.EMPTY; - } + return getMetadata(getCodeSource(url, fileName)); } private Metadata getMetadata(Path codeSource) { - codeSource = codeSource.toAbsolutePath().normalize(); - return metadataCache.computeIfAbsent(codeSource, (Path path) -> { Manifest manifest = null; CodeSource cs = null; @@ -493,7 +473,7 @@ private byte[] getRawClassByteArray(String name, boolean allowFromParent) throws url = parentClassLoader.getResource(name); if (!isValidParentUrl(url, name)) { - if (LOG_CLASS_LOAD) Log.info(LogCategory.KNOT, "refusing to load class %s from parent class loader", name); + if (LOG_CLASS_LOAD) Log.info(LogCategory.KNOT, "refusing to load class %s at %s from parent class loader", name, getCodeSource(url, name)); return null; } @@ -513,6 +493,14 @@ private byte[] getRawClassByteArray(String name, boolean allowFromParent) throws } } + private static Path getCodeSource(URL url, String fileName) { + try { + return LoaderUtil.normalizeExistingPath(UrlUtil.getCodeSource(url, fileName)); + } catch (UrlConversionException e) { + throw ExceptionUtil.wrap(e); + } + } + private static ClassLoader getPlatformClassLoader() { try { return (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null); // Java 9+ only diff --git a/src/main/java/net/fabricmc/loader/impl/launch/server/FabricServerLauncher.java b/src/main/java/net/fabricmc/loader/impl/launch/server/FabricServerLauncher.java index 8e2cc3839..849180f54 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/server/FabricServerLauncher.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/server/FabricServerLauncher.java @@ -28,6 +28,7 @@ import java.util.Properties; import net.fabricmc.loader.impl.launch.knot.KnotServer; +import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.SystemProperties; public class FabricServerLauncher { @@ -74,7 +75,7 @@ private static void setup(String... runArguments) throws IOException { System.setProperty(SystemProperties.GAME_JAR_PATH, getServerJarPath()); } - Path serverJar = Paths.get(System.getProperty(SystemProperties.GAME_JAR_PATH)).toAbsolutePath().normalize(); + Path serverJar = LoaderUtil.normalizePath(Paths.get(System.getProperty(SystemProperties.GAME_JAR_PATH))); if (!Files.exists(serverJar)) { System.err.println("The Minecraft server .JAR is missing (" + serverJar + ")!"); diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/DependencyOverrides.java b/src/main/java/net/fabricmc/loader/impl/metadata/DependencyOverrides.java index 01c5c4752..4a1037eb0 100644 --- a/src/main/java/net/fabricmc/loader/impl/metadata/DependencyOverrides.java +++ b/src/main/java/net/fabricmc/loader/impl/metadata/DependencyOverrides.java @@ -37,12 +37,13 @@ import net.fabricmc.loader.impl.FormattedException; import net.fabricmc.loader.impl.lib.gson.JsonReader; import net.fabricmc.loader.impl.lib.gson.JsonToken; +import net.fabricmc.loader.impl.util.LoaderUtil; public final class DependencyOverrides { private final Map> dependencyOverrides; public DependencyOverrides(Path configDir) { - Path path = configDir.resolve("fabric_loader_dependencies.json").toAbsolutePath().normalize(); + Path path = configDir.resolve("fabric_loader_dependencies.json"); if (!Files.exists(path)) { dependencyOverrides = Collections.emptyMap(); @@ -52,7 +53,7 @@ public DependencyOverrides(Path configDir) { try (JsonReader reader = new JsonReader(new InputStreamReader(Files.newInputStream(path), StandardCharsets.UTF_8))) { dependencyOverrides = parse(reader); } catch (IOException | ParseMetadataException e) { - throw new FormattedException("Error parsing dependency overrides!", "Failed to parse " + path.toString(), e); + throw new FormattedException("Error parsing dependency overrides!", "Failed to parse " + LoaderUtil.normalizePath(path), e); } } diff --git a/src/main/java/net/fabricmc/loader/impl/util/LoaderUtil.java b/src/main/java/net/fabricmc/loader/impl/util/LoaderUtil.java index caafe7cfb..024d509ee 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/LoaderUtil.java +++ b/src/main/java/net/fabricmc/loader/impl/util/LoaderUtil.java @@ -16,6 +16,10 @@ package net.fabricmc.loader.impl.util; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Locale; public final class LoaderUtil { @@ -23,6 +27,22 @@ public static String getClassFileName(String className) { return className.replace('.', '/').concat(".class"); } + public static Path normalizePath(Path path) { + if (Files.exists(path)) { + return normalizeExistingPath(path); + } else { + return path.toAbsolutePath().normalize(); + } + } + + public static Path normalizeExistingPath(Path path) { + try { + return path.toRealPath(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + public static void verifyNotInTargetCl(Class cls) { if (cls.getClassLoader().getClass().getName().equals("net.fabricmc.loader.impl.launch.knot.KnotClassLoader")) { // This usually happens when fabric loader has been added to the target class loader. This is a bad state. diff --git a/src/main/java/net/fabricmc/loader/impl/util/SimpleClassPath.java b/src/main/java/net/fabricmc/loader/impl/util/SimpleClassPath.java index dbdd3b973..b919065bd 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/SimpleClassPath.java +++ b/src/main/java/net/fabricmc/loader/impl/util/SimpleClassPath.java @@ -77,7 +77,7 @@ public CpEntry getEntry(String subPath) throws IOException { try { openJars[i] = zf = new ZipFile(path.toFile()); } catch (IOException | ZipError e) { - throw new IOException(String.format("error opening %s: %s", path.toAbsolutePath(), e), e); + throw new IOException(String.format("error opening %s: %s", LoaderUtil.normalizePath(path), e), e); } } diff --git a/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java b/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java index 92ab63409..b4fc9f090 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java +++ b/src/main/java/net/fabricmc/loader/impl/util/UrlUtil.java @@ -49,8 +49,12 @@ public static Path getCodeSource(URL url, String localPath) throws UrlConversion } } - public static Path asPath(URL url) throws URISyntaxException { - return Paths.get(url.toURI()); + public static Path asPath(URL url) { + try { + return Paths.get(url.toURI()); + } catch (URISyntaxException e) { + throw ExceptionUtil.wrap(e); + } } public static URL asUrl(File file) throws MalformedURLException { @@ -65,10 +69,6 @@ public static Path getCodeSource(Class cls) { CodeSource cs = cls.getProtectionDomain().getCodeSource(); if (cs == null) return null; - try { - return asPath(cs.getLocation()); - } catch (URISyntaxException e) { - throw ExceptionUtil.wrap(e); - } + return asPath(cs.getLocation()); } } diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java b/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java index 68e42263b..542bdcb39 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/BuiltinLogHandler.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.List; +import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.SystemProperties; /** @@ -169,7 +170,7 @@ public void run() { if (fileName.isEmpty()) return; try { - Path file = Paths.get(fileName).toAbsolutePath().normalize(); + Path file = LoaderUtil.normalizePath(Paths.get(fileName)); Files.createDirectories(file.getParent()); try (Writer writer = Files.newBufferedWriter(file, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) { diff --git a/src/main/legacyJava/net/fabricmc/loader/launch/common/FabricLauncherBase.java b/src/main/legacyJava/net/fabricmc/loader/launch/common/FabricLauncherBase.java index e8a3e7248..854cd7953 100644 --- a/src/main/legacyJava/net/fabricmc/loader/launch/common/FabricLauncherBase.java +++ b/src/main/legacyJava/net/fabricmc/loader/launch/common/FabricLauncherBase.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; -import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; import java.util.ArrayList; @@ -52,11 +51,7 @@ public MappingConfiguration getMappingConfiguration() { @Override public void propose(URL url) { - try { - parent.addToClassPath(UrlUtil.asPath(url)); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } + parent.addToClassPath(UrlUtil.asPath(url)); } @Override diff --git a/src/main/legacyJava/net/fabricmc/loader/util/UrlUtil.java b/src/main/legacyJava/net/fabricmc/loader/util/UrlUtil.java index eb646ad0a..f86c02589 100644 --- a/src/main/legacyJava/net/fabricmc/loader/util/UrlUtil.java +++ b/src/main/legacyJava/net/fabricmc/loader/util/UrlUtil.java @@ -16,10 +16,11 @@ package net.fabricmc.loader.util; -import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Path; +import net.fabricmc.loader.impl.util.ExceptionUtil.WrappedException; + /** * @deprecated Internal API, do not use */ @@ -30,8 +31,8 @@ private UrlUtil() { } public static Path asPath(URL url) throws UrlConversionException { try { return net.fabricmc.loader.impl.util.UrlUtil.asPath(url); - } catch (URISyntaxException e) { - throw new UrlConversionException(e); + } catch (WrappedException e) { + throw new UrlConversionException(e.getCause()); } } } From 3a6911325f92a8639155f3789f5d97662be389cd Mon Sep 17 00:00:00 2001 From: Player Date: Sun, 24 Apr 2022 12:29:25 +0200 Subject: [PATCH 25/80] Support custom server launch jars with extra libraries --- .../loader/impl/game/LibClassifier.java | 38 ++++++++++++++++++- .../loader/impl/game/LoaderLibrary.java | 4 ++ .../loader/impl/util/ManifestUtil.java | 19 ++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java index c8882dd9d..d0f85e276 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java @@ -30,12 +30,16 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; import java.util.zip.ZipError; import java.util.zip.ZipFile; import net.fabricmc.api.EnvType; import net.fabricmc.loader.impl.game.LibClassifier.LibraryType; import net.fabricmc.loader.impl.util.LoaderUtil; +import net.fabricmc.loader.impl.util.ManifestUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.loader.impl.util.log.Log; @@ -50,23 +54,27 @@ public final class LibClassifier & LibraryType> { private final Set loaderOrigins = new HashSet<>(); private final List unmatchedOrigins = new ArrayList<>(); - public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) { + public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) throws IOException { L[] libs = cls.getEnumConstants(); this.libs = new ArrayList<>(libs.length); this.origins = new EnumMap<>(cls); this.localPaths = new EnumMap<>(cls); + // game provider libs + for (L lib : libs) { if (lib.isApplicable(env)) { this.libs.add(lib); } } + // loader libs + StringBuilder sb = DEBUG ? new StringBuilder() : null; for (LoaderLibrary lib : LoaderLibrary.values()) { - if (lib.env != null && lib.env != env) continue; + if (!lib.isApplicable(env)) continue; if (lib.path != null) { Path path = LoaderUtil.normalizeExistingPath(lib.path); @@ -78,6 +86,8 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) { } } + // game provider itself + Path gameProviderPath = UrlUtil.getCodeSource(gameProvider.getClass()); if (gameProviderPath != null) { @@ -91,6 +101,30 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) { } if (DEBUG) Log.info(LogCategory.LIB_CLASSIFICATION, "Loader libraries:%n%s", sb); + + // process indirectly referenced libs + + processManifestClassPath(LoaderLibrary.SERVER_LAUNCH, env); // not used by fabric itself, but others add Log4J this way + } + + private void processManifestClassPath(LoaderLibrary lib, EnvType env) throws IOException { + if (lib.path == null || !lib.isApplicable(env) || !Files.isRegularFile(lib.path)) return; + + Manifest manifest; + + try (ZipFile zf = new ZipFile(lib.path.toFile())) { + ZipEntry entry = zf.getEntry(JarFile.MANIFEST_NAME); + if (entry == null) return; + + manifest = new Manifest(zf.getInputStream(entry)); + } + + List cp = ManifestUtil.getClassPath(manifest, lib.path); + if (cp == null) return; + + for (URL url : cp) { + process(url); + } } public void process(URL url) throws IOException { diff --git a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java index 53a48bd7e..bcc9df043 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java @@ -75,4 +75,8 @@ enum LoaderLibrary { throw new RuntimeException(e); } } + + boolean isApplicable(EnvType env) { + return this.env == null || this.env == env; + } } diff --git a/src/main/java/net/fabricmc/loader/impl/util/ManifestUtil.java b/src/main/java/net/fabricmc/loader/impl/util/ManifestUtil.java index 91dfe5f4a..b04752299 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/ManifestUtil.java +++ b/src/main/java/net/fabricmc/loader/impl/util/ManifestUtil.java @@ -19,12 +19,16 @@ import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; +import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.nio.file.Files; import java.nio.file.Path; import java.security.CodeSource; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; import java.util.jar.Attributes.Name; import java.util.jar.Manifest; @@ -69,4 +73,19 @@ public static Manifest readManifest(Path basePath) throws IOException { public static String getManifestValue(Manifest manifest, Name name) { return manifest.getMainAttributes().getValue(name); } + + public static List getClassPath(Manifest manifest, Path baseDir) throws MalformedURLException { + String cp = ManifestUtil.getManifestValue(manifest, Name.CLASS_PATH); + if (cp == null) return null; + + StringTokenizer tokenizer = new StringTokenizer(cp); + List ret = new ArrayList<>(); + URL context = UrlUtil.asUrl(baseDir); + + while (tokenizer.hasMoreElements()) { + ret.add(new URL(context, tokenizer.nextToken())); + } + + return ret; + } } From 2d6c3c51fea79c05a493fde18021b58b7f512f5e Mon Sep 17 00:00:00 2001 From: Player Date: Mon, 25 Apr 2022 03:13:24 +0200 Subject: [PATCH 26/80] Refactor LogCategory handling --- .../impl/game/minecraft/Log4jLogHandler.java | 3 +- .../impl/game/minecraft/Slf4jLogHandler.java | 4 +- .../launchwrapper/FabricTweaker.java | 2 +- .../loader/impl/launch/knot/MixinLogger.java | 2 +- .../impl/util/log/ConsoleLogHandler.java | 2 +- .../loader/impl/util/log/LogCategory.java | 50 +++++++++++++------ 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Log4jLogHandler.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Log4jLogHandler.java index cc3d520e4..5ee611270 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Log4jLogHandler.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Log4jLogHandler.java @@ -55,8 +55,7 @@ private static Logger getLogger(LogCategory category) { Logger ret = (Logger) category.data; if (ret == null) { - String name = category.name.isEmpty() ? Log.NAME : String.format("%s/%s", Log.NAME, category.name); - category.data = ret = LogManager.getLogger(name); + category.data = ret = LogManager.getLogger(category.toString()); } return ret; diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Slf4jLogHandler.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Slf4jLogHandler.java index fcaf66679..a80da30ce 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Slf4jLogHandler.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/Slf4jLogHandler.java @@ -19,7 +19,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; import net.fabricmc.loader.impl.util.log.LogHandler; import net.fabricmc.loader.impl.util.log.LogLevel; @@ -99,8 +98,7 @@ private static Logger getLogger(LogCategory category) { Logger ret = (Logger) category.data; if (ret == null) { - String name = category.name.isEmpty() ? Log.NAME : String.format("%s/%s", Log.NAME, category.name); - category.data = ret = LoggerFactory.getLogger(name); + category.data = ret = LoggerFactory.getLogger(category.toString()); } return ret; diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java index 2326fb46d..a20943423 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java @@ -61,7 +61,7 @@ import net.fabricmc.loader.impl.util.log.LogCategory; public abstract class FabricTweaker extends FabricLauncherBase implements ITweaker { - private static final LogCategory LOG_CATEGORY = new LogCategory("GameProvider", "Tweaker"); + private static final LogCategory LOG_CATEGORY = LogCategory.create("GameProvider", "Tweaker"); protected Arguments arguments; private LaunchClassLoader launchClassLoader; private final List classPath = new ArrayList<>(); diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/MixinLogger.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/MixinLogger.java index 80f5439e3..158e6f625 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/MixinLogger.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/MixinLogger.java @@ -42,7 +42,7 @@ static ILogger get(String name) { MixinLogger(String name) { super(name); - this.logCategory = new LogCategory(name.replace("mixin", LogCategory.MIXIN.name).replace(".", LogCategory.SEPARATOR)); + this.logCategory = LogCategory.create(name.replace("mixin", LogCategory.MIXIN.name).replace(".", LogCategory.SEPARATOR)); } @Override diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/ConsoleLogHandler.java b/src/main/java/net/fabricmc/loader/impl/util/log/ConsoleLogHandler.java index 62303c7c6..81bd5320a 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/ConsoleLogHandler.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/ConsoleLogHandler.java @@ -35,7 +35,7 @@ public void log(long time, LogLevel level, LogCategory category, String msg, Thr } protected static String formatLog(long time, LogLevel level, LogCategory category, String msg, Throwable exc) { - String ret = String.format("[%tT] [%s] [%s/%s]: %s%n", time, level.name(), Log.NAME, category.name, msg); + String ret = String.format("[%tT] [%s] [%s/%s]: %s%n", time, level.name(), category.context, category.name, msg); if (exc != null) { StringWriter writer = new StringWriter(ret.length() + 500); diff --git a/src/main/java/net/fabricmc/loader/impl/util/log/LogCategory.java b/src/main/java/net/fabricmc/loader/impl/util/log/LogCategory.java index c789a2f56..9d56c5234 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/log/LogCategory.java +++ b/src/main/java/net/fabricmc/loader/impl/util/log/LogCategory.java @@ -17,28 +17,46 @@ package net.fabricmc.loader.impl.util.log; public final class LogCategory { - public static final LogCategory DISCOVERY = new LogCategory("Discovery"); - public static final LogCategory ENTRYPOINT = new LogCategory("Entrypoint"); - public static final LogCategory GAME_PATCH = new LogCategory("GamePatch"); - public static final LogCategory GAME_PROVIDER = new LogCategory("GameProvider"); - public static final LogCategory GAME_REMAP = new LogCategory("GameRemap"); - public static final LogCategory GENERAL = new LogCategory(); - public static final LogCategory KNOT = new LogCategory("Knot"); - public static final LogCategory LIB_CLASSIFICATION = new LogCategory("LibClassify"); - public static final LogCategory LOG = new LogCategory("Log"); - public static final LogCategory MAPPINGS = new LogCategory("Mappings"); - public static final LogCategory METADATA = new LogCategory("Metadata"); - public static final LogCategory MOD_REMAP = new LogCategory("ModRemap"); - public static final LogCategory MIXIN = new LogCategory("Mixin"); - public static final LogCategory RESOLUTION = new LogCategory("Resolution"); - public static final LogCategory TEST = new LogCategory("Test"); + public static final LogCategory DISCOVERY = create("Discovery"); + public static final LogCategory ENTRYPOINT = create("Entrypoint"); + public static final LogCategory GAME_PATCH = create("GamePatch"); + public static final LogCategory GAME_PROVIDER = create("GameProvider"); + public static final LogCategory GAME_REMAP = create("GameRemap"); + public static final LogCategory GENERAL = create(); + public static final LogCategory KNOT = create("Knot"); + public static final LogCategory LIB_CLASSIFICATION = create("LibClassify"); + public static final LogCategory LOG = create("Log"); + public static final LogCategory MAPPINGS = create("Mappings"); + public static final LogCategory METADATA = create("Metadata"); + public static final LogCategory MOD_REMAP = create("ModRemap"); + public static final LogCategory MIXIN = create("Mixin"); + public static final LogCategory RESOLUTION = create("Resolution"); + public static final LogCategory TEST = create("Test"); public static final String SEPARATOR = "/"; + public final String context; public final String name; public Object data; - public LogCategory(String... names) { + public static LogCategory create(String... names) { + return new LogCategory(Log.NAME, names); + } + + /** + * Create a log category for external uses, no API guarantees! + */ + public static LogCategory createCustom(String context, String... names) { + return new LogCategory(context, names); + } + + private LogCategory(String context, String[] names) { + this.context = context; this.name = String.join(SEPARATOR, names); } + + @Override + public String toString() { + return name.isEmpty() ? context : context+SEPARATOR+name; + } } From 09172bb24b7c96e72c9e076d8493e25916e326ee Mon Sep 17 00:00:00 2001 From: Player Date: Tue, 26 Apr 2022 00:29:23 +0200 Subject: [PATCH 27/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 657a1ca51..69a3d110c 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.2"; + public static final String VERSION = "0.14.3"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 458c46a3d8ae667ab3f65994a9562c290bfc3d20 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sat, 30 Apr 2022 21:02:51 +0100 Subject: [PATCH 28/80] Add GitHub action to help manage support issues. --- .github/workflows/manage_issues.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/manage_issues.yml diff --git a/.github/workflows/manage_issues.yml b/.github/workflows/manage_issues.yml new file mode 100644 index 000000000..b3259ecea --- /dev/null +++ b/.github/workflows/manage_issues.yml @@ -0,0 +1,14 @@ +name: Manage Issues + +on: + issues: + types: [ labeled, unlabeled ] + +jobs: + labels: + runs-on: ubuntu-latest + steps: + - uses: FabricMC/fabric-action-scripts@v1 + with: + context: ${{ github.event.action }} + label: ${{ github.event.label.name }} From 52d6dc0ba80addc61e08d4740f015cc383d26153 Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 27 Apr 2022 02:43:14 +0200 Subject: [PATCH 29/80] Skip code source operations for URLs with unknown protocols (injected by hacky mods) --- .../loader/impl/launch/knot/KnotClassDelegate.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index 66431e658..f839da56f 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -259,7 +259,7 @@ Class loadClass(String name, boolean resolve) throws ClassNotFoundException { private boolean isValidParentUrl(URL url, String fileName) { if (url == null) return false; if (DISABLE_ISOLATION) return true; - if (url.getProtocol().equals("jrt")) return true; + if (!hasRegularCodeSource(url)) return true; Path codeSource = getCodeSource(url, fileName); Set validParentCodeSources = this.validParentCodeSources; @@ -280,7 +280,7 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound String fileName = LoaderUtil.getClassFileName(name); URL url = classLoader.getResource(fileName); - if (url != null && !url.getProtocol().equals("jrt")) { + if (url != null && hasRegularCodeSource(url)) { Path codeSource = getCodeSource(url, fileName); String[] prefixes = allowedPrefixes.get(codeSource); @@ -345,7 +345,7 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound private Metadata getMetadata(String name) { String fileName = LoaderUtil.getClassFileName(name); URL url = classLoader.getResource(fileName); - if (url == null) return Metadata.EMPTY; + if (url == null || !hasRegularCodeSource(url)) return Metadata.EMPTY; return getMetadata(getCodeSource(url, fileName)); } @@ -493,6 +493,10 @@ private byte[] getRawClassByteArray(String name, boolean allowFromParent) throws } } + private static boolean hasRegularCodeSource(URL url) { + return url.getProtocol().equals("file") || url.getProtocol().equals("jar"); + } + private static Path getCodeSource(URL url, String fileName) { try { return LoaderUtil.normalizeExistingPath(UrlUtil.getCodeSource(url, fileName)); From 461fe80cdc29d2b540fe3596512a94c63365ab26 Mon Sep 17 00:00:00 2001 From: Player Date: Sun, 1 May 2022 15:15:06 +0200 Subject: [PATCH 30/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 69a3d110c..2c7e0928c 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.3"; + public static final String VERSION = "0.14.4"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 101ebe18bb06f083df94b72ad9a1a3a3e02c7c0e Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Tue, 3 May 2022 19:18:35 +0100 Subject: [PATCH 31/80] Update mixin --- gradle.properties | 2 +- src/main/resources/fabric-installer.json | 2 +- src/main/resources/fabric-installer.launchwrapper.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 64d0f93b7..f9a90f7e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ description = The mod loading component of Fabric url = https://github.com/FabricMC/fabric-loader asm_version = 9.3 -mixin_version = 0.11.3+mixin.0.8.5 +mixin_version = 0.11.4+mixin.0.8.5 diff --git a/src/main/resources/fabric-installer.json b/src/main/resources/fabric-installer.json index 98170817d..f98473ef7 100644 --- a/src/main/resources/fabric-installer.json +++ b/src/main/resources/fabric-installer.json @@ -9,7 +9,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:sponge-mixin:0.11.3+mixin.0.8.5", + "name": "net.fabricmc:sponge-mixin:0.11.4+mixin.0.8.5", "url": "https://maven.fabricmc.net/" }, { diff --git a/src/main/resources/fabric-installer.launchwrapper.json b/src/main/resources/fabric-installer.launchwrapper.json index ce083583f..cc97f8317 100644 --- a/src/main/resources/fabric-installer.launchwrapper.json +++ b/src/main/resources/fabric-installer.launchwrapper.json @@ -12,7 +12,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:sponge-mixin:0.11.3+mixin.0.8.5", + "name": "net.fabricmc:sponge-mixin:0.11.4+mixin.0.8.5", "url": "https://maven.fabricmc.net/" }, { From c8dd81d4aebffdab41634fec71c4af0575ec733e Mon Sep 17 00:00:00 2001 From: Player Date: Mon, 2 May 2022 16:06:50 +0200 Subject: [PATCH 32/80] Fix handling of classes defined at runtime. These classes are not backed by a resource on the platform class loader, but still load as expected. Co-authored-by: modmuss50 --- .../loader/impl/launch/knot/KnotClassDelegate.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index f839da56f..a4389eeac 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -222,9 +222,13 @@ Class loadClass(String name, boolean resolve) throws ClassNotFoundException { URL url = parentClassLoader.getResource(fileName); if (url == null) { // no .class file - String msg = "can't find class "+name; - if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, msg); - throw new ClassNotFoundException(msg); + try { + c = PLATFORM_CLASS_LOADER.loadClass(name); + if (LOG_CLASS_LOAD) Log.info(LogCategory.KNOT, "loaded resources-less class %s from platform class loader"); + } catch (ClassNotFoundException e) { + if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, "can't find class %s", name); + throw e; + } } else if (!isValidParentUrl(url, fileName)) { // available, but restricted // The class would technically be available, but the game provider restricted it from being // loaded by setting validParentUrls and not including "url". Typical causes are: From 4850c7ff2a5ab7ff9b9d6749f696a79f7d3bc06e Mon Sep 17 00:00:00 2001 From: altrisi Date: Sun, 8 May 2022 18:01:18 +0200 Subject: [PATCH 33/80] Start the game using MethodHandles (#669) * Start the game using MethodHandle Co-authored-by: liach <7806504+liach@users.noreply.github.com> * Use `void.class` instead of `Void.TYPE` Co-authored-by: liach <7806504+liach@users.noreply.github.com> --- .../game/minecraft/MinecraftGameProvider.java | 20 ++++++++++++------- proguard.conf | 1 + .../launch/server/FabricServerLauncher.java | 6 ++++-- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index 75be774e3..2f7b41536 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -17,8 +17,9 @@ package net.fabricmc.loader.impl.game.minecraft; import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; @@ -446,14 +447,19 @@ public void launch(ClassLoader loader) { targetClass = "net.fabricmc.loader.impl.game.minecraft.applet.AppletMain"; } + MethodHandle invoker; + try { Class c = loader.loadClass(targetClass); - Method m = c.getMethod("main", String[].class); - m.invoke(null, (Object) arguments.toArray()); - } catch (InvocationTargetException e) { - throw new FormattedException("Minecraft has crashed!", e.getCause()); - } catch (ReflectiveOperationException e) { + invoker = MethodHandles.lookup().findStatic(c, "main", MethodType.methodType(void.class, String[].class)); + } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) { throw new FormattedException("Failed to start Minecraft", e); } + + try { + invoker.invokeExact(arguments.toArray()); + } catch (Throwable t) { + throw new FormattedException("Minecraft has crashed!", t); + } } } diff --git a/proguard.conf b/proguard.conf index 5204bf0ac..8befe5628 100644 --- a/proguard.conf +++ b/proguard.conf @@ -4,6 +4,7 @@ -dontoptimize -keepparameternames -keepattributes * +-dontwarn java.lang.invoke.MethodHandle -keep class !net.fabricmc.loader.impl.lib.** { *; diff --git a/src/main/java/net/fabricmc/loader/impl/launch/server/FabricServerLauncher.java b/src/main/java/net/fabricmc/loader/impl/launch/server/FabricServerLauncher.java index 849180f54..250383679 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/server/FabricServerLauncher.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/server/FabricServerLauncher.java @@ -20,6 +20,8 @@ import java.io.InputStreamReader; import java.io.Reader; import java.io.Writer; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -64,8 +66,8 @@ public static void main(String[] args) { try { Class c = Class.forName(mainClass); - c.getMethod("main", String[].class).invoke(null, (Object) args); - } catch (Exception e) { + MethodHandles.lookup().findStatic(c, "main", MethodType.methodType(void.class, String[].class)).invokeExact(args); + } catch (Throwable e) { throw new RuntimeException("An exception occurred when launching the server!", e); } } From f7ea03d8c795ae2ca247e3a5456b538e23cf6d31 Mon Sep 17 00:00:00 2001 From: Player Date: Sun, 8 May 2022 18:11:56 +0200 Subject: [PATCH 34/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 2c7e0928c..a578954ce 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.4"; + public static final String VERSION = "0.14.5"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From feb10d6d223c63e019a32cdd6afcaa9c90a709fa Mon Sep 17 00:00:00 2001 From: Player Date: Thu, 19 May 2022 15:37:53 +0200 Subject: [PATCH 35/80] Ignore class path group entries not on the class path --- .../impl/game/minecraft/launchwrapper/FabricTweaker.java | 6 +++++- .../impl/discovery/ClasspathModCandidateFinder.java | 8 +++++++- .../java/net/fabricmc/loader/impl/launch/knot/Knot.java | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java index a20943423..44ca87194 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java @@ -54,6 +54,7 @@ import net.fabricmc.loader.impl.launch.FabricMixinBootstrap; import net.fabricmc.loader.impl.util.Arguments; import net.fabricmc.loader.impl.util.FileSystemUtil; +import net.fabricmc.loader.impl.util.LoaderUtil; import net.fabricmc.loader.impl.util.ManifestUtil; import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.UrlUtil; @@ -125,7 +126,10 @@ private void init() { classPath.clear(); for (URL url : launchClassLoader.getSources()) { - classPath.add(UrlUtil.asPath(url)); + Path path = UrlUtil.asPath(url); + if (!Files.exists(path)) continue; + + classPath.add(LoaderUtil.normalizeExistingPath(path)); } GameProvider provider = new MinecraftGameProvider(); diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java index 1168041ec..58ba8931f 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -86,6 +87,7 @@ private static Map> getPathGroups() { String prop = System.getProperty(SystemProperties.PATH_GROUPS); if (prop == null) return Collections.emptyMap(); + Set cp = new HashSet<>(FabricLauncherBase.getLauncher().getClassPath()); Map> ret = new HashMap<>(); for (String group : prop.split(File.pathSeparator+File.pathSeparator)) { @@ -101,7 +103,11 @@ private static Map> getPathGroups() { continue; } - paths.add(LoaderUtil.normalizeExistingPath(resolvedPath)); + resolvedPath = LoaderUtil.normalizeExistingPath(resolvedPath); + + if (cp.contains(resolvedPath)) { + paths.add(resolvedPath); + } } if (paths.size() < 2) { diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java index ff32d62b2..c162461a1 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java @@ -121,7 +121,7 @@ protected ClassLoader init(String[] args) { continue; } - classPath.add(path); + classPath.add(LoaderUtil.normalizeExistingPath(path)); } if (unsupported != null) Log.warn(LogCategory.KNOT, "Knot does not support wildcard class path entries: %s - the game may not load properly!", String.join(", ", unsupported)); From ae9cf6fae6a05b09ecfd0144a2625f72922f2620 Mon Sep 17 00:00:00 2001 From: Player Date: Thu, 19 May 2022 18:04:46 +0200 Subject: [PATCH 36/80] reduce class path group warnings to debug level --- .../loader/impl/discovery/ClasspathModCandidateFinder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java index 58ba8931f..55caa1b1c 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ClasspathModCandidateFinder.java @@ -99,7 +99,7 @@ private static Map> getPathGroups() { Path resolvedPath = Paths.get(path); if (!Files.exists(resolvedPath)) { - Log.warn(LogCategory.DISCOVERY, "Skipping missing class path group entry %s", path); + Log.debug(LogCategory.DISCOVERY, "Skipping missing class path group entry %s", path); continue; } @@ -111,7 +111,7 @@ private static Map> getPathGroups() { } if (paths.size() < 2) { - Log.warn(LogCategory.DISCOVERY, "Skipping class path group with no effect: %s", group); + Log.debug(LogCategory.DISCOVERY, "Skipping class path group with no effect: %s", group); continue; } From b07a5809599d56a20678343026aacd2cf75e8a1b Mon Sep 17 00:00:00 2001 From: Player Date: Fri, 20 May 2022 01:34:22 +0200 Subject: [PATCH 37/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index a578954ce..34acb35f7 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.5"; + public static final String VERSION = "0.14.6"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 57e0bb8c7498658e7a8f2cf1354d2333e8358371 Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 25 May 2022 01:45:54 +0200 Subject: [PATCH 38/80] Fix logging for MC versions without log libs --- .../loader/impl/game/minecraft/MinecraftGameProvider.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index 2f7b41536..6e9259073 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -193,8 +193,6 @@ public boolean locateGame(FabricLauncher launcher, String[] args) { envGameJar = classifier.getOrigin(envGameLib); if (envGameJar == null) return false; - Log.configureBuiltin(true, false); - commonGameJar = classifier.getOrigin(McLibrary.MC_COMMON); if (commonGameJarDeclared && commonGameJar == null) { @@ -212,12 +210,15 @@ public boolean locateGame(FabricLauncher launcher, String[] args) { hasModLoader = classifier.has(McLibrary.MODLOADER); log4jAvailable = classifier.has(McLibrary.LOG4J_API) && classifier.has(McLibrary.LOG4J_CORE); slf4jAvailable = classifier.has(McLibrary.SLF4J_API) && classifier.has(McLibrary.SLF4J_CORE); + boolean hasLogLib = log4jAvailable || slf4jAvailable; + + Log.configureBuiltin(hasLogLib, !hasLogLib); for (McLibrary lib : McLibrary.LOGGING) { Path path = classifier.getOrigin(lib); if (path != null) { - if (log4jAvailable || slf4jAvailable) { + if (hasLogLib) { logJars.add(path); } else if (!gameJars.contains(path)) { miscGameLibraries.add(path); From 89ea8144149ef26ca5c844e7b5acb96a3f27a85e Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 8 Jun 2022 17:45:00 +0200 Subject: [PATCH 39/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 34acb35f7..a73949d3d 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.6"; + public static final String VERSION = "0.14.7"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 38cb655932dc282088544f9537361ea07fb2d270 Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 15 Jun 2022 20:47:50 +0200 Subject: [PATCH 40/80] Add support for the MC client using a method that forwards to the actual main implementation as used by 22w24a --- .../game/minecraft/patch/EntrypointPatch.java | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java index 1449c37b2..3340efa60 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java @@ -98,7 +98,31 @@ public void process(FabricLauncher launcher, Function class throw new RuntimeException("Could not find main method in " + entrypoint + "!"); } - if (type == EnvType.SERVER) { + if (type == EnvType.CLIENT && mainMethod.instructions.size() < 10) { + // 22w24+ forwards to another method in the same class instead of processing in main() directly, use that other method instead if that's the case + MethodInsnNode invocation = null; + + for (AbstractInsnNode insn : mainMethod.instructions) { + MethodInsnNode methodInsn; + + if (invocation == null + && insn.getType() == AbstractInsnNode.METHOD_INSN + && (methodInsn = (MethodInsnNode) insn).owner.equals(mainClass.name)) { + // capture first method insn to the same class + invocation = methodInsn; + } else if (insn.getOpcode() > Opcodes.ALOAD // ignore constant and variable loads as well as NOP, labels and line numbers + && insn.getOpcode() != Opcodes.RETURN) { // and RETURN + // found unexpected insn for a simple forwarding method + invocation = null; + break; + } + } + + if (invocation != null) { // simple forwarder confirmed, use its target for further processing + final MethodInsnNode reqMethod = invocation; + mainMethod = findMethod(mainClass, m -> m.name.equals(reqMethod.name) && m.desc.equals(reqMethod.desc)); + } + } else if (type == EnvType.SERVER) { // pre-1.6 method search route MethodInsnNode newGameInsn = (MethodInsnNode) findInsn(mainMethod, (insn) -> insn.getOpcode() == Opcodes.INVOKESPECIAL && ((MethodInsnNode) insn).name.equals("") && ((MethodInsnNode) insn).owner.equals(mainClass.name), From 1fe6f4d0832454e2efa24c0859c493c6602d4c4c Mon Sep 17 00:00:00 2001 From: Player Date: Sat, 11 Jun 2022 15:06:57 +0200 Subject: [PATCH 41/80] Allow defining additional system libraries through a system property --- .../game/minecraft/MinecraftGameProvider.java | 2 +- .../loader/impl/game/LibClassifier.java | 41 +++++++++++++++---- .../impl/launch/knot/KnotClassDelegate.java | 3 +- .../loader/impl/util/SystemProperties.java | 2 + 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index 6e9259073..c2275cbbd 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -227,7 +227,7 @@ public boolean locateGame(FabricLauncher launcher, String[] args) { } miscGameLibraries.addAll(classifier.getUnmatchedOrigins()); - validParentClassPath = classifier.getLoaderOrigins(); + validParentClassPath = classifier.getSystemLibraries(); } catch (IOException e) { throw ExceptionUtil.wrap(e); } diff --git a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java index d0f85e276..ce051e500 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java @@ -16,10 +16,12 @@ package net.fabricmc.loader.impl.game; +import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -51,7 +53,7 @@ public final class LibClassifier & LibraryType> { private final List libs; private final Map origins; private final Map localPaths; - private final Set loaderOrigins = new HashSet<>(); + private final Set systemLibraries = new HashSet<>(); private final List unmatchedOrigins = new ArrayList<>(); public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) throws IOException { @@ -69,16 +71,36 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) throw } } - // loader libs + // system libs configured through system property StringBuilder sb = DEBUG ? new StringBuilder() : null; + String systemLibProp = System.getProperty(SystemProperties.SYSTEM_LIBRARIES); + + if (systemLibProp != null) { + for (String lib : systemLibProp.split(File.pathSeparator)) { + Path path = Paths.get(lib); + + if (!Files.exists(path)) { + Log.info(LogCategory.LIB_CLASSIFICATION, "Skipping missing system library entry %s", path); + continue; + } + + path = LoaderUtil.normalizeExistingPath(path); + + if (systemLibraries.add(path)) { + if (DEBUG) sb.append(String.format("🇸 %s%n", path)); + } + } + } + + // loader libs for (LoaderLibrary lib : LoaderLibrary.values()) { if (!lib.isApplicable(env)) continue; if (lib.path != null) { Path path = LoaderUtil.normalizeExistingPath(lib.path); - loaderOrigins.add(path); + systemLibraries.add(path); if (DEBUG) sb.append(String.format("✅ %s %s%n", lib.name(), path)); } else { @@ -93,14 +115,14 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) throw if (gameProviderPath != null) { gameProviderPath = LoaderUtil.normalizeExistingPath(gameProviderPath); - if (loaderOrigins.add(gameProviderPath)) { + if (systemLibraries.add(gameProviderPath)) { if (DEBUG) sb.append(String.format("✅ gameprovider %s%n", gameProviderPath)); } } else { if (DEBUG) sb.append("❎ gameprovider"); } - if (DEBUG) Log.info(LogCategory.LIB_CLASSIFICATION, "Loader libraries:%n%s", sb); + if (DEBUG) Log.info(LogCategory.LIB_CLASSIFICATION, "Loader/system libraries:%n%s", sb); // process indirectly referenced libs @@ -159,7 +181,7 @@ private static > Set makeSet(L[] libs) { private void process(Path path, Set excludedLibs) throws IOException { path = LoaderUtil.normalizeExistingPath(path); - if (loaderOrigins.contains(path)) return; + if (systemLibraries.contains(path)) return; boolean matched = false; @@ -240,8 +262,11 @@ public List getUnmatchedOrigins() { return unmatchedOrigins; } - public Collection getLoaderOrigins() { - return loaderOrigins; + /** + * Returns system level libraries, typically Loader and its dependencies. + */ + public Collection getSystemLibraries() { + return systemLibraries; } public boolean remove(Path path) { diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index a4389eeac..c793e6995 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -234,7 +234,8 @@ Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // loaded by setting validParentUrls and not including "url". Typical causes are: // - accessing classes too early (game libs shouldn't be used until Loader is ready) // - using jars that are only transient (deobfuscation input or pass-through installers) - String msg = String.format("can't load class %s at %s as it hasn't been exposed to the game (yet?)", name, getCodeSource(url, fileName)); + String msg = String.format("can't load class %s at %s as it hasn't been exposed to the game (yet? The system property "+SystemProperties.PATH_GROUPS+" may not be set correctly in-dev)", + name, getCodeSource(url, fileName)); if (LOG_CLASS_LOAD_ERRORS) Log.warn(LogCategory.KNOT, msg); throw new ClassNotFoundException(msg); } else { // load from system cl diff --git a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java index f2c18c128..331a255fd 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java +++ b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java @@ -38,6 +38,8 @@ public final class SystemProperties { public static final String REMAP_CLASSPATH_FILE = "fabric.remapClasspathFile"; // class path groups to map multiple class path entries to a mod (paths separated by path separator, groups by double path separator) public static final String PATH_GROUPS = "fabric.classPathGroups"; + // system level libraries, matching code sources will not be assumed to be part of the game or mods and remain on the system class path (paths separated by path separator) + public static final String SYSTEM_LIBRARIES = "fabric.systemLibraries"; // throw exceptions from entrypoints, discovery etc. directly instead of gathering and attaching as suppressed public static final String DEBUG_THROW_DIRECTLY = "fabric.debug.throwDirectly"; // logs library classification activity From a553e3f7ea78eb9d44eda7846b84ef41d01307a9 Mon Sep 17 00:00:00 2001 From: Cat Core <34719527+arthurbambou@users.noreply.github.com> Date: Fri, 10 Jun 2022 02:18:37 +0200 Subject: [PATCH 42/80] Fix realms library detection --- .../java/net/fabricmc/loader/impl/game/minecraft/McLibrary.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McLibrary.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McLibrary.java index b88710cf7..c15c76952 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McLibrary.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McLibrary.java @@ -24,7 +24,7 @@ enum McLibrary implements LibraryType { MC_SERVER(EnvType.SERVER, "net/minecraft/server/Main.class", "net/minecraft/server/MinecraftServer.class", "com/mojang/minecraft/server/MinecraftServer.class"), MC_COMMON("net/minecraft/server/MinecraftServer.class"), MC_BUNDLER(EnvType.SERVER, "net/minecraft/bundler/Main.class"), - REALMS(EnvType.CLIENT, "realmsVersion"), + REALMS(EnvType.CLIENT, "realmsVersion", "com/mojang/realmsclient/RealmsVersion.class"), MODLOADER("ModLoader"), LOG4J_API("org/apache/logging/log4j/LogManager.class"), LOG4J_CORE("META-INF/services/org.apache.logging.log4j.spi.Provider", "META-INF/log4j-provider.properties"), From d713b2a801ad142394ac0eb1d1eaa452fc783021 Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 15 Jun 2022 20:58:10 +0200 Subject: [PATCH 43/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index a73949d3d..e633fd55d 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.7"; + public static final String VERSION = "0.14.8"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 2d9e1b5000b0dd6758bf38d91f8b973d36688dc8 Mon Sep 17 00:00:00 2001 From: Player Date: Fri, 15 Jul 2022 13:45:30 +0200 Subject: [PATCH 44/80] Fix locale sensitive formatting with functional impact --- .../loader/impl/game/minecraft/MinecraftGameProvider.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index c2275cbbd..572855006 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -119,7 +119,7 @@ public Collection getBuiltinMods() { int version = versionData.getClassVersion().getAsInt() - 44; try { - metadata.addDependency(new ModDependencyImpl(ModDependency.Kind.DEPENDS, "java", Collections.singletonList(String.format(">=%d", version)))); + metadata.addDependency(new ModDependencyImpl(ModDependency.Kind.DEPENDS, "java", Collections.singletonList(String.format(Locale.ENGLISH, ">=%d", version)))); } catch (VersionParsingException e) { throw new RuntimeException(e); } @@ -299,7 +299,7 @@ public void initialize(FabricLauncher launcher) { } else if (i == 1) { name = "common"; } else { - name = String.format("extra-%d", i - 2); + name = String.format(Locale.ENGLISH, "extra-%d", i - 2); } obfJars.put(name, gameJars.get(i)); From 8e02185934eebd9d16f6412e80dbad9cc9007493 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Fri, 5 Aug 2022 17:53:24 +0100 Subject: [PATCH 45/80] Support mods from multiple jars and directories. (#704) Before this change mods could only be loaded across multiple directories. --- .../loader/impl/discovery/ModDiscoverer.java | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java index 3071fdf8f..ca43e3fc3 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java @@ -244,40 +244,44 @@ protected ModCandidate compute() { } } else { // regular classes-dir or jar try { - if (paths.size() != 1 || Files.isDirectory(paths.get(0))) { - return computeDir(); - } else { - return computeJarFile(); + for (Path path : paths) { + final ModCandidate candidate; + + if (Files.isDirectory(path)) { + candidate = computeDir(path); + } else { + candidate = computeJarFile(path); + } + + if (candidate != null) { + return candidate; + } } } catch (ParseMetadataException e) { // already contains all context throw ExceptionUtil.wrap(e); } catch (Throwable t) { throw new RuntimeException(String.format("Error analyzing %s: %s", paths, t), t); } + + return null; } } - private ModCandidate computeDir() throws IOException, ParseMetadataException { - for (Path path : paths) { - Path modJson = path.resolve("fabric.mod.json"); - if (!Files.exists(modJson)) continue; + private ModCandidate computeDir(Path path) throws IOException, ParseMetadataException { + Path modJson = path.resolve("fabric.mod.json"); + if (!Files.exists(modJson)) return null; - LoaderModMetadata metadata; + LoaderModMetadata metadata; - try (InputStream is = Files.newInputStream(modJson)) { - metadata = parseMetadata(is, path.toString()); - } - - return ModCandidate.createPlain(paths, metadata, requiresRemap, Collections.emptyList()); + try (InputStream is = Files.newInputStream(modJson)) { + metadata = parseMetadata(is, path.toString()); } - return null; + return ModCandidate.createPlain(paths, metadata, requiresRemap, Collections.emptyList()); } - private ModCandidate computeJarFile() throws IOException, ParseMetadataException { - assert paths.size() == 1; - - try (ZipFile zf = new ZipFile(paths.get(0).toFile())) { + private ModCandidate computeJarFile(Path path) throws IOException, ParseMetadataException { + try (ZipFile zf = new ZipFile(path.toFile())) { ZipEntry entry = zf.getEntry("fabric.mod.json"); if (entry == null) return null; From 79b034e4a6c63dcc21e5d7879d624b584ddac22a Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Fri, 5 Aug 2022 17:55:06 +0100 Subject: [PATCH 46/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index e633fd55d..bca3d0442 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.8"; + public static final String VERSION = "0.14.9"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 6adfe08efeb04c8dde829053e3cc546c01ef8415 Mon Sep 17 00:00:00 2001 From: Player Date: Tue, 9 Aug 2022 22:57:32 +0200 Subject: [PATCH 47/80] Implement version detection for classic/alpha/beta client jars, cleanup --- .../impl/game/minecraft/McVersionLookup.java | 91 +++++++++++++++---- 1 file changed, 71 insertions(+), 20 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java index 2f4fb40e4..8b45d8440 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java @@ -149,6 +149,16 @@ public static void fillVersionFromJar(SimpleClassPath cp, McVersion.Builder buil return; } } + + // classic: version-like String constant used in Minecraft.init, Minecraft referenced by field in MinecraftApplet + String type; + + if (((is = cp.getInputStream("net/minecraft/client/MinecraftApplet.class")) != null || (is = cp.getInputStream("com/mojang/minecraft/MinecraftApplet.class")) != null) + && (type = analyze(is, new FieldTypeCaptureVisitor())) != null + && (is = cp.getInputStream(type.concat(".class"))) != null + && fromAnalyzer(is, new MethodConstantVisitor("init"), builder)) { + return; + } } catch (IOException e) { e.printStackTrace(); } @@ -220,15 +230,22 @@ private static boolean fromVersionJson(InputStream is, McVersion.Builder builder } private static boolean fromAnalyzer(InputStream is, T analyzer, McVersion.Builder builder) { + String result = analyze(is, analyzer); + + if (result != null) { + builder.setNameAndRelease(result); + return true; + } else { + return false; + } + } + + private static String analyze(InputStream is, T analyzer) { try { ClassReader cr = new ClassReader(is); cr.accept(analyzer, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); - String result = analyzer.getResult(); - if (result != null) { - builder.setNameAndRelease(result); - return true; - } + return analyzer.getResult(); } catch (IOException e) { e.printStackTrace(); } finally { @@ -239,7 +256,7 @@ private static boolean fromAnalyzer(InputStr } } - return false; + return null; } protected static String getRelease(String version) { @@ -559,6 +576,10 @@ private interface Analyzer { } private static final class FieldStringConstantVisitor extends ClassVisitor implements Analyzer { + private final String fieldName; + private String className; + private String result; + FieldStringConstantVisitor(String fieldName) { super(FabricLoaderImpl.ASM_VERSION); @@ -623,13 +644,13 @@ protected void visitAnyInsn() { String lastLdc; }; } - - private final String fieldName; - private String className; - private String result; } private static final class MethodStringConstantContainsVisitor extends ClassVisitor implements Analyzer { + private final String methodOwner; + private final String methodName; + private String result; + MethodStringConstantContainsVisitor(String methodOwner, String methodName) { super(FabricLoaderImpl.ASM_VERSION); @@ -678,13 +699,12 @@ protected void visitAnyInsn() { String lastLdc; }; } + } - private final String methodOwner; + private static final class MethodConstantRetVisitor extends ClassVisitor implements Analyzer { private final String methodName; private String result; - } - private static final class MethodConstantRetVisitor extends ClassVisitor implements Analyzer { MethodConstantRetVisitor(String methodName) { super(FabricLoaderImpl.ASM_VERSION); @@ -737,13 +757,15 @@ protected void visitAnyInsn() { String lastLdc; }; } - - private final String methodName; - private String result; } private static final class MethodConstantVisitor extends ClassVisitor implements Analyzer { private static final String STARTING_MESSAGE = "Starting minecraft server version "; + private static final String CLASSIC_PREFIX = "Minecraft "; + + private final String methodNameHint; + private String result; + private boolean foundInMethodHint; MethodConstantVisitor(String methodNameHint) { super(FabricLoaderImpl.ASM_VERSION); @@ -782,6 +804,12 @@ public void visitLdcInsn(Object value) { if (!str.startsWith("Beta") && str.startsWith("0.")) { str = "Alpha " + str; } + } else if (str.startsWith(CLASSIC_PREFIX)) { + str = str.substring(CLASSIC_PREFIX.length()); + + if (str.startsWith(CLASSIC_PREFIX)) { // some beta versions repeat the Minecraft prefix + str = str.substring(CLASSIC_PREFIX.length()); + } } // 1.0.0 - 1.13.2 have an obfuscated method that just returns the version, so we can use that @@ -794,10 +822,6 @@ public void visitLdcInsn(Object value) { } }; } - - private final String methodNameHint; - private String result; - private boolean foundInMethodHint; } private abstract static class InsnFwdMethodVisitor extends MethodVisitor { @@ -872,4 +896,31 @@ public void visitMultiANewArrayInsn(java.lang.String descriptor, int numDimensio visitAnyInsn(); } } + + private static final class FieldTypeCaptureVisitor extends ClassVisitor implements Analyzer { + private String type; + + FieldTypeCaptureVisitor() { + super(FabricLoaderImpl.ASM_VERSION); + } + + @Override + public String getResult() { + return type; + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + if (type == null && descriptor.startsWith("L") && !descriptor.startsWith("Ljava/")) { + type = descriptor.substring(1, descriptor.length() - 1); + } + + return null; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return null; + } + } } From b80af708f8d3712af0bbe570bbffb2fec99b95ca Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 19 Oct 2022 21:42:21 +0200 Subject: [PATCH 48/80] Fix server entrypoint patch for 22w42a --- .../loader/impl/game/minecraft/patch/EntrypointPatch.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java index 3340efa60..e407349dd 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java @@ -282,7 +282,8 @@ public void process(FabricLauncher launcher, Function class // Is only method that returns a class instance // If we do not find this, then we are certain this is 20w22a. MethodNode serverStartMethod = findMethod(mainClass, method -> { - if (method.name.equals("main") && method.desc.equals("([Ljava/lang/String;)V")) { + if ((method.access & Opcodes.ACC_SYNTHETIC) == 0 // reject non-synthetic + || method.name.equals("main") && method.desc.equals("([Ljava/lang/String;)V")) { // reject main method (theoretically superfluous now) return false; } From 755b23bb439d9ad423118b2d78c092ec931af85a Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 19 Oct 2022 20:16:39 +0200 Subject: [PATCH 49/80] Extend hardcoded snapshot version to release mapping --- .../impl/game/minecraft/McVersionLookup.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java index 8b45d8440..402542eb8 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java @@ -282,7 +282,21 @@ protected static String getRelease(String version) { int year = Integer.parseInt(matcher.group(1)); int week = Integer.parseInt(matcher.group(2)); - if (year == 20 && week >= 6) { + if (year == 22 && week >= 42 || year >= 23) { + return "1.19.3"; + } else if (year == 22 && week == 24) { + return "1.19.1"; + } else if (year == 22 && week >= 11 && week <= 19) { + return "1.19"; + } else if (year == 22 && week >= 3 && week <= 7) { + return "1.18.2"; + } else if (year == 21 && week >= 37 && week <= 44) { + return "1.18"; + } else if (year == 20 && week >= 45 || year == 21 && week <= 20) { + return "1.17"; + } else if (year == 20 && week >= 27 && week <= 30) { + return "1.16.2"; + } else if (year == 20 && week >= 6 && week <= 22) { return "1.16"; } else if (year == 19 && week >= 34) { return "1.15"; From 281b51158571811738663d21c34244804950f24a Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 19 Oct 2022 20:33:35 +0200 Subject: [PATCH 50/80] Add support for MC version.json without release entry --- .../impl/game/minecraft/McVersionLookup.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java index 402542eb8..0871a2596 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java @@ -214,14 +214,19 @@ private static boolean fromVersionJson(InputStream is, McVersion.Builder builder version = name; } - if (version != null && release != null) { - builder.setId(id); - builder.setName(name); + if (version == null) return false; + + builder.setId(id); + builder.setName(name); + + if (release == null) { + builder.setNameAndRelease(version); + } else { builder.setVersion(version); builder.setRelease(release); - - return true; } + + return true; } catch (IOException e) { e.printStackTrace(); } From 6470436589bc98aed4d3cf93c94d6dc3df2b2272 Mon Sep 17 00:00:00 2001 From: Player Date: Wed, 19 Oct 2022 23:14:28 +0200 Subject: [PATCH 51/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index bca3d0442..3429cc1a7 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.9"; + public static final String VERSION = "0.14.10"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From f5b4bf2c1750b1b90934b7a259f1a8fb99b85776 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Fri, 4 Nov 2022 18:14:39 +0000 Subject: [PATCH 52/80] Fix: Resolve duplicate class definition attempt. This occurred when the first class to be loaded after mixin configs are added is referenced in an `IMixinConfigPlugin` belonging to one of said configs. --- .../loader/impl/launch/knot/KnotClassDelegate.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java index c793e6995..0b7c38e2c 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassDelegate.java @@ -323,6 +323,14 @@ Class tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound byte[] input = getPostMixinClassByteArray(name, allowFromParent); if (input == null) return null; + // The class we're currently loading could have been loaded already during Mixin initialization triggered by `getPostMixinClassByteArray`. + // If this is the case, we want to return the instance that was already defined to avoid attempting a duplicate definition. + Class existingClass = classLoader.findLoadedClassFwd(name); + + if (existingClass != null) { + return existingClass; + } + if (allowFromParent) { parentSourcedClasses.add(name); } From 5dadf857ff5f30f5a681a1033e806794423c91ef Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Mon, 28 Nov 2022 14:52:14 -0500 Subject: [PATCH 53/80] Fix mod metadata parsing tests --- .../loader/impl/discovery/ModDiscoverer.java | 6 +++--- .../loader/impl/metadata/MetadataVerifier.java | 12 +++++------- .../loader/impl/metadata/ModMetadataParser.java | 11 +++++------ .../net/fabricmc/test/V1ModJsonParsingTests.java | 4 +--- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java index ca43e3fc3..ac87f78a1 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java @@ -118,11 +118,11 @@ public List discoverMods(FabricLoaderImpl loader, Map computeNestedMods(ZipEntrySource entrySource) throws I } private LoaderModMetadata parseMetadata(InputStream is, String localPath) throws ParseMetadataException { - return ModMetadataParser.parseMetadata(is, localPath, parentPaths, versionOverrides, depOverrides); + return ModMetadataParser.parseMetadata(is, localPath, parentPaths, versionOverrides, depOverrides, FabricLoaderImpl.INSTANCE.isDevelopmentEnvironment()); } } diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java b/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java index 6af58ab33..c0157c109 100644 --- a/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java +++ b/src/main/java/net/fabricmc/loader/impl/metadata/MetadataVerifier.java @@ -26,10 +26,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.SemanticVersion; import net.fabricmc.loader.api.VersionParsingException; -import net.fabricmc.loader.impl.FabricLoaderImpl; import net.fabricmc.loader.impl.discovery.ModCandidate; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; @@ -37,10 +35,10 @@ public final class MetadataVerifier { private static final Pattern MOD_ID_PATTERN = Pattern.compile("[a-z][a-z0-9-_]{1,63}"); - public static ModCandidate verifyIndev(ModCandidate mod) { - if (FabricLoaderImpl.INSTANCE.isDevelopmentEnvironment()) { + public static ModCandidate verifyIndev(ModCandidate mod, boolean isDevelopment) { + if (isDevelopment) { try { - MetadataVerifier.verify(mod.getMetadata()); + MetadataVerifier.verify(mod.getMetadata(), isDevelopment); } catch (ParseMetadataException e) { e.setModPaths(mod.getLocalPath(), Collections.emptyList()); throw new RuntimeException("Invalid mod metadata", e); @@ -50,7 +48,7 @@ public static ModCandidate verifyIndev(ModCandidate mod) { return mod; } - static void verify(LoaderModMetadata metadata) throws ParseMetadataException { + static void verify(LoaderModMetadata metadata, boolean isDevelopment) throws ParseMetadataException { checkModId(metadata.getId(), "mod id"); for (String providesDecl : metadata.getProvides()) { @@ -59,7 +57,7 @@ static void verify(LoaderModMetadata metadata) throws ParseMetadataException { // TODO: verify mod id and version decls in deps - if (FabricLoader.getInstance().isDevelopmentEnvironment()) { + if (isDevelopment) { if (metadata.getSchemaVersion() < ModMetadataParser.LATEST_VERSION) { Log.warn(LogCategory.METADATA, "Mod %s uses an outdated schema version: %d < %d", metadata.getId(), metadata.getSchemaVersion(), ModMetadataParser.LATEST_VERSION); } diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java b/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java index 9cb2ef49f..875c2a104 100644 --- a/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java +++ b/src/main/java/net/fabricmc/loader/impl/metadata/ModMetadataParser.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Set; -import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.impl.lib.gson.JsonReader; import net.fabricmc.loader.impl.lib.gson.JsonToken; import net.fabricmc.loader.impl.util.log.Log; @@ -40,14 +39,14 @@ public final class ModMetadataParser { // Per the ECMA-404 (www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf), the JSON spec does not prohibit duplicate keys. // For all intents and purposes of replicating the logic of Gson's fromJson before we have migrated to JsonReader, duplicate keys will replace previous entries. public static LoaderModMetadata parseMetadata(InputStream is, String modPath, List modParentPaths, - VersionOverrides versionOverrides, DependencyOverrides depOverrides) throws ParseMetadataException { + VersionOverrides versionOverrides, DependencyOverrides depOverrides, boolean isDevelopment) throws ParseMetadataException { try { - LoaderModMetadata ret = readModMetadata(is); + LoaderModMetadata ret = readModMetadata(is, isDevelopment); versionOverrides.apply(ret); depOverrides.apply(ret); - MetadataVerifier.verify(ret); + MetadataVerifier.verify(ret, isDevelopment); return ret; } catch (ParseMetadataException e) { @@ -60,7 +59,7 @@ public static LoaderModMetadata parseMetadata(InputStream is, String modPath, Li } } - private static LoaderModMetadata readModMetadata(InputStream is) throws IOException, ParseMetadataException { + private static LoaderModMetadata readModMetadata(InputStream is, boolean isDevelopment) throws IOException, ParseMetadataException { // So some context: // Per the json specification, ordering of fields is not typically enforced. // Furthermore we cannot guarantee the `schemaVersion` is the first field in every `fabric.mod.json` @@ -128,7 +127,7 @@ private static LoaderModMetadata readModMetadata(InputStream is) throws IOExcept LoaderModMetadata ret = readModMetadata(reader, schemaVersion); reader.endObject(); - if (FabricLoader.getInstance().isDevelopmentEnvironment()) { + if (isDevelopment) { Log.warn(LogCategory.METADATA, "\"fabric.mod.json\" from mod %s did not have \"schemaVersion\" as first field.", ret.getId()); } diff --git a/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java b/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java index fd520dab8..d8553adba 100644 --- a/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java +++ b/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java @@ -31,7 +31,6 @@ import java.util.Map; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -43,7 +42,6 @@ import net.fabricmc.loader.impl.metadata.ParseMetadataException; import net.fabricmc.loader.impl.metadata.VersionOverrides; -@Disabled // TODO needs fixing. final class V1ModJsonParsingTests { private static Path testLocation; private static Path specPath; @@ -188,7 +186,7 @@ public void testWarnings() { } private static LoaderModMetadata parseMetadata(Path path) throws IOException, ParseMetadataException { try (InputStream is = Files.newInputStream(path)) { - return ModMetadataParser.parseMetadata(null, "dummy", Collections.emptyList(), new VersionOverrides(), new DependencyOverrides(Paths.get("randomMissing"))); + return ModMetadataParser.parseMetadata(is, "dummy", Collections.emptyList(), new VersionOverrides(), new DependencyOverrides(Paths.get("randomMissing")), false); } } } From 354af34127e52378410d182dd7b458e8c9b893d5 Mon Sep 17 00:00:00 2001 From: Player Date: Tue, 29 Nov 2022 19:50:22 +0100 Subject: [PATCH 54/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 3429cc1a7..c519b3931 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.10"; + public static final String VERSION = "0.14.11"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 55e9897e0335cbf822136b9ced0603372758289c Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Tue, 20 Dec 2022 11:58:38 +0000 Subject: [PATCH 55/80] Update to ASM 9.4 (#749) --- build.gradle | 8 +++++++- gradle.properties | 2 +- src/main/resources/fabric-installer.json | 10 +++++----- src/main/resources/fabric-installer.launchwrapper.json | 10 +++++----- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index eab165844..3c4b39f28 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,13 @@ allprojects { name = 'Fabric' url = 'https://maven.fabricmc.net/' } - mavenCentral() + mavenCentral() { + content { + // Force ASM to come from the fabric maven. + // This ensures that the ASM version has been mirrored for use by the launcher/installer. + excludeGroupByRegex "org.ow2.asm" + } + } } } diff --git a/gradle.properties b/gradle.properties index f9a90f7e0..06e218ab6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,5 +5,5 @@ group = net.fabricmc description = The mod loading component of Fabric url = https://github.com/FabricMC/fabric-loader -asm_version = 9.3 +asm_version = 9.4 mixin_version = 0.11.4+mixin.0.8.5 diff --git a/src/main/resources/fabric-installer.json b/src/main/resources/fabric-installer.json index f98473ef7..01d360181 100644 --- a/src/main/resources/fabric-installer.json +++ b/src/main/resources/fabric-installer.json @@ -21,23 +21,23 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm:9.3", + "name": "org.ow2.asm:asm:9.4", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-analysis:9.3", + "name": "org.ow2.asm:asm-analysis:9.4", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-commons:9.3", + "name": "org.ow2.asm:asm-commons:9.4", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-tree:9.3", + "name": "org.ow2.asm:asm-tree:9.4", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-util:9.3", + "name": "org.ow2.asm:asm-util:9.4", "url": "https://maven.fabricmc.net/" } ], diff --git a/src/main/resources/fabric-installer.launchwrapper.json b/src/main/resources/fabric-installer.launchwrapper.json index cc97f8317..8e14f2c8d 100644 --- a/src/main/resources/fabric-installer.launchwrapper.json +++ b/src/main/resources/fabric-installer.launchwrapper.json @@ -24,23 +24,23 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm:9.3", + "name": "org.ow2.asm:asm:9.4", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-analysis:9.3", + "name": "org.ow2.asm:asm-analysis:9.4", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-commons:9.3", + "name": "org.ow2.asm:asm-commons:9.4", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-tree:9.3", + "name": "org.ow2.asm:asm-tree:9.4", "url": "https://maven.fabricmc.net/" }, { - "name": "org.ow2.asm:asm-util:9.3", + "name": "org.ow2.asm:asm-util:9.4", "url": "https://maven.fabricmc.net/" } ], From 7fde9dc1f94b56bcc796264923b082c2bcfedab2 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Tue, 20 Dec 2022 11:59:28 +0000 Subject: [PATCH 56/80] Fix crash in MixinIntermediaryDevRemapper when trying to remap methods in Object. (#746) --- .../impl/util/mappings/MixinIntermediaryDevRemapper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/util/mappings/MixinIntermediaryDevRemapper.java b/src/main/java/net/fabricmc/loader/impl/util/mappings/MixinIntermediaryDevRemapper.java index c465f390d..b88a7ed68 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/mappings/MixinIntermediaryDevRemapper.java +++ b/src/main/java/net/fabricmc/loader/impl/util/mappings/MixinIntermediaryDevRemapper.java @@ -154,7 +154,7 @@ public String mapMethodName(String owner, String name, String desc) { return s; } - if (!classInfo.getSuperName().startsWith("java/")) { + if (classInfo.getSuperName() != null && !classInfo.getSuperName().startsWith("java/")) { ClassInfo cSuper = classInfo.getSuperClass(); if (cSuper != null) { @@ -221,7 +221,7 @@ public String mapFieldName(String owner, String name, String desc) { return s; } - if (c.getSuperName().startsWith("java/")) { + if (c.getSuperName() == null || c.getSuperName().startsWith("java/")) { break; } From 97b84454b68bb8728057c28311f36dd2578ea91f Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Tue, 20 Dec 2022 12:05:25 +0000 Subject: [PATCH 57/80] Don't combine resources from the two classloaders in KnotClassLoader.getResources (#747) --- .../impl/launch/knot/KnotClassLoader.java | 47 +++---------------- 1 file changed, 7 insertions(+), 40 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java index 8d2bbaff2..711dbfc0c 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/KnotClassLoader.java @@ -98,46 +98,13 @@ public InputStream getResourceAsStream(String name) { public Enumeration getResources(String name) throws IOException { Objects.requireNonNull(name); - Enumeration first = urlLoader.getResources(name); - Enumeration second = originalLoader.getResources(name); - return new Enumeration() { - Enumeration current = first; - - @Override - public boolean hasMoreElements() { - if (current == null) { - return false; - } - - if (current.hasMoreElements()) { - return true; - } - - if (current == first && second.hasMoreElements()) { - return true; - } - - return false; - } - - @Override - public URL nextElement() { - if (current == null) { - return null; - } - - if (!current.hasMoreElements()) { - if (current == first) { - current = second; - } else { - current = null; - return null; - } - } - - return current.nextElement(); - } - }; + final Enumeration resources = urlLoader.getResources(name); + + if (!resources.hasMoreElements()) { + return originalLoader.getResources(name); + } + + return resources; } @Override From ff00bf409384c7bda7c18a1f6498251ebd7c6c69 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Tue, 20 Dec 2022 12:08:08 +0000 Subject: [PATCH 58/80] Add auto client test and Update build dependencies (#748) --- .github/workflows/build.yml | 29 ++- .github/workflows/manage_issues.yml | 4 +- .github/workflows/release.yml | 7 +- HEADER | 27 ++- build.gradle | 22 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 61574 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 269 ++++++++++++++--------- gradlew.bat | 15 +- minecraft/minecraft-test/build.gradle | 76 ++++++- 10 files changed, 300 insertions(+), 152 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3c7ce4057..8eb70f9c4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,16 +4,35 @@ jobs: build: strategy: matrix: - java: [8-jdk, 11-jdk, 16-jdk, 17-jdk] - runs-on: ubuntu-20.04 + java: [8-jdk, 11-jdk, 16-jdk, 17-jdk, 19-jdk] + runs-on: ubuntu-22.04 container: image: eclipse-temurin:${{ matrix.java }} options: --user root steps: - - uses: actions/checkout@v1 - - uses: gradle/wrapper-validation-action@v1 + - uses: actions/checkout@v3 - run: ./gradlew build publishToMavenLocal --stacktrace --warning-mode fail - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: Artifacts path: build/libs/ + + client_test: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - uses: actions/setup-java@v3 + with: + distribution: 'microsoft' + java-version: '17' + - name: Run Auto test Client + uses: modmuss50/xvfb-action@v1 + with: + run: ./gradlew :minecraft:minecraft-test:runProductionAutoTestClient --stacktrace --warning-mode=fail + - uses: actions/upload-artifact@v3 + if: always() + with: + name: Client Test Screenshots + path: minecraft/minecraft-test/run/screenshots \ No newline at end of file diff --git a/.github/workflows/manage_issues.yml b/.github/workflows/manage_issues.yml index b3259ecea..db534ff59 100644 --- a/.github/workflows/manage_issues.yml +++ b/.github/workflows/manage_issues.yml @@ -6,9 +6,9 @@ on: jobs: labels: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: FabricMC/fabric-action-scripts@v1 + - uses: FabricMC/fabric-action-scripts@v2 with: context: ${{ github.event.action }} label: ${{ github.event.label.name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 16c262bde..60ffe6a34 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,22 +6,21 @@ permissions: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 container: image: eclipse-temurin:17-jdk options: --user root steps: - run: apt update && apt install git -y && git --version - run: git config --global --add safe.directory /__w/fabric-loader/fabric-loader - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: FabricMC/fabric-action-scripts@v1 + - uses: FabricMC/fabric-action-scripts@v2 id: changelog with: context: changelog workflow_id: release.yml - - uses: gradle/wrapper-validation-action@v1 - run: ./gradlew checkVersion build publish github --stacktrace env: MAVEN_URL: ${{ secrets.MAVEN_URL }} diff --git a/HEADER b/HEADER index ffc271abc..1848d5c49 100644 --- a/HEADER +++ b/HEADER @@ -1,13 +1,16 @@ -Copyright 2016 FabricMC +/* + * Copyright 2016 FabricMC + * + * 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. + */ -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. diff --git a/build.gradle b/build.gradle index 3c4b39f28..92ed29029 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { dependencies { classpath 'org.kohsuke:github-api:1.135' - classpath 'com.guardsquare:proguard-gradle:' + (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11) ? '7.2.0-beta2' : '7.1.0') + classpath 'com.guardsquare:proguard-gradle:' + (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11) ? '7.3.0' : '7.1.0') } } @@ -11,10 +11,10 @@ plugins { id 'eclipse' id 'maven-publish' id 'checkstyle' - id 'org.cadixdev.licenser' version '0.6.1' - id 'fabric-loom' version '0.11-SNAPSHOT' apply false + id 'com.diffplug.spotless' version "6.12.0" + id 'fabric-loom' version '1.0-SNAPSHOT' apply false id 'com.github.johnrengelman.shadow' version '7.1.2' - id "me.modmuss50.remotesign" version "0.2.4" + id 'me.modmuss50.remotesign' version "0.4.0" } sourceCompatibility = JavaVersion.VERSION_1_8 @@ -22,13 +22,13 @@ targetCompatibility = JavaVersion.VERSION_1_8 archivesBaseName = "fabric-loader" -// Fetch build number from Jenkins def ENV = System.getenv() allprojects { apply plugin: 'java-library' apply plugin: 'eclipse' apply plugin: 'checkstyle' + apply plugin: "com.diffplug.spotless" def constantsSource = rootProject.file("src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java").text version = (constantsSource =~ /\s+VERSION\s*=\s*"(.*)";/)[0][1] + (ENV.GITHUB_ACTIONS ? "" : "+local") @@ -168,7 +168,6 @@ task proguardJar(type: ProGuardTask, dependsOn: fatJar) { inputs.files(fatJar, classpath) outputs.files(proguardFile) - outputs.upToDateWhen { false } doFirst { classpath.resolve().forEach { @@ -267,12 +266,11 @@ allprojects { toolVersion = '8.44' } - license { - header project.rootProject.file("HEADER") - include '**/*.java' - - // Exclude gson since it is google's code, we just modify and bundle it - exclude '**/lib/gson/*.java' + spotless { + java { + licenseHeaderFile(rootProject.file("HEADER")) + targetExclude '**/lib/gson/*.java' + } } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch delta 36900 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

yHlr~Qj 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 GTKgBj+ffbx diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b..f398c33c4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882ed..65dcd68d6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,101 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -106,80 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd32c..93e3f59f1 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/minecraft/minecraft-test/build.gradle b/minecraft/minecraft-test/build.gradle index 4d637f08d..4cf7a4002 100644 --- a/minecraft/minecraft-test/build.gradle +++ b/minecraft/minecraft-test/build.gradle @@ -1,7 +1,7 @@ apply plugin: "fabric-loom" -sourceCompatibility = JavaVersion.VERSION_16 -targetCompatibility = JavaVersion.VERSION_16 +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 loom { runConfigs.configureEach { @@ -9,9 +9,11 @@ loom { } } +def minecraft_version = "1.19.3"; + dependencies { - minecraft "com.mojang:minecraft:1.18" - mappings "net.fabricmc:yarn:1.18+build.1:v2" + minecraft "com.mojang:minecraft:${minecraft_version}" + mappings "net.fabricmc:yarn:${minecraft_version}+build.3:v2" implementation project(":minecraft") @@ -34,5 +36,69 @@ dependencies { tasks.withType(JavaCompile).configureEach { it.options.encoding = "UTF-8" - it.options.release = 16 + it.options.release = 17 +} + +import net.fabricmc.loom.util.OperatingSystem +import groovy.json.JsonSlurper + +configurations { + productionRuntime { + extendsFrom configurations.minecraftLibraries + extendsFrom configurations.loaderLibraries + extendsFrom configurations.minecraftRuntimeOnlyLibraries + } + productionRuntimeMods +} + +dependencies { + productionRuntime "net.fabricmc:intermediary:${minecraft_version}" + + // Include the external libraries on the classpath + def installerJson = new JsonSlurper().parse(rootProject.file("src/main/resources/fabric-installer.json")) + installerJson.libraries.common.each { + productionRuntime it.name + } + + // Use Fabric's auto client test + productionRuntimeMods "net.fabricmc.fabric-api:fabric-api:0.69.1+1.19.3" + productionRuntimeMods "net.fabricmc.fabric-api:fabric-api:0.69.1+1.19.3:testmod" +} + +def loaderJarTask = project(":").tasks.proguardJar + +// This is very far beyond loom's API if you copy this, you're on your own. +task runProductionAutoTestClient(type: JavaExec, dependsOn: [loaderJarTask]) { + classpath.from configurations.productionRuntime + classpath.from loaderJarTask + mainClass = "net.fabricmc.loader.impl.launch.knot.KnotClient" + workingDir = file("run") + + afterEvaluate { + dependsOn downloadAssets + } + + doFirst { + classpath.from loom.minecraftProvider.minecraftClientJar + workingDir.mkdirs() + + args( + "--assetIndex", loom.minecraftProvider.versionInfo.assetIndex().fabricId(loom.minecraftProvider.minecraftVersion()), + "--assetsDir", new File(loom.files.userCache, "assets").absolutePath, + "--gameDir", workingDir.absolutePath + ) + + if (OperatingSystem.CURRENT_OS == OperatingSystem.MAC_OS) { + jvmArgs( + "-XstartOnFirstThread" + ) + } + + def mods = configurations.productionRuntimeMods.files.join(File.pathSeparator) + + jvmArgs( + "-Dfabric.addMods=${mods}", + "-Dfabric.autoTest" + ) + } } \ No newline at end of file From 0a456127f8cb69a24d5ddf6a0699dd17785c0ffd Mon Sep 17 00:00:00 2001 From: ishland Date: Tue, 20 Dec 2022 20:22:40 +0800 Subject: [PATCH 59/80] Fixes MixinIntermediaryDevRemapper.mapMethodName unable to map ambiguous values (#751) --- .../loader/impl/util/mappings/MixinIntermediaryDevRemapper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/util/mappings/MixinIntermediaryDevRemapper.java b/src/main/java/net/fabricmc/loader/impl/util/mappings/MixinIntermediaryDevRemapper.java index b88a7ed68..e05aee8d6 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/mappings/MixinIntermediaryDevRemapper.java +++ b/src/main/java/net/fabricmc/loader/impl/util/mappings/MixinIntermediaryDevRemapper.java @@ -138,7 +138,7 @@ public String mapMethodName(String owner, String name, String desc) { } } - ClassInfo classInfo = ClassInfo.forName(owner); + ClassInfo classInfo = ClassInfo.forName(map(owner)); if (classInfo == null) { // unknown class? return name; From 8ddfc5ec7817dbe5d5419fb22bb8e49b78324809 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Tue, 20 Dec 2022 14:57:45 +0000 Subject: [PATCH 60/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index c519b3931..9b18b5344 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.11"; + public static final String VERSION = "0.14.12"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 2a378f1c563b6ec96ae6620a278ecd23fa09da0f Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 18 Jan 2023 15:09:59 +0000 Subject: [PATCH 61/80] Support 1.19.4 in McVersionLookup --- .../fabricmc/loader/impl/game/minecraft/McVersionLookup.java | 4 +++- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java index 0871a2596..509371bb0 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java @@ -287,7 +287,9 @@ protected static String getRelease(String version) { int year = Integer.parseInt(matcher.group(1)); int week = Integer.parseInt(matcher.group(2)); - if (year == 22 && week >= 42 || year >= 23) { + if (year >= 23 && week >= 3) { + return "1.19.4"; + } else if (year == 22 && week >= 42) { return "1.19.3"; } else if (year == 22 && week == 24) { return "1.19.1"; diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 9b18b5344..c83341611 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.12"; + public static final String VERSION = "0.14.13"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 34b8958944def17324802a18b0fbc90e0de235c3 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sat, 4 Feb 2023 13:52:37 +0000 Subject: [PATCH 62/80] Fix entrypoint location in 1.19.4. (#764) Should be a bit more reliable going forward. --- .../game/minecraft/MinecraftGameProvider.java | 8 ++-- .../game/minecraft/patch/EntrypointPatch.java | 47 ++++++++++++++++++- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index 572855006..d51321551 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -85,8 +85,8 @@ public class MinecraftGameProvider implements GameProvider { private McVersion versionData; private boolean hasModLoader = false; - private static final GameTransformer TRANSFORMER = new GameTransformer( - new EntrypointPatch(), + private final GameTransformer transformer = new GameTransformer( + new EntrypointPatch(this), new BrandingPatch(), new EntrypointPatchFML125()); @@ -337,7 +337,7 @@ public void initialize(FabricLauncher launcher) { setupLogHandler(launcher, true); - TRANSFORMER.locateEntrypoints(launcher, gameJars); + transformer.locateEntrypoints(launcher, gameJars); } private void setupLogHandler(FabricLauncher launcher, boolean useTargetCl) { @@ -404,7 +404,7 @@ public String[] getLaunchArguments(boolean sanitize) { @Override public GameTransformer getEntrypointTransformer() { - return TRANSFORMER; + return transformer; } @Override diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java index e407349dd..8858165dc 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/patch/EntrypointPatch.java @@ -35,13 +35,27 @@ import org.objectweb.asm.tree.VarInsnNode; import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.Version; +import net.fabricmc.loader.api.VersionParsingException; +import net.fabricmc.loader.api.metadata.version.VersionPredicate; import net.fabricmc.loader.impl.game.minecraft.Hooks; +import net.fabricmc.loader.impl.game.minecraft.MinecraftGameProvider; import net.fabricmc.loader.impl.game.patch.GamePatch; import net.fabricmc.loader.impl.launch.FabricLauncher; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; +import net.fabricmc.loader.impl.util.version.VersionParser; +import net.fabricmc.loader.impl.util.version.VersionPredicateParser; public class EntrypointPatch extends GamePatch { + private static final VersionPredicate VERSION_1_19_4 = createVersionPredicate(">=1.19.4-"); + + private final MinecraftGameProvider gameProvider; + + public EntrypointPatch(MinecraftGameProvider gameProvider) { + this.gameProvider = gameProvider; + } + private void finishEntrypoint(EnvType type, ListIterator it) { String methodName = String.format("start%s", type == EnvType.CLIENT ? "Client" : "Server"); it.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Hooks.INTERNAL_NAME, methodName, "(Ljava/io/File;Ljava/lang/Object;)V", false)); @@ -51,6 +65,7 @@ private void finishEntrypoint(EnvType type, ListIterator it) { public void process(FabricLauncher launcher, Function classSource, Consumer classEmitter) { EnvType type = launcher.getEnvironmentType(); String entrypoint = launcher.getEntrypoint(); + Version gameVersion = getGameVersion(); if (!entrypoint.startsWith("net.minecraft.") && !entrypoint.startsWith("com.mojang.")) { return; @@ -181,6 +196,7 @@ public void process(FabricLauncher launcher, Function class MethodNode gameMethod = null; MethodNode gameConstructor = null; AbstractInsnNode lwjglLogNode = null; + AbstractInsnNode currentThreadNode = null; int gameMethodQuality = 0; if (!is20w22aServerOrHigher) { @@ -198,12 +214,19 @@ public void process(FabricLauncher launcher, Function class // Try to find a method with an LDC string "LWJGL Version: ". // This is the "init()" method, or as of 19w38a is the constructor, or called somewhere in that vicinity, // and is by far superior in hooking into for a well-off mod start. + // Also try and find a Thread.currentThread() call before the LWJGL version print. int qual = 2; boolean hasLwjglLog = false; for (AbstractInsnNode insn : gmCandidate.instructions) { - if (insn instanceof LdcInsnNode) { + if (insn.getOpcode() == Opcodes.INVOKESTATIC && insn instanceof MethodInsnNode) { + final MethodInsnNode methodInsn = (MethodInsnNode) insn; + + if ("currentThread".equals(methodInsn.name) && "java/lang/Thread".equals(methodInsn.owner) && "()Ljava/lang/Thread;".equals(methodInsn.desc)) { + currentThreadNode = methodInsn; + } + } else if (insn instanceof LdcInsnNode) { Object cst = ((LdcInsnNode) insn).cst; if (cst instanceof String) { @@ -466,7 +489,11 @@ public void process(FabricLauncher launcher, Function class it = gameMethod.instructions.iterator(); } - if (lwjglLogNode != null) { + // Add the hook just before the Thread.currentThread() call for 1.19.4 or later + // If older 4 method insn's before the lwjgl log + if (currentThreadNode != null && VERSION_1_19_4.test(gameVersion)) { + moveBefore(it, currentThreadNode); + } else if (lwjglLogNode != null) { moveBefore(it, lwjglLogNode); for (int i = 0; i < 4; i++) { @@ -538,4 +565,20 @@ private boolean hasStrInMethod(String cls, String methodName, String methodDesc, return false; } + + private Version getGameVersion() { + try { + return VersionParser.parseSemantic(gameProvider.getNormalizedGameVersion()); + } catch (VersionParsingException e) { + throw new RuntimeException(e); + } + } + + private static VersionPredicate createVersionPredicate(String predicate) { + try { + return VersionPredicateParser.parse(predicate); + } catch (VersionParsingException e) { + throw new RuntimeException(e); + } + } } From 96b52a4c0ddba96bb802dadbbdb905261f2a2662 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sat, 4 Feb 2023 13:53:17 +0000 Subject: [PATCH 63/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index c83341611..ef918b9ed 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -69,7 +69,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.13"; + public static final String VERSION = "0.14.14"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 1032cfa0eff2be037bc362a8f7caca5c2b964181 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 8 Feb 2023 18:47:54 +0000 Subject: [PATCH 64/80] Fix forked error window not closing when the parent process is shutdown. (#768) * Fix forked error window not closing when the parent process is shutdown. * Remove shutdown hook. --- .../java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java b/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java index 17f315dd9..e0ac4f28a 100644 --- a/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java +++ b/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java @@ -74,11 +74,18 @@ private static void openForked(FabricStatusTree tree) throws IOException, Interr .redirectError(ProcessBuilder.Redirect.INHERIT) .start(); + final Thread shutdownHook = new Thread(process::destroy); + + Runtime.getRuntime().addShutdownHook(shutdownHook); + try (DataOutputStream os = new DataOutputStream(process.getOutputStream())) { tree.writeTo(os); } int rVal = process.waitFor(); + + Runtime.getRuntime().removeShutdownHook(shutdownHook); + if (rVal != 0) throw new IOException("subprocess exited with code "+rVal); } From f6d91ee3b3d32dffb5aa7763254ac98bcaa3ed23 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Mon, 30 Jan 2023 21:18:29 +0000 Subject: [PATCH 65/80] Opt of loom remapping --- build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 92ed29029..3229e0926 100644 --- a/build.gradle +++ b/build.gradle @@ -144,7 +144,8 @@ task fatJar(type: ShadowJar, dependsOn: getSat4jAbout) { manifest { attributes ( - 'Main-Class': 'net.fabricmc.loader.impl.launch.server.FabricServerLauncher' + 'Main-Class': 'net.fabricmc.loader.impl.launch.server.FabricServerLauncher', + 'Fabric-Loom-Remap': 'false' ) } From a936ed61093b8bd1db43f40c665cb1ee1001e28f Mon Sep 17 00:00:00 2001 From: Frederik van der Els <49305700+xpple@users.noreply.github.com> Date: Mon, 13 Feb 2023 18:25:14 +0100 Subject: [PATCH 66/80] Fix typo (#763) * Fix typo * Thanks NebelNidas --- src/main/java/net/fabricmc/loader/api/FabricLoader.java | 2 +- .../java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/api/FabricLoader.java b/src/main/java/net/fabricmc/loader/api/FabricLoader.java index 6fe4776e0..1e31b8fbc 100644 --- a/src/main/java/net/fabricmc/loader/api/FabricLoader.java +++ b/src/main/java/net/fabricmc/loader/api/FabricLoader.java @@ -178,7 +178,7 @@ static FabricLoader getInstance() { *

The game instance may not always be available depending on the game version and {@link EnvType environment}. * * @return A client or server instance object - * @deprecated This method is experimental and it's use is discouraged. + * @deprecated This method is experimental and its use is discouraged. */ /* @Nullable */ @Deprecated diff --git a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java index ac87f78a1..9d786e0a5 100644 --- a/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java +++ b/src/main/java/net/fabricmc/loader/impl/discovery/ModDiscoverer.java @@ -173,7 +173,7 @@ public List discoverMods(FabricLoaderImpl loader, Map ret = Collections.newSetFromMap(new IdentityHashMap<>(candidates.size() * 2)); Queue queue = new ArrayDeque<>(candidates); From 0c5e3f4e109c5115e7aaf36bcb90f8c2719b1712 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sat, 18 Feb 2023 13:59:03 +0000 Subject: [PATCH 67/80] Update mixin (#770) * Update mixin * Update mixin * Update mixin --- gradle.properties | 2 +- src/main/resources/fabric-installer.json | 2 +- src/main/resources/fabric-installer.launchwrapper.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 06e218ab6..78d2e4de5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ description = The mod loading component of Fabric url = https://github.com/FabricMC/fabric-loader asm_version = 9.4 -mixin_version = 0.11.4+mixin.0.8.5 +mixin_version = 0.12.3+mixin.0.8.5 diff --git a/src/main/resources/fabric-installer.json b/src/main/resources/fabric-installer.json index 01d360181..62b470e3d 100644 --- a/src/main/resources/fabric-installer.json +++ b/src/main/resources/fabric-installer.json @@ -9,7 +9,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:sponge-mixin:0.11.4+mixin.0.8.5", + "name": "net.fabricmc:sponge-mixin:0.12.3+mixin.0.8.5", "url": "https://maven.fabricmc.net/" }, { diff --git a/src/main/resources/fabric-installer.launchwrapper.json b/src/main/resources/fabric-installer.launchwrapper.json index 8e14f2c8d..bf236356e 100644 --- a/src/main/resources/fabric-installer.launchwrapper.json +++ b/src/main/resources/fabric-installer.launchwrapper.json @@ -12,7 +12,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:sponge-mixin:0.11.4+mixin.0.8.5", + "name": "net.fabricmc:sponge-mixin:0.12.3+mixin.0.8.5", "url": "https://maven.fabricmc.net/" }, { From 8765403937197e8b67162dc98cbac9d6ac6eddf4 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Sat, 18 Feb 2023 15:17:21 +0000 Subject: [PATCH 68/80] Add fabric-loader-junit to allow unit testing of transformed classes. (#767) Also updates the build script to Gradle 8 --- .gitignore | 3 +- build.gradle | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 61608 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 4 +- junit/.gitignore | 6 ++ junit/build.gradle | 61 +++++++++++++++++ .../FabricLoaderLauncherSessionListener.java | 62 ++++++++++++++++++ ....platform.launcher.LauncherSessionListener | 1 + minecraft/minecraft-test/build.gradle | 12 ++++ ...MixinGuiMain.java => MixinGrassBlock.java} | 25 ++++--- .../fabricmc.test.mixins.client.json | 1 + .../minecraft/test/junit/JunitTest.java | 62 ++++++++++++++++++ .../game/minecraft/MinecraftGameProvider.java | 3 +- settings.gradle | 1 + .../loader/impl/game/LoaderLibrary.java | 31 ++++++++- .../loader/impl/launch/knot/Knot.java | 2 +- .../loader/impl/util/SystemProperties.java | 2 + .../fabricmc/test/V1ModJsonParsingTests.java | 2 +- 19 files changed, 260 insertions(+), 24 deletions(-) create mode 100644 junit/.gitignore create mode 100644 junit/build.gradle create mode 100644 junit/src/main/java/net/fabricmc/loader/impl/junit/FabricLoaderLauncherSessionListener.java create mode 100644 junit/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener rename minecraft/minecraft-test/src/main/java/net/fabricmc/minecraft/test/mixin/{MixinGuiMain.java => MixinGrassBlock.java} (58%) create mode 100644 minecraft/minecraft-test/src/test/java/net/fabricmc/minecraft/test/junit/JunitTest.java diff --git a/.gitignore b/.gitignore index d7e9a8f31..e7c2a6448 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,5 @@ !/settings.gradle !/proguard.conf -!/minecraft \ No newline at end of file +!/minecraft +!/junit \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3229e0926..25584794f 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ plugins { id 'maven-publish' id 'checkstyle' id 'com.diffplug.spotless' version "6.12.0" - id 'fabric-loom' version '1.0-SNAPSHOT' apply false + id 'fabric-loom' version '1.1-SNAPSHOT' apply false id 'com.github.johnrengelman.shadow' version '7.1.2' id 'me.modmuss50.remotesign' version "0.4.0" } @@ -84,7 +84,7 @@ dependencies { testCompileOnly 'org.jetbrains:annotations:23.0.0' // Unit testing for mod metadata - testImplementation('org.junit.jupiter:junit-jupiter:5.8.2') + testImplementation('org.junit.jupiter:junit-jupiter:5.9.2') } processResources { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..ccebba7710deaf9f98673a68957ea02138b60d0a 100644 GIT binary patch delta 5094 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 delta 5107 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 I6Xdi10eY*&F8}}l diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f398c33c4..fc10b601f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d6..79a61d421 100755 --- a/gradlew +++ b/gradlew @@ -144,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac diff --git a/junit/.gitignore b/junit/.gitignore new file mode 100644 index 000000000..31ecb6570 --- /dev/null +++ b/junit/.gitignore @@ -0,0 +1,6 @@ +# Ignore everything +/* + +!/src +!/build.gradle +!/.gitignore diff --git a/junit/build.gradle b/junit/build.gradle new file mode 100644 index 000000000..f464d7641 --- /dev/null +++ b/junit/build.gradle @@ -0,0 +1,61 @@ +apply plugin: 'maven-publish' +apply plugin: 'me.modmuss50.remotesign' + +sourceCompatibility = JavaVersion.VERSION_1_8 +targetCompatibility = JavaVersion.VERSION_1_8 + +archivesBaseName = "fabric-loader-junit" +version = rootProject.version +group = rootProject.group + +def ENV = System.getenv() +def signingEnabled = ENV.SIGNING_SERVER + +repositories { + mavenCentral() +} + +dependencies { + api project(":") + + api platform("org.junit:junit-bom:5.9.2") + api "org.junit.jupiter:junit-jupiter-engine" + implementation "org.junit.platform:junit-platform-launcher" +} + +java { + withSourcesJar() +} + +if (signingEnabled) { + remoteSign { + requestUrl = ENV.SIGNING_SERVER + pgpAuthKey = ENV.SIGNING_PGP_KEY + jarAuthKey = ENV.SIGNING_JAR_KEY + + afterEvaluate { + sign publishing.publications.maven + } + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.archivesBaseName + from components.java + } + } + + repositories { + if (ENV.MAVEN_URL) { + maven { + url ENV.MAVEN_URL + credentials { + username ENV.MAVEN_USERNAME + password ENV.MAVEN_PASSWORD + } + } + } + } +} \ No newline at end of file diff --git a/junit/src/main/java/net/fabricmc/loader/impl/junit/FabricLoaderLauncherSessionListener.java b/junit/src/main/java/net/fabricmc/loader/impl/junit/FabricLoaderLauncherSessionListener.java new file mode 100644 index 000000000..74a76a131 --- /dev/null +++ b/junit/src/main/java/net/fabricmc/loader/impl/junit/FabricLoaderLauncherSessionListener.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016 FabricMC + * + * 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 net.fabricmc.loader.impl.junit; + +import org.junit.platform.launcher.LauncherSession; +import org.junit.platform.launcher.LauncherSessionListener; + +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.impl.launch.knot.Knot; +import net.fabricmc.loader.impl.util.SystemProperties; + +public class FabricLoaderLauncherSessionListener implements LauncherSessionListener { + static { + System.setProperty(SystemProperties.DEVELOPMENT, "true"); + System.setProperty(SystemProperties.UNIT_TEST, "true"); + } + + private final Knot knot; + private final ClassLoader classLoader; + + private ClassLoader launcherSessionClassLoader; + + public FabricLoaderLauncherSessionListener() { + final Thread currentThread = Thread.currentThread(); + final ClassLoader originalClassLoader = currentThread.getContextClassLoader(); + + try { + knot = new Knot(EnvType.CLIENT); + classLoader = knot.init(new String[]{}); + } finally { + // Knot.init sets the context class loader, revert it back for now. + currentThread.setContextClassLoader(originalClassLoader); + } + } + + @Override + public void launcherSessionOpened(LauncherSession session) { + final Thread currentThread = Thread.currentThread(); + launcherSessionClassLoader = currentThread.getContextClassLoader(); + currentThread.setContextClassLoader(classLoader); + } + + @Override + public void launcherSessionClosed(LauncherSession session) { + final Thread currentThread = Thread.currentThread(); + currentThread.setContextClassLoader(launcherSessionClassLoader); + } +} diff --git a/junit/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener b/junit/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener new file mode 100644 index 000000000..ec41e4461 --- /dev/null +++ b/junit/src/main/resources/META-INF/services/org.junit.platform.launcher.LauncherSessionListener @@ -0,0 +1 @@ +net.fabricmc.loader.impl.junit.FabricLoaderLauncherSessionListener \ No newline at end of file diff --git a/minecraft/minecraft-test/build.gradle b/minecraft/minecraft-test/build.gradle index 4cf7a4002..bc524f4c1 100644 --- a/minecraft/minecraft-test/build.gradle +++ b/minecraft/minecraft-test/build.gradle @@ -11,11 +11,17 @@ loom { def minecraft_version = "1.19.3"; +repositories { + mavenCentral() +} + dependencies { minecraft "com.mojang:minecraft:${minecraft_version}" mappings "net.fabricmc:yarn:${minecraft_version}+build.3:v2" implementation project(":minecraft") + implementation project(":minecraft").sourceSets.main.output + implementation project(":").sourceSets.main.output // Required for mixin annotation processor annotationProcessor "org.ow2.asm:asm:${project.asm_version}" @@ -32,6 +38,12 @@ dependencies { exclude module: 'launchwrapper' exclude module: 'guava' } + + testImplementation project(":junit") +} + +test { + useJUnitPlatform() } tasks.withType(JavaCompile).configureEach { diff --git a/minecraft/minecraft-test/src/main/java/net/fabricmc/minecraft/test/mixin/MixinGuiMain.java b/minecraft/minecraft-test/src/main/java/net/fabricmc/minecraft/test/mixin/MixinGrassBlock.java similarity index 58% rename from minecraft/minecraft-test/src/main/java/net/fabricmc/minecraft/test/mixin/MixinGuiMain.java rename to minecraft/minecraft-test/src/main/java/net/fabricmc/minecraft/test/mixin/MixinGrassBlock.java index 903df1b63..4e2a106ce 100644 --- a/minecraft/minecraft-test/src/main/java/net/fabricmc/minecraft/test/mixin/MixinGuiMain.java +++ b/minecraft/minecraft-test/src/main/java/net/fabricmc/minecraft/test/mixin/MixinGrassBlock.java @@ -19,21 +19,18 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.screen.TitleScreen; -import net.minecraft.client.util.math.MatrixStack; -import net.minecraft.text.Text; +import net.minecraft.block.BlockState; +import net.minecraft.block.GrassBlock; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.random.Random; +import net.minecraft.world.World; -@Mixin(value = TitleScreen.class, remap = false) -public abstract class MixinGuiMain extends Screen { - protected MixinGuiMain(Text textComponent_1) { - super(textComponent_1); - } - - @Inject(method = "render", at = @At("RETURN")) - public void render(MatrixStack matrixStack, int mouseX, int mouseY, float delta, CallbackInfo info) { - this.textRenderer.draw(matrixStack, "Fabric Test Mod", 2, this.height - 30, -1); +@Mixin(GrassBlock.class) +public class MixinGrassBlock { + @Inject(method = "canGrow", at = @At("HEAD"), cancellable = true) + public void canGrow(World world, Random random, BlockPos pos, BlockState state, CallbackInfoReturnable cir) { + cir.setReturnValue(false); } } diff --git a/minecraft/minecraft-test/src/main/resources/fabricmc.test.mixins.client.json b/minecraft/minecraft-test/src/main/resources/fabricmc.test.mixins.client.json index 3189ecf95..12a8404f5 100644 --- a/minecraft/minecraft-test/src/main/resources/fabricmc.test.mixins.client.json +++ b/minecraft/minecraft-test/src/main/resources/fabricmc.test.mixins.client.json @@ -3,6 +3,7 @@ "package": "net.fabricmc.minecraft.test.mixin", "compatibilityLevel": "JAVA_8", "mixins": [ + "MixinGrassBlock" ], "injectors": { "defaultRequire": 1 diff --git a/minecraft/minecraft-test/src/test/java/net/fabricmc/minecraft/test/junit/JunitTest.java b/minecraft/minecraft-test/src/test/java/net/fabricmc/minecraft/test/junit/JunitTest.java new file mode 100644 index 000000000..142135207 --- /dev/null +++ b/minecraft/minecraft-test/src/test/java/net/fabricmc/minecraft/test/junit/JunitTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016 FabricMC + * + * 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 net.fabricmc.minecraft.test.junit; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import net.minecraft.Bootstrap; +import net.minecraft.SharedConstants; +import net.minecraft.block.Blocks; +import net.minecraft.block.GrassBlock; +import net.minecraft.item.Items; +import net.minecraft.registry.Registries; +import net.minecraft.util.Identifier; + +import net.fabricmc.loader.api.FabricLoader; + +public class JunitTest { + @BeforeAll + public static void setup() { + SharedConstants.createGameVersion(); + Bootstrap.initialize(); + } + + @Test + public void testItems() { + Identifier id = Registries.ITEM.getId(Items.DIAMOND); + assertEquals(id.toString(), "minecraft:diamond"); + + System.out.println(id); + } + + @Test + public void testMixin() { + // MixinGrassBlock sets canGrow to false + GrassBlock grassBlock = (GrassBlock) Blocks.GRASS_BLOCK; + boolean canGrow = grassBlock.canGrow(null, null, null, null); + assertFalse(canGrow); + } + + @Test + public void testAccessLoader() { + FabricLoader.getInstance().getAllMods(); + } +} diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index d51321551..f6a315436 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -325,7 +325,8 @@ public void initialize(FabricLauncher launcher) { realmsJar = obfJars.get("realms"); } - if (!logJars.isEmpty()) { + // Load the logger libraries on the platform CL when in a unit test + if (!logJars.isEmpty() && !Boolean.getBoolean(SystemProperties.UNIT_TEST)) { for (Path jar : logJars) { if (gameJars.contains(jar)) { launcher.addToClassPath(jar, ALLOWED_EARLY_CLASS_PREFIXES); diff --git a/settings.gradle b/settings.gradle index 9c7c525d2..53ecb17d9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,6 +10,7 @@ pluginManagement { rootProject.name='fabric-loader' include "minecraft" +include "junit" if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_17)) { include "minecraft:minecraft-test" diff --git a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java index bcc9df043..1a3c6c224 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java @@ -30,6 +30,7 @@ import net.fabricmc.accesswidener.AccessWidener; import net.fabricmc.api.EnvType; +import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.UrlConversionException; import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.mapping.tree.TinyMappingFactory; @@ -49,10 +50,23 @@ enum LoaderLibrary { SAT4J_CORE(ContradictionException.class), SAT4J_PB(SolverFactory.class), SERVER_LAUNCH("fabric-server-launch.properties", EnvType.SERVER), // installer generated jar to run setup loader's class path - SERVER_LAUNCHER("net/fabricmc/installer/ServerLauncher.class", EnvType.SERVER); // installer based launch-through method + SERVER_LAUNCHER("net/fabricmc/installer/ServerLauncher.class", EnvType.SERVER), + JUNIT_API("org/junit/jupiter/api/Test.class", null), + JUNIT_PLATFORM_ENGINE("org/junit/platform/engine/TestEngine.class", null), + JUNIT_PLATFORM_LAUNCHER("org/junit/platform/launcher/core/LauncherFactory.class", null), + JUNIT_JUPITER("org/junit/jupiter/engine/JupiterTestEngine.class", null), + FABRIC_LOADER_JUNIT("net/fabricmc/loader/impl/junit/FabricLoaderLauncherSessionListener.class", null), + + // Logging libraries are only loaded from the platform CL when running as a unit test. + LOG4J_API("org/apache/logging/log4j/LogManager.class", true), + LOG4J_CORE("META-INF/services/org.apache.logging.log4j.spi.Provider", true), + LOG4J_CONFIG("log4j2.xml", true), + LOG4J_PLUGIN_3("net/minecrell/terminalconsole/util/LoggerNamePatternSelector.class", true), + SLF4J_API("org/slf4j/Logger.class", true); final Path path; final EnvType env; + final boolean loggerLibrary; LoaderLibrary(Class cls) { this(UrlUtil.getCodeSource(cls)); @@ -63,9 +77,14 @@ enum LoaderLibrary { this.path = path; this.env = null; + this.loggerLibrary = false; } LoaderLibrary(String file, EnvType env) { + this(file, env, false); + } + + LoaderLibrary(String file, EnvType env, boolean loggerLibrary) { URL url = LoaderLibrary.class.getClassLoader().getResource(file); try { @@ -74,9 +93,19 @@ enum LoaderLibrary { } catch (UrlConversionException e) { throw new RuntimeException(e); } + + this.loggerLibrary = false; + } + + LoaderLibrary(String path, boolean loggerLibrary) { + this(path, null, loggerLibrary); } boolean isApplicable(EnvType env) { + if (loggerLibrary) { + return Boolean.getBoolean(SystemProperties.UNIT_TEST); + } + return this.env == null || this.env == env; } } diff --git a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java index c162461a1..f7a713505 100644 --- a/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java +++ b/src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java @@ -81,7 +81,7 @@ public Knot(EnvType type) { this.envType = type; } - protected ClassLoader init(String[] args) { + public ClassLoader init(String[] args) { setProperties(properties); // configure fabric vars diff --git a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java index 331a255fd..5ae63d62b 100644 --- a/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java +++ b/src/main/java/net/fabricmc/loader/impl/util/SystemProperties.java @@ -62,6 +62,8 @@ public final class SystemProperties { public static final String DEBUG_RESOLUTION_TIMEOUT = "fabric.debug.resolutionTimeout"; // replace mod versions (modA:versionA,modB:versionB,...) public static final String DEBUG_REPLACE_VERSION = "fabric.debug.replaceVersion"; + // whether fabric loader is running in a unit test, this affects logging classpath setup + public static final String UNIT_TEST = "fabric.unitTest"; private SystemProperties() { } diff --git a/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java b/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java index d8553adba..30ee4415f 100644 --- a/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java +++ b/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java @@ -48,7 +48,7 @@ final class V1ModJsonParsingTests { private static Path errorPath; @BeforeAll - private static void setupPaths() { + public static void setupPaths() { testLocation = new File(System.getProperty("user.dir")) .toPath() .resolve("src") From d54566e4265f2fcf1c25f3a37afa75d0b7273d32 Mon Sep 17 00:00:00 2001 From: ArtDev <45949002+artdeell@users.noreply.github.com> Date: Sat, 18 Feb 2023 18:19:57 +0300 Subject: [PATCH 69/80] Create and run applet on AWT EventQueue thread (#757) * Create and run applet on AWT EventQueue thread * Fix styling * Fixed styling, again --- .../impl/game/minecraft/applet/AppletMain.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/applet/AppletMain.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/applet/AppletMain.java index ddf9d3b77..3e7eb627e 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/applet/AppletMain.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/applet/AppletMain.java @@ -18,8 +18,11 @@ import java.io.File; -public final class AppletMain { - private AppletMain() { } +public final class AppletMain implements Runnable { + final String[] args; + private AppletMain(String[] args) { + this.args = args; + } public static File hookGameDir(File file) { File proposed = AppletLauncher.gameDir; @@ -32,6 +35,11 @@ public static File hookGameDir(File file) { } public static void main(String[] args) { + java.awt.EventQueue.invokeLater(new AppletMain(args)); + } + + @Override + public void run() { AppletFrame me = new AppletFrame("Minecraft", null); me.launch(args); } From 9f4ba698707b8c87aa6dba5ec4bdd320c1b7fda3 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Sat, 18 Feb 2023 10:26:20 -0500 Subject: [PATCH 70/80] Fix icon objects in mod metadata not being parsed correctly (#743) * Fix icon objects in mod metadata not being parsed correctly Fixes #741 * Add mod metadata parsing tests for icons --- .../impl/metadata/V1ModMetadataParser.java | 10 +++-- .../fabricmc/test/V1ModJsonParsingTests.java | 37 +++++++++++++++++++ .../parsing/v1/spec/any_icon_size.json | 6 +++ .../testing/parsing/v1/spec/icon_sizes.json | 10 +++++ .../testing/parsing/v1/spec/no_icon.json | 5 +++ 5 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 src/test/resources/testing/parsing/v1/spec/any_icon_size.json create mode 100644 src/test/resources/testing/parsing/v1/spec/icon_sizes.json create mode 100644 src/test/resources/testing/parsing/v1/spec/no_icon.json diff --git a/src/main/java/net/fabricmc/loader/impl/metadata/V1ModMetadataParser.java b/src/main/java/net/fabricmc/loader/impl/metadata/V1ModMetadataParser.java index cf65a6ccc..890255f60 100644 --- a/src/main/java/net/fabricmc/loader/impl/metadata/V1ModMetadataParser.java +++ b/src/main/java/net/fabricmc/loader/impl/metadata/V1ModMetadataParser.java @@ -595,10 +595,6 @@ private static V1ModMetadata.IconEntry readIcon(JsonReader reader) throws IOExce final SortedMap iconMap = new TreeMap<>(Comparator.naturalOrder()); while (reader.hasNext()) { - if (reader.peek() != JsonToken.STRING) { - throw new ParseMetadataException("Icon path must be a string", reader); - } - String key = reader.nextName(); int size; @@ -612,6 +608,12 @@ private static V1ModMetadata.IconEntry readIcon(JsonReader reader) throws IOExce if (size < 1) { throw new ParseMetadataException("Size must be positive!", reader); } + + if (reader.peek() != JsonToken.STRING) { + throw new ParseMetadataException("Icon path must be a string", reader); + } + + iconMap.put(size, reader.nextString()); } reader.endObject(); diff --git a/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java b/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java index 30ee4415f..3c58e49ab 100644 --- a/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java +++ b/src/test/java/net/fabricmc/test/V1ModJsonParsingTests.java @@ -29,6 +29,7 @@ import java.nio.file.Paths; import java.util.Collections; import java.util.Map; +import java.util.Optional; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; @@ -158,6 +159,42 @@ public void testLongFile() throws IOException, ParseMetadataException { } } + @Test + @DisplayName("Any icon size test file") + public void testAnyIconSizeFile() throws IOException, ParseMetadataException { + final LoaderModMetadata metadata = parseMetadata(specPath.resolve("any_icon_size.json")); + + validateIconPath(metadata, 32, 64); + validateIconPath(metadata, 64, 64); + validateIconPath(metadata, 128, 64); + } + + @Test + @DisplayName("No icon test file") + public void testNoIconFile() throws IOException, ParseMetadataException { + final LoaderModMetadata metadata = parseMetadata(specPath.resolve("no_icon.json")); + assertEquals(Optional.empty(), metadata.getIconPath(32)); + } + + @Test + @DisplayName("Icon sizes test file") + public void testIconSizesFile() throws IOException, ParseMetadataException { + final LoaderModMetadata metadata = parseMetadata(specPath.resolve("icon_sizes.json")); + + validateIconPath(metadata, 32, 64); + validateIconPath(metadata, 64, 64); + validateIconPath(metadata, 128, 128); + validateIconPath(metadata, 256, 256); + validateIconPath(metadata, 512, 256); + } + + private void validateIconPath(LoaderModMetadata metadata, int preferredSize, int expectedSize) { + Optional expected = Optional.of("assets/testing/icon-" + expectedSize + ".png"); + Optional actual = metadata.getIconPath(preferredSize); + + assertEquals(expected, actual, "Found " + actual.orElse("no icon") + " for preferred size " + preferredSize + " instead of expected size " + expectedSize); + } + /* * Spec violation tests */ diff --git a/src/test/resources/testing/parsing/v1/spec/any_icon_size.json b/src/test/resources/testing/parsing/v1/spec/any_icon_size.json new file mode 100644 index 000000000..1332eed48 --- /dev/null +++ b/src/test/resources/testing/parsing/v1/spec/any_icon_size.json @@ -0,0 +1,6 @@ +{ + "schemaVersion": 1, + "id": "v1-parsing-test", + "version": "1.0.0-SNAPSHOT", + "icon": "assets/testing/icon-64.png" +} diff --git a/src/test/resources/testing/parsing/v1/spec/icon_sizes.json b/src/test/resources/testing/parsing/v1/spec/icon_sizes.json new file mode 100644 index 000000000..ef8460596 --- /dev/null +++ b/src/test/resources/testing/parsing/v1/spec/icon_sizes.json @@ -0,0 +1,10 @@ +{ + "schemaVersion": 1, + "id": "v1-parsing-test", + "version": "1.0.0-SNAPSHOT", + "icon": { + "64": "assets/testing/icon-64.png", + "128": "assets/testing/icon-128.png", + "256": "assets/testing/icon-256.png" + } +} diff --git a/src/test/resources/testing/parsing/v1/spec/no_icon.json b/src/test/resources/testing/parsing/v1/spec/no_icon.json new file mode 100644 index 000000000..f8105ea76 --- /dev/null +++ b/src/test/resources/testing/parsing/v1/spec/no_icon.json @@ -0,0 +1,5 @@ +{ + "schemaVersion": 1, + "id": "v1-parsing-test", + "version": "1.0.0-SNAPSHOT" +} From a09dbf4ffbc3947b81cca61faa39b5c43b822936 Mon Sep 17 00:00:00 2001 From: Julian Burner <48808497+NebelNidas@users.noreply.github.com> Date: Wed, 22 Feb 2023 10:15:08 +0100 Subject: [PATCH 71/80] Print mod list as tree (#765) * Print mod list as tree * Remove root-level decorations * Also sort nested mods alphabetically --- .../loader/impl/FabricLoaderImpl.java | 79 ++++++++++++++----- 1 file changed, 59 insertions(+), 20 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index ef918b9ed..f1456f7ef 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -24,12 +24,14 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; import org.objectweb.asm.Opcodes; @@ -223,26 +225,7 @@ private void setup() throws ModResolutionException { modCandidates = ModResolver.resolve(modCandidates, getEnvironmentType(), envDisabledMods); - // dump mod list - - StringBuilder modListText = new StringBuilder(); - - for (ModCandidate mod : modCandidates) { - if (modListText.length() > 0) modListText.append('\n'); - - modListText.append("\t- "); - modListText.append(mod.getId()); - modListText.append(' '); - modListText.append(mod.getVersion().getFriendlyString()); - - if (!mod.getParentMods().isEmpty()) { - modListText.append(" via "); - modListText.append(mod.getParentMods().iterator().next().getId()); - } - } - - int count = modCandidates.size(); - Log.info(LogCategory.GENERAL, "Loading %d mod%s:%n%s", count, count != 1 ? "s" : "", modListText); + dumpModList(modCandidates); Path cacheDir = gameDir.resolve(CACHE_DIR_NAME); Path outputdir = cacheDir.resolve(PROCESSED_MODS_DIR_NAME); @@ -296,6 +279,62 @@ private void setup() throws ModResolutionException { modCandidates = null; } + private void dumpModList(List mods) { + StringBuilder modListText = new StringBuilder(); + + boolean[] lastItemOfNestLevel = new boolean[mods.size()]; + List topLevelMods = mods.stream() + .filter(mod -> mod.getParentMods().isEmpty()) + .collect(Collectors.toList()); + int topLevelModsCount = topLevelMods.size(); + + for (int i = 0; i < topLevelModsCount; i++) { + boolean lastItem = i == topLevelModsCount - 1; + + if (lastItem) lastItemOfNestLevel[0] = true; + + dumpModList0(topLevelMods.get(i), modListText, 0, lastItemOfNestLevel); + } + + int modsCount = mods.size(); + Log.info(LogCategory.GENERAL, "Loading %d mod%s:%n%s", modsCount, modsCount != 1 ? "s" : "", modListText); + } + + private void dumpModList0(ModCandidate mod, StringBuilder log, int nestLevel, boolean[] lastItemOfNestLevel) { + if (log.length() > 0) log.append('\n'); + + for (int depth = 0; depth < nestLevel; depth++) { + log.append(depth == 0 ? "\t" : lastItemOfNestLevel[depth] ? " " : " │"); + } + + log.append(nestLevel == 0 ? "\t" : " "); + log.append(nestLevel == 0 ? "─" : lastItemOfNestLevel[nestLevel] ? "└──" : "├──"); + log.append(' '); + log.append(mod.getId()); + log.append(' '); + log.append(mod.getVersion().getFriendlyString()); + + List nestedMods = new ArrayList<>(mod.getNestedMods()); + nestedMods.sort(Comparator.comparing(nestedMod -> nestedMod.getMetadata().getId())); + + if (!nestedMods.isEmpty()) { + Iterator iterator = nestedMods.iterator(); + ModCandidate nestedMod; + boolean lastItem; + + while (iterator.hasNext()) { + nestedMod = iterator.next(); + lastItem = !iterator.hasNext(); + + if (lastItem) lastItemOfNestLevel[nestLevel+1] = true; + + dumpModList0(nestedMod, log, nestLevel + 1, lastItemOfNestLevel); + + if (lastItem) lastItemOfNestLevel[nestLevel+1] = false; + } + } + } + private void finishModLoading() { // add mods to classpath // TODO: This can probably be made safer, but that's a long-term goal From ebdc57e07bffac2229911e0d7af6fd4110e6a80f Mon Sep 17 00:00:00 2001 From: apple502j <33279053+apple502j@users.noreply.github.com> Date: Wed, 22 Feb 2023 18:18:52 +0900 Subject: [PATCH 72/80] Add more localization support (#772) * Localize more messages * Add ja-JP translation * Allow English text to be queried * Make FormattedException translatable * Misc fixes * Update src/main/java/net/fabricmc/loader/impl/FormattedException.java --- .../game/minecraft/MinecraftGameProvider.java | 4 +- .../launchwrapper/FabricTweaker.java | 2 +- .../loader/impl/FabricLoaderImpl.java | 4 +- .../loader/impl/FormattedException.java | 28 ++++ .../loader/impl/gui/FabricGuiEntry.java | 11 +- .../impl/launch/FabricLauncherBase.java | 4 +- .../loader/impl/launch/knot/Knot.java | 2 +- .../impl/metadata/DependencyOverrides.java | 2 +- .../loader/impl/util/Localization.java | 19 ++- .../net/fabricmc/loader/Messages.properties | 17 ++- .../fabricmc/loader/Messages_ja_JP.properties | 140 ++++++++++++++++++ .../net/fabricmc/test/LocalizationTests.java | 30 ++++ 12 files changed, 244 insertions(+), 19 deletions(-) create mode 100644 src/main/resources/net/fabricmc/loader/Messages_ja_JP.properties create mode 100644 src/test/java/net/fabricmc/test/LocalizationTests.java diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java index f6a315436..b3b4659f3 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/MinecraftGameProvider.java @@ -455,13 +455,13 @@ public void launch(ClassLoader loader) { Class c = loader.loadClass(targetClass); invoker = MethodHandles.lookup().findStatic(c, "main", MethodType.methodType(void.class, String[].class)); } catch (NoSuchMethodException | IllegalAccessException | ClassNotFoundException e) { - throw new FormattedException("Failed to start Minecraft", e); + throw FormattedException.ofLocalized("exception.minecraft.invokeFailure", e); } try { invoker.invokeExact(arguments.toArray()); } catch (Throwable t) { - throw new FormattedException("Minecraft has crashed!", t); + throw FormattedException.ofLocalized("exception.minecraft.generic", t); } } } diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java index 44ca87194..8fc423f5f 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/launchwrapper/FabricTweaker.java @@ -163,7 +163,7 @@ private void init() { try { EntrypointUtils.invoke("preLaunch", PreLaunchEntrypoint.class, PreLaunchEntrypoint::onPreLaunch); } catch (RuntimeException e) { - throw new FormattedException("A mod crashed on startup!", e); + throw FormattedException.ofLocalized("exception.initializerFailure", e); } } diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index f1456f7ef..25c2e529d 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -189,9 +189,9 @@ public void load() { setup(); } catch (ModResolutionException exception) { if (exception.getCause() == null) { - throw new FormattedException("Incompatible mod set!", exception.getMessage()); + throw FormattedException.ofLocalized("exception.incompatible", exception.getMessage()); } else { - throw new FormattedException("Incompatible mod set!", exception); + throw FormattedException.ofLocalized("exception.incompatible", exception); } } } diff --git a/src/main/java/net/fabricmc/loader/impl/FormattedException.java b/src/main/java/net/fabricmc/loader/impl/FormattedException.java index 4912b9fd6..b7752825b 100644 --- a/src/main/java/net/fabricmc/loader/impl/FormattedException.java +++ b/src/main/java/net/fabricmc/loader/impl/FormattedException.java @@ -16,9 +16,12 @@ package net.fabricmc.loader.impl; +import net.fabricmc.loader.impl.util.Localization; + @SuppressWarnings("serial") public final class FormattedException extends RuntimeException { private final String mainText; + private String translatedText; public FormattedException(String mainText, String message) { super(message); @@ -44,7 +47,32 @@ public FormattedException(String mainText, Throwable cause) { this.mainText = mainText; } + public static FormattedException ofLocalized(String key, String message) { + return new FormattedException(Localization.formatRoot(key), message).addTranslation(key); + } + + public static FormattedException ofLocalized(String key, String format, Object... args) { + return new FormattedException(Localization.formatRoot(key), format, args).addTranslation(key); + } + + public static FormattedException ofLocalized(String key, String message, Throwable cause) { + return new FormattedException(Localization.formatRoot(key), message, cause).addTranslation(key); + } + + public static FormattedException ofLocalized(String key, Throwable cause) { + return new FormattedException(Localization.formatRoot(key), cause).addTranslation(key); + } + public String getMainText() { return mainText; } + + public String getDisplayedText() { + return translatedText == null || translatedText.equals(mainText) ? mainText : translatedText + " (" + mainText + ")"; + } + + private FormattedException addTranslation(String key) { + this.translatedText = Localization.format(key); + return this; + } } diff --git a/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java b/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java index e0ac4f28a..44b6beeab 100644 --- a/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java +++ b/src/main/java/net/fabricmc/loader/impl/gui/FabricGuiEntry.java @@ -33,6 +33,7 @@ import net.fabricmc.loader.impl.gui.FabricStatusTree.FabricStatusTab; import net.fabricmc.loader.impl.gui.FabricStatusTree.FabricTreeWarningLevel; import net.fabricmc.loader.impl.util.LoaderUtil; +import net.fabricmc.loader.impl.util.Localization; import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.loader.impl.util.log.Log; import net.fabricmc.loader.impl.util.log.LogCategory; @@ -100,7 +101,7 @@ public static void main(String[] args) throws Exception { public static void displayCriticalError(Throwable exception, boolean exitAfter) { Log.error(LogCategory.GENERAL, "A critical error occurred", exception); - displayError("Failed to launch!", exception, exitAfter); + displayError(Localization.format("gui.error.header"), exception, exitAfter); } public static void displayError(String mainText, Throwable exception, boolean exitAfter) { @@ -113,7 +114,7 @@ public static void displayError(String mainText, Throwable exception, boolean ex exception.printStackTrace(new PrintWriter(error)); } - tree.addButton("Copy error", FabricBasicButtonType.CLICK_MANY).withClipboard(error.toString()); + tree.addButton(Localization.format("gui.button.copyError"), FabricBasicButtonType.CLICK_MANY).withClipboard(error.toString()); }, exitAfter); } @@ -123,17 +124,17 @@ public static void displayError(String mainText, Throwable exception, Consumer Date: Wed, 22 Feb 2023 09:46:59 +0000 Subject: [PATCH 73/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 25c2e529d..5b882347f 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -71,7 +71,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.14"; + public static final String VERSION = "0.14.15"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 3b057b7e1c4fc4bc0404ff1f1ce8f55ecc0a85b8 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 22 Feb 2023 09:50:43 +0000 Subject: [PATCH 74/80] Fix release --- junit/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junit/build.gradle b/junit/build.gradle index f464d7641..88b782a2c 100644 --- a/junit/build.gradle +++ b/junit/build.gradle @@ -41,7 +41,7 @@ if (signingEnabled) { publishing { publications { - mavenJava(MavenPublication) { + maven(MavenPublication) { artifactId project.archivesBaseName from components.java } From 48f6ce7860cce14e74d0baa25dd955b8e801221d Mon Sep 17 00:00:00 2001 From: Julian Burner <48808497+NebelNidas@users.noreply.github.com> Date: Fri, 24 Feb 2023 11:10:17 +0100 Subject: [PATCH 75/80] Switch to ASCII characters for mod list tree formatting (#774) --- .../java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 5b882347f..a1eed4658 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -304,11 +304,11 @@ private void dumpModList0(ModCandidate mod, StringBuilder log, int nestLevel, bo if (log.length() > 0) log.append('\n'); for (int depth = 0; depth < nestLevel; depth++) { - log.append(depth == 0 ? "\t" : lastItemOfNestLevel[depth] ? " " : " │"); + log.append(depth == 0 ? "\t" : lastItemOfNestLevel[depth] ? " " : " | "); } - log.append(nestLevel == 0 ? "\t" : " "); - log.append(nestLevel == 0 ? "─" : lastItemOfNestLevel[nestLevel] ? "└──" : "├──"); + log.append(nestLevel == 0 ? "\t" : " "); + log.append(nestLevel == 0 ? "-" : lastItemOfNestLevel[nestLevel] ? " \\--" : " |--"); log.append(' '); log.append(mod.getId()); log.append(' '); From ec53d2b20cf6eb0d05280b6f4170cab690f2dcfb Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Fri, 24 Feb 2023 23:28:02 +0000 Subject: [PATCH 76/80] Update mixin --- gradle.properties | 2 +- src/main/resources/fabric-installer.json | 2 +- src/main/resources/fabric-installer.launchwrapper.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 78d2e4de5..4d2d9da94 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,4 +6,4 @@ description = The mod loading component of Fabric url = https://github.com/FabricMC/fabric-loader asm_version = 9.4 -mixin_version = 0.12.3+mixin.0.8.5 +mixin_version = 0.12.4+mixin.0.8.5 diff --git a/src/main/resources/fabric-installer.json b/src/main/resources/fabric-installer.json index 62b470e3d..49dac23e8 100644 --- a/src/main/resources/fabric-installer.json +++ b/src/main/resources/fabric-installer.json @@ -9,7 +9,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:sponge-mixin:0.12.3+mixin.0.8.5", + "name": "net.fabricmc:sponge-mixin:0.12.4+mixin.0.8.5", "url": "https://maven.fabricmc.net/" }, { diff --git a/src/main/resources/fabric-installer.launchwrapper.json b/src/main/resources/fabric-installer.launchwrapper.json index bf236356e..dce8325da 100644 --- a/src/main/resources/fabric-installer.launchwrapper.json +++ b/src/main/resources/fabric-installer.launchwrapper.json @@ -12,7 +12,7 @@ "url": "https://maven.fabricmc.net/" }, { - "name": "net.fabricmc:sponge-mixin:0.12.3+mixin.0.8.5", + "name": "net.fabricmc:sponge-mixin:0.12.4+mixin.0.8.5", "url": "https://maven.fabricmc.net/" }, { From 56fc75048f0ba5bec66054939079be651a23d937 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Fri, 24 Feb 2023 23:28:17 +0000 Subject: [PATCH 77/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index a1eed4658..35a2195f8 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -71,7 +71,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.15"; + public static final String VERSION = "0.14.16"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From e0caaaa29446c567fc138bf9d5b529fb4ce4b826 Mon Sep 17 00:00:00 2001 From: Player Date: Sat, 25 Feb 2023 15:23:47 -0500 Subject: [PATCH 78/80] Fix JUnit run specific log library handling --- .../loader/impl/game/LibClassifier.java | 10 ++++++---- .../loader/impl/game/LoaderLibrary.java | 20 ++++++++----------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java index ce051e500..cb629ff65 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java @@ -95,8 +95,10 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) throw // loader libs + boolean junitRun = System.getProperty(SystemProperties.UNIT_TEST) != null; + for (LoaderLibrary lib : LoaderLibrary.values()) { - if (!lib.isApplicable(env)) continue; + if (!lib.isApplicable(env, junitRun)) continue; if (lib.path != null) { Path path = LoaderUtil.normalizeExistingPath(lib.path); @@ -126,11 +128,11 @@ public LibClassifier(Class cls, EnvType env, GameProvider gameProvider) throw // process indirectly referenced libs - processManifestClassPath(LoaderLibrary.SERVER_LAUNCH, env); // not used by fabric itself, but others add Log4J this way + processManifestClassPath(LoaderLibrary.SERVER_LAUNCH, env, junitRun); // not used by fabric itself, but others add Log4J this way } - private void processManifestClassPath(LoaderLibrary lib, EnvType env) throws IOException { - if (lib.path == null || !lib.isApplicable(env) || !Files.isRegularFile(lib.path)) return; + private void processManifestClassPath(LoaderLibrary lib, EnvType env, boolean junitRun) throws IOException { + if (lib.path == null || !lib.isApplicable(env, junitRun) || !Files.isRegularFile(lib.path)) return; Manifest manifest; diff --git a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java index 1a3c6c224..af43c2ee5 100644 --- a/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java +++ b/src/main/java/net/fabricmc/loader/impl/game/LoaderLibrary.java @@ -30,7 +30,6 @@ import net.fabricmc.accesswidener.AccessWidener; import net.fabricmc.api.EnvType; -import net.fabricmc.loader.impl.util.SystemProperties; import net.fabricmc.loader.impl.util.UrlConversionException; import net.fabricmc.loader.impl.util.UrlUtil; import net.fabricmc.mapping.tree.TinyMappingFactory; @@ -50,7 +49,7 @@ enum LoaderLibrary { SAT4J_CORE(ContradictionException.class), SAT4J_PB(SolverFactory.class), SERVER_LAUNCH("fabric-server-launch.properties", EnvType.SERVER), // installer generated jar to run setup loader's class path - SERVER_LAUNCHER("net/fabricmc/installer/ServerLauncher.class", EnvType.SERVER), + SERVER_LAUNCHER("net/fabricmc/installer/ServerLauncher.class", EnvType.SERVER), // installer based launch-through method JUNIT_API("org/junit/jupiter/api/Test.class", null), JUNIT_PLATFORM_ENGINE("org/junit/platform/engine/TestEngine.class", null), JUNIT_PLATFORM_LAUNCHER("org/junit/platform/launcher/core/LauncherFactory.class", null), @@ -66,7 +65,7 @@ enum LoaderLibrary { final Path path; final EnvType env; - final boolean loggerLibrary; + final boolean junitRunOnly; LoaderLibrary(Class cls) { this(UrlUtil.getCodeSource(cls)); @@ -77,14 +76,14 @@ enum LoaderLibrary { this.path = path; this.env = null; - this.loggerLibrary = false; + this.junitRunOnly = false; } LoaderLibrary(String file, EnvType env) { this(file, env, false); } - LoaderLibrary(String file, EnvType env, boolean loggerLibrary) { + LoaderLibrary(String file, EnvType env, boolean junitRunOnly) { URL url = LoaderLibrary.class.getClassLoader().getResource(file); try { @@ -94,18 +93,15 @@ enum LoaderLibrary { throw new RuntimeException(e); } - this.loggerLibrary = false; + this.junitRunOnly = junitRunOnly; } LoaderLibrary(String path, boolean loggerLibrary) { this(path, null, loggerLibrary); } - boolean isApplicable(EnvType env) { - if (loggerLibrary) { - return Boolean.getBoolean(SystemProperties.UNIT_TEST); - } - - return this.env == null || this.env == env; + boolean isApplicable(EnvType env, boolean junitRun) { + return (this.env == null || this.env == env) + && (!junitRunOnly || junitRun); } } From 2f19a54883adbd25b7164644bb8239fbb9c26e21 Mon Sep 17 00:00:00 2001 From: Player Date: Sat, 25 Feb 2023 15:25:38 -0500 Subject: [PATCH 79/80] Bump version --- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 35a2195f8..5259984be 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -71,7 +71,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.16"; + public static final String VERSION = "0.14.17"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir From 09646a90ebe2a5db717a2fd2b36484bfa2dbc050 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 22 Mar 2023 16:37:31 +0000 Subject: [PATCH 80/80] Support 1.20 snapshot versions. --- .../fabricmc/loader/impl/game/minecraft/McVersionLookup.java | 4 +++- src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java index 509371bb0..7b03338f8 100644 --- a/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java +++ b/minecraft/src/main/java/net/fabricmc/loader/impl/game/minecraft/McVersionLookup.java @@ -287,7 +287,9 @@ protected static String getRelease(String version) { int year = Integer.parseInt(matcher.group(1)); int week = Integer.parseInt(matcher.group(2)); - if (year >= 23 && week >= 3) { + if (year >= 23 && week >= 12) { + return "1.20"; + } else if (year == 23 && week <= 7) { return "1.19.4"; } else if (year == 22 && week >= 42) { return "1.19.3"; diff --git a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java index 5259984be..3fe3396e9 100644 --- a/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java +++ b/src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java @@ -71,7 +71,7 @@ public final class FabricLoaderImpl extends net.fabricmc.loader.FabricLoader { public static final int ASM_VERSION = Opcodes.ASM9; - public static final String VERSION = "0.14.17"; + public static final String VERSION = "0.14.18"; public static final String MOD_ID = "fabricloader"; public static final String CACHE_DIR_NAME = ".fabric"; // relative to game dir