diff --git a/build.gradle b/build.gradle
index 41bcfe9..aa7259e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,7 +2,7 @@ plugins {
     id 'java-library'
     id 'eclipse'
     id 'com.github.ben-manes.versions' version '0.48.0'
-    id 'net.neoforged.gradleutils' version '3.0.0-alpha.4'
+    id 'net.neoforged.gradleutils' version '3.0.0'
     id 'org.gradlex.extra-java-module-info' version '1.4.2'
     id 'maven-publish'
 }
diff --git a/gradle/daemon-jvm.properties b/gradle/daemon-jvm.properties
new file mode 100644
index 0000000..ebc8101
--- /dev/null
+++ b/gradle/daemon-jvm.properties
@@ -0,0 +1,3 @@
+
+#This file is generated by updateDaemonJvm
+toolchainVersion=21
diff --git a/settings.gradle b/settings.gradle
index 08664b0..7c8cbff 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,5 @@
 plugins {
-    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.6.0'
+    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
 }
 
 rootProject.name = "modlauncher"
\ No newline at end of file
diff --git a/src/main/java/cpw/mods/modlauncher/ArgumentHandler.java b/src/main/java/cpw/mods/modlauncher/ArgumentHandler.java
index 3b2d735..0363c26 100644
--- a/src/main/java/cpw/mods/modlauncher/ArgumentHandler.java
+++ b/src/main/java/cpw/mods/modlauncher/ArgumentHandler.java
@@ -28,7 +28,7 @@
 import java.util.function.*;
 
 public class ArgumentHandler {
-    private String[] args;
+    private final String[] args;
     private OptionSet optionSet;
     private OptionSpec<String> profileOption;
     private OptionSpec<Path> gameDirOption;
@@ -37,16 +37,8 @@ public class ArgumentHandler {
     private OptionSpec<String> launchTarget;
     private OptionSpec<String> uuidOption;
 
-    record DiscoveryData(Path gameDir, String launchTarget, String[] arguments) {}
-
-    DiscoveryData setArgs(String[] args) {
+    public ArgumentHandler(String[] args) {
         this.args = args;
-        final OptionParser parser = new OptionParser();
-        final var gameDir = parser.accepts("gameDir", "Alternative game directory").withRequiredArg().withValuesConvertedBy(new PathConverter(PathProperties.DIRECTORY_EXISTING)).defaultsTo(Path.of("."));
-        final var launchTarget = parser.accepts("launchTarget", "LauncherService target to launch").withRequiredArg();
-        parser.allowsUnrecognizedOptions();
-        final OptionSet optionSet = parser.parse(args);
-        return new DiscoveryData(optionSet.valueOf(gameDir), optionSet.valueOf(launchTarget), args);
     }
 
     void processArguments(Environment env, Consumer<OptionParser> parserConsumer, BiConsumer<OptionSet, BiFunction<String, OptionSet, ITransformationService.OptionResult>> resultConsumer) {
diff --git a/src/main/java/cpw/mods/modlauncher/DiscoveryData.java b/src/main/java/cpw/mods/modlauncher/DiscoveryData.java
new file mode 100644
index 0000000..2b70701
--- /dev/null
+++ b/src/main/java/cpw/mods/modlauncher/DiscoveryData.java
@@ -0,0 +1,19 @@
+package cpw.mods.modlauncher;
+
+import joptsimple.OptionParser;
+import joptsimple.OptionSet;
+import joptsimple.util.PathConverter;
+import joptsimple.util.PathProperties;
+
+import java.nio.file.Path;
+
+public record DiscoveryData(Path gameDir, String launchTarget, String[] arguments) {
+    public static DiscoveryData create(String[] programArgs) {
+        final OptionParser parser = new OptionParser();
+        final var gameDir = parser.accepts("gameDir", "Alternative game directory").withRequiredArg().withValuesConvertedBy(new PathConverter(PathProperties.DIRECTORY_EXISTING)).defaultsTo(Path.of("."));
+        final var launchTarget = parser.accepts("launchTarget", "LauncherService target to launch").withRequiredArg();
+        parser.allowsUnrecognizedOptions();
+        final OptionSet optionSet = parser.parse(programArgs);
+        return new DiscoveryData(optionSet.valueOf(gameDir), optionSet.valueOf(launchTarget), programArgs);
+    }
+}
diff --git a/src/main/java/cpw/mods/modlauncher/Environment.java b/src/main/java/cpw/mods/modlauncher/Environment.java
index 1c09e34..b4310d1 100644
--- a/src/main/java/cpw/mods/modlauncher/Environment.java
+++ b/src/main/java/cpw/mods/modlauncher/Environment.java
@@ -28,12 +28,17 @@
  * Environment implementation class
  */
 public final class Environment implements IEnvironment {
-    private final TypesafeMap environment;
-    private final Launcher launcher;
+    private final TypesafeMap environment = new TypesafeMap(IEnvironment.class);
+    private final Function<String, Optional<ILaunchPluginService>> launchPlugins;
+    private final Function<String, Optional<ILaunchHandlerService>> launchService;
+    private final IModuleLayerManager moduleLayerHandler;
 
-    Environment(Launcher launcher) {
-        environment = new TypesafeMap(IEnvironment.class);
-        this.launcher = launcher;
+    public Environment(Function<String, Optional<ILaunchPluginService>> launchPlugins,
+                       Function<String, Optional<ILaunchHandlerService>> launchService,
+                       IModuleLayerManager moduleLayerHandler) {
+        this.launchPlugins = launchPlugins;
+        this.launchService = launchService;
+        this.moduleLayerHandler = moduleLayerHandler;
     }
 
     @Override
@@ -43,17 +48,17 @@ public final <T> Optional<T> getProperty(TypesafeMap.Key<T> key) {
 
     @Override
     public Optional<ILaunchPluginService> findLaunchPlugin(final String name) {
-        return launcher.findLaunchPlugin(name);
+        return launchPlugins.apply(name);
     }
 
     @Override
     public Optional<ILaunchHandlerService> findLaunchHandler(final String name) {
-        return launcher.findLaunchHandler(name);
+        return launchService.apply(name);
     }
 
     @Override
     public Optional<IModuleLayerManager> findModuleLayerManager() {
-        return launcher.findLayerManager();
+        return Optional.of(this.moduleLayerHandler);
     }
 
     @Override
diff --git a/src/main/java/cpw/mods/modlauncher/LaunchPluginHandler.java b/src/main/java/cpw/mods/modlauncher/LaunchPluginHandler.java
index f395314..cb0cff7 100644
--- a/src/main/java/cpw/mods/modlauncher/LaunchPluginHandler.java
+++ b/src/main/java/cpw/mods/modlauncher/LaunchPluginHandler.java
@@ -83,7 +83,7 @@ public EnumMap<ILaunchPluginService.Phase, List<ILaunchPluginService>> computeLa
         return phaseObjectEnumMap;
     }
 
-    void offerScanResultsToPlugins(List<SecureJar> scanResults) {
+    public void offerScanResultsToPlugins(List<SecureJar> scanResults) {
         plugins.forEach((n,p)->p.addResources(scanResults));
     }
 
@@ -102,7 +102,7 @@ int offerClassNodeToPlugins(final ILaunchPluginService.Phase phase, final List<I
         return flags;
     }
 
-    void announceLaunch(final TransformingClassLoader transformerLoader, final NamedPath[] specialPaths) {
+    public void announceLaunch(final TransformingClassLoader transformerLoader, final NamedPath[] specialPaths) {
         plugins.forEach((k, p)->p.initializeLaunch((s->transformerLoader.buildTransformedClassNodeFor(s, k)), specialPaths));
     }
 }
diff --git a/src/main/java/cpw/mods/modlauncher/LaunchServiceHandler.java b/src/main/java/cpw/mods/modlauncher/LaunchServiceHandler.java
index 00c882e..47c63cc 100644
--- a/src/main/java/cpw/mods/modlauncher/LaunchServiceHandler.java
+++ b/src/main/java/cpw/mods/modlauncher/LaunchServiceHandler.java
@@ -31,12 +31,16 @@
 /**
  * Identifies the launch target and dispatches to it
  */
-class LaunchServiceHandler {
+public class LaunchServiceHandler {
     private static final Logger LOGGER = LogManager.getLogger();
     private final Map<String, LaunchServiceHandlerDecorator> launchHandlerLookup;
 
     public LaunchServiceHandler(final ModuleLayerHandler layerHandler) {
-        this.launchHandlerLookup = ServiceLoaderUtils.streamServiceLoader(()->ServiceLoader.load(layerHandler.getLayer(IModuleLayerManager.Layer.BOOT).orElseThrow(), ILaunchHandlerService.class), sce -> LOGGER.fatal("Encountered serious error loading transformation service, expect problems", sce))
+        this(ServiceLoaderUtils.streamServiceLoader(()->ServiceLoader.load(layerHandler.getLayer(IModuleLayerManager.Layer.BOOT).orElseThrow(), ILaunchHandlerService.class), sce -> LOGGER.fatal("Encountered serious error loading transformation service, expect problems", sce)));
+    }
+
+    public LaunchServiceHandler(Stream<ILaunchHandlerService> launchHandlers) {
+        this.launchHandlerLookup = launchHandlers
                 .collect(Collectors.toMap(ILaunchHandlerService::name, LaunchServiceHandlerDecorator::new));
         LOGGER.debug(MODLAUNCHER,"Found launch services [{}]", () -> String.join(",",launchHandlerLookup.keySet()));
     }
@@ -45,7 +49,7 @@ public Optional<ILaunchHandlerService> findLaunchHandler(final String name) {
         return Optional.ofNullable(launchHandlerLookup.getOrDefault(name, null)).map(LaunchServiceHandlerDecorator::service);
     }
 
-    private void launch(String target, String[] arguments, ModuleLayer gameLayer, TransformingClassLoader classLoader, final LaunchPluginHandler launchPluginHandler) {
+    public void launch(String target, String[] arguments, ModuleLayer gameLayer, TransformingClassLoader classLoader, final LaunchPluginHandler launchPluginHandler) {
         final LaunchServiceHandlerDecorator launchServiceHandlerDecorator = launchHandlerLookup.get(target);
         final NamedPath[] paths = launchServiceHandlerDecorator.service().getPaths();
         launchPluginHandler.announceLaunch(classLoader, paths);
@@ -71,7 +75,7 @@ public void launch(ArgumentHandler argumentHandler, ModuleLayer gameLayer, Trans
         launch(launchTarget, args, gameLayer, classLoader, launchPluginHandler);
     }
 
-    void validateLaunchTarget(final ArgumentHandler argumentHandler) {
+    public void validateLaunchTarget(final ArgumentHandler argumentHandler) {
         if (!launchHandlerLookup.containsKey(argumentHandler.getLaunchTarget())) {
             LOGGER.error(MODLAUNCHER, "Cannot find launch target {}, unable to launch",
                     argumentHandler.getLaunchTarget());
diff --git a/src/main/java/cpw/mods/modlauncher/Launcher.java b/src/main/java/cpw/mods/modlauncher/Launcher.java
index a247d65..4828957 100644
--- a/src/main/java/cpw/mods/modlauncher/Launcher.java
+++ b/src/main/java/cpw/mods/modlauncher/Launcher.java
@@ -21,7 +21,6 @@
 import cpw.mods.jarhandling.SecureJar;
 import cpw.mods.modlauncher.api.*;
 import org.apache.logging.log4j.LogManager;
-import cpw.mods.modlauncher.serviceapi.ILaunchPluginService;
 
 import java.util.*;
 import java.util.stream.Collectors;
@@ -38,26 +37,43 @@ public class Launcher {
     private final TransformationServicesHandler transformationServicesHandler;
     private final Environment environment;
     private final TransformStore transformStore;
-    private final ArgumentHandler argumentHandler;
     private final LaunchServiceHandler launchService;
     private final LaunchPluginHandler launchPlugins;
     private final ModuleLayerHandler moduleLayerHandler;
     private TransformingClassLoader classLoader;
+    private ArgumentHandler argumentHandler;
 
-    private Launcher() {
-        INSTANCE = this;
+    public Launcher() {
         LogManager.getLogger().info(MODLAUNCHER,"ModLauncher {} starting: java version {} by {}; OS {} arch {} version {}", ()->IEnvironment.class.getPackage().getImplementationVersion(),  () -> System.getProperty("java.version"), ()->System.getProperty("java.vendor"), ()->System.getProperty("os.name"), ()->System.getProperty("os.arch"), ()->System.getProperty("os.version"));
         this.moduleLayerHandler = new ModuleLayerHandler();
         this.launchService = new LaunchServiceHandler(this.moduleLayerHandler);
         this.blackboard = new TypesafeMap();
-        this.environment = new Environment(this);
-        environment.computePropertyIfAbsent(IEnvironment.Keys.MLSPEC_VERSION.get(), s->IEnvironment.class.getPackage().getSpecificationVersion());
-        environment.computePropertyIfAbsent(IEnvironment.Keys.MLIMPL_VERSION.get(), s->IEnvironment.class.getPackage().getImplementationVersion());
-        environment.computePropertyIfAbsent(IEnvironment.Keys.MODLIST.get(), s->new ArrayList<>());
         this.transformStore = new TransformStore();
         this.transformationServicesHandler = new TransformationServicesHandler(this.transformStore, this.moduleLayerHandler);
-        this.argumentHandler = new ArgumentHandler();
         this.launchPlugins = new LaunchPluginHandler(this.moduleLayerHandler);
+        this.environment = new Environment(
+                launchPlugins::get,
+                launchService::findLaunchHandler,
+                moduleLayerHandler
+        );
+        environment.computePropertyIfAbsent(IEnvironment.Keys.MLSPEC_VERSION.get(), s->IEnvironment.class.getPackage().getSpecificationVersion());
+        environment.computePropertyIfAbsent(IEnvironment.Keys.MLIMPL_VERSION.get(), s->IEnvironment.class.getPackage().getImplementationVersion());
+        environment.computePropertyIfAbsent(IEnvironment.Keys.MODLIST.get(), s->new ArrayList<>());
+    }
+
+    public Launcher(TransformationServicesHandler transformationServicesHandler,
+                    Environment environment,
+                    TransformStore transformStore,
+                    LaunchServiceHandler launchService,
+                    LaunchPluginHandler launchPlugins,
+                    ModuleLayerHandler moduleLayerHandler) {
+        this.blackboard = new TypesafeMap();
+        this.transformationServicesHandler = transformationServicesHandler;
+        this.environment = environment;
+        this.transformStore = transformStore;
+        this.launchService = launchService;
+        this.launchPlugins = launchPlugins;
+        this.moduleLayerHandler = moduleLayerHandler;
     }
 
     public static void main(String... args) {
@@ -71,7 +87,9 @@ public static void main(String... args) {
         }
         LogManager.getLogger().info(MODLAUNCHER,"ModLauncher running: args {}", () -> LaunchServiceHandler.hideAccessToken(args));
         LogManager.getLogger().info(MODLAUNCHER, "JVM identified as {} {} {}", props.getProperty("java.vm.vendor"), props.getProperty("java.vm.name"), props.getProperty("java.vm.version"));
-        new Launcher().run(args);
+        var launcher = new Launcher();
+        INSTANCE = launcher;
+        launcher.run(args);
     }
 
     public final TypesafeMap blackboard() {
@@ -79,8 +97,8 @@ public final TypesafeMap blackboard() {
     }
 
     private void run(String... args) {
-        final ArgumentHandler.DiscoveryData discoveryData = this.argumentHandler.setArgs(args);
-        this.transformationServicesHandler.discoverServices(discoveryData);
+        this.argumentHandler = new ArgumentHandler(args);
+        this.transformationServicesHandler.discoverServices(DiscoveryData.create(args));
         final var scanResults = this.transformationServicesHandler.initializeTransformationServices(this.argumentHandler, this.environment)
                 .stream().collect(Collectors.groupingBy(ITransformationService.Resource::target));
         scanResults.getOrDefault(IModuleLayerManager.Layer.PLUGIN, List.of())
@@ -107,14 +125,6 @@ public Environment environment() {
         return this.environment;
     }
 
-    Optional<ILaunchPluginService> findLaunchPlugin(final String name) {
-        return launchPlugins.get(name);
-    }
-
-    Optional<ILaunchHandlerService> findLaunchHandler(final String name) {
-        return launchService.findLaunchHandler(name);
-    }
-
     public Optional<IModuleLayerManager> findLayerManager() {
         return Optional.ofNullable(this.moduleLayerHandler);
     }
diff --git a/src/main/java/cpw/mods/modlauncher/ModuleLayerHandler.java b/src/main/java/cpw/mods/modlauncher/ModuleLayerHandler.java
index 4bb7bfb..2275877 100644
--- a/src/main/java/cpw/mods/modlauncher/ModuleLayerHandler.java
+++ b/src/main/java/cpw/mods/modlauncher/ModuleLayerHandler.java
@@ -23,6 +23,7 @@
 import cpw.mods.jarhandling.SecureJar;
 import cpw.mods.modlauncher.api.IModuleLayerManager;
 import cpw.mods.modlauncher.api.NamedPath;
+import org.jetbrains.annotations.Nullable;
 
 import java.lang.module.Configuration;
 import java.lang.module.ModuleFinder;
@@ -31,7 +32,7 @@
 import java.util.function.Consumer;
 
 public final class ModuleLayerHandler implements IModuleLayerManager {
-    record LayerInfo(ModuleLayer layer, ModuleClassLoader cl) {}
+    public record LayerInfo(ModuleLayer layer, ModuleClassLoader cl) {}
 
     private record PathOrJar(NamedPath path, SecureJar jar) {
         static PathOrJar from(SecureJar jar) {
@@ -48,16 +49,21 @@ SecureJar build() {
     private final EnumMap<Layer, List<PathOrJar>> layers = new EnumMap<>(Layer.class);
     private final EnumMap<Layer, LayerInfo> completedLayers = new EnumMap<>(Layer.class);
 
-    ModuleLayerHandler() {
+    public ModuleLayerHandler() {
+        this(null);
+    }
+
+    public ModuleLayerHandler(@Nullable ClassLoader parentLoader) {
         ClassLoader classLoader = getClass().getClassLoader();
         // Create a new ModuleClassLoader from the boot module layer if it doesn't exist already.
         // This allows us to launch without BootstrapLauncher.
         ModuleClassLoader cl = classLoader instanceof ModuleClassLoader moduleCl ? moduleCl
-            : new ModuleClassLoader("BOOT", ModuleLayer.boot().configuration(), List.of());
-        completedLayers.put(Layer.BOOT, new LayerInfo(getClass().getModule().getLayer(), cl));
+            : new ModuleClassLoader("BOOT", ModuleLayer.boot().configuration(), List.of(), parentLoader);
+        var ownLayer = Objects.requireNonNullElse(getClass().getModule().getLayer(), ModuleLayer.boot());
+        completedLayers.put(Layer.BOOT, new LayerInfo(ownLayer, cl));
     }
 
-    void addToLayer(final Layer layer, final SecureJar jar) {
+    public void addToLayer(final Layer layer, final SecureJar jar) {
         if (completedLayers.containsKey(layer)) throw new IllegalStateException("Layer already populated");
         layers.computeIfAbsent(layer, l->new ArrayList<>()).add(PathOrJar.from(jar));
     }
diff --git a/src/main/java/cpw/mods/modlauncher/TransformStore.java b/src/main/java/cpw/mods/modlauncher/TransformStore.java
index fd52cb3..0608b2c 100644
--- a/src/main/java/cpw/mods/modlauncher/TransformStore.java
+++ b/src/main/java/cpw/mods/modlauncher/TransformStore.java
@@ -24,6 +24,7 @@
 import org.objectweb.asm.tree.*;
 
 import java.util.*;
+import java.util.stream.Collectors;
 
 import static cpw.mods.modlauncher.LogMarkers.*;
 
@@ -41,30 +42,49 @@ public TransformStore() {
             transformers.put(type, new TransformList<>(type.getNodeType()));
     }
 
-    List<ITransformer<FieldNode>> getTransformersFor(String className, FieldNode field) {
+    public List<ITransformer<FieldNode>> getTransformersFor(String className, FieldNode field) {
         TransformTargetLabel tl = new TransformTargetLabel(className, field.name);
         TransformList<FieldNode> transformerlist = TargetType.FIELD.get(this.transformers);
         return transformerlist.getTransformersForLabel(tl);
     }
 
-    List<ITransformer<MethodNode>> getTransformersFor(String className, MethodNode method) {
+    public List<ITransformer<MethodNode>> getTransformersFor(String className, MethodNode method) {
         TransformTargetLabel tl = new TransformTargetLabel(className, method.name, method.desc);
         TransformList<MethodNode> transformerlist = TargetType.METHOD.get(this.transformers);
         return transformerlist.getTransformersForLabel(tl);
     }
 
-    List<ITransformer<ClassNode>> getTransformersFor(String className, TargetType<ClassNode> classType) {
+    public List<ITransformer<ClassNode>> getTransformersFor(String className, TargetType<ClassNode> classType) {
         TransformTargetLabel tl = new TransformTargetLabel(className, classType);
         TransformList<ClassNode> transformerlist = classType.get(this.transformers);
         return transformerlist.getTransformersForLabel(tl);
     }
 
+    public void addTransformer(ITransformer<?> xform, ITransformationService owner) {
+        final TargetType<?> targetType = xform.getTargetType();
+        Objects.requireNonNull(targetType, "Transformer type must not be null");
+        final Set<? extends ITransformer.Target<?>> targets = xform.targets();
+        if (!targets.isEmpty()) {
+            final Map<TargetType<?>, List<TransformTargetLabel>> targetTypeListMap = targets.stream()
+                    .map(TransformTargetLabel::new)
+                    .collect(Collectors.groupingBy(TransformTargetLabel::getTargetType));
+            if (targetTypeListMap.keySet().size() > 1 || !targetTypeListMap.containsKey(targetType)) {
+                LOGGER.error("Invalid target {} for transformer {}", targetType, xform);
+                throw new IllegalArgumentException("The transformer contains invalid targets");
+            }
+            targetTypeListMap.values()
+                    .stream()
+                    .flatMap(Collection::stream)
+                    .forEach(target -> addTransformer(target, xform, owner));
+        }
+    }
+
     @SuppressWarnings("unchecked")
-    <T> void addTransformer(TransformTargetLabel targetLabel, ITransformer<T> transformer, ITransformationService service) {
+    public <T> void addTransformer(TransformTargetLabel targetLabel, ITransformer<T> transformer, ITransformationService owner) {
         LOGGER.debug(MODLAUNCHER,"Adding transformer {} to {}", () -> transformer, () -> targetLabel);
         classNeedsTransforming.add(targetLabel.getClassName().getInternalName());
         final TransformList<T> transformList = (TransformList<T>) this.transformers.get(targetLabel.getTargetType());
-        transformList.addTransformer(targetLabel, new TransformerHolder<>(transformer, service));
+        transformList.addTransformer(targetLabel, new TransformerHolder<>(transformer, owner));
     }
 
     /**
diff --git a/src/main/java/cpw/mods/modlauncher/TransformTargetLabel.java b/src/main/java/cpw/mods/modlauncher/TransformTargetLabel.java
index df9ac70..109b16d 100644
--- a/src/main/java/cpw/mods/modlauncher/TransformTargetLabel.java
+++ b/src/main/java/cpw/mods/modlauncher/TransformTargetLabel.java
@@ -74,7 +74,7 @@ public final Type getElementDescriptor() {
         return this.elementDescriptor;
     }
 
-    final TargetType<?> getTargetType() {
+    public TargetType<?> getTargetType() {
         return this.labelType;
     }
 
diff --git a/src/main/java/cpw/mods/modlauncher/TransformationServiceDecorator.java b/src/main/java/cpw/mods/modlauncher/TransformationServiceDecorator.java
index 404a51e..88f0f00 100644
--- a/src/main/java/cpw/mods/modlauncher/TransformationServiceDecorator.java
+++ b/src/main/java/cpw/mods/modlauncher/TransformationServiceDecorator.java
@@ -24,7 +24,6 @@
 import org.jetbrains.annotations.VisibleForTesting;
 
 import java.util.*;
-import java.util.stream.*;
 
 import static cpw.mods.modlauncher.LogMarkers.*;
 
@@ -67,24 +66,9 @@ public void gatherTransformers(TransformStore transformStore) {
         LOGGER.debug(MODLAUNCHER,"Initializing transformers for transformation service {}", this.service::name);
         final List<? extends ITransformer<?>> transformers = this.service.transformers();
         Objects.requireNonNull(transformers, "The transformers list should not be null");
-        transformers.forEach(xform -> {
-            final TargetType<?> targetType = xform.getTargetType();
-            Objects.requireNonNull(targetType, "Transformer type must not be null");
-            final Set<? extends ITransformer.Target<?>> targets = xform.targets();
-            if (!targets.isEmpty()) {
-                final Map<TargetType<?>, List<TransformTargetLabel>> targetTypeListMap = targets.stream()
-                    .map(TransformTargetLabel::new)
-                    .collect(Collectors.groupingBy(TransformTargetLabel::getTargetType));
-                if (targetTypeListMap.keySet().size() > 1 || !targetTypeListMap.containsKey(targetType)) {
-                    LOGGER.error(MODLAUNCHER,"Invalid target {} for transformer {}", targetType, xform);
-                    throw new IllegalArgumentException("The transformer contains invalid targets");
-                }
-                targetTypeListMap.values()
-                    .stream()
-                    .flatMap(Collection::stream)
-                    .forEach(target -> transformStore.addTransformer(target, xform, service));
-            }
-        });
+        for (ITransformer<?> xform : transformers) {
+            transformStore.addTransformer(xform, service);
+        }
         LOGGER.debug(MODLAUNCHER,"Initialized transformers for transformation service {}", this.service::name);
     }
 
diff --git a/src/main/java/cpw/mods/modlauncher/TransformationServicesHandler.java b/src/main/java/cpw/mods/modlauncher/TransformationServicesHandler.java
index 6b4209e..ed7be6a 100644
--- a/src/main/java/cpw/mods/modlauncher/TransformationServicesHandler.java
+++ b/src/main/java/cpw/mods/modlauncher/TransformationServicesHandler.java
@@ -31,18 +31,24 @@
 
 import static cpw.mods.modlauncher.LogMarkers.*;
 
-class TransformationServicesHandler {
+public class TransformationServicesHandler {
     private static final Logger LOGGER = LogManager.getLogger();
     private Map<String, TransformationServiceDecorator> serviceLookup;
     private final TransformStore transformStore;
     private final ModuleLayerHandler layerHandler;
 
-    TransformationServicesHandler(TransformStore transformStore, ModuleLayerHandler layerHandler) {
+    public TransformationServicesHandler(TransformStore transformStore, ModuleLayerHandler layerHandler) {
         this.transformStore = transformStore;
         this.layerHandler = layerHandler;
     }
 
-    List<ITransformationService.Resource> initializeTransformationServices(ArgumentHandler argumentHandler, Environment environment) {
+    public TransformationServicesHandler(TransformStore transformStore, ModuleLayerHandler layerHandler, Environment environment, Collection<ITransformationService> services) {
+        this.transformStore = transformStore;
+        this.layerHandler = layerHandler;
+        setTransformationServices(services, environment);
+    }
+
+    public List<ITransformationService.Resource> initializeTransformationServices(ArgumentHandler argumentHandler, Environment environment) {
         loadTransformationServices(environment);
         validateTransformationServices();
         processArguments(argumentHandler, environment);
@@ -50,7 +56,9 @@ List<ITransformationService.Resource> initializeTransformationServices(ArgumentH
         return runScanningTransformationServices(environment);
     }
 
-    TransformingClassLoader buildTransformingClassLoader(final LaunchPluginHandler pluginHandler, final Environment environment, final ModuleLayerHandler layerHandler) {
+    public TransformingClassLoader buildTransformingClassLoader(final LaunchPluginHandler pluginHandler,
+                                                                final Environment environment,
+                                                                final ModuleLayerHandler layerHandler) {
         final var layerInfo = layerHandler.buildLayer(IModuleLayerManager.Layer.GAME, (cf, parents)->new TransformingClassLoader(transformStore, pluginHandler, environment, cf, parents));
         layerHandler.updateLayer(IModuleLayerManager.Layer.PLUGIN, li->li.cl().setFallbackClassLoader(layerInfo.cl()));
         return (TransformingClassLoader) layerInfo.cl();
@@ -74,7 +82,7 @@ private void offerArgumentResultsToServices(OptionSet optionSet, BiFunction<Stri
                 .forEach(service -> service.argumentValues(resultHandler.apply(service.name(), optionSet)));
     }
 
-    void initialiseServiceTransformers() {
+    public void initialiseServiceTransformers() {
         LOGGER.debug(MODLAUNCHER,"Transformation services loading transformers");
 
         serviceLookup.values().forEach(s -> s.gatherTransformers(transformStore));
@@ -110,7 +118,7 @@ private void loadTransformationServices(Environment environment) {
         serviceLookup.values().forEach(s -> s.onLoad(environment, serviceLookup.keySet()));
     }
 
-    void discoverServices(final ArgumentHandler.DiscoveryData discoveryData) {
+    public void discoverServices(final DiscoveryData discoveryData) {
         LOGGER.debug(MODLAUNCHER, "Discovering transformation services");
         var bootLayer = layerHandler.getLayer(IModuleLayerManager.Layer.BOOT).orElseThrow();
         var earlyDiscoveryServices = ServiceLoaderUtils.streamServiceLoader(()->ServiceLoader.load(bootLayer, ITransformerDiscoveryService.class),  sce -> LOGGER.fatal(MODLAUNCHER, "Encountered serious error loading transformation discoverer, expect problems", sce))
@@ -123,14 +131,18 @@ void discoverServices(final ArgumentHandler.DiscoveryData discoveryData) {
         additionalPaths.forEach(np->layerHandler.addToLayer(IModuleLayerManager.Layer.SERVICE, np));
         var serviceLayer = layerHandler.buildLayer(IModuleLayerManager.Layer.SERVICE);
         earlyDiscoveryServices.forEach(s->s.earlyInitialization(discoveryData.launchTarget(), discoveryData.arguments()));
-        serviceLookup = ServiceLoaderUtils.streamServiceLoader(()->ServiceLoader.load(serviceLayer.layer(), ITransformationService.class), sce -> LOGGER.fatal(MODLAUNCHER, "Encountered serious error loading transformation service, expect problems", sce))
-                .collect(Collectors.toMap(ITransformationService::name, TransformationServiceDecorator::new));
+        setTransformationServices(ServiceLoaderUtils.streamServiceLoader(()->ServiceLoader.load(serviceLayer.layer(), ITransformationService.class), sce -> LOGGER.fatal(MODLAUNCHER, "Encountered serious error loading transformation service, expect problems", sce))
+                .toList(), Launcher.INSTANCE.environment());
+    }
+
+    private void setTransformationServices(Collection<ITransformationService> services, Environment environment) {
+        serviceLookup = services.stream().collect(Collectors.toMap(ITransformationService::name, TransformationServiceDecorator::new));
         var modlist = serviceLookup.entrySet().stream().map(e->Map.of(
                 "name", e.getKey(),
                 "type", "TRANSFORMATIONSERVICE",
                 "file", ServiceLoaderUtils.fileNameFor(e.getValue().getClass())
                 )).toList();
-        Launcher.INSTANCE.environment().getProperty(IEnvironment.Keys.MODLIST.get()).ifPresent(ml->ml.addAll(modlist));
+        environment.getProperty(IEnvironment.Keys.MODLIST.get()).ifPresent(ml->ml.addAll(modlist));
         LOGGER.debug(MODLAUNCHER,"Found transformer services : [{}]", () -> String.join(",",serviceLookup.keySet()));
     }
 
diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF
deleted file mode 100644
index 5a898d3..0000000
--- a/src/main/resources/META-INF/MANIFEST.MF
+++ /dev/null
@@ -1,26 +0,0 @@
-Manifest-Version: 1.0
-
-Name: cpw/mods/modlauncher/api/
-Specification-Title: modlauncher
-Specification-Vendor: forge
-Specification-Version: 7.0
-Implementation-Title: modlauncher
-Implementation-Version: 9.0.1+0+master.09c65e8
-Implementation-Vendor: forge
-Implementation-Timestamp: 2021-06-09T01:29:30.431684967Z
-Git-Commit: 09c65e8
-Git-Branch: master
-Build-Number: 0
-
-Name: cpw/mods/modlauncher/cpw.mods.modlauncher.serviceapi/
-Specification-Title: modlauncherserviceapi
-Specification-Vendor: forge
-Specification-Version: 7.0
-Implementation-Title: modlauncher
-Implementation-Version: 9.0.1+0+master.09c65e8
-Implementation-Vendor: forge
-Implementation-Timestamp: 2021-06-09T01:29:30.433729643Z
-Git-Commit: 09c65e8
-Git-Branch: master
-Build-Number: 0
-
diff --git a/src/main/resources/cpw.mods.modlauncher.resourceroot b/src/main/resources/cpw.mods.modlauncher.resourceroot
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
index 78aa13c..78664cb 100644
--- a/src/main/resources/log4j2.xml
+++ b/src/main/resources/log4j2.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="warn" packages="cpw.mods.modlauncher.log">
+<Configuration status="warn">
     <filters>
         <MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/>
         <MarkerFilter marker="CLASSLOADING" onMatch="DENY" onMismatch="NEUTRAL"/>
diff --git a/src/test/java/cpw/mods/modlauncher/test/TransformingClassLoaderTests.java b/src/test/java/cpw/mods/modlauncher/test/TransformingClassLoaderTests.java
index ac0d125..e55719d 100644
--- a/src/test/java/cpw/mods/modlauncher/test/TransformingClassLoaderTests.java
+++ b/src/test/java/cpw/mods/modlauncher/test/TransformingClassLoaderTests.java
@@ -33,6 +33,7 @@
 import java.lang.reflect.Constructor;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -56,16 +57,19 @@ public List<? extends ITransformer<?>> transformers() {
         };
 
         TransformStore transformStore = new TransformStore();
-        ModuleLayerHandler layerHandler = Whitebox.invokeConstructor(ModuleLayerHandler.class);
+        ModuleLayerHandler layerHandler = new ModuleLayerHandler();
         LaunchPluginHandler lph = new LaunchPluginHandler(layerHandler);
         TransformationServiceDecorator sd = Whitebox.invokeConstructor(TransformationServiceDecorator.class, mockTransformerService);
         sd.gatherTransformers(transformStore);
         
-        Environment environment = Whitebox.invokeConstructor(Environment.class, new Class[]{ Launcher.class }, new Object[]{ null });
+        Environment environment = new Environment(
+                s -> Optional.empty(),
+                s -> Optional.empty(),
+                layerHandler
+        );
         new TypesafeMap(IEnvironment.class);
-        Constructor<TransformingClassLoader> constructor = Whitebox.getConstructor(TransformingClassLoader.class, TransformStore.class, LaunchPluginHandler.class, Environment.class, Configuration.class, List.class);
         Configuration configuration = createTestJarsConfiguration();
-        TransformingClassLoader tcl = constructor.newInstance(transformStore, lph, environment, configuration, List.of(ModuleLayer.boot()));
+        TransformingClassLoader tcl = new TransformingClassLoader(transformStore, lph, environment, configuration, List.of(ModuleLayer.boot()));
         ModuleLayer.boot().defineModules(configuration, s -> tcl);
         
         final Class<?> aClass = Class.forName(TARGET_CLASS, true, tcl);
diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml
index 2bd065a..a43313e 100644
--- a/src/test/resources/log4j2.xml
+++ b/src/test/resources/log4j2.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="warn" packages="cpw.mods.modlauncher.log">
+<Configuration status="warn">
     <filters>
         <MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY" onMismatch="NEUTRAL"/>
         <MarkerFilter marker="CLASSLOADING" onMatch="DENY" onMismatch="NEUTRAL"/>