3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-12-24 15:20:35 +01:00

Refactor ResourcePacks handling (#1225)

* Initial ResourcePack refactor

* Implement sendResourcePacks method

* Initializes the ResourcePackHandler at player initialization

* Move adventure to velocity resource pack conversion to the same class

* Added some internal resource pack documentation

* Refactored Modern ResourcePack handling

* Handle RemoveResourcePackPacket from backend server

* Fixed license

* Use removeIf instead of manual iteration

* Improve ModernResourcePackHandler

* fix hash conversion

* bundle resource packs

* keep old constructors of PlayerResourcePackStatusEvent

* add @Nullable to PlayerResourcePackStatusEvent#getPackId

* Use a single instance of BundleDelimiterPacket

* Throw UnSupportedOperationException on operations not supported by LegacyResourcePackHandler

* Use a single instance on empty packets

* Handle active packet bundle sending from backend server in case of sending a packet bundle of resource packs

* Improve packet bundling

* Fixed login for players with version 1.20.2

---------

Co-authored-by: Gero <gecam59@gmail.com>
Dieser Commit ist enthalten in:
Adrian 2024-02-08 08:51:45 -05:00 committet von GitHub
Ursprung 825c3c68d1
Commit cbd07b1434
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
30 geänderte Dateien mit 945 neuen und 307 gelöschten Zeilen

Datei anzeigen

@ -12,6 +12,7 @@ import com.velocitypowered.api.event.annotation.AwaitingEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -24,6 +25,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
public class PlayerResourcePackStatusEvent {
private final Player player;
private final @MonotonicNonNull UUID packId;
private final Status status;
private final @MonotonicNonNull ResourcePackInfo packInfo;
private boolean overwriteKick;
@ -32,20 +34,31 @@ public class PlayerResourcePackStatusEvent {
* Instantiates this event.
*
* @deprecated Use {@link PlayerResourcePackStatusEvent#PlayerResourcePackStatusEvent
* (Player, Status, ResourcePackInfo)} instead.
* (Player, UUID, Status, ResourcePackInfo)} instead.
*/
@Deprecated
public PlayerResourcePackStatusEvent(Player player, Status status) {
this.player = Preconditions.checkNotNull(player, "player");
this.status = Preconditions.checkNotNull(status, "status");
this.packInfo = null;
this(player, null, status, null);
}
/**
* Instantiates this event.
*
* @deprecated Use {@link PlayerResourcePackStatusEvent#PlayerResourcePackStatusEvent
* (Player, UUID, Status, ResourcePackInfo)} instead.
*/
@Deprecated
public PlayerResourcePackStatusEvent(Player player, Status status, ResourcePackInfo packInfo) {
this(player, null, status, packInfo);
}
/**
* Instantiates this event.
*/
public PlayerResourcePackStatusEvent(Player player, Status status, ResourcePackInfo packInfo) {
public PlayerResourcePackStatusEvent(
Player player, UUID packId, Status status, ResourcePackInfo packInfo) {
this.player = Preconditions.checkNotNull(player, "player");
this.packId = packId == null ? packInfo == null ? null : packInfo.getId() : packId;
this.status = Preconditions.checkNotNull(status, "status");
this.packInfo = packInfo;
}
@ -59,6 +72,16 @@ public class PlayerResourcePackStatusEvent {
return player;
}
/**
* Returns the id of the resource pack.
*
* @return the id
*/
@Nullable
public UUID getPackId() {
return packId;
}
/**
* Returns the new status for the resource pack.
*

Datei anzeigen

@ -8,13 +8,14 @@
package com.velocitypowered.api.proxy.player;
import java.util.UUID;
import net.kyori.adventure.resource.ResourcePackRequestLike;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the information for a resource pack to apply that can be sent to the client.
*/
public interface ResourcePackInfo {
public interface ResourcePackInfo extends ResourcePackRequestLike {
/**
* Gets the id of this resource-pack.

Datei anzeigen

@ -20,6 +20,7 @@ package com.velocitypowered.proxy.connection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
@ -324,4 +325,8 @@ public interface MinecraftSessionHandler {
default boolean handle(ChatAcknowledgementPacket chatAcknowledgement) {
return false;
}
default boolean handle(BundleDelimiterPacket bundleDelimiterPacket) {
return false;
}
}

Datei anzeigen

@ -35,13 +35,16 @@ import com.velocitypowered.proxy.command.CommandGraphInjector;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
@ -125,6 +128,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return false;
}
@Override
public boolean handle(BundleDelimiterPacket bundleDelimiterPacket) {
serverConn.getPlayer().getBundleHandler().toggleBundleSession();
return false;
}
@Override
public boolean handle(StartUpdatePacket packet) {
MinecraftConnection smc = serverConn.ensureConnected();
@ -188,13 +197,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return;
}
if (serverResourcePackSendEvent.getResult().isAllowed()) {
ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack();
final ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack();
if (toSend != serverResourcePackSendEvent.getReceivedResourcePack()) {
((VelocityResourcePackInfo) toSend)
.setOriginalOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
}
serverConn.getPlayer().queueResourcePack(toSend);
serverConn.getPlayer().resourcePackHandler().queueResourcePack(toSend);
} else if (serverConn.getConnection() != null) {
serverConn.getConnection().write(new ResourcePackResponsePacket(
packet.getId(),
@ -219,7 +228,15 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(RemoveResourcePackPacket packet) {
return false; //TODO
final ConnectedPlayer player = serverConn.getPlayer();
final ResourcePackHandler handler = player.resourcePackHandler();
if (packet.getId() != null) {
handler.remove(packet.getId());
} else {
handler.clearAppliedResourcePacks();
}
playerConnection.write(packet);
return true;
}
@Override

Datei anzeigen

@ -84,8 +84,8 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
public void activated() {
ConnectedPlayer player = serverConn.getPlayer();
if (player.getProtocolVersion() == ProtocolVersion.MINECRAFT_1_20_2) {
resourcePackToApply = player.getAppliedResourcePack();
player.clearAppliedResourcePack();
resourcePackToApply = player.resourcePackHandler().getFirstAppliedPack();
player.resourcePackHandler().clearAppliedResourcePacks();
}
}
@ -136,7 +136,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
}
resourcePackToApply = null;
serverConn.getPlayer().queueResourcePack(toSend);
serverConn.getPlayer().resourcePackHandler().queueResourcePack(toSend);
} else if (serverConn.getConnection() != null) {
serverConn.getConnection().write(new ResourcePackResponsePacket(
packet.getId(), packet.getHash(), PlayerResourcePackStatusEvent.Status.DECLINED));
@ -174,8 +174,9 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
smc.setActiveSessionHandler(StateRegistry.PLAY,
new TransitionSessionHandler(server, serverConn, resultFuture));
}
if (player.getAppliedResourcePack() == null && resourcePackToApply != null) {
player.queueResourcePack(resourcePackToApply);
if (player.resourcePackHandler().getFirstAppliedPack() == null
&& resourcePackToApply != null) {
player.resourcePackHandler().queueResourcePack(resourcePackToApply);
}
smc.setAutoReading(true);
}, smc.eventLoop());
@ -228,7 +229,7 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
/**
* Represents the state of the configuration stage.
*/
public static enum State {
public enum State {
START, NEGOTIATING, PLUGIN_MESSAGE_INTERRUPT, RESOURCE_PACK_INTERRUPT, COMPLETE
}
}

Datei anzeigen

@ -142,10 +142,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
}
if (player.getIdentifiedKey() != null) {
IdentifiedKey playerKey = player.getIdentifiedKey();
final IdentifiedKey playerKey = player.getIdentifiedKey();
if (playerKey.getSignatureHolder() == null) {
if (playerKey instanceof IdentifiedKeyImpl) {
IdentifiedKeyImpl unlinkedKey = (IdentifiedKeyImpl) playerKey;
if (playerKey instanceof IdentifiedKeyImpl unlinkedKey) {
// Failsafe
if (!unlinkedKey.internalAddHolder(player.getUniqueId())) {
if (onlineMode) {
@ -153,7 +152,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
Component.translatable("multiplayer.disconnect.invalid_public_key"));
return;
} else {
logger.warn("Key for player " + player.getUsername() + " could not be verified!");
logger.warn("Key for player {} could not be verified!", player.getUsername());
}
}
} else {
@ -161,8 +160,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
}
} else {
if (!Objects.equals(playerKey.getSignatureHolder(), playerUniqueId)) {
logger.warn("UUID for Player " + player.getUsername() + " mismatches! "
+ "Chat/Commands signatures will not work correctly for this player!");
logger.warn("UUID for Player {} mismatches! "
+ "Chat/Commands signatures will not work correctly for this player!",
player.getUsername());
}
}
}
@ -240,7 +240,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
return server.getEventManager().fire(event).thenRunAsync(() -> {
Optional<RegisteredServer> toTry = event.getInitialServer();
if (!toTry.isPresent()) {
if (toTry.isEmpty()) {
player.disconnect0(
Component.translatable("velocity.error.no-available-servers", NamedTextColor.RED),
true);
@ -263,7 +263,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
this.inbound.cleanup();
}
static enum State {
enum State {
START, SUCCESS_SENT, ACKNOWLEDGED
}
}

Datei anzeigen

@ -0,0 +1,94 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import java.util.concurrent.CompletableFuture;
/**
* BundleDelimiterHandler.
*/
public final class BundleDelimiterHandler {
private final ConnectedPlayer player;
private boolean inBundleSession = false;
private CompletableFuture<Void> finishedBundleSessionFuture;
public BundleDelimiterHandler(ConnectedPlayer player) {
this.player = player;
}
public boolean isInBundleSession() {
return this.inBundleSession;
}
/**
* Toggles the player to be in the process of receiving multiple packets
* from the backend server via a packet bundle.
*/
public void toggleBundleSession() {
if (this.inBundleSession) {
this.finishedBundleSessionFuture.complete(null);
this.finishedBundleSessionFuture = null;
} else {
this.finishedBundleSessionFuture = new CompletableFuture<>();
}
this.inBundleSession = !this.inBundleSession;
}
/**
* Bundles all packets sent in the given Runnable.
*/
public CompletableFuture<Void> bundlePackets(final Runnable sendPackets) {
VelocityServerConnection connectedServer = player.getConnectedServer();
MinecraftConnection connection = connectedServer == null
? null : connectedServer.getConnection();
if (connection == null) {
sendPackets(sendPackets);
return CompletableFuture.completedFuture(null);
}
CompletableFuture<Void> future = new CompletableFuture<>();
connection.eventLoop().execute(() -> {
if (inBundleSession) {
finishedBundleSessionFuture.thenRun(() -> {
sendPackets(sendPackets);
future.complete(null);
});
} else {
if (connection.getState() == StateRegistry.PLAY) {
sendPackets(sendPackets);
} else {
sendPackets.run();
}
future.complete(null);
}
});
return future;
}
private void sendPackets(Runnable sendPackets) {
player.getConnection().write(BundleDelimiterPacket.INSTANCE);
try {
sendPackets.run();
} finally {
player.getConnection().write(BundleDelimiterPacket.INSTANCE);
}
}
}

Datei anzeigen

@ -22,6 +22,7 @@ import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
@ -70,17 +71,13 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
configSwitchFuture = new CompletableFuture<>();
}
@Override
public void deactivated() {
}
@Override
public boolean handle(KeepAlivePacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
final VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null) {
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
final Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection();
final MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) {
player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime));
smc.write(packet);
@ -101,7 +98,9 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
if (player.getConnectionInFlight() != null) {
player.getConnectionInFlight().ensureConnected().write(packet);
}
return player.onResourcePackResponse(packet.getStatus());
return player.resourcePackHandler().onResourcePackResponse(
new ResourcePackResponseBundle(packet.getId(), packet.getStatus())
);
}
@Override
@ -196,9 +195,9 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
smc.write(brandPacket);
}
player.getConnection().write(new FinishedUpdatePacket());
player.getConnection().write(FinishedUpdatePacket.INSTANCE);
smc.write(new FinishedUpdatePacket());
smc.write(FinishedUpdatePacket.INSTANCE);
smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
return configSwitchFuture;

Datei anzeigen

@ -38,6 +38,7 @@ import com.velocitypowered.proxy.connection.backend.BackendConnectionPhases;
import com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
@ -172,11 +173,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(KeepAlivePacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer();
final VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null) {
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
final Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection();
final MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) {
player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime));
smc.write(packet);
@ -204,7 +205,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return true;
}
if (!updateTimeKeeper(packet.getTimeStamp())) {
return true;
}
@ -392,7 +392,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(ResourcePackResponsePacket packet) {
return player.onResourcePackResponse(packet.getStatus());
return player.resourcePackHandler().onResourcePackResponse(
new ResourcePackResponseBundle(packet.getId(), packet.getStatus()));
}
@Override

Datei anzeigen

@ -31,7 +31,6 @@ import com.velocitypowered.api.event.player.KickedFromServerEvent.Notify;
import com.velocitypowered.api.event.player.KickedFromServerEvent.RedirectPlayer;
import com.velocitypowered.api.event.player.KickedFromServerEvent.ServerKickResult;
import com.velocitypowered.api.event.player.PlayerModInfoEvent;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.network.ProtocolVersion;
@ -55,6 +54,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
@ -66,7 +66,6 @@ import com.velocitypowered.proxy.protocol.packet.HeaderAndFooterPacket;
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ChatQueue;
import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
@ -82,19 +81,14 @@ import com.velocitypowered.proxy.tablist.VelocityTabListLegacy;
import com.velocitypowered.proxy.util.ClosestLocaleMatcher;
import com.velocitypowered.proxy.util.DurationUtils;
import com.velocitypowered.proxy.util.TranslatableMapper;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import java.net.InetSocketAddress;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.Queue;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@ -129,7 +123,6 @@ import org.jetbrains.annotations.NotNull;
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable,
VelocityInboundConnection {
private static final int MAX_PLUGIN_CHANNELS = 1024;
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build();
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
@ -159,14 +152,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private ClientConnectionPhase connectionPhase;
private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>();
private @MonotonicNonNull List<String> serversToTry = null;
private @MonotonicNonNull Boolean previousResourceResponse;
private final Queue<ResourcePackInfo> outstandingResourcePacks = new ArrayDeque<>();
private @Nullable ResourcePackInfo pendingResourcePack;
private @Nullable ResourcePackInfo appliedResourcePack;
private @NotNull List<ResourcePackInfo> pendingResourcePacks = new ArrayList<>();
private @NotNull List<ResourcePackInfo> appliedResourcePacks = new ArrayList<>();
private final ResourcePackHandler resourcePackHandler;
private final BundleDelimiterHandler bundleHandler = new BundleDelimiterHandler(this);
private final @NotNull Pointers pointers =
Player.super.pointers().toBuilder().withDynamic(Identity.UUID, this::getUniqueId)
Player.super.pointers().toBuilder()
.withDynamic(Identity.UUID, this::getUniqueId)
.withDynamic(Identity.NAME, this::getUsername)
.withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername()))
.withDynamic(Identity.LOCALE, this::getEffectiveLocale)
@ -174,7 +165,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
.withStatic(FacetPointers.TYPE, Type.PLAYER).build();
private @Nullable String clientBrand;
private @Nullable Locale effectiveLocale;
private @Nullable IdentifiedKey playerKey;
private final @Nullable IdentifiedKey playerKey;
private @Nullable ClientSettingsPacket clientSettingsPacket;
private final ChatQueue chatQueue;
private final ChatBuilderFactory chatBuilderFactory;
@ -200,6 +191,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.playerKey = playerKey;
this.chatQueue = new ChatQueue(this);
this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion());
this.resourcePackHandler = ResourcePackHandler.create(this, server);
}
/**
@ -219,6 +211,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return chatQueue;
}
public BundleDelimiterHandler getBundleHandler() {
return this.bundleHandler;
}
@Override
public @NonNull Identity identity() {
return this.identity;
@ -238,7 +234,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
@Override
public void setEffectiveLocale(Locale locale) {
public void setEffectiveLocale(final @Nullable Locale locale) {
effectiveLocale = locale;
}
@ -293,6 +289,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings;
}
@Nullable
public ClientSettingsPacket getClientSettingsPacket() {
return clientSettingsPacket;
}
@ -367,7 +364,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override
public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
Component translated = translateMessage(message);
final Component translated = translateMessage(message);
connection.write(getChatBuilderFactory().builder()
.component(translated).forIdentity(identity).toClient());
@ -432,7 +429,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
@Override
public void sendPlayerListHeaderAndFooter(final Component header, final Component footer) {
public void sendPlayerListHeaderAndFooter(final @NotNull Component header,
final @NotNull Component footer) {
Component translatedHeader = translateMessage(header);
Component translatedFooter = translateMessage(footer);
this.playerListHeader = translatedHeader;
@ -472,6 +470,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
}
@SuppressWarnings("ConstantValue")
@Override
public <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value) {
if (part == null) {
@ -744,11 +743,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return;
}
if (event.getResult() instanceof DisconnectPlayer) {
DisconnectPlayer res = (DisconnectPlayer) event.getResult();
if (event.getResult() instanceof final DisconnectPlayer res) {
disconnect(res.getReasonComponent());
} else if (event.getResult() instanceof RedirectPlayer) {
RedirectPlayer res = (RedirectPlayer) event.getResult();
} else if (event.getResult() instanceof final RedirectPlayer res) {
createConnectionRequest(res.getServer(), previousConnection).connect()
.whenCompleteAsync((status, throwable) -> {
if (throwable != null) {
@ -794,8 +791,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
break;
}
}, connection.eventLoop());
} else if (event.getResult() instanceof Notify) {
Notify res = (Notify) event.getResult();
} else if (event.getResult() instanceof final Notify res) {
if (event.kickedDuringServerConnect() && previousConnection != null) {
sendMessage(Identity.nil(), res.getMessageComponent());
} else {
@ -906,7 +902,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
DisconnectEvent.LoginStatus status;
if (connectedPlayer.isPresent()) {
if (!connectedPlayer.get().getCurrentServer().isPresent()) {
if (connectedPlayer.get().getCurrentServer().isEmpty()) {
status = LoginStatus.PRE_SERVER_JOIN;
} else {
status = connectedPlayer.get() == this ? LoginStatus.SUCCESSFUL_LOGIN
@ -933,9 +929,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override
public String toString() {
boolean isPlayerAddressLoggingEnabled = server.getConfiguration()
final boolean isPlayerAddressLoggingEnabled = server.getConfiguration()
.isPlayerAddressLoggingEnabled();
String playerIp =
final String playerIp =
isPlayerAddressLoggingEnabled ? getRemoteAddress().toString() : "<ip address withheld>";
return "[connected player] " + profile.getName() + " (" + playerIp + ")";
}
@ -956,11 +952,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
@Override
@Nullable
public String getClientBrand() {
return clientBrand;
}
void setClientBrand(String clientBrand) {
void setClientBrand(final @Nullable String clientBrand) {
this.clientBrand = clientBrand;
}
@ -981,6 +978,15 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
}
/**
* Get the ResourcePackHandler corresponding to the player's version.
*
* @return the ResourcePackHandler of this player
*/
public ResourcePackHandler resourcePackHandler() {
return this.resourcePackHandler;
}
@Override
@Deprecated
public void sendResourcePack(String url) {
@ -997,7 +1003,15 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
public void sendResourcePackOffer(ResourcePackInfo packInfo) {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
Preconditions.checkNotNull(packInfo, "packInfo");
queueResourcePack(packInfo);
this.resourcePackHandler.queueResourcePack(packInfo);
}
}
@Override
public void sendResourcePacks(@NotNull ResourcePackRequest request) {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
Preconditions.checkNotNull(request, "packRequest");
this.resourcePackHandler.queueResourcePack(request);
}
}
@ -1005,15 +1019,21 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
public void clearResourcePacks() {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
connection.write(new RemoveResourcePackPacket());
this.resourcePackHandler.clearAppliedResourcePacks();
}
}
@Override
public void removeResourcePacks(@NotNull UUID id, @NotNull UUID @NotNull ... others) {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
connection.write(new RemoveResourcePackPacket(id));
Preconditions.checkNotNull(id, "packUUID");
if (this.resourcePackHandler.remove(id)) {
connection.write(new RemoveResourcePackPacket(id));
}
for (final UUID other : others) {
connection.write(new RemoveResourcePackPacket(other));
if (this.resourcePackHandler.remove(other)) {
connection.write(new RemoveResourcePackPacket(other));
}
}
}
}
@ -1039,174 +1059,26 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
}
/**
* Queues a resource-pack for sending to the player and sends it immediately if the queue is
* empty.
*/
public void queueResourcePack(ResourcePackInfo info) {
outstandingResourcePacks.add(info);
if (outstandingResourcePacks.size() == 1) {
tickResourcePackQueue();
}
}
private void tickResourcePackQueue() {
ResourcePackInfo queued = outstandingResourcePacks.peek();
if (queued != null) {
// Check if the player declined a resource pack once already
if (previousResourceResponse != null && !previousResourceResponse) {
// If that happened we can flush the queue right away.
// Unless its 1.17+ and forced it will come back denied anyway
while (!outstandingResourcePacks.isEmpty()) {
queued = outstandingResourcePacks.peek();
if (queued.getShouldForce() && getProtocolVersion()
.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
break;
}
onResourcePackResponse(PlayerResourcePackStatusEvent.Status.DECLINED);
queued = null;
}
if (queued == null) {
// Exit as the queue was cleared
return;
}
}
ResourcePackRequestPacket request = new ResourcePackRequestPacket();
request.setId(queued.getId());
request.setUrl(queued.getUrl());
if (queued.getHash() != null) {
request.setHash(ByteBufUtil.hexDump(queued.getHash()));
} else {
request.setHash("");
}
request.setRequired(queued.getShouldForce());
request.setPrompt(queued.getPrompt() == null ? null :
new ComponentHolder(getProtocolVersion(), queued.getPrompt()));
connection.write(request);
}
}
@Override
@Deprecated
public @Nullable ResourcePackInfo getAppliedResourcePack() {
return appliedResourcePack;
return this.resourcePackHandler.getFirstAppliedPack();
}
@Override
@Deprecated
public @Nullable ResourcePackInfo getPendingResourcePack() {
return pendingResourcePack;
return this.resourcePackHandler.getFirstPendingPack();
}
@Override
public Collection<ResourcePackInfo> getAppliedResourcePacks() {
return new ArrayList<>(appliedResourcePacks);
return this.resourcePackHandler.getAppliedResourcePacks();
}
@Override
public Collection<ResourcePackInfo> getPendingResourcePacks() {
return new ArrayList<>(pendingResourcePacks);
}
/**
* Clears the applied resource pack field.
*/
public void clearAppliedResourcePack() {
appliedResourcePack = null;
}
/**
* Processes a client response to a sent resource-pack.
*/
public boolean onResourcePackResponse(PlayerResourcePackStatusEvent.Status status) {
final boolean peek = status.isIntermediate();
final ResourcePackInfo queued = peek
? outstandingResourcePacks.peek() : outstandingResourcePacks.poll();
server.getEventManager().fire(new PlayerResourcePackStatusEvent(this, status, queued))
.thenAcceptAsync(event -> {
if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED
&& event.getPackInfo() != null && event.getPackInfo().getShouldForce()
&& (!event.isOverwriteKick() || event.getPlayer()
.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_17))
) {
event.getPlayer().disconnect(Component
.translatable("multiplayer.requiredTexturePrompt.disconnect"));
}
});
switch (status) {
case ACCEPTED:
previousResourceResponse = true;
pendingResourcePack = queued;
if (this.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
pendingResourcePacks.clear();
}
pendingResourcePacks.add(queued);
break;
case DECLINED:
previousResourceResponse = false;
break;
case SUCCESSFUL:
appliedResourcePack = queued;
pendingResourcePack = null;
appliedResourcePacks.add(queued);
if (this.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
pendingResourcePacks.clear();
}
if (queued != null) {
pendingResourcePacks.removeIf(resourcePackInfo -> {
if (resourcePackInfo.getId() == null) {
return resourcePackInfo.getUrl().equals(queued.getUrl())
&& Arrays.equals(resourcePackInfo.getHash(), queued.getHash());
}
return resourcePackInfo.getId().equals(queued.getId());
});
}
break;
case FAILED_DOWNLOAD:
pendingResourcePack = null;
if (this.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
pendingResourcePacks.clear();
}
if (queued != null) {
pendingResourcePacks.removeIf(resourcePackInfo -> {
if (resourcePackInfo.getId() == null) {
return resourcePackInfo.getUrl().equals(queued.getUrl())
&& Arrays.equals(resourcePackInfo.getHash(), queued.getHash());
}
return resourcePackInfo.getId().equals(queued.getId());
});
}
break;
case DISCARDED:
if (queued != null && queued.getId() != null) {
appliedResourcePacks.removeIf(resourcePackInfo -> {
return queued.getId().equals(resourcePackInfo.getId());
});
}
break;
default:
break;
}
if (!peek) {
connection.eventLoop().execute(this::tickResourcePackQueue);
}
return queued != null
&& queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER;
}
/**
* Gives an indication about the previous resource pack responses.
*/
public @Nullable Boolean getPreviousResourceResponse() {
//TODO can probably be removed
return previousResourceResponse;
return this.resourcePackHandler.getPendingResourcePacks();
}
/**
@ -1228,7 +1100,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
*/
public void switchToConfigState() {
CompletableFuture.runAsync(() -> {
connection.write(new StartUpdatePacket());
connection.write(StartUpdatePacket.INSTANCE);
connection.getChannel().pipeline()
.get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
// Make sure we don't send any play packets to the player after update start
@ -1271,7 +1143,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}
}
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
private final RegisteredServer toConnect;
private final @Nullable VelocityRegisteredServer previousServer;
@ -1315,7 +1187,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
new ServerPreConnectEvent(ConnectedPlayer.this, toConnect, previousServer);
return server.getEventManager().fire(event).thenComposeAsync(newEvent -> {
Optional<RegisteredServer> newDest = newEvent.getResult().getServer();
if (!newDest.isPresent()) {
if (newDest.isEmpty()) {
return completedFuture(
plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED, toConnect));
}

Datei anzeigen

@ -67,7 +67,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(LegacyPingPacket packet) {
connection.setProtocolVersion(ProtocolVersion.LEGACY);
StatusSessionHandler handler =
final StatusSessionHandler handler =
new StatusSessionHandler(server, new LegacyInboundConnection(connection, packet));
connection.setActiveSessionHandler(StateRegistry.STATUS, handler);
handler.handle(packet);
@ -85,9 +85,9 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(HandshakePacket handshake) {
InitialInboundConnection ic = new InitialInboundConnection(connection,
final InitialInboundConnection ic = new InitialInboundConnection(connection,
cleanVhost(handshake.getServerAddress()), handshake);
StateRegistry nextState = getStateForProtocol(handshake.getNextStatus());
final StateRegistry nextState = getStateForProtocol(handshake.getNextStatus());
if (nextState == null) {
LOGGER.error("{} provided invalid protocol {}", ic, handshake.getNextStatus());
connection.close(true);
@ -96,14 +96,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
connection.setAssociation(ic);
switch (nextState) {
case STATUS:
connection.setActiveSessionHandler(StateRegistry.STATUS,
case STATUS -> connection.setActiveSessionHandler(StateRegistry.STATUS,
new StatusSessionHandler(server, ic));
break;
case LOGIN:
this.handleLogin(handshake, ic);
break;
default:
case LOGIN -> this.handleLogin(handshake, ic);
default ->
// If you get this, it's a bug in Velocity.
throw new AssertionError("getStateForProtocol provided invalid state!");
}
@ -113,24 +109,23 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
}
private static @Nullable StateRegistry getStateForProtocol(int status) {
switch (status) {
case StateRegistry.STATUS_ID:
return StateRegistry.STATUS;
case StateRegistry.LOGIN_ID:
return StateRegistry.LOGIN;
default:
return null;
}
return switch (status) {
case StateRegistry.STATUS_ID -> StateRegistry.STATUS;
case StateRegistry.LOGIN_ID -> StateRegistry.LOGIN;
default -> null;
};
}
private void handleLogin(HandshakePacket handshake, InitialInboundConnection ic) {
if (!ProtocolVersion.isSupported(handshake.getProtocolVersion())) {
ic.disconnectQuietly(Component.translatable("multiplayer.disconnect.outdated_client")
.args(Component.text(ProtocolVersion.SUPPORTED_VERSION_STRING)));
ic.disconnectQuietly(Component.translatable()
.key("multiplayer.disconnect.outdated_client")
.arguments(Component.text(ProtocolVersion.SUPPORTED_VERSION_STRING))
.build());
return;
}
InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
final InetAddress address = ((InetSocketAddress) connection.getRemoteAddress()).getAddress();
if (!server.getIpAttemptLimiter().attempt(address)) {
// Bump connection into correct protocol state so that we can send the disconnect packet.
connection.setState(StateRegistry.LOGIN);
@ -151,7 +146,7 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
return;
}
LoginInboundConnection lic = new LoginInboundConnection(ic);
final LoginInboundConnection lic = new LoginInboundConnection(ic);
server.getEventManager().fireAndForget(new ConnectionHandshakeEvent(lic));
connection.setActiveSessionHandler(StateRegistry.LOGIN,
new InitialLoginSessionHandler(server, connection, lic));
@ -213,16 +208,10 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
connection.close(true);
}
private static class LegacyInboundConnection implements VelocityInboundConnection {
private final MinecraftConnection connection;
private final LegacyPingPacket ping;
private LegacyInboundConnection(MinecraftConnection connection,
LegacyPingPacket ping) {
this.connection = connection;
this.ping = ping;
}
private record LegacyInboundConnection(
MinecraftConnection connection,
LegacyPingPacket ping
) implements VelocityInboundConnection {
@Override
public InetSocketAddress getRemoteAddress() {

Datei anzeigen

@ -74,9 +74,9 @@ public final class InitialInboundConnection implements VelocityInboundConnection
@Override
public String toString() {
boolean isPlayerAddressLoggingEnabled = connection.server.getConfiguration()
final boolean isPlayerAddressLoggingEnabled = connection.server.getConfiguration()
.isPlayerAddressLoggingEnabled();
String playerIp =
final String playerIp =
isPlayerAddressLoggingEnabled
? connection.getRemoteAddress().toString() : "<ip address withheld>";
return "[initial connection] " + playerIp;

Datei anzeigen

@ -95,7 +95,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
.thenAcceptAsync(
(event) -> {
StringBuilder json = new StringBuilder();
final StringBuilder json = new StringBuilder();
VelocityServer.getPingGsonInstance(connection.getProtocolVersion())
.toJson(event.getPing(), json);
connection.write(new StatusResponsePacket(json));

Datei anzeigen

@ -19,10 +19,14 @@ package com.velocitypowered.proxy.connection.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import io.netty.buffer.ByteBufUtil;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
import net.kyori.adventure.resource.ResourcePackRequest;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
/**
* Implements {@link ResourcePackInfo}.
@ -31,13 +35,13 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
private final UUID id;
private final String url;
private final @Nullable byte[] hash;
private final byte @Nullable [] hash;
private final boolean shouldForce;
private final @Nullable Component prompt; // 1.17+ only
private final Origin origin;
private Origin originalOrigin;
private VelocityResourcePackInfo(UUID id, String url, @Nullable byte[] hash, boolean shouldForce,
private VelocityResourcePackInfo(UUID id, String url, byte @Nullable [] hash, boolean shouldForce,
@Nullable Component prompt, Origin origin) {
this.id = id;
this.url = url;
@ -69,7 +73,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
}
@Override
public @Nullable byte[] getHash() {
public byte @Nullable[] getHash() {
return hash == null ? null : hash.clone(); // Thanks spotbugs, very helpful.
}
@ -105,6 +109,32 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
.setPrompt(prompt);
}
@Override
public @NotNull ResourcePackRequest asResourcePackRequest() {
return ResourcePackRequest.resourcePackRequest()
.packs(net.kyori.adventure.resource.ResourcePackInfo.resourcePackInfo()
.id(this.id)
.uri(URI.create(this.url))
.hash(this.hash == null ? "" : ByteBufUtil.hexDump(this.hash))
.build())
.required(this.shouldForce)
.prompt(this.prompt)
.build();
}
@SuppressWarnings("checkstyle:MissingJavadocMethod")
public static ResourcePackInfo fromAdventureRequest(
final ResourcePackRequest request,
final net.kyori.adventure.resource.ResourcePackInfo pack
) {
return new BuilderImpl(pack.uri().toString())
.setHash(pack.hash().isEmpty() ? null : ByteBufUtil.decodeHexDump(pack.hash()))
.setId(pack.id())
.setShouldForce(request.required())
.setPrompt(request.prompt())
.build();
}
/**
* Implements the builder for {@link ResourcePackInfo} instances.
*/
@ -113,7 +143,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
private UUID id;
private final String url;
private boolean shouldForce;
private @Nullable byte[] hash;
private byte @Nullable [] hash;
private @Nullable Component prompt;
private Origin origin = Origin.PLUGIN_ON_PROXY;
@ -135,7 +165,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
}
@Override
public BuilderImpl setHash(@Nullable byte[] hash) {
public BuilderImpl setHash(final byte @Nullable [] hash) {
if (hash != null) {
Preconditions.checkArgument(hash.length == 20, "Hash length is not 20");
this.hash = hash.clone(); // Thanks spotbugs, very helpful.

Datei anzeigen

@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.player.resourcepack;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
/**
* Legacy (Minecraft 1.17-1.20.2) ResourcePackHandler.
*/
public final class Legacy117ResourcePackHandler extends LegacyResourcePackHandler {
Legacy117ResourcePackHandler(final ConnectedPlayer player, final VelocityServer server) {
super(player, server);
}
@Override
protected boolean shouldDisconnectForForcePack(final PlayerResourcePackStatusEvent event) {
return super.shouldDisconnectForForcePack(event) && !event.isOverwriteKick();
}
}

Datei anzeigen

@ -0,0 +1,176 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.player.resourcepack;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.List;
import java.util.Queue;
import java.util.UUID;
import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
/**
* Legacy (Minecraft <1.17) ResourcePackHandler.
*/
public sealed class LegacyResourcePackHandler extends ResourcePackHandler
permits Legacy117ResourcePackHandler {
protected @MonotonicNonNull Boolean previousResourceResponse;
protected final Queue<ResourcePackInfo> outstandingResourcePacks = new ArrayDeque<>();
private @Nullable ResourcePackInfo pendingResourcePack;
private @Nullable ResourcePackInfo appliedResourcePack;
LegacyResourcePackHandler(final ConnectedPlayer player, final VelocityServer server) {
super(player, server);
}
@Override
@Nullable
public ResourcePackInfo getFirstAppliedPack() {
return appliedResourcePack;
}
@Override
@Nullable
public ResourcePackInfo getFirstPendingPack() {
return pendingResourcePack;
}
@Override
public @NotNull Collection<ResourcePackInfo> getAppliedResourcePacks() {
if (appliedResourcePack == null) {
return List.of();
}
return List.of(appliedResourcePack);
}
@Override
public @NotNull Collection<ResourcePackInfo> getPendingResourcePacks() {
if (pendingResourcePack == null) {
return List.of();
}
return List.of(pendingResourcePack);
}
@Override
public void clearAppliedResourcePacks() {
// This is valid only for players with 1.20.2 versions
this.appliedResourcePack = null;
}
@Override
public boolean remove(final @NotNull UUID id) throws UnsupportedOperationException {
throw new UnsupportedOperationException("Cannot remove a ResourcePack from a legacy client");
}
@Override
public void queueResourcePack(@NotNull ResourcePackInfo info) {
outstandingResourcePacks.add(info);
if (outstandingResourcePacks.size() == 1) {
tickResourcePackQueue();
}
}
private void tickResourcePackQueue() {
ResourcePackInfo queued = outstandingResourcePacks.peek();
if (queued != null) {
// Check if the player declined a resource pack once already
if (previousResourceResponse != null && !previousResourceResponse) {
// If that happened we can flush the queue right away.
// Unless its 1.17+ and forced it will come back denied anyway
while (!outstandingResourcePacks.isEmpty()) {
queued = outstandingResourcePacks.peek();
if (queued.getShouldForce() && player.getProtocolVersion()
.noLessThan(ProtocolVersion.MINECRAFT_1_17)) {
break;
}
onResourcePackResponse(new ResourcePackResponseBundle(queued.getId(),
PlayerResourcePackStatusEvent.Status.DECLINED));
queued = null;
}
if (queued == null) {
// Exit as the queue was cleared
return;
}
}
sendResourcePackRequestPacket(queued);
}
}
@Override
public boolean onResourcePackResponse(
final @NotNull ResourcePackResponseBundle bundle
) {
final boolean peek = bundle.status().isIntermediate();
final ResourcePackInfo queued = peek
? outstandingResourcePacks.peek() : outstandingResourcePacks.poll();
server.getEventManager()
.fire(new PlayerResourcePackStatusEvent(
this.player, bundle.uuid(), bundle.status(), queued))
.thenAcceptAsync(event -> {
if (shouldDisconnectForForcePack(event)) {
event.getPlayer().disconnect(Component
.translatable("multiplayer.requiredTexturePrompt.disconnect"));
}
});
switch (bundle.status()) {
case ACCEPTED -> {
previousResourceResponse = true;
pendingResourcePack = queued;
}
case DECLINED -> previousResourceResponse = false;
case SUCCESSFUL -> {
appliedResourcePack = queued;
pendingResourcePack = null;
}
case FAILED_DOWNLOAD -> pendingResourcePack = null;
case DISCARDED -> {
if (queued != null && queued.getId() != null
&& appliedResourcePack != null
&& appliedResourcePack.getId().equals(queued.getId())) {
appliedResourcePack = null;
}
}
default -> {
}
}
if (!peek) {
player.getConnection().eventLoop().execute(this::tickResourcePackQueue);
}
return queued != null
&& queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER;
}
protected boolean shouldDisconnectForForcePack(final PlayerResourcePackStatusEvent event) {
return event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED
&& event.getPackInfo() != null && event.getPackInfo().getShouldForce();
}
}

Datei anzeigen

@ -0,0 +1,176 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.player.resourcepack;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import net.kyori.adventure.resource.ResourcePackRequest;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Modern (Minecraft 1.20.3+) ResourcePackHandler
*/
public final class ModernResourcePackHandler extends ResourcePackHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(ModernResourcePackHandler.class);
private final ListMultimap<UUID, ResourcePackInfo> outstandingResourcePacks =
Multimaps.newListMultimap(new ConcurrentHashMap<>(), LinkedList::new);
private final Map<UUID, ResourcePackInfo> pendingResourcePacks = new ConcurrentHashMap<>();
private final Map<UUID, ResourcePackInfo> appliedResourcePacks = new ConcurrentHashMap<>();
ModernResourcePackHandler(final ConnectedPlayer player, final VelocityServer server) {
super(player, server);
}
@Override
public @Nullable ResourcePackInfo getFirstAppliedPack() {
if (appliedResourcePacks.isEmpty()) {
return null;
}
return appliedResourcePacks.values().iterator().next();
}
@Override
public @Nullable ResourcePackInfo getFirstPendingPack() {
if (pendingResourcePacks.isEmpty()) {
return null;
}
return pendingResourcePacks.values().iterator().next();
}
@Override
public @NotNull Collection<ResourcePackInfo> getAppliedResourcePacks() {
return List.copyOf(appliedResourcePacks.values());
}
@Override
public @NotNull Collection<ResourcePackInfo> getPendingResourcePacks() {
return List.copyOf(pendingResourcePacks.values());
}
@Override
public void clearAppliedResourcePacks() {
this.outstandingResourcePacks.clear();
this.pendingResourcePacks.clear();
this.appliedResourcePacks.clear();
}
@Override
public boolean remove(final @NotNull UUID uuid) {
outstandingResourcePacks.removeAll(uuid);
return appliedResourcePacks.remove(uuid) != null | pendingResourcePacks.remove(uuid) != null;
}
@Override
public void queueResourcePack(final @NotNull ResourcePackInfo info) {
final List<ResourcePackInfo> outstandingResourcePacks =
this.outstandingResourcePacks.get(info.getId());
outstandingResourcePacks.add(info);
if (outstandingResourcePacks.size() == 1) {
tickResourcePackQueue(outstandingResourcePacks.get(0).getId());
}
}
@Override
public void queueResourcePack(final @NotNull ResourcePackRequest request) {
if (request.packs().size() > 1) {
player.getBundleHandler().bundlePackets(() -> {
super.queueResourcePack(request);
});
} else {
super.queueResourcePack(request);
}
}
private void tickResourcePackQueue(final @NotNull UUID uuid) {
final List<ResourcePackInfo> outstandingResourcePacks =
this.outstandingResourcePacks.get(uuid);
if (!outstandingResourcePacks.isEmpty()) {
sendResourcePackRequestPacket(outstandingResourcePacks.get(0));
}
}
@Override
public boolean onResourcePackResponse(
final @NotNull ResourcePackResponseBundle bundle
) {
final UUID uuid = bundle.uuid();
final List<ResourcePackInfo> outstandingResourcePacks =
this.outstandingResourcePacks.get(uuid);
final boolean peek = bundle.status().isIntermediate();
final ResourcePackInfo queued = outstandingResourcePacks.isEmpty() ? null :
peek ? outstandingResourcePacks.get(0) : outstandingResourcePacks.remove(0);
server.getEventManager()
.fire(new PlayerResourcePackStatusEvent(this.player, uuid, bundle.status(), queued))
.thenAcceptAsync(event -> {
if (event.getStatus() == PlayerResourcePackStatusEvent.Status.DECLINED
&& event.getPackInfo() != null && event.getPackInfo().getShouldForce()
&& !event.isOverwriteKick()
) {
player.disconnect(Component
.translatable("multiplayer.requiredTexturePrompt.disconnect"));
}
});
switch (bundle.status()) {
// The player has accepted the resource pack and will proceed to download it.
case ACCEPTED -> {
if (queued != null) {
pendingResourcePacks.put(uuid, queued);
}
}
// The resource pack has been applied correctly.
case SUCCESSFUL -> {
if (queued != null) {
appliedResourcePacks.put(uuid, queued);
}
pendingResourcePacks.remove(uuid);
}
// An error occurred while trying to download the resource pack to the client,
// so the resource pack cannot be applied.
case DISCARDED, DECLINED, FAILED_RELOAD, FAILED_DOWNLOAD, INVALID_URL -> {
pendingResourcePacks.remove(uuid);
appliedResourcePacks.remove(uuid);
}
// The other cases in which no action is taken are documented in the javadocs.
default -> {
}
}
if (!peek) {
player.getConnection().eventLoop().execute(() -> tickResourcePackQueue(uuid));
}
return queued != null
&& queued.getOriginalOrigin() != ResourcePackInfo.Origin.DOWNSTREAM_SERVER;
}
}

Datei anzeigen

@ -0,0 +1,142 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.player.resourcepack;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBufUtil;
import java.util.Collection;
import java.util.UUID;
import net.kyori.adventure.resource.ResourcePackRequest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* ResourcePackHandler.
*/
public abstract sealed class ResourcePackHandler
permits LegacyResourcePackHandler, ModernResourcePackHandler {
protected final ConnectedPlayer player;
protected final VelocityServer server;
protected ResourcePackHandler(final ConnectedPlayer player, final VelocityServer server) {
this.player = player;
this.server = server;
}
/**
* Creates a new ResourcePackHandler.
*
* @param player the player.
* @param server the velocity server
*
* @return a new ResourcePackHandler
*/
public static @NotNull ResourcePackHandler create(final ConnectedPlayer player,
final VelocityServer server) {
final ProtocolVersion protocolVersion = player.getProtocolVersion();
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_17)) {
return new LegacyResourcePackHandler(player, server);
}
if (protocolVersion.lessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
return new Legacy117ResourcePackHandler(player, server);
}
return new ModernResourcePackHandler(player, server);
}
public abstract @Nullable ResourcePackInfo getFirstAppliedPack();
public abstract @Nullable ResourcePackInfo getFirstPendingPack();
public abstract @NotNull Collection<ResourcePackInfo> getAppliedResourcePacks();
public abstract @NotNull Collection<ResourcePackInfo> getPendingResourcePacks();
/**
* Clears the applied resource pack field.
*/
public abstract void clearAppliedResourcePacks();
public abstract boolean remove(final UUID id);
/**
* Queues a resource-pack for sending to the player and sends it immediately if the queue is
* empty.
*/
public abstract void queueResourcePack(final @NotNull ResourcePackInfo info);
/**
* Queues a resource-request for sending to the player and sends it immediately if the queue is
* empty.
*/
public void queueResourcePack(final @NotNull ResourcePackRequest request) {
for (final net.kyori.adventure.resource.ResourcePackInfo pack : request.packs()) {
queueResourcePack(VelocityResourcePackInfo.fromAdventureRequest(request, pack));
}
}
protected void sendResourcePackRequestPacket(final @NotNull ResourcePackInfo queued) {
final ResourcePackRequestPacket request = new ResourcePackRequestPacket();
request.setId(queued.getId());
request.setUrl(queued.getUrl());
if (queued.getHash() != null) {
request.setHash(ByteBufUtil.hexDump(queued.getHash()));
} else {
request.setHash("");
}
request.setRequired(queued.getShouldForce());
request.setPrompt(queued.getPrompt() == null ? null :
new ComponentHolder(player.getProtocolVersion(), queued.getPrompt()));
player.getConnection().write(request);
}
/**
* Processes a client response to a sent resource-pack.
* <ul>
* <p>Cases in which no action will be taken:</p>
*
* <br><li><b>DOWNLOADED</b>
* <p>In this case the resource pack is downloaded and will be applied to the client,
* no action is required in Velocity.</p>
*
* <br><li><b>INVALID_URL</b>
* <p>In this case, the client has received a resource pack request
* and the first check it performs is if the URL is valid, if not,
* it will return this value</p>
*
* <br><li><b>FAILED_RELOAD</b>
* <p>In this case, when trying to reload the client's resources,
* an error occurred while reloading a resource pack</p>
* </ul>
*
* <br><li><b>DECLINED</b>
* <p>Only in modern versions, as the resource pack has already been rejected,
* there is nothing to do, if the resource pack is required,
* the client will be kicked out of the server.</p>
*
* @param bundle the resource pack response bundle
*/
public abstract boolean onResourcePackResponse(
final @NotNull ResourcePackResponseBundle bundle);
}

Datei anzeigen

@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.connection.player.resourcepack;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import java.util.UUID;
@SuppressWarnings("checkstyle:MissingJavadocType")
public record ResourcePackResponseBundle(UUID uuid, PlayerResourcePackStatusEvent.Status status) {
}

Datei anzeigen

@ -119,7 +119,7 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
if (revision == Revision.GENERIC_V1) {
String pemKey = EncryptionUtils.pemEncodeRsaKey(publicKey);
long expires = expiryTemporal.toEpochMilli();
byte[] toVerify = ("" + expires + pemKey).getBytes(StandardCharsets.US_ASCII);
byte[] toVerify = (expires + pemKey).getBytes(StandardCharsets.US_ASCII);
return EncryptionUtils.verifySignature(
EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature,
toVerify);
@ -166,12 +166,10 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
if (this == o) {
return true;
}
if (!(o instanceof IdentifiedKey)) {
if (!(o instanceof final IdentifiedKey that)) {
return false;
}
IdentifiedKey that = (IdentifiedKey) o;
return Objects.equal(this.getSignedPublicKey(), that.getSignedPublicKey())
&& Objects.equal(this.getExpiryTemporal(), that.getExpiryTemporal())
&& Arrays.equals(this.getSignature(), that.getSignature())

Datei anzeigen

@ -48,6 +48,7 @@ import static com.velocitypowered.proxy.protocol.ProtocolUtils.Direction.SERVERB
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import com.velocitypowered.proxy.protocol.packet.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
@ -144,7 +145,7 @@ public enum StateRegistry {
PluginMessagePacket.class, PluginMessagePacket::new,
map(0x01, MINECRAFT_1_20_2, false));
serverbound.register(
FinishedUpdatePacket.class, FinishedUpdatePacket::new,
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x02, MINECRAFT_1_20_2, false));
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x03, MINECRAFT_1_20_2, false));
@ -163,7 +164,7 @@ public enum StateRegistry {
DisconnectPacket.class, () -> new DisconnectPacket(false),
map(0x01, MINECRAFT_1_20_2, false));
clientbound.register(
FinishedUpdatePacket.class, FinishedUpdatePacket::new,
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x02, MINECRAFT_1_20_2, false));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x03, MINECRAFT_1_20_2, false));
@ -289,7 +290,7 @@ public enum StateRegistry {
map(0x27, MINECRAFT_1_20_2, false),
map(0x28, MINECRAFT_1_20_3, false));
serverbound.register(
FinishedUpdatePacket.class, FinishedUpdatePacket::new,
FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x0B, MINECRAFT_1_20_2, false));
clientbound.register(
@ -569,9 +570,13 @@ public enum StateRegistry {
map(0x49, MINECRAFT_1_20_3, false));
clientbound.register(
StartUpdatePacket.class,
StartUpdatePacket::new,
() -> StartUpdatePacket.INSTANCE,
map(0x65, MINECRAFT_1_20_2, false),
map(0x67, MINECRAFT_1_20_3, false));
clientbound.register(
BundleDelimiterPacket.class,
() -> BundleDelimiterPacket.INSTANCE,
map(0x00, MINECRAFT_1_19_4, false));
}
},
LOGIN {

Datei anzeigen

@ -39,7 +39,7 @@ public class LegacyPingEncoder extends MessageToByteEncoder<LegacyDisconnect> {
protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out)
throws Exception {
out.writeByte(0xff);
writeLegacyString(out, msg.getReason());
writeLegacyString(out, msg.reason());
}
private static void writeLegacyString(ByteBuf out, String string) {

Datei anzeigen

@ -0,0 +1,46 @@
/*
* Copyright (C) 2018-2021 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.packet;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public final class BundleDelimiterPacket implements MinecraftPacket {
public static final BundleDelimiterPacket INSTANCE = new BundleDelimiterPacket();
private BundleDelimiterPacket() {
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
}
@Override
public void encode(ByteBuf buf, ProtocolUtils.Direction direction, ProtocolVersion version) {
}
@Override
public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this);
}
}

Datei anzeigen

@ -25,19 +25,14 @@ import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
public class LegacyDisconnect {
@SuppressWarnings("checkstyle:MissingJavadocType")
public record LegacyDisconnect(String reason) {
private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0,
ImmutableList.of());
private static final String LEGACY_COLOR_CODE = Character
.toString(LegacyComponentSerializer.SECTION_CHAR);
private final String reason;
private LegacyDisconnect(String reason) {
this.reason = reason;
}
/**
* Converts a modern server list ping response into an legacy disconnect packet.
*
@ -47,22 +42,21 @@ public class LegacyDisconnect {
*/
public static LegacyDisconnect fromServerPing(ServerPing response,
LegacyMinecraftPingVersion version) {
Players players = response.getPlayers().orElse(FAKE_PLAYERS);
final Players players = response.getPlayers().orElse(FAKE_PLAYERS);
switch (version) {
case MINECRAFT_1_3:
return switch (version) {
case MINECRAFT_1_3 ->
// Minecraft 1.3 and below use the section symbol as a delimiter. Accordingly, we must
// remove all section symbols, along with fetching just the first line of an (unformatted)
// MOTD.
return new LegacyDisconnect(String.join(LEGACY_COLOR_CODE,
new LegacyDisconnect(String.join(LEGACY_COLOR_CODE,
cleanSectionSymbol(getFirstLine(PlainTextComponentSerializer.plainText().serialize(
response.getDescriptionComponent()))),
Integer.toString(players.getOnline()),
Integer.toString(players.getMax())));
case MINECRAFT_1_4:
case MINECRAFT_1_6:
case MINECRAFT_1_4, MINECRAFT_1_6 ->
// Minecraft 1.4-1.6 provide support for more fields, and additionally support color codes.
return new LegacyDisconnect(String.join("\0",
new LegacyDisconnect(String.join("\0",
LEGACY_COLOR_CODE + "1",
Integer.toString(response.getVersion().getProtocol()),
response.getVersion().getName(),
@ -71,9 +65,8 @@ public class LegacyDisconnect {
Integer.toString(players.getOnline()),
Integer.toString(players.getMax())
));
default:
throw new IllegalArgumentException("Unknown version " + version);
}
default -> throw new IllegalArgumentException("Unknown version " + version);
};
}
private static String cleanSectionSymbol(String string) {
@ -81,7 +74,7 @@ public class LegacyDisconnect {
}
private static String getFirstLine(String legacyMotd) {
int newline = legacyMotd.indexOf('\n');
final int newline = legacyMotd.indexOf('\n');
return newline == -1 ? legacyMotd : legacyMotd.substring(0, newline);
}
@ -93,11 +86,7 @@ public class LegacyDisconnect {
*/
public static LegacyDisconnect from(TextComponent component) {
// We intentionally use the legacy serializers, because the old clients can't understand JSON.
String serialized = LegacyComponentSerializer.legacySection().serialize(component);
final String serialized = LegacyComponentSerializer.legacySection().serialize(component);
return new LegacyDisconnect(serialized);
}
public String getReason() {
return reason;
}
}

Datei anzeigen

@ -23,7 +23,6 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf;
import java.util.UUID;
public class RemoveResourcePackPacket implements MinecraftPacket {
@ -37,6 +36,10 @@ public class RemoveResourcePackPacket implements MinecraftPacket {
this.id = id;
}
public UUID getId() {
return id;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (buf.readBoolean()) {

Datei anzeigen

@ -28,11 +28,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ResourcePackRequestPacket implements MinecraftPacket {
@ -80,7 +79,7 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
return prompt;
}
public void setPrompt(ComponentHolder prompt) {
public void setPrompt(@Nullable ComponentHolder prompt) {
this.prompt = prompt;
}
@ -126,7 +125,7 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
}
public VelocityResourcePackInfo toServerPromptedPack() {
ResourcePackInfo.Builder builder =
final ResourcePackInfo.Builder builder =
new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url))
.setId(id).setPrompt(prompt == null ? null : prompt.getComponent())
.setShouldForce(isRequired).setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);

Datei anzeigen

@ -54,6 +54,10 @@ public class ResourcePackResponsePacket implements MinecraftPacket {
return hash;
}
public UUID getId() {
return id;
}
@Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {

Datei anzeigen

@ -24,6 +24,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class FinishedUpdatePacket implements MinecraftPacket {
public static final FinishedUpdatePacket INSTANCE = new FinishedUpdatePacket();
private FinishedUpdatePacket() {
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,

Datei anzeigen

@ -24,6 +24,10 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
public class StartUpdatePacket implements MinecraftPacket {
public static final StartUpdatePacket INSTANCE = new StartUpdatePacket();
private StartUpdatePacket() {
}
@Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction,

Datei anzeigen

@ -44,8 +44,8 @@ public enum TranslatableMapper implements BiConsumer<TranslatableComponent, Cons
final Consumer<Component> componentConsumer
) {
for (final Translator source : GlobalTranslator.translator().sources()) {
if (source instanceof TranslationRegistry
&& ((TranslationRegistry) source).contains(translatableComponent.key())) {
if (source instanceof TranslationRegistry registry
&& registry.contains(translatableComponent.key())) {
componentConsumer.accept(GlobalTranslator.render(translatableComponent,
ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
return;
@ -56,8 +56,7 @@ public enum TranslatableMapper implements BiConsumer<TranslatableComponent, Cons
return;
}
for (final Translator source : GlobalTranslator.translator().sources()) {
if (source instanceof TranslationRegistry
&& ((TranslationRegistry) source).contains(fallback)) {
if (source instanceof TranslationRegistry registry && registry.contains(fallback)) {
componentConsumer.accept(
GlobalTranslator.render(Component.translatable(fallback),
ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));