Skip to content

Commit

Permalink
Merge branch 'GeyserMC:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
vvcDaDa authored Feb 7, 2024
2 parents 939c4f6 + fa339d3 commit b9cbdaa
Show file tree
Hide file tree
Showing 19 changed files with 12,098 additions and 67 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The ultimate goal of this project is to allow Minecraft: Bedrock Edition users t

Special thanks to the DragonProxy project for being a trailblazer in protocol translation and for all the team members who have joined us here!

### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.51 and Minecraft Java 1.20.4
### Currently supporting Minecraft Bedrock 1.20.40 - 1.20.61 and Minecraft Java 1.20.4

## Setting Up
Take a look [here](https://wiki.geysermc.org/geyser/setup/) for how to set up Geyser.
Expand Down
12 changes: 10 additions & 2 deletions core/src/main/java/org/geysermc/geyser/network/GameProtocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec;
import org.cloudburstmc.protocol.bedrock.codec.v622.Bedrock_v622;
import org.cloudburstmc.protocol.bedrock.codec.v630.Bedrock_v630;
import org.cloudburstmc.protocol.bedrock.codec.v649.Bedrock_v649;
import org.cloudburstmc.protocol.bedrock.netty.codec.packet.BedrockPacketCodec;
import org.geysermc.geyser.session.GeyserSession;

Expand All @@ -46,7 +47,7 @@ public final class GameProtocol {
* Default Bedrock codec that should act as a fallback. Should represent the latest available
* release of the game that Geyser supports.
*/
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v630.CODEC;
public static final BedrockCodec DEFAULT_BEDROCK_CODEC = Bedrock_v649.CODEC;

/**
* A list of all supported Bedrock versions that can join Geyser
Expand All @@ -63,9 +64,12 @@ public final class GameProtocol {
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v622.CODEC.toBuilder()
.minecraftVersion("1.20.40/1.20.41")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
SUPPORTED_BEDROCK_CODECS.add(Bedrock_v630.CODEC.toBuilder()
.minecraftVersion("1.20.50/1.20.51")
.build());
SUPPORTED_BEDROCK_CODECS.add(DEFAULT_BEDROCK_CODEC.toBuilder()
.minecraftVersion("1.20.60/1.20.61")
.build());
}

/**
Expand All @@ -88,6 +92,10 @@ public static boolean isPre1_20_50(GeyserSession session) {
return session.getUpstream().getProtocolVersion() < Bedrock_v630.CODEC.getProtocolVersion();
}

public static boolean is1_20_60orHigher(int protocolVersion) {
return protocolVersion >= Bedrock_v649.CODEC.getProtocolVersion();
}

/**
* Gets the {@link PacketCodec} for Minecraft: Java Edition.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
import org.cloudburstmc.protocol.bedrock.data.ExperimentData;
import org.cloudburstmc.protocol.bedrock.data.PacketCompressionAlgorithm;
import org.cloudburstmc.protocol.bedrock.data.ResourcePackType;
import org.cloudburstmc.protocol.bedrock.netty.codec.compression.CompressionStrategy;
import org.cloudburstmc.protocol.bedrock.netty.codec.compression.SimpleCompressionStrategy;
import org.cloudburstmc.protocol.bedrock.netty.codec.compression.ZlibCompression;
import org.cloudburstmc.protocol.bedrock.packet.BedrockPacket;
import org.cloudburstmc.protocol.bedrock.packet.LoginPacket;
import org.cloudburstmc.protocol.bedrock.packet.ModalFormResponsePacket;
Expand All @@ -48,6 +51,7 @@
import org.cloudburstmc.protocol.bedrock.packet.ResourcePacksInfoPacket;
import org.cloudburstmc.protocol.bedrock.packet.SetTitlePacket;
import org.cloudburstmc.protocol.common.PacketSignal;
import org.cloudburstmc.protocol.common.util.Zlib;
import org.geysermc.geyser.Constants;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
Expand Down Expand Up @@ -77,11 +81,16 @@ public class UpstreamPacketHandler extends LoggingPacketHandler {

private boolean networkSettingsRequested = false;
private final Deque<String> packsToSent = new ArrayDeque<>();
private final CompressionStrategy compressionStrategy;

private SessionLoadResourcePacksEventImpl resourcePackLoadEvent;

public UpstreamPacketHandler(GeyserImpl geyser, GeyserSession session) {
super(geyser, session);

ZlibCompression compression = new ZlibCompression(Zlib.RAW);
compression.setLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
this.compressionStrategy = new SimpleCompressionStrategy(compression);
}

private PacketSignal translateAndDefault(BedrockPacket packet) {
Expand Down Expand Up @@ -149,9 +158,8 @@ public PacketSignal handle(RequestNetworkSettingsPacket packet) {
responsePacket.setCompressionAlgorithm(algorithm);
responsePacket.setCompressionThreshold(512);
session.sendUpstreamPacketImmediately(responsePacket);
session.getUpstream().getSession().getPeer().setCompression(compressionStrategy);

session.getUpstream().getSession().setCompression(algorithm);
session.getUpstream().getSession().setCompressionLevel(this.geyser.getConfig().getBedrock().getCompressionLevel());
networkSettingsRequested = true;
return PacketSignal.HANDLED;
}
Expand Down
130 changes: 130 additions & 0 deletions core/src/main/java/org/geysermc/geyser/network/netty/Bootstraps.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2019-2023 GeyserMC. http://geysermc.org
*
* 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.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Geyser
*/

package org.geysermc.geyser.network.netty;

import io.netty.bootstrap.AbstractBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.epoll.Native;
import io.netty.channel.unix.UnixChannelOption;
import lombok.experimental.UtilityClass;

import java.util.Optional;
import java.util.concurrent.CompletableFuture;

@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
@UtilityClass
public final class Bootstraps {
private static final Optional<int[]> KERNEL_VERSION;

// The REUSEPORT_AVAILABLE socket option is available starting from kernel version 3.9.
// This option allows multiple sockets to listen on the same IP address and port without conflict.
private static final int[] REUSEPORT_VERSION = new int[]{3, 9, 0};
private static final boolean REUSEPORT_AVAILABLE;

static {
String kernelVersion;
try {
kernelVersion = Native.KERNEL_VERSION;
} catch (Throwable e) {
kernelVersion = null;
}
if (kernelVersion != null && kernelVersion.contains("-")) {
int index = kernelVersion.indexOf('-');
if (index > -1) {
kernelVersion = kernelVersion.substring(0, index);
}
int[] kernelVer = fromString(kernelVersion);
KERNEL_VERSION = Optional.of(kernelVer);
REUSEPORT_AVAILABLE = checkVersion(kernelVer, 0);
} else {
KERNEL_VERSION = Optional.empty();
REUSEPORT_AVAILABLE = false;
}
}

public static Optional<int[]> getKernelVersion() {
return KERNEL_VERSION;
}

public static boolean isReusePortAvailable() {
return REUSEPORT_AVAILABLE;
}

@SuppressWarnings({"rawtypes, unchecked"})
public static void setupBootstrap(AbstractBootstrap bootstrap) {
if (REUSEPORT_AVAILABLE) {
bootstrap.option(UnixChannelOption.SO_REUSEPORT, true);
}
}

private static int[] fromString(String ver) {
String[] parts = ver.split("\\.");
if (parts.length < 2) {
throw new IllegalArgumentException("At least 2 version numbers required");
}

return new int[]{
Integer.parseInt(parts[0]),
Integer.parseInt(parts[1]),
parts.length == 2 ? 0 : Integer.parseInt(parts[2])
};
}

private static boolean checkVersion(int[] ver, int i) {
if (ver[i] > REUSEPORT_VERSION[i]) {
return true;
} else if (ver[i] == REUSEPORT_VERSION[i]) {
if (ver.length == (i + 1)) {
return true;
} else {
return checkVersion(ver, i + 1);
}
}
return false;
}

public static CompletableFuture<Void> allOf(ChannelFuture... futures) {
if (futures == null || futures.length == 0) {
return CompletableFuture.completedFuture(null);
}
@SuppressWarnings("unchecked")
CompletableFuture<Channel>[] completableFutures = new CompletableFuture[futures.length];
for (int i = 0; i < futures.length; i++) {
ChannelFuture channelFuture = futures[i];
CompletableFuture<Channel> completableFuture = new CompletableFuture<>();
channelFuture.addListener(future -> {
if (future.cause() != null) {
completableFuture.completeExceptionally(future.cause());
}
completableFuture.complete(channelFuture.channel());
});
completableFutures[i] = completableFuture;
}

return CompletableFuture.allOf(completableFutures);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,16 @@ public final class GeyserServer {

private final GeyserImpl geyser;
private EventLoopGroup group;
// Split childGroup may improve IO
private EventLoopGroup childGroup;
private final ServerBootstrap bootstrap;
private EventLoopGroup playerGroup;

@Getter
private final ExpiringMap<InetSocketAddress, InetSocketAddress> proxiedAddresses;
private final int listenCount;

private ChannelFuture bootstrapFuture;
private ChannelFuture[] bootstrapFutures;

/**
* The port to broadcast in the pong. This can be different from the port the server is bound to, e.g. due to port forwarding.
Expand All @@ -109,9 +112,14 @@ public final class GeyserServer {

public GeyserServer(GeyserImpl geyser, int threadCount) {
this.geyser = geyser;
this.group = TRANSPORT.eventLoopGroupFactory().apply(threadCount);
this.listenCount = Bootstraps.isReusePortAvailable() ? Integer.getInteger("Geyser.ListenCount", 2) : 1;
GeyserImpl.getInstance().getLogger().debug("Listen thread count: " + listenCount);
this.group = TRANSPORT.eventLoopGroupFactory().apply(listenCount);
this.childGroup = TRANSPORT.eventLoopGroupFactory().apply(threadCount);

this.bootstrap = this.createBootstrap(this.group);
this.bootstrap = this.createBootstrap();
// setup SO_REUSEPORT if exists
Bootstraps.setupBootstrap(this.bootstrap);

if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
this.proxiedAddresses = ExpiringMap.builder()
Expand All @@ -130,46 +138,51 @@ public GeyserServer(GeyserImpl geyser, int threadCount) {
}

public CompletableFuture<Void> bind(InetSocketAddress address) {
CompletableFuture<Void> future = new CompletableFuture<>();
this.bootstrapFuture = this.bootstrap.bind(address).addListener(bindResult -> {
if (bindResult.cause() != null) {
future.completeExceptionally(bindResult.cause());
return;
}
future.complete(null);
});
bootstrapFutures = new ChannelFuture[listenCount];
for (int i = 0; i < listenCount; i++) {
ChannelFuture future = bootstrap.bind(address);
addHandlers(future);
bootstrapFutures[i] = future;
}

Channel channel = this.bootstrapFuture.channel();
return Bootstraps.allOf(bootstrapFutures);
}

private void addHandlers(ChannelFuture future) {
Channel channel = future.channel();
// Add our ping handler
channel.pipeline()
.addFirst(RakConnectionRequestHandler.NAME, new RakConnectionRequestHandler(this))
.addAfter(RakServerOfflineHandler.NAME, RakPingHandler.NAME, new RakPingHandler(this));

// Add proxy handler
if (this.geyser.getConfig().getBedrock().isEnableProxyProtocol()) {
channel.pipeline().addFirst("proxy-protocol-decoder", new ProxyServerHandler());
}

return future;
}

public void shutdown() {
try {
Future<?> future1 = this.group.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Future<?> futureChildGroup = this.childGroup.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
this.childGroup = null;
Future<?> futureGroup = this.group.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
this.group = null;
Future<?> future2 = this.playerGroup.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
Future<?> futurePlayerGroup = this.playerGroup.shutdownGracefully(SHUTDOWN_QUIET_PERIOD_MS, SHUTDOWN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
this.playerGroup = null;
future1.sync();
future2.sync();

futureChildGroup.sync();
futureGroup.sync();
futurePlayerGroup.sync();

SkinProvider.shutdown();
} catch (InterruptedException e) {
GeyserImpl.getInstance().getLogger().severe("Exception in shutdown process", e);
}
this.bootstrapFuture.channel().closeFuture().syncUninterruptibly();
for (ChannelFuture f : bootstrapFutures) {
f.channel().closeFuture().syncUninterruptibly();
}
}

private ServerBootstrap createBootstrap(EventLoopGroup group) {
private ServerBootstrap createBootstrap() {
if (this.geyser.getConfig().isDebugMode()) {
this.geyser.getLogger().debug("EventLoop type: " + TRANSPORT.datagramChannel());
if (TRANSPORT.datagramChannel() == NioDatagramChannel.class) {
Expand All @@ -188,7 +201,7 @@ private ServerBootstrap createBootstrap(EventLoopGroup group) {
this.geyser.getLogger().debug("Setting MTU to " + this.geyser.getConfig().getMtu());
return new ServerBootstrap()
.channelFactory(RakChannelFactory.server(TRANSPORT.datagramChannel()))
.group(group)
.group(group, childGroup)
.option(RakChannelOption.RAK_HANDLE_PING, true)
.option(RakChannelOption.RAK_MAX_MTU, this.geyser.getConfig().getMtu())
.childHandler(serverInitializer);
Expand Down Expand Up @@ -224,7 +237,7 @@ public boolean onConnectionRequest(InetSocketAddress inetSocketAddress) {
return true;
}

public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
public BedrockPong onQuery(Channel channel, InetSocketAddress inetSocketAddress) {
if (geyser.getConfig().isDebugMode() && PRINT_DEBUG_PINGS) {
String ip;
if (geyser.getConfig().isLogPlayerIpAddresses()) {
Expand Down Expand Up @@ -257,7 +270,7 @@ public BedrockPong onQuery(InetSocketAddress inetSocketAddress) {
.version(GameProtocol.DEFAULT_BEDROCK_CODEC.getMinecraftVersion()) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
.ipv4Port(this.broadcastPort)
.ipv6Port(this.broadcastPort)
.serverId(bootstrapFuture.channel().config().getOption(RakChannelOption.RAK_GUID));
.serverId(channel.config().getOption(RakChannelOption.RAK_GUID));

if (config.isPassthroughMotd() && pingInfo != null && pingInfo.getDescription() != null) {
String[] motd = MessageTranslator.convertMessageLenient(pingInfo.getDescription()).split("\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public class RakPingHandler extends SimpleChannelInboundHandler<RakPing> {
protected void channelRead0(ChannelHandlerContext ctx, RakPing msg) {
long guid = ctx.channel().config().getOption(RakChannelOption.RAK_GUID);

RakPong pong = msg.reply(guid, this.server.onQuery(msg.getSender()).toByteBuf());
RakPong pong = msg.reply(guid, this.server.onQuery(ctx.channel(), msg.getSender()).toByteBuf());
ctx.writeAndFlush(pong);
}
}
Loading

0 comments on commit b9cbdaa

Please sign in to comment.