13
0
geforkt von Mirrors/Velocity

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

Datei anzeigen

@ -8,13 +8,14 @@
package com.velocitypowered.api.proxy.player; package com.velocitypowered.api.proxy.player;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.resource.ResourcePackRequestLike;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
/** /**
* Represents the information for a resource pack to apply that can be sent to the client. * 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. * 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.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket; import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket; 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.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
@ -324,4 +325,8 @@ public interface MinecraftSessionHandler {
default boolean handle(ChatAcknowledgementPacket chatAcknowledgement) { default boolean handle(ChatAcknowledgementPacket chatAcknowledgement) {
return false; 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.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; 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.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket; import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket; 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.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket; import com.velocitypowered.proxy.protocol.packet.KeepAlivePacket;
@ -125,6 +128,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return false; return false;
} }
@Override
public boolean handle(BundleDelimiterPacket bundleDelimiterPacket) {
serverConn.getPlayer().getBundleHandler().toggleBundleSession();
return false;
}
@Override @Override
public boolean handle(StartUpdatePacket packet) { public boolean handle(StartUpdatePacket packet) {
MinecraftConnection smc = serverConn.ensureConnected(); MinecraftConnection smc = serverConn.ensureConnected();
@ -188,13 +197,13 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
return; return;
} }
if (serverResourcePackSendEvent.getResult().isAllowed()) { if (serverResourcePackSendEvent.getResult().isAllowed()) {
ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack(); final ResourcePackInfo toSend = serverResourcePackSendEvent.getProvidedResourcePack();
if (toSend != serverResourcePackSendEvent.getReceivedResourcePack()) { if (toSend != serverResourcePackSendEvent.getReceivedResourcePack()) {
((VelocityResourcePackInfo) toSend) ((VelocityResourcePackInfo) toSend)
.setOriginalOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER); .setOriginalOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);
} }
serverConn.getPlayer().queueResourcePack(toSend); serverConn.getPlayer().resourcePackHandler().queueResourcePack(toSend);
} else if (serverConn.getConnection() != null) { } else if (serverConn.getConnection() != null) {
serverConn.getConnection().write(new ResourcePackResponsePacket( serverConn.getConnection().write(new ResourcePackResponsePacket(
packet.getId(), packet.getId(),
@ -219,7 +228,15 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(RemoveResourcePackPacket packet) { 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 @Override

Datei anzeigen

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

Datei anzeigen

@ -142,10 +142,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
} }
if (player.getIdentifiedKey() != null) { if (player.getIdentifiedKey() != null) {
IdentifiedKey playerKey = player.getIdentifiedKey(); final IdentifiedKey playerKey = player.getIdentifiedKey();
if (playerKey.getSignatureHolder() == null) { if (playerKey.getSignatureHolder() == null) {
if (playerKey instanceof IdentifiedKeyImpl) { if (playerKey instanceof IdentifiedKeyImpl unlinkedKey) {
IdentifiedKeyImpl unlinkedKey = (IdentifiedKeyImpl) playerKey;
// Failsafe // Failsafe
if (!unlinkedKey.internalAddHolder(player.getUniqueId())) { if (!unlinkedKey.internalAddHolder(player.getUniqueId())) {
if (onlineMode) { if (onlineMode) {
@ -153,7 +152,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
Component.translatable("multiplayer.disconnect.invalid_public_key")); Component.translatable("multiplayer.disconnect.invalid_public_key"));
return; return;
} else { } else {
logger.warn("Key for player " + player.getUsername() + " could not be verified!"); logger.warn("Key for player {} could not be verified!", player.getUsername());
} }
} }
} else { } else {
@ -161,8 +160,9 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
} }
} else { } else {
if (!Objects.equals(playerKey.getSignatureHolder(), playerUniqueId)) { if (!Objects.equals(playerKey.getSignatureHolder(), playerUniqueId)) {
logger.warn("UUID for Player " + player.getUsername() + " mismatches! " logger.warn("UUID for Player {} mismatches! "
+ "Chat/Commands signatures will not work correctly for this player!"); + "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(() -> { return server.getEventManager().fire(event).thenRunAsync(() -> {
Optional<RegisteredServer> toTry = event.getInitialServer(); Optional<RegisteredServer> toTry = event.getInitialServer();
if (!toTry.isPresent()) { if (toTry.isEmpty()) {
player.disconnect0( player.disconnect0(
Component.translatable("velocity.error.no-available-servers", NamedTextColor.RED), Component.translatable("velocity.error.no-available-servers", NamedTextColor.RED),
true); true);
@ -263,7 +263,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler {
this.inbound.cleanup(); this.inbound.cleanup();
} }
static enum State { enum State {
START, SUCCESS_SENT, ACKNOWLEDGED 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.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; 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.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
@ -70,17 +71,13 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
configSwitchFuture = new CompletableFuture<>(); configSwitchFuture = new CompletableFuture<>();
} }
@Override
public void deactivated() {
}
@Override @Override
public boolean handle(KeepAlivePacket packet) { public boolean handle(KeepAlivePacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer(); final VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null) { if (serverConnection != null) {
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId()); final Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
if (sentTime != null) { if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection(); final MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) { if (smc != null) {
player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime)); player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime));
smc.write(packet); smc.write(packet);
@ -101,7 +98,9 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
if (player.getConnectionInFlight() != null) { if (player.getConnectionInFlight() != null) {
player.getConnectionInFlight().ensureConnected().write(packet); player.getConnectionInFlight().ensureConnected().write(packet);
} }
return player.onResourcePackResponse(packet.getStatus()); return player.resourcePackHandler().onResourcePackResponse(
new ResourcePackResponseBundle(packet.getId(), packet.getStatus())
);
} }
@Override @Override
@ -196,9 +195,9 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
smc.write(brandPacket); 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); smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
return configSwitchFuture; 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.BungeeCordMessageResponder;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants; 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.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket; import com.velocitypowered.proxy.protocol.packet.BossBarPacket;
@ -172,11 +173,11 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(KeepAlivePacket packet) { public boolean handle(KeepAlivePacket packet) {
VelocityServerConnection serverConnection = player.getConnectedServer(); final VelocityServerConnection serverConnection = player.getConnectedServer();
if (serverConnection != null) { if (serverConnection != null) {
Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId()); final Long sentTime = serverConnection.getPendingPings().remove(packet.getRandomId());
if (sentTime != null) { if (sentTime != null) {
MinecraftConnection smc = serverConnection.getConnection(); final MinecraftConnection smc = serverConnection.getConnection();
if (smc != null) { if (smc != null) {
player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime)); player.setPing(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - sentTime));
smc.write(packet); smc.write(packet);
@ -204,7 +205,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
return true; return true;
} }
if (!updateTimeKeeper(packet.getTimeStamp())) { if (!updateTimeKeeper(packet.getTimeStamp())) {
return true; return true;
} }
@ -392,7 +392,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(ResourcePackResponsePacket packet) { public boolean handle(ResourcePackResponsePacket packet) {
return player.onResourcePackResponse(packet.getStatus()); return player.resourcePackHandler().onResourcePackResponse(
new ResourcePackResponseBundle(packet.getId(), packet.getStatus()));
} }
@Override @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.RedirectPlayer;
import com.velocitypowered.api.event.player.KickedFromServerEvent.ServerKickResult; import com.velocitypowered.api.event.player.KickedFromServerEvent.ServerKickResult;
import com.velocitypowered.api.event.player.PlayerModInfoEvent; 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.PlayerSettingsChangedEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.network.ProtocolVersion; 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.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; 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.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; 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.KeepAlivePacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.RemoveResourcePackPacket; 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.ChatQueue;
import com.velocitypowered.proxy.protocol.packet.chat.ChatType; import com.velocitypowered.proxy.protocol.packet.chat.ChatType;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; 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.ClosestLocaleMatcher;
import com.velocitypowered.proxy.util.DurationUtils; import com.velocitypowered.proxy.util.DurationUtils;
import com.velocitypowered.proxy.util.TranslatableMapper; import com.velocitypowered.proxy.util.TranslatableMapper;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@ -129,7 +123,6 @@ import org.jetbrains.annotations.NotNull;
public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable, public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, KeyIdentifiable,
VelocityInboundConnection { VelocityInboundConnection {
private static final int MAX_PLUGIN_CHANNELS = 1024;
private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE = private static final PlainTextComponentSerializer PASS_THRU_TRANSLATE =
PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build(); PlainTextComponentSerializer.builder().flattener(TranslatableMapper.FLATTENER).build();
static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED; static final PermissionProvider DEFAULT_PERMISSIONS = s -> PermissionFunction.ALWAYS_UNDEFINED;
@ -159,14 +152,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
private ClientConnectionPhase connectionPhase; private ClientConnectionPhase connectionPhase;
private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>(); private final CompletableFuture<Void> teardownFuture = new CompletableFuture<>();
private @MonotonicNonNull List<String> serversToTry = null; private @MonotonicNonNull List<String> serversToTry = null;
private @MonotonicNonNull Boolean previousResourceResponse; private final ResourcePackHandler resourcePackHandler;
private final Queue<ResourcePackInfo> outstandingResourcePacks = new ArrayDeque<>(); private final BundleDelimiterHandler bundleHandler = new BundleDelimiterHandler(this);
private @Nullable ResourcePackInfo pendingResourcePack;
private @Nullable ResourcePackInfo appliedResourcePack;
private @NotNull List<ResourcePackInfo> pendingResourcePacks = new ArrayList<>();
private @NotNull List<ResourcePackInfo> appliedResourcePacks = new ArrayList<>();
private final @NotNull Pointers pointers = 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.NAME, this::getUsername)
.withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername())) .withDynamic(Identity.DISPLAY_NAME, () -> Component.text(this.getUsername()))
.withDynamic(Identity.LOCALE, this::getEffectiveLocale) .withDynamic(Identity.LOCALE, this::getEffectiveLocale)
@ -174,7 +165,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
.withStatic(FacetPointers.TYPE, Type.PLAYER).build(); .withStatic(FacetPointers.TYPE, Type.PLAYER).build();
private @Nullable String clientBrand; private @Nullable String clientBrand;
private @Nullable Locale effectiveLocale; private @Nullable Locale effectiveLocale;
private @Nullable IdentifiedKey playerKey; private final @Nullable IdentifiedKey playerKey;
private @Nullable ClientSettingsPacket clientSettingsPacket; private @Nullable ClientSettingsPacket clientSettingsPacket;
private final ChatQueue chatQueue; private final ChatQueue chatQueue;
private final ChatBuilderFactory chatBuilderFactory; private final ChatBuilderFactory chatBuilderFactory;
@ -200,6 +191,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
this.playerKey = playerKey; this.playerKey = playerKey;
this.chatQueue = new ChatQueue(this); this.chatQueue = new ChatQueue(this);
this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion()); this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion());
this.resourcePackHandler = ResourcePackHandler.create(this, server);
} }
/** /**
@ -219,6 +211,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return chatQueue; return chatQueue;
} }
public BundleDelimiterHandler getBundleHandler() {
return this.bundleHandler;
}
@Override @Override
public @NonNull Identity identity() { public @NonNull Identity identity() {
return this.identity; return this.identity;
@ -238,7 +234,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
@Override @Override
public void setEffectiveLocale(Locale locale) { public void setEffectiveLocale(final @Nullable Locale locale) {
effectiveLocale = locale; effectiveLocale = locale;
} }
@ -293,6 +289,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings; return settings == null ? ClientSettingsWrapper.DEFAULT : this.settings;
} }
@Nullable
public ClientSettingsPacket getClientSettingsPacket() { public ClientSettingsPacket getClientSettingsPacket() {
return clientSettingsPacket; return clientSettingsPacket;
} }
@ -367,7 +364,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override @Override
public void sendMessage(@NonNull Identity identity, @NonNull Component message) { public void sendMessage(@NonNull Identity identity, @NonNull Component message) {
Component translated = translateMessage(message); final Component translated = translateMessage(message);
connection.write(getChatBuilderFactory().builder() connection.write(getChatBuilderFactory().builder()
.component(translated).forIdentity(identity).toClient()); .component(translated).forIdentity(identity).toClient());
@ -432,7 +429,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
@Override @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 translatedHeader = translateMessage(header);
Component translatedFooter = translateMessage(footer); Component translatedFooter = translateMessage(footer);
this.playerListHeader = translatedHeader; this.playerListHeader = translatedHeader;
@ -472,6 +470,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
} }
@SuppressWarnings("ConstantValue")
@Override @Override
public <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value) { public <T> void sendTitlePart(@NotNull TitlePart<T> part, @NotNull T value) {
if (part == null) { if (part == null) {
@ -744,11 +743,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
return; return;
} }
if (event.getResult() instanceof DisconnectPlayer) { if (event.getResult() instanceof final DisconnectPlayer res) {
DisconnectPlayer res = (DisconnectPlayer) event.getResult();
disconnect(res.getReasonComponent()); disconnect(res.getReasonComponent());
} else if (event.getResult() instanceof RedirectPlayer) { } else if (event.getResult() instanceof final RedirectPlayer res) {
RedirectPlayer res = (RedirectPlayer) event.getResult();
createConnectionRequest(res.getServer(), previousConnection).connect() createConnectionRequest(res.getServer(), previousConnection).connect()
.whenCompleteAsync((status, throwable) -> { .whenCompleteAsync((status, throwable) -> {
if (throwable != null) { if (throwable != null) {
@ -794,8 +791,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
break; break;
} }
}, connection.eventLoop()); }, connection.eventLoop());
} else if (event.getResult() instanceof Notify) { } else if (event.getResult() instanceof final Notify res) {
Notify res = (Notify) event.getResult();
if (event.kickedDuringServerConnect() && previousConnection != null) { if (event.kickedDuringServerConnect() && previousConnection != null) {
sendMessage(Identity.nil(), res.getMessageComponent()); sendMessage(Identity.nil(), res.getMessageComponent());
} else { } else {
@ -906,7 +902,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
DisconnectEvent.LoginStatus status; DisconnectEvent.LoginStatus status;
if (connectedPlayer.isPresent()) { if (connectedPlayer.isPresent()) {
if (!connectedPlayer.get().getCurrentServer().isPresent()) { if (connectedPlayer.get().getCurrentServer().isEmpty()) {
status = LoginStatus.PRE_SERVER_JOIN; status = LoginStatus.PRE_SERVER_JOIN;
} else { } else {
status = connectedPlayer.get() == this ? LoginStatus.SUCCESSFUL_LOGIN status = connectedPlayer.get() == this ? LoginStatus.SUCCESSFUL_LOGIN
@ -933,9 +929,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
@Override @Override
public String toString() { public String toString() {
boolean isPlayerAddressLoggingEnabled = server.getConfiguration() final boolean isPlayerAddressLoggingEnabled = server.getConfiguration()
.isPlayerAddressLoggingEnabled(); .isPlayerAddressLoggingEnabled();
String playerIp = final String playerIp =
isPlayerAddressLoggingEnabled ? getRemoteAddress().toString() : "<ip address withheld>"; isPlayerAddressLoggingEnabled ? getRemoteAddress().toString() : "<ip address withheld>";
return "[connected player] " + profile.getName() + " (" + playerIp + ")"; return "[connected player] " + profile.getName() + " (" + playerIp + ")";
} }
@ -956,11 +952,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
@Override @Override
@Nullable
public String getClientBrand() { public String getClientBrand() {
return clientBrand; return clientBrand;
} }
void setClientBrand(String clientBrand) { void setClientBrand(final @Nullable String clientBrand) {
this.clientBrand = 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 @Override
@Deprecated @Deprecated
public void sendResourcePack(String url) { public void sendResourcePack(String url) {
@ -997,7 +1003,15 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
public void sendResourcePackOffer(ResourcePackInfo packInfo) { public void sendResourcePackOffer(ResourcePackInfo packInfo) {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) { if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_8)) {
Preconditions.checkNotNull(packInfo, "packInfo"); 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,18 +1019,24 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
public void clearResourcePacks() { public void clearResourcePacks() {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
connection.write(new RemoveResourcePackPacket()); connection.write(new RemoveResourcePackPacket());
this.resourcePackHandler.clearAppliedResourcePacks();
} }
} }
@Override @Override
public void removeResourcePacks(@NotNull UUID id, @NotNull UUID @NotNull ... others) { public void removeResourcePacks(@NotNull UUID id, @NotNull UUID @NotNull ... others) {
if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { if (this.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) {
Preconditions.checkNotNull(id, "packUUID");
if (this.resourcePackHandler.remove(id)) {
connection.write(new RemoveResourcePackPacket(id)); connection.write(new RemoveResourcePackPacket(id));
}
for (final UUID other : others) { for (final UUID other : others) {
if (this.resourcePackHandler.remove(other)) {
connection.write(new RemoveResourcePackPacket(other)); connection.write(new RemoveResourcePackPacket(other));
} }
} }
} }
}
@Override @Override
public void removeResourcePacks(@NotNull ResourcePackRequest request) { public void removeResourcePacks(@NotNull ResourcePackRequest request) {
@ -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 @Override
@Deprecated @Deprecated
public @Nullable ResourcePackInfo getAppliedResourcePack() { public @Nullable ResourcePackInfo getAppliedResourcePack() {
return appliedResourcePack; return this.resourcePackHandler.getFirstAppliedPack();
} }
@Override @Override
@Deprecated @Deprecated
public @Nullable ResourcePackInfo getPendingResourcePack() { public @Nullable ResourcePackInfo getPendingResourcePack() {
return pendingResourcePack; return this.resourcePackHandler.getFirstPendingPack();
} }
@Override @Override
public Collection<ResourcePackInfo> getAppliedResourcePacks() { public Collection<ResourcePackInfo> getAppliedResourcePacks() {
return new ArrayList<>(appliedResourcePacks); return this.resourcePackHandler.getAppliedResourcePacks();
} }
@Override @Override
public Collection<ResourcePackInfo> getPendingResourcePacks() { public Collection<ResourcePackInfo> getPendingResourcePacks() {
return new ArrayList<>(pendingResourcePacks); return this.resourcePackHandler.getPendingResourcePacks();
}
/**
* 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;
} }
/** /**
@ -1228,7 +1100,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
*/ */
public void switchToConfigState() { public void switchToConfigState() {
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
connection.write(new StartUpdatePacket()); connection.write(StartUpdatePacket.INSTANCE);
connection.getChannel().pipeline() connection.getChannel().pipeline()
.get(MinecraftEncoder.class).setState(StateRegistry.CONFIG); .get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
// Make sure we don't send any play packets to the player after update start // 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 RegisteredServer toConnect;
private final @Nullable VelocityRegisteredServer previousServer; private final @Nullable VelocityRegisteredServer previousServer;
@ -1315,7 +1187,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
new ServerPreConnectEvent(ConnectedPlayer.this, toConnect, previousServer); new ServerPreConnectEvent(ConnectedPlayer.this, toConnect, previousServer);
return server.getEventManager().fire(event).thenComposeAsync(newEvent -> { return server.getEventManager().fire(event).thenComposeAsync(newEvent -> {
Optional<RegisteredServer> newDest = newEvent.getResult().getServer(); Optional<RegisteredServer> newDest = newEvent.getResult().getServer();
if (!newDest.isPresent()) { if (newDest.isEmpty()) {
return completedFuture( return completedFuture(
plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED, toConnect)); plainResult(ConnectionRequestBuilder.Status.CONNECTION_CANCELLED, toConnect));
} }

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -95,7 +95,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
.thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping))) .thenCompose(ping -> server.getEventManager().fire(new ProxyPingEvent(inbound, ping)))
.thenAcceptAsync( .thenAcceptAsync(
(event) -> { (event) -> {
StringBuilder json = new StringBuilder(); final StringBuilder json = new StringBuilder();
VelocityServer.getPingGsonInstance(connection.getProtocolVersion()) VelocityServer.getPingGsonInstance(connection.getProtocolVersion())
.toJson(event.getPing(), json); .toJson(event.getPing(), json);
connection.write(new StatusResponsePacket(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.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import io.netty.buffer.ByteBufUtil;
import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
import net.kyori.adventure.resource.ResourcePackRequest;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
/** /**
* Implements {@link ResourcePackInfo}. * Implements {@link ResourcePackInfo}.
@ -31,13 +35,13 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
private final UUID id; private final UUID id;
private final String url; private final String url;
private final @Nullable byte[] hash; private final byte @Nullable [] hash;
private final boolean shouldForce; private final boolean shouldForce;
private final @Nullable Component prompt; // 1.17+ only private final @Nullable Component prompt; // 1.17+ only
private final Origin origin; private final Origin origin;
private Origin originalOrigin; 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) { @Nullable Component prompt, Origin origin) {
this.id = id; this.id = id;
this.url = url; this.url = url;
@ -69,7 +73,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
} }
@Override @Override
public @Nullable byte[] getHash() { public byte @Nullable[] getHash() {
return hash == null ? null : hash.clone(); // Thanks spotbugs, very helpful. return hash == null ? null : hash.clone(); // Thanks spotbugs, very helpful.
} }
@ -105,6 +109,32 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
.setPrompt(prompt); .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. * Implements the builder for {@link ResourcePackInfo} instances.
*/ */
@ -113,7 +143,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
private UUID id; private UUID id;
private final String url; private final String url;
private boolean shouldForce; private boolean shouldForce;
private @Nullable byte[] hash; private byte @Nullable [] hash;
private @Nullable Component prompt; private @Nullable Component prompt;
private Origin origin = Origin.PLUGIN_ON_PROXY; private Origin origin = Origin.PLUGIN_ON_PROXY;
@ -135,7 +165,7 @@ public final class VelocityResourcePackInfo implements ResourcePackInfo {
} }
@Override @Override
public BuilderImpl setHash(@Nullable byte[] hash) { public BuilderImpl setHash(final byte @Nullable [] hash) {
if (hash != null) { if (hash != null) {
Preconditions.checkArgument(hash.length == 20, "Hash length is not 20"); Preconditions.checkArgument(hash.length == 20, "Hash length is not 20");
this.hash = hash.clone(); // Thanks spotbugs, very helpful. 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) { if (revision == Revision.GENERIC_V1) {
String pemKey = EncryptionUtils.pemEncodeRsaKey(publicKey); String pemKey = EncryptionUtils.pemEncodeRsaKey(publicKey);
long expires = expiryTemporal.toEpochMilli(); long expires = expiryTemporal.toEpochMilli();
byte[] toVerify = ("" + expires + pemKey).getBytes(StandardCharsets.US_ASCII); byte[] toVerify = (expires + pemKey).getBytes(StandardCharsets.US_ASCII);
return EncryptionUtils.verifySignature( return EncryptionUtils.verifySignature(
EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature, EncryptionUtils.SHA1_WITH_RSA, EncryptionUtils.getYggdrasilSessionKey(), signature,
toVerify); toVerify);
@ -166,12 +166,10 @@ public class IdentifiedKeyImpl implements IdentifiedKey {
if (this == o) { if (this == o) {
return true; return true;
} }
if (!(o instanceof IdentifiedKey)) { if (!(o instanceof final IdentifiedKey that)) {
return false; return false;
} }
IdentifiedKey that = (IdentifiedKey) o;
return Objects.equal(this.getSignedPublicKey(), that.getSignedPublicKey()) return Objects.equal(this.getSignedPublicKey(), that.getSignedPublicKey())
&& Objects.equal(this.getExpiryTemporal(), that.getExpiryTemporal()) && Objects.equal(this.getExpiryTemporal(), that.getExpiryTemporal())
&& Arrays.equals(this.getSignature(), that.getSignature()) && 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.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket; import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.BossBarPacket; 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.ClientSettingsPacket;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket; import com.velocitypowered.proxy.protocol.packet.EncryptionRequestPacket;
@ -144,7 +145,7 @@ public enum StateRegistry {
PluginMessagePacket.class, PluginMessagePacket::new, PluginMessagePacket.class, PluginMessagePacket::new,
map(0x01, MINECRAFT_1_20_2, false)); map(0x01, MINECRAFT_1_20_2, false));
serverbound.register( serverbound.register(
FinishedUpdatePacket.class, FinishedUpdatePacket::new, FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x02, MINECRAFT_1_20_2, false)); map(0x02, MINECRAFT_1_20_2, false));
serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new, serverbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x03, MINECRAFT_1_20_2, false)); map(0x03, MINECRAFT_1_20_2, false));
@ -163,7 +164,7 @@ public enum StateRegistry {
DisconnectPacket.class, () -> new DisconnectPacket(false), DisconnectPacket.class, () -> new DisconnectPacket(false),
map(0x01, MINECRAFT_1_20_2, false)); map(0x01, MINECRAFT_1_20_2, false));
clientbound.register( clientbound.register(
FinishedUpdatePacket.class, FinishedUpdatePacket::new, FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x02, MINECRAFT_1_20_2, false)); map(0x02, MINECRAFT_1_20_2, false));
clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new, clientbound.register(KeepAlivePacket.class, KeepAlivePacket::new,
map(0x03, MINECRAFT_1_20_2, false)); map(0x03, MINECRAFT_1_20_2, false));
@ -289,7 +290,7 @@ public enum StateRegistry {
map(0x27, MINECRAFT_1_20_2, false), map(0x27, MINECRAFT_1_20_2, false),
map(0x28, MINECRAFT_1_20_3, false)); map(0x28, MINECRAFT_1_20_3, false));
serverbound.register( serverbound.register(
FinishedUpdatePacket.class, FinishedUpdatePacket::new, FinishedUpdatePacket.class, () -> FinishedUpdatePacket.INSTANCE,
map(0x0B, MINECRAFT_1_20_2, false)); map(0x0B, MINECRAFT_1_20_2, false));
clientbound.register( clientbound.register(
@ -569,9 +570,13 @@ public enum StateRegistry {
map(0x49, MINECRAFT_1_20_3, false)); map(0x49, MINECRAFT_1_20_3, false));
clientbound.register( clientbound.register(
StartUpdatePacket.class, StartUpdatePacket.class,
StartUpdatePacket::new, () -> StartUpdatePacket.INSTANCE,
map(0x65, MINECRAFT_1_20_2, false), map(0x65, MINECRAFT_1_20_2, false),
map(0x67, MINECRAFT_1_20_3, false)); map(0x67, MINECRAFT_1_20_3, false));
clientbound.register(
BundleDelimiterPacket.class,
() -> BundleDelimiterPacket.INSTANCE,
map(0x00, MINECRAFT_1_19_4, false));
} }
}, },
LOGIN { LOGIN {

Datei anzeigen

@ -39,7 +39,7 @@ public class LegacyPingEncoder extends MessageToByteEncoder<LegacyDisconnect> {
protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out) protected void encode(ChannelHandlerContext ctx, LegacyDisconnect msg, ByteBuf out)
throws Exception { throws Exception {
out.writeByte(0xff); out.writeByte(0xff);
writeLegacyString(out, msg.getReason()); writeLegacyString(out, msg.reason());
} }
private static void writeLegacyString(ByteBuf out, String string) { 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.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; 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, private static final ServerPing.Players FAKE_PLAYERS = new ServerPing.Players(0, 0,
ImmutableList.of()); ImmutableList.of());
private static final String LEGACY_COLOR_CODE = Character private static final String LEGACY_COLOR_CODE = Character
.toString(LegacyComponentSerializer.SECTION_CHAR); .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. * 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, public static LegacyDisconnect fromServerPing(ServerPing response,
LegacyMinecraftPingVersion version) { LegacyMinecraftPingVersion version) {
Players players = response.getPlayers().orElse(FAKE_PLAYERS); final Players players = response.getPlayers().orElse(FAKE_PLAYERS);
switch (version) { return switch (version) {
case MINECRAFT_1_3: case MINECRAFT_1_3 ->
// Minecraft 1.3 and below use the section symbol as a delimiter. Accordingly, we must // 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) // remove all section symbols, along with fetching just the first line of an (unformatted)
// MOTD. // MOTD.
return new LegacyDisconnect(String.join(LEGACY_COLOR_CODE, new LegacyDisconnect(String.join(LEGACY_COLOR_CODE,
cleanSectionSymbol(getFirstLine(PlainTextComponentSerializer.plainText().serialize( cleanSectionSymbol(getFirstLine(PlainTextComponentSerializer.plainText().serialize(
response.getDescriptionComponent()))), response.getDescriptionComponent()))),
Integer.toString(players.getOnline()), Integer.toString(players.getOnline()),
Integer.toString(players.getMax()))); Integer.toString(players.getMax())));
case MINECRAFT_1_4: case MINECRAFT_1_4, MINECRAFT_1_6 ->
case MINECRAFT_1_6:
// Minecraft 1.4-1.6 provide support for more fields, and additionally support color codes. // 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", LEGACY_COLOR_CODE + "1",
Integer.toString(response.getVersion().getProtocol()), Integer.toString(response.getVersion().getProtocol()),
response.getVersion().getName(), response.getVersion().getName(),
@ -71,9 +65,8 @@ public class LegacyDisconnect {
Integer.toString(players.getOnline()), Integer.toString(players.getOnline()),
Integer.toString(players.getMax()) Integer.toString(players.getMax())
)); ));
default: default -> throw new IllegalArgumentException("Unknown version " + version);
throw new IllegalArgumentException("Unknown version " + version); };
}
} }
private static String cleanSectionSymbol(String string) { private static String cleanSectionSymbol(String string) {
@ -81,7 +74,7 @@ public class LegacyDisconnect {
} }
private static String getFirstLine(String legacyMotd) { 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); return newline == -1 ? legacyMotd : legacyMotd.substring(0, newline);
} }
@ -93,11 +86,7 @@ public class LegacyDisconnect {
*/ */
public static LegacyDisconnect from(TextComponent component) { public static LegacyDisconnect from(TextComponent component) {
// We intentionally use the legacy serializers, because the old clients can't understand JSON. // 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); 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;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.UUID; import java.util.UUID;
public class RemoveResourcePackPacket implements MinecraftPacket { public class RemoveResourcePackPacket implements MinecraftPacket {
@ -37,6 +36,10 @@ public class RemoveResourcePackPacket implements MinecraftPacket {
this.id = id; this.id = id;
} }
public UUID getId() {
return id;
}
@Override @Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (buf.readBoolean()) { 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 com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; 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.UUID;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
public class ResourcePackRequestPacket implements MinecraftPacket { public class ResourcePackRequestPacket implements MinecraftPacket {
@ -80,7 +79,7 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
return prompt; return prompt;
} }
public void setPrompt(ComponentHolder prompt) { public void setPrompt(@Nullable ComponentHolder prompt) {
this.prompt = prompt; this.prompt = prompt;
} }
@ -126,7 +125,7 @@ public class ResourcePackRequestPacket implements MinecraftPacket {
} }
public VelocityResourcePackInfo toServerPromptedPack() { public VelocityResourcePackInfo toServerPromptedPack() {
ResourcePackInfo.Builder builder = final ResourcePackInfo.Builder builder =
new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url)) new VelocityResourcePackInfo.BuilderImpl(Preconditions.checkNotNull(url))
.setId(id).setPrompt(prompt == null ? null : prompt.getComponent()) .setId(id).setPrompt(prompt == null ? null : prompt.getComponent())
.setShouldForce(isRequired).setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER); .setShouldForce(isRequired).setOrigin(ResourcePackInfo.Origin.DOWNSTREAM_SERVER);

Datei anzeigen

@ -54,6 +54,10 @@ public class ResourcePackResponsePacket implements MinecraftPacket {
return hash; return hash;
} }
public UUID getId() {
return id;
}
@Override @Override
public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { public void decode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
if (protocolVersion.noLessThan(ProtocolVersion.MINECRAFT_1_20_3)) { 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; import io.netty.buffer.ByteBuf;
public class FinishedUpdatePacket implements MinecraftPacket { public class FinishedUpdatePacket implements MinecraftPacket {
public static final FinishedUpdatePacket INSTANCE = new FinishedUpdatePacket();
private FinishedUpdatePacket() {
}
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, 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; import io.netty.buffer.ByteBuf;
public class StartUpdatePacket implements MinecraftPacket { public class StartUpdatePacket implements MinecraftPacket {
public static final StartUpdatePacket INSTANCE = new StartUpdatePacket();
private StartUpdatePacket() {
}
@Override @Override
public void decode(ByteBuf buf, ProtocolUtils.Direction direction, 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 final Consumer<Component> componentConsumer
) { ) {
for (final Translator source : GlobalTranslator.translator().sources()) { for (final Translator source : GlobalTranslator.translator().sources()) {
if (source instanceof TranslationRegistry if (source instanceof TranslationRegistry registry
&& ((TranslationRegistry) source).contains(translatableComponent.key())) { && registry.contains(translatableComponent.key())) {
componentConsumer.accept(GlobalTranslator.render(translatableComponent, componentConsumer.accept(GlobalTranslator.render(translatableComponent,
ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault()))); ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));
return; return;
@ -56,8 +56,7 @@ public enum TranslatableMapper implements BiConsumer<TranslatableComponent, Cons
return; return;
} }
for (final Translator source : GlobalTranslator.translator().sources()) { for (final Translator source : GlobalTranslator.translator().sources()) {
if (source instanceof TranslationRegistry if (source instanceof TranslationRegistry registry && registry.contains(fallback)) {
&& ((TranslationRegistry) source).contains(fallback)) {
componentConsumer.accept( componentConsumer.accept(
GlobalTranslator.render(Component.translatable(fallback), GlobalTranslator.render(Component.translatable(fallback),
ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault()))); ClosestLocaleMatcher.INSTANCE.lookupClosest(Locale.getDefault())));