From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 12 May 2024 17:30:50 -0700 Subject: [PATCH] Add datapack registration lifecycle event == AT == public net/minecraft/server/packs/repository/FolderRepositorySource$FolderPackDetector public net/minecraft/server/packs/repository/FolderRepositorySource$FolderPackDetector (Lnet/minecraft/world/level/validation/DirectoryValidator;)V diff --git a/src/main/java/io/papermc/paper/datapack/PaperDatapackRegistrar.java b/src/main/java/io/papermc/paper/datapack/PaperDatapackRegistrar.java new file mode 100644 index 0000000000000000000000000000000000000000..aa5c7dfddea67db036c066d5151821248b945550 --- /dev/null +++ b/src/main/java/io/papermc/paper/datapack/PaperDatapackRegistrar.java @@ -0,0 +1,166 @@ +package io.papermc.paper.datapack; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import com.mojang.logging.LogUtils; +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.plugin.bootstrap.BootstrapContext; +import io.papermc.paper.plugin.configuration.PluginMeta; +import io.papermc.paper.plugin.lifecycle.event.registrar.PaperRegistrar; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.function.Consumer; +import net.kyori.adventure.text.Component; +import net.minecraft.server.packs.PackLocationInfo; +import net.minecraft.server.packs.PackSelectionConfig; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.VanillaPackResourcesBuilder; +import net.minecraft.server.packs.repository.FolderRepositorySource; +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.server.packs.repository.PackDetector; +import net.minecraft.world.level.validation.ContentValidationException; +import net.minecraft.world.level.validation.DirectoryValidator; +import net.minecraft.world.level.validation.ForbiddenSymlinkInfo; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.jetbrains.annotations.Unmodifiable; +import org.slf4j.Logger; + +@DefaultQualifier(NonNull.class) +public class PaperDatapackRegistrar implements PaperRegistrar, DatapackRegistrar { + + private static final Logger LOGGER = LogUtils.getClassLogger(); + + private final PackDetector detector; + public final Map discoveredPacks; + private @Nullable BootstrapContext owner; + + public PaperDatapackRegistrar(final DirectoryValidator symlinkValidator, final Map discoveredPacks) { + this.detector = new FolderRepositorySource.FolderPackDetector(symlinkValidator); + this.discoveredPacks = discoveredPacks; + } + + @Override + public void setCurrentContext(final @Nullable BootstrapContext owner) { + this.owner = owner; + } + + @Override + public boolean hasPackDiscovered(final String name) { + return this.discoveredPacks.containsKey(name); + } + + @Override + public @NonNull DiscoveredDatapack getDiscoveredPack(final String name) { + if (!this.hasPackDiscovered(name)) { + throw new NoSuchElementException("No pack with id " + name + " was discovered"); + } + return new PaperDiscoveredDatapack(this.discoveredPacks.get(name)); + } + + @Override + public boolean removeDiscoveredPack(final String name) { + return this.discoveredPacks.remove(name) != null; + } + + @Override + public @Unmodifiable Map getDiscoveredPacks() { + final ImmutableMap.Builder builder = ImmutableMap.builderWithExpectedSize(this.discoveredPacks.size()); + for (final Map.Entry entry : this.discoveredPacks.entrySet()) { + builder.put(entry.getKey(), new PaperDiscoveredDatapack(entry.getValue())); + } + return builder.build(); + } + + @Override + public @Nullable DiscoveredDatapack discoverPack(final URI uri, final String id, final Consumer configurer) throws IOException { + Preconditions.checkState(this.owner != null, "Cannot register a datapack without specifying a PluginMeta yet"); + return this.discoverPack(this.owner.getPluginMeta(), uri, id, configurer); + } + + @Override + public @Nullable DiscoveredDatapack discoverPack(final Path path, final String id, final Consumer configurer) throws IOException { + Preconditions.checkState(this.owner != null, "Cannot register a datapack without specifying a PluginMeta yet"); + return this.discoverPack(this.owner.getPluginMeta(), path, id, configurer); + } + + @Override + public @Nullable DiscoveredDatapack discoverPack(final PluginMeta pluginMeta, final URI uri, final String id, final Consumer configurer) throws IOException { + return this.discoverPack(pluginMeta, VanillaPackResourcesBuilder.safeGetPath(uri), id, configurer); + } + + @Override + public @Nullable DiscoveredDatapack discoverPack(final PluginMeta pluginMeta, final Path path, final String id, final Consumer configurer) throws IOException { + final List badLinks = new ArrayList<>(); + final Pack.@Nullable ResourcesSupplier resourcesSupplier = this.detector.detectPackResources(path, badLinks); + if (!badLinks.isEmpty()) { + LOGGER.warn("Ignoring potential pack entry: {}", ContentValidationException.getMessage(path, badLinks)); + } else if (resourcesSupplier != null) { + final String packId = pluginMeta.getName() + "/" + id; + final ConfigurerImpl configurerImpl = new ConfigurerImpl(Component.text(packId)); + configurer.accept(configurerImpl); + final PackLocationInfo locInfo = new PackLocationInfo(packId, + PaperAdventure.asVanilla(configurerImpl.title), + PluginPackSource.INSTANCE, + Optional.empty() + ); + final @Nullable Pack pack = Pack.readMetaAndCreate(locInfo, + resourcesSupplier, + PackType.SERVER_DATA, + new PackSelectionConfig( + configurerImpl.required, + configurerImpl.position, + configurerImpl.fixedPosition + )); + if (pack != null) { + this.discoveredPacks.put(packId, pack); + return new PaperDiscoveredDatapack(pack); + } + return null; + } else { + LOGGER.info("Found non-pack entry '{}', ignoring", path); + } + return null; + } + + static final class ConfigurerImpl implements Configurer { + + private Component title; + private boolean required = false; + private boolean fixedPosition = false; + private Pack.Position position = Pack.Position.TOP; + + ConfigurerImpl(final Component title) { + this.title = title; + } + + @Override + public Configurer title(final Component title) { + this.title = title; + return this; + } + + @Override + public Configurer required(final boolean required) { + this.required = required; + return this; + } + + @Override + public Configurer position(final boolean fixed, final Datapack.Position position) { + this.fixedPosition = fixed; + this.position = switch (position) { + case TOP -> Pack.Position.TOP; + case BOTTOM -> Pack.Position.BOTTOM; + }; + return this; + } + } +} diff --git a/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java b/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java new file mode 100644 index 0000000000000000000000000000000000000000..572a62ceafcd066adfc0c2588cc43a0b61cedb7f --- /dev/null +++ b/src/main/java/io/papermc/paper/datapack/PaperDiscoveredDatapack.java @@ -0,0 +1,46 @@ +package io.papermc.paper.datapack; + +import java.util.Objects; +import net.minecraft.server.packs.repository.Pack; + +public class PaperDiscoveredDatapack implements DiscoveredDatapack{ + + private final String name; + private final Datapack.Compatibility compatibility; + + PaperDiscoveredDatapack(Pack pack) { + this.name = pack.getId(); + this.compatibility = Datapack.Compatibility.valueOf(pack.getCompatibility().name()); + } + + @Override + public String getName() { + return this.name; + } + + @Override + public Datapack.Compatibility getCompatibility() { + return this.compatibility; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || this.getClass() != o.getClass()) return false; + final PaperDiscoveredDatapack that = (PaperDiscoveredDatapack) o; + return Objects.equals(this.name, that.name) && this.compatibility == that.compatibility; + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.compatibility); + } + + @Override + public String toString() { + return "PaperDiscoveredDatapack{" + + "name='" + this.name + '\'' + + ", compatibility=" + this.compatibility + + '}'; + } +} diff --git a/src/main/java/io/papermc/paper/datapack/PluginPackSource.java b/src/main/java/io/papermc/paper/datapack/PluginPackSource.java new file mode 100644 index 0000000000000000000000000000000000000000..dfea23ddde7b929f4d47c5de9539cf8bb96bcfff --- /dev/null +++ b/src/main/java/io/papermc/paper/datapack/PluginPackSource.java @@ -0,0 +1,26 @@ +package io.papermc.paper.datapack; + +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.server.packs.repository.PackSource; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.framework.qual.DefaultQualifier; + +@DefaultQualifier(NonNull.class) +final class PluginPackSource implements PackSource { + + static final PackSource INSTANCE = new PluginPackSource(); + + private PluginPackSource() { + } + + @Override + public Component decorate(final Component packDisplayName) { + return Component.translatable("pack.nameAndSource", packDisplayName, "plugin").withStyle(ChatFormatting.GRAY); + } + + @Override + public boolean shouldAddAutomatically() { + return true; + } +} diff --git a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java index cca76f2d1623952017a83fdb027f77a601c79b3e..9770bd30943b81d85e3ccdf1ebdbdf0524bff243 100644 --- a/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java +++ b/src/main/java/io/papermc/paper/plugin/lifecycle/event/LifecycleEventRunner.java @@ -27,7 +27,8 @@ public class LifecycleEventRunner { private static final Logger LOGGER = LogUtils.getClassLogger(); private static final Supplier>> BLOCKS_RELOADING = Suppliers.memoize(() -> Set.of( // lazy due to cyclic initialization - LifecycleEvents.COMMANDS + LifecycleEvents.COMMANDS, + LifecycleEvents.DATAPACK_DISCOVERY )); public static final LifecycleEventRunner INSTANCE = new LifecycleEventRunner(); diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java index 696d075ca2883f3c37e35f983c4d020e5db89d16..78a4dd6a6b5649fd42a729c98a8e03dd80349290 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java @@ -2365,7 +2365,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop discoverNewPacks(PackRepository dataPackManager, WorldData saveProperties, Collection enabledDataPacks) { - dataPackManager.reload(); + dataPackManager.reload(true); // Paper - will perform a full reload Collection collection1 = Lists.newArrayList(enabledDataPacks); Collection collection2 = saveProperties.getDataConfiguration().dataPacks().getDisabled(); Iterator iterator = dataPackManager.getAvailableIds().iterator(); diff --git a/src/main/java/net/minecraft/server/packs/repository/PackRepository.java b/src/main/java/net/minecraft/server/packs/repository/PackRepository.java index 7cae8350023fb138bfcc5af28af6d36a3433d063..75d98b8c0850906a51b519746f112cb0e45b2d77 100644 --- a/src/main/java/net/minecraft/server/packs/repository/PackRepository.java +++ b/src/main/java/net/minecraft/server/packs/repository/PackRepository.java @@ -21,8 +21,12 @@ public class PackRepository { private final Set sources; private Map available = ImmutableMap.of(); private List selected = ImmutableList.of(); + private final net.minecraft.world.level.validation.DirectoryValidator validator; // Paper - add validator - public PackRepository(RepositorySource... providers) { + // Paper start - add validator + public PackRepository(net.minecraft.world.level.validation.DirectoryValidator validator, RepositorySource... providers) { + this.validator = validator; + // Paper end - add validator this.sources = ImmutableSet.copyOf(providers); } @@ -33,9 +37,14 @@ public class PackRepository { } public void reload() { + // Paper start - perform a full reload + this.reload(false); + } + public void reload(boolean addRequiredPacks) { + // Paper end List list = this.selected.stream().map(Pack::getId).collect(ImmutableList.toImmutableList()); this.available = this.discoverAvailable(); - this.selected = this.rebuildSelected(list); + this.selected = this.rebuildSelected(list, addRequiredPacks); // Paper } private Map discoverAvailable() { @@ -45,11 +54,18 @@ public class PackRepository { repositorySource.loadPacks(profile -> map.put(profile.getId(), profile)); } - return ImmutableMap.copyOf(map); + // Paper start - custom plugin-loaded datapacks + final io.papermc.paper.datapack.PaperDatapackRegistrar registrar = new io.papermc.paper.datapack.PaperDatapackRegistrar(this.validator, map); + io.papermc.paper.plugin.lifecycle.event.LifecycleEventRunner.INSTANCE.callStaticRegistrarEvent(io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents.DATAPACK_DISCOVERY, + registrar, + io.papermc.paper.plugin.bootstrap.BootstrapContext.class + ); + return ImmutableMap.copyOf(registrar.discoveredPacks); + // Paper end - custom plugin-loaded datapacks } public void setSelected(Collection enabled) { - this.selected = this.rebuildSelected(enabled); + this.selected = this.rebuildSelected(enabled, false); // Paper - add willReload boolean } public boolean addPack(String profile) { @@ -76,11 +92,11 @@ public class PackRepository { } } - private List rebuildSelected(Collection enabledNames) { + private List rebuildSelected(Collection enabledNames, boolean addRequiredPacks) { // Paper - add addRequiredPacks boolean List list = this.getAvailablePacks(enabledNames).collect(Util.toMutableList()); for (Pack pack : this.available.values()) { - if (pack.isRequired() && !list.contains(pack)) { + if (pack.isRequired() && !list.contains(pack) && addRequiredPacks) { // Paper - add addRequiredPacks boolean pack.getDefaultPosition().insert(list, pack, Pack::selectionConfig, false); } } diff --git a/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java b/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java index 396ec10a76bdadbf5be2f0e15e88eed47619004d..ac9256a65fab2896fcb42808ad65105701eae6f4 100644 --- a/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java +++ b/src/main/java/net/minecraft/server/packs/repository/ServerPacksSource.java @@ -83,13 +83,13 @@ public class ServerPacksSource extends BuiltInPackSource { } public static PackRepository createPackRepository(Path dataPacksPath, DirectoryValidator symlinkFinder) { - return new PackRepository( + return new PackRepository(symlinkFinder, // Paper - add validator new ServerPacksSource(symlinkFinder), new FolderRepositorySource(dataPacksPath, PackType.SERVER_DATA, PackSource.WORLD, symlinkFinder) ); } public static PackRepository createVanillaTrustedRepository() { - return new PackRepository(new ServerPacksSource(new DirectoryValidator(path -> true))); + return new PackRepository(new DirectoryValidator(path -> true), new ServerPacksSource(new DirectoryValidator(path -> true))); // Paper - add validator } public static PackRepository createPackRepository(LevelStorageSource.LevelStorageAccess session) {