Skip to content
This repository has been archived by the owner on Jan 24, 2022. It is now read-only.

Commit

Permalink
Rewrite jimfs compat to work on OpenJ9, and in a cleaner way
Browse files Browse the repository at this point in the history
Instead of resetting the FileSystemProvider list and changing the
system classloader so that it is found properly when reloading,
run ServiceLoader on our own classloader and add providers to the
existing list (copying into an ArrayList to allow modifiability)
Also refactored some code to make the intent clearer.
  • Loading branch information
comp500 committed Jul 2, 2020
1 parent aa3d069 commit 9c9aa8f
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 132 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ apply plugin: 'net.minecraftforge.gradle'
apply plugin: 'eclipse'
apply plugin: 'maven-publish'

version = '1.0.11'
version = '1.0.12'
group = 'link.infra.jumploader' // http://maven.apache.org/guides/mini/guide-naming-conventions.html
archivesBaseName = 'jumploader'

Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
modGroup=link.infra.jumploader
modVersion=1.0.11
modVersion=1.0.12
modBaseName=jumploader
24 changes: 4 additions & 20 deletions src/main/java/link/infra/jumploader/Jumploader.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import link.infra.jumploader.specialcases.ReflectionHack;
import link.infra.jumploader.specialcases.SpecialCaseHandler;
import link.infra.jumploader.ui.GUIManager;
import link.infra.jumploader.util.ReflectionUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

Expand All @@ -24,7 +25,6 @@
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
Expand All @@ -35,7 +35,7 @@
import java.util.Set;

