From 91b7aa665bae7476fd163c367b9182142d2af6be Mon Sep 17 00:00:00 2001 From: Technici4n <13494793+Technici4n@users.noreply.github.com> Date: Sun, 10 Apr 2022 17:33:09 +0200 Subject: [PATCH] Fix tags_populated resource conditions in 1.18.2 (#2099) * Fix tags_populated resource conditions * move gametest entrypoint further up before resource manager getting loaded * Add maxShiftBy Co-authored-by: deirn <deirn@bai.lol> Co-authored-by: modmuss50 <modmuss50@gmail.com> --- .../mixin/gametest/server/MainMixin.java | 8 ++- .../fabric-gametest-api-v1.mixins.json | 3 +- .../conditions/v1/ResourceConditions.java | 8 ++- .../conditions/ResourceConditionsImpl.java | 52 +++++++++++++++++-- .../conditions/DataPackContentsMixin.java | 42 +++++++++++++++ .../conditions/JsonDataLoaderMixin.java | 3 ++ ...inglePreparationResourceReloaderMixin.java | 4 ++ .../conditions/TagManagerLoaderMixin.java | 50 ++++++++++++++++++ ...ric-resource-conditions-api-v1.mixins.json | 4 +- 9 files changed, 163 insertions(+), 11 deletions(-) create mode 100644 fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java create mode 100644 fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/TagManagerLoaderMixin.java diff --git a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java index d09bf250e..a8c556c2f 100644 --- a/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java +++ b/fabric-gametest-api-v1/src/main/java/net/fabricmc/fabric/mixin/gametest/server/MainMixin.java @@ -34,12 +34,9 @@ import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import net.minecraft.resource.ResourcePackManager; import net.minecraft.server.Main; -import net.minecraft.server.SaveLoader; import net.minecraft.server.dedicated.EulaReader; import net.minecraft.server.dedicated.ServerPropertiesLoader; import net.minecraft.util.UserCache; -import net.minecraft.util.registry.DynamicRegistryManager; -import net.minecraft.world.SaveProperties; import net.minecraft.world.level.storage.LevelStorage; import net.minecraft.world.level.storage.LevelSummary; @@ -52,8 +49,9 @@ public class MainMixin { return FabricGameTestHelper.ENABLED || reader.isEulaAgreedTo(); } - @Inject(method = "main", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;startServer(Ljava/util/function/Function;)Lnet/minecraft/server/MinecraftServer;")) - private static void main(String[] args, CallbackInfo info, OptionParser optionParser, OptionSpec optionSpec, OptionSpec optionSpec2, OptionSpec optionSpec3, OptionSpec optionSpec4, OptionSpec optionSpec5, OptionSpec optionSpec6, OptionSpec optionSpec7, OptionSpec optionSpec8, OptionSpec optionSpec9, OptionSpec optionSpec10, OptionSpec optionSpec11, OptionSpec optionSpec12, OptionSpec optionSpec13, OptionSpec optionSpec14, OptionSpec optionSpec15, OptionSet optionSet, Path path, ServerPropertiesLoader serverPropertiesLoader, Path path2, EulaReader eulaReader, File file, YggdrasilAuthenticationService yggdrasilAuthenticationService, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, String string, LevelStorage levelStorage, LevelStorage.Session session, LevelSummary levelSummary, boolean bl, ResourcePackManager resourcePackManager, SaveLoader lv2, DynamicRegistryManager.Immutable lv3, SaveProperties saveProperties) { + // Inject after resourcePackManager is stored + @Inject(method = "main", cancellable = true, locals = LocalCapture.CAPTURE_FAILHARD, at = @At(value = "INVOKE", shift = At.Shift.BY, by = 2, target = "Lnet/minecraft/resource/ResourcePackManager;<init>(Lnet/minecraft/resource/ResourceType;[Lnet/minecraft/resource/ResourcePackProvider;)V")) + private static void main(String[] args, CallbackInfo info, OptionParser optionParser, OptionSpec optionSpec, OptionSpec optionSpec2, OptionSpec optionSpec3, OptionSpec optionSpec4, OptionSpec optionSpec5, OptionSpec optionSpec6, OptionSpec optionSpec7, OptionSpec optionSpec8, OptionSpec optionSpec9, OptionSpec optionSpec10, OptionSpec optionSpec11, OptionSpec optionSpec12, OptionSpec optionSpec13, OptionSpec optionSpec14, OptionSpec optionSpec15, OptionSet optionSet, Path path, ServerPropertiesLoader serverPropertiesLoader, Path path2, EulaReader eulaReader, File file, YggdrasilAuthenticationService yggdrasilAuthenticationService, MinecraftSessionService minecraftSessionService, GameProfileRepository gameProfileRepository, UserCache userCache, String string, LevelStorage levelStorage, LevelStorage.Session session, LevelSummary levelSummary, boolean bl, ResourcePackManager resourcePackManager) { if (FabricGameTestHelper.ENABLED) { FabricGameTestHelper.runHeadlessServer(session, resourcePackManager); info.cancel(); // Do not progress in starting the normal dedicated server diff --git a/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json b/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json index 636359718..8a7826430 100644 --- a/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json +++ b/fabric-gametest-api-v1/src/main/resources/fabric-gametest-api-v1.mixins.json @@ -14,6 +14,7 @@ "server.MainMixin" ], "injectors": { - "defaultRequire": 1 + "defaultRequire": 1, + "maxShiftBy": 2 } } diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/ResourceConditions.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/ResourceConditions.java index 576af9a0e..4f26822b8 100644 --- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/ResourceConditions.java +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/api/resource/conditions/v1/ResourceConditions.java @@ -38,7 +38,10 @@ import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl; * A resource condition is an identified {@code Predicate<JsonObject>} that can decide whether a resource should be loaded or not. * <ul> * <li>A JSON object that may contain a condition can be parsed with {@link #objectMatchesConditions}. - * This is the preferred way of implementing conditional objects, as it handles the details of the format (see below) and catches and logs thrown exceptions.</li> + * This is the preferred way of implementing conditional objects, as it handles the details of the format (see below) and catches and logs thrown exceptions. + * This function should only be called from the "apply" phase of a {@link net.minecraft.resource.ResourceReloader}, + * otherwise some conditions might behave in unexpected ways. + * </li> * <li>The lower-level {@link #conditionsMatch} and {@link #conditionMatches} may be useful when implementing conditions.</li> * <li>Conditions are registered with {@link #register} and queried with {@link #get}.</li> * </ul> @@ -113,6 +116,9 @@ public final class ResourceConditions { * Check if the passed JSON object either has no {@code fabric:conditions} tag, or all of its conditions match. * This should be called for objects that may contain a conditions entry. * + * <p>This function should only be called from the "apply" phase of a {@link net.minecraft.resource.ResourceReloader}, + * otherwise some conditions might behave in unexpected ways. + * * <p>If an exception is thrown during condition testing, it will be caught and logged, and false will be returned. */ public static boolean objectMatchesConditions(JsonObject object) { diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java index 35a2333cf..80f49eee5 100644 --- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/impl/resource/conditions/ResourceConditionsImpl.java @@ -16,19 +16,27 @@ package net.fabricmc.fabric.impl.resource.conditions; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import com.google.common.base.Preconditions; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.minecraft.tag.Tag; import net.minecraft.tag.TagKey; +import net.minecraft.tag.TagManagerLoader; import net.minecraft.util.Identifier; import net.minecraft.util.JsonHelper; import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryEntry; import net.minecraft.util.registry.RegistryKey; import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider; @@ -124,16 +132,54 @@ public class ResourceConditionsImpl { return and; } + /** + * Stores the tags deserialized by {@link TagManagerLoader} before they are bound, to use them in the tags_populated conditions. + * The tags are set at the end of the "apply" phase in {@link TagManagerLoader}, and cleared in {@link net.minecraft.server.DataPackContents#refresh}. + * If the resource reload fails, the thread local is not cleared and: + * - the map will remain in memory until the next reload; + * - any call to {@link #tagsPopulatedMatch} will check the tags from the failed reload instead of failing directly. + * This is probably acceptable. + */ + public static final ThreadLocal<Map<RegistryKey<?>, Map<Identifier, Tag<RegistryEntry<?>>>>> LOADED_TAGS = new ThreadLocal<>(); + + @SuppressWarnings({"unchecked", "rawtypes"}) + public static void setTags(List<TagManagerLoader.RegistryTags<?>> tags) { + Map<RegistryKey<?>, Map<Identifier, Tag<RegistryEntry<?>>>> tagMap = new HashMap<>(); + + for (TagManagerLoader.RegistryTags<?> registryTags : tags) { + tagMap.put(registryTags.key(), (Map) registryTags.tags()); + } + + LOADED_TAGS.set(tagMap); + } + + public static void clearTags() { + LOADED_TAGS.remove(); + } + public static <T> boolean tagsPopulatedMatch(JsonObject object, RegistryKey<? extends Registry<T>> registryKey) { JsonArray array = JsonHelper.getArray(object, "values"); + @Nullable + Map<RegistryKey<?>, Map<Identifier, Tag<RegistryEntry<?>>>> allTags = LOADED_TAGS.get(); + + if (allTags == null) { + LOGGER.warn("Can't retrieve deserialized tags. Failing tags_populated resource condition check."); + return false; + } + + Map<Identifier, Tag<RegistryEntry<?>>> registryTags = allTags.get(registryKey); + + if (registryTags == null) { + // No tag for this registry + return array.isEmpty(); + } for (JsonElement element : array) { if (element.isJsonPrimitive()) { Identifier id = new Identifier(element.getAsString()); - TagKey<T> tag = TagKey.of(registryKey, id); - Registry<T> registry = (Registry<T>) Registry.REGISTRIES.get(registryKey.getValue()); + Tag<RegistryEntry<?>> tag = registryTags.get(id); - if (!registry.containsTag(tag)) { + if (tag == null || tag.values().isEmpty()) { return false; } } else { diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java new file mode 100644 index 000000000..1867a3a4f --- /dev/null +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/DataPackContentsMixin.java @@ -0,0 +1,42 @@ +/* + * 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.conditions; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.server.DataPackContents; +import net.minecraft.util.registry.DynamicRegistryManager; + +import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl; + +/** + * Clear the tags captured by {@link DataPackContentsMixin}. + * This must happen after the resource reload is complete, to ensure that the tags remain available throughout the entire "apply" phase. + */ +@Mixin(DataPackContents.class) +public class DataPackContentsMixin { + @Inject( + method = "refresh", + at = @At("HEAD") + ) + public void hookRefresh(DynamicRegistryManager dynamicRegistryManager, CallbackInfo ci) { + ResourceConditionsImpl.clearTags(); + } +} diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/JsonDataLoaderMixin.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/JsonDataLoaderMixin.java index 49a1caf65..ab76e4b82 100644 --- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/JsonDataLoaderMixin.java +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/JsonDataLoaderMixin.java @@ -33,6 +33,9 @@ import net.minecraft.util.profiler.Profiler; import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions; import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl; +/** + * Using {@link SinglePreparationResourceReloaderMixin}, apply resource conditions at the very beginning of the "apply" phase. + */ @Mixin(JsonDataLoader.class) public class JsonDataLoaderMixin extends SinglePreparationResourceReloaderMixin { @Shadow diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/SinglePreparationResourceReloaderMixin.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/SinglePreparationResourceReloaderMixin.java index de03e19c4..fedbcc79d 100644 --- a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/SinglePreparationResourceReloaderMixin.java +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/SinglePreparationResourceReloaderMixin.java @@ -25,6 +25,10 @@ import net.minecraft.resource.ResourceManager; import net.minecraft.resource.SinglePreparationResourceReloader; import net.minecraft.util.profiler.Profiler; +/** + * This mixin allows us to inject arbitrary logic at the beginning of the "apply" phase. + * Used by the subclass {@link JsonDataLoaderMixin}. + */ @Mixin(SinglePreparationResourceReloader.class) public class SinglePreparationResourceReloaderMixin { // thenAcceptAsync in reload diff --git a/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/TagManagerLoaderMixin.java b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/TagManagerLoaderMixin.java new file mode 100644 index 000000000..d4fd72d7f --- /dev/null +++ b/fabric-resource-conditions-api-v1/src/main/java/net/fabricmc/fabric/mixin/resource/conditions/TagManagerLoaderMixin.java @@ -0,0 +1,50 @@ +/* + * 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.conditions; + +import java.util.List; + +import org.spongepowered.asm.mixin.Dynamic; +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.tag.TagManagerLoader; + +import net.fabricmc.fabric.impl.resource.conditions.ResourceConditionsImpl; + +/** + * Capture deserialized tags, right at the end of the "apply" phase of the tag loader, for use by the xxx_tags_populated condition. + * This gives access to these tags during the rest of the "apply" phase, when resource conditions are applied. + */ +@Mixin(TagManagerLoader.class) +public class TagManagerLoaderMixin { + @Shadow + private List<TagManagerLoader.RegistryTags<?>> registryTags; + + // lambda body inside thenAcceptAsync, in the reload method + @Dynamic + @Inject( + method = "method_40098(Ljava/util/List;Ljava/lang/Void;)V", + at = @At("RETURN") + ) + private void hookApply(List<?> list, Void void_, CallbackInfo ci) { + ResourceConditionsImpl.setTags(registryTags); + } +} diff --git a/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json b/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json index 7d98aef51..1d03936d4 100644 --- a/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json +++ b/fabric-resource-conditions-api-v1/src/main/resources/fabric-resource-conditions-api-v1.mixins.json @@ -3,7 +3,9 @@ "package": "net.fabricmc.fabric.mixin.resource.conditions", "compatibilityLevel": "JAVA_16", "mixins": [ + "DataPackContentsMixin", "JsonDataLoaderMixin", - "SinglePreparationResourceReloaderMixin" + "SinglePreparationResourceReloaderMixin", + "TagManagerLoaderMixin" ] }