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:
Ursprung
825c3c68d1
Commit
cbd07b1434
@ -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.
|
||||||
*
|
*
|
||||||
|
@ -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.
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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));
|
||||||
|
@ -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.
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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) {
|
||||||
|
}
|
@ -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())
|
||||||
|
@ -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 {
|
||||||
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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()) {
|
||||||
|
@ -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);
|
||||||
|
@ -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)) {
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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())));
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren