Skip to content

Commit

Permalink
launch: Detect builtin plugins when not first on classpath
Browse files Browse the repository at this point in the history
This corrects builtin plugin detection in situations where the
SpongeVanilla jar is not the first in the classpath, and properly allows
gathering enumerations of resources.

ModLaucher has been bumped, but there are still issues getting a code
source and location from a class file contained in a plugin that need to
be resolved.
  • Loading branch information
zml2008 committed Jan 14, 2021
1 parent df9c5a0 commit efdc773
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 29 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mappingsVersion=1.16.4
recommendedVersion=0-SNAPSHOT
asmVersion=7.2
mixinVersion=0.8.2
modlauncherVersion=7.0.1
modlauncherVersion=8.0.9
pluginSpiVersion=0.1.4-SNAPSHOT
guavaVersion=21.0
junitVersion=5.7.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.NoSuchElementException;
import java.util.concurrent.Callable;
import java.util.function.Function;
import java.util.function.Predicate;
Expand Down Expand Up @@ -86,7 +88,7 @@ public abstract class AbstractVanillaLaunchHandler implements ILaunchHandlerServ

@Override
public void configureTransformationClassLoader(final ITransformingClassLoaderBuilder builder) {
builder.setClassBytesLocator(this.getResourceLocator());
builder.setResourceEnumeratorLocator(this.getResourceLocator());
}

@Override
Expand All @@ -113,31 +115,61 @@ public Callable<Void> launchService(final String[] arguments, final ITransformin
};
}

