diff --git a/README.md b/README.md index 1d0fd04..bb55f51 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ You can join the [Discord](https://discord.gg/5z4GuSnqmQ) for help with this for - New incoming connection batches additional packets to more closely imitate the vanilla client: - A `Connected Ping` - The first game packet, `Request Network Settings Packet` + - Attempts to detect it from the connection, but uses `RakChannelOption.RAK_CLIENT_BEDROCK_PROTOCOL_VERSION` if it is not detected in the pipeline - Allows for resetting security state if `Open Connection Reply 1` is resent by the server - Only do retries with `Open Connection Request 1`, and reserve `Open Connection Request 2` only as a direct response to `Open Connection Reply 1` - Allows using datagram channel factories for raknet (from [@AlexProgrammerDE](https://github.com/AlexProgrammerDE)) diff --git a/gradle.properties b/gradle.properties index 2cfef23..519c152 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,2 @@ # Only update version on publishing to Maven Central -version=1.1.0 +version=1.2.0 diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelOption.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelOption.java index 2f8a939..aac6cb2 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelOption.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/channel/raknet/config/RakChannelOption.java @@ -190,7 +190,7 @@ public class RakChannelOption extends ChannelOption { valueOf(RakChannelOption.class, "RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS"); /** - * The protocol version of the RakNet client for sending RequestNetworkSettingsPacket in compatibility mode. + * The fllback protocol version of the RakNet client for sending RequestNetworkSettingsPacket in compatibility mode if one is not found in the pipeline. */ public static final ChannelOption RAK_CLIENT_BEDROCK_PROTOCOL_VERSION = valueOf(RakChannelOption.class, "RAK_CLIENT_BEDROCK_PROTOCOL_VERSION"); diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientNetworkSettingsHandler.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientNetworkSettingsHandler.java new file mode 100644 index 0000000..cf1f513 --- /dev/null +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientNetworkSettingsHandler.java @@ -0,0 +1,78 @@ +package org.cloudburstmc.netty.handler.codec.raknet.client; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; +import io.netty.util.AttributeKey; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.cloudburstmc.netty.channel.raknet.RakChannel; +import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; + +import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_GAME_PACKET; + +public class RakClientNetworkSettingsHandler extends ChannelOutboundHandlerAdapter { + public static final String NAME = "rak-client-network-settings-handler"; + public static final AttributeKey NETWORK_SETTINGS_PAYLOAD = AttributeKey.valueOf("network-settings-payload"); + private static final InternalLogger log = InternalLoggerFactory.getInstance(RakClientNetworkSettingsHandler.class); + + private final RakChannel channel; + + public RakClientNetworkSettingsHandler(RakChannel channel) { + this.channel = channel; + } + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + if (!(msg instanceof ByteBuf)) { + ctx.write(msg, promise); + return; + } + + ByteBuf packet = (ByteBuf) msg; + + if (packet.capacity() < 4) { + ctx.write(msg, promise); + return; + } + + if (packet.getByte(0) != (byte) ID_GAME_PACKET) { + ctx.write(msg, promise); + return; + } + + int rakVersion = this.channel.config().getOption(RakChannelOption.RAK_PROTOCOL_VERSION); + + switch (rakVersion) { + case 11: + case 10: + case 9: + if (packet.getByte(1) != (byte) 0x06) break; + if (packet.getByte(2) != (byte) 0xc1) break; + if ((packet.getByte(3) & (byte) 0b10000111) != (byte) 0b00000001) break; + onNetworkSettings(ctx, packet); + return; + case 8: + if (packet.getByte(1) != (byte) 0x07) break; + if (packet.getByte(2) != 0xc1) break; + onNetworkSettings(ctx, packet); + return; + case 7: + if (packet.getByte(1) != (byte) 0x05) break; + if (packet.getByte(2) != (byte) 0xc1) break; + onNetworkSettings(ctx, packet); + return; + default: + throw new UnsupportedOperationException("Unsupported protocol version: " + rakVersion); + } + + ctx.write(msg, promise); + } + + private void onNetworkSettings(ChannelHandlerContext ctx, ByteBuf packet) { + log.info("Detected network settings packet, removing handler"); + ctx.channel().attr(NETWORK_SETTINGS_PAYLOAD).set(packet.retain()); + ctx.channel().pipeline().remove(RakClientNetworkSettingsHandler.NAME); + } +} \ No newline at end of file diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandler.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandler.java index 6317532..bbd6792 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandler.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandler.java @@ -55,16 +55,16 @@ public RakClientOfflineHandler(RakChannel rakChannel, ChannelPromise promise) { @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { Channel channel = ctx.channel(); - long timeout = this.rakChannel.config().getOption(RakChannelOption.RAK_CONNECT_TIMEOUT); + long timeout = this.rakChannel().config().getOption(RakChannelOption.RAK_CONNECT_TIMEOUT); this.timeoutFuture = channel.eventLoop().schedule(this::onTimeout, timeout, TimeUnit.MILLISECONDS); this.retryFuture = channel.eventLoop().scheduleAtFixedRate(() -> this.onRetryAttempt(channel), 0, - this.rakChannel.config().getOption(RakChannelOption.RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS), TimeUnit.MILLISECONDS); - this.successPromise.addListener(future -> safeCancel(this.timeoutFuture, channel)); - this.successPromise.addListener(future -> safeCancel(this.retryFuture, channel)); + this.rakChannel().config().getOption(RakChannelOption.RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS), TimeUnit.MILLISECONDS); + this.successPromise().addListener(future -> safeCancel(this.timeoutFuture, channel)); + this.successPromise().addListener(future -> safeCancel(this.retryFuture, channel)); this.retryFuture.addListener(future -> { if (future.cause() != null) { - this.successPromise.tryFailure(future.cause()); + this.successPromise().tryFailure(future.cause()); } }); } @@ -75,32 +75,25 @@ public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { safeCancel(this.retryFuture, ctx.channel()); } - private void onRetryAttempt(Channel channel) { - if (this.rakChannel.config().getOption(RakChannelOption.RAK_COMPATIBILITY_MODE)) { - if (this.state != RakOfflineState.HANDSHAKE_COMPLETED) { + void onRetryAttempt(Channel channel) { + switch (this.state()) { + case HANDSHAKE_1: this.sendOpenConnectionRequest1(channel); - this.connectionAttempts++; - } - } else { - switch (this.state) { - case HANDSHAKE_1: - this.sendOpenConnectionRequest1(channel); - this.connectionAttempts++; - break; - case HANDSHAKE_2: - this.sendOpenConnectionRequest2(channel); - break; - } + this.incrementConnectionAttempts(); + break; + case HANDSHAKE_2: + this.sendOpenConnectionRequest2(channel); + break; } } - private void onTimeout() { - this.successPromise.tryFailure(new ConnectTimeoutException()); + void onTimeout() { + this.successPromise().tryFailure(new ConnectTimeoutException()); } - private void onSuccess(ChannelHandlerContext ctx) { + void onSuccess(ChannelHandlerContext ctx) { // Create new session which decodes RakDatagramPacket to RakMessage - RakSessionCodec sessionCodec = new RakSessionCodec(this.rakChannel); + RakSessionCodec sessionCodec = new RakSessionCodec(this.rakChannel()); ctx.pipeline().addAfter(NAME, RakDatagramCodec.NAME, new RakDatagramCodec()); ctx.pipeline().addAfter(RakDatagramCodec.NAME, RakAcknowledgeHandler.NAME, new RakAcknowledgeHandler(sessionCodec)); ctx.pipeline().addAfter(RakAcknowledgeHandler.NAME, RakSessionCodec.NAME, sessionCodec); @@ -109,8 +102,7 @@ private void onSuccess(ChannelHandlerContext ctx) { ctx.pipeline().addAfter(ConnectedPongHandler.NAME, DisconnectNotificationHandler.NAME, DisconnectNotificationHandler.INSTANCE); // Replicate server behavior, and transform unhandled encapsulated packets to rakMessage ctx.pipeline().addAfter(DisconnectNotificationHandler.NAME, EncapsulatedToMessageHandler.NAME, EncapsulatedToMessageHandler.INSTANCE); - ctx.pipeline().addAfter(DisconnectNotificationHandler.NAME, RakClientOnlineInitialHandler.NAME, new RakClientOnlineInitialHandler(this.rakChannel, this.successPromise)); - ctx.pipeline().fireChannelActive(); + ctx.pipeline().addAfter(DisconnectNotificationHandler.NAME, RakClientOnlineInitialHandler.NAME, new RakClientOnlineInitialHandler(this.rakChannel(), this.successPromise())); } @Override @@ -119,16 +111,16 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Excep return; // Empty packet? } - if (this.state == RakOfflineState.HANDSHAKE_COMPLETED) { + if (this.state() == RakOfflineState.HANDSHAKE_COMPLETED) { // Forward open connection messages if handshake was completed ctx.fireChannelRead(buf.retain()); return; } short packetId = buf.readUnsignedByte(); - ByteBuf magicBuf = this.rakChannel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); + ByteBuf magicBuf = this.rakChannel().config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); if (!buf.isReadable(magicBuf.readableBytes()) || !ByteBufUtil.equals(buf.readSlice(magicBuf.readableBytes()), magicBuf)) { - this.successPromise.tryFailure(new CorruptedFrameException("RakMagic does not match")); + this.successPromise().tryFailure(new CorruptedFrameException("RakMagic does not match")); return; } @@ -139,27 +131,28 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Excep case ID_OPEN_CONNECTION_REPLY_2: this.onOpenConnectionReply2(ctx, buf); this.onSuccess(ctx); + ctx.pipeline().fireChannelActive(); return; case ID_INCOMPATIBLE_PROTOCOL_VERSION: - this.rakChannel.pipeline().fireUserEventTriggered(RakDisconnectReason.INCOMPATIBLE_PROTOCOL_VERSION); - this.successPromise.tryFailure(new IllegalStateException("Incompatible raknet version")); + this.rakChannel().pipeline().fireUserEventTriggered(RakDisconnectReason.INCOMPATIBLE_PROTOCOL_VERSION); + this.successPromise().tryFailure(new IllegalStateException("Incompatible raknet version")); return; case ID_ALREADY_CONNECTED: - this.rakChannel.pipeline().fireUserEventTriggered(RakDisconnectReason.ALREADY_CONNECTED); - this.successPromise.tryFailure(new ChannelException("Already connected")); + this.rakChannel().pipeline().fireUserEventTriggered(RakDisconnectReason.ALREADY_CONNECTED); + this.successPromise().tryFailure(new ChannelException("Already connected")); return; case ID_NO_FREE_INCOMING_CONNECTIONS: - this.rakChannel.pipeline().fireUserEventTriggered(RakDisconnectReason.NO_FREE_INCOMING_CONNECTIONS); - this.successPromise.tryFailure(new ChannelException("No free incoming connections")); + this.rakChannel().pipeline().fireUserEventTriggered(RakDisconnectReason.NO_FREE_INCOMING_CONNECTIONS); + this.successPromise().tryFailure(new ChannelException("No free incoming connections")); return; case ID_IP_RECENTLY_CONNECTED: - this.rakChannel.pipeline().fireUserEventTriggered(RakDisconnectReason.IP_RECENTLY_CONNECTED); - this.successPromise.tryFailure(new ChannelException("Address recently connected")); + this.rakChannel().pipeline().fireUserEventTriggered(RakDisconnectReason.IP_RECENTLY_CONNECTED); + this.successPromise().tryFailure(new ChannelException("Address recently connected")); return; } } - private void onOpenConnectionReply1(ChannelHandlerContext ctx, ByteBuf buffer) { + void onOpenConnectionReply1(ChannelHandlerContext ctx, ByteBuf buffer) { long serverGuid = buffer.readLong(); boolean security = buffer.readBoolean(); if (security) { @@ -170,38 +163,35 @@ private void onOpenConnectionReply1(ChannelHandlerContext ctx, ByteBuf buffer) { } int mtu = buffer.readShort(); - this.rakChannel.config().setOption(RakChannelOption.RAK_MTU, mtu); - this.rakChannel.config().setOption(RakChannelOption.RAK_REMOTE_GUID, serverGuid); + this.rakChannel().config().setOption(RakChannelOption.RAK_MTU, mtu); + this.rakChannel().config().setOption(RakChannelOption.RAK_REMOTE_GUID, serverGuid); - this.state = RakOfflineState.HANDSHAKE_2; + this.state(RakOfflineState.HANDSHAKE_2); this.sendOpenConnectionRequest2(ctx.channel()); } - private void onOpenConnectionReply2(ChannelHandlerContext ctx, ByteBuf buffer) { + void onOpenConnectionReply2(ChannelHandlerContext ctx, ByteBuf buffer) { buffer.readLong(); // serverGuid - if (this.rakChannel.config().getOption(RakChannelOption.RAK_COMPATIBILITY_MODE)) { - RakUtils.skipAddress(buffer); // serverAddress - } else { - RakUtils.readAddress(buffer); // serverAddress - } + RakUtils.readAddress(buffer); // serverAddress + int mtu = buffer.readShort(); boolean security = buffer.readBoolean(); // security if (security) { - this.successPromise.tryFailure(new SecurityException()); + this.successPromise().tryFailure(new SecurityException()); return; } - this.rakChannel.config().setOption(RakChannelOption.RAK_MTU, mtu); - this.state = RakOfflineState.HANDSHAKE_COMPLETED; + this.rakChannel().config().setOption(RakChannelOption.RAK_MTU, mtu); + this.state(RakOfflineState.HANDSHAKE_COMPLETED); } - private void sendOpenConnectionRequest1(Channel channel) { - int mtuSizeIndex = Math.min(this.connectionAttempts / 4, this.rakChannel.config().getOption(RakChannelOption.RAK_MTU_SIZES).length - 1); - int mtuSize = this.rakChannel.config().getOption(RakChannelOption.RAK_MTU_SIZES)[mtuSizeIndex]; + void sendOpenConnectionRequest1(Channel channel) { + int mtuSizeIndex = Math.min(this.connectionAttempts() / 4, this.rakChannel().config().getOption(RakChannelOption.RAK_MTU_SIZES).length - 1); + int mtuSize = this.rakChannel().config().getOption(RakChannelOption.RAK_MTU_SIZES)[mtuSizeIndex]; - ByteBuf magicBuf = this.rakChannel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); - int rakVersion = this.rakChannel.config().getOption(RakChannelOption.RAK_PROTOCOL_VERSION); - InetSocketAddress address = (InetSocketAddress) this.rakChannel.remoteAddress(); + ByteBuf magicBuf = this.rakChannel().config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); + int rakVersion = this.rakChannel().config().getOption(RakChannelOption.RAK_PROTOCOL_VERSION); + InetSocketAddress address = (InetSocketAddress) this.rakChannel().remoteAddress(); ByteBuf request = channel.alloc().ioBuffer(mtuSize); request.writeByte(ID_OPEN_CONNECTION_REQUEST_1); @@ -212,9 +202,9 @@ private void sendOpenConnectionRequest1(Channel channel) { channel.writeAndFlush(request); } - private void sendOpenConnectionRequest2(Channel channel) { - int mtuSize = this.rakChannel.config().getOption(RakChannelOption.RAK_MTU); - ByteBuf magicBuf = this.rakChannel.config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); + void sendOpenConnectionRequest2(Channel channel) { + int mtuSize = this.rakChannel().config().getOption(RakChannelOption.RAK_MTU); + ByteBuf magicBuf = this.rakChannel().config().getOption(RakChannelOption.RAK_UNCONNECTED_MAGIC); ByteBuf request = channel.alloc().ioBuffer(this.security ? 39 : 34); request.writeByte(ID_OPEN_CONNECTION_REQUEST_2); @@ -225,11 +215,35 @@ private void sendOpenConnectionRequest2(Channel channel) { } RakUtils.writeAddress(request, (InetSocketAddress) channel.remoteAddress()); request.writeShort(mtuSize); - request.writeLong(this.rakChannel.config().getOption(RakChannelOption.RAK_GUID)); + request.writeLong(this.rakChannel().config().getOption(RakChannelOption.RAK_GUID)); channel.writeAndFlush(request); } - private static void safeCancel(ScheduledFuture future, Channel channel) { + RakOfflineState state() { + return this.state; + } + + void state(RakOfflineState state) { + this.state = state; + } + + int connectionAttempts() { + return this.connectionAttempts; + } + + void incrementConnectionAttempts() { + this.connectionAttempts++; + } + + RakChannel rakChannel() { + return this.rakChannel; + } + + ChannelPromise successPromise() { + return this.successPromise; + } + + static void safeCancel(ScheduledFuture future, Channel channel) { channel.eventLoop().execute(() -> { // Make sure this is not called at two places at the same time if (!future.isCancelled()) { future.cancel(false); diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandlerCompatible.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandlerCompatible.java new file mode 100644 index 0000000..7302c14 --- /dev/null +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOfflineHandlerCompatible.java @@ -0,0 +1,66 @@ +package org.cloudburstmc.netty.handler.codec.raknet.client; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import org.cloudburstmc.netty.channel.raknet.RakChannel; +import org.cloudburstmc.netty.channel.raknet.RakOfflineState; +import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; +import org.cloudburstmc.netty.handler.codec.raknet.common.ConnectedPingHandler; +import org.cloudburstmc.netty.handler.codec.raknet.common.ConnectedPongHandler; +import org.cloudburstmc.netty.handler.codec.raknet.common.DisconnectNotificationHandler; +import org.cloudburstmc.netty.handler.codec.raknet.common.EncapsulatedToMessageHandler; +import org.cloudburstmc.netty.handler.codec.raknet.common.RakAcknowledgeHandler; +import org.cloudburstmc.netty.handler.codec.raknet.common.RakDatagramCodec; +import org.cloudburstmc.netty.handler.codec.raknet.common.RakSessionCodec; +import org.cloudburstmc.netty.util.RakUtils; + +public class RakClientOfflineHandlerCompatible extends RakClientOfflineHandler { + public static final String NAME = "rak-client-handler"; + + public RakClientOfflineHandlerCompatible(RakChannel rakChannel, ChannelPromise promise) { + super(rakChannel, promise); + } + + @Override + void onRetryAttempt(Channel channel) { + if (this.state() == RakOfflineState.HANDSHAKE_COMPLETED) { + return; + } + this.sendOpenConnectionRequest1(channel); + this.incrementConnectionAttempts(); + } + + @Override + void onSuccess(ChannelHandlerContext ctx) { + RakSessionCodec sessionCodec = new RakSessionCodec(this.rakChannel()); + ctx.pipeline().addAfter(NAME, RakDatagramCodec.NAME, new RakDatagramCodec()); + ctx.pipeline().addAfter(RakDatagramCodec.NAME, RakAcknowledgeHandler.NAME, new RakAcknowledgeHandler(sessionCodec)); + ctx.pipeline().addAfter(RakAcknowledgeHandler.NAME, RakSessionCodec.NAME, sessionCodec); + // Ensure new incoming connection batches with request network settings game packet + ctx.pipeline().addAfter(RakSessionCodec.NAME, RakClientNetworkSettingsHandler.NAME, new RakClientNetworkSettingsHandler(this.rakChannel())); + ctx.pipeline().addAfter(RakSessionCodec.NAME, ConnectedPingHandler.NAME, new ConnectedPingHandler()); + ctx.pipeline().addAfter(ConnectedPingHandler.NAME, ConnectedPongHandler.NAME, new ConnectedPongHandler(sessionCodec)); + ctx.pipeline().addAfter(ConnectedPongHandler.NAME, DisconnectNotificationHandler.NAME, DisconnectNotificationHandler.INSTANCE); + // Replicate server behavior, and transform unhandled encapsulated packets to rakMessage + ctx.pipeline().addAfter(DisconnectNotificationHandler.NAME, EncapsulatedToMessageHandler.NAME, EncapsulatedToMessageHandler.INSTANCE); + ctx.pipeline().addAfter(DisconnectNotificationHandler.NAME, RakClientOnlineInitialHandlerCompatible.NAME, new RakClientOnlineInitialHandlerCompatible(this.rakChannel(), this.successPromise())); + } + + @Override + void onOpenConnectionReply2(ChannelHandlerContext ctx, ByteBuf buffer) { + buffer.readLong(); // serverGuid + RakUtils.skipAddress(buffer); // serverAddress + + int mtu = buffer.readShort(); + boolean security = buffer.readBoolean(); // security + if (security) { + this.successPromise().tryFailure(new SecurityException()); + return; + } + + this.rakChannel().config().setOption(RakChannelOption.RAK_MTU, mtu); + this.state(RakOfflineState.HANDSHAKE_COMPLETED); + } +} diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandler.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandler.java index 5c1dc5d..949e24a 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandler.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandler.java @@ -50,8 +50,8 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception { this.sendConnectionRequest(ctx); } - private void sendConnectionRequest(ChannelHandlerContext ctx) { - long guid = this.rakChannel.config().getOption(RakChannelOption.RAK_GUID); + void sendConnectionRequest(ChannelHandlerContext ctx) { + long guid = this.rakChannel().config().getOption(RakChannelOption.RAK_GUID); ByteBuf buffer = ctx.alloc().ioBuffer(18); buffer.writeByte(ID_CONNECTION_REQUEST); @@ -61,12 +61,12 @@ private void sendConnectionRequest(ChannelHandlerContext ctx) { ctx.writeAndFlush(new RakMessage(buffer, RakReliability.RELIABLE, RakPriority.IMMEDIATE)); } - private void onSuccess(ChannelHandlerContext ctx) { + void onSuccess(ChannelHandlerContext ctx) { // At this point connection is fully initialized. Channel channel = ctx.channel(); channel.pipeline().remove(RakClientOfflineHandler.NAME); channel.pipeline().remove(RakClientOnlineInitialHandler.NAME); - this.successPromise.trySuccess(); + this.successPromise().trySuccess(); } @Override @@ -80,7 +80,7 @@ protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket messag this.onSuccess(ctx); break; case ID_CONNECTION_REQUEST_FAILED: - this.successPromise.tryFailure(new IllegalStateException("Connection denied")); + this.successPromise().tryFailure(new IllegalStateException("Connection denied")); break; default: ctx.fireChannelRead(message.retain()); @@ -88,56 +88,36 @@ protected void channelRead0(ChannelHandlerContext ctx, EncapsulatedPacket messag } } - private void onConnectionRequestAccepted(ChannelHandlerContext ctx, ByteBuf buf) { + void onConnectionRequestAccepted(ChannelHandlerContext ctx, ByteBuf buf) { buf.skipBytes(1); - - boolean compatibilityMode = this.rakChannel.config().getOption(RakChannelOption.RAK_COMPATIBILITY_MODE); - if (compatibilityMode) { - RakUtils.skipAddress(buf); // Client address - } else { - RakUtils.readAddress(buf); // Client address - } - + RakUtils.readAddress(buf); // Client address buf.readUnsignedShort(); // System index - // Address + 2 * Long - Minimum amount of data - int required = IPV4_MESSAGE_SIZE + 16; - - long pingTime = 0; - while (buf.isReadable(required)) { - if (compatibilityMode) { - RakUtils.skipAddress(buf); - } else { - RakUtils.readAddress(buf); - } + while (buf.isReadable(IPV4_MESSAGE_SIZE + 16)) { + RakUtils.readAddress(buf); } - pingTime = buf.readLong(); - buf.readLong(); ByteBuf incomingBuffer = ctx.alloc().ioBuffer(); - incomingBuffer.writeByte(ID_NEW_INCOMING_CONNECTION); - RakUtils.writeAddress(incomingBuffer, (InetSocketAddress) ctx.channel().remoteAddress()); - for (int i = 0; i < this.rakChannel.config().getOption(RakChannelOption.RAK_CLIENT_INTERNAL_ADDRESSES); i++) { - RakUtils.writeAddress(incomingBuffer, LOCAL_ADDRESS); - } - incomingBuffer.writeLong(pingTime); - incomingBuffer.writeLong(System.currentTimeMillis()); - ctx.write(new RakMessage(incomingBuffer, RakReliability.RELIABLE_ORDERED, RakPriority.NORMAL)); - - if (compatibilityMode) { - ByteBuf pingBuffer = ctx.alloc().ioBuffer(); - pingBuffer.writeByte(ID_CONNECTED_PING); - pingBuffer.writeLong(System.currentTimeMillis()); - ctx.write(new RakMessage(pingBuffer, RakReliability.UNRELIABLE, RakPriority.NORMAL)); - - ByteBuf netSettingsBuffer = ctx.alloc().ioBuffer(); - netSettingsBuffer.writeByte(ID_GAME_PACKET); - netSettingsBuffer.writeByte(0x06); // length - netSettingsBuffer.writeByte(0xc1).writeByte(0x01); // Request network settings packet - netSettingsBuffer.writeInt(this.rakChannel.config().getOption(RakChannelOption.RAK_CLIENT_BEDROCK_PROTOCOL_VERSION)); - ctx.write(new RakMessage(netSettingsBuffer, RakReliability.RELIABLE_ORDERED, RakPriority.NORMAL)); + this.writeIncomingConnection(ctx, incomingBuffer, buf.readLong()); + buf.readLong(); + ctx.writeAndFlush(new RakMessage(incomingBuffer, RakReliability.RELIABLE_ORDERED, RakPriority.NORMAL)); + } + + void writeIncomingConnection(ChannelHandlerContext ctx, ByteBuf buf, long pingTime) { + buf.writeByte(ID_NEW_INCOMING_CONNECTION); + RakUtils.writeAddress(buf, (InetSocketAddress) ctx.channel().remoteAddress()); + for (int i = 0; i < this.rakChannel().config().getOption(RakChannelOption.RAK_CLIENT_INTERNAL_ADDRESSES); i++) { + RakUtils.writeAddress(buf, LOCAL_ADDRESS); } + buf.writeLong(pingTime); + buf.writeLong(System.currentTimeMillis()); + } + + RakChannel rakChannel() { + return this.rakChannel; + } - ctx.flush(); + ChannelPromise successPromise() { + return this.successPromise; } } diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandlerCompatible.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandlerCompatible.java new file mode 100644 index 0000000..1c93cbb --- /dev/null +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientOnlineInitialHandlerCompatible.java @@ -0,0 +1,79 @@ +package org.cloudburstmc.netty.handler.codec.raknet.client; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import org.cloudburstmc.netty.channel.raknet.RakChannel; +import org.cloudburstmc.netty.channel.raknet.RakPriority; +import org.cloudburstmc.netty.channel.raknet.RakReliability; +import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; +import org.cloudburstmc.netty.channel.raknet.packet.RakMessage; +import org.cloudburstmc.netty.util.RakUtils; + +import static org.cloudburstmc.netty.channel.raknet.RakConstants.*; + +public class RakClientOnlineInitialHandlerCompatible extends RakClientOnlineInitialHandler { + public static final String NAME = "rak-client-online-initial-handler"; + + private long pingTime = 0; + + public RakClientOnlineInitialHandlerCompatible(RakChannel rakChannel, ChannelPromise promise) { + super(rakChannel, promise); + } + + @Override + void onSuccess(ChannelHandlerContext ctx) { + super.onSuccess(ctx); + + ByteBuf incomingBuffer = ctx.alloc().ioBuffer(); + this.writeIncomingConnection(ctx, incomingBuffer, pingTime); + ctx.write(new RakMessage(incomingBuffer, RakReliability.RELIABLE_ORDERED, RakPriority.NORMAL)); + + ByteBuf pingBuffer = ctx.alloc().ioBuffer(); + pingBuffer.writeByte(ID_CONNECTED_PING); + pingBuffer.writeLong(System.currentTimeMillis()); + ctx.write(new RakMessage(pingBuffer, RakReliability.UNRELIABLE, RakPriority.NORMAL)); + + ByteBuf netSettingsBuffer = ctx.channel().attr(RakClientNetworkSettingsHandler.NETWORK_SETTINGS_PAYLOAD).get(); + if (netSettingsBuffer == null) { + netSettingsBuffer = ctx.alloc().ioBuffer(); + netSettingsBuffer.writeByte(ID_GAME_PACKET); + int rakVersion = this.rakChannel().config().getOption(RakChannelOption.RAK_PROTOCOL_VERSION); + switch (rakVersion) { + case 11: + case 10: + case 9: + netSettingsBuffer.writeByte(0x06); // length + netSettingsBuffer.writeByte(0xc1).writeByte(0x01); // header + break; + case 8: + netSettingsBuffer.writeByte(0x07); // length + netSettingsBuffer.writeByte(0xc1).writeByte(0x00).writeByte(0x00); // header + break; + case 7: + netSettingsBuffer.writeByte(0x05); // length + netSettingsBuffer.writeByte(0xc1); // header + break; + default: + throw new UnsupportedOperationException("Unsupported protocol version: " + rakVersion); + } + netSettingsBuffer.writeInt(this.rakChannel().config().getOption(RakChannelOption.RAK_CLIENT_BEDROCK_PROTOCOL_VERSION)); + } + ctx.write(new RakMessage(netSettingsBuffer, RakReliability.RELIABLE_ORDERED, RakPriority.NORMAL)); + + ctx.flush(); + } + + @Override + void onConnectionRequestAccepted(ChannelHandlerContext ctx, ByteBuf buf) { + buf.skipBytes(1); + RakUtils.skipAddress(buf); + buf.skipBytes(2); // System index + + while (buf.isReadable(IPV4_MESSAGE_SIZE + 16)) { + RakUtils.skipAddress(buf); + } + this.pingTime = buf.readLong(); + return; + } +} diff --git a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientRouteHandler.java b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientRouteHandler.java index f689666..47e9527 100644 --- a/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientRouteHandler.java +++ b/transport-raknet/src/main/java/org/cloudburstmc/netty/handler/codec/raknet/client/RakClientRouteHandler.java @@ -19,6 +19,7 @@ import io.netty.channel.*; import io.netty.util.concurrent.PromiseCombiner; import org.cloudburstmc.netty.channel.raknet.RakClientChannel; +import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption; import org.cloudburstmc.netty.handler.codec.raknet.common.UnconnectedPongDecoder; import java.net.InetSocketAddress; @@ -47,8 +48,12 @@ public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, Sock ChannelFuture parentFuture = this.channel.parent().connect(remoteAddress, localAddress); parentFuture.addListener(future -> { if (future.isSuccess()) { - this.channel.rakPipeline().addAfter(UnconnectedPongDecoder.NAME, - RakClientOfflineHandler.NAME, new RakClientOfflineHandler(channel, this.channel.getConnectPromise())); + this.channel.rakPipeline().addAfter( + UnconnectedPongDecoder.NAME, + RakClientOfflineHandler.NAME, + channel.config().getOption(RakChannelOption.RAK_COMPATIBILITY_MODE) ? + new RakClientOfflineHandlerCompatible(channel, this.channel.getConnectPromise()) : + new RakClientOfflineHandler(channel, this.channel.getConnectPromise())); } });