-
Notifications
You must be signed in to change notification settings - Fork 59
Add support to create a pached Minecraft Jar on Startup #383
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
Open
shartte
wants to merge
31
commits into
main
Choose a base branch
from
feature/autoinstall
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
3ceac95
Start working on auto installer.
shartte 383f458
Fix tests
shartte 881a66d
Fix tests
shartte 8de2cce
Remove unused
shartte 1e3b62e
Add dynamic game installation discovery service
marchermans 5d8e7e2
Add parameters for better detection of the game discovery or installa…
marchermans 279e2a2
Introduce a better design for the running of the GameDiscovery Test
marchermans 58a2372
Add exception handling to game discovery and installation process
marchermans eb4f549
Properly handle the exception.
marchermans 090bd38
Fix the license
marchermans ea95474
Update the documentation.
marchermans 693c1db
Fix immaculate.
marchermans 34ee39e
Introduce support for cause listing in the log when a mod loading exc…
marchermans 2b41bfc
Remove the NeoForge version from the game discovery and installation …
marchermans 0b73585
Fix formatting and introduce better logging for ModLoadingExceptions …
marchermans 16d9111
Merge branch 'main' into feature/autoinstall
marchermans 805b0ef
Address needed and requested Changes.
marchermans 0ae4b67
Port to a direct variant
marchermans dca2167
Fix formatting and properly wire the neoforge version
marchermans fcc6ce8
Fix license violations
marchermans d6a327a
Fix tests
marchermans c5a21cb
Fix formatting
marchermans fa707a9
Mark the neoforge and minecraft jar as located by the game discovery …
marchermans ad0a515
Handle the case where two different path objects are not equal becaus…
marchermans fd62efa
Also normalize both paths
marchermans 5b9dc7c
Update to first review, add support for purging the cache dir to the …
marchermans d265a00
Refactoring and cleanup
shartte aea89d8
Merge branch 'main' into feature/autoinstall
shartte e792886
Fix incorrect assumption about dedicated server-jar
shartte ef69cb6
Fix a minor issue / use suppresesd exceptions
shartte a69607a
Remove unused system property.
shartte File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
loader/src/main/java/net/neoforged/fml/loading/ClassLoaderStack.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,164 @@ | ||
| /* | ||
| * Copyright (c) NeoForged and contributors | ||
| * SPDX-License-Identifier: LGPL-2.1-only | ||
| */ | ||
|
|
||
| package net.neoforged.fml.loading; | ||
|
|
||
| import java.net.MalformedURLException; | ||
| import java.net.URL; | ||
| import java.net.URLClassLoader; | ||
| import java.nio.file.Path; | ||
| import java.util.ArrayList; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import net.neoforged.fml.classloading.JarContentsModule; | ||
| import net.neoforged.fml.classloading.ResourceMaskingClassLoader; | ||
| import net.neoforged.fml.jarcontents.CompositeJarContents; | ||
| import net.neoforged.fml.jarcontents.EmptyJarContents; | ||
| import net.neoforged.fml.jarcontents.FolderJarContents; | ||
| import net.neoforged.fml.jarcontents.JarContents; | ||
| import net.neoforged.fml.jarcontents.JarFileContents; | ||
| import net.neoforged.fml.util.ClasspathResourceUtils; | ||
| import net.neoforged.fml.util.PathPrettyPrinting; | ||
| import net.neoforged.neoforgespi.LocatedPaths; | ||
| import org.jetbrains.annotations.Nullable; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public final class ClassLoaderStack implements AutoCloseable { | ||
| private static final Logger LOGGER = LoggerFactory.getLogger(ClassLoaderStack.class); | ||
|
|
||
| private final List<AutoCloseable> ownedResources = new ArrayList<>(); | ||
| private final LocatedPaths locatedPaths; | ||
| /** | ||
| * The context class-loader that will be restored when the loader is closed. | ||
| */ | ||
| @Nullable | ||
| private final ClassLoader originalClassLoader; | ||
| /** | ||
| * The current tail of the class-loader chain. It is moved whenever a new set of Jars is loaded. | ||
| */ | ||
| private ClassLoader currentClassLoader; | ||
|
|
||
| public ClassLoaderStack(ClassLoader initialLoader, LocatedPaths locatedPaths) { | ||
| this.currentClassLoader = initialLoader; | ||
| this.locatedPaths = locatedPaths; | ||
| this.originalClassLoader = Thread.currentThread().getContextClassLoader(); | ||
| Thread.currentThread().setContextClassLoader(currentClassLoader); | ||
| } | ||
|
|
||
| /** | ||
| * Loads the given services into a URL classloader. | ||
| */ | ||
| public void appendLoader(String loaderName, List<JarContents> jars) { | ||
| if (jars.isEmpty()) { | ||
| LOGGER.info("No additional classpath items for {} were found.", loaderName); | ||
| return; | ||
| } | ||
|
|
||
| LOGGER.info("Loading {}:", loaderName); | ||
|
|
||
| List<URL> rootUrls = new ArrayList<>(jars.size()); | ||
| for (var jar : jars) { | ||
| if (jar instanceof CompositeJarContents compositeJarContents && compositeJarContents.isFiltered()) { | ||
| throw new IllegalArgumentException("Cannot use simple URLClassLoader for filtered content " + jar); | ||
| } | ||
|
|
||
| // TODO: Order on the classpath matters, we need to double-check the content roots are in the right order here | ||
| for (var contentRoot : jar.getContentRoots()) { | ||
| LOGGER.info(" - {}", PathPrettyPrinting.prettyPrint(contentRoot)); | ||
| try { | ||
| rootUrls.add(contentRoot.toUri().toURL()); | ||
| } catch (MalformedURLException e) { | ||
| throw new RuntimeException(e); // This should not happen for file URLs | ||
| } | ||
| locatedPaths.addLocated(contentRoot); // Prevents it from getting picked up again | ||
| } | ||
| } | ||
|
|
||
| var loader = new URLClassLoader(loaderName, rootUrls.toArray(URL[]::new), currentClassLoader); | ||
| ownedResources.add(loader); | ||
| currentClassLoader = loader; | ||
| Thread.currentThread().setContextClassLoader(loader); | ||
| } | ||
|
|
||
| public ClassLoader getCurrentClassLoader() { | ||
| return currentClassLoader; | ||
| } | ||
|
|
||
| public void append(ClassLoader loader) { | ||
| currentClassLoader = loader; | ||
| } | ||
|
|
||
| /** | ||
| * If any location being added is already on the classpath, we add a masking classloader to ensure | ||
| * that resources are not double-reported when using getResources/getResource. | ||
| * <p> | ||
| * The primary purpose of this is in mod and NeoForge development environments, where IDEs put the mod | ||
| * on the app classpath, but we also add it as content to the game layer. This method is responsible | ||
| * for setting up a classloader that prevents getResource/getResources from reporting Jar resources | ||
| * for both the jar on the App classpath and on the transforming classloader. | ||
| */ | ||
| public void maskContentAlreadyOnClasspath(List<JarContentsModule> content) { | ||
| var classpathItems = ClasspathResourceUtils.getAllClasspathItems(getCurrentClassLoader()); | ||
|
|
||
| // Collect all paths that make up the game content, which are already on the classpath | ||
| Set<Path> needsMasking = new HashSet<>(); | ||
| for (var secureJar : content) { | ||
| for (var basePath : getBasePaths(secureJar.contents(), true)) { | ||
| if (classpathItems.contains(basePath)) { | ||
| needsMasking.add(basePath); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (!needsMasking.isEmpty()) { | ||
| if (LOGGER.isDebugEnabled()) { | ||
| LOGGER.debug("Masking classpath elements: {}", needsMasking.stream().map(PathPrettyPrinting::prettyPrint).toList()); | ||
| } | ||
|
|
||
| var maskedLoader = new ResourceMaskingClassLoader(currentClassLoader, needsMasking); | ||
| if (Thread.currentThread().getContextClassLoader() == currentClassLoader) { | ||
| Thread.currentThread().setContextClassLoader(maskedLoader); | ||
| } | ||
| currentClassLoader = maskedLoader; | ||
| } | ||
| } | ||
|
|
||
| private static List<Path> getBasePaths(JarContents contents, boolean ignoreFilter) { | ||
| var result = new ArrayList<Path>(); | ||
| switch (contents) { | ||
| case CompositeJarContents compositeModContainer -> { | ||
| if (!ignoreFilter && compositeModContainer.isFiltered()) { | ||
| throw new IllegalStateException("Cannot load filtered Jar content into a URL classloader"); | ||
| } | ||
| for (var delegate : compositeModContainer.getDelegates()) { | ||
| result.addAll(getBasePaths(delegate, ignoreFilter)); | ||
| } | ||
| } | ||
| case EmptyJarContents ignored -> {} | ||
| case FolderJarContents folderModContainer -> result.add(folderModContainer.getPrimaryPath()); | ||
| case JarFileContents jarModContainer -> result.add(jarModContainer.getPrimaryPath()); | ||
| default -> throw new IllegalStateException("Don't know how to handle " + contents); | ||
| } | ||
| return result; | ||
| } | ||
|
|
||
| @Override | ||
| public void close() { | ||
| for (var ownedResource : ownedResources) { | ||
| try { | ||
| ownedResource.close(); | ||
| } catch (Exception e) { | ||
| LOGGER.error("Failed to close resource {} owned by class loader stack", ownedResource, e); | ||
| } | ||
| } | ||
| ownedResources.clear(); | ||
|
|
||
| if (Thread.currentThread().getContextClassLoader() == currentClassLoader) { | ||
| Thread.currentThread().setContextClassLoader(originalClassLoader); | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
implementation?