package net.minecraft.client.gui.screens; import com.mojang.logging.LogUtils; import io.netty.channel.ChannelFuture; import java.net.InetSocketAddress; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.DefaultUncaughtExceptionHandler; import net.minecraft.Util; import net.minecraft.client.GameNarrator; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.multiplayer.TransferState; import net.minecraft.client.multiplayer.chat.report.ReportEnvironment; import net.minecraft.client.multiplayer.resolver.ResolvedServerAddress; import net.minecraft.client.multiplayer.resolver.ServerAddress; import net.minecraft.client.multiplayer.resolver.ServerNameResolver; import net.minecraft.client.quickplay.QuickPlay; import net.minecraft.client.quickplay.QuickPlayLog; import net.minecraft.client.resources.server.ServerPackManager.PackPromptStatus; import net.minecraft.network.Connection; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.login.LoginProtocols; import net.minecraft.network.protocol.login.ServerboundHelloPacket; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @Environment(EnvType.CLIENT) public class ConnectScreen extends Screen { private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0); static final Logger LOGGER = LogUtils.getLogger(); private static final long NARRATION_DELAY_MS = 2000L; public static final Component ABORT_CONNECTION = Component.translatable("connect.aborted"); public static final Component UNKNOWN_HOST_MESSAGE = Component.translatable("disconnect.genericReason", Component.translatable("disconnect.unknownHost")); @Nullable volatile Connection connection; @Nullable ChannelFuture channelFuture; volatile boolean aborted; final Screen parent; private Component status = Component.translatable("connect.connecting"); private long lastNarration = -1L; final Component connectFailedTitle; private ConnectScreen(Screen parent, Component connectFailedTitle) { super(GameNarrator.NO_TITLE); this.parent = parent; this.connectFailedTitle = connectFailedTitle; } public static void startConnecting( Screen parent, Minecraft minecraft, ServerAddress serverAddress, ServerData serverData, boolean isQuickPlay, @Nullable TransferState transferState ) { if (minecraft.screen instanceof ConnectScreen) { LOGGER.error("Attempt to connect while already connecting"); } else { Component component; if (transferState != null) { component = CommonComponents.TRANSFER_CONNECT_FAILED; } else if (isQuickPlay) { component = QuickPlay.ERROR_TITLE; } else { component = CommonComponents.CONNECT_FAILED; } ConnectScreen connectScreen = new ConnectScreen(parent, component); if (transferState != null) { connectScreen.updateStatus(Component.translatable("connect.transferring")); } minecraft.disconnect(); minecraft.prepareForMultiplayer(); minecraft.updateReportEnvironment(ReportEnvironment.thirdParty(serverData.ip)); minecraft.quickPlayLog().setWorldData(QuickPlayLog.Type.MULTIPLAYER, serverData.ip, serverData.name); minecraft.setScreen(connectScreen); connectScreen.connect(minecraft, serverAddress, serverData, transferState); } } private void connect(Minecraft minecraft, ServerAddress serverAddress, ServerData serverData, @Nullable TransferState transferState) { LOGGER.info("Connecting to {}, {}", serverAddress.getHost(), serverAddress.getPort()); Thread thread = new Thread("Server Connector #" + UNIQUE_THREAD_ID.incrementAndGet()) { public void run() { InetSocketAddress inetSocketAddress = null; try { if (ConnectScreen.this.aborted) { return; } Optional optional = ServerNameResolver.DEFAULT.resolveAddress(serverAddress).map(ResolvedServerAddress::asInetSocketAddress); if (ConnectScreen.this.aborted) { return; } if (optional.isEmpty()) { minecraft.execute( () -> minecraft.setScreen(new DisconnectedScreen(ConnectScreen.this.parent, ConnectScreen.this.connectFailedTitle, ConnectScreen.UNKNOWN_HOST_MESSAGE)) ); return; } inetSocketAddress = (InetSocketAddress)optional.get(); Connection connection; synchronized (ConnectScreen.this) { if (ConnectScreen.this.aborted) { return; } connection = new Connection(PacketFlow.CLIENTBOUND); connection.setBandwidthLogger(minecraft.getDebugOverlay().getBandwidthLogger()); ConnectScreen.this.channelFuture = Connection.connect(inetSocketAddress, minecraft.options.useNativeTransport(), connection); } ConnectScreen.this.channelFuture.syncUninterruptibly(); synchronized (ConnectScreen.this) { if (ConnectScreen.this.aborted) { connection.disconnect(ConnectScreen.ABORT_CONNECTION); return; } ConnectScreen.this.connection = connection; minecraft.getDownloadedPackSource().configureForServerControl(connection, convertPackStatus(serverData.getResourcePackStatus())); } ConnectScreen.this.connection .initiateServerboundPlayConnection( inetSocketAddress.getHostName(), inetSocketAddress.getPort(), LoginProtocols.SERVERBOUND, LoginProtocols.CLIENTBOUND, new ClientHandshakePacketListenerImpl( ConnectScreen.this.connection, minecraft, serverData, ConnectScreen.this.parent, false, null, ConnectScreen.this::updateStatus, transferState ), transferState != null ); ConnectScreen.this.connection.send(new ServerboundHelloPacket(minecraft.getUser().getName(), minecraft.getUser().getProfileId())); } catch (Exception var9) { if (ConnectScreen.this.aborted) { return; } Exception exception3; if (var9.getCause() instanceof Exception exception2) { exception3 = exception2; } else { exception3 = var9; } ConnectScreen.LOGGER.error("Couldn't connect to server", (Throwable)var9); String string = inetSocketAddress == null ? exception3.getMessage() : exception3.getMessage() .replaceAll(inetSocketAddress.getHostName() + ":" + inetSocketAddress.getPort(), "") .replaceAll(inetSocketAddress.toString(), ""); minecraft.execute( () -> minecraft.setScreen( new DisconnectedScreen(ConnectScreen.this.parent, ConnectScreen.this.connectFailedTitle, Component.translatable("disconnect.genericReason", string)) ) ); } } private static PackPromptStatus convertPackStatus(ServerData.ServerPackStatus packStatus) { return switch (packStatus) { case ENABLED -> PackPromptStatus.ALLOWED; case DISABLED -> PackPromptStatus.DECLINED; case PROMPT -> PackPromptStatus.PENDING; }; } }; thread.setUncaughtExceptionHandler(new DefaultUncaughtExceptionHandler(LOGGER)); thread.start(); } private void updateStatus(Component status) { this.status = status; } @Override public void tick() { if (this.connection != null) { if (this.connection.isConnected()) { this.connection.tick(); } else { this.connection.handleDisconnection(); } } } @Override public boolean shouldCloseOnEsc() { return false; } @Override protected void init() { this.addRenderableWidget(Button.builder(CommonComponents.GUI_CANCEL, button -> { synchronized (this) { this.aborted = true; if (this.channelFuture != null) { this.channelFuture.cancel(true); this.channelFuture = null; } if (this.connection != null) { this.connection.disconnect(ABORT_CONNECTION); } } this.minecraft.setScreen(this.parent); }).bounds(this.width / 2 - 100, this.height / 4 + 120 + 12, 200, 20).build()); } @Override public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { super.render(guiGraphics, mouseX, mouseY, partialTick); long l = Util.getMillis(); if (l - this.lastNarration > 2000L) { this.lastNarration = l; this.minecraft.getNarrator().sayNow(Component.translatable("narrator.joining")); } guiGraphics.drawCenteredString(this.font, this.status, this.width / 2, this.height / 2 - 50, 16777215); } }