Skip to content

Commit

Permalink
Add VirtualJar for generated packages (McModLauncher#54)
Browse files Browse the repository at this point in the history
This will let frameworks like mixin define additional packages in the
GAME layer for runtime generated classes with the following additional
code in a transformation service:
```java
    @OverRide
    public List<Resource> completeScan(final IModuleLayerManager layerManager) {
        try {
            return List.of(
                    new Resource(IModuleLayerManager.Layer.GAME, List.of(
                            new VirtualJar(
                                    "mixin_generated_classes",
                                    Path.of(getClass().getProtectionDomain().getCodeSource().getLocation().toURI()),
                                    Constants.SYNTHETIC_PACKAGE,
                                    Constants.SYNTHETIC_PACKAGE + ".args"))));
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
```

Fixes McModLauncher/modlauncher#90. Tested with a forgedev `@ModifyArgs`
mixin :)

---------

Co-authored-by: Matyrobbrt <[email protected]>
  • Loading branch information
Technici4n and Matyrobbrt authored Nov 23, 2023
1 parent 008028a commit 67e23de
Show file tree
Hide file tree
Showing 4 changed files with 186 additions and 1 deletion.
38 changes: 37 additions & 1 deletion src/main/java/cpw/mods/jarhandling/SecureJar.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
Expand Down Expand Up @@ -83,15 +85,45 @@ static SecureJar from(JarContents contents, JarMetadata metadata) {
*/
Path getRootPath();

/**
* All the functions that are necessary to turn a {@link SecureJar} into a module.
*/
interface ModuleDataProvider {
/**
* {@return the name of the module}
*/
String name();

/**
* {@return the descriptor of the module}
*/
ModuleDescriptor descriptor();

/**
* @see ModuleReference#location()
*/
@Nullable
URI uri();

/**
* @see ModuleReader#find(String)
*/
Optional<URI> findFile(String name);

/**
* @see ModuleReader#open(String)
*/
Optional<InputStream> open(final String name);

/**
* {@return the manifest of the jar}
*/
Manifest getManifest();

/**
* {@return the signers if the class name can be verified, or {@code null} otherwise}
*/
@Nullable
CodeSigner[] verifyAndGetSigners(String cname, byte[] bytes);
}

Expand Down Expand Up @@ -134,7 +166,11 @@ default Set<String> getPackages() {
* @deprecated Obtain via the {@link ModuleDescriptor} of the jar if you really need this.
*/
@Deprecated(forRemoval = true, since = "2.1.16")
List<Provider> getProviders();
default List<Provider> getProviders() {
return moduleDataProvider().descriptor().provides().stream()
.map(p -> new Provider(p.service(), p.providers()))
.toList();
}

/**
* @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
Expand Down
147 changes: 147 additions & 0 deletions src/main/java/cpw/mods/jarhandling/VirtualJar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package cpw.mods.jarhandling;

import cpw.mods.niofs.union.UnionFileSystem;
import cpw.mods.niofs.union.UnionFileSystemProvider;
import org.jetbrains.annotations.Nullable;

import java.io.InputStream;
import java.lang.module.ModuleDescriptor;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.spi.FileSystemProvider;
import java.security.CodeSigner;
import java.util.Optional;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

/**
* Implementation of {@link SecureJar} that does not actually contain any files,
* but still defines packages.
*
* <p>This can be used by frameworks that generate classes at runtime, in specific packages,
* and need to make a {@link SecureJar}-based module system implementation aware of these packages.
*/
public final class VirtualJar implements SecureJar {
/**
* Creates a new virtual jar.
*
* @param name the name of the virtual jar; will be used as the module name
* @param referencePath a path to an existing directory or jar file, for debugging and display purposes
* (for example a path to the real jar of the caller)
* @param packages the list of packages in this virtual jar
*/
public VirtualJar(String name, Path referencePath, String... packages) {
if (!Files.exists(referencePath)) {
throw new IllegalArgumentException("VirtualJar reference path " + referencePath + " must exist");
}

this.moduleDescriptor = ModuleDescriptor.newAutomaticModule(name)
.packages(Set.of(packages))
.build();
// Create a dummy file system from the reference path, with a filter that always returns false
this.dummyFileSystem = UFSP.newFileSystem((path, basePath) -> false, referencePath);
}

// Implementation details below
private static final UnionFileSystemProvider UFSP = (UnionFileSystemProvider) FileSystemProvider.installedProviders()
.stream()
.filter(fsp->fsp.getScheme().equals("union"))
.findFirst()
.orElseThrow(()->new IllegalStateException("Couldn't find UnionFileSystemProvider"));

private final ModuleDescriptor moduleDescriptor;
private final ModuleDataProvider moduleData = new VirtualJarModuleDataProvider();
private final UnionFileSystem dummyFileSystem;
private final Manifest manifest = new Manifest();

@Override
public ModuleDataProvider moduleDataProvider() {
return moduleData;
}

@Override
public Path getPrimaryPath() {
return dummyFileSystem.getPrimaryPath();
}

@Override
public @Nullable CodeSigner[] getManifestSigners() {
return null;
}

@Override
public Status verifyPath(Path path) {
return Status.NONE;
}

@Override
public Status getFileStatus(String name) {
return Status.NONE;
}

@Override
public @Nullable Attributes getTrustedManifestEntries(String name) {
return null;
}

@Override
public boolean hasSecurityData() {
return false;
}

@Override
public String name() {
return moduleDescriptor.name();
}

@Override
public Path getPath(String first, String... rest) {
return dummyFileSystem.getPath(first, rest);
}

@Override
public Path getRootPath() {
return dummyFileSystem.getRoot();
}

private class VirtualJarModuleDataProvider implements ModuleDataProvider {
@Override
public String name() {
return VirtualJar.this.name();
}

@Override
public ModuleDescriptor descriptor() {
return moduleDescriptor;
}

@Override
@Nullable
public URI uri() {
return null;
}

@Override
public Optional<URI> findFile(String name) {
return Optional.empty();
}

@Override
public Optional<InputStream> open(String name) {
return Optional.empty();
}

@Override
public Manifest getManifest() {
return manifest;
}

@Override
@Nullable
public CodeSigner[] verifyAndGetSigners(String cname, byte[] bytes) {
return null;
}
}
}
1 change: 1 addition & 0 deletions src/main/java/cpw/mods/jarhandling/impl/Jar.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ public Manifest getManifest() {
}

@Override
@Nullable
public CodeSigner[] verifyAndGetSigners(final String cname, final byte[] bytes) {
return jar.signingData.verifyAndGetSigners(jar.manifest, cname, bytes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ private Optional<StatusData> getData(final String name) {
return Optional.ofNullable(statusData.get(name));
}

@Nullable
synchronized CodeSigner[] verifyAndGetSigners(Manifest manifest, String name, byte[] bytes) {
if (!hasSecurityData()) return null;
if (statusData.containsKey(name)) return statusData.get(name).signers;
Expand Down

0 comments on commit 67e23de

Please sign in to comment.