", at = @At("TAIL"))
+ private void removeHiddenPacksInit(Runnable updateCallback, Function iconIdSupplier, ResourcePackManager resourcePackManager, Consumer applier, CallbackInfo ci) {
+ this.enabledPacks.removeIf(profile -> ((FabricResourcePackProfile) profile).fabric_isHidden());
+ this.disabledPacks.removeIf(profile -> ((FabricResourcePackProfile) profile).fabric_isHidden());
+ }
+
+ @Inject(method = "refresh", at = @At("TAIL"))
+ private void removeHiddenPacksRefresh(CallbackInfo ci) {
+ this.enabledPacks.removeIf(profile -> ((FabricResourcePackProfile) profile).fabric_isHidden());
+ this.disabledPacks.removeIf(profile -> ((FabricResourcePackProfile) profile).fabric_isHidden());
+ }
+}
diff --git a/fabric-resource-loader-v0/src/client/resources/fabric-resource-loader-v0.client.mixins.json b/fabric-resource-loader-v0/src/client/resources/fabric-resource-loader-v0.client.mixins.json
index 8230da5131..8e7b6d57dc 100644
--- a/fabric-resource-loader-v0/src/client/resources/fabric-resource-loader-v0.client.mixins.json
+++ b/fabric-resource-loader-v0/src/client/resources/fabric-resource-loader-v0.client.mixins.json
@@ -3,12 +3,13 @@
"package": "net.fabricmc.fabric.mixin.resource.loader.client",
"compatibilityLevel": "JAVA_17",
"client": [
- "VanillaResourcePackProviderMixin",
- "DefaultClientResourcePackProviderMixin",
"CreateWorldScreenMixin",
"FontManagerMixin",
"GameOptionsMixin",
- "KeyedResourceReloadListenerClientMixin"
+ "KeyedResourceReloadListenerClientMixin",
+ "ResourcePackOrganizerMixin",
+ "VanillaResourcePackProviderMixin",
+ "GameOptionsWriteVisitorMixin"
],
"injectors": {
"defaultRequire": 1
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/ModResourcePack.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/ModResourcePack.java
index e66b3606df..b707e742fe 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/ModResourcePack.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/ModResourcePack.java
@@ -29,4 +29,6 @@ public interface ModResourcePack extends ResourcePack {
* resource pack.
*/
ModMetadata getFabricModMetadata();
+
+ ModResourcePack createOverlay(String overlay);
}
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/package-info.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/package-info.java
index fe46117940..95010b1262 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/package-info.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/api/resource/package-info.java
@@ -17,27 +17,48 @@
/**
* The Resource Loader, version 0.
*
- * Quick note about vocabulary in Resource Loader and Minecraft:
- *
- * - Resource Pack refers to both client-sided resource pack and data pack.
- * - Virtual Resource Pack refers to a resource pack that may be generated at runtime, or simply doesn't exist directly on disk.
- * - Group Resource Pack refers to a virtual resource pack that groups multiple resource packs together.
- *
- *
+ * Quick note about vocabulary in Minecraft:
+ * Resource Pack refers to both client-sided resource pack and data pack.
*
* Modded Resource Pack Handling
- * 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.
+ * There are two types of resource packs that mods can provide.
+ *
+ * Bundled Resource Pack
+ * Mods can "bundle" resource packs with the mod by putting files inside the {@code assets} or {@code data}
+ * sub-directories of the {@code resources} directory. They are always enabled, initially loaded after the vanilla pack,
+ * and cannot be disabled. Individual mods' packs are hidden to users in the Resource Pack option screen or the {@code /datapack}
+ * command. Instead, a placeholder pack, named "Fabric Mods", will be displayed. This pack, with the ID {@code fabric}, provides
+ * no resource itself; it is merely a marker used internally.
*
* Built-in Mod Resource Pack
- * 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)}
+ * The Resource Loader adds manually registered mod resource packs. Those resource packs are located inside the
+ * {@code resources/resourcepacks/} directory. For example, a built-in data pack with the ID {@code example:test} should be placed
+ * under {@code resources/resourcepacks/test/data/}. The packs are then 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)}.
+ * Users can manually enable or disable the packs, unless it is specified to be always enabled.
+ *
+ * Programmer Art and High Contrast Support
+ * Bundled resource packs support Programmer Art and High Contrast vanilla resource packs. Simply place assets
+ * under {@code resources/programmer_art/assets/} or {@code resources/high_contrast/assets/}, respectively.
+ * Internally, these are treated as a separate internal pack, loaded just after the respective vanilla pack.
+ * Toggling the vanilla packs automatically toggles the bundled ones as well; you cannot separately enable or disable them.
+ *
+ * Example
+ * Mod A ({@code mod_a} provides a bundled resource pack with both Programmer Art and High Contrast support.
+ * Mod B ({@code mod_b}) provides a bundled resource pack and one built-in resource pack, Extra ({@code mod_b:extra}).
+ * When neither the Programmer Art nor High Contrast is enabled, the user sees "Vanilla", "Fabric Mods", and "Extra" in the
+ * Resource Packs screen. Internally, between Fabric Mods and Extra packs, two hidden packs exist: {@code mod_a} and {@code mod_b}.
+ *
+ * Suppose the user then enables both the Programmer Art and High Contrast, and the Resource Packs screen lists
+ * "Vanilla", "Fabric", Programmer Art, "Extra", and High Contrast. Internally, there are 4 hidden packs:
+ *
+ *
+ * - {@code mod_a} and {@code mod_b} between "Fabric" and Programmer Art.
+ * - {@code mod_a_programmer_art}, containing Mod A's Programmer Art assets, just after Programmer Art pack.
+ * - {@code mod_a_high_contrast}, containing Mod A's High Contrast assets, just after High Contrast pack.
+ *
*
- * Vanilla Built-in Resource Packs
- * The Resource Loader will inject resources into the Programmer Art and High Contrast resource packs for each mod
- * that provides resources in the {@code programmer_art} or {@code high_contrast} top-level directory of the mod
- * whose structure is similar to a normal resource pack.
+ * Note that while the current behavior is to sort bundled resource packs by mod ID in descending order (A to Z), this may change over time.
*
* Resource Reload Listener
* The Resource Loader allows mods to register resource reload listeners through
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/FabricModResourcePack.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/FabricModResourcePack.java
deleted file mode 100644
index 1296eaaf41..0000000000
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/FabricModResourcePack.java
+++ /dev/null
@@ -1,86 +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.impl.resource.loader;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-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.AbstractFileResourcePack;
-import net.minecraft.resource.InputSupplier;
-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 packs) {
- super(type, packs);
- }
-
- @Override
- public InputSupplier openRoot(String... pathSegments) {
- String fileName = String.join("/", pathSegments);
-
- if ("pack.mcmeta".equals(fileName)) {
- String description = "pack.description.modResources";
- String fallback = "Mod resources.";
- String pack = String.format("{\"pack\":{\"pack_format\":" + SharedConstants.getGameVersion().getResourceVersion(type) + ",\"description\":{\"translate\":\"%s\",\"fallback\":\"%s.\"}}}", description, fallback);
- return () -> IOUtils.toInputStream(pack, Charsets.UTF_8);
- } else if ("pack.png".equals(fileName)) {
- return FabricLoader.getInstance().getModContainer("fabric-resource-loader-v0")
- .flatMap(container -> container.getMetadata().getIconPath(512).flatMap(container::findPath))
- .map(path -> (InputSupplier) (() -> Files.newInputStream(path)))
- .orElse(null);
- }
-
- return null;
- }
-
- @Override
- public @Nullable T parseMetadata(ResourceMetadataReader metaReader) throws IOException {
- InputSupplier inputSupplier = this.openRoot("pack.mcmeta");
-
- if (inputSupplier != null) {
- try (InputStream input = inputSupplier.get()) {
- return AbstractFileResourcePack.parseMetadata(metaReader, input);
- }
- } else {
- return null;
- }
- }
-
- @Override
- public String getName() {
- return "fabric";
- }
-
- @Override
- public boolean isAlwaysStable() {
- return true;
- }
-}
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/FabricResourcePackProfile.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/FabricResourcePackProfile.java
new file mode 100644
index 0000000000..5f9d9873c7
--- /dev/null
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/FabricResourcePackProfile.java
@@ -0,0 +1,47 @@
+/*
+ * 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.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * Fabric addition to ResourcePackProfile.
+ * @see ModResourcePackCreator
+ */
+public interface FabricResourcePackProfile {
+ /**
+ * Returns whether the pack is internal and hidden from end users.
+ */
+ default boolean fabric_isHidden() {
+ return false;
+ }
+
+ /**
+ * Returns whether every parent is enabled. If this is not empty, the pack's status
+ * is synced to that of the parent pack(s), where the pack gets enabled if and only
+ * if each of the parent is enabled. Note that non-Fabric packs always return {@code true}.
+ *
+ * @return whether every parent is enabled.
+ */
+ default boolean fabric_parentsEnabled(Set enabled) {
+ return true;
+ }
+
+ default void fabric_setParentsPredicate(Predicate> predicate) {
+ }
+}
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/GroupResourcePack.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/GroupResourcePack.java
deleted file mode 100644
index f4c8981afd..0000000000
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/GroupResourcePack.java
+++ /dev/null
@@ -1,123 +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.impl.resource.loader;
-
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
-
-import net.minecraft.resource.InputSupplier;
-import net.minecraft.resource.NamespaceResourceManager;
-import net.minecraft.resource.Resource;
-import net.minecraft.resource.ResourcePack;
-import net.minecraft.resource.ResourceType;
-import net.minecraft.resource.metadata.ResourceMetadata;
-import net.minecraft.util.Identifier;
-
-/**
- * Represents a group resource pack, holds multiple resource packs as one.
- */
-public abstract class GroupResourcePack implements ResourcePack {
- protected final ResourceType type;
- protected final List extends ResourcePack> packs;
- protected final Map> namespacedPacks = new Object2ObjectOpenHashMap<>();
-
- public GroupResourcePack(ResourceType type, List extends ResourcePack> 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 InputSupplier open(ResourceType type, Identifier id) {
- List extends ResourcePack> packs = this.namespacedPacks.get(id.getNamespace());
-
- if (packs != null) {
- // Last to first, since higher priority packs are at the end
- for (int i = packs.size() - 1; i >= 0; i--) {
- ResourcePack pack = packs.get(i);
- InputSupplier supplier = pack.open(type, id);
-
- if (supplier != null) {
- return supplier;
- }
- }
- }
-
- return null;
- }
-
- @Override
- public void findResources(ResourceType type, String namespace, String prefix, ResultConsumer consumer) {
- List extends ResourcePack> packs = this.namespacedPacks.get(namespace);
-
- if (packs == null) {
- return;
- }
-
- // First to last, since later calls override previously returned data
- for (ResourcePack pack : packs) {
- pack.findResources(type, namespace, prefix, consumer);
- }
- }
-
- @Override
- public Set getNamespaces(ResourceType type) {
- return this.namespacedPacks.keySet();
- }
-
- public void appendResources(ResourceType type, Identifier id, List resources) {
- List extends ResourcePack> packs = this.namespacedPacks.get(id.getNamespace());
-
- if (packs == null) {
- return;
- }
-
- Identifier metadataId = NamespaceResourceManager.getMetadataPath(id);
-
- // Last to first, since higher priority packs are at the end
- for (int i = packs.size() - 1; i >= 0; i--) {
- ResourcePack pack = packs.get(i);
- InputSupplier supplier = pack.open(type, id);
-
- if (supplier != null) {
- InputSupplier metadataSupplier = () -> {
- InputSupplier rawMetadataSupplier = pack.open(this.type, metadataId);
- return rawMetadataSupplier != null ? NamespaceResourceManager.loadMetadata(rawMetadataSupplier) : ResourceMetadata.NONE;
- };
-
- resources.add(new Resource(pack, supplier, metadataSupplier));
- }
- }
- }
-
- public String getFullName() {
- return this.getName() + " (" + this.packs.stream().map(ResourcePack::getName).collect(Collectors.joining(", ")) + ")";
- }
-
- @Override
- public void close() {
- this.packs.forEach(ResourcePack::close);
- }
-}
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 fa56fe02e2..e1c42d4472 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
@@ -33,6 +33,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
@@ -59,14 +60,18 @@ public class ModNioResourcePack implements ResourcePack, ModResourcePack {
private static final FileSystem DEFAULT_FS = FileSystems.getDefault();
private final String id;
- private final ModMetadata modInfo;
+ private final ModContainer mod;
private final List basePaths;
private final ResourceType type;
- private final AutoCloseable closer;
private final ResourcePackActivationType activationType;
private final Map> namespaces;
+ /**
+ * Whether the pack is bundled and loaded by default, as opposed to registered built-in packs.
+ * @see ModResourcePackUtil#appendModResourcePacks(List, ResourceType, String)
+ */
+ private final boolean modBundled;
- public static ModNioResourcePack create(String id, ModContainer mod, String subPath, ResourceType type, ResourcePackActivationType activationType) {
+ public static ModNioResourcePack create(String id, ModContainer mod, String subPath, ResourceType type, ResourcePackActivationType activationType, boolean modBundled) {
List rootPaths = mod.getRootPaths();
List paths;
@@ -89,19 +94,27 @@ public static ModNioResourcePack create(String id, ModContainer mod, String subP
if (paths.isEmpty()) return null;
- ModNioResourcePack ret = new ModNioResourcePack(id, mod.getMetadata(), paths, type, null, activationType);
+ ModNioResourcePack ret = new ModNioResourcePack(id, mod, paths, type, activationType, modBundled);
return ret.getNamespaces(type).isEmpty() ? null : ret;
}
- private ModNioResourcePack(String id, ModMetadata modInfo, List paths, ResourceType type, AutoCloseable closer, ResourcePackActivationType activationType) {
+ private ModNioResourcePack(String id, ModContainer mod, List paths, ResourceType type, ResourcePackActivationType activationType, boolean modBundled) {
this.id = id;
- this.modInfo = modInfo;
+ this.mod = mod;
this.basePaths = paths;
this.type = type;
- this.closer = closer;
this.activationType = activationType;
- this.namespaces = readNamespaces(paths, modInfo.getId());
+ this.modBundled = modBundled;
+ this.namespaces = readNamespaces(paths, mod.getMetadata().getId());
+ }
+
+ @Override
+ public ModNioResourcePack createOverlay(String overlay) {
+ // See DirectoryResourcePack.
+ return new ModNioResourcePack(id, mod, basePaths.stream().map(
+ path -> path.resolve(overlay)
+ ).toList(), type, activationType, modBundled);
}
static Map> readNamespaces(List paths, String modId) {
@@ -188,8 +201,8 @@ private InputSupplier openFile(String filename) {
return () -> Files.newInputStream(path);
}
- if (ModResourcePackUtil.containsDefault(this.modInfo, filename)) {
- return () -> ModResourcePackUtil.openDefault(this.modInfo, this.type, filename);
+ if (ModResourcePackUtil.containsDefault(filename, this.modBundled)) {
+ return () -> ModResourcePackUtil.openDefault(this.mod, this.type, filename);
}
return null;
@@ -239,7 +252,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
}
});
} catch (IOException e) {
- LOGGER.warn("findResources at " + path + " in namespace " + namespace + ", mod " + modInfo.getId() + " failed!", e);
+ LOGGER.warn("findResources at " + path + " in namespace " + namespace + ", mod " + mod.getMetadata().getId() + " failed!", e);
}
}
}
@@ -251,25 +264,18 @@ public Set getNamespaces(ResourceType type) {
@Override
public T parseMetadata(ResourceMetadataReader metaReader) throws IOException {
- try (InputStream is = openFile("pack.mcmeta").get()) {
+ try (InputStream is = Objects.requireNonNull(openFile("pack.mcmeta")).get()) {
return AbstractFileResourcePack.parseMetadata(metaReader, is);
}
}
@Override
public void close() {
- if (closer != null) {
- try {
- closer.close();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
}
@Override
public ModMetadata getFabricModMetadata() {
- return modInfo;
+ return mod.getMetadata();
}
public ResourcePackActivationType getActivationType() {
@@ -281,6 +287,11 @@ public String getName() {
return id;
}
+ @Override
+ public boolean isAlwaysStable() {
+ return this.modBundled;
+ }
+
private static boolean exists(Path path) {
// NIO Files.exists is notoriously slow when checking the file system
return path.getFileSystem() == DEFAULT_FS ? path.toFile().exists() : Files.exists(path);
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackCreator.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackCreator.java
index 484c2deab0..ea3464c4d9 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackCreator.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackCreator.java
@@ -18,15 +18,17 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.VisibleForTesting;
-import net.minecraft.resource.OverlayResourcePack;
-import net.minecraft.resource.ResourcePack;
import net.minecraft.resource.ResourcePackProfile;
import net.minecraft.resource.ResourcePackProvider;
import net.minecraft.resource.ResourcePackSource;
import net.minecraft.resource.ResourceType;
-import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
import net.fabricmc.fabric.api.resource.ModResourcePack;
@@ -35,6 +37,22 @@
* Represents a resource pack provider for mods and built-in mods resource packs.
*/
public class ModResourcePackCreator implements ResourcePackProvider {
+ /**
+ * The ID of the root resource pack profile for bundled packs.
+ */
+ public static final String FABRIC = "fabric";
+ private static final String PROGRAMMER_ART = "programmer_art";
+ private static final String HIGH_CONTRAST = "high_contrast";
+ public static final Set POST_CHANGE_HANDLE_REQUIRED = Set.of(FABRIC, PROGRAMMER_ART, HIGH_CONTRAST);
+ @VisibleForTesting
+ public static final Predicate> BASE_PARENT = enabled -> enabled.contains(FABRIC);
+ @VisibleForTesting
+ public static final Predicate> PROGRAMMER_ART_PARENT = enabled -> enabled.contains(FABRIC) && enabled.contains(PROGRAMMER_ART);
+ @VisibleForTesting
+ public static final Predicate> HIGH_CONTRAST_PARENT = enabled -> enabled.contains(FABRIC) && enabled.contains(HIGH_CONTRAST);
+ /**
+ * This can be used to check if a pack profile is for mod-provided packs.
+ */
public static final ResourcePackSource RESOURCE_PACK_SOURCE = new ResourcePackSource() {
@Override
public Text decorate(Text packName) {
@@ -72,49 +90,51 @@ public void register(Consumer consumer) {
4. User resource packs
*/
+ consumer.accept(ResourcePackProfile.create(
+ FABRIC,
+ Text.translatable("pack.name.fabricMods"),
+ true,
+ new PlaceholderResourcePack.Factory(this.type),
+ this.type,
+ ResourcePackProfile.InsertionPosition.TOP,
+ RESOURCE_PACK_SOURCE
+ ));
+
// Build a list of mod resource packs.
- List 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 they are inserted
- // on top to ensure that they are applied after vanilla built-in resource packs.
- MutableText title = Text.translatable("pack.name.fabricMods");
- ResourcePackProfile resourcePackProfile = ResourcePackProfile.create("fabric", title, true, new ResourcePackProfile.PackFactory() {
- @Override
- public ResourcePack open(String name) {
- return new FabricModResourcePack(type, packs);
- }
-
- @Override
- public ResourcePack openWithOverlays(String name, ResourcePackProfile.Metadata metadata) {
- final ResourcePack basePack = open(name);
- final List overlays = metadata.overlays();
-
- if (overlays.isEmpty()) {
- return basePack;
- }
-
- final List overlayedPacks = new ArrayList<>(overlays.size());
-
- for (String overlay : overlays) {
- List innerPacks = new ArrayList<>();
- ModResourcePackUtil.appendModResourcePacks(innerPacks, type, overlay);
-
- overlayedPacks.add(new FabricModResourcePack(type, innerPacks));
- }
-
- return new OverlayResourcePack(basePack, overlayedPacks);
- }
- }, type, ResourcePackProfile.InsertionPosition.TOP, RESOURCE_PACK_SOURCE);
-
- if (resourcePackProfile != null) {
- consumer.accept(resourcePackProfile);
- }
+ registerModPack(consumer, null, BASE_PARENT);
+
+ if (this.type == ResourceType.CLIENT_RESOURCES) {
+ // Programmer Art/High Contrast data packs can never be enabled.
+ registerModPack(consumer, PROGRAMMER_ART, PROGRAMMER_ART_PARENT);
+ registerModPack(consumer, HIGH_CONTRAST, HIGH_CONTRAST_PARENT);
}
// Register all built-in resource packs provided by mods.
ResourceManagerHelperImpl.registerBuiltinResourcePacks(this.type, consumer);
}
+
+ private void registerModPack(Consumer consumer, @Nullable String subPath, Predicate> parents) {
+ List packs = new ArrayList<>();
+ ModResourcePackUtil.appendModResourcePacks(packs, this.type, subPath);
+
+ for (ModResourcePack pack : packs) {
+ Text displayName = subPath == null
+ ? Text.translatable("pack.name.fabricMod", pack.getFabricModMetadata().getName())
+ : Text.translatable("pack.name.fabricMod.subPack", pack.getFabricModMetadata().getName(), Text.translatable("resourcePack." + subPath + ".name"));
+ ResourcePackProfile profile = ResourcePackProfile.create(
+ subPath == null ? pack.getName() : pack.getName() + "_" + subPath,
+ displayName,
+ subPath == null,
+ new ModResourcePackFactory(pack),
+ this.type,
+ ResourcePackProfile.InsertionPosition.TOP,
+ RESOURCE_PACK_SOURCE
+ );
+
+ if (profile != null) {
+ ((FabricResourcePackProfile) profile).fabric_setParentsPredicate(parents);
+ consumer.accept(profile);
+ }
+ }
+ }
}
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackFactory.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackFactory.java
new file mode 100644
index 0000000000..8282470ce5
--- /dev/null
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModResourcePackFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+
+import net.minecraft.resource.OverlayResourcePack;
+import net.minecraft.resource.ResourcePack;
+import net.minecraft.resource.ResourcePackProfile;
+
+import net.fabricmc.fabric.api.resource.ModResourcePack;
+
+public record ModResourcePackFactory(ModResourcePack pack) implements ResourcePackProfile.PackFactory {
+ @Override
+ public ResourcePack open(String name) {
+ return pack;
+ }
+
+ @Override
+ public ResourcePack openWithOverlays(String name, ResourcePackProfile.Metadata metadata) {
+ if (metadata.overlays().isEmpty()) {
+ return pack;
+ } else {
+ List overlays = new ArrayList<>(metadata.overlays().size());
+
+ for (String overlay : metadata.overlays()) {
+ overlays.add(pack.createOverlay(overlay));
+ }
+
+ return new OverlayResourcePack(pack, overlays);
+ }
+ }
+}
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 1038e88dee..093b95d274 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
@@ -16,12 +16,19 @@
package net.fabricmc.fabric.impl.resource.loader;
+import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import com.google.common.base.Charsets;
@@ -29,6 +36,8 @@
import com.google.gson.JsonObject;
import org.apache.commons.io.IOUtils;
import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import net.minecraft.SharedConstants;
import net.minecraft.resource.DataConfiguration;
@@ -37,6 +46,7 @@
import net.minecraft.resource.ResourcePackProfile;
import net.minecraft.resource.ResourceType;
import net.minecraft.resource.featuretoggle.FeatureFlags;
+import net.minecraft.resource.metadata.PackResourceMetadata;
import net.minecraft.text.Text;
import net.fabricmc.fabric.api.resource.ModResourcePack;
@@ -49,7 +59,8 @@
* Internal utilities for managing resource packs.
*/
public final class ModResourcePackUtil {
- private static final Gson GSON = new Gson();
+ public static final Gson GSON = new Gson();
+ private static final Logger LOGGER = LoggerFactory.getLogger(ModResourcePackUtil.class);
private ModResourcePackUtil() {
}
@@ -67,7 +78,7 @@ public static void appendModResourcePacks(List packs, ResourceT
continue;
}
- ModResourcePack pack = ModNioResourcePack.create(container.getMetadata().getId(), container, subPath, type, ResourcePackActivationType.ALWAYS_ENABLED);
+ ModResourcePack pack = ModNioResourcePack.create(container.getMetadata().getId(), container, subPath, type, ResourcePackActivationType.ALWAYS_ENABLED, true);
if (pack != null) {
packs.add(pack);
@@ -75,25 +86,76 @@ public static void appendModResourcePacks(List packs, ResourceT
}
}
- public static boolean containsDefault(ModMetadata info, String filename) {
- return "pack.mcmeta".equals(filename);
+ public static void refreshAutoEnabledPacks(List enabledProfiles, Map allProfiles) {
+ LOGGER.debug("[Fabric] Starting internal pack sorting with: {}", enabledProfiles.stream().map(ResourcePackProfile::getName).toList());
+ enabledProfiles.removeIf(profile -> ((FabricResourcePackProfile) profile).fabric_isHidden());
+ LOGGER.debug("[Fabric] Removed all internal packs, result: {}", enabledProfiles.stream().map(ResourcePackProfile::getName).toList());
+ ListIterator it = enabledProfiles.listIterator();
+ Set seen = new LinkedHashSet<>();
+
+ while (it.hasNext()) {
+ ResourcePackProfile profile = it.next();
+ seen.add(profile.getName());
+
+ for (ResourcePackProfile p : allProfiles.values()) {
+ FabricResourcePackProfile fp = (FabricResourcePackProfile) p;
+
+ if (fp.fabric_isHidden() && fp.fabric_parentsEnabled(seen) && seen.add(p.getName())) {
+ it.add(p);
+ LOGGER.debug("[Fabric] cur @ {}, auto-enabled {}, currently enabled: {}", profile.getName(), p.getName(), seen);
+ }
+ }
+ }
+
+ LOGGER.debug("[Fabric] Final sorting result: {}", enabledProfiles.stream().map(ResourcePackProfile::getName).toList());
+ }
+
+ public static boolean containsDefault(String filename, boolean modBundled) {
+ return "pack.mcmeta".equals(filename) || (modBundled && "pack.png".equals(filename));
}
- public static InputStream openDefault(ModMetadata info, ResourceType type, String filename) {
+ public static InputStream getDefaultIcon() throws IOException {
+ Optional loaderIconPath = FabricLoader.getInstance().getModContainer("fabric-resource-loader-v0")
+ .flatMap(resourceLoaderContainer -> resourceLoaderContainer.getMetadata().getIconPath(512).flatMap(resourceLoaderContainer::findPath));
+
+ if (loaderIconPath.isPresent()) {
+ return Files.newInputStream(loaderIconPath.get());
+ }
+
+ // Should never happen in practice
+ return null;
+ }
+
+ public static InputStream openDefault(ModContainer container, ResourceType type, String filename) throws IOException {
switch (filename) {
case "pack.mcmeta":
- String description = Objects.requireNonNullElse(info.getName(), "");
+ String description = Objects.requireNonNullElse(container.getMetadata().getName(), "");
String metadata = serializeMetadata(SharedConstants.getGameVersion().getResourceVersion(type), description);
return IOUtils.toInputStream(metadata, Charsets.UTF_8);
+ case "pack.png":
+ Optional path = container.getMetadata().getIconPath(512).flatMap(container::findPath);
+
+ if (path.isPresent()) {
+ return Files.newInputStream(path.get());
+ } else {
+ return getDefaultIcon();
+ }
default:
return null;
}
}
+ public static PackResourceMetadata getMetadataPack(int packVersion, Text description) {
+ return new PackResourceMetadata(description, packVersion, Optional.empty());
+ }
+
+ public static JsonObject getMetadataPackJson(int packVersion, Text description) {
+ return PackResourceMetadata.SERIALIZER.toJson(getMetadataPack(packVersion, description));
+ }
+
public static String serializeMetadata(int packVersion, String description) {
- JsonObject pack = new JsonObject();
- pack.addProperty("pack_format", packVersion);
- pack.addProperty("description", description);
+ // This seems to be still manually deserialized
+ JsonObject pack = getMetadataPackJson(packVersion, Text.literal(description));
JsonObject metadata = new JsonObject();
metadata.add("pack", pack);
return GSON.toJson(metadata);
@@ -123,8 +185,13 @@ public static DataConfiguration createDefaultDataConfiguration() {
// This ensures that any built-in registered data packs by mods which needs to be enabled by default are
// as the data pack screen automatically put any data pack as disabled except the Default data pack.
for (ResourcePackProfile profile : moddedResourcePacks) {
+ if (profile.getSource() == ModResourcePackCreator.RESOURCE_PACK_SOURCE) {
+ enabled.add(profile.getName());
+ continue;
+ }
+
try (ResourcePack pack = profile.createResourcePack()) {
- if (pack instanceof FabricModResourcePack || (pack instanceof ModNioResourcePack && ((ModNioResourcePack) pack).getActivationType().isEnabledByDefault())) {
+ if (pack instanceof ModNioResourcePack && ((ModNioResourcePack) pack).getActivationType().isEnabledByDefault()) {
enabled.add(profile.getName());
} else {
disabled.add(profile.getName());
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/PlaceholderResourcePack.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/PlaceholderResourcePack.java
new file mode 100644
index 0000000000..18b578cd3c
--- /dev/null
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/PlaceholderResourcePack.java
@@ -0,0 +1,110 @@
+/*
+ * 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.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Set;
+
+import org.apache.commons.io.IOUtils;
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.SharedConstants;
+import net.minecraft.resource.InputSupplier;
+import net.minecraft.resource.ResourcePack;
+import net.minecraft.resource.ResourcePackProfile;
+import net.minecraft.resource.ResourceType;
+import net.minecraft.resource.metadata.PackResourceMetadata;
+import net.minecraft.resource.metadata.ResourceMetadataMap;
+import net.minecraft.resource.metadata.ResourceMetadataReader;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+public record PlaceholderResourcePack(ResourceType type) implements ResourcePack {
+ private static final Text DESCRIPTION_TEXT = Text.translatable("pack.description.modResources");
+
+ public PackResourceMetadata getMetadata() {
+ return ModResourcePackUtil.getMetadataPack(
+ SharedConstants.getGameVersion().getResourceVersion(type),
+ DESCRIPTION_TEXT
+ );
+ }
+
+ @Nullable
+ @Override
+ public InputSupplier openRoot(String... segments) {
+ if (segments.length > 0) {
+ switch (segments[0]) {
+ case "pack.mcmeta":
+ return () -> {
+ String metadata = ModResourcePackUtil.GSON.toJson(PackResourceMetadata.SERIALIZER.toJson(getMetadata()));
+ return IOUtils.toInputStream(metadata, StandardCharsets.UTF_8);
+ };
+ case "pack.png":
+ return ModResourcePackUtil::getDefaultIcon;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * This pack has no actual contents.
+ */
+ @Nullable
+ @Override
+ public InputSupplier open(ResourceType type, Identifier id) {
+ return null;
+ }
+
+ @Override
+ public void findResources(ResourceType type, String namespace, String prefix, ResultConsumer consumer) {
+ }
+
+ @Override
+ public Set getNamespaces(ResourceType type) {
+ return Collections.emptySet();
+ }
+
+ @Nullable
+ @Override
+ public T parseMetadata(ResourceMetadataReader metaReader) {
+ return ResourceMetadataMap.of(PackResourceMetadata.SERIALIZER, getMetadata()).get(metaReader);
+ }
+
+ @Override
+ public String getName() {
+ return ModResourcePackCreator.FABRIC;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ public record Factory(ResourceType type) implements ResourcePackProfile.PackFactory {
+ @Override
+ public ResourcePack open(String name) {
+ return new PlaceholderResourcePack(this.type);
+ }
+
+ @Override
+ public ResourcePack openWithOverlays(String name, ResourcePackProfile.Metadata metadata) {
+ return open(name);
+ }
+ }
+}
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 e7b25f74a2..d45c6d8ce9 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
@@ -74,8 +74,8 @@ public static boolean registerBuiltinResourcePack(Identifier id, String subPath,
List paths = container.getRootPaths();
String separator = paths.get(0).getFileSystem().getSeparator();
subPath = subPath.replace("/", separator);
- ModNioResourcePack resourcePack = ModNioResourcePack.create(id.toString(), container, subPath, ResourceType.CLIENT_RESOURCES, activationType);
- ModNioResourcePack dataPack = ModNioResourcePack.create(id.toString(), container, subPath, ResourceType.SERVER_DATA, activationType);
+ ModNioResourcePack resourcePack = ModNioResourcePack.create(id.toString(), container, subPath, ResourceType.CLIENT_RESOURCES, activationType, false);
+ ModNioResourcePack dataPack = ModNioResourcePack.create(id.toString(), container, subPath, ResourceType.SERVER_DATA, activationType, false);
if (resourcePack == null && dataPack == null) return false;
if (resourcePack != null) {
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourcePackSourceTracker.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourcePackSourceTracker.java
index aecdd4945c..6374b91207 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourcePackSourceTracker.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ResourcePackSourceTracker.java
@@ -28,7 +28,6 @@
* See {@link net.fabricmc.fabric.mixin.resource.loader.ResourcePackProfileMixin ResourcePackProfileMixin}.
*
* The sources are later read for use in {@link FabricResource} and {@link FabricResourceImpl}.
- * See {@link net.fabricmc.fabric.mixin.resource.loader.NamespaceResourceManagerMixin NamespaceResourceManagerMixin}.
*/
public final class ResourcePackSourceTracker {
// Use a weak hash map so that if resource packs would be deleted, this won't keep them alive.
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/DatapackCommandMixin.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/DatapackCommandMixin.java
new file mode 100644
index 0000000000..8d2338b1f4
--- /dev/null
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/DatapackCommandMixin.java
@@ -0,0 +1,68 @@
+/*
+ * 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.util.Collection;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
+import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
+import com.llamalad7.mixinextras.sugar.Local;
+import com.mojang.brigadier.context.CommandContext;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Unique;
+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 net.minecraft.resource.ResourcePackManager;
+import net.minecraft.resource.ResourcePackProfile;
+import net.minecraft.server.command.DatapackCommand;
+import net.minecraft.server.command.ServerCommandSource;
+import net.minecraft.text.Text;
+
+import net.fabricmc.fabric.impl.resource.loader.FabricResourcePackProfile;
+
+/**
+ * Disables enabling/disabling internal data packs.
+ * Listing them is still allowed, but they do not appear in suggestions.
+ */
+@Mixin(DatapackCommand.class)
+public class DatapackCommandMixin {
+ @Unique
+ private static final DynamicCommandExceptionType INTERNAL_PACK_EXCEPTION = new DynamicCommandExceptionType(
+ packName -> Text.stringifiedTranslatable("commands.datapack.fabric.internal", packName));
+
+ @Redirect(method = "method_13136", at = @At(value = "INVOKE", target = "Lnet/minecraft/resource/ResourcePackManager;getEnabledNames()Ljava/util/Collection;"))
+ private static Collection filterEnabledPackSuggestions(ResourcePackManager dataPackManager) {
+ return dataPackManager.getEnabledProfiles().stream().filter(profile -> !((FabricResourcePackProfile) profile).fabric_isHidden()).map(ResourcePackProfile::getName).toList();
+ }
+
+ @WrapOperation(method = "method_13120", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;filter(Ljava/util/function/Predicate;)Ljava/util/stream/Stream;", ordinal = 0))
+ private static Stream filterDisabledPackSuggestions(Stream instance, Predicate super ResourcePackProfile> predicate, Operation> original) {
+ return original.call(instance, predicate).filter(profile -> !((FabricResourcePackProfile) profile).fabric_isHidden());
+ }
+
+ @Inject(method = "getPackContainer", at = @At(value = "INVOKE", target = "Ljava/util/Collection;contains(Ljava/lang/Object;)Z", shift = At.Shift.BEFORE))
+ private static void errorOnInternalPack(CommandContext context, String name, boolean enable, CallbackInfoReturnable cir, @Local ResourcePackProfile profile) throws CommandSyntaxException {
+ if (((FabricResourcePackProfile) profile).fabric_isHidden()) throw INTERNAL_PACK_EXCEPTION.create(profile.getName());
+ }
+}
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/NamespaceResourceManagerMixin.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/NamespaceResourceManagerMixin.java
deleted file mode 100644
index 74f3972a59..0000000000
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/NamespaceResourceManagerMixin.java
+++ /dev/null
@@ -1,63 +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;
-
-import java.io.InputStream;
-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.InputSupplier;
-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;
-
-/**
- * Patches getAllResources and method_41265 to work with GroupResourcePack.
- */
-@Mixin(NamespaceResourceManager.class)
-public class NamespaceResourceManagerMixin {
- private final ThreadLocal> fabric$getAllResources$resources = new ThreadLocal<>();
-
- @Inject(method = "getAllResources",
- at = @At(value = "INVOKE", target = "Ljava/util/List;size()I"),
- locals = LocalCapture.CAPTURE_FAILHARD)
- private void onGetAllResources(Identifier id, CallbackInfoReturnable> cir, Identifier metadataId, List resources) {
- this.fabric$getAllResources$resources.set(resources);
- }
-
- @Redirect(method = "getAllResources",
- at = @At(value = "INVOKE", target = "Lnet/minecraft/resource/ResourcePack;open(Lnet/minecraft/resource/ResourceType;Lnet/minecraft/util/Identifier;)Lnet/minecraft/resource/InputSupplier;"))
- private InputSupplier onResourceAdd(ResourcePack pack, ResourceType type, Identifier id) {
- if (pack instanceof GroupResourcePack) {
- ((GroupResourcePack) pack).appendResources(type, id, this.fabric$getAllResources$resources.get());
-
- return null;
- }
-
- return pack.open(type, id);
- }
-}
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ReloadableResourceManagerImplMixin.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ReloadableResourceManagerImplMixin.java
deleted file mode 100644
index 80ae55c5a5..0000000000
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ReloadableResourceManagerImplMixin.java
+++ /dev/null
@@ -1,46 +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;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-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.CallbackInfoReturnable;
-
-import net.minecraft.resource.ReloadableResourceManagerImpl;
-import net.minecraft.resource.ResourcePack;
-
-import net.fabricmc.fabric.impl.resource.loader.GroupResourcePack;
-
-@Mixin(ReloadableResourceManagerImpl.class)
-public class ReloadableResourceManagerImplMixin {
- // 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 packs, CallbackInfoReturnable cir) {
- cir.setReturnValue(packs.stream().map(pack -> {
- if (pack instanceof GroupResourcePack) {
- return ((GroupResourcePack) pack).getFullName();
- } else {
- return pack.getName();
- }
- }).collect(Collectors.joining(", ")));
- }
-}
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourceMixin.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourceMixin.java
index bd510500c2..3e89f84642 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourceMixin.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourceMixin.java
@@ -27,8 +27,6 @@
/**
* Implements {@link FabricResource} (resource source getter/setter)
* for vanilla's basic {@link Resource} used for most game resources.
- *
- * @see NamespaceResourceManagerMixin the usage site for this mixin
*/
@Mixin(Resource.class)
class ResourceMixin implements FabricResource {
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourcePackManagerMixin.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourcePackManagerMixin.java
index f6a0bd59e3..bfa3157006 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourcePackManagerMixin.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourcePackManagerMixin.java
@@ -16,16 +16,25 @@
package net.fabricmc.fabric.mixin.resource.loader;
+import java.util.Collection;
import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
+import com.llamalad7.mixinextras.sugar.Local;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
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.FileResourcePackProvider;
import net.minecraft.resource.ResourcePackManager;
@@ -34,14 +43,22 @@
import net.minecraft.resource.ResourcePackSource;
import net.minecraft.resource.ResourceType;
+import net.fabricmc.fabric.impl.resource.loader.FabricResourcePackProfile;
import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator;
+import net.fabricmc.fabric.impl.resource.loader.ModResourcePackUtil;
@Mixin(ResourcePackManager.class)
-public abstract class ResourcePackManagerMixin {
+public abstract class ResourcePackManagerMixin {
+ @Unique
+ private static final Logger LOGGER = LoggerFactory.getLogger("ResourcePackManagerMixin");
+
@Shadow
@Final
@Mutable
- private Set providers;
+ public Set providers;
+
+ @Shadow
+ private Map profiles;
@Inject(method = "", at = @At("RETURN"))
public void construct(ResourcePackProvider[] resourcePackProviders, CallbackInfo info) {
@@ -65,4 +82,25 @@ public void construct(ResourcePackProvider[] resourcePackProviders, CallbackInfo
providers.add(new ModResourcePackCreator(ResourceType.SERVER_DATA));
}
}
+
+ @Inject(method = "buildEnabledProfiles", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableList;copyOf(Ljava/util/Collection;)Lcom/google/common/collect/ImmutableList;", shift = At.Shift.BEFORE))
+ private void handleAutoEnableDisable(Collection enabledNames, CallbackInfoReturnable> cir, @Local List enabledAfterFirstRun) {
+ ModResourcePackUtil.refreshAutoEnabledPacks(enabledAfterFirstRun, this.profiles);
+ }
+
+ @Inject(method = "enable", at = @At(value = "INVOKE", target = "Ljava/util/List;add(Ljava/lang/Object;)Z", shift = At.Shift.AFTER))
+ private void handleAutoEnable(String profile, CallbackInfoReturnable cir, @Local List newlyEnabled) {
+ if (ModResourcePackCreator.POST_CHANGE_HANDLE_REQUIRED.contains(profile)) {
+ ModResourcePackUtil.refreshAutoEnabledPacks(newlyEnabled, this.profiles);
+ }
+ }
+
+ @Inject(method = "disable", at = @At(value = "INVOKE", target = "Ljava/util/List;remove(Ljava/lang/Object;)Z"))
+ private void handleAutoDisable(String profile, CallbackInfoReturnable cir, @Local List enabled) {
+ if (ModResourcePackCreator.POST_CHANGE_HANDLE_REQUIRED.contains(profile)) {
+ Set currentlyEnabled = enabled.stream().map(ResourcePackProfile::getName).collect(Collectors.toSet());
+ enabled.removeIf(p -> !((FabricResourcePackProfile) p).fabric_parentsEnabled(currentlyEnabled));
+ LOGGER.debug("[Fabric] Internal pack auto-removed upon disabling {}, result: {}", profile, enabled.stream().map(ResourcePackProfile::getName).toList());
+ }
+ }
}
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourcePackProfileMixin.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourcePackProfileMixin.java
index 05f8c413a9..a9c2ffec73 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourcePackProfileMixin.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/ResourcePackProfileMixin.java
@@ -16,9 +16,13 @@
package net.fabricmc.fabric.mixin.resource.loader;
+import java.util.Set;
+import java.util.function.Predicate;
+
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
+import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@@ -27,6 +31,7 @@
import net.minecraft.resource.ResourcePackProfile;
import net.minecraft.resource.ResourcePackSource;
+import net.fabricmc.fabric.impl.resource.loader.FabricResourcePackProfile;
import net.fabricmc.fabric.impl.resource.loader.ResourcePackSourceTracker;
/**
@@ -37,13 +42,32 @@
* @see ResourcePackSourceTracker
*/
@Mixin(ResourcePackProfile.class)
-abstract class ResourcePackProfileMixin {
+abstract class ResourcePackProfileMixin implements FabricResourcePackProfile {
+ @Unique
+ private static final Predicate> DEFAULT_PARENT_PREDICATE = parents -> true;
@Shadow
@Final
private ResourcePackSource source;
+ @Unique
+ private Predicate> parentsPredicate = DEFAULT_PARENT_PREDICATE;
@Inject(method = "createResourcePack", at = @At("RETURN"))
private void onCreateResourcePack(CallbackInfoReturnable info) {
ResourcePackSourceTracker.setSource(info.getReturnValue(), source);
}
+
+ @Override
+ public boolean fabric_isHidden() {
+ return parentsPredicate != DEFAULT_PARENT_PREDICATE;
+ }
+
+ @Override
+ public boolean fabric_parentsEnabled(Set enabled) {
+ return parentsPredicate.test(enabled);
+ }
+
+ @Override
+ public void fabric_setParentsPredicate(Predicate> predicate) {
+ this.parentsPredicate = predicate;
+ }
}
diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/en_us.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/en_us.json
index 3611a8ce40..892c6fe421 100644
--- a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/en_us.json
+++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/en_us.json
@@ -3,5 +3,7 @@
"pack.source.fabricmod": "Fabric mod",
"pack.source.builtinMod": "built-in: %s",
"pack.name.fabricMod": "Fabric Mod \"%s\"",
- "pack.name.fabricMods": "Fabric Mods"
+ "pack.name.fabricMods": "Fabric Mods",
+ "pack.name.fabricMod.subPack": "Fabric Mod \"%s\" (%s)",
+ "commands.datapack.fabric.internal": "Cannot enable or disable Fabric internal pack \"%s\"."
}
diff --git a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/ja_jp.json b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/ja_jp.json
index 7d57b4def8..1bd4a28edf 100644
--- a/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/ja_jp.json
+++ b/fabric-resource-loader-v0/src/main/resources/assets/fabric-resource-loader-v0/lang/ja_jp.json
@@ -1,5 +1,6 @@
{
"pack.source.fabricmod": "Fabric mod",
"pack.source.builtinMod": "ビルトイン: %s",
- "pack.name.fabricMods": "Fabric Mod"
+ "pack.name.fabricMods": "Fabric Mod",
+ "commands.datapack.fabric.internal": "「%s」はFabric内部で管理されているため、有効化・無効化できません"
}
diff --git a/fabric-resource-loader-v0/src/main/resources/fabric-resource-loader-v0.mixins.json b/fabric-resource-loader-v0/src/main/resources/fabric-resource-loader-v0.mixins.json
index a33a2ec0c0..cf05b13a5e 100644
--- a/fabric-resource-loader-v0/src/main/resources/fabric-resource-loader-v0.mixins.json
+++ b/fabric-resource-loader-v0/src/main/resources/fabric-resource-loader-v0.mixins.json
@@ -3,11 +3,10 @@
"package": "net.fabricmc.fabric.mixin.resource.loader",
"compatibilityLevel": "JAVA_17",
"mixins": [
+ "DatapackCommandMixin",
"KeyedResourceReloadListenerMixin",
"LifecycledResourceManagerImplMixin",
"MinecraftServerMixin",
- "NamespaceResourceManagerMixin",
- "ReloadableResourceManagerImplMixin",
"ResourceMixin",
"ResourcePackManagerMixin",
"ResourcePackProfileMixin",
diff --git a/fabric-resource-loader-v0/src/test/java/net/fabricmc/fabric/test/resource/loader/unit/ModResourcePackUtilTests.java b/fabric-resource-loader-v0/src/test/java/net/fabricmc/fabric/test/resource/loader/unit/ModResourcePackUtilTests.java
new file mode 100644
index 0000000000..8921f275cf
--- /dev/null
+++ b/fabric-resource-loader-v0/src/test/java/net/fabricmc/fabric/test/resource/loader/unit/ModResourcePackUtilTests.java
@@ -0,0 +1,248 @@
+/*
+ * 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.test.resource.loader.unit;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Predicate;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import org.jetbrains.annotations.Nullable;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import net.minecraft.Bootstrap;
+import net.minecraft.SharedConstants;
+import net.minecraft.resource.ResourcePackProfile;
+
+import net.fabricmc.fabric.impl.resource.loader.FabricResourcePackProfile;
+import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator;
+import net.fabricmc.fabric.impl.resource.loader.ModResourcePackUtil;
+
+public class ModResourcePackUtilTests {
+ private static final Gson GSON = new Gson();
+
+ @BeforeAll
+ static void beforeAll() {
+ SharedConstants.createGameVersion();
+ Bootstrap.initialize();
+ }
+
+ @Test
+ void testRefreshAutoEnabledPacks() {
+ // Vanilla uses tree map, and we test the behavior
+ Map profiles = new TreeMap<>();
+ Map modAProfiles = new TreeMap<>();
+ Map modBProfiles = new TreeMap<>();
+ Map allProfiles = new TreeMap<>();
+ ResourcePackProfile vanilla = mockProfile(profiles, "vanilla", null);
+ ResourcePackProfile fabric = mockProfile(profiles, ModResourcePackCreator.FABRIC, null);
+ ResourcePackProfile modA = mockProfile(modAProfiles, "mod_a", ModResourcePackCreator.BASE_PARENT);
+ ResourcePackProfile modAProg = mockProfile(modAProfiles, "mod_a_programmer_art", ModResourcePackCreator.PROGRAMMER_ART_PARENT);
+ ResourcePackProfile modAHigh = mockProfile(modAProfiles, "mod_a_high_contrast", ModResourcePackCreator.HIGH_CONTRAST_PARENT);
+ ResourcePackProfile modB = mockProfile(modBProfiles, "mod_b", ModResourcePackCreator.BASE_PARENT);
+ ResourcePackProfile modBProg = mockProfile(modBProfiles, "mod_b_programmer_art", ModResourcePackCreator.PROGRAMMER_ART_PARENT);
+ ResourcePackProfile modBHigh = mockProfile(modBProfiles, "mod_b_high_contrast", ModResourcePackCreator.HIGH_CONTRAST_PARENT);
+ ResourcePackProfile programmerArt = mockProfile(profiles, "programmer_art", null);
+ ResourcePackProfile highContrast = mockProfile(profiles, "high_contrast", null);
+ ResourcePackProfile userPackA = mockProfile(profiles, "user_pack_a", null);
+ ResourcePackProfile userPackB = mockProfile(profiles, "user_pack_b", null);
+ modAProfiles.putAll(profiles);
+ modBProfiles.putAll(profiles);
+ allProfiles.putAll(modAProfiles);
+ allProfiles.putAll(modBProfiles);
+
+ testRefreshAutoEnabledPacks(
+ profiles,
+ List.of(vanilla, fabric),
+ List.of(vanilla, fabric),
+ "keep (no mods)"
+ );
+ testRefreshAutoEnabledPacks(
+ profiles,
+ List.of(vanilla, fabric, userPackA),
+ List.of(vanilla, fabric, userPackA),
+ "keep (no mods, keep user pack)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, modA),
+ List.of(vanilla, fabric, modA),
+ "keep (mod A only)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, modA, programmerArt, modAProg),
+ List.of(vanilla, fabric, modA, programmerArt, modAProg),
+ "keep (programmer_art)"
+ );
+ testRefreshAutoEnabledPacks(
+ allProfiles,
+ List.of(vanilla, fabric, modA, modB, programmerArt, modAProg, modBProg),
+ List.of(vanilla, fabric, modA, modB, programmerArt, modAProg, modBProg),
+ "keep (mod A and mod B, programmer_art)"
+ );
+ testRefreshAutoEnabledPacks(
+ allProfiles,
+ List.of(vanilla, fabric, modA, modB, programmerArt, modAProg, modBProg, highContrast, modAHigh, modBHigh),
+ List.of(vanilla, fabric, modA, modB, programmerArt, modAProg, modBProg, highContrast, modAHigh, modBHigh),
+ "keep (mod A and mod B, both)"
+ );
+ testRefreshAutoEnabledPacks(
+ allProfiles,
+ List.of(vanilla, fabric, modA, modB, highContrast, modAHigh, modBHigh, programmerArt, modAProg, modBProg),
+ List.of(vanilla, fabric, modA, modB, highContrast, modAHigh, modBHigh, programmerArt, modAProg, modBProg),
+ "keep (remembers programmer_art-high_contrast order)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric),
+ List.of(vanilla, fabric, modA),
+ "fix (adding missing mods)"
+ );
+ testRefreshAutoEnabledPacks(
+ allProfiles,
+ List.of(vanilla, fabric, userPackA),
+ List.of(vanilla, fabric, modA, modB, userPackA),
+ "fix (adding missing mods at the right place)"
+ );
+ testRefreshAutoEnabledPacks(
+ allProfiles,
+ List.of(vanilla, fabric, modB, modA),
+ List.of(vanilla, fabric, modA, modB),
+ "fix (mod A and B, sorting)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, userPackB, modA, userPackA),
+ List.of(vanilla, fabric, modA, userPackB, userPackA),
+ "fix (user pack goes last)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, modA, programmerArt),
+ List.of(vanilla, fabric, modA, programmerArt, modAProg),
+ "fix (adding 1 met dep)"
+ );
+ testRefreshAutoEnabledPacks(
+ modBProfiles,
+ List.of(vanilla, fabric, modB, highContrast),
+ List.of(vanilla, fabric, modB, highContrast, modBHigh),
+ "fix (adding 1 met dep, part 2)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, modA, programmerArt, highContrast),
+ List.of(vanilla, fabric, modA, programmerArt, modAProg, highContrast, modAHigh),
+ "fix (adding 2 met deps)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, modA, programmerArt, modAProg, highContrast),
+ List.of(vanilla, fabric, modA, programmerArt, modAProg, highContrast, modAHigh),
+ "fix (adding 2 met deps + preexisting)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, modA, modAProg, modAHigh),
+ List.of(vanilla, fabric, modA),
+ "fix (removing 2 unmet deps)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, modA, programmerArt, modAProg, modAHigh),
+ List.of(vanilla, fabric, modA, programmerArt, modAProg),
+ "fix (removing 1 unmet dep)"
+ );
+ testRefreshAutoEnabledPacks(
+ modBProfiles,
+ List.of(vanilla, fabric, modB, highContrast, modBProg, modBHigh),
+ List.of(vanilla, fabric, modB, highContrast, modBHigh),
+ "fix (removing 1 unmet dep, part 2)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, modAProg, programmerArt, modA),
+ List.of(vanilla, fabric, modA, programmerArt, modAProg),
+ "reorder (bundled comes just after parents)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, modAProg, userPackA, programmerArt, modA, userPackB),
+ List.of(vanilla, fabric, modA, userPackA, programmerArt, modAProg, userPackB),
+ "reorder (keep user pack order)"
+ );
+ testRefreshAutoEnabledPacks(
+ modAProfiles,
+ List.of(vanilla, fabric, userPackB, modA, programmerArt, userPackA, modAProg),
+ List.of(vanilla, fabric, modA, userPackB, programmerArt, modAProg, userPackA),
+ "reorder (no user pack between parent-bundled)"
+ );
+ }
+
+ private ResourcePackProfile mockProfile(Map profiles, String id, @Nullable Predicate> parents) {
+ ResourcePackProfile profile = ResourcePackProfile.of(
+ id,
+ null,
+ false,
+ null,
+ null,
+ null,
+ false,
+ ModResourcePackCreator.RESOURCE_PACK_SOURCE
+ );
+
+ if (parents != null) ((FabricResourcePackProfile) profile).fabric_setParentsPredicate(parents);
+
+ profiles.put(id, profile);
+ return profile;
+ }
+
+ private void testRefreshAutoEnabledPacks(Map profiles, List before, List after, String reason) {
+ List processed = new ArrayList<>(before);
+ ModResourcePackUtil.refreshAutoEnabledPacks(processed, profiles);
+ assertEquals(
+ after.stream().map(ResourcePackProfile::getName).toList(),
+ processed.stream().map(ResourcePackProfile::getName).toList(),
+ () -> "Testing %s; input %s".formatted(reason, before.stream().map(ResourcePackProfile::getName).toList())
+ );
+ }
+
+ @Test
+ void testSerializeMetadata() {
+ // Test various metadata serialization issues (#2407)
+ testMetadataSerialization("");
+ testMetadataSerialization("Quotes: \"\" \"");
+ testMetadataSerialization("Backslash: \\ \\\\");
+ }
+
+ private void testMetadataSerialization(String description) throws JsonParseException {
+ String metadata = ModResourcePackUtil.serializeMetadata(1, description);
+ JsonObject json = assertDoesNotThrow(() -> GSON.fromJson(metadata, JsonObject.class), () -> "Failed to serialize " + description);
+
+ String parsedDescription = json.get("pack").getAsJsonObject().get("description").getAsString();
+ assertEquals(description, parsedDescription, "Parsed description differs from original one");
+ }
+}
diff --git a/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/BuiltinResourcePackTestMod.java b/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/BuiltinResourcePackTestMod.java
index 02ce7fd79f..a7257c1b28 100644
--- a/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/BuiltinResourcePackTestMod.java
+++ b/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/BuiltinResourcePackTestMod.java
@@ -16,9 +16,6 @@
package net.fabricmc.fabric.test.resource.loader;
-import com.google.gson.Gson;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -28,7 +25,6 @@
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.fabricmc.fabric.api.resource.ResourcePackActivationType;
-import net.fabricmc.fabric.impl.resource.loader.ModResourcePackUtil;
import net.fabricmc.loader.api.FabricLoader;
public class BuiltinResourcePackTestMod implements ModInitializer {
@@ -36,8 +32,6 @@ public class BuiltinResourcePackTestMod implements ModInitializer {
private static final Logger LOGGER = LoggerFactory.getLogger(BuiltinResourcePackTestMod.class);
- private static final Gson GSON = new Gson();
-
@Override
public void onInitialize() {
// Should always be present as it's **this** mod.
@@ -49,27 +43,5 @@ public void onInitialize() {
.map(container -> ResourceManagerHelper.registerBuiltinResourcePack(new Identifier(MODID, "test2"),
container, ResourcePackActivationType.NORMAL))
.filter(success -> !success).ifPresent(success -> LOGGER.warn("Could not register built-in resource pack."));
-
- // Test various metadata serialization issues (#2407)
- testMetadataSerialization("");
- testMetadataSerialization("Quotes: \"\" \"");
- testMetadataSerialization("Backslash: \\ \\\\");
- }
-
- private void testMetadataSerialization(String description) {
- String metadata = ModResourcePackUtil.serializeMetadata(1, description);
- JsonObject json;
-
- try {
- json = GSON.fromJson(metadata, JsonObject.class);
- } catch (JsonParseException exc) {
- throw new AssertionError("Metadata parsing test for description \"%s\" failed".formatted(description), exc);
- }
-
- String parsedDescription = json.get("pack").getAsJsonObject().get("description").getAsString();
-
- if (!description.equals(parsedDescription)) {
- throw new AssertionError("Metadata parsing test for description failed: expected \"%s\", got \"%s\"".formatted(description, parsedDescription));
- }
}
}