Skip to content

Commit 07e1fae

Browse files
committed
Add support for custom Bedrock transports
1 parent ed861ee commit 07e1fae

9 files changed

Lines changed: 555 additions & 213 deletions

core/src/main/java/org/geysermc/geyser/network/GeyserBedrockPeer.java

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,29 @@
2626
package org.geysermc.geyser.network;
2727

2828
import io.netty.channel.Channel;
29+
import lombok.Getter;
30+
import lombok.Setter;
31+
import org.cloudburstmc.netty.channel.raknet.RakChildChannel;
32+
import org.cloudburstmc.netty.handler.codec.raknet.common.RakSessionCodec;
2933
import org.cloudburstmc.protocol.bedrock.BedrockPeer;
3034
import org.cloudburstmc.protocol.bedrock.BedrockSessionFactory;
3135

36+
import javax.crypto.SecretKey;
3237
import java.net.SocketAddress;
3338

3439
public class GeyserBedrockPeer extends BedrockPeer {
40+
@Getter
41+
@Setter
3542
private SocketAddress proxiedAddress;
3643

44+
/**
45+
* Whether Bedrock encryption can be negotiated. RakNet clients can; transports behind a trusted proxy that
46+
* already authenticated the player set this {@code false} to skip the handshake.
47+
*/
48+
@Getter
49+
@Setter
50+
private boolean encryptionSupported = true;
51+
3752
public GeyserBedrockPeer(Channel channel, BedrockSessionFactory sessionFactory) {
3853
super(channel, sessionFactory);
3954
}
@@ -43,7 +58,30 @@ public SocketAddress getRealAddress() {
4358
return proxied == null ? this.getSocketAddress() : proxied;
4459
}
4560

46-
public void setProxiedAddress(SocketAddress proxiedAddress) {
47-
this.proxiedAddress = proxiedAddress;
61+
@Override
62+
public void enableEncryption(SecretKey secretKey) {
63+
if (!this.encryptionSupported) {
64+
// Security is the transport's responsibility on this connection; do not install Bedrock ciphers.
65+
return;
66+
}
67+
super.enableEncryption(secretKey);
68+
}
69+
70+
/**
71+
* The round-trip latency to the peer in milliseconds, used for {@code /list}-style ping displays and dumps.
72+
* <p>
73+
* The default implementation reads RakNet's estimate. Custom transports should override this function
74+
* and supply their own measurement.
75+
*
76+
* @return the latency in milliseconds, or {@code 0} if unavailable
77+
*/
78+
public int getPing() {
79+
if (this.getChannel() instanceof RakChildChannel rakChildChannel) {
80+
RakSessionCodec rakSessionCodec = rakChildChannel.rakPipeline().get(RakSessionCodec.class);
81+
if (rakSessionCodec != null) {
82+
return (int) Math.floor(rakSessionCodec.getPing());
83+
}
84+
}
85+
return 0;
4886
}
4987
}

core/src/main/java/org/geysermc/geyser/network/GeyserServerInitializer.java

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,25 @@
2626
package org.geysermc.geyser.network;
2727

2828
import io.netty.channel.Channel;
29-
import io.netty.channel.DefaultEventLoopGroup;
30-
import io.netty.util.concurrent.DefaultThreadFactory;
31-
import lombok.Getter;
3229
import org.checkerframework.checker.nullness.qual.NonNull;
3330
import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption;
3431
import org.cloudburstmc.protocol.bedrock.BedrockPeer;
3532
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
36-
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
3733
import org.cloudburstmc.protocol.bedrock.netty.initializer.BedrockServerInitializer;
3834
import org.geysermc.geyser.GeyserImpl;
39-
import org.geysermc.geyser.session.GeyserSession;
4035

36+
/**
37+
* The per-connection child handler for the built-in RakNet transport. Builds cloudburst's standard Bedrock
38+
* pipeline (which is RakNet-aware) and delegates the transport-agnostic session creation to
39+
* {@link GeyserSessionInitializer}.
40+
*/
4141
public class GeyserServerInitializer extends BedrockServerInitializer {
42-
private final GeyserImpl geyser;
4342
private final boolean rakCookiesEnabled;
44-
// There is a constructor that doesn't require inputting threads, but older Netty versions don't have it
45-
@Getter
46-
private final DefaultEventLoopGroup eventLoopGroup = new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser player thread"));
43+
private final GeyserSessionInitializer sessionInitializer;
4744

48-
public GeyserServerInitializer(GeyserImpl geyser, boolean rakCookiesEnabled) {
49-
this.geyser = geyser;
45+
public GeyserServerInitializer(GeyserImpl geyser, boolean rakCookiesEnabled, GeyserSessionInitializer sessionInitializer) {
5046
this.rakCookiesEnabled = rakCookiesEnabled;
47+
this.sessionInitializer = sessionInitializer;
5148
}
5249

5350
@Override
@@ -60,21 +57,7 @@ protected void preInitChannel(Channel channel) throws Exception {
6057

6158
@Override
6259
public void initSession(@NonNull BedrockServerSession bedrockServerSession) {
63-
try {
64-
bedrockServerSession.setLogging(this.geyser.config().debugMode());
65-
GeyserSession session = new GeyserSession(this.geyser, bedrockServerSession, this.eventLoopGroup.next());
66-
67-
if (!bedrockServerSession.isSubClient()) {
68-
Channel channel = bedrockServerSession.getPeer().getChannel();
69-
channel.pipeline().addAfter(BedrockPacketCodec.NAME, InvalidPacketHandler.NAME, new InvalidPacketHandler(session));
70-
}
71-
72-
bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(this.geyser, session));
73-
} catch (Throwable e) {
74-
// Error must be caught or it will be swallowed
75-
this.geyser.getLogger().error("Error occurred while initializing player!", e);
76-
bedrockServerSession.disconnect(e.getMessage());
77-
}
60+
this.sessionInitializer.initializeSession(bedrockServerSession);
7861
}
7962

8063
@Override
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright (c) 2019-2026 GeyserMC. http://geysermc.org
3+
*
4+
* Permission is hereby granted, free of charge, to any person obtaining a copy
5+
* of this software and associated documentation files (the "Software"), to deal
6+
* in the Software without restriction, including without limitation the rights
7+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
* copies of the Software, and to permit persons to whom the Software is
9+
* furnished to do so, subject to the following conditions:
10+
*
11+
* The above copyright notice and this permission notice shall be included in
12+
* all copies or substantial portions of the Software.
13+
*
14+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
* THE SOFTWARE.
21+
*
22+
* @author GeyserMC
23+
* @link https://github.com/GeyserMC/Geyser
24+
*/
25+
26+
package org.geysermc.geyser.network;
27+
28+
import io.netty.channel.Channel;
29+
import io.netty.channel.DefaultEventLoopGroup;
30+
import io.netty.util.concurrent.DefaultThreadFactory;
31+
import lombok.Getter;
32+
import org.checkerframework.checker.nullness.qual.NonNull;
33+
import org.cloudburstmc.protocol.bedrock.BedrockPeer;
34+
import org.cloudburstmc.protocol.bedrock.BedrockServerSession;
35+
import org.cloudburstmc.protocol.bedrock.BedrockSession;
36+
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
37+
import org.geysermc.geyser.GeyserImpl;
38+
import org.geysermc.geyser.session.GeyserSession;
39+
40+
/**
41+
* Transport-agnostic logic for turning an established {@link BedrockServerSession} into a fully wired Geyser
42+
* session. Both the built-in RakNet transport (via {@link GeyserServerInitializer}) and extension-provided
43+
* transports funnel through here, so session creation stays consistent regardless of
44+
* how the connection was established.
45+
*/
46+
public class GeyserSessionInitializer {
47+
private final GeyserImpl geyser;
48+
49+
/**
50+
* The event loop group {@link GeyserSession}s run their per-player logic on. Shared across all transports.
51+
*/
52+
// There is a constructor that doesn't require inputting threads, but older Netty versions don't have it
53+
@Getter
54+
private final DefaultEventLoopGroup eventLoopGroup = new DefaultEventLoopGroup(0, new DefaultThreadFactory("Geyser player thread"));
55+
56+
public GeyserSessionInitializer(GeyserImpl geyser) {
57+
this.geyser = geyser;
58+
}
59+
60+
/**
61+
* Creates a {@link BedrockPeer} that produces Geyser sessions. Useful for transports that build their own
62+
* Netty pipeline by hand (instead of extending cloudburst's
63+
* {@link org.cloudburstmc.protocol.bedrock.netty.initializer.BedrockServerInitializer}), which is necessary
64+
* for non-RakNet transports.
65+
*
66+
* @param channel the connection channel
67+
* @return a peer wired to create Geyser sessions
68+
*/
69+
public BedrockPeer createPeer(Channel channel) {
70+
return new GeyserBedrockPeer(channel, this::createSession);
71+
}
72+
73+
private BedrockSession createSession(BedrockPeer peer, int subClientId) {
74+
BedrockServerSession session = new BedrockServerSession(peer, subClientId);
75+
this.initializeSession(session);
76+
return session;
77+
}
78+
79+
/**
80+
* Wires a freshly created {@link BedrockServerSession} into Geyser: creates the {@link GeyserSession},
81+
* installs the invalid-packet guard, and sets the upstream packet handler.
82+
*
83+
* @param bedrockServerSession the session to initialize
84+
*/
85+
public void initializeSession(@NonNull BedrockServerSession bedrockServerSession) {
86+
try {
87+
bedrockServerSession.setLogging(this.geyser.config().debugMode());
88+
GeyserSession session = new GeyserSession(this.geyser, bedrockServerSession, this.eventLoopGroup.next());
89+
90+
if (!bedrockServerSession.isSubClient()) {
91+
Channel channel = bedrockServerSession.getPeer().getChannel();
92+
channel.pipeline().addAfter(BedrockPacketCodec.NAME, InvalidPacketHandler.NAME, new InvalidPacketHandler(session));
93+
}
94+
95+
bedrockServerSession.setPacketHandler(new UpstreamPacketHandler(this.geyser, session));
96+
} catch (Throwable e) {
97+
// Error must be caught or it will be swallowed
98+
this.geyser.getLogger().error("Error occurred while initializing player!", e);
99+
bedrockServerSession.disconnect(e.getMessage());
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)