Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/main/java/net/fabricmc/loader/impl/FabricLoaderImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import net.fabricmc.loader.api.MappingResolver;
import net.fabricmc.loader.api.SemanticVersion;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
import net.fabricmc.loader.impl.discovery.ArgumentModCandidateFinder;
import net.fabricmc.loader.impl.discovery.ClasspathModCandidateFinder;
import net.fabricmc.loader.impl.discovery.DirectoryModCandidateFinder;
import net.fabricmc.loader.impl.discovery.ModCandidate;
Expand Down Expand Up @@ -179,9 +180,11 @@ public void load() {
}

private void setup() throws ModResolutionException {
boolean remapRegularMods = isDevelopmentEnvironment();
ModDiscoverer discoverer = new ModDiscoverer();
discoverer.addCandidateFinder(new ClasspathModCandidateFinder());
discoverer.addCandidateFinder(new DirectoryModCandidateFinder(gameDir.resolve("mods"), isDevelopmentEnvironment()));
discoverer.addCandidateFinder(new DirectoryModCandidateFinder(gameDir.resolve("mods"), remapRegularMods));
discoverer.addCandidateFinder(new ArgumentModCandidateFinder(remapRegularMods));

List<ModCandidate> mods = ModResolver.resolve(discoverer.discoverMods(this));

Expand All @@ -201,7 +204,7 @@ private void setup() throws ModResolutionException {

// runtime mod remapping

if (isDevelopmentEnvironment()) {
if (remapRegularMods) {
if (System.getProperty(SystemProperties.REMAP_CLASSPATH_FILE) == null) {
Log.warn(LogCategory.MOD_REMAP, "Runtime mod remapping disabled due to no fabric.remapClasspathFile being specified. You may need to update loom.");
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright 2016 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.fabricmc.loader.impl.discovery;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;

import net.fabricmc.loader.impl.FabricLoaderImpl;
import net.fabricmc.loader.impl.util.Arguments;
import net.fabricmc.loader.impl.util.SystemProperties;
import net.fabricmc.loader.impl.util.log.Log;
import net.fabricmc.loader.impl.util.log.LogCategory;

public class ArgumentModCandidateFinder implements ModCandidateFinder {
private final boolean requiresRemap;

public ArgumentModCandidateFinder(boolean requiresRemap) {
this.requiresRemap = requiresRemap;
}

@Override
public void findCandidates(ModCandidateConsumer out) {
String list = System.getProperty(SystemProperties.ADD_MODS);
if (list != null) addMods(list, "system property", out);

list = FabricLoaderImpl.INSTANCE.getGameProvider().getArguments().remove(Arguments.ADD_MODS);
if (list != null) addMods(list, "argument", out);
}

private void addMods(String list, String source, ModCandidateConsumer out) {
for (String pathStr : list.split(File.pathSeparator)) {
if (pathStr.isEmpty()) continue;

if (pathStr.startsWith("@")) {
Path path = Paths.get(pathStr.substring(1));

if (!Files.isRegularFile(path)) {
Log.warn(LogCategory.DISCOVERY, "Missing/invalid %s provided mod list file %s", source, path);
continue;
}

try (BufferedReader reader = Files.newBufferedReader(path)) {
String fileSource = String.format("%s file %s", source, path);
String line;

while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;

addMod(line, fileSource, out);
}
} catch (IOException e) {
throw new RuntimeException(String.format("Error reading %s provided mod list file %s", source, path), e);
}
} else {
addMod(pathStr, source, out);
}
}
}

private void addMod(String pathStr, String source, ModCandidateConsumer out) {
Path path = Paths.get(pathStr).toAbsolutePath().normalize();

if (!Files.exists(path)) { // missing
Log.warn(LogCategory.DISCOVERY, "Missing %s provided mod path %s", source, path);
} else if (Files.isDirectory(path)) { // directory for extracted mod (in-dev usually) or jars (like mods, but recursive)
if (isHidden(path)) {
Log.warn(LogCategory.DISCOVERY, "Ignoring hidden %s provided mod path %s", source, path);
return;
}

if (Files.exists(path.resolve("fabric.mod.json"))) { // extracted mod
out.accept(path, requiresRemap);
} else { // dir containing jars
try {
List<String> skipped = new ArrayList<>();

Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (DirectoryModCandidateFinder.isValidFile(file)) {
out.accept(file, requiresRemap);
} else {
skipped.add(path.relativize(file).toString());
}

return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
if (isHidden(dir)) {
return FileVisitResult.SKIP_SUBTREE;
} else {
return FileVisitResult.CONTINUE;
}
}
});

if (!skipped.isEmpty()) {
Log.warn(LogCategory.DISCOVERY, "Incompatible files in %s provided mod directory %s (non-jar or hidden): %s", source, path, String.join(", ", skipped));
}
} catch (IOException e) {
Log.warn(LogCategory.DISCOVERY, "Error processing %s provided mod path %s: %s", source, path, e);
}
}
} else { // single file
if (!DirectoryModCandidateFinder.isValidFile(path)) {
Log.warn(LogCategory.DISCOVERY, "Incompatible file in %s provided mod path %s (non-jar or hidden)", source, path);
} else {
out.accept(path, requiresRemap);
}
}
}

private static boolean isHidden(Path path) {
try {
return path.getFileName().toString().startsWith(".") || Files.isHidden(path);
} catch (IOException e) {
Log.warn(LogCategory.DISCOVERY, "Error determining whether %s is hidden: %s", path, e);
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.util.EnumSet;

import net.fabricmc.loader.impl.util.log.Log;
import net.fabricmc.loader.impl.util.log.LogCategory;

public class DirectoryModCandidateFinder implements ModCandidateFinder {
private final Path path;
private final boolean requiresRemap;
Expand Down Expand Up @@ -52,18 +55,7 @@ public void findCandidates(ModCandidateConsumer out) {
Files.walkFileTree(this.path, EnumSet.of(FileVisitOption.FOLLOW_LINKS), 1, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
/*
* We only propose a file as a possible mod in the following scenarios:
* General: Must be a jar file
*
* Some OSes Generate metadata so consider the following because of OSes:
* UNIX: Exclude if file is hidden; this occurs when starting a file name with `.`
* MacOS: Exclude hidden + startsWith "." since Mac OS names their metadata files in the form of `.mod.jar`
*/

String fileName = file.getFileName().toString();

if (fileName.endsWith(".jar") && !fileName.startsWith(".") && !Files.isHidden(file)) {
if (isValidFile(file)) {
out.accept(file, requiresRemap);
}

Expand All @@ -74,4 +66,28 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
throw new RuntimeException("Exception while searching for mods in '" + path + "'!", e);
}
}

static boolean isValidFile(Path path) {
/*
* We only propose a file as a possible mod in the following scenarios:
* General: Must be a jar file
*
* Some OSes Generate metadata so consider the following because of OSes:
* UNIX: Exclude if file is hidden; this occurs when starting a file name with `.`
* MacOS: Exclude hidden + startsWith "." since Mac OS names their metadata files in the form of `.mod.jar`
*/

if (!Files.isRegularFile(path)) return false;

try {
if (Files.isHidden(path)) return false;
} catch (IOException e) {
Log.warn(LogCategory.DISCOVERY, "Error checking if file %s is hidden", path, e);
return false;
}

String fileName = path.getFileName().toString();

return fileName.endsWith(".jar") && !fileName.startsWith(".");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,15 @@ public void addCandidateFinder(ModCandidateFinder f) {
public Collection<ModCandidate> discoverMods(FabricLoaderImpl loader) throws ModResolutionException {
long startTime = System.nanoTime();
ForkJoinPool pool = new ForkJoinPool();
Set<Path> paths = new HashSet<>(); // suppresses duplicate paths
List<Future<ModCandidate>> futures = new ArrayList<>();

ModCandidateConsumer taskSubmitter = (path, requiresRemap) -> {
futures.add(pool.submit(new ModScanTask(path, requiresRemap)));
path = path.toAbsolutePath().normalize();

if (paths.add(path)) {
futures.add(pool.submit(new ModScanTask(path, requiresRemap)));
}
};

for (ModCandidateFinder finder : candidateFinders) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/net/fabricmc/loader/impl/game/GameProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.metadata.ModMetadata;
import net.fabricmc.loader.impl.game.patch.GameTransformer;
import net.fabricmc.loader.impl.util.Arguments;

public interface GameProvider {
String getGameId();
Expand All @@ -42,6 +43,7 @@ public interface GameProvider {
GameTransformer getEntrypointTransformer();
void launch(ClassLoader loader);

Arguments getArguments();
String[] getLaunchArguments(boolean sanitize);

default boolean canOpenErrorGui() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import net.fabricmc.loader.impl.game.minecraft.patch.EntrypointPatch;
import net.fabricmc.loader.impl.game.minecraft.patch.EntrypointPatchFML125;
import net.fabricmc.loader.impl.game.patch.GameTransformer;
import net.fabricmc.loader.impl.launch.FabricLauncherBase;
import net.fabricmc.loader.impl.metadata.BuiltinModMetadata;
import net.fabricmc.loader.impl.metadata.ModDependencyImpl;
import net.fabricmc.loader.impl.util.Arguments;
Expand Down Expand Up @@ -110,7 +109,7 @@ public Path getLaunchDirectory() {
return new File(".").toPath();
}

return FabricLauncherBase.getLaunchDirectory(arguments).toPath();
return getLaunchDirectory(arguments).toPath();
}

@Override
Expand Down Expand Up @@ -166,38 +165,75 @@ public boolean locateGame(EnvType envType, String[] args, ClassLoader loader) {
if (version == null) version = System.getProperty(SystemProperties.GAME_VERSION);
versionData = McVersionLookup.getVersion(gameJar, entrypointClasses, version);

FabricLauncherBase.processArgumentMap(arguments, envType);
processArgumentMap(arguments, envType);

return true;
}

private static void processArgumentMap(Arguments argMap, EnvType envType) {
switch (envType) {
case CLIENT:
if (!argMap.containsKey("accessToken")) {
argMap.put("accessToken", "FabricMC");
}

if (!argMap.containsKey("version")) {
argMap.put("version", "Fabric");
}

String versionType = "";

if (argMap.containsKey("versionType") && !argMap.get("versionType").equalsIgnoreCase("release")) {
versionType = argMap.get("versionType") + "/";
}

argMap.put("versionType", versionType + "Fabric");

if (!argMap.containsKey("gameDir")) {
argMap.put("gameDir", getLaunchDirectory(argMap).getAbsolutePath());
}

break;
case SERVER:
argMap.remove("version");
argMap.remove("gameDir");
argMap.remove("assetsDir");
break;
}
}

private static File getLaunchDirectory(Arguments argMap) {
return new File(argMap.getOrDefault("gameDir", "."));
}

@Override
public String[] getLaunchArguments(boolean sanitize) {
if (arguments != null) {
List<String> list = new ArrayList<>(Arrays.asList(arguments.toArray()));
public Arguments getArguments() {
return arguments;
}

if (sanitize) {
int remove = 0;
Iterator<String> iterator = list.iterator();
@Override
public String[] getLaunchArguments(boolean sanitize) {
if (arguments == null) return new String[0];
if (!sanitize) return arguments.toArray();

while (iterator.hasNext()) {
String next = iterator.next();
List<String> list = new ArrayList<>(Arrays.asList(arguments.toArray()));
int remove = 0;
Iterator<String> iterator = list.iterator();

if ("--accessToken".equals(next)) {
remove = 2;
}
while (iterator.hasNext()) {
String next = iterator.next();

if (remove > 0) {
iterator.remove();
remove--;
}
}
if ("--accessToken".equals(next)) {
remove = 2;
}

return list.toArray(new String[0]);
if (remove > 0) {
iterator.remove();
remove--;
}
}

return new String[0];
return list.toArray(new String[0]);
}

@Override
Expand Down
Loading