Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand class path entries to expose indirectly referenced ones #702

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,15 @@ public boolean locateGame(FabricLauncher launcher, String[] args) {

if (commonGameJarDeclared) {
if (envGameJar != null) {
classifier.process(envGameJar, McLibrary.MC_COMMON);
classifier.process(envGameJar, true, McLibrary.MC_COMMON);
}

classifier.process(commonGameJar);
classifier.process(commonGameJar, true);
} else if (envGameJar != null) {
classifier.process(envGameJar);
classifier.process(envGameJar, true);
}

classifier.process(launcher.getClassPath());
classifier.process(launcher.getClassPath(), false);

if (classifier.has(McLibrary.MC_BUNDLER)) {
BundlerProcessor.process(classifier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
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;
Expand Down Expand Up @@ -123,15 +122,14 @@ public void injectIntoClassLoader(LaunchClassLoader launchClassLoader) {
private void init() {
setupUncaughtExceptionHandler();

classPath.clear();
List<Path> paths = new ArrayList<>();

for (URL url : launchClassLoader.getSources()) {
Path path = UrlUtil.asPath(url);
if (!Files.exists(path)) continue;

classPath.add(LoaderUtil.normalizeExistingPath(path));
paths.add(UrlUtil.asPath(url));
}

classPath.clear();
classPath.addAll(normalizeClassPath(paths));
GameProvider provider = new MinecraftGameProvider();

if (!provider.isEnabled()
Expand Down
22 changes: 9 additions & 13 deletions src/main/java/net/fabricmc/loader/impl/game/LibClassifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@
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;

Expand Down Expand Up @@ -135,10 +133,8 @@ private void processManifestClassPath(LoaderLibrary lib, EnvType env) throws IOE
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));
manifest = ManifestUtil.readManifest(zf);
if (manifest == null) return;
}

List<URL> cp = ManifestUtil.getClassPath(manifest, lib.path);
Expand All @@ -150,21 +146,21 @@ private void processManifestClassPath(LoaderLibrary lib, EnvType env) throws IOE
}

public void process(URL url) throws IOException {
process(UrlUtil.asPath(url));
process(UrlUtil.asPath(url), true);
}

@SafeVarargs
public final void process(Iterable<Path> paths, L... excludedLibs) throws IOException {
public final void process(Iterable<Path> paths, boolean normalize, L... excludedLibs) throws IOException {
Set<L> excluded = makeSet(excludedLibs);

for (Path path : paths) {
process(path, excluded);
process(path, normalize, excluded);
}
}

@SafeVarargs
public final void process(Path path, L... excludedLibs) throws IOException {
process(path, makeSet(excludedLibs));
public final void process(Path path, boolean normalize, L... excludedLibs) throws IOException {
process(path, normalize, makeSet(excludedLibs));
}

private static <L extends Enum<L>> Set<L> makeSet(L[] libs) {
Expand All @@ -179,8 +175,8 @@ private static <L extends Enum<L>> Set<L> makeSet(L[] libs) {
return ret;
}

private void process(Path path, Set<L> excludedLibs) throws IOException {
path = LoaderUtil.normalizeExistingPath(path);
private void process(Path path, boolean normalize, Set<L> excludedLibs) throws IOException {
if (normalize) path = LoaderUtil.normalizeExistingPath(path);
if (systemLibraries.contains(path)) return;

boolean matched = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,32 @@
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipError;
import java.util.zip.ZipFile;

import org.spongepowered.asm.mixin.MixinEnvironment;

import net.fabricmc.loader.impl.FabricLoaderImpl;
import net.fabricmc.loader.impl.FormattedException;
import net.fabricmc.loader.impl.game.GameProvider;
import net.fabricmc.loader.impl.gui.FabricGuiEntry;
import net.fabricmc.loader.impl.util.LoaderUtil;
import net.fabricmc.loader.impl.util.ManifestUtil;
import net.fabricmc.loader.impl.util.UrlUtil;
import net.fabricmc.loader.impl.util.log.Log;
import net.fabricmc.loader.impl.util.log.LogCategory;

Expand Down Expand Up @@ -143,4 +161,76 @@ protected static void finishMixinBootstrapping() {
public static boolean isMixinReady() {
return mixinReady;
}

/**
* Normalize the class path by normalizing the paths, deduplicating, resolving/expanding indirect references and
* suppressing purely other-jar referencing jars.
*/
protected static List<Path> normalizeClassPath(List<Path> cp) {
Set<Path> checkedPaths = new HashSet<>(cp.size());
List<Path> ret = new ArrayList<>(cp.size());
List<Path> missing = new ArrayList<>();
Deque<Path> queue = new ArrayDeque<>(cp);
Path path;

while ((path = queue.pollFirst()) != null) {
if (!Files.exists(path)) {
missing.add(path);
continue;
}

path = LoaderUtil.normalizeExistingPath(path);
if (!checkedPaths.add(path)) continue;

if (Files.isRegularFile(path)) { // jar, expand (resolve additional manifest-referenced cp entries) and suppress file if it is only such a referencing container
try (ZipFile zf = new ZipFile(path.toFile())) {
Manifest manifest = ManifestUtil.readManifest(zf);
List<URL> urls;

if (manifest != null
&& (urls = ManifestUtil.getClassPath(manifest, path)) != null) {
for (int i = urls.size() - 1; i >= 0; i--) {
queue.addFirst(UrlUtil.asPath(urls.get(i)));
}

// check for non-manifest/signature files to determine whether the jar can be ignored

final String prefix = ManifestUtil.MANIFEST_DIR+"/";
boolean foundNonManifest = false;

for (Enumeration<? extends ZipEntry> e = zf.entries(); e.hasMoreElements(); ) {
ZipEntry entry = e.nextElement();
if (entry.isDirectory()) continue;

String name = entry.getName();
String fileName;

if (!name.startsWith(prefix) // file outside META-INF
|| (fileName = name.substring(prefix.length())).indexOf('/') >= 0 // file not directly within META-INF
|| !fileName.equals(ManifestUtil.MANIFEST_FILE) && !ManifestUtil.isSignatureFile(fileName)) { // neither MANIFEST.MF nor one of the sig files
foundNonManifest = true;
break;
}
}

if (!foundNonManifest) { // only a jar with a manifest and potentially signature, ignore
// don't add to ret
continue;
}
}
} catch (ZipError | Exception e) {
throw new RuntimeException("error reading "+path, e);
}
}

ret.add(path);
}

if (!missing.isEmpty()) {
Log.warn(LogCategory.GENERAL, "Class path entries reference missing files: %s - the game may not load properly!",
missing.stream().map(Path::toString).collect(Collectors.joining(", ")));
}

return ret;
}
}
24 changes: 7 additions & 17 deletions src/main/java/net/fabricmc/loader/impl/launch/knot/Knot.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
Expand Down Expand Up @@ -101,31 +100,22 @@ protected ClassLoader init(String[] args) {
}
}

classPath.clear();

List<String> missing = null;
List<String> unsupported = null;
List<Path> paths = new ArrayList<>();
List<String> unsupported = new ArrayList<>();

for (String cpEntry : System.getProperty("java.class.path").split(File.pathSeparator)) {
if (cpEntry.equals("*") || cpEntry.endsWith(File.separator + "*")) {
if (unsupported == null) unsupported = new ArrayList<>();
unsupported.add(cpEntry);
continue;
}

Path path = Paths.get(cpEntry);

if (!Files.exists(path)) {
if (missing == null) missing = new ArrayList<>();
missing.add(cpEntry);
continue;
}

classPath.add(LoaderUtil.normalizeExistingPath(path));
paths.add(Paths.get(cpEntry));
}

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));
if (!unsupported.isEmpty()) Log.warn(LogCategory.KNOT, "Knot does not support wildcard class path entries: %s - the game may not load properly!", String.join(", ", unsupported));

classPath.clear();
classPath.addAll(normalizeClassPath(paths));

provider = createGameProvider(args);
Log.finishBuiltinConfig();
Expand Down
27 changes: 26 additions & 1 deletion src/main/java/net/fabricmc/loader/impl/util/ManifestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,15 @@
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.Attributes.Name;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public final class ManifestUtil {
public static final String MANIFEST_DIR = "META-INF";
public static final String MANIFEST_FILE = "MANIFEST.MF";

public static Manifest readManifest(Class<?> cls) throws IOException, URISyntaxException {
CodeSource cs = cls.getProtectionDomain().getCodeSource();
if (cs == null) return null;
Expand Down Expand Up @@ -62,14 +68,25 @@ public static Manifest readManifest(URL codeSourceUrl) throws IOException, URISy
}

public static Manifest readManifest(Path basePath) throws IOException {
Path path = basePath.resolve("META-INF").resolve("MANIFEST.MF");
Path path = basePath.resolve(MANIFEST_DIR).resolve(MANIFEST_FILE);
if (!Files.exists(path)) return null;

try (InputStream stream = Files.newInputStream(path)) {
return new Manifest(stream);
}
}

public static Manifest readManifest(ZipFile zf) throws IOException {
if (zf instanceof JarFile) return ((JarFile) zf).getManifest();

ZipEntry entry = zf.getEntry(JarFile.MANIFEST_NAME);
if (entry == null) return null;

try (InputStream stream = zf.getInputStream(entry)) {
return new Manifest(stream);
}
}

public static String getManifestValue(Manifest manifest, Name name) {
return manifest.getMainAttributes().getValue(name);
}
Expand All @@ -88,4 +105,12 @@ public static List<URL> getClassPath(Manifest manifest, Path baseDir) throws Mal

return ret;
}

public static boolean isSignatureFile(String fileName) {
return fileName.endsWith(".SF")
|| fileName.endsWith(".DSA")
|| fileName.endsWith(".RSA")
|| fileName.endsWith(".EC")
|| fileName.startsWith("SIG-");
}
}