Resource loader improvements ()

* Add resource pack activation type, programmer art API, group mod resource packs.

* Add missing license headers.

* Cleanup and add documentation.

* Fix performance issues.

* Rename invoker.

* Add comment in ReloadableResourceManagerImplMixin.

* Add package-info to resource loader.

(cherry picked from commit 73b29211a6)
This commit is contained in:
LambdAurora 2020-12-23 15:05:43 +00:00 committed by modmuss50
parent 96f0bfaa41
commit 0ea93ebaf8
21 changed files with 715 additions and 86 deletions

View file

@ -24,7 +24,7 @@ import net.fabricmc.fabric.impl.resource.loader.ResourceManagerHelperImpl;
import net.fabricmc.loader.api.ModContainer;
/**
* Helper for working with {@link ResourceManager} instances.
* Helper for working with {@link ResourceManager} instances, and other resource loader generalities.
*/
public interface ResourceManagerHelper {
/**
@ -55,6 +55,26 @@ public interface ResourceManagerHelper {
return ResourceManagerHelperImpl.get(type);
}
/**
* Registers a built-in resource pack.
*
* <p>A built-in resource pack is an extra resource pack provided by your mod which is not always active, it's similar to the "Programmer Art" resource pack.
*
* <p>Why and when to use it? A built-in resource pack should be used to provide extra assets/data that should be optional with your mod but still directly provided by it.
* For example it could provide textures of your mod in another resolution, or could allow to provide different styles of your assets.
*
* <p>The path in which the resource pack is located is in the mod JAR file under the {@code "resourcepacks/<id path>"} directory. {@code id path} being the path specified
* in the identifier of this built-in resource pack.
*
* @param id the identifier of the resource pack
* @param container the mod container
* @param activationType the activation type of the resource pack
* @return {@code true} if successfully registered the resource pack, else {@code false}
*/
static boolean registerBuiltinResourcePack(Identifier id, ModContainer container, ResourcePackActivationType activationType) {
return ResourceManagerHelperImpl.registerBuiltinResourcePack(id, "resourcepacks/" + id.getPath(), container, activationType);
}
/**
* Registers a built-in resource pack.
*
@ -68,13 +88,17 @@ public interface ResourceManagerHelper {
* <p>Note about the enabled by default parameter: a resource pack cannot be enabled by default, only data packs can.
* Making this work for resource packs is near impossible without touching how Vanilla handles disabled resource packs.
*
* @param id The identifier of the resource pack.
* @param subPath The sub path in the mod resources.
* @param container The mod container.
* @param enabledByDefault True if enabled by default, else false.
* @return True if successfully registered the resource pack, else false.
* @param id the identifier of the resource pack
* @param subPath the sub path in the mod resources
* @param container the mod container
* @param enabledByDefault {@code true} if enabled by default, else {@code false}
* @return {@code true} if successfully registered the resource pack, else {@code false}
* @deprecated Please use {@link #registerBuiltinResourcePack(Identifier, ModContainer, ResourcePackActivationType)} instead, the {@code sub path} should be removed in a future
* release in favor of the identifier path.
*/
@Deprecated
static boolean registerBuiltinResourcePack(Identifier id, String subPath, ModContainer container, boolean enabledByDefault) {
return ResourceManagerHelperImpl.registerBuiltinResourcePack(id, subPath, container, enabledByDefault);
return ResourceManagerHelperImpl.registerBuiltinResourcePack(id, subPath, container,
enabledByDefault ? ResourcePackActivationType.DEFAULT_ENABLED : ResourcePackActivationType.NORMAL);
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 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.fabric.api.resource;
/**
* Represents the resource pack activation type.
*/
public enum ResourcePackActivationType {
/**
* Normal activation. The user has full control over the activation of the resource pack.
*/
NORMAL,
/**
* Enabled by default. The user has still full control over the activation of the resource pack.
*
* <p>Note: this setting can only be satisfied on data packs, client resource packs cannot be by default enabled.
*/
DEFAULT_ENABLED,
/**
* Always enabled. The user cannot disable the resource pack.
*/
ALWAYS_ENABLED;
/**
* Returns whether this resource pack will be enabled by default or not.
*
* @return {@code true} if enabled by default, else {@code false}
*/
public boolean isEnabledByDefault() {
return this == DEFAULT_ENABLED || this == ALWAYS_ENABLED;
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 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.
*/
/**
* The Resource Loader, version 0.
*
* <p><h3>Quick note about vocabulary in Resource Loader and Minecraft:</h3>
* <ul>
* <li>Resource Pack refers to both client-sided resource pack and data pack.</li>
* <li>Virtual Resource Pack refers to a resource pack that may be generated at runtime, or simply doesn't exist directly on disk.</li>
* <li>Group Resource Pack refers to a virtual resource pack that groups multiple resource packs together.</li>
* </ul>
* </p>
*
* <p><h3>Modded Resource Pack Handling</h3></p>
* <p>The Resource Loader will create a resource pack for each mod that provides resources in {@code assets} or {@code data}
* sub-directories.
* Those mod resource packs are grouped into a single always-enabled group resource pack which is shown in the resource pack screen.</p>
*
* <p><h4>Built-in Mod Resource Pack</h4></p>
* <p>The Resource Loader adds manually registered mod resource packs. Those resource packs are registered with
* {@link net.fabricmc.fabric.api.resource.ResourceManagerHelper#registerBuiltinResourcePack(net.minecraft.util.Identifier, net.fabricmc.loader.api.ModContainer, net.fabricmc.fabric.api.resource.ResourcePackActivationType)}</p>
*
* <p><h4>Programmer Art Resource Pack</h4></p>
* <p>The Resource Loader will inject resources into the Programmer Art resource pack for each mod that provides
* Programmer Art resources in the {@code programmer_art} top-level directory of the mod
* whose structure is similar to a normal resource pack.</p>
*
* <p><h3>Resource Reload Listener</h3></p>
* <p>The Resource Loader allows mods to register resource reload listeners through
* {@link net.fabricmc.fabric.api.resource.ResourceManagerHelper#registerReloadListener(net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener)},
* which are triggered when resources are reloaded.
* A resource reload listener can depend on another and vanilla resource reload listener identifiers may be found in {@link net.fabricmc.fabric.api.resource.ResourceReloadListenerKeys}.</p>
*/
package net.fabricmc.fabric.api.resource;

View file

@ -0,0 +1,108 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 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.fabric.impl.resource.loader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.List;
import com.google.common.base.Charsets;
import net.minecraft.SharedConstants;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.Nullable;
import net.minecraft.resource.AbstractFileResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.resource.metadata.ResourceMetadataReader;
import net.fabricmc.fabric.api.resource.ModResourcePack;
import net.fabricmc.loader.api.FabricLoader;
/**
* The Fabric mods resource pack, holds all the mod resource packs as one pack.
*/
public class FabricModResourcePack extends GroupResourcePack {
public FabricModResourcePack(ResourceType type, List<ModResourcePack> packs) {
super(type, packs);
}
@Override
public InputStream openRoot(String fileName) throws IOException {
if ("pack.mcmeta".equals(fileName)) {
String description = "Mod resources.";
String pack = String.format("{\"pack\":{\"pack_format\":" + type.getPackVersion(SharedConstants.getGameVersion()) + ",\"description\":\"%s\"}}", description);
return IOUtils.toInputStream(pack, Charsets.UTF_8);
} else if ("pack.png".equals(fileName)) {
InputStream stream = FabricLoader.getInstance().getModContainer("fabric-resource-loader-v0")
.flatMap(container -> container.getMetadata().getIconPath(512).map(container::getPath))
.filter(Files::exists)
.map(iconPath -> {
try {
return Files.newInputStream(iconPath);
} catch (IOException e) {
return null;
}
}).orElse(null);
if (stream != null) {
return stream;
}
}
// ReloadableResourceManagerImpl gets away with FileNotFoundException.
throw new FileNotFoundException("\"" + fileName + "\" in Fabric mod resource pack");
}
@Override
public <T> @Nullable T parseMetadata(ResourceMetadataReader<T> metaReader) throws IOException {
try {
InputStream inputStream = this.openRoot("pack.mcmeta");
Throwable error = null;
T metadata;
try {
metadata = AbstractFileResourcePack.parseMetadata(metaReader, inputStream);
} catch (Throwable e) {
error = e;
throw e;
} finally {
if (inputStream != null) {
if (error != null) {
try {
inputStream.close();
} catch (Throwable e) {
error.addSuppressed(e);
}
} else {
inputStream.close();
}
}
}
return metadata;
} catch (FileNotFoundException | RuntimeException e) {
return null;
}
}
@Override
public String getName() {
return "Fabric Mods";
}
}

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 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.fabric.impl.resource.loader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourceImpl;
import net.minecraft.resource.ResourceNotFoundException;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.resource.ModResourcePack;
import net.fabricmc.fabric.mixin.resource.loader.NamespaceResourceManagerAccessor;
/**
* Represents a group resource pack, holds multiple resource packs as one.
*/
public abstract class GroupResourcePack implements ResourcePack {
protected final ResourceType type;
protected final List<ModResourcePack> packs;
protected final Object2ObjectMap<String, List<ModResourcePack>> namespacedPacks = new Object2ObjectOpenHashMap<>();
public GroupResourcePack(ResourceType type, List<ModResourcePack> packs) {
this.type = type;
this.packs = packs;
this.packs.forEach(pack -> pack.getNamespaces(this.type)
.forEach(namespace -> this.namespacedPacks.computeIfAbsent(namespace, value -> new ArrayList<>())
.add(pack)));
}
@Override
public InputStream open(ResourceType type, Identifier id) throws IOException {
List<ModResourcePack> packs = this.namespacedPacks.get(id.getNamespace());
if (packs != null) {
for (int i = packs.size() - 1; i >= 0; i--) {
ResourcePack pack = packs.get(i);
if (pack.contains(type, id)) {
return pack.open(type, id);
}
}
}
throw new ResourceNotFoundException(null,
String.format("%s/%s/%s", type.getDirectory(), id.getNamespace(), id.getPath()));
}
@Override
public Collection<Identifier> findResources(ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter) {
List<ModResourcePack> packs = this.namespacedPacks.get(namespace);
if (packs == null) {
return Collections.emptyList();
}
Set<Identifier> resources = new HashSet<>();
for (int i = packs.size() - 1; i >= 0; i--) {
ResourcePack pack = packs.get(i);
Collection<Identifier> modResources = pack.findResources(type, namespace, prefix, maxDepth, pathFilter);
resources.addAll(modResources);
}
return resources;
}
@Override
public boolean contains(ResourceType type, Identifier id) {
List<ModResourcePack> packs = this.namespacedPacks.get(id.getNamespace());
if (packs == null) {
return false;
}
for (int i = packs.size() - 1; i >= 0; i--) {
ResourcePack pack = packs.get(i);
if (pack.contains(type, id)) {
return true;
}
}
return false;
}
@Override
public Set<String> getNamespaces(ResourceType type) {
return this.namespacedPacks.keySet();
}
public void appendResources(NamespaceResourceManagerAccessor manager, Identifier id, List<Resource> resources) throws IOException {
List<ModResourcePack> packs = this.namespacedPacks.get(id.getNamespace());
if (packs == null) {
return;
}
Identifier metadataId = NamespaceResourceManagerAccessor.fabric$accessor_getMetadataPath(id);
for (ModResourcePack pack : packs) {
if (pack.contains(manager.getType(), id)) {
InputStream metadataInputStream = pack.contains(manager.getType(), metadataId) ? manager.fabric$accessor_open(metadataId, pack) : null;
resources.add(new ResourceImpl(pack.getName(), id, manager.fabric$accessor_open(id, pack), metadataInputStream));
}
}
}
public String getFullName() {
return this.getName() + " (" + this.packs.stream().map(ResourcePack::getName).collect(Collectors.joining(", ")) + ")";
}
@Override
public void close() {
this.packs.forEach(ResourcePack::close);
}
}

View file

@ -40,6 +40,7 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.InvalidIdentifierException;
import net.fabricmc.fabric.api.resource.ModResourcePack;
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
import net.fabricmc.loader.api.metadata.ModMetadata;
public class ModNioResourcePack extends AbstractFileResourcePack implements ModResourcePack {
@ -51,9 +52,9 @@ public class ModNioResourcePack extends AbstractFileResourcePack implements ModR
private final boolean cacheable;
private final AutoCloseable closer;
private final String separator;
private final boolean defaultEnabled;
private final ResourcePackActivationType activationType;
public ModNioResourcePack(ModMetadata modInfo, Path path, ResourceType type, AutoCloseable closer, boolean defaultEnabled) {
public ModNioResourcePack(ModMetadata modInfo, Path path, ResourceType type, AutoCloseable closer, ResourcePackActivationType activationType) {
super(null);
this.modInfo = modInfo;
this.basePath = path.toAbsolutePath().normalize();
@ -61,8 +62,7 @@ public class ModNioResourcePack extends AbstractFileResourcePack implements ModR
this.cacheable = false; /* TODO */
this.closer = closer;
this.separator = basePath.getFileSystem().getSeparator();
// Specific to registered built-in resource packs.
this.defaultEnabled = defaultEnabled;
this.activationType = activationType;
}
private Path getPath(String filename) {
@ -203,8 +203,8 @@ public class ModNioResourcePack extends AbstractFileResourcePack implements ModR
return modInfo;
}
public boolean shouldBeEnabledByDefault() {
return this.defaultEnabled;
public ResourcePackActivationType getActivationType() {
return this.activationType;
}
@Override

View file

@ -20,7 +20,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourcePackProfile;
import net.minecraft.resource.ResourcePackProvider;
import net.minecraft.resource.ResourcePackSource;
@ -68,19 +67,16 @@ public class ModResourcePackCreator implements ResourcePackProvider {
*/
// Build a list of mod resource packs.
List<ResourcePack> packs = new ArrayList<>();
ModResourcePackUtil.appendModResourcePacks(packs, type);
for (ResourcePack pack : packs) {
if (!(pack instanceof ModResourcePack)) {
throw new RuntimeException("Not a ModResourcePack!");
}
List<ModResourcePack> packs = new ArrayList<>();
ModResourcePackUtil.appendModResourcePacks(packs, type, null);
if (!packs.isEmpty()) {
// Make the resource pack profile for mod resource packs.
// Mod resource packs must always be enabled to avoid issues
// and inserted on top to ensure that they are applied before user resource packs and after default/programmer art resource pack.
ResourcePackProfile resourcePackProfile = ResourcePackProfile.of("fabric/" + ((ModResourcePack) pack).getFabricModMetadata().getId(),
true, () -> pack, factory, ResourcePackProfile.InsertionPosition.TOP,
// @TODO: "inserted on top" comment is deprecated, it does not guarantee the condition "applied before user resource packs".
ResourcePackProfile resourcePackProfile = ResourcePackProfile.of("Fabric Mods",
true, () -> new FabricModResourcePack(this.type, packs), factory, ResourcePackProfile.InsertionPosition.TOP,
RESOURCE_PACK_SOURCE);
if (resourcePackProfile != null) {

View file

@ -17,16 +17,19 @@
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;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.Nullable;
import net.minecraft.SharedConstants;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.fabricmc.fabric.api.resource.ModResourcePack;
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.ModMetadata;
@ -35,19 +38,39 @@ import net.fabricmc.loader.api.metadata.ModMetadata;
* Internal utilities for managing resource packs.
*/
public final class ModResourcePackUtil {
private ModResourcePackUtil() { }
public static void appendModResourcePacks(List<ResourcePack> packList, ResourceType type) {
private ModResourcePackUtil() {
}
/**
* Appends mod resource packs to the given list.
*
* @param packs the resource pack list to append
* @param type the type of resource
* @param subPath the resource pack sub path directory in mods, may be {@code null}
*/
public static void appendModResourcePacks(List<ModResourcePack> packs, ResourceType type, @Nullable String subPath) {
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
if (container.getMetadata().getType().equals("builtin")) {
continue;
}
Path path = container.getRootPath();
ResourcePack pack = new ModNioResourcePack(container.getMetadata(), path, type, null, true);
if (subPath != null) {
Path childPath = path.resolve(subPath.replaceAll("/", 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()) {
packList.add(pack);
packs.add(pack);
}
}
}

View file

@ -40,6 +40,7 @@ import net.minecraft.util.Pair;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
import net.fabricmc.loader.api.ModContainer;
public class ResourceManagerHelperImpl implements ResourceManagerHelper {
@ -57,14 +58,15 @@ public class ResourceManagerHelperImpl implements ResourceManagerHelper {
/**
* Registers a built-in resource pack. Internal implementation.
*
* @param id The identifier of the resource pack.
* @param subPath The sub path in the mod resources.
* @param container The mod container.
* @param enabledByDefault True if enabled by default, else false.
* @return True if successfully registered the resource pack, else false.
* @param id the identifier of the resource pack
* @param subPath the sub path in the mod resources
* @param container the mod container
* @param activationType the activation type of the resource pack
* @return {@code true} if successfully registered the resource pack, else {@code false}
* @see ResourceManagerHelper#registerBuiltinResourcePack(Identifier, ModContainer, ResourcePackActivationType)
* @see ResourceManagerHelper#registerBuiltinResourcePack(Identifier, String, ModContainer, boolean)
*/
public static boolean registerBuiltinResourcePack(Identifier id, String subPath, ModContainer container, boolean enabledByDefault) {
public static boolean registerBuiltinResourcePack(Identifier id, String subPath, ModContainer container, ResourcePackActivationType activationType) {
String separator = container.getRootPath().getFileSystem().getSeparator();
subPath = subPath.replace("/", separator);
@ -76,14 +78,14 @@ public class ResourceManagerHelperImpl implements ResourceManagerHelper {
String name = id.getNamespace() + "/" + id.getPath();
builtinResourcePacks.add(new Pair<>(name, new ModNioResourcePack(container.getMetadata(), resourcePackPath, ResourceType.CLIENT_RESOURCES, null, enabledByDefault) {
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, enabledByDefault) {
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.
@ -96,10 +98,13 @@ public class ResourceManagerHelperImpl implements ResourceManagerHelper {
public static void registerBuiltinResourcePacks(ResourceType resourceType, Consumer<ResourcePackProfile> consumer, ResourcePackProfile.Factory factory) {
// Loop through each registered built-in resource packs and add them if valid.
for (Pair<String, ModNioResourcePack> entry : builtinResourcePacks) {
ModNioResourcePack pack = entry.getRight();
// Add the built-in pack only if namespaces for the specified resource type are present.
if (!entry.getRight().getNamespaces(resourceType).isEmpty()) {
if (!pack.getNamespaces(resourceType).isEmpty()) {
// Make the resource pack profile for built-in pack, should never be always enabled.
ResourcePackProfile profile = ResourcePackProfile.of(entry.getLeft(), false,
ResourcePackProfile profile = ResourcePackProfile.of(entry.getLeft(),
pack.getActivationType() == ResourcePackActivationType.ALWAYS_ENABLED,
entry::getRight, factory, ResourcePackProfile.InsertionPosition.TOP, ResourcePackSource.PACK_SOURCE_BUILTIN);
if (profile != null) {
consumer.accept(profile);

View file

@ -0,0 +1,111 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 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.fabric.impl.resource.loader.client.pack;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import net.minecraft.resource.AbstractFileResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.resource.metadata.ResourceMetadataReader;
import net.minecraft.util.Identifier;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.resource.ModResourcePack;
import net.fabricmc.fabric.impl.resource.loader.GroupResourcePack;
/**
* Represents the Programmer Art resource pack with support for modded content.
*
* <p>Any vanilla resources are provided like in Vanilla through the original programmer art, any missing resources
* will be searched in the provided modded resource packs.
*/
@Environment(EnvType.CLIENT)
public class ProgrammerArtResourcePack extends GroupResourcePack {
private final AbstractFileResourcePack originalResourcePack;
public ProgrammerArtResourcePack(AbstractFileResourcePack originalResourcePack, List<ModResourcePack> modResourcePacks) {
super(ResourceType.CLIENT_RESOURCES, modResourcePacks);
this.originalResourcePack = originalResourcePack;
}
@Override
public InputStream openRoot(String fileName) throws IOException {
if (!fileName.contains("/") && !fileName.contains("\\")) {
// There should be nothing to read at the root of mod's Programmer Art extensions.
return this.originalResourcePack.openRoot(fileName);
} else {
throw new IllegalArgumentException("Root resources can only be filenames, not paths (no / allowed!)");
}
}
@Override
public InputStream open(ResourceType type, Identifier id) throws IOException {
if (this.originalResourcePack.contains(type, id)) {
return this.originalResourcePack.open(type, id);
}
return super.open(type, id);
}
@Override
public Collection<Identifier> findResources(ResourceType type, String namespace, String prefix, int maxDepth, Predicate<String> pathFilter) {
Set<Identifier> resources = new HashSet<>(this.originalResourcePack.findResources(type, namespace, prefix, maxDepth, pathFilter));
resources.addAll(super.findResources(type, namespace, prefix, maxDepth, pathFilter));
return resources;
}
@Override
public boolean contains(ResourceType type, Identifier id) {
return this.originalResourcePack.contains(type, id) || super.contains(type, id);
}
@Override
public Set<String> getNamespaces(ResourceType type) {
Set<String> namespaces = this.originalResourcePack.getNamespaces(type);
namespaces.addAll(super.getNamespaces(type));
return namespaces;
}
@Override
public <T> @Nullable T parseMetadata(ResourceMetadataReader<T> metaReader) throws IOException {
return this.originalResourcePack.parseMetadata(metaReader);
}
@Override
public String getName() {
return "Programmer Art";
}
@Override
public void close() {
this.originalResourcePack.close();
super.close();
}
}

View file

@ -46,7 +46,7 @@ public class MinecraftServerMixin {
if (((ResourcePackProfileAccessor) profile).getResourcePackSource() == ResourcePackSource.PACK_SOURCE_BUILTIN && !profileName.equals("vanilla")) {
ResourcePack pack = profile.createResourcePack();
// Prevents automatic load for built-in data packs provided by mods.
return pack instanceof ModNioResourcePack && !((ModNioResourcePack) pack).shouldBeEnabledByDefault();
return pack instanceof ModNioResourcePack && !((ModNioResourcePack) pack).getActivationType().isEnabledByDefault();
}
return false;

View file

@ -0,0 +1,43 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 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.fabric.mixin.resource.loader;
import java.io.IOException;
import java.io.InputStream;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.resource.NamespaceResourceManager;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
@Mixin(NamespaceResourceManager.class)
public interface NamespaceResourceManagerAccessor {
@Accessor("type")
ResourceType getType();
@Invoker("open")
InputStream fabric$accessor_open(Identifier id, ResourcePack pack) throws IOException;
@Invoker("getMetadataPath")
static Identifier fabric$accessor_getMetadataPath(Identifier id) {
throw new UnsupportedOperationException("Invoker injection failed.");
}
}

View file

@ -0,0 +1,69 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 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.fabric.mixin.resource.loader;
import java.io.IOException;
import java.util.List;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.resource.NamespaceResourceManager;
import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceType;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.impl.resource.loader.GroupResourcePack;
@Mixin(NamespaceResourceManager.class)
public class NamespaceResourceManagerMixin {
private final ThreadLocal<List<Resource>> fabric$getAllResources$resources = new ThreadLocal<>();
@Inject(
method = "getAllResources",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/resource/NamespaceResourceManager;getMetadataPath(Lnet/minecraft/util/Identifier;)Lnet/minecraft/util/Identifier;"
),
locals = LocalCapture.CAPTURE_FAILHARD
)
private void onGetAllResources(Identifier id, CallbackInfoReturnable<List<Resource>> cir, List<Resource> resources) {
this.fabric$getAllResources$resources.set(resources);
}
@Redirect(
method = "getAllResources",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/resource/ResourcePack;contains(Lnet/minecraft/resource/ResourceType;Lnet/minecraft/util/Identifier;)Z"
)
)
private boolean onResourceAdd(ResourcePack pack, ResourceType type, Identifier id) throws IOException {
if (pack instanceof GroupResourcePack) {
((GroupResourcePack) pack).appendResources((NamespaceResourceManagerAccessor) this, id, this.fabric$getAllResources$resources.get());
return false;
}
return pack.contains(type, id);
}
}

View file

@ -19,6 +19,7 @@ package net.fabricmc.fabric.mixin.resource.loader;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@ -28,12 +29,14 @@ import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.resource.ReloadableResourceManagerImpl;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceReloadListener;
import net.minecraft.resource.ResourceType;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourceReloadMonitor;
import net.minecraft.util.Unit;
import net.fabricmc.fabric.impl.resource.loader.GroupResourcePack;
import net.fabricmc.fabric.impl.resource.loader.ResourceManagerHelperImpl;
@Mixin(ReloadableResourceManagerImpl.class)
@ -47,7 +50,20 @@ public class ReloadableResourceManagerImplMixin {
private List<ResourceReloadListener> listeners;
@Inject(at = @At(value = "INVOKE", target = "Lorg/apache/logging/log4j/Logger;isDebugEnabled()Z", remap = false), method = "beginMonitoredReload")
public void reload(Executor prepareExecutor, Executor applyExecutor, CompletableFuture<Unit> initialStage, List<ResourcePack> packs, CallbackInfoReturnable<ResourceReloadMonitor> info) {
private void reload(Executor prepareExecutor, Executor applyExecutor, CompletableFuture<Unit> initialStage, List<ResourcePack> packs, CallbackInfoReturnable<ResourceReloadMonitor> info) {
ResourceManagerHelperImpl.sort(type, this.listeners);
}
// private static synthetic method_29491(Ljava/util/List;)Ljava/lang/Object;
// Supplier lambda in beginMonitoredReload method.
@Inject(method = "method_29491", at = @At("HEAD"), cancellable = true)
private static void getResourcePackNames(List<ResourcePack> packs, CallbackInfoReturnable<String> cir) {
cir.setReturnValue(packs.stream().map(pack -> {
if (pack instanceof GroupResourcePack) {
return ((GroupResourcePack) pack).getFullName();
} else {
return pack.getName();
}
}).collect(Collectors.joining(", ")));
}
}

View file

@ -16,17 +16,27 @@
package net.fabricmc.fabric.mixin.resource.loader.client;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.resource.AbstractFileResourcePack;
import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourcePackProfile;
import net.minecraft.client.resource.ClientBuiltinResourcePackProvider;
import net.minecraft.resource.ResourceType;
import net.fabricmc.fabric.api.resource.ModResourcePack;
import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator;
import net.fabricmc.fabric.impl.resource.loader.ModResourcePackUtil;
import net.fabricmc.fabric.impl.resource.loader.client.pack.ProgrammerArtResourcePack;
@Mixin(ClientBuiltinResourcePackProvider.class)
public class ClientBuiltinResourcePackProviderMixin {
@ -35,4 +45,24 @@ public class ClientBuiltinResourcePackProviderMixin {
// Register mod and built-in resource packs after the vanilla built-in resource packs are registered.
ModResourcePackCreator.CLIENT_RESOURCE_PACK_PROVIDER.register(consumer, factory);
}
// ClientBuiltinResourcePackProvider#method_25454 first lambda.
@Inject(method = "method_25457", at = @At("RETURN"), cancellable = true)
private static void onSupplyZipProgrammerArtPack(File file, CallbackInfoReturnable<ResourcePack> cir) {
AbstractFileResourcePack originalPack = (AbstractFileResourcePack) cir.getReturnValue();
cir.setReturnValue(new ProgrammerArtResourcePack(originalPack, getProgrammerArtModResourcePacks()));
}
// ClientBuiltinResourcePackProvider#method_25454 second lambda.
@Inject(method = "method_25456", at = @At("RETURN"), cancellable = true)
private static void onSupplyDirProgrammerArtPack(File file, CallbackInfoReturnable<ResourcePack> cir) {
AbstractFileResourcePack originalPack = (AbstractFileResourcePack) cir.getReturnValue();
cir.setReturnValue(new ProgrammerArtResourcePack(originalPack, getProgrammerArtModResourcePacks()));
}
private static List<ModResourcePack> getProgrammerArtModResourcePacks() {
List<ModResourcePack> packs = new ArrayList<>();
ModResourcePackUtil.appendModResourcePacks(packs, ResourceType.CLIENT_RESOURCES, "programmer_art");
return packs;
}
}

View file

@ -64,7 +64,7 @@ public class CreateWorldScreenMixin {
for (ResourcePackProfile profile : moddedResourcePacks) {
ResourcePack pack = profile.createResourcePack();
if (pack instanceof ModNioResourcePack && ((ModNioResourcePack) pack).shouldBeEnabledByDefault()) {
if (pack instanceof ModNioResourcePack && ((ModNioResourcePack) pack).getActivationType().isEnabledByDefault()) {
enabled.add(profile.getName());
} else {
disabled.add(profile.getName());

View file

@ -48,7 +48,7 @@ public class GameOptionsMixin {
for (ResourcePackProfile profile : profiles) {
ResourcePack pack = profile.createResourcePack();
if (profile.getSource() == ModResourcePackCreator.RESOURCE_PACK_SOURCE
|| (pack instanceof ModNioResourcePack && ((ModNioResourcePack) pack).shouldBeEnabledByDefault())) {
|| (pack instanceof ModNioResourcePack && ((ModNioResourcePack) pack).getActivationType().isEnabledByDefault())) {
this.resourcePacks.add(profile.getName());
}
}

View file

@ -1,40 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 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.fabric.mixin.resource.loader.client;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.client.gui.screen.pack.PackListWidget;
import net.minecraft.client.gui.screen.pack.PackScreen;
import net.minecraft.client.gui.screen.pack.ResourcePackOrganizer;
import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator;
@Mixin(PackScreen.class)
public class PackScreenMixin {
@Inject(method = "method_29672", at = @At("HEAD"), cancellable = true)
private void addPackEntry(PackListWidget packListWidget, ResourcePackOrganizer.Pack pack, CallbackInfo info) {
// Every mod resource packs should be hidden from the user.
// Registered built-in resource packs should not be hidden as they are optional for the user.
if (pack.getSource() == ModResourcePackCreator.RESOURCE_PACK_SOURCE) {
info.cancel();
}
}
}

View file

@ -7,6 +7,8 @@
"DefaultResourcePackMixin",
"KeyedResourceReloadListenerMixin",
"MinecraftServerMixin",
"NamespaceResourceManagerAccessor",
"NamespaceResourceManagerMixin",
"ReloadableResourceManagerImplMixin",
"ResourcePackManagerMixin",
"ResourcePackManagerAccessor",
@ -17,8 +19,7 @@
"client.CreateWorldScreenMixin",
"client.FontManagerResourceReloadListenerMixin",
"client.GameOptionsMixin",
"client.KeyedResourceReloadListenerClientMixin",
"client.PackScreenMixin"
"client.KeyedResourceReloadListenerClientMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -23,6 +23,7 @@ import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
import net.fabricmc.loader.api.FabricLoader;
public class BuiltinResourcePackTestMod implements ModInitializer {
@ -34,7 +35,8 @@ public class BuiltinResourcePackTestMod implements ModInitializer {
public void onInitialize() {
// Should always be present as it's **this** mod.
FabricLoader.getInstance().getModContainer(MODID)
.map(container -> ResourceManagerHelper.registerBuiltinResourcePack(new Identifier(MODID, "test"), "resourcepacks/test", container, false))
.map(container -> ResourceManagerHelper.registerBuiltinResourcePack(new Identifier(MODID, "test"),
container, ResourcePackActivationType.NORMAL))
.filter(success -> !success).ifPresent(success -> LOGGER.warn("Could not register built-in resource pack."));
}
}

View file

@ -1,6 +1,6 @@
{
"pack": {
"pack_format": 6,
"pack_format": 7,
"description": "Fabric Resource Loader Test Builtin Pack."
}
}