2022-10-30 00:23:21 +00:00
/ *
* Copyright ( c ) 2019 - 2022 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 ;
2022-10-30 22:00:08 +00:00
import com.github.steveice10.packetlib.helper.TransportHelper ;
2022-10-30 00:23:21 +00:00
import io.netty.bootstrap.ServerBootstrap ;
import io.netty.channel.ChannelFuture ;
import io.netty.channel.EventLoopGroup ;
2022-10-30 22:00:08 +00:00
import io.netty.channel.epoll.Epoll ;
import io.netty.channel.epoll.EpollDatagramChannel ;
import io.netty.channel.epoll.EpollEventLoopGroup ;
import io.netty.channel.kqueue.KQueue ;
import io.netty.channel.kqueue.KQueueDatagramChannel ;
import io.netty.channel.kqueue.KQueueEventLoopGroup ;
import io.netty.channel.nio.NioEventLoopGroup ;
import io.netty.channel.socket.DatagramChannel ;
2022-10-30 00:23:21 +00:00
import io.netty.channel.socket.nio.NioDatagramChannel ;
import org.cloudburstmc.netty.channel.raknet.RakChannelFactory ;
2022-10-30 03:02:11 +00:00
import org.cloudburstmc.netty.channel.raknet.config.RakChannelOption ;
2022-10-30 22:00:08 +00:00
import org.cloudburstmc.netty.handler.codec.raknet.server.RakServerOfflineHandler ;
2022-10-30 03:02:11 +00:00
import org.cloudburstmc.protocol.bedrock.BedrockPong ;
2022-10-30 00:23:21 +00:00
import org.geysermc.geyser.GeyserImpl ;
2022-10-30 22:00:08 +00:00
import org.geysermc.geyser.configuration.GeyserConfiguration ;
2022-10-30 03:02:11 +00:00
import org.geysermc.geyser.network.GameProtocol ;
2022-10-30 00:23:21 +00:00
import org.geysermc.geyser.network.GeyserServerInitializer ;
2022-10-30 22:00:08 +00:00
import org.geysermc.geyser.ping.GeyserPingInfo ;
import org.geysermc.geyser.ping.IGeyserPingPassthrough ;
import org.geysermc.geyser.text.GeyserLocale ;
import org.geysermc.geyser.translator.text.MessageTranslator ;
2022-10-30 00:23:21 +00:00
import java.net.InetSocketAddress ;
2022-10-30 22:00:08 +00:00
import java.nio.charset.StandardCharsets ;
2023-04-07 01:47:37 +00:00
import java.util.concurrent.CompletableFuture ;
import java.util.function.IntFunction ;
2022-10-30 00:23:21 +00:00
public final class GeyserServer {
2022-10-30 22:00:08 +00:00
private static final boolean PRINT_DEBUG_PINGS = Boolean . parseBoolean ( System . getProperty ( " Geyser.PrintPingsInDebugMode " , " true " ) ) ;
/ *
The following constants are all used to ensure the ping does not reach a length where it is unparsable by the Bedrock client
* /
private static final int MINECRAFT_VERSION_BYTES_LENGTH = GameProtocol . DEFAULT_BEDROCK_CODEC . getMinecraftVersion ( ) . getBytes ( StandardCharsets . UTF_8 ) . length ;
private static final int BRAND_BYTES_LENGTH = GeyserImpl . NAME . getBytes ( StandardCharsets . UTF_8 ) . length ;
/ * *
* The MOTD , sub - MOTD and Minecraft version ( { @link # MINECRAFT_VERSION_BYTES_LENGTH } ) combined cannot reach this length .
* /
private static final int MAGIC_RAKNET_LENGTH = 338 ;
private static final Transport TRANSPORT = compatibleTransport ( ) ;
2022-10-30 00:23:21 +00:00
private final GeyserImpl geyser ;
private final EventLoopGroup group ;
private final ServerBootstrap bootstrap ;
private ChannelFuture future ;
public GeyserServer ( GeyserImpl geyser , int threadCount ) {
this . geyser = geyser ;
2022-10-30 22:00:08 +00:00
this . group = TRANSPORT . eventLoopGroupFactory ( ) . apply ( threadCount ) ;
2022-10-30 00:23:21 +00:00
2022-10-30 22:00:08 +00:00
this . bootstrap = this . createBootstrap ( this . group ) ;
2022-10-30 00:23:21 +00:00
}
2023-04-07 01:47:37 +00:00
public CompletableFuture < Void > bind ( InetSocketAddress address ) {
CompletableFuture < Void > future = new CompletableFuture < > ( ) ;
this . future = this . bootstrap . bind ( address ) . addListener ( bindResult - > {
if ( bindResult . cause ( ) ! = null ) {
future . completeExceptionally ( bindResult . cause ( ) ) ;
return ;
}
future . complete ( null ) ;
} ) ;
2022-10-30 22:00:08 +00:00
// Add our ping handler
2023-04-07 01:47:37 +00:00
this . future . channel ( ) . pipeline ( ) . addAfter ( RakServerOfflineHandler . NAME , RakPingHandler . NAME , new RakPingHandler ( this ) ) ;
return future ;
2022-10-30 00:23:21 +00:00
}
public void shutdown ( ) {
this . group . shutdownGracefully ( ) ;
this . future . channel ( ) . closeFuture ( ) . syncUninterruptibly ( ) ;
}
private ServerBootstrap createBootstrap ( EventLoopGroup group ) {
2022-10-30 22:00:08 +00:00
// TODO
boolean enableProxyProtocol = this . geyser . getConfig ( ) . getBedrock ( ) . isEnableProxyProtocol ( ) ;
if ( this . geyser . getConfig ( ) . isDebugMode ( ) ) {
this . geyser . getLogger ( ) . debug ( " EventLoop type: " + TRANSPORT . datagramChannel ( ) ) ;
if ( TRANSPORT . datagramChannel ( ) = = NioDatagramChannel . class ) {
if ( System . getProperties ( ) . contains ( " disableNativeEventLoop " ) ) {
this . geyser . getLogger ( ) . debug ( " EventLoop type is NIO because native event loops are disabled. " ) ;
} else {
this . geyser . getLogger ( ) . debug ( " Reason for no Epoll: " + Epoll . unavailabilityCause ( ) . toString ( ) ) ;
this . geyser . getLogger ( ) . debug ( " Reason for no KQueue: " + KQueue . unavailabilityCause ( ) . toString ( ) ) ;
}
}
}
2022-10-30 00:23:21 +00:00
return new ServerBootstrap ( )
2022-10-30 22:00:08 +00:00
. channelFactory ( RakChannelFactory . server ( TRANSPORT . datagramChannel ( ) ) )
2022-10-30 00:23:21 +00:00
. group ( group )
2022-10-30 22:00:08 +00:00
. option ( RakChannelOption . RAK_HANDLE_PING , true )
2022-10-30 00:23:21 +00:00
. childHandler ( new GeyserServerInitializer ( this . geyser ) ) ;
}
2022-10-30 03:02:11 +00:00
2022-10-30 22:00:08 +00:00
public BedrockPong onQuery ( InetSocketAddress inetSocketAddress ) {
if ( geyser . getConfig ( ) . isDebugMode ( ) & & PRINT_DEBUG_PINGS ) {
String ip = geyser . getConfig ( ) . isLogPlayerIpAddresses ( ) ? inetSocketAddress . toString ( ) : " <IP address withheld> " ;
geyser . getLogger ( ) . debug ( GeyserLocale . getLocaleStringLog ( " geyser.network.pinged " , ip ) ) ;
}
GeyserConfiguration config = geyser . getConfig ( ) ;
GeyserPingInfo pingInfo = null ;
if ( config . isPassthroughMotd ( ) | | config . isPassthroughPlayerCounts ( ) ) {
IGeyserPingPassthrough pingPassthrough = geyser . getBootstrap ( ) . getGeyserPingPassthrough ( ) ;
pingInfo = pingPassthrough . getPingInformation ( inetSocketAddress ) ;
}
BedrockPong pong = new BedrockPong ( )
2022-10-30 03:02:11 +00:00
. edition ( " MCPE " )
. gameType ( " Survival " ) // Can only be Survival or Creative as of 1.16.210.59
. nintendoLimited ( false )
. protocolVersion ( GameProtocol . DEFAULT_BEDROCK_CODEC . getProtocolVersion ( ) )
. version ( GameProtocol . DEFAULT_BEDROCK_CODEC . getMinecraftVersion ( ) ) // Required to not be empty as of 1.16.210.59. Can only contain . and numbers.
2022-10-30 22:00:08 +00:00
. ipv4Port ( this . geyser . getConfig ( ) . getBedrock ( ) . port ( ) ) ;
if ( config . isPassthroughMotd ( ) & & pingInfo ! = null & & pingInfo . getDescription ( ) ! = null ) {
String [ ] motd = MessageTranslator . convertMessageLenient ( pingInfo . getDescription ( ) ) . split ( " \ n " ) ;
String mainMotd = motd [ 0 ] ; // First line of the motd.
String subMotd = ( motd . length ! = 1 ) ? motd [ 1 ] : GeyserImpl . NAME ; // Second line of the motd if present, otherwise default.
pong . motd ( mainMotd . trim ( ) ) ;
pong . subMotd ( subMotd . trim ( ) ) ; // Trimmed to shift it to the left, prevents the universe from collapsing on us just because we went 2 characters over the text box's limit.
} else {
pong . motd ( config . getBedrock ( ) . primaryMotd ( ) ) ;
pong . subMotd ( config . getBedrock ( ) . secondaryMotd ( ) ) ;
}
2022-12-21 00:47:45 +00:00
// https://github.com/GeyserMC/Geyser/issues/3388
pong . motd ( pong . motd ( ) . replace ( ';' , ':' ) ) ;
pong . subMotd ( pong . subMotd ( ) . replace ( ';' , ':' ) ) ;
2022-10-30 22:00:08 +00:00
// Fallbacks to prevent errors and allow Bedrock to see the server
if ( pong . motd ( ) = = null | | pong . motd ( ) . isBlank ( ) ) {
pong . motd ( GeyserImpl . NAME ) ;
}
if ( pong . subMotd ( ) = = null | | pong . subMotd ( ) . isBlank ( ) ) {
// Sub-MOTD cannot be empty as of 1.16.210.59
pong . subMotd ( GeyserImpl . NAME ) ;
}
// The ping will not appear if the MOTD + sub-MOTD is of a certain length.
// We don't know why, though
byte [ ] motdArray = pong . motd ( ) . getBytes ( StandardCharsets . UTF_8 ) ;
int subMotdLength = pong . subMotd ( ) . getBytes ( StandardCharsets . UTF_8 ) . length ;
if ( motdArray . length + subMotdLength > ( MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH ) ) {
// Shorten the sub-MOTD first since that only appears locally
if ( subMotdLength > BRAND_BYTES_LENGTH ) {
pong . subMotd ( GeyserImpl . NAME ) ;
subMotdLength = BRAND_BYTES_LENGTH ;
}
if ( motdArray . length > ( MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength ) ) {
// If the top MOTD is still too long, we chop it down
byte [ ] newMotdArray = new byte [ MAGIC_RAKNET_LENGTH - MINECRAFT_VERSION_BYTES_LENGTH - subMotdLength ] ;
System . arraycopy ( motdArray , 0 , newMotdArray , 0 , newMotdArray . length ) ;
pong . motd ( new String ( newMotdArray , StandardCharsets . UTF_8 ) ) ;
}
}
2022-12-21 00:47:45 +00:00
if ( config . isPassthroughPlayerCounts ( ) & & pingInfo ! = null ) {
pong . playerCount ( pingInfo . getPlayers ( ) . getOnline ( ) ) ;
pong . maximumPlayerCount ( pingInfo . getPlayers ( ) . getMax ( ) ) ;
} else {
pong . playerCount ( geyser . getSessionManager ( ) . getSessions ( ) . size ( ) ) ;
pong . maximumPlayerCount ( config . getMaxPlayers ( ) ) ;
}
2022-10-30 22:00:08 +00:00
//Bedrock will not even attempt a connection if the client thinks the server is full
//so we have to fake it not being full
if ( pong . playerCount ( ) > = pong . maximumPlayerCount ( ) ) {
pong . maximumPlayerCount ( pong . playerCount ( ) + 1 ) ;
}
return pong ;
}
private static Transport compatibleTransport ( ) {
TransportHelper . TransportMethod transportMethod = TransportHelper . determineTransportMethod ( ) ;
if ( transportMethod = = TransportHelper . TransportMethod . EPOLL ) {
return new Transport ( EpollDatagramChannel . class , EpollEventLoopGroup : : new ) ;
}
if ( transportMethod = = TransportHelper . TransportMethod . KQUEUE ) {
return new Transport ( KQueueDatagramChannel . class , KQueueEventLoopGroup : : new ) ;
}
// if (transportMethod == TransportHelper.TransportMethod.IO_URING) {
// return new Transport(IOUringDatagramChannel.class, IOUringEventLoopGroup::new);
// }
return new Transport ( NioDatagramChannel . class , NioEventLoopGroup : : new ) ;
}
2023-04-07 01:47:37 +00:00
private record Transport ( Class < ? extends DatagramChannel > datagramChannel , IntFunction < EventLoopGroup > eventLoopGroupFactory ) {
2022-10-30 03:02:11 +00:00
}
2022-10-30 00:23:21 +00:00
}