Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ 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))
Expand All @@ -23,4 +22,4 @@ The library is published to Maven Central. See the [latest release](https://gith

### Snapshots [![](https://jitpack.io/v/dev.kastle/NetworkCompatible.svg)](https://jitpack.io/#dev.kastle/NetworkCompatible)

Snapshots are avaible from [jitpack](https://jitpack.io/#dev.kastle/NetworkCompatible).
Snapshots are avaible from [jitpack](https://jitpack.io/#dev.kastle/NetworkCompatible).
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@

import java.util.Map;

import static org.cloudburstmc.netty.channel.raknet.RakConstants.DEFAULT_UNCONNECTED_MAGIC;
import static org.cloudburstmc.netty.channel.raknet.RakConstants.MTU_SIZES;
import static org.cloudburstmc.netty.channel.raknet.RakConstants.SESSION_TIMEOUT_MS;
import static org.cloudburstmc.netty.channel.raknet.RakConstants.TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS;
import static org.cloudburstmc.netty.channel.raknet.RakConstants.*;

/**
* The extended implementation of {@link RakChannelConfig} based on {@link DefaultRakSessionConfig} used by client.
Expand All @@ -43,7 +40,6 @@ public class DefaultRakClientConfig extends DefaultRakSessionConfig {
private volatile boolean ipDontFragment = false;
private volatile int clientInternalAddresses = 10;
private volatile int timeBetweenSendConnectionAttemptsMS = TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS;
private volatile int bedrockProtocolVersion;

public DefaultRakClientConfig(Channel channel) {
super(channel);
Expand All @@ -52,10 +48,9 @@ public DefaultRakClientConfig(Channel channel) {
@Override
public Map<ChannelOption<?>, Object> getOptions() {
return this.getOptions(
super.getOptions(),
super.getOptions(),
RakChannelOption.RAK_UNCONNECTED_MAGIC, RakChannelOption.RAK_CONNECT_TIMEOUT, RakChannelOption.RAK_REMOTE_GUID, RakChannelOption.RAK_SESSION_TIMEOUT, RakChannelOption.RAK_COMPATIBILITY_MODE,
RakChannelOption.RAK_MTU_SIZES, RakChannelOption.RAK_IP_DONT_FRAGMENT, RakChannelOption.RAK_CLIENT_INTERNAL_ADDRESSES, RakChannelOption.RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS,
RakChannelOption.RAK_CLIENT_BEDROCK_PROTOCOL_VERSION);
RakChannelOption.RAK_MTU_SIZES, RakChannelOption.RAK_IP_DONT_FRAGMENT, RakChannelOption.RAK_CLIENT_INTERNAL_ADDRESSES, RakChannelOption.RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS);
}

@SuppressWarnings("unchecked")
Expand All @@ -79,8 +74,6 @@ public <T> T getOption(ChannelOption<T> option) {
return (T) Integer.valueOf(this.clientInternalAddresses);
} else if (option == RakChannelOption.RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS) {
return (T) Integer.valueOf(this.timeBetweenSendConnectionAttemptsMS);
} else if (option == RakChannelOption.RAK_CLIENT_BEDROCK_PROTOCOL_VERSION) {
return (T) Integer.valueOf(this.bedrockProtocolVersion);
}
return super.getOption(option);
}
Expand Down Expand Up @@ -116,9 +109,6 @@ public <T> boolean setOption(ChannelOption<T> option, T value) {
} else if (option == RakChannelOption.RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS) {
this.setTimeBetweenSendConnectionAttemptsMS((Integer) value);
return true;
} else if (option == RakChannelOption.RAK_CLIENT_BEDROCK_PROTOCOL_VERSION) {
this.setBedrockProtocolVersion((Integer) value);
return true;
}
return super.setOption(option, value);
}
Expand Down Expand Up @@ -203,12 +193,4 @@ public int getTimeBetweenSendConnectionAttemptsMS() {
public void setTimeBetweenSendConnectionAttemptsMS(int timeBetweenSendConnectionAttemptsMS) {
this.timeBetweenSendConnectionAttemptsMS = timeBetweenSendConnectionAttemptsMS;
}

public int getBedrockProtocolVersion() {
return this.bedrockProtocolVersion;
}

public void setBedrockProtocolVersion(int bedrockProtocolVersion) {
this.bedrockProtocolVersion = bedrockProtocolVersion;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,6 @@ public class RakChannelOption<T> extends ChannelOption<T> {
public static final ChannelOption<Integer> RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS =
valueOf(RakChannelOption.class, "RAK_TIME_BETWEEN_SEND_CONNECTION_ATTEMPTS_MS");

/**
* 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<Integer> RAK_CLIENT_BEDROCK_PROTOCOL_VERSION =
valueOf(RakChannelOption.class, "RAK_CLIENT_BEDROCK_PROTOCOL_VERSION");

@SuppressWarnings("deprecation")
protected RakChannelOption() {
super(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,50 @@
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 io.netty.util.concurrent.Promise;
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 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<ByteBuf> NETWORK_SETTINGS_PAYLOAD = AttributeKey.valueOf("network-settings-payload");
private static final InternalLogger log = InternalLoggerFactory.getInstance(RakClientNetworkSettingsHandler.class);

private final RakChannel channel;
private final Promise<RakMessage> networkSettingsPacketPromise;

public RakClientNetworkSettingsHandler(RakChannel channel) {
public RakClientNetworkSettingsHandler(RakChannel channel, Promise<RakMessage> networkSettingsPacketPromise) {
this.channel = channel;
this.networkSettingsPacketPromise = networkSettingsPacketPromise;
}

@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if (!(msg instanceof ByteBuf)) {
ctx.write(msg, promise);
return;
ctx.channel().pipeline().remove(RakClientNetworkSettingsHandler.NAME);

RakMessage packet;

if (!(msg instanceof RakMessage)) {
if (msg instanceof ByteBuf) {
packet = new RakMessage((ByteBuf) msg, RakReliability.RELIABLE_ORDERED, RakPriority.NORMAL);
} else {
throw new IllegalStateException("First packet was not an instance of RakMessage or ByteBuf: class " + msg.getClass().getName());
}
} else {
packet = (RakMessage) msg;
}

ByteBuf packet = (ByteBuf) msg;
ByteBuf content = packet.content();

if (packet.capacity() < 4) {
ctx.write(msg, promise);
return;
if (content.capacity() < 4) {
throw new IllegalStateException("First packet was not a RequestNetworkSettings packet: Content less than 4 bytes");
}

if (packet.getByte(0) != (byte) ID_GAME_PACKET) {
ctx.write(msg, promise);
return;
if (content.getByte(0) != (byte) ID_GAME_PACKET) {
throw new IllegalStateException("First packet was not a RequestNetworkSettings packet: Expected RakNet game packet ID (" + ID_GAME_PACKET + "), but got " + content.getByte(0));
}

int rakVersion = this.channel.config().getOption(RakChannelOption.RAK_PROTOCOL_VERSION);
Expand All @@ -48,31 +56,25 @@ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise)
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);
if (content.getByte(1) != (byte) 0x06) break;
if (content.getByte(2) != (byte) 0xc1) break;
if ((content.getByte(3) & (byte) 0b10000111) != (byte) 0b00000001) break;
this.networkSettingsPacketPromise.setSuccess(packet);
return;
case 8:
if (packet.getByte(1) != (byte) 0x07) break;
if (packet.getByte(2) != 0xc1) break;
onNetworkSettings(ctx, packet);
if (content.getByte(1) != (byte) 0x07) break;
if (content.getByte(2) != (byte) 0xc1) break;
this.networkSettingsPacketPromise.setSuccess(packet);
return;
case 7:
if (packet.getByte(1) != (byte) 0x05) break;
if (packet.getByte(2) != (byte) 0xc1) break;
onNetworkSettings(ctx, packet);
if (content.getByte(1) != (byte) 0x05) break;
if (content.getByte(2) != (byte) 0xc1) break;
this.networkSettingsPacketPromise.setSuccess(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);
throw new IllegalStateException("First packet was not a RequestNetworkSettings packet: Invalid Bedrock packet ID");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,12 @@
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.Promise;
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.handler.codec.raknet.common.RakSessionCodecCompatible;
import org.cloudburstmc.netty.channel.raknet.packet.RakMessage;
import org.cloudburstmc.netty.handler.codec.raknet.common.*;
import org.cloudburstmc.netty.util.RakUtils;

public class RakClientOfflineHandlerCompatible extends RakClientOfflineHandler {
Expand All @@ -36,17 +31,18 @@ void onRetryAttempt(Channel channel) {
@Override
void onSuccess(ChannelHandlerContext ctx) {
RakSessionCodec sessionCodec = new RakSessionCodecCompatible(this.rakChannel());
Promise<RakMessage> networkSettingsPacketPromise = ctx.executor().newPromise();
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, RakClientNetworkSettingsHandler.NAME, new RakClientNetworkSettingsHandler(this.rakChannel(), networkSettingsPacketPromise));
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()));
ctx.pipeline().addAfter(DisconnectNotificationHandler.NAME, RakClientOnlineInitialHandlerCompatible.NAME, new RakClientOnlineInitialHandlerCompatible(this.rakChannel(), this.successPromise(), networkSettingsPacketPromise));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,46 @@
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.concurrent.Promise;
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.*;
import static org.cloudburstmc.netty.channel.raknet.RakConstants.ID_CONNECTED_PING;
import static org.cloudburstmc.netty.channel.raknet.RakConstants.IPV4_MESSAGE_SIZE;

public class RakClientOnlineInitialHandlerCompatible extends RakClientOnlineInitialHandler {
public static final String NAME = "rak-client-online-initial-handler";

private final Promise<RakMessage> networkSettingsPacketPromise;
private long pingTime = 0;

public RakClientOnlineInitialHandlerCompatible(RakChannel rakChannel, ChannelPromise promise) {
super(rakChannel, promise);
public RakClientOnlineInitialHandlerCompatible(RakChannel rakChannel, ChannelPromise successPromise, Promise<RakMessage> networkSettingsPacketPromise) {
super(rakChannel, successPromise);
this.networkSettingsPacketPromise = networkSettingsPacketPromise;
}

@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));
// Wait for the RequestNetworkSettings packet before sending the final batch
this.networkSettingsPacketPromise.addListener(future -> {
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 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.write(future.get());

ctx.flush();
ctx.flush();
});
}

@Override
Expand Down