From 596d4758ba9efedc60ede2789644ea10263c48e4 Mon Sep 17 00:00:00 2001 From: Corey Shupe Date: Tue, 1 Feb 2022 23:30:45 -0500 Subject: [PATCH] Implement resource pack send event. (#625) --- .../player/ServerResourcePackSendEvent.java | 70 +++++++++++++++++++ .../api/proxy/player/ResourcePackInfo.java | 38 +++++++++- .../backend/BackendPlaySessionHandler.java | 56 ++++++++++++--- .../connection/client/ConnectedPlayer.java | 3 +- .../player/VelocityResourcePackInfo.java | 30 +++++++- .../protocol/packet/ResourcePackResponse.java | 8 +++ 6 files changed, 191 insertions(+), 14 deletions(-) create mode 100644 api/src/main/java/com/velocitypowered/api/event/player/ServerResourcePackSendEvent.java diff --git a/api/src/main/java/com/velocitypowered/api/event/player/ServerResourcePackSendEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/ServerResourcePackSendEvent.java new file mode 100644 index 00000000..95237020 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/ServerResourcePackSendEvent.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2018 Velocity Contributors + * + * The Velocity API is licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in the api top-level directory. + */ + +package com.velocitypowered.api.event.player; + +import com.velocitypowered.api.event.ResultedEvent; +import com.velocitypowered.api.event.annotation.AwaitingEvent; +import com.velocitypowered.api.proxy.ServerConnection; +import com.velocitypowered.api.proxy.player.ResourcePackInfo; + +/** + * This event is fired when the downstream server tries to send a player a ResourcePack packet. + * The proxy will wait on this event to finish before forwarding the resource pack to the user. + * If this event is denied, it will retroactively send a DENIED status to the downstream + * server in response. + * If the downstream server has it set to "forced" it will forcefully disconnect the user. + */ +@AwaitingEvent +public class ServerResourcePackSendEvent implements ResultedEvent { + private GenericResult result; + private final ResourcePackInfo receivedResourcePack; + private ResourcePackInfo providedResourcePack; + private final ServerConnection serverConnection; + + /** + * Constructs a new ServerResourcePackSendEvent. + * + * @param receivedResourcePack The resource pack the server sent. + * @param serverConnection The connection this occurred on. + */ + public ServerResourcePackSendEvent( + ResourcePackInfo receivedResourcePack, + ServerConnection serverConnection + ) { + this.result = ResultedEvent.GenericResult.allowed(); + this.receivedResourcePack = receivedResourcePack; + this.serverConnection = serverConnection; + this.providedResourcePack = receivedResourcePack; + } + + public ServerConnection getServerConnection() { + return serverConnection; + } + + public ResourcePackInfo getReceivedResourcePack() { + return receivedResourcePack; + } + + public ResourcePackInfo getProvidedResourcePack() { + return providedResourcePack; + } + + public void setProvidedResourcePack(ResourcePackInfo providedResourcePack) { + this.providedResourcePack = providedResourcePack; + } + + @Override + public GenericResult getResult() { + return this.result; + } + + @Override + public void setResult(GenericResult result) { + this.result = result; + } +} diff --git a/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java b/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java index 4643dd42..4b9700a2 100644 --- a/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java +++ b/api/src/main/java/com/velocitypowered/api/proxy/player/ResourcePackInfo.java @@ -46,12 +46,48 @@ public interface ResourcePackInfo { byte[] getHash(); /** - * Gets the {@link Origin} of the resource-pack. + * Gets the {@link Origin} of this resource-pack. * * @return the origin of the resource pack */ Origin getOrigin(); + /** + * Gets the original {@link Origin} of the resource-pack. + * The original origin may differ if the resource pack was altered in the event + * {@link com.velocitypowered.api.event.player.ServerResourcePackSendEvent}. + * + * @return the origin of the resource pack + */ + Origin getOriginalOrigin(); + + /** + * Returns a copy of this {@link ResourcePackInfo} instance as a builder so that it can + * be modified. + * It is not guaranteed that + * {@code resourcePackInfo.asBuilder().build().equals(resourcePackInfo)} is true. That is due to + * the transient {@link ResourcePackInfo#getOrigin()} and + * {@link ResourcePackInfo#getOriginalOrigin()} fields. + * + * + * @return a content-copy of this instance as a {@link ResourcePackInfo.Builder} + */ + ResourcePackInfo.Builder asBuilder(); + + /** + * Returns a copy of this {@link ResourcePackInfo} instance as a builder with the new URL as the pack URL so that + * it can be modified. + * It is not guaranteed that + * {@code resourcePackInfo.asBuilder(resourcePackInfo.getUrl()).build().equals(resourcePackInfo)} is true. + * That is due to the transient {@link ResourcePackInfo#getOrigin()} and + * {@link ResourcePackInfo#getOriginalOrigin()} fields. + * + * @param newUrl The new URL to use in the updated builder. + * + * @return a content-copy of this instance as a {@link ResourcePackInfo.Builder} + */ + ResourcePackInfo.Builder asBuilder(String newUrl); + interface Builder { /** diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index 39cc3738..d83e6f0c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -21,12 +21,12 @@ import static com.velocitypowered.proxy.connection.backend.BungeeCordMessageResp import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; -import com.mojang.brigadier.builder.ArgumentBuilder; -import com.mojang.brigadier.tree.CommandNode; import com.mojang.brigadier.tree.RootCommandNode; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; +import com.velocitypowered.api.event.player.ServerResourcePackSendEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.player.ResourcePackInfo; @@ -45,6 +45,7 @@ import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.PlayerListItem; import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequest; +import com.velocitypowered.proxy.protocol.packet.ResourcePackResponse; import com.velocitypowered.proxy.protocol.packet.TabCompleteResponse; import com.velocitypowered.proxy.protocol.util.PluginMessageUtil; import io.netty.buffer.ByteBuf; @@ -52,7 +53,7 @@ import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.handler.timeout.ReadTimeoutException; -import java.util.Collection; + import java.util.regex.Pattern; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -138,17 +139,50 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public boolean handle(ResourcePackRequest packet) { ResourcePackInfo.Builder builder = new VelocityResourcePackInfo.BuilderImpl( - Preconditions.checkNotNull(packet.getUrl())) - .setPrompt(packet.getPrompt()) - .setShouldForce(packet.isRequired()); - // Why SpotBugs decides that this is unsafe I have no idea; - if (packet.getHash() != null && !Preconditions.checkNotNull(packet.getHash()).isEmpty()) { - if (PLAUSIBLE_SHA1_HASH.matcher(packet.getHash()).matches()) { - builder.setHash(ByteBufUtil.decodeHexDump(packet.getHash())); + Preconditions.checkNotNull(packet.getUrl())) + .setPrompt(packet.getPrompt()) + .setShouldForce(packet.isRequired()) + .setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER); + + String hash = packet.getHash(); + if (hash != null && !hash.isEmpty()) { + if (PLAUSIBLE_SHA1_HASH.matcher(hash).matches()) { + builder.setHash(ByteBufUtil.decodeHexDump(hash)); } } - serverConn.getPlayer().queueResourcePack(builder.build()); + ServerResourcePackSendEvent event = new ServerResourcePackSendEvent( + builder.build(), this.serverConn); + + server.getEventManager().fire(event).thenAcceptAsync(serverResourcePackSendEvent -> { + if (playerConnection.isClosed()) { + return; + } + if (serverResourcePackSendEvent.getResult().isAllowed()) { + ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack(); + if (toSend != serverResourcePackSendEvent.getReceivedResourcePack()) { + ((VelocityResourcePackInfo) toSend) + .setOriginalOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER); + } + + serverConn.getPlayer().queueResourcePack(builder.build()); + } else if (serverConn.getConnection() != null) { + serverConn.getConnection().write(new ResourcePackResponse( + packet.getHash(), + PlayerResourcePackStatusEvent.Status.DECLINED + )); + } + }, playerConnection.eventLoop()).exceptionally((ex) -> { + if (serverConn.getConnection() != null) { + serverConn.getConnection().write(new ResourcePackResponse( + packet.getHash(), + PlayerResourcePackStatusEvent.Status.DECLINED + )); + } + logger.error("Exception while handling resource pack send for {}", playerConnection, ex); + return null; + }); + return true; } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 88bc3dc2..f3f7f13a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -985,7 +985,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player { connection.eventLoop().execute(this::tickResourcePackQueue); } - return queued != null && queued.getOrigin() == ResourcePackInfo.Origin.DOWNSTREAM_SERVER; + return queued != null + && queued.getOriginalOrigin() == ResourcePackInfo.Origin.DOWNSTREAM_SERVER; } /** diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java index 250e9b3a..743bba87 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/VelocityResourcePackInfo.java @@ -28,6 +28,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { private final boolean shouldForce; private final @Nullable Component prompt; // 1.17+ only private final Origin origin; + private Origin originalOrigin; private VelocityResourcePackInfo(String url, @Nullable byte[] hash, boolean shouldForce, @Nullable Component prompt, Origin origin) { @@ -36,6 +37,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { this.shouldForce = shouldForce; this.prompt = prompt; this.origin = origin; + this.originalOrigin = origin; } @Override @@ -63,6 +65,31 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { return origin; } + public void setOriginalOrigin(Origin originalOrigin) { + this.originalOrigin = originalOrigin; + } + + @Override + public Origin getOriginalOrigin() { + return originalOrigin; + } + + @Override + public Builder asBuilder() { + return new BuilderImpl(url) + .setShouldForce(shouldForce) + .setHash(hash) + .setPrompt(prompt); + } + + @Override + public Builder asBuilder(String newUrl) { + return new BuilderImpl(newUrl) + .setShouldForce(shouldForce) + .setHash(hash) + .setPrompt(prompt); + } + public static final class BuilderImpl implements ResourcePackInfo.Builder { private final String url; private boolean shouldForce; @@ -102,8 +129,9 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo { return new VelocityResourcePackInfo(url, hash, shouldForce, prompt, origin); } - public void setOrigin(Origin origin) { + public BuilderImpl setOrigin(Origin origin) { this.origin = origin; + return this; } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java index 3269ff0d..53a9afff 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/ResourcePackResponse.java @@ -31,6 +31,14 @@ public class ResourcePackResponse implements MinecraftPacket { private String hash = ""; private @MonotonicNonNull Status status; + public ResourcePackResponse() { + } + + public ResourcePackResponse(String hash, @MonotonicNonNull Status status) { + this.hash = hash; + this.status = status; + } + public Status getStatus() { if (status == null) { throw new IllegalStateException("Packet not yet deserialized");