public class Jumploader implements ITransformationService {
public static final String VERSION = "1.0.11";
public static final String VERSION = "1.0.12";
public static final String USER_AGENT = "Jumploader/" + VERSION;

private final Logger LOGGER = LogManager.getLogger();
Expand All @@ -56,32 +56,16 @@ public void beginScanning(@Nonnull IEnvironment env) {
throw new RuntimeException("Jumploader shouldn't get to this stage!");
}

@SuppressWarnings("unchecked")
private static <T> T reflectField(Object destObj, String name) throws NoSuchFieldException, IllegalAccessException, ClassCastException {
Field field = destObj.getClass().getDeclaredField(name);
field.setAccessible(true);
return (T) field.get(destObj);
}

@Override
public void onLoad(@Nonnull IEnvironment env, @Nonnull Set<String> set) {
LOGGER.info("Jumploader " + VERSION + " initialising, discovering environment...");

// OpenJ9 is currently unsupported - it does not allow bypassing the reflection blacklist to change the system classloader
// See FabricLoaderReflectionHack.java
if (System.getProperty("java.vm.name", "").toLowerCase().contains("openj9")) {
LOGGER.fatal("OpenJ9 is currently unsupported with Jumploader");
LOGGER.fatal("Please use Hotspot, or install your desired modloader directly instead of jumploading it!");
LOGGER.fatal("See https://fabricmc.net/wiki/install for a Fabric installation guide");
System.exit(1);
}

// Get the game arguments
ParsedArguments argsParsedTemp;
// Very bad reflection, don't try this at home!!
try {
ArgumentHandler handler = reflectField(Launcher.INSTANCE, "argumentHandler");
String[] gameArgs = reflectField(handler, "args");
ArgumentHandler handler = ReflectionUtil.reflectField(Launcher.INSTANCE, "argumentHandler");
String[] gameArgs = ReflectionUtil.reflectField(handler, "args");
if (gameArgs == null) {
LOGGER.warn("Game arguments are null, have they been parsed yet?");
gameArgs = new String[0];
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package link.infra.jumploader.specialcases;

import link.infra.jumploader.resources.ParsedArguments;
import link.infra.jumploader.util.ReflectionUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.net.URL;
import java.nio.file.spi.FileSystemProvider;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;

public class FileSystemProviderAppender implements ReflectionHack {
private final Logger LOGGER = LogManager.getLogger();

private void loadProvidersFromClassLoader(ClassLoader classLoader, List<FileSystemProvider> list) {
ServiceLoader<FileSystemProvider> loader = ServiceLoader.load(FileSystemProvider.class, classLoader);

for (FileSystemProvider provider: loader) {
String scheme = provider.getScheme();
if (!scheme.equalsIgnoreCase("file")) {
if (list.stream().noneMatch(p -> p.getScheme().equalsIgnoreCase(scheme))) {
list.add(provider);
}
}
}
}

@Override
public void applyReflectionHack(ClassLoader loadingClassloader) {
// Ensure the existing providers are loaded first
FileSystemProvider.installedProviders();

try {
final Object lock = ReflectionUtil.reflectStaticField(FileSystemProvider.class, "lock");
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (lock) {
// Load providers from the Jumploader classloader, and add them to the FileSystemProvider list
ReflectionUtil.<List<FileSystemProvider>>transformStaticField(FileSystemProvider.class, "installedProviders", existingProviders -> {
List<FileSystemProvider> newList = new ArrayList<>(existingProviders);
AccessController.doPrivileged((PrivilegedAction<Void>) () -> {loadProvidersFromClassLoader(loadingClassloader, newList); return null;});
return newList;
});
}
} catch (NoSuchFieldException | IllegalAccessException | ClassCastException e) {
LOGGER.warn("Failed to fix FileSystemProvider loading, jar-in-jar may not work", e);
}
}

@Override
public boolean shouldApply(List<URL> loadedJars, String mainClass, ParsedArguments gameArguments) {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package link.infra.jumploader.specialcases;

import link.infra.jumploader.resources.ParsedArguments;
import link.infra.jumploader.util.ReflectionUtil;
import link.infra.jumploader.util.RegexUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLStreamHandler;
import java.util.Hashtable;
import java.util.List;
import java.util.regex.Pattern;

public class JimfsReflectionHack implements ReflectionHack {
private final Logger LOGGER = LogManager.getLogger();

private static void injectJimfsHandler(ClassLoader classLoader) throws NoSuchFieldException, IllegalAccessException, ClassCastException, ClassNotFoundException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// Add the jimfs handler to the URL handlers field, because Class.forName by default uses the classloader that loaded the calling class (in this case the system classloader, so we have to do it manually)
Hashtable<String, URLStreamHandler> handlers = ReflectionUtil.reflectStaticField(URL.class, "handlers");
handlers.putIfAbsent("jimfs", (URLStreamHandler) Class.forName("com.google.common.jimfs.Handler", true, classLoader).getDeclaredConstructor().newInstance());
}

@Override
public void applyReflectionHack(ClassLoader loadingClassloader) {
// Jimfs requires some funky hacks to load under a custom classloader, Java protocol handlers don't handle custom classloaders very well
// See also FileSystemProviderAppender
try {
JimfsReflectionHack.injectJimfsHandler(loadingClassloader);
} catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException | InstantiationException | ClassCastException | NoSuchMethodException | InvocationTargetException e) {
LOGGER.warn("Failed to fix jimfs loading, jar-in-jar may not work", e);
}
}

@Override
public boolean shouldApply(List<URL> loadedJars, String mainClass, ParsedArguments gameArguments) {
return RegexUtil.patternMatchesJars(Pattern.compile("jimfs-(.+)\\.jar$"), loadedJars);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@

public class SpecialCaseHandler {
private final List<SpecialCase> allCases = Arrays.asList(
new FabricLoaderReflectionHack(),
new JimfsReflectionHack(),
new MixinHideModLauncherBlacklist(),
new ServerSideRemoveFMLArgs(),
new ClassRedefinerASM(),
new ForgeJarClasspathModifier()
new ForgeJarClasspathModifier(),
new FileSystemProviderAppender()
);

private final List<SpecialCase> appliedCases = new ArrayList<>();
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/link/infra/jumploader/util/ReflectionUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package link.infra.jumploader.util;

import java.lang.reflect.Field;
import java.util.function.Function;

public class ReflectionUtil {
@SuppressWarnings("unchecked")
public static <T> T reflectField(Object destObj, String name) throws NoSuchFieldException, IllegalAccessException, ClassCastException {
Field field = destObj.getClass().getDeclaredField(name);
field.setAccessible(true);
return (T) field.get(destObj);
}

@SuppressWarnings("unchecked")
public static <T> T reflectStaticField(Class<?> destClass, String name) throws NoSuchFieldException, IllegalAccessException, ClassCastException {
Field field = destClass.getDeclaredField(name);
field.setAccessible(true);
return (T) field.get(null);
}

@SuppressWarnings({"unchecked", "UnusedReturnValue"})
public static <T> T transformStaticField(Class<?> destClass, String name, Function<T, T> transformer) throws NoSuchFieldException, IllegalAccessException, ClassCastException {
Field field = destClass.getDeclaredField(name);
field.setAccessible(true);
T value = transformer.apply((T) field.get(null));
field.set(null, value);
return value;
}
}
2 changes: 1 addition & 1 deletion src/main/resources/fabric.mod.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"id": "jumploader",
"version": "1.0.11",
"version": "1.0.12",

"name": "Jumploader",
"description": "Allows the use of Fabric mods in Twitch modpacks, by loading Fabric as if it were a Forge mod.",
Expand Down

0 comments on commit 9c9aa8f

Please sign in to comment.