1
1
package dev .su5ed .sinytra .connector .locator ;
2
2
3
+ import com .electronwill .nightconfig .core .file .FileConfig ;
3
4
import com .google .common .collect .HashMultimap ;
4
5
import com .google .common .collect .Multimap ;
5
6
import com .mojang .logging .LogUtils ;
8
9
import dev .su5ed .sinytra .connector .loader .ConnectorEarlyLoader ;
9
10
import dev .su5ed .sinytra .connector .loader .ConnectorLoaderModMetadata ;
10
11
import dev .su5ed .sinytra .connector .transformer .jar .JarTransformer ;
12
+ import net .fabricmc .loader .api .FabricLoader ;
11
13
import net .fabricmc .loader .impl .metadata .NestedJarEntry ;
12
14
import net .minecraftforge .fml .loading .ClasspathLocatorUtils ;
13
15
import net .minecraftforge .fml .loading .EarlyLoadingException ;
19
21
import net .minecraftforge .fml .loading .moddiscovery .AbstractJarFileModProvider ;
20
22
import net .minecraftforge .fml .loading .moddiscovery .ModFile ;
21
23
import net .minecraftforge .fml .loading .moddiscovery .ModJarMetadata ;
24
+ import net .minecraftforge .fml .loading .moddiscovery .NightConfigWrapper ;
22
25
import net .minecraftforge .fml .loading .progress .StartupNotificationManager ;
23
26
import net .minecraftforge .forgespi .language .IModInfo ;
24
27
import net .minecraftforge .forgespi .locating .IDependencyLocator ;
60
63
public class ConnectorLocator extends AbstractJarFileModProvider implements IDependencyLocator {
61
64
private static final String NAME = "connector_locator" ;
62
65
private static final String SUFFIX = ".jar" ;
66
+ private static final String PLACEHOLDER_PROPERTY = "connector:placeholder" ;
63
67
64
68
private static final Logger LOGGER = LogUtils .getLogger ();
65
69
private static final MethodHandle MJM_INIT = uncheck (() -> MethodHandles .privateLookupIn (ModJarMetadata .class , MethodHandles .lookup ()).findConstructor (ModJarMetadata .class , MethodType .methodType (void .class )));
@@ -86,58 +90,65 @@ public List<IModFile> scanMods(Iterable<IModFile> loadedMods) {
86
90
return List .of ();
87
91
}
88
92
89
- private List <IModFile > locateFabricMods (Iterable <IModFile > loadedMods ) {
93
+ private List <IModFile > locateFabricMods (Iterable <IModFile > discoveredMods ) {
90
94
LOGGER .debug (SCAN , "Scanning mods dir {} for mods" , FMLPaths .MODSDIR .get ());
91
95
Path tempDir = ConnectorUtil .CONNECTOR_FOLDER .resolve ("temp" );
92
96
// Get all existing mod ids
93
- Collection <SimpleModInfo > loadedModInfos = StreamSupport .stream (loadedMods .spliterator (), false )
97
+ Collection <SimpleModInfo > loadedModInfos = StreamSupport .stream (discoveredMods .spliterator (), false )
94
98
.flatMap (modFile -> Optional .ofNullable (modFile .getModFileInfo ()).stream ())
95
99
.flatMap (modFileInfo -> {
96
100
IModFile modFile = modFileInfo .getFile ();
97
101
List <IModInfo > modInfos = modFileInfo .getMods ();
102
+ // Ignore placeholder mods
103
+ if (modFileInfo .getFileProperties ().containsKey (PLACEHOLDER_PROPERTY )) {
104
+ // Set mod version to 0.0 to prioritize the Fabric mod when FML resolves duplicates
105
+ modInfos .forEach (mod -> mod .getVersion ().parseVersion ("0.0" ));
106
+ return Stream .empty ();
107
+ }
98
108
if (!modInfos .isEmpty ()) {
99
109
return modInfos .stream ().map (modInfo -> new SimpleModInfo (modInfo .getModId (), modInfo .getVersion (), false , modFile ));
100
110
}
101
111
String version = modFileInfo .getFile ().getSecureJar ().moduleDataProvider ().descriptor ().version ().map (ModuleDescriptor .Version ::toString ).orElse ("0.0" );
102
112
return Stream .of (new SimpleModInfo (modFileInfo .moduleName (), new DefaultArtifactVersion (version ), true , modFile ));
103
113
})
104
114
.toList ();
115
+ Collection <IModFile > loadedModFiles = loadedModInfos .stream ().map (SimpleModInfo ::origin ).toList ();
105
116
Collection <String > loadedModIds = loadedModInfos .stream ().filter (mod -> !mod .library ()).map (SimpleModInfo ::modid ).collect (Collectors .toUnmodifiableSet ());
106
117
// Discover fabric mod jars
107
118
List <Path > excluded = ModDirTransformerDiscoverer .allExcluded ();
108
119
List <JarTransformer .TransformableJar > discoveredJars = Stream .of (scanModsDir (excluded ), scanClasspath (), scanFromArguments (excluded )).flatMap (s -> s )
109
120
.map (rethrowFunction (p -> cacheTransformableJar (p .toFile ())))
110
121
.filter (jar -> {
111
- String modid = jar .modPath ().metadata ().modMetadata (). getId ();
112
- return !shouldIgnoreMod (modid , loadedModIds );
122
+ ConnectorLoaderModMetadata metadata = jar .modPath ().metadata ().modMetadata ();
123
+ return !shouldIgnoreMod (metadata , loadedModIds );
113
124
})
114
125
.toList ();
115
126
Multimap <JarTransformer .TransformableJar , JarTransformer .TransformableJar > parentToChildren = HashMultimap .create ();
116
127
// Discover fabric nested mod jars
117
128
List <JarTransformer .TransformableJar > discoveredNestedJars = discoveredJars .stream ()
118
129
.flatMap (jar -> {
119
130
ConnectorLoaderModMetadata metadata = jar .modPath ().metadata ().modMetadata ();
120
- return shouldIgnoreMod (metadata . getId () , loadedModIds ) ? Stream .empty () : discoverNestedJarsRecursive (tempDir , jar , metadata .getJars (), parentToChildren , loadedModIds );
131
+ return shouldIgnoreMod (metadata , loadedModIds ) ? Stream .empty () : discoverNestedJarsRecursive (tempDir , jar , metadata .getJars (), parentToChildren , loadedModIds );
121
132
})
122
133
.toList ();
123
134
// Collect mods that are (likely) going to be excluded by FML's UniqueModListBuilder. Exclude them from global split package filtering
124
135
Collection <? super IModFile > ignoredModFiles = new ArrayList <>();
125
136
// Remove mods loaded by FML
126
137
List <JarTransformer .TransformableJar > uniqueJars = handleDuplicateMods (discoveredJars , discoveredNestedJars , loadedModInfos , ignoredModFiles );
127
138
// Ensure we have all required dependencies before transforming
128
- List <JarTransformer .TransformableJar > candidates = DependencyResolver .resolveDependencies (uniqueJars , parentToChildren , loadedMods );
139
+ List <JarTransformer .TransformableJar > candidates = DependencyResolver .resolveDependencies (uniqueJars , parentToChildren , loadedModFiles );
129
140
// Get renamer library classpath
130
- List <Path > renameLibs = StreamSupport .stream (loadedMods . spliterator (), false ).map (modFile -> modFile .getSecureJar ().getRootPath ()).toList ();
141
+ List <Path > renameLibs = loadedModFiles .stream ().map (modFile -> modFile .getSecureJar ().getRootPath ()).toList ();
131
142
// Run jar transformations (or get existing outputs from cache)
132
- List <JarTransformer .FabricModPath > transformed = JarTransformer .transform (candidates , renameLibs , loadedMods );
143
+ List <JarTransformer .FabricModPath > transformed = JarTransformer .transform (candidates , renameLibs , loadedModFiles );
133
144
// Skip last step to save time if an error occured during transformation
134
145
if (ConnectorEarlyLoader .hasEncounteredException ()) {
135
146
StartupNotificationManager .addModMessage ("JAR TRANSFORMATION ERROR" );
136
147
LOGGER .error ("Cancelling jar discovery due to previous error" );
137
148
return List .of ();
138
149
}
139
150
// Deal with split packages (thanks modules)
140
- List <SplitPackageMerger .FilteredModPath > moduleSafeJars = SplitPackageMerger .mergeSplitPackages (transformed , loadedMods , ignoredModFiles );
151
+ List <SplitPackageMerger .FilteredModPath > moduleSafeJars = SplitPackageMerger .mergeSplitPackages (transformed , loadedModFiles , ignoredModFiles );
141
152
142
153
List <IModFile > modFiles = new ArrayList <>(moduleSafeJars .stream ().map (this ::createConnectorModFile ).toList ());
143
154
// Create mod file for generated adapter mixins jar
@@ -154,9 +165,9 @@ private Stream<Path> scanModsDir(List<Path> excluded) {
154
165
155
166
private Stream <Path > filterPaths (Stream <Path > stream , List <Path > excluded ) {
156
167
return stream
157
- .filter (p -> !excluded .contains (p ) && StringUtils .toLowerCase (p .getFileName ().toString ()).endsWith (SUFFIX ))
158
- .sorted (Comparator .comparing (path -> StringUtils .toLowerCase (path .getFileName ().toString ())))
159
- .filter (ConnectorLocator ::isFabricModJar );
168
+ .filter (p -> !excluded .contains (p ) && StringUtils .toLowerCase (p .getFileName ().toString ()).endsWith (SUFFIX ))
169
+ .sorted (Comparator .comparing (path -> StringUtils .toLowerCase (path .getFileName ().toString ())))
170
+ .filter (ConnectorLocator ::isFabricModJar );
160
171
}
161
172
162
173
private Stream <Path > scanClasspath () {
@@ -190,7 +201,8 @@ private Stream<Path> scanFromArguments(List<Path> excluded) {
190
201
Arrays .stream (paths ).filter (s -> !s .isBlank ()).map (Path ::of ).forEach (path -> {
191
202
if (Files .isDirectory (path )) {
192
203
uncheck (() -> Files .list (path )).forEach (files ::add );
193
- } else {
204
+ }
205
+ else {
194
206
files .add (path );
195
207
}
196
208
});
@@ -216,7 +228,8 @@ private IModFile createModOrThrow(Path... paths) {
216
228
private static boolean isFabricModJar (Path path ) {
217
229
SecureJar secureJar = SecureJar .from (path );
218
230
String name = secureJar .name ();
219
- if (secureJar .moduleDataProvider ().findFile (ConnectorUtil .MODS_TOML ).isPresent ()) {
231
+ Path modsToml = secureJar .getPath (ConnectorUtil .MODS_TOML );
232
+ if (Files .exists (modsToml ) && !containsPlaceholder (modsToml )) {
220
233
LOGGER .debug (SCAN , "Skipping jar {} as it contains a mods.toml file" , path );
221
234
return false ;
222
235
}
@@ -228,6 +241,21 @@ private static boolean isFabricModJar(Path path) {
228
241
return false ;
229
242
}
230
243
244
+ private static boolean containsPlaceholder (Path modsTomlPath ) {
245
+ try {
246
+ FileConfig fileConfig = FileConfig .of (modsTomlPath );
247
+ fileConfig .load ();
248
+ fileConfig .close ();
249
+ NightConfigWrapper config = new NightConfigWrapper (fileConfig );
250
+ return config .<Map <String , Object >>getConfigElement ("properties" )
251
+ .map (map -> map .containsKey (PLACEHOLDER_PROPERTY ))
252
+ .orElse (false );
253
+ } catch (Throwable t ) {
254
+ LOGGER .error ("Error reading placeholder information from {}" , modsTomlPath , t );
255
+ return false ;
256
+ }
257
+ }
258
+
231
259
private static Stream <JarTransformer .TransformableJar > discoverNestedJarsRecursive (Path tempDir , JarTransformer .TransformableJar parent , Collection <NestedJarEntry > jars , Multimap <JarTransformer .TransformableJar , JarTransformer .TransformableJar > parentToChildren , Collection <String > loadedModIds ) {
232
260
SecureJar secureJar = SecureJar .from (parent .input ().toPath ());
233
261
return jars .stream ()
@@ -236,7 +264,7 @@ private static Stream<JarTransformer.TransformableJar> discoverNestedJarsRecursi
236
264
.flatMap (path -> {
237
265
JarTransformer .TransformableJar jar = uncheck (() -> prepareNestedJar (tempDir , secureJar .getPrimaryPath ().getFileName ().toString (), path ));
238
266
ConnectorLoaderModMetadata metadata = jar .modPath ().metadata ().modMetadata ();
239
- if (shouldIgnoreMod (metadata . getId () , loadedModIds )) {
267
+ if (shouldIgnoreMod (metadata , loadedModIds )) {
240
268
return Stream .empty ();
241
269
}
242
270
parentToChildren .put (parent , jar );
@@ -289,7 +317,9 @@ private static List<JarTransformer.TransformableJar> handleDuplicateMods(List<Ja
289
317
.toList ();
290
318
}
291
319
292
- private static boolean shouldIgnoreMod (String id , Collection <String > loadedModIds ) {
320
+ private static boolean shouldIgnoreMod (ConnectorLoaderModMetadata metadata , Collection <String > loadedModIds ) {
321
+ if (!metadata .loadsInEnvironment (FabricLoader .getInstance ().getEnvironmentType ())) return true ;
322
+ String id = metadata .getId ();
293
323
return ConnectorUtil .DISABLED_MODS .contains (id ) || loadedModIds .contains (id );
294
324
}
295
325
0 commit comments