protected Function<String, Optional<URL>> getResourceLocator() {
protected Function<String, Enumeration<URL>> getResourceLocator() {
return s -> {
for (final Map.Entry<PluginLanguageService<PluginResource>, List<PluginCandidate<PluginResource>>> serviceCandidates :
Main.getInstance().getPluginEngine().getCandidates().entrySet()) {
for (final PluginCandidate<PluginResource> candidate : serviceCandidates.getValue()) {
final PluginResource resource = candidate.getResource();

if (resource instanceof JVMPluginResource) {
if (((JVMPluginResource) resource).getType() != ResourceType.JAR) {
continue;
}
return new Enumeration<URL>() {
final Iterator<Map.Entry<PluginLanguageService<PluginResource>, List<PluginCandidate<PluginResource>>>> serviceCandidates =
Main.getInstance().getPluginEngine().getCandidates().entrySet().iterator();
Iterator<PluginCandidate<PluginResource>> candidates;
URL next = this.computeNext();

@Override
public boolean hasMoreElements() {
return this.next != null;
}

@Override
public URL nextElement() {
final URL next = this.next;
if (next == null) {
throw new NoSuchElementException();
}
this.next = this.computeNext();
return next;
}

final Path resolved = resource.getFileSystem().getPath(s);
if (Files.exists(resolved)) {
try {
return Optional.of(resolved.toUri().toURL());
} catch (final MalformedURLException ex) {
throw new RuntimeException(ex);
private URL computeNext() {
while (true) {
if (this.candidates != null && !this.candidates.hasNext()) {
this.candidates = null;
}
if (this.candidates == null) {
if (!this.serviceCandidates.hasNext()) {
return null;
}
this.candidates = this.serviceCandidates.next().getValue().iterator();
}

if (this.candidates.hasNext()) {
final PluginResource resource = this.candidates.next().getResource();
if (resource instanceof JVMPluginResource) {
if (((JVMPluginResource) resource).getType() != ResourceType.JAR) {
continue;
}
}

final Path resolved = resource.getFileSystem().getPath(s);
if (Files.exists(resolved)) {
try {
return resolved.toUri().toURL();
} catch (final MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
}
}
}
}

return Optional.empty();
};
};
}

Expand Down
2 changes: 1 addition & 1 deletion vanilla/src/applaunch/resources/log4j2.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<Root level="all">
<Root level="DEBUG">
<AppenderRef ref="SysOut" level="INFO"/>
<AppenderRef ref="File" level="INFO"/>
<AppenderRef ref="DebugFile" level="DEBUG"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public final class InstallerMain {

private static final Pattern CLASSPATH_SPLITTER = Pattern.compile(";", Pattern.LITERAL);

static {
System.setProperty("log4j.configurationFile", "log4j2_launcher.xml");
}
Expand Down Expand Up @@ -88,9 +91,11 @@ public void run() throws Exception {
}
final String depsClasspath = this.installer.getLibraryManager().getAll().values().stream().map(LibraryManager.Library::getFile).
map(Path::toAbsolutePath).map(Path::normalize).map(Path::toString).collect(Collectors.joining(File.pathSeparator));
final String classpath = Paths.get(System.getProperty("java.class.path")).toAbsolutePath() + File.pathSeparator +
depsClasspath + File.pathSeparator +
minecraftJar.toAbsolutePath().normalize().toString();
final String launchClasspath = CLASSPATH_SPLITTER.splitAsStream(System.getProperty("java.class.path"))
.map(it -> Paths.get(it).toAbsolutePath().toString())
.collect(Collectors.joining(File.pathSeparator));
final String classpath = launchClasspath + File.pathSeparator + depsClasspath +
File.pathSeparator + minecraftJar.toAbsolutePath().normalize().toString();
final List<String> gameArgs = Arrays.asList(this.installer.getLauncherConfig().args.split(" "));

this.installer.getLogger().debug("Setting classpath to: " + classpath);
Expand Down
3 changes: 3 additions & 0 deletions vanilla/src/installer/resources/log4j2_launcher.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<AppenderRef ref="SysOut" level="INFO"/>
<AppenderRef ref="File" level="INFO"/>
<AppenderRef ref="DebugFile" level="TRACE"/>
<filters>
<MarkerFilter marker="CLASSDUMP" onMatch="DENY" onMismatch="NEUTRAL"/>
</filters>
</Root>
</Loggers>
</Configuration>
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,14 @@
import org.spongepowered.vanilla.launch.plugin.VanillaPluginManager;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Enumeration;

public abstract class VanillaLaunch extends Launch {

Expand Down Expand Up @@ -84,12 +90,44 @@ protected final void createPlatformPlugins(final PluginEngine pluginEngine) {
.orElseThrow(() -> new RuntimeException("The game directory has not been added to the environment!"));

try {
final Collection<PluginMetadata> read = PluginMetadataHelper.builder().build().read(VanillaLaunch.class.getResourceAsStream(
"/META-INF/" + JVMPluginResourceLocatorService.DEFAULT_METADATA_FILENAME));
// This is a bit nasty, but allows Sponge to detect builtin platform plugins when it's not the first entry on the classpath.
final URL classUrl = VanillaLaunch.class.getResource("/" + VanillaLaunch.class.getName().replace('.', '/') + ".class");

Collection<PluginMetadata> read = null;
if (classUrl.getProtocol().equals("file")) { // In development environment, we aren't even discovered by ModLauncher for some reason
read = PluginMetadataHelper.builder().build().read(VanillaLaunch.class.getResourceAsStream(
"/META-INF/" + JVMPluginResourceLocatorService.DEFAULT_METADATA_FILENAME));
} else if (classUrl.getProtocol().equals("jar")) { // In production
// Extract the path of the underlying jar file, and parse it as a path to normalize it
final String[] classUrlSplit = classUrl.getPath().split("!");
final Path expectedFile = Paths.get(new URI(classUrlSplit[0]));

// Then go through every possible resource
final Enumeration<URL> manifests =
VanillaLaunch.class.getClassLoader().getResources("/META-INF/" + JVMPluginResourceLocatorService.DEFAULT_METADATA_FILENAME);
while (manifests.hasMoreElements()) {
final URL next = manifests.nextElement();
if (!next.getProtocol().equals("jar")) {
continue;
}

// And stop when the normalized jar in that resource matches the URL of the jar that loaded VanillaLaunch?
final String[] pathSplit = next.getPath().split("!");
if (pathSplit.length == 2) {
if (Paths.get(new URI(pathSplit[0])).equals(expectedFile)) {
read = PluginMetadataHelper.builder().build().read(next.openStream());
break;
}
}
}
}
if (read == null) {
throw new RuntimeException("Could not determine location for implementation metadata!");
}
for (final PluginMetadata metadata : read) {
this.getPluginManager().addDummyPlugin(new DummyPluginContainer(metadata, gameDirectory, this.getLogger(), this));
}
} catch (final IOException e) {
} catch (final IOException | URISyntaxException e) {
throw new RuntimeException("Could not load metadata information for the implementation! This should be impossible!");
}
}
Expand Down

0 comments on commit efdc773

Please sign in to comment.