diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModNioResourcePack.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModNioResourcePack.java index 470b4e9f4..992783ff5 100644 --- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModNioResourcePack.java +++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModNioResourcePack.java @@ -20,13 +20,18 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.file.DirectoryStream; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Pattern; @@ -41,38 +46,136 @@ import net.minecraft.util.InvalidIdentifierException; import net.fabricmc.fabric.api.resource.ModResourcePack; import net.fabricmc.fabric.api.resource.ResourcePackActivationType; +import net.fabricmc.loader.api.ModContainer; import net.fabricmc.loader.api.metadata.ModMetadata; public class ModNioResourcePack extends AbstractFileResourcePack implements ModResourcePack { private static final Logger LOGGER = LoggerFactory.getLogger(ModNioResourcePack.class); private static final Pattern RESOURCE_PACK_PATH = Pattern.compile("[a-z0-9-_]+"); - private final ModMetadata modInfo; - private final Path basePath; - private final ResourceType type; - private final boolean cacheable; - private final AutoCloseable closer; - private final String separator; - private final ResourcePackActivationType activationType; - public ModNioResourcePack(ModMetadata modInfo, Path path, ResourceType type, AutoCloseable closer, ResourcePackActivationType activationType) { + private final String name; + private final ModMetadata modInfo; + private final List<Path> basePaths; + private final ResourceType type; + private final AutoCloseable closer; + private final ResourcePackActivationType activationType; + private final Map<ResourceType, Set<String>> namespaces; + + public static ModNioResourcePack create(String name, ModContainer mod, String subPath, ResourceType type, ResourcePackActivationType activationType) { + List<Path> rootPaths = mod.getRootPaths(); + List<Path> paths; + + if (subPath == null) { + paths = rootPaths; + } else { + paths = new ArrayList<>(rootPaths.size()); + + for (Path path : rootPaths) { + path = path.toAbsolutePath().normalize(); + Path childPath = path.resolve(subPath.replace("/", path.getFileSystem().getSeparator())).normalize(); + + if (!childPath.startsWith(path) || !Files.exists(childPath)) { + continue; + } + + paths.add(childPath); + } + } + + if (paths.isEmpty()) return null; + + ModNioResourcePack ret = new ModNioResourcePack(name, mod.getMetadata(), paths, type, null, activationType); + + return ret.getNamespaces(type).isEmpty() ? null : ret; + } + + private ModNioResourcePack(String name, ModMetadata modInfo, List<Path> paths, ResourceType type, AutoCloseable closer, ResourcePackActivationType activationType) { super(null); + + this.name = name; this.modInfo = modInfo; - this.basePath = path.toAbsolutePath().normalize(); + this.basePaths = paths; this.type = type; - this.cacheable = false; /* TODO */ this.closer = closer; - this.separator = basePath.getFileSystem().getSeparator(); this.activationType = activationType; + this.namespaces = readNamespaces(paths, modInfo.getId()); + } + + private static Map<ResourceType, Set<String>> readNamespaces(List<Path> paths, String modId) { + Map<ResourceType, Set<String>> ret = new EnumMap<>(ResourceType.class); + + for (ResourceType type : ResourceType.values()) { + Set<String> namespaces = null; + + for (Path path : paths) { + Path dir = path.resolve(type.getDirectory()); + if (!Files.isDirectory(dir)) continue; + + String separator = path.getFileSystem().getSeparator(); + + try (DirectoryStream<Path> ds = Files.newDirectoryStream(dir)) { + for (Path p : ds) { + if (!Files.isDirectory(p)) continue; + + String s = p.getFileName().toString(); + // s may contain trailing slashes, remove them + s = s.replace(separator, ""); + + if (!RESOURCE_PACK_PATH.matcher(s).matches()) { + LOGGER.warn("Fabric NioResourcePack: ignored invalid namespace: {} in mod ID {}", s, modId); + continue; + } + + if (namespaces == null) namespaces = new HashSet<>(); + + namespaces.add(s); + } + } catch (IOException e) { + LOGGER.warn("getNamespaces in mod " + modId + " failed!", e); + } + } + + ret.put(type, namespaces != null ? namespaces : Collections.emptySet()); + } + + return ret; } private Path getPath(String filename) { - Path childPath = basePath.resolve(filename.replace("/", separator)).toAbsolutePath().normalize(); + if (hasAbsentNs(filename)) return null; - if (childPath.startsWith(basePath) && Files.exists(childPath)) { - return childPath; - } else { - return null; + for (Path basePath : basePaths) { + Path childPath = basePath.resolve(filename.replace("/", basePath.getFileSystem().getSeparator())).toAbsolutePath().normalize(); + + if (childPath.startsWith(basePath) && Files.exists(childPath)) { + return childPath; + } } + + return null; + } + + private static final String resPrefix = ResourceType.CLIENT_RESOURCES.getDirectory()+"/"; + private static final String dataPrefix = ResourceType.SERVER_DATA.getDirectory()+"/"; + + private boolean hasAbsentNs(String filename) { + int prefixLen; + ResourceType type; + + if (filename.startsWith(resPrefix)) { + prefixLen = resPrefix.length(); + type = ResourceType.CLIENT_RESOURCES; + } else if (filename.startsWith(dataPrefix)) { + prefixLen = dataPrefix.length(); + type = ResourceType.SERVER_DATA; + } else { + return false; + } + + int nsEnd = filename.indexOf('/', prefixLen); + if (nsEnd < 0) return false; + + return !namespaces.get(type).contains(filename.substring(prefixLen, nsEnd)); } @Override @@ -107,84 +210,47 @@ public class ModNioResourcePack extends AbstractFileResourcePack implements ModR @Override public Collection<Identifier> findResources(ResourceType type, String namespace, String path, int depth, Predicate<String> predicate) { + if (!namespaces.getOrDefault(type, Collections.emptySet()).contains(namespace)) { + return Collections.emptyList(); + } + List<Identifier> ids = new ArrayList<>(); - String nioPath = path.replace("/", separator); - Path namespacePath = getPath(type.getDirectory() + "/" + namespace); + for (Path basePath : basePaths) { + String separator = basePath.getFileSystem().getSeparator(); + Path nsPath = basePath.resolve(type.getDirectory()).resolve(namespace); + Path searchPath = nsPath.resolve(path.replace("/", separator)).normalize(); + if (!Files.exists(searchPath)) continue; - if (namespacePath != null) { - Path searchPath = namespacePath.resolve(nioPath).toAbsolutePath().normalize(); + try { + Files.walkFileTree(searchPath, new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String fileName = file.getFileName().toString(); - if (Files.exists(searchPath)) { - try { - Files.walk(searchPath, depth) - .filter(Files::isRegularFile) - .filter((p) -> { - String filename = p.getFileName().toString(); - return !filename.endsWith(".mcmeta") && predicate.test(filename); - }) - .map(namespacePath::relativize) - .map((p) -> p.toString().replace(separator, "/")) - .forEach((s) -> { - try { - ids.add(new Identifier(namespace, s)); - } catch (InvalidIdentifierException e) { - LOGGER.error(e.getMessage()); - } - }); - } catch (IOException e) { - LOGGER.warn("findResources at " + path + " in namespace " + namespace + ", mod " + modInfo.getId() + " failed!", e); - } + if (!fileName.endsWith(".mcmeta") + && predicate.test(fileName)) { + try { + ids.add(new Identifier(namespace, nsPath.relativize(file).toString().replace(separator, "/"))); + } catch (InvalidIdentifierException e) { + LOGGER.error(e.getMessage()); + } + } + + return FileVisitResult.CONTINUE; + } + }); + } catch (IOException e) { + LOGGER.warn("findResources at " + path + " in namespace " + namespace + ", mod " + modInfo.getId() + " failed!", e); } } return ids; } - private Set<String> namespaceCache; - - protected void warnInvalidNamespace(String s) { - LOGGER.warn("Fabric NioResourcePack: ignored invalid namespace: {} in mod ID {}", s, modInfo.getId()); - } - @Override public Set<String> getNamespaces(ResourceType type) { - if (namespaceCache != null) { - return namespaceCache; - } - - try { - Path typePath = getPath(type.getDirectory()); - - if (typePath == null || !(Files.isDirectory(typePath))) { - return Collections.emptySet(); - } - - Set<String> namespaces = new HashSet<>(); - - try (DirectoryStream<Path> stream = Files.newDirectoryStream(typePath, Files::isDirectory)) { - for (Path path : stream) { - String s = path.getFileName().toString(); - // s may contain trailing slashes, remove them - s = s.replace(separator, ""); - - if (RESOURCE_PACK_PATH.matcher(s).matches()) { - namespaces.add(s); - } else { - this.warnInvalidNamespace(s); - } - } - } - - if (cacheable) { - namespaceCache = namespaces; - } - - return namespaces; - } catch (IOException e) { - LOGGER.warn("getNamespaces in mod " + modInfo.getId() + " failed!", e); - return Collections.emptySet(); - } + return namespaces.getOrDefault(type, Collections.emptySet()); } @Override @@ -209,6 +275,6 @@ public class ModNioResourcePack extends AbstractFileResourcePack implements ModR @Override public String getName() { - return ModResourcePackUtil.getName(modInfo); + return name; } } diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackUtil.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackUtil.java index 8682fb808..667853335 100644 --- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackUtil.java +++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackUtil.java @@ -17,8 +17,6 @@ package net.fabricmc.fabric.impl.resource.loader; import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.List; import com.google.common.base.Charsets; @@ -54,21 +52,9 @@ public final class ModResourcePackUtil { continue; } - Path path = container.getRootPath(); + ModResourcePack pack = ModNioResourcePack.create(getName(container.getMetadata()), container, null, type, ResourcePackActivationType.ALWAYS_ENABLED); - if (subPath != null) { - Path childPath = path.resolve(subPath.replace("/", path.getFileSystem().getSeparator())).toAbsolutePath().normalize(); - - if (!childPath.startsWith(path) || !Files.exists(childPath)) { - continue; - } - - path = childPath; - } - - ModResourcePack pack = new ModNioResourcePack(container.getMetadata(), path, type, null, ResourcePackActivationType.ALWAYS_ENABLED); - - if (!pack.getNamespaces(type).isEmpty()) { + if (pack != null) { packs.add(pack); } } diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java index 5aa7736a5..27618a3eb 100644 --- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java +++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourceManagerHelperImpl.java @@ -69,30 +69,18 @@ public class ResourceManagerHelperImpl implements ResourceManagerHelper { * @see ResourceManagerHelper#registerBuiltinResourcePack(Identifier, String, ModContainer, boolean) */ public static boolean registerBuiltinResourcePack(Identifier id, String subPath, ModContainer container, ResourcePackActivationType activationType) { - String separator = container.getRootPath().getFileSystem().getSeparator(); - subPath = subPath.replace("/", separator); + String name = id.getNamespace() + "/" + id.getPath(); + ModNioResourcePack resourcePack = ModNioResourcePack.create(name, container, subPath, ResourceType.CLIENT_RESOURCES, activationType); + ModNioResourcePack dataPack = ModNioResourcePack.create(name, container, subPath, ResourceType.SERVER_DATA, activationType); + if (resourcePack == null && dataPack == null) return false; - Path resourcePackPath = container.getRootPath().resolve(subPath).toAbsolutePath().normalize(); - - if (!Files.exists(resourcePackPath)) { - return false; + if (resourcePack != null) { + builtinResourcePacks.add(new Pair<>(name, resourcePack)); } - String name = id.getNamespace() + "/" + id.getPath(); - - builtinResourcePacks.add(new Pair<>(name, new ModNioResourcePack(container.getMetadata(), resourcePackPath, ResourceType.CLIENT_RESOURCES, null, activationType) { - @Override - public String getName() { - return name; // Built-in resource pack provided by a mod, the name is overriden. - } - })); - - builtinResourcePacks.add(new Pair<>(name, new ModNioResourcePack(container.getMetadata(), resourcePackPath, ResourceType.SERVER_DATA, null, activationType) { - @Override - public String getName() { - return name; // Built-in resource pack provided by a mod, the name is overriden. - } - })); + if (dataPack != null) { + builtinResourcePacks.add(new Pair<>(name, dataPack)); + } return true; } diff --git a/fabric-resource-loader-v0/src/main/resources/fabric.mod.json b/fabric-resource-loader-v0/src/main/resources/fabric.mod.json index 60c01dfbe..7aa70d715 100644 --- a/fabric-resource-loader-v0/src/main/resources/fabric.mod.json +++ b/fabric-resource-loader-v0/src/main/resources/fabric.mod.json @@ -16,7 +16,7 @@ "FabricMC" ], "depends": { - "fabricloader": ">=0.4.0" + "fabricloader": ">=0.13.0" }, "description": "Asset and data resource loading.", "mixins": [