diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/impl/client/resource/loader/FabricWrappedVanillaResourcePack.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/impl/client/resource/loader/FabricWrappedVanillaResourcePack.java deleted file mode 100644 index 8d21cac073..0000000000 --- a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/impl/client/resource/loader/FabricWrappedVanillaResourcePack.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.impl.client.resource.loader; - -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.stream.Stream; - -import org.jetbrains.annotations.Nullable; - -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.fabric.impl.resource.loader.GroupResourcePack; - -/** - * Represents a vanilla built-in resource pack with support for modded content. - * - *

Vanilla resources are provided as usual through the original resource pack (if not overridden), - * all other resources will be searched for in the provided modded resource packs.

- */ -public class FabricWrappedVanillaResourcePack extends GroupResourcePack { - private final AbstractFileResourcePack originalResourcePack; - - public FabricWrappedVanillaResourcePack(AbstractFileResourcePack originalResourcePack, List modResourcePacks) { - // Mod resource packs have higher priority, add them last (so vanilla assets can be overridden) - super(ResourceType.CLIENT_RESOURCES, Stream.concat(Stream.of(originalResourcePack), modResourcePacks.stream()).toList()); - this.originalResourcePack = originalResourcePack; - } - - @Override - public InputSupplier openRoot(String... pathSegments) { - return this.originalResourcePack.openRoot(pathSegments); - } - - @Override - public @Nullable T parseMetadata(ResourceMetadataReader metaReader) throws IOException { - return this.originalResourcePack.parseMetadata(metaReader); - } - - @Override - public String getName() { - return this.originalResourcePack.getName(); - } -} diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/DefaultClientResourcePackProviderMixin.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/DefaultClientResourcePackProviderMixin.java deleted file mode 100644 index b3d1015e9a..0000000000 --- a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/DefaultClientResourcePackProviderMixin.java +++ /dev/null @@ -1,77 +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 java.util.ArrayList; -import java.util.List; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyArg; - -import net.minecraft.client.resource.DefaultClientResourcePackProvider; -import net.minecraft.resource.AbstractFileResourcePack; -import net.minecraft.resource.ResourcePack; -import net.minecraft.resource.ResourcePackProfile; -import net.minecraft.resource.ResourcePackSource; -import net.minecraft.resource.ResourceType; -import net.minecraft.text.Text; - -import net.fabricmc.fabric.api.resource.ModResourcePack; -import net.fabricmc.fabric.impl.client.resource.loader.FabricWrappedVanillaResourcePack; -import net.fabricmc.fabric.impl.resource.loader.ModResourcePackUtil; - -@Mixin(DefaultClientResourcePackProvider.class) -public class DefaultClientResourcePackProviderMixin { - /** - * Injects into the method which registers/creates vanilla built-in resource packs, - * and replaces the local {@link net.minecraft.resource.ResourcePackProfile.PackFactory} - * instance with our custom wrapper that supports loading from mods. - */ - @ModifyArg( - method = "create(Ljava/lang/String;Lnet/minecraft/resource/ResourcePackProfile$PackFactory;Lnet/minecraft/text/Text;)Lnet/minecraft/resource/ResourcePackProfile;", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/resource/ResourcePackProfile;create(Ljava/lang/String;Lnet/minecraft/text/Text;ZLnet/minecraft/resource/ResourcePackProfile$PackFactory;Lnet/minecraft/resource/ResourceType;Lnet/minecraft/resource/ResourcePackProfile$InsertionPosition;Lnet/minecraft/resource/ResourcePackSource;)Lnet/minecraft/resource/ResourcePackProfile;" - ), - index = 3 - ) - private ResourcePackProfile.PackFactory onCreateVanillaBuiltinResourcePack(String name, Text displayName, boolean alwaysEnabled, - ResourcePackProfile.PackFactory packFactory, ResourceType type, ResourcePackProfile.InsertionPosition position, ResourcePackSource source) { - return new ResourcePackProfile.PackFactory() { - @Override - public ResourcePack open(String name) { - return new FabricWrappedVanillaResourcePack((AbstractFileResourcePack) packFactory.open(name), getModResourcePacks(name)); - } - - @Override - public ResourcePack openWithOverlays(String string, ResourcePackProfile.Metadata metadata) { - // VanillaResourcePackProvider does not handle overlays - return open(name); - } - }; - } - - /** - * {@return all baked-in mod resource packs that provide resources in the specified subPath}. - */ - private static List getModResourcePacks(String subPath) { - List packs = new ArrayList<>(); - ModResourcePackUtil.appendModResourcePacks(packs, ResourceType.CLIENT_RESOURCES, subPath); - return packs; - } -} diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/GameOptionsMixin.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/GameOptionsMixin.java index b9533c3f87..303d688955 100644 --- a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/GameOptionsMixin.java +++ b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/GameOptionsMixin.java @@ -99,7 +99,7 @@ private void onLoad(CallbackInfo ci) { for (ResourcePackProfile profile : profiles) { // Always add "Fabric Mods" pack to enabled resource packs. - if (profile.getSource() == ModResourcePackCreator.RESOURCE_PACK_SOURCE) { + if (profile.getName().equals(ModResourcePackCreator.FABRIC)) { resourcePacks.add(profile.getName()); continue; } diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/GameOptionsWriteVisitorMixin.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/GameOptionsWriteVisitorMixin.java new file mode 100644 index 0000000000..0563ad2a45 --- /dev/null +++ b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/GameOptionsWriteVisitorMixin.java @@ -0,0 +1,63 @@ +/* + * 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 java.util.ArrayList; +import java.util.List; + +import com.llamalad7.mixinextras.sugar.Local; +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.ModifyArg; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ResourcePackProfile; + +import net.fabricmc.fabric.impl.resource.loader.FabricResourcePackProfile; + +/** + * Mixins to the anonymous class in #write method. + */ +@Mixin(targets = "net/minecraft/client/option/GameOptions$3") +public class GameOptionsWriteVisitorMixin { + @Unique + private static List toPackListString(List packs) { + List copy = new ArrayList<>(packs.size()); + ResourcePackManager manager = MinecraftClient.getInstance().getResourcePackManager(); + + for (String pack : packs) { + ResourcePackProfile profile = manager.getProfile(pack); + + // Nonexistent pack profiles should be handled in the same way as vanilla + if (profile == null || !((FabricResourcePackProfile) profile).fabric_isHidden()) copy.add(pack); + } + + return copy; + } + + @SuppressWarnings("unchecked") + @ModifyArg(method = "visitObject", at = @At(value = "INVOKE", target = "Ljava/util/function/Function;apply(Ljava/lang/Object;)Ljava/lang/Object;")) + private T skipHiddenPacks(T value, @Local String key) { + if ("resourcePacks".equals(key) && value instanceof List) { + return (T) toPackListString((List) value); + } + + return value; + } +} diff --git a/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/ResourcePackOrganizerMixin.java b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/ResourcePackOrganizerMixin.java new file mode 100644 index 0000000000..d6444d83c6 --- /dev/null +++ b/fabric-resource-loader-v0/src/client/java/net/fabricmc/fabric/mixin/resource/loader/client/ResourcePackOrganizerMixin.java @@ -0,0 +1,61 @@ +/* + * 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 java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +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.ResourcePackOrganizer; +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ResourcePackProfile; + +import net.fabricmc.fabric.impl.resource.loader.FabricResourcePackProfile; + +@Mixin(ResourcePackOrganizer.class) +public class ResourcePackOrganizerMixin { + @Shadow + @Final + List enabledPacks; + + @Shadow + @Final + List disabledPacks; + + /** + * Do not list hidden packs in either enabledPacks or disabledPacks. + * They are managed entirely by ResourcePackManager on save, and are invisible to client. + */ + @Inject(method = "", 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 packs; - protected final Map> namespacedPacks = new Object2ObjectOpenHashMap<>(); - - public GroupResourcePack(ResourceType type, List 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 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 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 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 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)); - } } }