Finish up classloading system. Use ModJARURL to locate resources

and enable ModLauncher to use those instead. This allows all mods to load
within the scope of the game classloader, removing weird conflicts and
class discovery problems.
This commit is contained in:
cpw 2019-01-04 11:44:57 -05:00
parent 6baddc7e26
commit fd9c83b65f
21 changed files with 360 additions and 144 deletions

View File

@ -126,6 +126,7 @@ project(':forge') {
mavenLocal()
mavenCentral()
}
ext {
SPEC_VERSION = '24.0' // This is overwritten by git tag, but here so dev time doesnt explode
// The new versioning sceme is <MCVersion>-<ForgeMC>.<RB>.<CommitsSinceRB>
@ -133,11 +134,30 @@ project(':forge') {
// Essentially, the same as the old, except dropping the first number, and the builds are no longer unique.
MCP_ARTIFACT = project(':mcp').mcp.config
}
def getVersion = {
//TAG-offset-hash
def raw = grgit.describe(longDescr: true)
def desc = (raw == null ? '0.0-0-unknown' : grgit.describe(longDescr: true)).split('-') as List
def hash = desc.remove(desc.size() - 1)
def offset = desc.remove(desc.size() - 1)
def tag = desc.join('-')
def branch = grgit.branch.current().name
if (branch in ['master', 'HEAD', MC_VERSION, MC_VERSION + '.0'])
branch = null
if (branch != null && branch.endsWith('.x') && MC_VERSION.startsWith(branch.substring(0, branch.length() - 2))) //1.13.x
branch = null
SPEC_VERSION = tag
return "${MC_VERSION}-${tag}.${offset}${t -> if (branch != null) t << '-' + branch}".toString() //Bake the response instead of making it dynamic
}
version = getVersion()
patcher {
parent = project(':clean')
patches = file("$rootDir/patches/minecraft")
patchedSrc = file('src/main/java')
accessTransformer = file("$rootDir/src/main/resources/forge_at.cfg")
accessTransformer = file("$rootDir/src/main/resources/META-INF/accesstransformer.cfg")
exc = file("$rootDir/src/main/resources/forge.exc")
srgPatches = true
clientRun {
@ -153,9 +173,10 @@ project(':forge') {
'org.lwjgl.system.SharedLibraryExtractDirectory': 'lwjgl_dll',
'mc.version': MC_VERSION,
'mcp.version': MCP_VERSION,
'forge.version': "${project.version.substring(MC_VERSION.length() + 1)}".toString(),
'forge.version': project.version.substring(MC_VERSION.length() + 1),
'forge.spec': SPEC_VERSION,
'forge.group': project.group
'forge.group': project.group,
'fmllauncher.version': SPEC_VERSION
]
}
serverRun {
@ -173,24 +194,6 @@ project(':forge') {
}
}
def getVersion = {
//TAG-offset-hash
def raw = grgit.describe(longDescr: true)
def desc = (raw == null ? '0.0-0-unknown' : grgit.describe(longDescr: true)).split('-') as List
def hash = desc.remove(desc.size() - 1)
def offset = desc.remove(desc.size() - 1)
def tag = desc.join('-')
def branch = grgit.branch.current().name
if (branch in ['master', 'HEAD', MC_VERSION, MC_VERSION + '.0'])
branch = null
if (branch != null && branch.endsWith('.x') && MC_VERSION.startsWith(branch.substring(0, branch.length() - 2))) //1.13.x
branch = null
SPEC_VERSION = tag
return "${MC_VERSION}-${tag}.${offset}${t -> if (branch != null) t << '-' + branch}".toString() //Bake the response instead of making it dynamic
}
version = getVersion()
ext {
MANIFESTS = [
'/': [
@ -251,7 +254,7 @@ project(':forge') {
installer 'org.ow2.asm:asm:6.2'
installer 'org.ow2.asm:asm-commons:6.2'
installer 'org.ow2.asm:asm-tree:6.2'
installer 'cpw.mods:modlauncher:0.2.0'
installer 'cpw.mods:modlauncher:0.3.0'
installer 'net.minecraftforge:accesstransformers:0.10+:shadowed'
installer 'net.minecraftforge:eventbus:0.1+:service'
installer 'net.minecraftforge:forgespi:0.1+'

View File

@ -71,7 +71,7 @@ public class FMLClientLaunchProvider extends FMLCommonLaunchHandler implements I
final String forgeVersion = (String) arguments.get("forgeVersion");
final String mcVersion = (String) arguments.get("mcVersion");
final String forgeGroup = (String) arguments.get("forgeGroup");
mods.add(forgeGroup+":forge::universal:"+mcVersion+"-"+forgeVersion);
mods.add(forgeGroup+":forge:universal:"+mcVersion+"-"+forgeVersion);
// generics are gross yea?
((Map)arguments).put("mavenRoots", mavenRoots);
((Map)arguments).put("mods", mods);

View File

@ -30,6 +30,7 @@ import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
@ -87,8 +88,8 @@ public class FMLDevClientLaunchProvider extends FMLCommonLaunchHandler implement
{
// we're injecting forge into the exploded dir finder
final Path forgemodstoml = LibraryFinder.findJarPathFor("META-INF/mods.toml", "forgemodstoml");
((Map<String, List<Pair<Path,Path>>>) arguments).computeIfAbsent("explodedTargets", a->new ArrayList<>()).
add(Pair.of(compiledClasses, forgemodstoml));
((Map<String, List<Pair<Path,List<Path>>>>) arguments).computeIfAbsent("explodedTargets", a->new ArrayList<>()).
add(Pair.of(forgemodstoml, Collections.singletonList(compiledClasses)));
}
@Override

View File

@ -29,9 +29,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.Callable;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
@ -86,8 +84,8 @@ public class FMLDevServerLaunchProvider extends FMLCommonLaunchHandler implement
{
// we're injecting forge into the exploded dir finder
final Path forgemodstoml = LibraryFinder.findJarPathFor("META-INF/mods.toml", "forgemodstoml");
((Map<String, List<Pair<Path,Path>>>) arguments).computeIfAbsent("explodedTargets", a->new ArrayList<>()).
add(Pair.of(compiledClasses, forgemodstoml));
((Map<String, List<Pair<Path,List<Path>>>>) arguments).computeIfAbsent("explodedTargets", a->new ArrayList<>()).
add(Pair.of(forgemodstoml, Collections.singletonList(compiledClasses)));
}
@Override

View File

@ -19,6 +19,7 @@
package net.minecraftforge.fml.loading;
import cpw.mods.modlauncher.TransformingClassLoader;
import cpw.mods.modlauncher.api.IEnvironment;
import cpw.mods.modlauncher.api.ILaunchHandlerService;
import cpw.mods.modlauncher.api.ITransformationService;
@ -56,7 +57,7 @@ public class FMLLoader
private static LanguageLoadingProvider languageLoadingProvider;
private static Dist dist;
private static LoadingModList loadingModList;
private static ClassLoader launchClassLoader;
private static TransformingClassLoader launchClassLoader;
private static RuntimeDistCleaner runtimeDistCleaner;
private static Path gamePath;
private static Path forgePath;
@ -207,7 +208,7 @@ public class FMLLoader
public static void beforeStart(ITransformingClassLoader launchClassLoader)
{
FMLLoader.launchClassLoader = launchClassLoader.getInstance();
FMLLoader.launchClassLoader = (TransformingClassLoader) launchClassLoader.getInstance();
}
@ -217,7 +218,7 @@ public class FMLLoader
}
public static ClassLoader getLaunchClassLoader()
public static TransformingClassLoader getLaunchClassLoader()
{
return launchClassLoader;
}

View File

@ -69,7 +69,7 @@ public class FMLServerLaunchProvider extends FMLCommonLaunchHandler implements I
final String forgeVersion = (String) arguments.get("forgeVersion");
final String mcVersion = (String) arguments.get("mcVersion");
final String forgeGroup = (String) arguments.get("forgeGroup");
mods.add(forgeGroup+":forge::universal:"+mcVersion+"-"+forgeVersion);
mods.add(forgeGroup+":forge:universal:"+mcVersion+"-"+forgeVersion);
// generics are gross yea?
((Map)arguments).put("mavenRoots", mavenRoots);
((Map)arguments).put("mods", mods);

View File

@ -49,22 +49,27 @@ public class LibraryFinder {
static Path findJarPathFor(final String className, final String jarName) {
final URL resource = LibraryFinder.class.getClassLoader().getResource(className);
return findJarPathFor(className, jarName, resource);
}
public static Path findJarPathFor(final String resourceName, final String jarName, final URL resource) {
try {
Path path;
final URI uri = resource.toURI();
if (uri.getRawSchemeSpecificPart().contains("!")) {
path = Paths.get(new URI(uri.getRawSchemeSpecificPart().split("!")[0]));
} else {
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-className.length())));
path = Paths.get(new URI("file://"+uri.getRawSchemeSpecificPart().substring(0, uri.getRawSchemeSpecificPart().length()-resourceName.length())));
}
LOGGER.debug(CORE, "Found JAR {} at path {}", jarName, path.toString());
return path;
} catch (NullPointerException | URISyntaxException e) {
LOGGER.error(CORE, "Failed to find JAR for class {} - {}", className, jarName);
throw new RuntimeException("Unable to locate "+className+" - "+jarName, e);
LOGGER.error(CORE, "Failed to find JAR for class {} - {}", resourceName, jarName);
throw new RuntimeException("Unable to locate "+resourceName+" - "+jarName, e);
}
}
static Path[] commonLibPaths(Path[] extras) {
public static Path[] commonLibPaths(Path[] extras) {
final Path realms = findJarPathFor("com/mojang/realmsclient/RealmsVersion.class", "realms");
return ObjectArrays.concat(extras, realms);
}

View File

@ -25,6 +25,8 @@ import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
import net.minecraftforge.fml.loading.moddiscovery.ModInfo;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
@ -102,6 +104,20 @@ public class LoadingModList
return null;
}
public URL findURLForResource(final String resourceName) {
for (ModFileInfo mf : modFiles) {
final Path resource = mf.getFile().findResource(resourceName);
if (Files.exists(resource)) {
try {
return new URL("modjar://"+mf.getMods().get(0).getModId()+"/"+resourceName);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}
return null;
}
public ModFileInfo getModFileById(String modid)
{
return this.fileById.get(modid);

View File

@ -25,7 +25,8 @@ import java.nio.file.Paths;
/**
* Convert a maven coordinate into a Path.
*
* {@code <groupId>:<artifactId>[:<extension>[:<classifier>]]:<version>}, must not be {@code null}.
* This is gradle standard not maven standard coordinate formatting
* {@code <groupId>:<artifactId>[:<classifier>]:<version>[@extension]}, must not be {@code null}.
*/
public class MavenCoordinateResolver {
@ -33,9 +34,10 @@ public class MavenCoordinateResolver {
final String[] parts = coordinate.split(":");
final String groupId = parts[0];
final String artifactId = parts[1];
final String extension = parts.length > 3 ? parts[2] : "";
final String classifier = parts.length > 4 ? parts[3] : "";
final String version = parts[parts.length-1];
final String classifier = parts.length > 3 ? parts[2] : "";
final String[] versext = parts[parts.length-1].split("@");
final String version = versext[0];
final String extension = versext.length > 1 ? versext[1] : "";
return get(groupId, artifactId, extension, classifier, version);
}

View File

@ -25,29 +25,43 @@ import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.nio.file.Files;
import java.nio.file.Path;
public class ModJarURLHandler extends URLStreamHandler
{
// modjar:///modid!path/to/file
// modjar://modid/path/to/file
@Override
protected URLConnection openConnection(URL url) throws IOException {
protected URLConnection openConnection(URL url) {
return new URLConnection(url) {
private Path resource;
private String modpath;
private String modid;
@Override
public void connect() throws IOException
public void connect()
{
final String path = url.getPath();
final String[] parts = path.split("!");
if (parts.length!=2) throw new IOException("Illegal URL format "+path);
modid = parts[0];
modpath = parts[1];
if (resource == null) {
modid = url.getHost();
// trim first char
modpath = url.getPath().substring(1);
resource = FMLLoader.getLoadingModList().getModFileById(modid).getFile().findResource(modpath);
}
}
@Override
public InputStream getInputStream() throws IOException
{
return Files.newInputStream(FMLLoader.getLoadingModList().getModFileById(modid).getFile().findResource(modpath));
connect();
return Files.newInputStream(resource);
}
@Override
public long getContentLengthLong() {
try {
connect();
return Files.size(resource);
} catch (IOException e) {
return -1L;
}
}
};
}

View File

@ -36,8 +36,8 @@ import static net.minecraftforge.fml.loading.LogMarkers.SCAN;
public class ExplodedDirectoryLocator implements IModLocator {
private static final Logger LOGGER = LogManager.getLogger();
private final List<Pair<Path,Path>> rootDirs;
private final Map<ModFile, Pair<Path,Path>> mods;
private final List<Pair<Path,List<Path>>> rootDirs;
private final Map<ModFile, Pair<Path,List<Path>>> mods;
public ExplodedDirectoryLocator() {
this.rootDirs = new ArrayList<>();
@ -49,7 +49,7 @@ public class ExplodedDirectoryLocator implements IModLocator {
final Path modstoml = Paths.get("META-INF", "mods.toml");
// Collect all the mods.toml files found
rootDirs.forEach(pathPathPair -> {
Path resources = pathPathPair.getRight();
Path resources = pathPathPair.getLeft();
Path modtoml = resources.resolve(modstoml);
if (Files.exists(modtoml)) {
ModFile mf = new ModFile(pathPathPair.getLeft(), this);
@ -73,25 +73,29 @@ public class ExplodedDirectoryLocator implements IModLocator {
}
final Path target = Paths.get(path[0], Arrays.copyOfRange(path, 1, path.length));
// try right path first (resources)
Path found = mods.get(modFile).getRight().resolve(target);
Path found = mods.get(modFile).getLeft().resolve(target);
if (Files.exists(found)) return found;
// then try left path (classes)
return mods.get(modFile).getLeft().resolve(target);
return mods.get(modFile).getRight().stream().map(p->p.resolve(target)).findFirst().orElse(found.resolve(target));
}
@Override
public void scanFile(final ModFile modFile, final Consumer<Path> pathConsumer) {
LOGGER.debug(SCAN,"Scanning directory {}", modFile.getFilePath().toString());
final Pair<Path, Path> pathPathPair = mods.get(modFile);
// classes are in the left branch of the pair
try (Stream<Path> files = Files.find(pathPathPair.getLeft(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
files.forEach(pathConsumer);
} catch (IOException e) {
e.printStackTrace();
}
LOGGER.debug(SCAN,"Directory scan complete {}", pathPathPair.getLeft());
LOGGER.debug(SCAN,"Scanning exploded directory {}", modFile.getFilePath().toString());
final Pair<Path, List<Path>> pathPathPair = mods.get(modFile);
// classes are in the right branch of the pair
pathPathPair.getRight().forEach(path->scanIndividualPath(path, pathConsumer));
LOGGER.debug(SCAN,"Exploded directory scan complete {}", pathPathPair.getLeft().toString());
}
private void scanIndividualPath(final Path path, Consumer<Path> pathConsumer) {
LOGGER.debug(SCAN, "Scanning exploded target {}", path.toString());
try (Stream<Path> files = Files.find(path, Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) {
files.forEach(pathConsumer);
} catch (IOException e) {
LOGGER.info("Exception scanning {}", path, e);
}
}
@Override
public String toString()
{
@ -107,7 +111,7 @@ public class ExplodedDirectoryLocator implements IModLocator {
@SuppressWarnings("unchecked")
@Override
public void initArguments(final Map<String, ?> arguments) {
final List<Pair<Path, Path>> explodedTargets = ((Map<String, List<Pair<Path, Path>>>) arguments).get("explodedTargets");
final List<Pair<Path, List<Path>>> explodedTargets = ((Map<String, List<Pair<Path, List<Path>>>>) arguments).get("explodedTargets");
if (explodedTargets != null && !explodedTargets.isEmpty()) {
rootDirs.addAll(explodedTargets);
}

View File

@ -49,12 +49,18 @@ public class LaunchTesting
logcontext.getConfiguration().addFilter(eventbusFilter);
logcontext.getConfiguration().addFilter(distxformFilter);
logcontext.updateLoggers();
File invsorter = new File("/home/cpw/projects/minecraft/inventorysorter/classes");
if (invsorter.exists()) {
System.setProperty("fml.explodedDir", "/home/cpw/projects/minecraft/inventorysorter/classes"); //TODO: Move this to a example included in our tests, not a random location...
}
String assets = System.getenv().getOrDefault("assetDirectory", "assets");
String target = System.getenv().get("target");
String[] launchArgs = new String[]{
"--gameDir", ".",
"--launchTarget", target,
"--fml.forgeVersion", System.getProperty("forge.version"),
"--fml.mcpVersion", System.getProperty("mcp.version"),
"--fml.mcVersion", System.getProperty("mc.version"),
"--fml.forgeGroup", System.getProperty("forge.group")
};
if (target == null) {
throw new IllegalArgumentException("Environment variable target must be set.");
@ -62,23 +68,15 @@ public class LaunchTesting
if (Objects.equals(target,"fmldevclient")) {
hackNatives();
Launcher.main("--launchTarget", target,
"--gameDir", ".",
"--accessToken", "blah",
"--version", "FMLDev",
"--assetIndex", "1.13",
"--assetsDir", assets,
"--userProperties", "{}",
"--fml.forgeVersion", "24.0.0",
"--fml.mcpVersion", "2018.11.30",
"--fml.mcVersion", "1.13");
} else if (Objects.equals(target, "fmldevserver")) {
String[] launchargs = ObjectArrays.concat(new String[] {"--launchTarget", target,
"--gameDir", ".", "--fml.forgeVersion", "24.0.0",
"--fml.mcpVersion", "2018.11.30",
"--fml.mcVersion", "1.13"}, args, String.class);
Launcher.main(launchargs);
launchArgs = ObjectArrays.concat(launchArgs, new String[] {
"--accessToken", "blah",
"--version", "FMLDev",
"--assetIndex", "1.13",
"--assetsDir", assets,
"--userProperties", "{}"
}, String.class);
}
Launcher.main(launchArgs);
Thread.sleep(10000);
}

View File

@ -19,6 +19,7 @@
package net.minecraftforge.fml;
import cpw.mods.modlauncher.TransformingClassLoader;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ModelRegistryEvent;
import net.minecraftforge.common.MinecraftForge;
@ -40,12 +41,13 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static net.minecraftforge.fml.Logging.CORE;
import static net.minecraftforge.fml.Logging.LOADING;
public class ModLoader
{
private static final Logger LOGGER = LogManager.getLogger();
private static ModLoader INSTANCE;
private final ClassLoader launchClassLoader;
private final TransformingClassLoader launchClassLoader;
private final LoadingModList loadingModList;
private final ModLoadingClassLoader modClassLoader;
@ -112,6 +114,7 @@ public class ModLoader
{
final Map<String, IModInfo> modInfoMap = modFile.getModFileInfo().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, Function.identity()));
LOGGER.debug(LOADING, "ModContainer is {}", ModContainer.class.getClassLoader());
return modFile.getScanResult().getTargets().entrySet().stream().
map(e-> {
try {

View File

@ -19,20 +19,18 @@
package net.minecraftforge.fml;
import cpw.mods.modlauncher.TransformingClassLoader;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.ModJarURLHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.SecureClassLoader;
import java.util.function.Predicate;
import static net.minecraftforge.fml.Logging.LOADING;
public class ModLoadingClassLoader extends SecureClassLoader
public class ModLoadingClassLoader extends TransformingClassLoader
{
private static final Logger LOGGER = LogManager.getLogger();
@ -41,62 +39,44 @@ public class ModLoadingClassLoader extends SecureClassLoader
}
private final Predicate<String> classLoadingPredicate;
private final TransformingClassLoader tcl;
protected ModLoadingClassLoader(final ClassLoader parent) {
super(parent);
protected ModLoadingClassLoader(final TransformingClassLoader parent) {
super(parent, path->FMLLoader.getLoadingModList().findURLForResource(path));
this.tcl = parent;
this.classLoadingPredicate = FMLLoader.getClassLoaderExclusions();
}
@Override
protected URL locateResource(final String path) {
return FMLLoader.getLoadingModList().findURLForResource(path);
}
@Override
public URL getResource(String name)
{
return super.getResource(name);
return locateResource(name);
}
/*
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
Class<?> existingClass = tcl.getLoadedClass(name);
if (existingClass != null) return existingClass;
final String className = name.replace('.', '/').concat(".class");
final Path classResource = FMLLoader.getLoadingModList().findResource(className);
if (classResource != null)
{
return findClass(name);
}
return super.loadClass(name, resolve);
}
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException
{
if (!classLoadingPredicate.test(name)) {
LOGGER.debug(LOADING, "Delegating to parent {}", name);
return getParent().loadClass(name);
}
LOGGER.debug(LOADING, "Loading class {}", name);
final String className = name.replace('.','/').concat(".class");
final Path classResource = FMLLoader.getLoadingModList().findResource(className);
if (classResource != null) {
try {
final byte[] bytes = Files.readAllBytes(classResource);
return defineClass(name, bytes, 0, bytes.length);
LOGGER.debug(LOADING, "Loading class {}", name);
if (!classLoadingPredicate.test(name)) {
LOGGER.debug(LOADING, "Delegating to parent {}", name);
return tcl.loadClass(name);
}
catch (IOException e)
{
throw new ClassNotFoundException("Failed to load class file " + classResource + " for "+ className, e);
}
} else if(getParent() != null) {
getParent().loadClass(name);
return tcl.loadClass(name, this::locateResource);
}
throw new ClassNotFoundException("Failed to find class file "+ className);
}
@Override
protected URL findResource(String name)
{
return super.findResource(name);
return locateResource(name);
}
}

View File

@ -35,8 +35,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static net.minecraftforge.fml.Logging.LOADING;
import static net.minecraftforge.fml.Logging.SCAN;
import static net.minecraftforge.fml.Logging.*;
public class FMLJavaModLanguageProvider implements IModLanguageProvider
{
@ -68,13 +67,14 @@ public class FMLJavaModLanguageProvider implements IModLanguageProvider
// in the classloader of the game - the context classloader is appropriate here.
try
{
final Constructor<?> constructor = Class.forName("net.minecraftforge.fml.javafmlmod.FMLModContainer", true, Thread.currentThread().getContextClassLoader()).
getConstructor(IModInfo.class, String.class, ClassLoader.class, ModFileScanData.class);
final Class<?> fmlContainer = Class.forName("net.minecraftforge.fml.javafmlmod.FMLModContainer", true, Thread.currentThread().getContextClassLoader());
LOGGER.debug(LOADING, "Loading FMLModContainer from classloader {} - got {}", Thread.currentThread().getContextClassLoader(), fmlContainer.getClassLoader());
final Constructor<?> constructor = fmlContainer.getConstructor(IModInfo.class, String.class, ClassLoader.class, ModFileScanData.class);
return (T)constructor.newInstance(info, className, modClassLoader, modFileScanResults);
}
catch (NoSuchMethodException | ClassNotFoundException | InstantiationException | IllegalAccessException | InvocationTargetException e)
{
LOGGER.error(LOADING,"Unable to load FMLModContainer, wut?", e);
LOGGER.fatal(LOADING,"Unable to load FMLModContainer, wut?", e);
throw new RuntimeException(e);
}
}

View File

@ -0,0 +1,37 @@
package net.minecraftforge.userdev;
import cpw.mods.modlauncher.api.ILaunchHandlerService;
import cpw.mods.modlauncher.api.ITransformingClassLoader;
import net.minecraftforge.api.distmarker.Dist;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.Callable;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLUserdevClientLaunchProvider extends FMLUserdevLaunchProvider implements ILaunchHandlerService {
private static final Logger LOGGER = LogManager.getLogger();
@Override
public Dist getDist() {
return Dist.CLIENT;
}
@Override
public String name() {
return "fmluserdevclient";
}
@Override
public Callable<Void> launchService(String[] arguments, ITransformingClassLoader launchClassLoader)
{
return () -> {
LOGGER.debug(CORE, "Launching minecraft in {} with arguments {}", launchClassLoader, arguments);
super.beforeStart(launchClassLoader);
launchClassLoader.addTargetPackageFilter(getPackagePredicate());
Class.forName("net.minecraft.client.main.Main", true, launchClassLoader.getInstance()).getMethod("main", String[].class).invoke(null, (Object)arguments);
return null;
};
}
}

View File

@ -0,0 +1,106 @@
package net.minecraftforge.userdev;
import com.google.common.collect.ObjectArrays;
import com.google.common.collect.Streams;
import cpw.mods.modlauncher.api.IEnvironment;
import net.minecraftforge.fml.loading.FMLCommonLaunchHandler;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.LibraryFinder;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public abstract class FMLUserdevLaunchProvider extends FMLCommonLaunchHandler {
private static final Logger LOGGER = LogManager.getLogger();
private Path forgeJar;
private Path mcJars;
@Override
public Path getForgePath(final String mcVersion, final String forgeVersion, final String forgeGroup) {
final URL forgePath = getClass().getClassLoader().getResource("net/minecraftforge/versions/forge/ForgeVersion.class");
if (forgePath == null) {
LOGGER.fatal(CORE, "Unable to locate forge on the classpath");
throw new RuntimeException("Unable to locate forge on the classpath");
}
forgeJar = LibraryFinder.findJarPathFor("ForgeVersion.class","forge", forgePath);
return forgeJar;
}
@SuppressWarnings("unchecked")
@Override
public void setup(final IEnvironment environment, final Map<String, ?> arguments) {
final List<String> mavenRoots = new ArrayList<>((List<String>) arguments.get("mavenRoots"));
final List<String> mods = new ArrayList<>((List<String>) arguments.get("mods"));
final String forgeVersion = (String) arguments.get("forgeVersion");
final String mcVersion = (String) arguments.get("mcVersion");
final String mcpVersion = (String) arguments.get("mcpVersion");
final String forgeGroup = (String) arguments.get("forgeGroup");
final String userdevVersion = mcVersion+"-"+forgeVersion+"_mapped_snapshot_"+mcpVersion;
int dirs = forgeGroup.split("\\.").length + 2;
Path fjroot = forgeJar;
do {
fjroot = fjroot.getParent();
} while (dirs-- > 0);
final String fjpath = fjroot.toString();
LOGGER.info(CORE, "Injecting forge as mod {} from maven path {}", userdevVersion, fjpath);
mavenRoots.add(fjpath);
mods.add(forgeGroup+":userdev:"+userdevVersion);
try {
final Enumeration<URL> resources = ClassLoader.getSystemClassLoader().getResources("META-INF/mods.toml");
final ArrayList<URL> modstoml = Collections.list(resources);
modstoml.stream().filter(u-> !u.getPath().contains("!"));
} catch (IOException e) {
LOGGER.fatal("Error trying to find resources", e);
throw new RuntimeException("wha?", e);
}
LOGGER.fatal(CORE, "Got mod coordinates {} from env", System.getenv("MOD_CLASSES"));
final List<Path> modClasses = Arrays.stream(System.getenv("MOD_CLASSES").split(File.pathSeparator)).
map(Paths::get).collect(Collectors.toList());
LOGGER.fatal(CORE, "Processed mod coordinates [{}]", modClasses.stream().map(Object::toString).collect(Collectors.joining(",")));
((Map<String, List<Pair<Path,List<Path>>>>) arguments).computeIfAbsent("explodedTargets", a->new ArrayList<>()).
add(Pair.of(modClasses.get(0), modClasses.subList(1, modClasses.size())));
// generics are gross yea?
((Map)arguments).put("mavenRoots", mavenRoots);
((Map)arguments).put("mods", mods);
}
@Override
protected void validatePaths(final Path forgePath, final Path[] mcPaths, final String forgeVersion, final String mcVersion, final String mcpVersion) {
}
@Override
public Path[] getMCPaths(final String mcVersion, final String mcpVersion, final String forgeVersion, final String forgeGroup) {
final URL mcDataPath = getClass().getClassLoader().getResource("assets/minecraft/lang/en_us.json");
if (mcDataPath == null) {
LOGGER.fatal(CORE, "Unable to locate minecraft data on the classpath");
throw new RuntimeException("Unable to locate minecraft data on the classpath");
}
mcJars = LibraryFinder.findJarPathFor("en_us.json","mcdata", mcDataPath);
return new Path[] {mcJars};
}
public Path[] identifyTransformationTargets()
{
return LibraryFinder.commonLibPaths(ObjectArrays.concat(FMLLoader.getForgePath(), FMLLoader.getMCPaths()));
}
}

View File

@ -0,0 +1,39 @@
package net.minecraftforge.userdev;
import cpw.mods.modlauncher.api.ILaunchHandlerService;
import cpw.mods.modlauncher.api.ITransformingClassLoader;
import net.minecraftforge.api.distmarker.Dist;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.Callable;
import static net.minecraftforge.fml.loading.LogMarkers.CORE;
public class FMLUserdevServerLaunchProvider extends FMLUserdevLaunchProvider implements ILaunchHandlerService {
private static final Logger LOGGER = LogManager.getLogger();
@Override
public Dist getDist() {
return Dist.DEDICATED_SERVER;
}
@Override
public String name() {
return "fmluserdevserver";
}
@Override
public Callable<Void> launchService(String[] arguments, ITransformingClassLoader launchClassLoader)
{
return () -> {
LOGGER.debug(CORE, "Launching minecraft in {} with arguments {}", launchClassLoader, arguments);
super.beforeStart(launchClassLoader);
launchClassLoader.addTargetPackageFilter(getPackagePredicate());
Thread.currentThread().setContextClassLoader(launchClassLoader.getInstance());
Class.forName("net.minecraft.server.MinecraftServer", true, launchClassLoader.getInstance()).getMethod("main", String[].class).invoke(null, (Object)arguments);
return null;
};
}
}

View File

@ -57,25 +57,33 @@ public class UserdevLauncher
throw new IllegalArgumentException("Environment variable 'assets' must be set to a valid path.");
}
if (target == null) {
throw new IllegalArgumentException("Environment variable 'target' must be set to 'fmldevclient' or 'fmldevserver'.");
throw new IllegalArgumentException("Environment variable 'target' must be set to 'fmluserdevclient' or 'fmluserdevserver'.");
}
if (Objects.equals(target,"fmldevclient")) {
String[] launchArgs = new String[]{
"--gameDir", ".",
"--launchTarget", target,
"--fml.forgeVersion", System.getenv("FORGE_VERSION"),
"--fml.mcpVersion", System.getenv("MCP_VERSION"),
"--fml.mcVersion", System.getenv("MC_VERSION"),
"--fml.forgeGroup", System.getenv("FORGE_GROUP")
};
if (Objects.equals(target,"fmluserdevclient")) {
hackNatives();
Launcher.main("--launchTarget", target,
"--gameDir", ".",
launchArgs = ObjectArrays.concat(launchArgs, new String[] {
"--accessToken", "blah",
"--version", "FMLDev",
"--assetIndex", "1.13",
"--assetsDir", assets,
"--userProperties", "{}");
} else if (Objects.equals(target, "fmldevserver")) {
String[] launchargs = ObjectArrays.concat(new String[] {"--launchTarget", target,
"--gameDir", "."}, args, String.class);
Launcher.main(launchargs);
"--userProperties", "{}"
}, String.class);
} else if (Objects.equals(target, "fmluserdevserver")) {
// we're good
} else {
throw new IllegalArgumentException("Unknown value for 'target' property: " + target);
}
Launcher.main(launchArgs);
Thread.sleep(10000);
}

View File

@ -0,0 +1,2 @@
net.minecraftforge.userdev.FMLUserdevClientLaunchProvider
net.minecraftforge.userdev.FMLUserdevServerLaunchProvider

View File

@ -1 +0,0 @@
net.minecraftforge.userdev.ClasspathLocator