package net.minecraft.server.dedicated; import com.google.common.collect.Lists; import com.mojang.authlib.GameProfile; import com.mojang.datafixers.DataFixer; import com.mojang.logging.LogUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Writer; import java.net.InetAddress; import java.net.Proxy; import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.function.Supplier; import net.minecraft.DefaultUncaughtExceptionHandler; import net.minecraft.DefaultUncaughtExceptionHandlerWithName; import net.minecraft.SharedConstants; import net.minecraft.SystemReport; import net.minecraft.Util; import net.minecraft.commands.CommandSourceStack; import net.minecraft.core.BlockPos; import net.minecraft.server.ConsoleInput; import net.minecraft.server.MinecraftServer; import net.minecraft.server.ServerInterface; import net.minecraft.server.ServerLinks; import net.minecraft.server.Services; import net.minecraft.server.WorldStem; import net.minecraft.server.ServerLinks.KnownLinkType; import net.minecraft.server.gui.MinecraftServerGui; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.progress.ChunkProgressListenerFactory; import net.minecraft.server.network.ServerTextFilter; import net.minecraft.server.network.TextFilter; import net.minecraft.server.packs.repository.PackRepository; import net.minecraft.server.players.GameProfileCache; import net.minecraft.server.players.OldUsersConverter; import net.minecraft.server.rcon.RconConsoleSource; import net.minecraft.server.rcon.thread.QueryThreadGs4; import net.minecraft.server.rcon.thread.RconThread; import net.minecraft.util.Mth; import net.minecraft.util.debugchart.DebugSampleSubscriptionTracker; import net.minecraft.util.debugchart.RemoteDebugSampleType; import net.minecraft.util.debugchart.RemoteSampleLogger; import net.minecraft.util.debugchart.SampleLogger; import net.minecraft.util.debugchart.TpsDebugDimensions; import net.minecraft.util.monitoring.jmx.MinecraftServerStatistics; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.GameType; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.SkullBlockEntity; import net.minecraft.world.level.storage.LevelStorageSource; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public class DedicatedServer extends MinecraftServer implements ServerInterface { static final Logger LOGGER = LogUtils.getLogger(); private static final int CONVERSION_RETRY_DELAY_MS = 5000; private static final int CONVERSION_RETRIES = 2; private final List consoleInput = Collections.synchronizedList(Lists.newArrayList()); @Nullable private QueryThreadGs4 queryThreadGs4; private final RconConsoleSource rconConsoleSource; @Nullable private RconThread rconThread; private final DedicatedServerSettings settings; @Nullable private MinecraftServerGui gui; @Nullable private final ServerTextFilter serverTextFilter; @Nullable private RemoteSampleLogger tickTimeLogger; @Nullable private DebugSampleSubscriptionTracker debugSampleSubscriptionTracker; private final ServerLinks serverLinks; public DedicatedServer( Thread serverThread, LevelStorageSource.LevelStorageAccess storageSource, PackRepository packRepository, WorldStem worldStem, DedicatedServerSettings settings, DataFixer fixerUpper, Services services, ChunkProgressListenerFactory progressListenerFactory ) { super(serverThread, storageSource, packRepository, worldStem, Proxy.NO_PROXY, fixerUpper, services, progressListenerFactory); this.settings = settings; this.rconConsoleSource = new RconConsoleSource(this); this.serverTextFilter = ServerTextFilter.createFromConfig(settings.getProperties()); this.serverLinks = createServerLinks(settings); } @Override public boolean initServer() throws IOException { Thread thread = new Thread("Server console handler") { public void run() { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); String string; try { while (!DedicatedServer.this.isStopped() && DedicatedServer.this.isRunning() && (string = bufferedReader.readLine()) != null) { DedicatedServer.this.handleConsoleInput(string, DedicatedServer.this.createCommandSourceStack()); } } catch (IOException var4) { DedicatedServer.LOGGER.error("Exception handling console input", (Throwable)var4); } } }; thread.setDaemon(true); thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); thread.start(); LOGGER.info("Starting minecraft server version {}", SharedConstants.getCurrentVersion().getName()); if (Runtime.getRuntime().maxMemory() / 1024L / 1024L < 512L) { LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\""); } LOGGER.info("Loading properties"); DedicatedServerProperties dedicatedServerProperties = this.settings.getProperties(); if (this.isSingleplayer()) { this.setLocalIp("127.0.0.1"); } else { this.setUsesAuthentication(dedicatedServerProperties.onlineMode); this.setPreventProxyConnections(dedicatedServerProperties.preventProxyConnections); this.setLocalIp(dedicatedServerProperties.serverIp); } this.setPvpAllowed(dedicatedServerProperties.pvp); this.setFlightAllowed(dedicatedServerProperties.allowFlight); this.setMotd(dedicatedServerProperties.motd); super.setPlayerIdleTimeout(dedicatedServerProperties.playerIdleTimeout.get()); this.setEnforceWhitelist(dedicatedServerProperties.enforceWhitelist); this.worldData.setGameType(dedicatedServerProperties.gamemode); LOGGER.info("Default game type: {}", dedicatedServerProperties.gamemode); InetAddress inetAddress = null; if (!this.getLocalIp().isEmpty()) { inetAddress = InetAddress.getByName(this.getLocalIp()); } if (this.getPort() < 0) { this.setPort(dedicatedServerProperties.serverPort); } this.initializeKeyPair(); LOGGER.info("Starting Minecraft server on {}:{}", this.getLocalIp().isEmpty() ? "*" : this.getLocalIp(), this.getPort()); try { this.getConnection().startTcpServerListener(inetAddress, this.getPort()); } catch (IOException var10) { LOGGER.warn("**** FAILED TO BIND TO PORT!"); LOGGER.warn("The exception was: {}", var10.toString()); LOGGER.warn("Perhaps a server is already running on that port?"); return false; } if (!this.usesAuthentication()) { LOGGER.warn("**** SERVER IS RUNNING IN OFFLINE/INSECURE MODE!"); LOGGER.warn("The server will make no attempt to authenticate usernames. Beware."); LOGGER.warn( "While this makes the game possible to play without internet access, it also opens up the ability for hackers to connect with any username they choose." ); LOGGER.warn("To change this, set \"online-mode\" to \"true\" in the server.properties file."); } if (this.convertOldUsers()) { this.getProfileCache().save(); } if (!OldUsersConverter.serverReadyAfterUserconversion(this)) { return false; } else { this.setPlayerList(new DedicatedPlayerList(this, this.registries(), this.playerDataStorage)); this.debugSampleSubscriptionTracker = new DebugSampleSubscriptionTracker(this.getPlayerList()); this.tickTimeLogger = new RemoteSampleLogger(TpsDebugDimensions.values().length, this.debugSampleSubscriptionTracker, RemoteDebugSampleType.TICK_TIME); long l = Util.getNanos(); SkullBlockEntity.setup(this.services, this); GameProfileCache.setUsesAuthentication(this.usesAuthentication()); LOGGER.info("Preparing level \"{}\"", this.getLevelIdName()); this.loadLevel(); long m = Util.getNanos() - l; String string = String.format(Locale.ROOT, "%.3fs", m / 1.0E9); LOGGER.info("Done ({})! For help, type \"help\"", string); if (dedicatedServerProperties.announcePlayerAchievements != null) { this.getGameRules().getRule(GameRules.RULE_ANNOUNCE_ADVANCEMENTS).set(dedicatedServerProperties.announcePlayerAchievements, this); } if (dedicatedServerProperties.enableQuery) { LOGGER.info("Starting GS4 status listener"); this.queryThreadGs4 = QueryThreadGs4.create(this); } if (dedicatedServerProperties.enableRcon) { LOGGER.info("Starting remote control listener"); this.rconThread = RconThread.create(this); } if (this.getMaxTickLength() > 0L) { Thread thread2 = new Thread(new ServerWatchdog(this)); thread2.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandlerWithName(LOGGER)); thread2.setName("Server Watchdog"); thread2.setDaemon(true); thread2.start(); } if (dedicatedServerProperties.enableJmxMonitoring) { MinecraftServerStatistics.registerJmxMonitoring(this); LOGGER.info("JMX monitoring enabled"); } return true; } } @Override public boolean isSpawningMonsters() { return this.settings.getProperties().spawnMonsters && super.isSpawningMonsters(); } @Override public DedicatedServerProperties getProperties() { return this.settings.getProperties(); } @Override public void forceDifficulty() { this.setDifficulty(this.getProperties().difficulty, true); } @Override public SystemReport fillServerSystemReport(SystemReport report) { report.setDetail("Is Modded", (Supplier)(() -> this.getModdedStatus().fullDescription())); report.setDetail("Type", (Supplier)(() -> "Dedicated Server (map_server.txt)")); return report; } @Override public void dumpServerProperties(Path path) throws IOException { DedicatedServerProperties dedicatedServerProperties = this.getProperties(); Writer writer = Files.newBufferedWriter(path); try { writer.write(String.format(Locale.ROOT, "sync-chunk-writes=%s%n", dedicatedServerProperties.syncChunkWrites)); writer.write(String.format(Locale.ROOT, "gamemode=%s%n", dedicatedServerProperties.gamemode)); writer.write(String.format(Locale.ROOT, "spawn-monsters=%s%n", dedicatedServerProperties.spawnMonsters)); writer.write(String.format(Locale.ROOT, "entity-broadcast-range-percentage=%d%n", dedicatedServerProperties.entityBroadcastRangePercentage)); writer.write(String.format(Locale.ROOT, "max-world-size=%d%n", dedicatedServerProperties.maxWorldSize)); writer.write(String.format(Locale.ROOT, "view-distance=%d%n", dedicatedServerProperties.viewDistance)); writer.write(String.format(Locale.ROOT, "simulation-distance=%d%n", dedicatedServerProperties.simulationDistance)); writer.write(String.format(Locale.ROOT, "generate-structures=%s%n", dedicatedServerProperties.worldOptions.generateStructures())); writer.write(String.format(Locale.ROOT, "use-native=%s%n", dedicatedServerProperties.useNativeTransport)); writer.write(String.format(Locale.ROOT, "rate-limit=%d%n", dedicatedServerProperties.rateLimitPacketsPerSecond)); } catch (Throwable var7) { if (writer != null) { try { writer.close(); } catch (Throwable var6) { var7.addSuppressed(var6); } } throw var7; } if (writer != null) { writer.close(); } } @Override public void onServerExit() { if (this.serverTextFilter != null) { this.serverTextFilter.close(); } if (this.gui != null) { this.gui.close(); } if (this.rconThread != null) { this.rconThread.stop(); } if (this.queryThreadGs4 != null) { this.queryThreadGs4.stop(); } } @Override public void tickConnection() { super.tickConnection(); this.handleConsoleInputs(); } @Override public boolean isLevelEnabled(Level level) { return level.dimension() == Level.NETHER ? this.getProperties().allowNether : true; } public void handleConsoleInput(String msg, CommandSourceStack source) { this.consoleInput.add(new ConsoleInput(msg, source)); } public void handleConsoleInputs() { while (!this.consoleInput.isEmpty()) { ConsoleInput consoleInput = (ConsoleInput)this.consoleInput.remove(0); this.getCommands().performPrefixedCommand(consoleInput.source, consoleInput.msg); } } @Override public boolean isDedicatedServer() { return true; } @Override public int getRateLimitPacketsPerSecond() { return this.getProperties().rateLimitPacketsPerSecond; } @Override public boolean isEpollEnabled() { return this.getProperties().useNativeTransport; } public DedicatedPlayerList getPlayerList() { return (DedicatedPlayerList)super.getPlayerList(); } @Override public boolean isPublished() { return true; } @Override public String getServerIp() { return this.getLocalIp(); } @Override public int getServerPort() { return this.getPort(); } @Override public String getServerName() { return this.getMotd(); } public void showGui() { if (this.gui == null) { this.gui = MinecraftServerGui.showFrameFor(this); } } @Override public boolean hasGui() { return this.gui != null; } @Override public boolean isCommandBlockEnabled() { return this.getProperties().enableCommandBlock; } @Override public int getSpawnProtectionRadius() { return this.getProperties().spawnProtection; } @Override public boolean isUnderSpawnProtection(ServerLevel level, BlockPos pos, Player player) { if (level.dimension() != Level.OVERWORLD) { return false; } else if (this.getPlayerList().getOps().isEmpty()) { return false; } else if (this.getPlayerList().isOp(player.getGameProfile())) { return false; } else if (this.getSpawnProtectionRadius() <= 0) { return false; } else { BlockPos blockPos = level.getSharedSpawnPos(); int i = Mth.abs(pos.getX() - blockPos.getX()); int j = Mth.abs(pos.getZ() - blockPos.getZ()); int k = Math.max(i, j); return k <= this.getSpawnProtectionRadius(); } } @Override public boolean repliesToStatus() { return this.getProperties().enableStatus; } @Override public boolean hidesOnlinePlayers() { return this.getProperties().hideOnlinePlayers; } @Override public int getOperatorUserPermissionLevel() { return this.getProperties().opPermissionLevel; } @Override public int getFunctionCompilationLevel() { return this.getProperties().functionPermissionLevel; } @Override public void setPlayerIdleTimeout(int idleTimeout) { super.setPlayerIdleTimeout(idleTimeout); this.settings .update(dedicatedServerProperties -> (DedicatedServerProperties)dedicatedServerProperties.playerIdleTimeout.update(this.registryAccess(), idleTimeout)); } @Override public boolean shouldRconBroadcast() { return this.getProperties().broadcastRconToOps; } @Override public boolean shouldInformAdmins() { return this.getProperties().broadcastConsoleToOps; } @Override public int getAbsoluteMaxWorldSize() { return this.getProperties().maxWorldSize; } @Override public int getCompressionThreshold() { return this.getProperties().networkCompressionThreshold; } @Override public boolean enforceSecureProfile() { DedicatedServerProperties dedicatedServerProperties = this.getProperties(); return dedicatedServerProperties.enforceSecureProfile && dedicatedServerProperties.onlineMode && this.services.canValidateProfileKeys(); } @Override public boolean logIPs() { return this.getProperties().logIPs; } protected boolean convertOldUsers() { boolean bl = false; for (int i = 0; !bl && i <= 2; i++) { if (i > 0) { LOGGER.warn("Encountered a problem while converting the user banlist, retrying in a few seconds"); this.waitForRetry(); } bl = OldUsersConverter.convertUserBanlist(this); } boolean bl2 = false; for (int var7 = 0; !bl2 && var7 <= 2; var7++) { if (var7 > 0) { LOGGER.warn("Encountered a problem while converting the ip banlist, retrying in a few seconds"); this.waitForRetry(); } bl2 = OldUsersConverter.convertIpBanlist(this); } boolean bl3 = false; for (int var8 = 0; !bl3 && var8 <= 2; var8++) { if (var8 > 0) { LOGGER.warn("Encountered a problem while converting the op list, retrying in a few seconds"); this.waitForRetry(); } bl3 = OldUsersConverter.convertOpsList(this); } boolean bl4 = false; for (int var9 = 0; !bl4 && var9 <= 2; var9++) { if (var9 > 0) { LOGGER.warn("Encountered a problem while converting the whitelist, retrying in a few seconds"); this.waitForRetry(); } bl4 = OldUsersConverter.convertWhiteList(this); } boolean bl5 = false; for (int var10 = 0; !bl5 && var10 <= 2; var10++) { if (var10 > 0) { LOGGER.warn("Encountered a problem while converting the player save files, retrying in a few seconds"); this.waitForRetry(); } bl5 = OldUsersConverter.convertPlayers(this); } return bl || bl2 || bl3 || bl4 || bl5; } private void waitForRetry() { try { Thread.sleep(5000L); } catch (InterruptedException var2) { } } public long getMaxTickLength() { return this.getProperties().maxTickTime; } @Override public int getMaxChainedNeighborUpdates() { return this.getProperties().maxChainedNeighborUpdates; } @Override public String getPluginNames() { return ""; } @Override public String runCommand(String command) { this.rconConsoleSource.prepareForCommand(); this.executeBlocking(() -> this.getCommands().performPrefixedCommand(this.rconConsoleSource.createCommandSourceStack(), command)); return this.rconConsoleSource.getCommandResponse(); } public void storeUsingWhiteList(boolean isStoreUsingWhiteList) { this.settings .update(dedicatedServerProperties -> (DedicatedServerProperties)dedicatedServerProperties.whiteList.update(this.registryAccess(), isStoreUsingWhiteList)); } @Override public void stopServer() { super.stopServer(); Util.shutdownExecutors(); SkullBlockEntity.clear(); } @Override public boolean isSingleplayerOwner(GameProfile profile) { return false; } @Override public int getScaledTrackingDistance(int trackingDistance) { return this.getProperties().entityBroadcastRangePercentage * trackingDistance / 100; } @Override public String getLevelIdName() { return this.storageSource.getLevelId(); } @Override public boolean forceSynchronousWrites() { return this.settings.getProperties().syncChunkWrites; } @Override public TextFilter createTextFilterForPlayer(ServerPlayer player) { return this.serverTextFilter != null ? this.serverTextFilter.createContext(player.getGameProfile()) : TextFilter.DUMMY; } @Nullable @Override public GameType getForcedGameType() { return this.settings.getProperties().forceGameMode ? this.worldData.getGameType() : null; } @Override public Optional getServerResourcePack() { return this.settings.getProperties().serverResourcePackInfo; } @Override public void endMetricsRecordingTick() { super.endMetricsRecordingTick(); this.debugSampleSubscriptionTracker.tick(this.getTickCount()); } @Override public SampleLogger getTickTimeLogger() { return this.tickTimeLogger; } @Override public boolean isTickTimeLoggingEnabled() { return this.debugSampleSubscriptionTracker.shouldLogSamples(RemoteDebugSampleType.TICK_TIME); } @Override public void subscribeToDebugSample(ServerPlayer player, RemoteDebugSampleType sampleType) { this.debugSampleSubscriptionTracker.subscribe(player, sampleType); } @Override public boolean acceptsTransfers() { return this.settings.getProperties().acceptsTransfers; } @Override public ServerLinks serverLinks() { return this.serverLinks; } @Override public int pauseWhileEmptySeconds() { return this.settings.getProperties().pauseWhenEmptySeconds; } private static ServerLinks createServerLinks(DedicatedServerSettings settings) { Optional optional = parseBugReportLink(settings.getProperties()); return (ServerLinks)optional.map(uRI -> new ServerLinks(List.of(KnownLinkType.BUG_REPORT.create(uRI)))).orElse(ServerLinks.EMPTY); } private static Optional parseBugReportLink(DedicatedServerProperties properties) { String string = properties.bugReportLink; if (string.isEmpty()) { return Optional.empty(); } else { try { return Optional.of(Util.parseAndValidateUntrustedUri(string)); } catch (Exception var3) { LOGGER.warn("Failed to parse bug link {}", string, var3); return Optional.empty(); } } } }