2021-11-20 21:34:30 +00:00
/ *
2022-01-01 19:03:05 +00:00
* Copyright ( c ) 2019 - 2022 GeyserMC . http : //geysermc.org
2021-11-20 21:34:30 +00:00
*
* 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 ;
import com.fasterxml.jackson.core.JsonParser ;
2022-03-03 23:52:26 +00:00
import com.fasterxml.jackson.core.type.TypeReference ;
2021-11-20 21:34:30 +00:00
import com.fasterxml.jackson.databind.DeserializationFeature ;
import com.fasterxml.jackson.databind.ObjectMapper ;
import com.github.steveice10.packetlib.tcp.TcpSession ;
import io.netty.channel.epoll.Epoll ;
import io.netty.util.NettyRuntime ;
import io.netty.util.concurrent.DefaultThreadFactory ;
import io.netty.util.internal.SystemPropertyUtil ;
2022-03-03 23:52:26 +00:00
import lombok.AccessLevel ;
2021-11-20 21:34:30 +00:00
import lombok.Getter ;
import lombok.Setter ;
2022-08-22 01:22:15 +00:00
import net.kyori.adventure.text.Component ;
import net.kyori.adventure.text.format.NamedTextColor ;
2022-08-11 23:01:26 +00:00
import org.checkerframework.checker.nullness.qual.MonotonicNonNull ;
2021-11-21 18:36:42 +00:00
import org.checkerframework.checker.nullness.qual.NonNull ;
import org.checkerframework.checker.nullness.qual.Nullable ;
2024-01-24 21:20:30 +00:00
import org.cloudburstmc.protocol.bedrock.codec.BedrockCodec ;
2021-11-21 18:36:42 +00:00
import org.geysermc.api.Geyser ;
2022-08-11 23:01:26 +00:00
import org.geysermc.cumulus.form.Form ;
import org.geysermc.cumulus.form.util.FormBuilder ;
2023-03-30 19:44:55 +00:00
import org.geysermc.erosion.packet.Packets ;
2023-05-05 16:22:31 +00:00
import org.geysermc.floodgate.core.FloodgatePlatform ;
2021-11-23 03:14:41 +00:00
import org.geysermc.geyser.api.GeyserApi ;
2024-02-19 21:25:49 +00:00
import org.geysermc.geyser.api.command.CommandSource ;
2022-01-16 04:54:08 +00:00
import org.geysermc.geyser.api.event.EventBus ;
2022-09-04 21:11:08 +00:00
import org.geysermc.geyser.api.event.EventRegistrar ;
2024-02-14 11:50:50 +00:00
import org.geysermc.geyser.api.event.lifecycle.* ;
2022-06-08 12:09:14 +00:00
import org.geysermc.geyser.api.network.AuthType ;
2022-03-20 03:30:12 +00:00
import org.geysermc.geyser.api.network.BedrockListener ;
2022-03-20 02:55:29 +00:00
import org.geysermc.geyser.api.network.RemoteServer ;
2024-02-19 21:25:49 +00:00
import org.geysermc.geyser.api.util.MinecraftVersion ;
import org.geysermc.geyser.api.util.PlatformType ;
2022-01-16 21:09:53 +00:00
import org.geysermc.geyser.command.GeyserCommandManager ;
2021-11-20 21:34:30 +00:00
import org.geysermc.geyser.configuration.GeyserConfiguration ;
import org.geysermc.geyser.entity.EntityDefinitions ;
2023-03-30 19:44:55 +00:00
import org.geysermc.geyser.erosion.UnixSocketClientListener ;
2022-01-16 04:54:08 +00:00
import org.geysermc.geyser.event.GeyserEventBus ;
2022-01-10 19:01:36 +00:00
import org.geysermc.geyser.extension.GeyserExtensionManager ;
2023-09-30 17:41:35 +00:00
import org.geysermc.geyser.floodgate.FloodgateProvider ;
2023-10-12 17:54:22 +00:00
import org.geysermc.geyser.floodgate.IntegratedFloodgateProvider ;
2023-09-30 17:41:35 +00:00
import org.geysermc.geyser.floodgate.NoFloodgateProvider ;
2023-10-13 14:47:55 +00:00
import org.geysermc.geyser.floodgate.ProxyFloodgateProvider ;
2024-01-24 21:20:30 +00:00
import org.geysermc.geyser.impl.MinecraftVersionImpl ;
2021-11-20 23:29:46 +00:00
import org.geysermc.geyser.level.WorldManager ;
2024-01-24 21:20:30 +00:00
import org.geysermc.geyser.network.GameProtocol ;
2022-10-30 00:23:21 +00:00
import org.geysermc.geyser.network.netty.GeyserServer ;
2021-11-20 21:34:30 +00:00
import org.geysermc.geyser.registry.BlockRegistries ;
import org.geysermc.geyser.registry.Registries ;
2023-12-05 23:54:42 +00:00
import org.geysermc.geyser.registry.provider.ProviderSupplier ;
2021-11-20 21:34:30 +00:00
import org.geysermc.geyser.scoreboard.ScoreboardUpdater ;
2021-11-22 19:52:26 +00:00
import org.geysermc.geyser.session.GeyserSession ;
2022-02-26 20:45:56 +00:00
import org.geysermc.geyser.session.PendingMicrosoftAuthentication ;
2021-11-22 19:52:26 +00:00
import org.geysermc.geyser.session.SessionManager ;
2022-11-07 02:32:55 +00:00
import org.geysermc.geyser.skin.BedrockSkinUploader ;
2022-12-18 18:18:06 +00:00
import org.geysermc.geyser.skin.ProvidedSkins ;
2022-01-30 22:06:45 +00:00
import org.geysermc.geyser.skin.SkinProvider ;
2021-11-22 19:52:26 +00:00
import org.geysermc.geyser.text.GeyserLocale ;
import org.geysermc.geyser.text.MinecraftLocale ;
import org.geysermc.geyser.translator.text.MessageTranslator ;
2021-11-20 23:29:46 +00:00
import org.geysermc.geyser.util.* ;
2021-11-20 21:34:30 +00:00
2022-03-03 23:52:26 +00:00
import java.io.File ;
import java.io.FileWriter ;
import java.io.IOException ;
2021-11-20 21:34:30 +00:00
import java.net.InetAddress ;
import java.net.InetSocketAddress ;
import java.net.UnknownHostException ;
2023-06-17 01:39:53 +00:00
import java.nio.file.Path ;
2021-11-20 21:34:30 +00:00
import java.text.DecimalFormat ;
2021-11-22 19:52:26 +00:00
import java.util.* ;
2022-12-18 18:18:06 +00:00
import java.util.concurrent.CompletableFuture ;
2022-03-03 23:52:26 +00:00
import java.util.concurrent.ConcurrentHashMap ;
2021-11-20 21:34:30 +00:00
import java.util.concurrent.Executors ;
import java.util.concurrent.ScheduledExecutorService ;
import java.util.regex.Matcher ;
import java.util.regex.Pattern ;
@Getter
2021-11-23 03:14:41 +00:00
public class GeyserImpl implements GeyserApi {
2021-11-20 21:34:30 +00:00
public static final ObjectMapper JSON_MAPPER = new ObjectMapper ( )
. enable ( JsonParser . Feature . IGNORE_UNDEFINED )
. enable ( JsonParser . Feature . ALLOW_COMMENTS )
. disable ( DeserializationFeature . FAIL_ON_UNKNOWN_PROPERTIES )
. enable ( JsonParser . Feature . ALLOW_UNQUOTED_FIELD_NAMES )
. enable ( JsonParser . Feature . ALLOW_SINGLE_QUOTES ) ;
public static final String NAME = " Geyser " ;
2024-01-24 21:20:30 +00:00
public static final String GIT_VERSION = " ${gitVersion} " ;
public static final String VERSION = " ${version} " ;
2022-02-27 22:38:55 +00:00
2022-07-02 17:42:31 +00:00
public static final String BUILD_NUMBER = " ${buildNumber} " ;
2022-02-27 22:38:55 +00:00
public static final String BRANCH = " ${branch} " ;
2022-09-11 23:26:22 +00:00
public static final String COMMIT = " ${commit} " ;
public static final String REPOSITORY = " ${repository} " ;
2021-11-20 21:34:30 +00:00
/ * *
* Oauth client ID for Microsoft authentication
* /
public static final String OAUTH_CLIENT_ID = " 204cefd1-4818-4de1-b98d-513fae875d88 " ;
private static final String IP_REGEX = " \\ b \\ d{1,3} \\ . \\ d{1,3} \\ . \\ d{1,3} \\ . \\ d{1,3} \\ b " ;
private final SessionManager sessionManager = new SessionManager ( ) ;
/ * *
* This is used in GeyserConnect to stop the bedrock server binding to a port
* /
@Setter
private static boolean shouldStartListener = true ;
2023-09-30 17:41:35 +00:00
private final FloodgateProvider floodgateProvider ;
2022-11-07 02:32:55 +00:00
private BedrockSkinUploader skinUploader ;
2021-11-20 21:34:30 +00:00
2023-03-30 19:44:55 +00:00
private UnixSocketClientListener erosionUnixListener ;
2024-02-14 11:50:50 +00:00
@Setter
2021-11-20 21:34:30 +00:00
private volatile boolean shuttingDown = false ;
2021-12-29 04:28:52 +00:00
private ScheduledExecutorService scheduledThread ;
2021-11-20 21:34:30 +00:00
2022-10-30 00:23:21 +00:00
private GeyserServer geyserServer ;
2021-11-20 21:34:30 +00:00
private final PlatformType platformType ;
private final GeyserBootstrap bootstrap ;
2022-09-04 21:11:08 +00:00
private final EventBus < EventRegistrar > eventBus ;
2022-09-14 18:19:56 +00:00
private final GeyserExtensionManager extensionManager ;
2022-03-20 02:55:29 +00:00
2021-12-29 04:28:52 +00:00
private Metrics metrics ;
2021-11-20 21:34:30 +00:00
2022-02-26 20:45:56 +00:00
private PendingMicrosoftAuthentication pendingMicrosoftAuthentication ;
2022-03-03 23:52:26 +00:00
@Getter ( AccessLevel . NONE )
private Map < String , String > savedRefreshTokens ;
2022-02-26 20:45:56 +00:00
2024-02-14 11:50:50 +00:00
@Getter
2021-11-21 18:36:42 +00:00
private static GeyserImpl instance ;
2024-02-14 11:50:50 +00:00
/ * *
* Determines if we ' re currently reloading . Replaces per - bootstrap reload checks
* /
private volatile boolean isReloading ;
2024-02-19 21:25:49 +00:00
/ * *
* Determines if Geyser is currently enabled . This is used to determine if { @link # disable ( ) } should be called during { @link # shutdown ( ) } .
* /
@Setter
private boolean isEnabled ;
2022-11-10 20:14:07 +00:00
private GeyserImpl ( PlatformType platformType , GeyserBootstrap bootstrap , FloodgatePlatform floodgatePlatform ) {
2021-11-21 18:36:42 +00:00
instance = this ;
2022-11-10 20:14:07 +00:00
if ( floodgatePlatform ! = null ) {
floodgatePlatform . load ( ) ;
floodgatePlatform . enable ( ) ;
2023-09-30 17:41:35 +00:00
// this.floodgatePlatform = floodgatePlatform.isProxy() ? new ProxyFloodgateProvider(floodgatePlatform) : new IntegratedFloodgateProvider(floodgatePlatform);
2023-10-12 17:54:22 +00:00
this . floodgateProvider = new IntegratedFloodgateProvider ( floodgatePlatform ) ;
// this.floodgateProvider = new ProxyFloodgateProvider(floodgatePlatform);
2022-11-10 20:14:07 +00:00
} else {
2023-10-13 14:47:55 +00:00
if ( bootstrap . getGeyserConfig ( ) . getRemote ( ) . authType ( ) = = AuthType . FLOODGATE ) {
this . floodgateProvider = new ProxyFloodgateProvider ( bootstrap . getConfigFolder ( ) ) ;
} else {
this . floodgateProvider = new NoFloodgateProvider ( ) ;
}
2022-11-10 20:14:07 +00:00
Geyser . set ( this ) ;
}
2021-11-21 18:36:42 +00:00
2021-11-20 21:34:30 +00:00
this . platformType = platformType ;
2021-12-29 04:28:52 +00:00
this . bootstrap = bootstrap ;
2022-09-04 21:11:08 +00:00
/* Initialize event bus */
this . eventBus = new GeyserEventBus ( ) ;
2024-02-14 11:50:50 +00:00
/* Create Extension Manager */
2022-09-04 21:11:08 +00:00
this . extensionManager = new GeyserExtensionManager ( ) ;
2024-02-14 11:50:50 +00:00
/* Finalize locale loading now that we know the default locale from the config */
GeyserLocale . finalizeDefaultLocale ( this ) ;
/* Load Extensions */
2022-09-04 21:11:08 +00:00
this . extensionManager . init ( ) ;
2022-09-14 18:19:56 +00:00
this . eventBus . fire ( new GeyserPreInitializeEvent ( this . extensionManager , this . eventBus ) ) ;
2022-09-04 21:11:08 +00:00
}
public void initialize ( ) {
2021-12-29 04:28:52 +00:00
long startupTime = System . currentTimeMillis ( ) ;
2021-11-20 21:34:30 +00:00
2021-12-29 04:28:52 +00:00
GeyserLogger logger = bootstrap . getGeyserLogger ( ) ;
2021-11-20 21:34:30 +00:00
logger . info ( " ****************************************** " ) ;
logger . info ( " " ) ;
2021-11-20 23:29:46 +00:00
logger . info ( GeyserLocale . getLocaleStringLog ( " geyser.core.load " , NAME , VERSION ) ) ;
2021-11-20 21:34:30 +00:00
logger . info ( " " ) ;
logger . info ( " ****************************************** " ) ;
2022-07-02 16:50:16 +00:00
/* Initialize registries */
Registries . init ( ) ;
BlockRegistries . init ( ) ;
/* Initialize translators */
EntityDefinitions . init ( ) ;
MessageTranslator . init ( ) ;
2022-12-18 18:18:06 +00:00
// Download the latest asset list and cache it
AssetUtils . generateAssetCache ( ) . whenComplete ( ( aVoid , ex ) - > {
if ( ex ! = null ) {
return ;
}
2024-02-23 16:58:39 +00:00
2022-12-18 18:18:06 +00:00
MinecraftLocale . ensureEN_US ( ) ;
String locale = GeyserLocale . getDefaultLocale ( ) ;
if ( ! " en_us " . equals ( locale ) ) {
// English will be loaded after assets are downloaded, if necessary
MinecraftLocale . downloadAndLoadLocale ( locale ) ;
}
ProvidedSkins . init ( ) ;
CompletableFuture . runAsync ( AssetUtils : : downloadAndRunClientJarTasks ) ;
} ) ;
2022-07-02 16:50:16 +00:00
2022-09-04 21:11:08 +00:00
startInstance ( ) ;
2021-12-29 04:28:52 +00:00
GeyserConfiguration config = bootstrap . getGeyserConfig ( ) ;
double completeTime = ( System . currentTimeMillis ( ) - startupTime ) / 1000D ;
2023-06-26 18:44:25 +00:00
String message = GeyserLocale . getLocaleStringLog ( " geyser.core.finish.done " , new DecimalFormat ( " #.### " ) . format ( completeTime ) ) ;
message + = " " + GeyserLocale . getLocaleStringLog ( " geyser.core.finish.console " ) ;
2021-12-29 04:28:52 +00:00
logger . info ( message ) ;
if ( platformType = = PlatformType . STANDALONE ) {
2022-12-30 21:24:16 +00:00
if ( config . getRemote ( ) . authType ( ) ! = AuthType . FLOODGATE ) {
// If the auth-type is Floodgate, then this Geyser instance is probably owned by the Java server
logger . warning ( GeyserLocale . getLocaleStringLog ( " geyser.core.movement_warn " ) ) ;
}
2022-07-09 22:39:02 +00:00
} else if ( config . getRemote ( ) . authType ( ) = = AuthType . FLOODGATE ) {
2021-12-29 04:28:52 +00:00
VersionCheckUtils . checkForOutdatedFloodgate ( logger ) ;
}
2024-02-14 11:50:50 +00:00
VersionCheckUtils . checkForOutdatedJava ( logger ) ;
2021-12-29 04:28:52 +00:00
}
2022-09-04 21:11:08 +00:00
private void startInstance ( ) {
2021-12-29 04:28:52 +00:00
this . scheduledThread = Executors . newSingleThreadScheduledExecutor ( new DefaultThreadFactory ( " Geyser Scheduled Thread " ) ) ;
2024-02-14 11:50:50 +00:00
if ( isReloading ) {
// If we're reloading, the default locale in the config might have changed.
GeyserLocale . finalizeDefaultLocale ( this ) ;
}
2021-12-29 04:28:52 +00:00
GeyserLogger logger = bootstrap . getGeyserLogger ( ) ;
GeyserConfiguration config = bootstrap . getGeyserConfig ( ) ;
2021-11-20 21:34:30 +00:00
ScoreboardUpdater . init ( ) ;
2022-01-31 14:57:43 +00:00
SkinProvider . registerCacheImageTask ( this ) ;
2023-06-17 01:39:53 +00:00
Registries . RESOURCE_PACKS . load ( ) ;
2021-11-20 21:34:30 +00:00
2023-04-23 01:33:23 +00:00
String geyserUdpPort = System . getProperty ( " geyserUdpPort " , " " ) ;
String pluginUdpPort = geyserUdpPort . isEmpty ( ) ? System . getProperty ( " pluginUdpPort " , " " ) : geyserUdpPort ;
if ( " -1 " . equals ( pluginUdpPort ) ) {
throw new UnsupportedOperationException ( " This hosting/service provider does not support applications running on the UDP port " ) ;
}
boolean portPropertyApplied = false ;
String pluginUdpAddress = System . getProperty ( " geyserUdpAddress " , System . getProperty ( " pluginUdpAddress " , " " ) ) ;
if ( platformType ! = PlatformType . STANDALONE ) {
int javaPort = bootstrap . getServerPort ( ) ;
if ( config . getRemote ( ) . address ( ) . equals ( " auto " ) ) {
config . setAutoconfiguredRemote ( true ) ;
String serverAddress = bootstrap . getServerBindAddress ( ) ;
if ( ! serverAddress . isEmpty ( ) & & ! " 0.0.0.0 " . equals ( serverAddress ) ) {
config . getRemote ( ) . setAddress ( serverAddress ) ;
} else {
// Set the remote address to localhost since that is where we are always connecting
try {
config . getRemote ( ) . setAddress ( InetAddress . getLocalHost ( ) . getHostAddress ( ) ) ;
} catch ( UnknownHostException ex ) {
logger . debug ( " Unknown host when trying to find localhost. " ) ;
if ( config . isDebugMode ( ) ) {
ex . printStackTrace ( ) ;
}
config . getRemote ( ) . setAddress ( InetAddress . getLoopbackAddress ( ) . getHostAddress ( ) ) ;
}
}
if ( javaPort ! = - 1 ) {
config . getRemote ( ) . setPort ( javaPort ) ;
}
}
boolean forceMatchServerPort = " server " . equals ( pluginUdpPort ) ;
if ( ( config . getBedrock ( ) . isCloneRemotePort ( ) | | forceMatchServerPort ) & & javaPort ! = - 1 ) {
config . getBedrock ( ) . setPort ( javaPort ) ;
if ( forceMatchServerPort ) {
if ( geyserUdpPort . isEmpty ( ) ) {
logger . info ( " Port set from system generic property to match Java server. " ) ;
} else {
logger . info ( " Port set from system property to match Java server. " ) ;
}
portPropertyApplied = true ;
}
}
if ( " server " . equals ( pluginUdpAddress ) ) {
String address = bootstrap . getServerBindAddress ( ) ;
if ( ! address . isEmpty ( ) ) {
config . getBedrock ( ) . setAddress ( address ) ;
}
} else if ( ! pluginUdpAddress . isEmpty ( ) ) {
config . getBedrock ( ) . setAddress ( pluginUdpAddress ) ;
}
if ( ! portPropertyApplied & & ! pluginUdpPort . isEmpty ( ) ) {
int port = Integer . parseInt ( pluginUdpPort ) ;
config . getBedrock ( ) . setPort ( port ) ;
if ( geyserUdpPort . isEmpty ( ) ) {
logger . info ( " Port set from generic system property: " + port ) ;
} else {
logger . info ( " Port set from system property: " + port ) ;
2021-11-20 21:34:30 +00:00
}
}
2023-04-26 16:52:12 +00:00
2024-01-24 21:28:03 +00:00
String broadcastPort = System . getProperty ( " geyserBroadcastPort " , " " ) ;
if ( ! broadcastPort . isEmpty ( ) ) {
int parsedPort ;
try {
parsedPort = Integer . parseInt ( broadcastPort ) ;
if ( parsedPort < 1 | | parsedPort > 65535 ) {
throw new NumberFormatException ( " The broadcast port must be between 1 and 65535 inclusive! " ) ;
}
} catch ( NumberFormatException e ) {
logger . error ( String . format ( " Invalid broadcast port: %s! Defaulting to configured port. " , broadcastPort + " ( " + e . getMessage ( ) + " ) " ) ) ;
parsedPort = config . getBedrock ( ) . port ( ) ;
}
config . getBedrock ( ) . setBroadcastPort ( parsedPort ) ;
logger . info ( " Broadcast port set from system property: " + parsedPort ) ;
}
2024-02-19 21:25:49 +00:00
if ( platformType ! = PlatformType . VIAPROXY ) {
2024-02-25 19:12:43 +00:00
boolean floodgatePresent = bootstrap . testFloodgatePluginPresent ( ) | | floodgateProvider ! = null ; //todo
2024-02-19 21:25:49 +00:00
if ( config . getRemote ( ) . authType ( ) = = AuthType . FLOODGATE & & ! floodgatePresent ) {
logger . severe ( GeyserLocale . getLocaleStringLog ( " geyser.bootstrap.floodgate.not_installed " ) + " "
+ GeyserLocale . getLocaleStringLog ( " geyser.bootstrap.floodgate.disabling " ) ) ;
return ;
} else if ( config . isAutoconfiguredRemote ( ) & & floodgatePresent ) {
// Floodgate installed means that the user wants Floodgate authentication
logger . debug ( " Auto-setting to Floodgate authentication. " ) ;
config . getRemote ( ) . setAuthType ( AuthType . FLOODGATE ) ;
}
2023-04-26 16:52:12 +00:00
}
2021-11-20 21:34:30 +00:00
}
2023-05-05 10:09:20 +00:00
//TODO end
2023-04-26 16:52:12 +00:00
2022-07-09 22:39:02 +00:00
String remoteAddress = config . getRemote ( ) . address ( ) ;
2021-11-20 21:34:30 +00:00
// Filters whether it is not an IP address or localhost, because otherwise it is not possible to find out an SRV entry.
if ( ! remoteAddress . matches ( IP_REGEX ) & & ! remoteAddress . equalsIgnoreCase ( " localhost " ) ) {
2022-07-20 23:59:03 +00:00
String [ ] record = WebUtils . findSrvRecord ( this , remoteAddress ) ;
if ( record ! = null ) {
int remotePort = Integer . parseInt ( record [ 2 ] ) ;
config . getRemote ( ) . setAddress ( remoteAddress = record [ 3 ] ) ;
config . getRemote ( ) . setPort ( remotePort ) ;
logger . debug ( " Found SRV record \" " + remoteAddress + " : " + remotePort + " \" " ) ;
2021-11-20 21:34:30 +00:00
}
}
// Ensure that PacketLib does not create an event loop for handling packets; we'll do that ourselves
TcpSession . USE_EVENT_LOOP_FOR_PACKETS = false ;
2022-02-26 20:45:56 +00:00
pendingMicrosoftAuthentication = new PendingMicrosoftAuthentication ( config . getPendingAuthenticationTimeout ( ) ) ;
2023-03-30 19:44:55 +00:00
Packets . initGeyser ( ) ;
if ( Epoll . isAvailable ( ) ) {
this . erosionUnixListener = new UnixSocketClientListener ( ) ;
} else {
logger . debug ( " Epoll is not available; Erosion's Unix socket handling will not work. " ) ;
}
2021-11-20 21:34:30 +00:00
CooldownUtils . setDefaultShowCooldown ( config . getShowCooldown ( ) ) ;
DimensionUtils . changeBedrockNetherId ( config . isAboveBedrockNetherBuilding ( ) ) ; // Apply End dimension ID workaround to Nether
Integer bedrockThreadCount = Integer . getInteger ( " Geyser.BedrockNetworkThreads " ) ;
if ( bedrockThreadCount = = null ) {
// Copy the code from Netty's default thread count fallback
bedrockThreadCount = Math . max ( 1 , SystemPropertyUtil . getInt ( " io.netty.eventLoopThreads " , NettyRuntime . availableProcessors ( ) * 2 ) ) ;
}
if ( shouldStartListener ) {
2023-04-07 01:47:37 +00:00
this . geyserServer = new GeyserServer ( this , bedrockThreadCount ) ;
this . geyserServer . bind ( new InetSocketAddress ( config . getBedrock ( ) . address ( ) , config . getBedrock ( ) . port ( ) ) )
. whenComplete ( ( avoid , throwable ) - > {
if ( throwable = = null ) {
logger . info ( GeyserLocale . getLocaleStringLog ( " geyser.core.start " , config . getBedrock ( ) . address ( ) ,
String . valueOf ( config . getBedrock ( ) . port ( ) ) ) ) ;
} else {
String address = config . getBedrock ( ) . address ( ) ;
int port = config . getBedrock ( ) . port ( ) ;
logger . severe ( GeyserLocale . getLocaleStringLog ( " geyser.core.fail " , address , String . valueOf ( port ) ) ) ;
if ( ! " 0.0.0.0 " . equals ( address ) ) {
logger . info ( Component . text ( " Suggestion: try setting `address` under `bedrock` in the Geyser config back to 0.0.0.0 " , NamedTextColor . GREEN ) ) ;
logger . info ( Component . text ( " Then, restart this server. " , NamedTextColor . GREEN ) ) ;
}
}
} ) . join ( ) ;
2021-11-20 21:34:30 +00:00
}
2022-07-09 22:39:02 +00:00
if ( config . getRemote ( ) . authType ( ) = = AuthType . FLOODGATE ) {
2022-05-24 23:16:40 +00:00
try {
// Note: this is positioned after the bind so the skin uploader doesn't try to run if Geyser fails
// to load successfully. Spigot complains about class loader if the plugin is disabled.
2022-11-07 02:32:55 +00:00
// TODO not Floodgate exclusive?
skinUploader = new BedrockSkinUploader ( this ) . start ( ) ;
2022-05-24 23:16:40 +00:00
} catch ( Exception exception ) {
2022-11-07 02:32:55 +00:00
logger . severe ( " Could not start the skin uploader! " , exception ) ;
2022-05-24 23:16:40 +00:00
}
}
2021-11-20 21:34:30 +00:00
if ( config . getMetrics ( ) . isEnabled ( ) ) {
metrics = new Metrics ( this , " GeyserMC " , config . getMetrics ( ) . getUniqueId ( ) , false , java . util . logging . Logger . getLogger ( " " ) ) ;
metrics . addCustomChart ( new Metrics . SingleLineChart ( " players " , sessionManager : : size ) ) ;
// Prevent unwanted words best we can
2022-07-09 22:39:02 +00:00
metrics . addCustomChart ( new Metrics . SimplePie ( " authMode " , ( ) - > config . getRemote ( ) . authType ( ) . toString ( ) . toLowerCase ( Locale . ROOT ) ) ) ;
2023-06-17 01:56:50 +00:00
metrics . addCustomChart ( new Metrics . SimplePie ( " platform " , platformType : : platformName ) ) ;
2021-11-20 23:29:46 +00:00
metrics . addCustomChart ( new Metrics . SimplePie ( " defaultLocale " , GeyserLocale : : getDefaultLocale ) ) ;
2021-11-20 21:34:30 +00:00
metrics . addCustomChart ( new Metrics . SimplePie ( " version " , ( ) - > GeyserImpl . VERSION ) ) ;
metrics . addCustomChart ( new Metrics . AdvancedPie ( " playerPlatform " , ( ) - > {
Map < String , Integer > valueMap = new HashMap < > ( ) ;
2021-11-22 19:52:26 +00:00
for ( GeyserSession session : sessionManager . getAllSessions ( ) ) {
2021-11-20 21:34:30 +00:00
if ( session = = null ) continue ;
if ( session . getClientData ( ) = = null ) continue ;
String os = session . getClientData ( ) . getDeviceOs ( ) . toString ( ) ;
if ( ! valueMap . containsKey ( os ) ) {
valueMap . put ( os , 1 ) ;
} else {
valueMap . put ( os , valueMap . get ( os ) + 1 ) ;
}
}
return valueMap ;
} ) ) ;
metrics . addCustomChart ( new Metrics . AdvancedPie ( " playerVersion " , ( ) - > {
Map < String , Integer > valueMap = new HashMap < > ( ) ;
2021-11-22 19:52:26 +00:00
for ( GeyserSession session : sessionManager . getAllSessions ( ) ) {
2021-11-20 21:34:30 +00:00
if ( session = = null ) continue ;
if ( session . getClientData ( ) = = null ) continue ;
String version = session . getClientData ( ) . getGameVersion ( ) ;
if ( ! valueMap . containsKey ( version ) ) {
valueMap . put ( version , 1 ) ;
} else {
valueMap . put ( version , valueMap . get ( version ) + 1 ) ;
}
}
return valueMap ;
} ) ) ;
String minecraftVersion = bootstrap . getMinecraftServerVersion ( ) ;
if ( minecraftVersion ! = null ) {
Map < String , Map < String , Integer > > versionMap = new HashMap < > ( ) ;
Map < String , Integer > platformMap = new HashMap < > ( ) ;
2023-06-17 01:56:50 +00:00
platformMap . put ( platformType . platformName ( ) , 1 ) ;
2021-11-20 21:34:30 +00:00
versionMap . put ( minecraftVersion , platformMap ) ;
metrics . addCustomChart ( new Metrics . DrilldownPie ( " minecraftServerVersion " , ( ) - > {
// By the end, we should return, for example:
// 1.16.5 => (Spigot, 1)
return versionMap ;
} ) ) ;
}
// The following code can be attributed to the PaperMC project
// https://github.com/PaperMC/Paper/blob/master/Spigot-Server-Patches/0005-Paper-Metrics.patch#L614
metrics . addCustomChart ( new Metrics . DrilldownPie ( " javaVersion " , ( ) - > {
Map < String , Map < String , Integer > > map = new HashMap < > ( ) ;
String javaVersion = System . getProperty ( " java.version " ) ;
Map < String , Integer > entry = new HashMap < > ( ) ;
entry . put ( javaVersion , 1 ) ;
// http://openjdk.java.net/jeps/223
// Java decided to change their versioning scheme and in doing so modified the
// java.version system property to return $major[.$minor][.$security][-ea], as opposed to
// 1.$major.0_$identifier we can handle pre-9 by checking if the "major" is equal to "1",
// otherwise, 9+
String majorVersion = javaVersion . split ( " \\ . " ) [ 0 ] ;
String release ;
int indexOf = javaVersion . lastIndexOf ( '.' ) ;
if ( majorVersion . equals ( " 1 " ) ) {
release = " Java " + javaVersion . substring ( 0 , indexOf ) ;
} else {
// of course, it really wouldn't be all that simple if they didn't add a quirk, now
// would it valid strings for the major may potentially include values such as -ea to
// denote a pre release
Matcher versionMatcher = Pattern . compile ( " \\ d+ " ) . matcher ( majorVersion ) ;
if ( versionMatcher . find ( ) ) {
majorVersion = versionMatcher . group ( 0 ) ;
}
release = " Java " + majorVersion ;
}
map . put ( release , entry ) ;
return map ;
} ) ) ;
} else {
metrics = null ;
}
2022-07-09 22:39:02 +00:00
if ( config . getRemote ( ) . authType ( ) = = AuthType . ONLINE ) {
2022-03-03 23:52:26 +00:00
// May be written/read to on multiple threads from each GeyserSession as well as writing the config
savedRefreshTokens = new ConcurrentHashMap < > ( ) ;
File tokensFile = bootstrap . getSavedUserLoginsFolder ( ) . resolve ( Constants . SAVED_REFRESH_TOKEN_FILE ) . toFile ( ) ;
if ( tokensFile . exists ( ) ) {
TypeReference < Map < String , String > > type = new TypeReference < > ( ) { } ;
Map < String , String > refreshTokenFile = null ;
try {
refreshTokenFile = JSON_MAPPER . readValue ( tokensFile , type ) ;
} catch ( IOException e ) {
logger . error ( " Cannot load saved user tokens! " , e ) ;
}
if ( refreshTokenFile ! = null ) {
List < String > validUsers = config . getSavedUserLogins ( ) ;
boolean doWrite = false ;
for ( Map . Entry < String , String > entry : refreshTokenFile . entrySet ( ) ) {
String user = entry . getKey ( ) ;
if ( ! validUsers . contains ( user ) ) {
// Perform a write to this file to purge the now-unused name
doWrite = true ;
continue ;
}
savedRefreshTokens . put ( user , entry . getValue ( ) ) ;
}
if ( doWrite ) {
scheduleRefreshTokensWrite ( ) ;
}
}
}
} else {
savedRefreshTokens = null ;
}
2024-02-14 11:50:50 +00:00
if ( isReloading ) {
this . eventBus . fire ( new GeyserPostReloadEvent ( this . extensionManager , this . eventBus ) ) ;
} else {
this . eventBus . fire ( new GeyserPostInitializeEvent ( this . extensionManager , this . eventBus ) ) ;
}
2022-08-22 01:22:15 +00:00
if ( config . isNotifyOnNewBedrockUpdate ( ) ) {
VersionCheckUtils . checkForGeyserUpdate ( this : : getLogger ) ;
}
2021-11-20 21:34:30 +00:00
}
2021-11-21 18:36:42 +00:00
@Override
2021-11-22 19:52:26 +00:00
public @NonNull List < GeyserSession > onlineConnections ( ) {
2022-08-09 18:06:53 +00:00
return sessionManager . getAllSessions ( ) ;
}
@Override
public int onlineConnectionsCount ( ) {
return sessionManager . size ( ) ;
2021-11-21 18:36:42 +00:00
}
2022-08-11 23:01:26 +00:00
@Override
public @MonotonicNonNull String usernamePrefix ( ) {
return null ;
}
2021-11-21 18:36:42 +00:00
@Override
2021-11-22 19:52:26 +00:00
public @Nullable GeyserSession connectionByUuid ( @NonNull UUID uuid ) {
2021-11-21 18:36:42 +00:00
return this . sessionManager . getSessions ( ) . get ( uuid ) ;
}
@Override
2021-11-22 19:52:26 +00:00
public @Nullable GeyserSession connectionByXuid ( @NonNull String xuid ) {
2022-08-11 23:01:26 +00:00
return sessionManager . sessionByXuid ( xuid ) ;
}
@Override
public boolean isBedrockPlayer ( @NonNull UUID uuid ) {
return connectionByUuid ( uuid ) ! = null ;
}
@Override
public boolean sendForm ( @NonNull UUID uuid , @NonNull Form form ) {
Objects . requireNonNull ( uuid ) ;
Objects . requireNonNull ( form ) ;
GeyserSession session = connectionByUuid ( uuid ) ;
if ( session = = null ) {
return false ;
2021-11-21 18:36:42 +00:00
}
2022-08-11 23:01:26 +00:00
return session . sendForm ( form ) ;
}
2021-11-21 18:36:42 +00:00
2022-08-11 23:01:26 +00:00
@Override
public boolean sendForm ( @NonNull UUID uuid , @NonNull FormBuilder < ? , ? , ? > formBuilder ) {
return sendForm ( uuid , formBuilder . build ( ) ) ;
}
@Override
public boolean transfer ( @NonNull UUID uuid , @NonNull String address , int port ) {
Objects . requireNonNull ( uuid ) ;
GeyserSession session = connectionByUuid ( uuid ) ;
if ( session = = null ) {
return false ;
}
return session . transfer ( address , port ) ;
2021-11-21 18:36:42 +00:00
}
2024-02-14 11:50:50 +00:00
public void disable ( ) {
2021-11-20 23:29:46 +00:00
bootstrap . getGeyserLogger ( ) . info ( GeyserLocale . getLocaleStringLog ( " geyser.core.shutdown " ) ) ;
2021-11-20 21:34:30 +00:00
if ( sessionManager . size ( ) > = 1 ) {
2021-11-20 23:29:46 +00:00
bootstrap . getGeyserLogger ( ) . info ( GeyserLocale . getLocaleStringLog ( " geyser.core.shutdown.kick.log " , sessionManager . size ( ) ) ) ;
2021-11-20 21:34:30 +00:00
sessionManager . disconnectAll ( " geyser.core.shutdown.kick.message " ) ;
2021-11-20 23:29:46 +00:00
bootstrap . getGeyserLogger ( ) . info ( GeyserLocale . getLocaleStringLog ( " geyser.core.shutdown.kick.done " ) ) ;
2021-11-20 21:34:30 +00:00
}
scheduledThread . shutdown ( ) ;
2022-10-30 00:23:21 +00:00
geyserServer . shutdown ( ) ;
2021-11-20 21:34:30 +00:00
if ( skinUploader ! = null ) {
skinUploader . close ( ) ;
}
2023-03-30 19:44:55 +00:00
if ( this . erosionUnixListener ! = null ) {
this . erosionUnixListener . close ( ) ;
}
2023-06-17 01:39:53 +00:00
Registries . RESOURCE_PACKS . get ( ) . clear ( ) ;
2021-12-29 04:28:52 +00:00
2024-02-19 21:25:49 +00:00
this . setEnabled ( false ) ;
2024-02-14 11:50:50 +00:00
}
public void shutdown ( ) {
shuttingDown = true ;
2024-02-19 21:25:49 +00:00
if ( isEnabled ) {
this . disable ( ) ;
}
2024-02-14 11:50:50 +00:00
this . commandManager ( ) . getCommands ( ) . clear ( ) ;
// Disable extensions, fire the shutdown event
2022-09-04 18:08:17 +00:00
this . eventBus . fire ( new GeyserShutdownEvent ( this . extensionManager , this . eventBus ) ) ;
2022-01-15 22:27:35 +00:00
this . extensionManager . disableExtensions ( ) ;
2022-01-10 17:45:26 +00:00
2021-11-20 23:29:46 +00:00
bootstrap . getGeyserLogger ( ) . info ( GeyserLocale . getLocaleStringLog ( " geyser.core.shutdown.done " ) ) ;
2021-11-20 21:34:30 +00:00
}
2024-02-14 11:50:50 +00:00
public void reloadGeyser ( ) {
isReloading = true ;
this . eventBus . fire ( new GeyserPreReloadEvent ( this . extensionManager , this . eventBus ) ) ;
bootstrap . onGeyserDisable ( ) ;
bootstrap . onGeyserEnable ( ) ;
isReloading = false ;
2021-11-20 21:34:30 +00:00
}
/ * *
2021-11-21 18:36:42 +00:00
* Returns false if this Geyser instance is running in an IDE . This only needs to be used in cases where files
* expected to be in a jarfile are not present .
2021-11-20 21:34:30 +00:00
*
2021-11-21 18:36:42 +00:00
* @return true if the version number is not ' DEV ' .
2021-11-20 21:34:30 +00:00
* /
2022-02-27 22:38:55 +00:00
public boolean isProductionEnvironment ( ) {
2022-07-09 22:39:02 +00:00
// First is if Blossom runs, second is if Blossom doesn't run
2023-12-05 23:54:42 +00:00
//noinspection ConstantConditions,MismatchedStringCase - changes in production
2022-07-09 22:39:02 +00:00
return ! ( " git-local/dev-0000000 " . equals ( GeyserImpl . GIT_VERSION ) | | " ${gitVersion} " . equals ( GeyserImpl . GIT_VERSION ) ) ;
2021-11-20 21:34:30 +00:00
}
2022-01-15 22:27:35 +00:00
@Override
2022-09-04 18:08:17 +00:00
@NonNull
2022-01-15 22:27:35 +00:00
public GeyserExtensionManager extensionManager ( ) {
return this . extensionManager ;
}
2022-09-04 18:08:17 +00:00
@NonNull
2022-01-16 21:09:53 +00:00
public GeyserCommandManager commandManager ( ) {
return this . bootstrap . getGeyserCommandManager ( ) ;
}
2022-05-01 17:25:24 +00:00
@Override
2023-12-05 23:54:42 +00:00
@SuppressWarnings ( " unchecked " )
2022-07-09 22:39:02 +00:00
public < R extends T , T > @NonNull R provider ( @NonNull Class < T > apiClass , @Nullable Object . . . args ) {
2023-12-05 23:54:42 +00:00
ProviderSupplier provider = Registries . PROVIDERS . get ( apiClass ) ;
if ( provider = = null ) {
throw new IllegalArgumentException ( " No provider found for " + apiClass ) ;
}
return ( R ) provider . create ( args ) ;
2022-05-01 17:25:24 +00:00
}
2022-01-16 04:54:08 +00:00
@Override
2022-09-04 18:08:17 +00:00
@NonNull
2022-09-04 21:11:08 +00:00
public EventBus < EventRegistrar > eventBus ( ) {
2022-01-16 04:54:08 +00:00
return this . eventBus ;
}
2022-09-04 18:08:17 +00:00
@NonNull
2022-03-20 03:30:12 +00:00
public RemoteServer defaultRemoteServer ( ) {
2022-07-09 22:39:02 +00:00
return getConfig ( ) . getRemote ( ) ;
2022-03-20 03:30:12 +00:00
}
@Override
2022-09-04 18:08:17 +00:00
@NonNull
2022-03-20 03:30:12 +00:00
public BedrockListener bedrockListener ( ) {
2022-07-09 22:39:02 +00:00
return getConfig ( ) . getBedrock ( ) ;
2022-03-20 03:35:41 +00:00
}
2023-06-17 01:39:53 +00:00
@Override
@NonNull
public Path configDirectory ( ) {
return bootstrap . getConfigFolder ( ) ;
}
@Override
@NonNull
public Path packDirectory ( ) {
return bootstrap . getConfigFolder ( ) . resolve ( " packs " ) ;
}
2023-06-17 01:56:50 +00:00
@Override
@NonNull
public PlatformType platformType ( ) {
return platformType ;
}
2024-01-24 21:20:30 +00:00
@Override
public @NonNull MinecraftVersion supportedJavaVersion ( ) {
return new MinecraftVersionImpl ( GameProtocol . getJavaMinecraftVersion ( ) , GameProtocol . getJavaProtocolVersion ( ) ) ;
}
@Override
public @NonNull List < MinecraftVersion > supportedBedrockVersions ( ) {
ArrayList < MinecraftVersion > versions = new ArrayList < > ( ) ;
for ( BedrockCodec codec : GameProtocol . SUPPORTED_BEDROCK_CODECS ) {
versions . add ( new MinecraftVersionImpl ( codec . getMinecraftVersion ( ) , codec . getProtocolVersion ( ) ) ) ;
}
return Collections . unmodifiableList ( versions ) ;
}
@Override
public @NonNull CommandSource consoleCommandSource ( ) {
return getLogger ( ) ;
}
2022-07-02 17:42:31 +00:00
public int buildNumber ( ) {
if ( ! this . isProductionEnvironment ( ) ) {
return 0 ;
}
return Integer . parseInt ( BUILD_NUMBER ) ;
}
2022-11-10 20:14:07 +00:00
public static GeyserImpl load ( PlatformType platformType , GeyserBootstrap bootstrap , FloodgatePlatform floodgatePlatform ) {
2021-12-29 04:28:52 +00:00
if ( instance = = null ) {
2022-11-10 20:14:07 +00:00
return new GeyserImpl ( platformType , bootstrap , floodgatePlatform ) ;
2021-12-29 04:28:52 +00:00
}
2022-09-04 21:11:08 +00:00
return instance ;
}
public static void start ( ) {
if ( instance = = null ) {
throw new RuntimeException ( " Geyser has not been loaded yet! " ) ;
}
2024-02-14 11:50:50 +00:00
if ( getInstance ( ) . isReloading ( ) ) {
2022-09-04 21:11:08 +00:00
instance . startInstance ( ) ;
} else {
instance . initialize ( ) ;
2021-12-29 04:28:52 +00:00
}
2024-02-19 21:25:49 +00:00
instance . setEnabled ( true ) ;
2021-11-20 21:34:30 +00:00
}
public GeyserLogger getLogger ( ) {
return bootstrap . getGeyserLogger ( ) ;
}
public GeyserConfiguration getConfig ( ) {
return bootstrap . getGeyserConfig ( ) ;
}
public WorldManager getWorldManager ( ) {
return bootstrap . getWorldManager ( ) ;
}
2022-03-03 23:52:26 +00:00
@Nullable
public String refreshTokenFor ( @NonNull String bedrockName ) {
return savedRefreshTokens . get ( bedrockName ) ;
}
public void saveRefreshToken ( @NonNull String bedrockName , @NonNull String refreshToken ) {
if ( ! getConfig ( ) . getSavedUserLogins ( ) . contains ( bedrockName ) ) {
// Do not save this login
return ;
}
// We can safely overwrite old instances because MsaAuthenticationService#getLoginResponseFromRefreshToken
// refreshes the token for us
if ( ! Objects . equals ( refreshToken , savedRefreshTokens . put ( bedrockName , refreshToken ) ) ) {
scheduleRefreshTokensWrite ( ) ;
}
}
private void scheduleRefreshTokensWrite ( ) {
scheduledThread . execute ( ( ) - > {
// Ensure all writes are handled on the same thread
File savedTokens = getBootstrap ( ) . getSavedUserLoginsFolder ( ) . resolve ( Constants . SAVED_REFRESH_TOKEN_FILE ) . toFile ( ) ;
TypeReference < Map < String , String > > type = new TypeReference < > ( ) { } ;
try ( FileWriter writer = new FileWriter ( savedTokens ) ) {
JSON_MAPPER . writerFor ( type )
. withDefaultPrettyPrinter ( )
. writeValue ( writer , savedRefreshTokens ) ;
} catch ( IOException e ) {
getLogger ( ) . error ( " Unable to write saved refresh tokens! " , e ) ;
}
} ) ;
}
2021-11-20 21:34:30 +00:00
}