diff --git a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/server/network/ServerLoginPacketListenerImplMixin_Forge.java b/forge/src/mixins/java/org/spongepowered/forge/mixin/core/server/network/ServerLoginPacketListenerImplMixin_Forge.java deleted file mode 100644 index 38fdc9aaff5..00000000000 --- a/forge/src/mixins/java/org/spongepowered/forge/mixin/core/server/network/ServerLoginPacketListenerImplMixin_Forge.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.forge.mixin.core.server.network; - -import net.minecraft.network.protocol.login.ServerLoginPacketListener; -import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.common.bridge.network.ServerLoginPacketListenerImplBridge; - -@Mixin(ServerLoginPacketListenerImpl.class) -public abstract class ServerLoginPacketListenerImplMixin_Forge implements ServerLoginPacketListener, ServerLoginPacketListenerImplBridge { - - // @formatter:off - @Shadow private ServerLoginPacketListenerImpl.State state; - // @formatter:on - - @Inject(method = "tick", at = @At("HEAD")) - private void impl$onTick(final CallbackInfo ci) { - // In SpongeVanilla we do channel registration during this state, not sure if we need to do anything in SpongeForge - if (this.state == ServerLoginPacketListenerImpl.State.NEGOTIATING) { - this.state = ServerLoginPacketListenerImpl.State.VERIFYING; - } - } -} diff --git a/forge/src/mixins/resources/mixins.spongeforge.core.json b/forge/src/mixins/resources/mixins.spongeforge.core.json index 757eb186614..ae82ddbcc44 100644 --- a/forge/src/mixins/resources/mixins.spongeforge.core.json +++ b/forge/src/mixins/resources/mixins.spongeforge.core.json @@ -25,7 +25,6 @@ "server.commands.SpreadPlayersCommandMixin_Forge", "server.level.ServerPlayerMixin_Forge", "server.network.ServerGamePacketListenerImplMixin_Forge", - "server.network.ServerLoginPacketListenerImplMixin_Forge", "world.entity.LivingEntityMixin_Forge", "world.entity.LivingEntityMixin_Forge_Attack_Impl", "world.entity.item.ItemEntityMixin_Forge", diff --git a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/server/network/ServerLoginPacketListenerImplMixin_Neo.java b/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/server/network/ServerLoginPacketListenerImplMixin_Neo.java deleted file mode 100644 index f59457771fc..00000000000 --- a/neoforge/src/mixins/java/org/spongepowered/neoforge/mixin/core/server/network/ServerLoginPacketListenerImplMixin_Neo.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.neoforge.mixin.core.server.network; - -import net.minecraft.network.protocol.login.ServerLoginPacketListener; -import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.common.bridge.network.ServerLoginPacketListenerImplBridge; - -@Mixin(ServerLoginPacketListenerImpl.class) -public abstract class ServerLoginPacketListenerImplMixin_Neo implements ServerLoginPacketListener, ServerLoginPacketListenerImplBridge { - - // @formatter:off - @Shadow private ServerLoginPacketListenerImpl.State state; - // @formatter:on - - @Inject(method = "tick", at = @At("HEAD")) - private void impl$onTick(final CallbackInfo ci) { - // In SpongeVanilla we do channel registration during this state, not sure if we need to do anything in SpongeNeo - if (this.state == ServerLoginPacketListenerImpl.State.NEGOTIATING) { - this.state = ServerLoginPacketListenerImpl.State.VERIFYING; - } - } -} diff --git a/neoforge/src/mixins/resources/mixins.spongeneo.core.json b/neoforge/src/mixins/resources/mixins.spongeneo.core.json index 00798c2c4a6..0bef1de107f 100644 --- a/neoforge/src/mixins/resources/mixins.spongeneo.core.json +++ b/neoforge/src/mixins/resources/mixins.spongeneo.core.json @@ -23,7 +23,6 @@ "server.level.ServerEntityMixin_Neo", "server.level.ServerPlayerMixin_Neo", "server.network.ServerGamePacketListenerImplMixin_Neo", - "server.network.ServerLoginPacketListenerImplMixin_Neo", "world.entity.LivingEntityMixin_Neo", "world.entity.LivingEntityMixin_Neo_Attack_Impl", "world.entity.item.ItemEntityMixin_Neo", diff --git a/src/main/java/org/spongepowered/common/bridge/network/ServerLoginPacketListenerImplBridge.java b/src/main/java/org/spongepowered/common/bridge/network/ServerLoginPacketListenerImplBridge.java index 0b82d04bb67..f34b8197135 100644 --- a/src/main/java/org/spongepowered/common/bridge/network/ServerLoginPacketListenerImplBridge.java +++ b/src/main/java/org/spongepowered/common/bridge/network/ServerLoginPacketListenerImplBridge.java @@ -26,11 +26,10 @@ import net.minecraft.network.Connection; -import java.util.concurrent.ExecutorService; public interface ServerLoginPacketListenerImplBridge { - ExecutorService bridge$getExecutor(); - Connection bridge$getConnection(); + + boolean bridge$isIntentDone(); } diff --git a/src/main/java/org/spongepowered/common/network/SpongeEngineConnection.java b/src/main/java/org/spongepowered/common/network/SpongeEngineConnection.java index 9271f8d1f7e..a69c4b2e324 100644 --- a/src/main/java/org/spongepowered/common/network/SpongeEngineConnection.java +++ b/src/main/java/org/spongepowered/common/network/SpongeEngineConnection.java @@ -37,6 +37,7 @@ import org.spongepowered.common.SpongeCommon; import org.spongepowered.common.adventure.SpongeAdventure; import org.spongepowered.common.bridge.network.ConnectionBridge; +import org.spongepowered.common.bridge.network.ServerLoginPacketListenerImplBridge; import org.spongepowered.common.event.tracking.PhaseTracker; import org.spongepowered.common.profile.SpongeGameProfile; @@ -66,7 +67,11 @@ public boolean active() { @Override public Optional state() { if (this.active()) { - return Optional.of((EngineConnectionState) this.connection.getPacketListener()); + final EngineConnectionState state = (EngineConnectionState) this.connection.getPacketListener(); + if (!(state instanceof ServerLoginPacketListenerImplBridge loginBridge) || loginBridge.bridge$isIntentDone()) { + return Optional.of(state); + } + return Optional.of(DummyIntent.of(state.transferred())); } return Optional.empty(); } @@ -134,4 +139,21 @@ boolean shouldFireDisconnectionImmediately() { return this == EventFireState.POST; } } + + private record DummyIntent(boolean transferred) implements EngineConnectionState.Intent { + + private static final DummyIntent TRANSFERRED_FALSE = new DummyIntent(false); + private static final DummyIntent TRANSFERRED_TRUE = new DummyIntent(true); + + @Override + public boolean transferred() { + return this.transferred; + } + + static DummyIntent of(final boolean transferred) { + return transferred + ? DummyIntent.TRANSFERRED_TRUE + : DummyIntent.TRANSFERRED_FALSE; + } + } } diff --git a/src/main/java/org/spongepowered/common/network/channel/ConnectionUtil.java b/src/main/java/org/spongepowered/common/network/channel/ConnectionUtil.java index b084789a010..b631800fe5f 100644 --- a/src/main/java/org/spongepowered/common/network/channel/ConnectionUtil.java +++ b/src/main/java/org/spongepowered/common/network/channel/ConnectionUtil.java @@ -35,6 +35,11 @@ public final class ConnectionUtil { + public static boolean isIntentPhase(final EngineConnection connection) { + final EngineConnectionState state = (EngineConnectionState) ((SpongeEngineConnection) connection).connection().getPacketListener(); + return state instanceof EngineConnectionState.Intent; + } + public static boolean isLoginPhase(final EngineConnection connection) { final EngineConnectionState state = (EngineConnectionState) ((SpongeEngineConnection) connection).connection().getPacketListener(); return state instanceof EngineConnectionState.Login; @@ -50,6 +55,12 @@ public static TransactionStore getTransactionStore(final EngineConnection connec return ((ConnectionBridge) networkManager).bridge$getTransactionStore(); } + public static void checkHandshakeOrIntentPhase(final EngineConnection connection) { + if (!ConnectionUtil.isIntentPhase(connection) && !ConnectionUtil.isLoginPhase(connection)) { + throw new IllegalStateException("This dispatcher may only be used for connections in the handshake phase."); + } + } + public static void checkHandshakePhase(final EngineConnection connection) { if (!ConnectionUtil.isLoginPhase(connection)) { throw new IllegalStateException("This dispatcher may only be used for connections in the handshake phase."); diff --git a/src/main/java/org/spongepowered/common/network/channel/raw/SpongeRawLoginDataChannel.java b/src/main/java/org/spongepowered/common/network/channel/raw/SpongeRawLoginDataChannel.java index 09642ebad79..3a83d426443 100644 --- a/src/main/java/org/spongepowered/common/network/channel/raw/SpongeRawLoginDataChannel.java +++ b/src/main/java/org/spongepowered/common/network/channel/raw/SpongeRawLoginDataChannel.java @@ -143,7 +143,7 @@ void handleTransactionResponse(final EngineConnection connection, final Object s @Override public CompletableFuture sendTo(final EngineConnection connection, final Consumer payload) { - ConnectionUtil.checkHandshakePhase(connection); + ConnectionUtil.checkHandshakeOrIntentPhase(connection); final EngineConnectionState state = (EngineConnectionState) ((SpongeEngineConnection) connection).connection().getPacketListener(); diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/network/MemoryServerHandshakePacketListenerImplMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/network/MemoryServerHandshakePacketListenerImplMixin.java index 681cca073a2..d9a2a7a13d5 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/network/MemoryServerHandshakePacketListenerImplMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/network/MemoryServerHandshakePacketListenerImplMixin.java @@ -26,6 +26,7 @@ import net.kyori.adventure.text.Component; import net.minecraft.network.Connection; +import net.minecraft.network.ProtocolInfo; import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket; import net.minecraft.network.protocol.login.LoginProtocols; import net.minecraft.server.network.MemoryServerHandshakePacketListenerImpl; @@ -37,6 +38,7 @@ import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.common.adventure.SpongeAdventure; import org.spongepowered.common.bridge.network.ConnectionBridge; @@ -58,6 +60,7 @@ public abstract class MemoryServerHandshakePacketListenerImplMixin implements Se target = "Lnet/minecraft/network/Connection;setupInboundProtocol(Lnet/minecraft/network/ProtocolInfo;Lnet/minecraft/network/PacketListener;)V"), cancellable = true) private void impl$onLogin(final CallbackInfo ci) { + this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND); final SpongeEngineConnection connection = ((ConnectionBridge) this.connection).bridge$getEngineConnection(); final Component message = Component.text("You are not allowed to log in to this server."); final ServerSideConnectionEvent.Intent event = SpongeEventFactory.createServerSideConnectionEventIntent( @@ -65,9 +68,13 @@ public abstract class MemoryServerHandshakePacketListenerImplMixin implements Se if (connection.postGuardedEvent(event)) { ci.cancel(); final net.minecraft.network.chat.Component kickReason = SpongeAdventure.asVanilla(event.message()); - this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND); this.connection.send(new ClientboundLoginDisconnectPacket(kickReason)); this.connection.disconnect(kickReason); } } + + @Redirect(method = "handleIntention", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/Connection;setupOutboundProtocol(Lnet/minecraft/network/ProtocolInfo;)V")) + private void impl$onSetupOutboundProtocol(final Connection instance, final ProtocolInfo $$0) { + //Moved to impl$onLogin + } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/network/ServerHandshakePacketListenerImplMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/network/ServerHandshakePacketListenerImplMixin.java index 04cee3c99bb..a4b834c2fd0 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/network/ServerHandshakePacketListenerImplMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/network/ServerHandshakePacketListenerImplMixin.java @@ -26,6 +26,7 @@ import net.kyori.adventure.text.Component; import net.minecraft.network.Connection; +import net.minecraft.network.ProtocolInfo; import net.minecraft.network.protocol.handshake.ClientIntentionPacket; import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket; import net.minecraft.network.protocol.login.LoginProtocols; @@ -78,13 +79,13 @@ public abstract class ServerHandshakePacketListenerImplMixin implements ServerHa target = "Lnet/minecraft/server/MinecraftServer;getStatus()Lnet/minecraft/network/protocol/status/ServerStatus;")), cancellable = true) private void impl$onLogin(final ClientIntentionPacket $$0, final CallbackInfo ci) { + this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND); final SpongeEngineConnection connection = ((ConnectionBridge) this.connection).bridge$getEngineConnection(); final Component message = Component.text("You are not allowed to log in to this server."); final ServerSideConnectionEvent.Intent event = SpongeEventFactory.createServerSideConnectionEventIntent( PhaseTracker.getCauseStackManager().currentCause(), message, message, (ServerSideConnection) connection, false); if (connection.postGuardedEvent(event)) { final net.minecraft.network.chat.Component kickReason = SpongeAdventure.asVanilla(event.message()); - this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND); this.connection.send(new ClientboundLoginDisconnectPacket(kickReason)); this.connection.disconnect(kickReason); ci.cancel(); @@ -93,6 +94,7 @@ public abstract class ServerHandshakePacketListenerImplMixin implements ServerHa @Redirect(method = "handleIntention", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;acceptsTransfers()Z")) private boolean impl$onTransfer(final MinecraftServer instance) { + this.connection.setupOutboundProtocol(LoginProtocols.CLIENTBOUND); this.impl$transferred = true; final SpongeEngineConnection connection = ((ConnectionBridge) this.connection).bridge$getEngineConnection(); final Component message = Component.translatable("multiplayer.disconnect.transfers_disabled"); @@ -109,4 +111,17 @@ public abstract class ServerHandshakePacketListenerImplMixin implements ServerHa private net.minecraft.network.chat.Component impl$setTransferDisconnectMessage(final net.minecraft.network.chat.Component component) { return ((ConnectionBridge) this.connection).bridge$getKickReason(); } + + @Redirect(method = "handleIntention", at = @At( + value = "INVOKE", + target = "Lnet/minecraft/network/Connection;setupOutboundProtocol(Lnet/minecraft/network/ProtocolInfo;)V"), + slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/server/MinecraftServer;acceptsTransfers()Z"))) + private void impl$onSetupOutboundProtocol(final Connection instance, final ProtocolInfo $$0) { + //Moved to impl$onTransfer + } + + @Redirect(method = "beginLogin", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/Connection;setupOutboundProtocol(Lnet/minecraft/network/ProtocolInfo;)V")) + private void impl$onSetupOutboundProtocol2(final Connection instance, final ProtocolInfo $$0) { + //Moved to impl$onLogin + } } diff --git a/src/mixins/java/org/spongepowered/common/mixin/core/server/network/ServerLoginPacketListenerImplMixin.java b/src/mixins/java/org/spongepowered/common/mixin/core/server/network/ServerLoginPacketListenerImplMixin.java index 49428799417..e77870bd13a 100644 --- a/src/mixins/java/org/spongepowered/common/mixin/core/server/network/ServerLoginPacketListenerImplMixin.java +++ b/src/mixins/java/org/spongepowered/common/mixin/core/server/network/ServerLoginPacketListenerImplMixin.java @@ -64,8 +64,10 @@ import org.spongepowered.common.bridge.network.ServerLoginPacketListenerImplBridge; import org.spongepowered.common.bridge.server.players.PlayerListBridge; import org.spongepowered.common.network.SpongeEngineConnection; +import org.spongepowered.common.network.channel.ConnectionUtil; import org.spongepowered.common.network.channel.SpongeChannelManager; import org.spongepowered.common.network.channel.SpongeChannelPayload; +import org.spongepowered.common.network.channel.TransactionStore; import org.spongepowered.common.profile.SpongeGameProfile; import java.math.BigInteger; @@ -104,6 +106,25 @@ public abstract class ServerLoginPacketListenerImplMixin implements ServerLoginP .setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)) .build()); + private static final int NEGOTIATION_NOT_STARTED = 0; + private static final int NEGOTIATION_INTENT = 1; + private static final int NEGOTIATION_HANDSHAKE = 2; + + private static final int INTENT_SYNC_PLUGIN_DATA = 0; + + // Handshake state: + // 1. Sync registered plugin channels + // 2. Post handshake event and plugins can start sending login payloads + // 3. Wait until the client responded for each of the plugin' requests + private static final int HANDSHAKE_NOT_STARTED = 0; + private static final int HANDSHAKE_CLIENT_TYPE = 1; + private static final int HANDSHAKE_SYNC_CHANNEL_REGISTRATIONS = 2; + private static final int HANDSHAKE_CHANNEL_REGISTRATION = 3; + private static final int HANDSHAKE_SYNC_PLUGIN_DATA = 4; + + private volatile int impl$negotiationPhase = ServerLoginPacketListenerImplMixin.NEGOTIATION_NOT_STARTED; + private volatile int impl$negotiationState; + @Override public Connection bridge$getConnection() { return this.connection; @@ -129,39 +150,49 @@ public abstract class ServerLoginPacketListenerImplMixin implements ServerLoginP private void impl$handleAuthEventCancellation(final CallbackInfo ci) { ci.cancel(); - ((PlayerListBridge) this.server.getPlayerList()).bridge$canPlayerLogin(this.connection.getRemoteAddress(), this.authenticatedProfile) - .handle((componentOpt, throwable) -> { - if (throwable != null) { - // An error occurred during login checks so we ask to abort. - ((ConnectionBridge) this.connection).bridge$setKickReason(Component.literal("An error occurred checking ban/whitelist status.")); - SpongeCommon.logger().error("An error occurred when checking the ban/whitelist status of {}.", this.authenticatedProfile.getId().toString()); - SpongeCommon.logger().error(throwable); - } else if (componentOpt != null) { - // We handle this later - ((ConnectionBridge) this.connection).bridge$setKickReason(componentOpt); - } - return null; - }).handleAsync((ignored, throwable) -> { - if (throwable != null) { - // We're just going to disconnect here, because something went horribly wrong. - if (throwable instanceof CompletionException) { - throw (CompletionException) throwable; - } else { - throw new CompletionException(throwable); - } - } - this.impl$fireAuthEvent(); - return null; - }, this.bridge$getExecutor()).exceptionally(throwable -> { - SpongeCommon.logger().error("Forcibly disconnecting user {}({}) due to an error during login.", this.authenticatedProfile.getName(), this.authenticatedProfile.getId(), throwable); - this.shadow$disconnect(Component.literal("Internal Server Error: unable to complete login.")); - return null; - }); + final ServerSideConnection connection = (ServerSideConnection) ((ConnectionBridge) this.connection).bridge$getEngineConnection(); + final TransactionStore store = ConnectionUtil.getTransactionStore(connection); + if (store.isEmpty()) { + this.impl$processVerification(); + } else { + this.state = ServerLoginPacketListenerImpl.State.NEGOTIATING; + this.impl$negotiationPhase = ServerLoginPacketListenerImplMixin.NEGOTIATION_INTENT; + this.impl$negotiationState = ServerLoginPacketListenerImplMixin.INTENT_SYNC_PLUGIN_DATA; + } } - @Override - public ExecutorService bridge$getExecutor() { - return ServerLoginPacketListenerImplMixin.impl$EXECUTOR; + private void impl$processVerification() { + this.impl$negotiationPhase = ServerLoginPacketListenerImplMixin.NEGOTIATION_HANDSHAKE; + this.impl$negotiationState = ServerLoginPacketListenerImplMixin.HANDSHAKE_NOT_STARTED; + + ((PlayerListBridge) this.server.getPlayerList()).bridge$canPlayerLogin(this.connection.getRemoteAddress(), this.authenticatedProfile) + .handle((componentOpt, throwable) -> { + if (throwable != null) { + // An error occurred during login checks so we ask to abort. + ((ConnectionBridge) this.connection).bridge$setKickReason(Component.literal("An error occurred checking ban/whitelist status.")); + SpongeCommon.logger().error("An error occurred when checking the ban/whitelist status of {}.", this.authenticatedProfile.getId().toString()); + SpongeCommon.logger().error(throwable); + } else if (componentOpt != null) { + // We handle this later + ((ConnectionBridge) this.connection).bridge$setKickReason(componentOpt); + } + return null; + }).handleAsync((ignored, throwable) -> { + if (throwable != null) { + // We're just going to disconnect here, because something went horribly wrong. + if (throwable instanceof CompletionException) { + throw (CompletionException) throwable; + } else { + throw new CompletionException(throwable); + } + } + this.impl$fireAuthEvent(); + return null; + }, ServerLoginPacketListenerImplMixin.impl$EXECUTOR).exceptionally(throwable -> { + SpongeCommon.logger().error("Forcibly disconnecting user {}({}) due to an error during login.", this.authenticatedProfile.getName(), this.authenticatedProfile.getId(), throwable); + this.shadow$disconnect(Component.literal("Internal Server Error: unable to complete login.")); + return null; + }); } private void impl$fireAuthEvent() { @@ -212,7 +243,7 @@ public void handleKey(final ServerboundKeyPacket packet) { } //Sponge start - ((ServerLoginPacketListenerImplBridge)this).bridge$getExecutor().submit(() -> { + ServerLoginPacketListenerImplMixin.impl$EXECUTOR.submit(() -> { //Sponge end final String username = Objects.requireNonNull(this.requestedUsername, "Player name not initialized"); @@ -263,4 +294,57 @@ public void handleKey(final ServerboundKeyPacket packet) { channelRegistry.handleLoginResponsePayload(connection, (EngineConnectionState) this, payload.id(), packet.transactionId(), payload.consumer()); }); } + + @Inject(method = "tick", at = @At("HEAD")) + private void impl$onTick(final CallbackInfo ci) { + if (this.state == ServerLoginPacketListenerImpl.State.NEGOTIATING) { + if (this.impl$negotiationPhase == ServerLoginPacketListenerImplMixin.NEGOTIATION_INTENT) { + this.impl$handleIntentNegotiation(); + } else if (this.impl$negotiationPhase == ServerLoginPacketListenerImplMixin.NEGOTIATION_HANDSHAKE) { + this.impl$handleHandshakeNegotiation(); + } + } + } + + private void impl$handleIntentNegotiation() { + if (this.impl$negotiationState == ServerLoginPacketListenerImplMixin.INTENT_SYNC_PLUGIN_DATA) { + final ServerSideConnection connection = (ServerSideConnection) ((ConnectionBridge) this.connection).bridge$getEngineConnection(); + final TransactionStore store = ConnectionUtil.getTransactionStore(connection); + if (store.isEmpty()) { + this.impl$processVerification(); + } + } + } + + private void impl$handleHandshakeNegotiation() { + final ServerSideConnection connection = (ServerSideConnection) ((ConnectionBridge) this.connection).bridge$getEngineConnection(); + if (this.impl$negotiationState == ServerLoginPacketListenerImplMixin.HANDSHAKE_NOT_STARTED) { + this.impl$negotiationState = ServerLoginPacketListenerImplMixin.HANDSHAKE_CLIENT_TYPE; + + ((SpongeChannelManager) Sponge.channelManager()).requestClientType(connection).thenAccept(result -> { + this.impl$negotiationState = ServerLoginPacketListenerImplMixin.HANDSHAKE_SYNC_CHANNEL_REGISTRATIONS; + }); + + } else if (this.impl$negotiationState == ServerLoginPacketListenerImplMixin.HANDSHAKE_SYNC_CHANNEL_REGISTRATIONS) { + this.impl$negotiationState = ServerLoginPacketListenerImplMixin.HANDSHAKE_CHANNEL_REGISTRATION; + + ((SpongeChannelManager) Sponge.channelManager()).sendLoginChannelRegistry(connection).thenAccept(result -> { + final Cause cause = Cause.of(EventContext.empty(), this); + final ServerSideConnectionEvent.Handshake event = + SpongeEventFactory.createServerSideConnectionEventHandshake(cause, connection, SpongeGameProfile.of(this.authenticatedProfile)); + SpongeCommon.post(event); + this.impl$negotiationState = ServerLoginPacketListenerImplMixin.HANDSHAKE_SYNC_PLUGIN_DATA; + }); + } else if (this.impl$negotiationState == ServerLoginPacketListenerImplMixin.HANDSHAKE_SYNC_PLUGIN_DATA) { + final TransactionStore store = ConnectionUtil.getTransactionStore(connection); + if (store.isEmpty()) { + this.state = ServerLoginPacketListenerImpl.State.VERIFYING; + } + } + } + + @Override + public boolean bridge$isIntentDone() { + return this.impl$negotiationState > ServerLoginPacketListenerImplMixin.NEGOTIATION_INTENT; + } } diff --git a/testplugins/src/main/java/org/spongepowered/test/channel/ChannelTest.java b/testplugins/src/main/java/org/spongepowered/test/channel/ChannelTest.java index 4919f95586e..f2f6fd07c62 100644 --- a/testplugins/src/main/java/org/spongepowered/test/channel/ChannelTest.java +++ b/testplugins/src/main/java/org/spongepowered/test/channel/ChannelTest.java @@ -66,6 +66,19 @@ final class Listeners { private BasicPacketChannel basicChannel; private RawDataChannel rawChannel; + @Listener + private void onConnectionIntent(final ServerSideConnectionEvent.Intent event) { + ChannelTest.this.plugin.logger().info("Starting intent phase."); + + final ServerSideConnection connection = event.connection(); + this.rawChannel.handshake().sendTo(connection, buf -> buf.writeVarInt(3)) + .thenAccept(response -> this.logReceived(this.rawChannel, response.readVarInt(), connection)) + .exceptionally(cause -> { + ChannelTest.this.plugin.logger().error("Failed to get a response to raw 3 value", cause); + return null; + }); + } + @Listener private void onRegisterChannel(final RegisterChannelEvent event) { this.channel = event.register(ResourceKey.of("channeltest", "default"), PacketChannel.class); diff --git a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/server/network/ServerLoginPacketListenerImplMixin_Vanilla.java b/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/server/network/ServerLoginPacketListenerImplMixin_Vanilla.java deleted file mode 100644 index f7408ae3717..00000000000 --- a/vanilla/src/mixins/java/org/spongepowered/vanilla/mixin/core/server/network/ServerLoginPacketListenerImplMixin_Vanilla.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * This file is part of Sponge, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.vanilla.mixin.core.server.network; - -import com.mojang.authlib.GameProfile; -import net.minecraft.network.Connection; -import net.minecraft.network.protocol.login.ServerLoginPacketListener; -import net.minecraft.server.MinecraftServer; -import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import org.spongepowered.api.Sponge; -import org.spongepowered.api.event.Cause; -import org.spongepowered.api.event.EventContext; -import org.spongepowered.api.event.SpongeEventFactory; -import org.spongepowered.api.event.network.ServerSideConnectionEvent; -import org.spongepowered.api.network.ServerSideConnection; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import org.spongepowered.common.SpongeCommon; -import org.spongepowered.common.bridge.network.ConnectionBridge; -import org.spongepowered.common.network.channel.ConnectionUtil; -import org.spongepowered.common.network.channel.SpongeChannelManager; -import org.spongepowered.common.network.channel.TransactionStore; -import org.spongepowered.common.profile.SpongeGameProfile; - -@Mixin(ServerLoginPacketListenerImpl.class) -public abstract class ServerLoginPacketListenerImplMixin_Vanilla implements ServerLoginPacketListener { - - // @formatter:off - @Shadow @Final MinecraftServer server; - @Shadow private ServerLoginPacketListenerImpl.State state; - @Shadow @Final Connection connection; - @Shadow private GameProfile authenticatedProfile; - // @formatter:on - - // Handshake phase: - // 1. Sync registered plugin channels - // 2. Post handshake event and plugins can start sending login payloads - // 3. Wait until the client responded for each of the plugin' requests - - private static final int HANDSHAKE_NOT_STARTED = 0; - private static final int HANDSHAKE_CLIENT_TYPE = 1; - private static final int HANDSHAKE_SYNC_CHANNEL_REGISTRATIONS = 2; - private static final int HANDSHAKE_CHANNEL_REGISTRATION = 3; - private static final int HANDSHAKE_SYNC_PLUGIN_DATA = 4; - - private int impl$handshakeState = ServerLoginPacketListenerImplMixin_Vanilla.HANDSHAKE_NOT_STARTED; - - @Inject(method = "tick", at = @At("HEAD")) - private void impl$onTick(final CallbackInfo ci) { - if (this.state == ServerLoginPacketListenerImpl.State.NEGOTIATING) { - final ServerSideConnection connection = (ServerSideConnection) ((ConnectionBridge) this.connection).bridge$getEngineConnection(); - if (this.impl$handshakeState == ServerLoginPacketListenerImplMixin_Vanilla.HANDSHAKE_NOT_STARTED) { - this.impl$handshakeState = ServerLoginPacketListenerImplMixin_Vanilla.HANDSHAKE_CLIENT_TYPE; - - ((SpongeChannelManager) Sponge.channelManager()).requestClientType(connection).thenAccept(result -> { - this.impl$handshakeState = ServerLoginPacketListenerImplMixin_Vanilla.HANDSHAKE_SYNC_CHANNEL_REGISTRATIONS; - }); - - } else if (this.impl$handshakeState == ServerLoginPacketListenerImplMixin_Vanilla.HANDSHAKE_SYNC_CHANNEL_REGISTRATIONS) { - this.impl$handshakeState = ServerLoginPacketListenerImplMixin_Vanilla.HANDSHAKE_CHANNEL_REGISTRATION; - - ((SpongeChannelManager) Sponge.channelManager()).sendLoginChannelRegistry(connection).thenAccept(result -> { - final Cause cause = Cause.of(EventContext.empty(), this); - final ServerSideConnectionEvent.Handshake event = - SpongeEventFactory.createServerSideConnectionEventHandshake(cause, connection, SpongeGameProfile.of(this.authenticatedProfile)); - SpongeCommon.post(event); - this.impl$handshakeState = ServerLoginPacketListenerImplMixin_Vanilla.HANDSHAKE_SYNC_PLUGIN_DATA; - }); - } else if (this.impl$handshakeState == ServerLoginPacketListenerImplMixin_Vanilla.HANDSHAKE_SYNC_PLUGIN_DATA) { - final TransactionStore store = ConnectionUtil.getTransactionStore(connection); - if (store.isEmpty()) { - this.state = ServerLoginPacketListenerImpl.State.VERIFYING; - } - } - } - } -} diff --git a/vanilla/src/mixins/resources/mixins.spongevanilla.core.json b/vanilla/src/mixins/resources/mixins.spongevanilla.core.json index d11ba8b1379..f70c40c0ea5 100644 --- a/vanilla/src/mixins/resources/mixins.spongevanilla.core.json +++ b/vanilla/src/mixins/resources/mixins.spongevanilla.core.json @@ -26,7 +26,6 @@ "server.commands.SpreadPlayersCommandMixin_Vanilla", "server.level.ServerPlayerMixin_Vanilla", "server.network.ServerGamePacketListenerImplMixin_Vanilla", - "server.network.ServerLoginPacketListenerImplMixin_Vanilla", "server.packs.repository.PackRepositoryMixin_Vanilla", "world.entity.EntityMixin_Vanilla", "world.entity.EntityTypeMixin_Vanilla",