Skip to content

Commit 67e23de

Browse files
Add VirtualJar for generated packages (#54)
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 <65940752+Matyrobbrt@users.noreply.github.com>
1 parent 008028a commit 67e23de

4 files changed

Lines changed: 186 additions & 1 deletion

File tree

src/main/java/cpw/mods/jarhandling/SecureJar.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.io.InputStream;
99
import java.io.UncheckedIOException;
1010
import java.lang.module.ModuleDescriptor;
11+
import java.lang.module.ModuleReader;
12+
import java.lang.module.ModuleReference;
1113
import java.net.URI;
1214
import java.nio.file.Files;
1315
import java.nio.file.Path;
@@ -83,15 +85,45 @@ static SecureJar from(JarContents contents, JarMetadata metadata) {
8385
*/
8486
Path getRootPath();
8587

88+
/**
89+
* All the functions that are necessary to turn a {@link SecureJar} into a module.
90+
*/
8691
interface ModuleDataProvider {
92+
/**
93+
* {@return the name of the module}
94+
*/
8795
String name();
96+
97+
/**
98+
* {@return the descriptor of the module}
99+
*/
88100
ModuleDescriptor descriptor();
101+
102+
/**
103+
* @see ModuleReference#location()
104+
*/
105+
@Nullable
89106
URI uri();
107+
108+
/**
109+
* @see ModuleReader#find(String)
110+
*/
90111
Optional<URI> findFile(String name);
112+
113+
/**
114+
* @see ModuleReader#open(String)
115+
*/
91116
Optional<InputStream> open(final String name);
92117

118+
/**
119+
* {@return the manifest of the jar}
120+
*/
93121
Manifest getManifest();
94122

123+
/**
124+
* {@return the signers if the class name can be verified, or {@code null} otherwise}
125+
*/
126+
@Nullable
95127
CodeSigner[] verifyAndGetSigners(String cname, byte[] bytes);
96128
}
97129

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

139175
/**
140176
* @deprecated Use {@link JarContentsBuilder} and {@link #from(JarContents)} instead.
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package cpw.mods.jarhandling;
2+
3+
import cpw.mods.niofs.union.UnionFileSystem;
4+
import cpw.mods.niofs.union.UnionFileSystemProvider;
5+
import org.jetbrains.annotations.Nullable;
6+
7+
import java.io.InputStream;
8+
import java.lang.module.ModuleDescriptor;
9+
import java.net.URI;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.nio.file.spi.FileSystemProvider;
13+
import java.security.CodeSigner;
14+
import java.util.Optional;
15+
import java.util.Set;
16+
import java.util.jar.Attributes;
17+
import java.util.jar.Manifest;
18+
19+
/**
20+
* Implementation of {@link SecureJar} that does not actually contain any files,
21+
* but still defines packages.
22+
*
23+
* <p>This can be used by frameworks that generate classes at runtime, in specific packages,
24+
* and need to make a {@link SecureJar}-based module system implementation aware of these packages.
25+
*/
26+
public final class VirtualJar implements SecureJar {
27+
/**
28+
* Creates a new virtual jar.
29+
*
30+
* @param name the name of the virtual jar; will be used as the module name
31+
* @param referencePath a path to an existing directory or jar file, for debugging and display purposes
32+
* (for example a path to the real jar of the caller)
33+
* @param packages the list of packages in this virtual jar
34+
*/
35+
public VirtualJar(String name, Path referencePath, String... packages) {
36+
if (!Files.exists(referencePath)) {
37+
throw new IllegalArgumentException("VirtualJar reference path " + referencePath + " must exist");
38+
}
39+
40+
this.moduleDescriptor = ModuleDescriptor.newAutomaticModule(name)
41+
.packages(Set.of(packages))
42+
.build();
43+
// Create a dummy file system from the reference path, with a filter that always returns false
44+
this.dummyFileSystem = UFSP.newFileSystem((path, basePath) -> false, referencePath);
45+
}
46+
47+
// Implementation details below
48+
private static final UnionFileSystemProvider UFSP = (UnionFileSystemProvider) FileSystemProvider.installedProviders()
49+
.stream()
50+
.filter(fsp->fsp.getScheme().equals("union"))
51+
.findFirst()
52+
.orElseThrow(()->new IllegalStateException("Couldn't find UnionFileSystemProvider"));
53+
54+
private final ModuleDescriptor moduleDescriptor;
55+
private final ModuleDataProvider moduleData = new VirtualJarModuleDataProvider();
56+
private final UnionFileSystem dummyFileSystem;
57+
private final Manifest manifest = new Manifest();
58+
59+
@Override
60+
public ModuleDataProvider moduleDataProvider() {
61+
return moduleData;
62+
}
63+
64+
@Override
65+
public Path getPrimaryPath() {
66+
return dummyFileSystem.getPrimaryPath();
67+
}
68+
69+
@Override
70+
public @Nullable CodeSigner[] getManifestSigners() {
71+
return null;
72+
}
73+
74+
@Override
75+
public Status verifyPath(Path path) {
76+
return Status.NONE;
77+
}
78+
79+
@Override
80+
public Status getFileStatus(String name) {
81+
return Status.NONE;
82+
}
83+
84+
@Override
85+
public @Nullable Attributes getTrustedManifestEntries(String name) {
86+
return null;
87+
}
88+
89+
@Override
90+
public boolean hasSecurityData() {
91+
return false;
92+
}
93+
94+
@Override
95+
public String name() {
96+
return moduleDescriptor.name();
97+
}
98+
99+
@Override
100+
public Path getPath(String first, String... rest) {
101+
return dummyFileSystem.getPath(first, rest);
102+
}
103+
104+
@Override
105+
public Path getRootPath() {
106+
return dummyFileSystem.getRoot();
107+
}
108+
109+
private class VirtualJarModuleDataProvider implements ModuleDataProvider {
110+
@Override
111+
public String name() {
112+
return VirtualJar.this.name();
113+
}
114+
115+
@Override
116+
public ModuleDescriptor descriptor() {
117+
return moduleDescriptor;
118+
}
119+
120+
@Override
121+
@Nullable
122+
public URI uri() {
123+
return null;
124+
}
125+
126+
@Override
127+
public Optional<URI> findFile(String name) {
128+
return Optional.empty();
129+
}
130+
131+
@Override
132+
public Optional<InputStream> open(String name) {
133+
return Optional.empty();
134+
}
135+
136+
@Override
137+
public Manifest getManifest() {
138+
return manifest;
139+
}
140+
141+
@Override
142+
@Nullable
143+
public CodeSigner[] verifyAndGetSigners(String cname, byte[] bytes) {
144+
return null;
145+
}
146+
}
147+
}

src/main/java/cpw/mods/jarhandling/impl/Jar.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ public Manifest getManifest() {
164164
}
165165

166166
@Override
167+
@Nullable
167168
public CodeSigner[] verifyAndGetSigners(final String cname, final byte[] bytes) {
168169
return jar.signingData.verifyAndGetSigners(jar.manifest, cname, bytes);
169170
}

src/main/java/cpw/mods/jarhandling/impl/JarSigningData.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ private Optional<StatusData> getData(final String name) {
9494
return Optional.ofNullable(statusData.get(name));
9595
}
9696

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

0 commit comments

Comments
 (0)