diff --git a/game/client/src/main/java/net/minecraft/client/Minecraft.java b/game/client/src/main/java/net/minecraft/client/Minecraft.java
index e0e7d0029..3bdbb0558 100644
--- a/game/client/src/main/java/net/minecraft/client/Minecraft.java
+++ b/game/client/src/main/java/net/minecraft/client/Minecraft.java
@@ -30,6 +30,7 @@ import net.minecraft.client.gui.error.ScreenConflictWarning;
 import net.minecraft.client.gui.error.ScreenMissingMinecraft;
 import net.minecraft.client.gui.error.ScreenOutOfMemory;
 import net.minecraft.client.gui.options.data.OptionsPages;
+import net.minecraft.client.gui.server.ScreenSelectServer;
 import net.minecraft.client.gui.toasts.GuiElementToastsHud;
 import net.minecraft.client.holiday.Holidays;
 import net.minecraft.client.player.controller.PlayerControllerSP;
@@ -913,8 +914,16 @@ public class Minecraft
         return saveFormat;
     }
 
+    private Screen lastScreen;
     public void displayScreen(@Nullable Screen screen)
     {
+        // Big sir's mouse fix, clearly the legacy lwjgl dependency is fucked in some way, may we hopefully fully replace it one day
+        if (screen == null && lastScreen != null) {
+            Mouse.setGrabbed(false);
+            Mouse.setGrabbed(true);
+        }
+        lastScreen = screen;
+
         if(currentScreen == null && screen != null)
         {
             if(controllerInput != null)
diff --git a/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleDispatcher.java b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleDispatcher.java
index b5e5a9eb9..a12f4f265 100644
--- a/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleDispatcher.java
+++ b/game/client/src/main/java/net/minecraft/client/entity/particle/ParticleDispatcher.java
@@ -35,38 +35,38 @@ public final class ParticleDispatcher extends Dispatcher<String, ParticleLambda>
         INSTANCE = this;
         Minecraft mc = Minecraft.getMinecraft();
         addDispatch("bubble", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleBubble(world, x, y, z, motionX, motionY, motionZ, true));
-        addDispatch("smoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionX, 1.0f, data));
-        addDispatch("note", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleNote(world, x, y, z, motionX, motionY, motionX, data));
-        addDispatch("portal", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticlePortal(world, x, y, z, motionX, motionY, motionX, data));
-        addDispatch("explode", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleExplode(world, x, y, z, motionX, motionY, motionX));
-        addDispatch("flame", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFlame(world, x, y, z, motionX, motionY, motionX, ParticleFlame.Type.ORANGE));
-        addDispatch("blueflame", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFlame(world, x, y, z, motionX, motionY, motionX, ParticleFlame.Type.BLUE));
-        addDispatch("soulflame", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFlame(world, x, y, z, motionX, motionY, motionX, ParticleFlame.Type.SOUL));
+        addDispatch("smoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionZ, 1.0f, data));
+        addDispatch("note", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleNote(world, x, y, z, motionX, motionY, motionZ, data));
+        addDispatch("portal", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticlePortal(world, x, y, z, motionX, motionY, motionZ, data));
+        addDispatch("explode", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleExplode(world, x, y, z, motionX, motionY, motionZ));
+        addDispatch("flame", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFlame(world, x, y, z, motionX, motionY, motionZ, ParticleFlame.Type.ORANGE));
+        addDispatch("blueflame", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFlame(world, x, y, z, motionX, motionY, motionZ, ParticleFlame.Type.BLUE));
+        addDispatch("soulflame", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFlame(world, x, y, z, motionX, motionY, motionZ, ParticleFlame.Type.SOUL));
         addDispatch("lava", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleLava(world, x, y, z));
         addDispatch("footstep", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFootstep(mc.textureManager, world, x, y, z));
-        addDispatch("splash", (world1, d, d1, d2, d3, d4, d5, data) -> new ParticleSplash(world1, d, d1, d2, d3, d4, d5));
-        addDispatch("largesmoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionX, 2.5f, data));
+        addDispatch("splash", (world1, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSplash(world1, x, y, z, motionX, motionY, motionZ));
+        addDispatch("largesmoke", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSmoke(world, x, y, z, motionX, motionY, motionZ, 2.5f, data));
         addDispatch("puffrgb", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticlePuffRGB(world, x, y, z, (float) motionX, (float) motionY, (float) motionZ, data));
         addDispatch("reddust", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleRedstoneDust(world, x, y, z, data));
         addDispatch("item", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleItemBreaking(world, x, y, z, Item.itemsList[MathHelper.clamp(data, 0, Item.itemsList.length - 1)]));
         addDispatch("block", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleBlockBreaking(world, x, y, z, motionX, motionY, motionZ, Blocks.blocksList[BlockParticleHelper.decodeBlockID(data)], BlockParticleHelper.decodeBlockSide(data), BlockParticleHelper.decodeBlockMeta(data)).init((int) x, (int) y, (int) z));
-        addDispatch("snowshovel", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSnowShovel(world, x, y, z, motionX, motionY, motionX));
-        addDispatch("heart", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleHeart(world, x, y, z, motionX, motionY, motionX));
+        addDispatch("snowshovel", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleSnowShovel(world, x, y, z, motionX, motionY, motionZ));
+        addDispatch("heart", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleHeart(world, x, y, z, motionX, motionY, motionZ));
         addDispatch("slimechunk", (world, x, y, z, motionX, motionY, motionZ, data) -> {
             if (mc.gameSettings.slimeParticles.value) {
                 return new ParticleSlimeChunk(world, x, y, z, Items.SLIMEBALL);
             }
             return null;
         });
-        addDispatch("fireflyGreen", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionX, MobFireflyCluster.FireflyColor.GREEN.getId()));
-        addDispatch("fireflyBlue", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionX, MobFireflyCluster.FireflyColor.BLUE.getId()));
-        addDispatch("fireflyOrange", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionX, MobFireflyCluster.FireflyColor.ORANGE.getId()));
-        addDispatch("fireflyRed", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionX, MobFireflyCluster.FireflyColor.RED.getId()));
-        addDispatch("arrowtrail", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleArrowGolden(world, x, y, z, motionX, motionY, motionX));
+        addDispatch("fireflyGreen", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.GREEN.getId()));
+        addDispatch("fireflyBlue", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.BLUE.getId()));
+        addDispatch("fireflyOrange", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.ORANGE.getId()));
+        addDispatch("fireflyRed", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleFirefly(world, x, y, z, motionX, motionY, motionZ, MobFireflyCluster.FireflyColor.RED.getId()));
+        addDispatch("arrowtrail", (world, x, y, z, motionX, motionY, motionZ, data) -> new ParticleArrowGolden(world, x, y, z, motionX, motionY, motionZ));
         addDispatch("fallingleaf", (world, x, y, z, motionX, motionY, motionZ, data) -> {
             int id = world.getBlockId(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z));
             if (id != 0) {
-                return new ParticleLeaf(world, x, y, z, motionX, motionY, motionX).init(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z));
+                return new ParticleLeaf(world, x, y, z, motionX, motionY, motionZ).init(MathHelper.floor(x), MathHelper.floor(y), MathHelper.floor(z));
             }
             return null;
         });
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectFailed.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectFailed.java
index 8c9f15c57..31a9671da 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectFailed.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectFailed.java
@@ -1,6 +1,7 @@
 package net.minecraft.client.gui;
 
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.server.ScreenSelectServer;
 import net.minecraft.core.lang.I18n;
 import org.jetbrains.annotations.NotNull;
 
@@ -49,7 +50,7 @@ public class ScreenConnectFailed extends Screen
     {
         if(button.id == 0)
         {
-            mc.displayScreen(new ScreenMainMenu());
+            mc.displayScreen(new ScreenSelectServer(new ScreenMainMenu()));
         }
     }
 
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectToIp.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectToIp.java
deleted file mode 100644
index 71e9c0207..000000000
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnectToIp.java
+++ /dev/null
@@ -1,96 +0,0 @@
-package net.minecraft.client.gui;
-
-import net.minecraft.core.lang.I18n;
-import net.minecraft.core.net.ServerAddress;
-import org.jetbrains.annotations.NotNull;
-import org.lwjgl.input.Keyboard;
-
-public class ScreenConnectToIp extends Screen
-{
-
-    public ScreenConnectToIp(Screen parent)
-    {
-        super(parent);
-    }
-
-    @Override
-	public void tick()
-    {
-        textField.updateCursorCounter();
-    }
-
-    @Override
-	public void init()
-    {
-        I18n stringtranslate = I18n.getInstance();
-        Keyboard.enableRepeatEvents(true);
-        buttons.clear();
-        buttons.add(new ButtonElement(0, width / 2 - 100, height / 4 + 96 + 12, stringtranslate.translateKey("gui.connect_to_ip.button.connect")));
-        buttons.add(new ButtonElement(1, width / 2 - 100, height / 4 + 120 + 12, stringtranslate.translateKey("gui.connect_to_ip.button.cancel")));
-        String s = mc.gameSettings.lastServer.value.replaceAll("_", ":");
-        buttons.get(0).enabled = s.length() > 0;
-        textField = new TextFieldElement(this, fontRenderer, width / 2 - 100, (height / 4 - 10) + 50 + 18, 200, 20, s, "");
-        textField.isFocused = true;
-        textField.setMaxStringLength(128);
-    }
-
-    @Override
-	public void removed()
-    {
-        Keyboard.enableRepeatEvents(false);
-    }
-
-    @Override
-	protected void buttonClicked(@NotNull ButtonElement button)
-    {
-        if(!button.enabled)
-        {
-            return;
-        }
-        if(button.id == 1)
-        {
-            mc.displayScreen(parentScreen);
-        } else
-        if(button.id == 0)
-        {
-            String serverUrl = textField.getText().trim();
-            mc.gameSettings.lastServer.value = serverUrl.replaceAll(":", "_");
-            mc.gameSettings.saveOptions();
-            ServerAddress address = ServerAddress.resolveServerIP(serverUrl);
-            mc.displayScreen(new ScreenConnecting(mc, address.getIP(), address.getPort()));
-        }
-    }
-
-    @Override
-	public void keyPressed(char eventCharacter, int eventKey, int mx, int my)
-    {
-        if(eventKey != Keyboard.KEY_BACK) super.keyPressed(eventCharacter, eventKey, mx, my);
-        textField.textboxKeyTyped(eventCharacter, eventKey);
-        if(eventCharacter == '\r')
-        {
-            buttonClicked((ButtonElement) buttons.get(0));
-        }
-        ((ButtonElement) buttons.get(0)).enabled = textField.getText().length() > 0;
-    }
-
-    @Override
-	public void mouseClicked(int mx, int my, int buttonNum)
-    {
-        super.mouseClicked(mx, my, buttonNum);
-        textField.mouseClicked(mx, my, buttonNum);
-    }
-
-    @Override
-	public void render(int mx, int my, float partialTick)
-    {
-        I18n i18n = I18n.getInstance();
-        renderBackground();
-        drawStringCenteredShadow(fontRenderer, i18n.translateKey("gui.connect_to_ip.label.title"), width / 2, (height / 4 - 60) + 20, 0xffffff);
-        drawStringShadow(fontRenderer, i18n.translateKey("gui.connect_to_ip.label.enter_ip"), width / 2 - 140, (height / 4 - 60) + 60 + 36, 0xa0a0a0);
-        textField.drawTextBox();
-        textField.updateCursor(mc, mx, my);
-        super.render(mx, my, partialTick);
-    }
-
-    private TextFieldElement textField;
-}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnecting.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenConnecting.java
index bb9cadba5..96294d34a 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenConnecting.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenConnecting.java
@@ -2,6 +2,7 @@ package net.minecraft.client.gui;
 
 import com.mojang.logging.LogUtils;
 import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.server.ScreenSelectServer;
 import net.minecraft.client.net.handler.PacketHandlerClient;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.net.packet.PacketPreLogin;
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenMainMenu.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenMainMenu.java
index a72a61e4b..6ee32e0dc 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenMainMenu.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenMainMenu.java
@@ -18,9 +18,9 @@ import com.mojang.logging.LogUtils;
 import net.minecraft.client.gui.error.ScreenMissingJCE;
 import net.minecraft.client.gui.hud.HudIngame;
 import net.minecraft.client.gui.hud.HudIngameDoom;
+import net.minecraft.client.gui.server.ScreenSelectServer;
 import net.minecraft.client.holiday.Holidays;
 import net.minecraft.client.render.Lighting;
-import net.minecraft.client.render.block.model.BlockModel;
 import net.minecraft.client.render.block.model.BlockModelDispatcher;
 import net.minecraft.client.render.texture.stitcher.TextureRegistry;
 import net.minecraft.client.render.tessellator.Tessellator;
@@ -75,7 +75,7 @@ public class ScreenMainMenu extends Screen
 	public ScreenMainMenu() {
 		ScreenPause.photoModeDisabled = false;
 
-        try (InputStream stream = ScreenMainMenu.class.getResourceAsStream("/assets/minecraft/textures/gui/title/splashes.txt")) {
+        try (InputStream stream = Minecraft.getMinecraft().texturePackList.getResourceAsStream("/assets/minecraft/textures/gui/title/splashes.txt")) {
 
             if (stream == null) {
                 LOGGER.error("Could not locate 'splashes.txt'! Does the file exist!?");
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenSelectServer.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenSelectServer.java
deleted file mode 100644
index 5b1464721..000000000
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenSelectServer.java
+++ /dev/null
@@ -1,311 +0,0 @@
-package net.minecraft.client.gui;
-
-import net.minecraft.client.gui.error.ScreenMissingJCE;
-import net.minecraft.client.gui.popup.PopupScreen;
-import net.minecraft.client.gui.popup.PopupBuilder;
-import net.minecraft.client.net.thread.ThreadPingServer;
-import net.minecraft.client.render.tessellator.Tessellator;
-import net.minecraft.core.Global;
-import net.minecraft.core.lang.I18n;
-import net.minecraft.core.net.SavedServerList;
-import net.minecraft.core.net.ServerAddress;
-import net.minecraft.core.net.ServerData;
-import net.minecraft.core.util.helper.MathHelper;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.crypto.Cipher;
-import java.io.File;
-import java.util.HashMap;
-import java.util.Map;
-
-public class ScreenSelectServer extends Screen {
-
-	protected String screenTitle = "Select server";
-	private boolean selected = false;
-	private int selectedServer;
-	private SavedServerList savedServerList;
-	private ServerSlot worldSlotContainer;
-	private boolean deleting;
-	
-	public ButtonElement buttonConnect;
-	public ButtonElement buttonDirectConnect;
-	public ButtonElement buttonAdd;
-	
-	public ButtonElement buttonEdit;
-	public ButtonElement buttonDelete;
-	public ButtonElement buttonRefresh;
-	public ButtonElement buttonCancel;
-	public @Nullable ButtonElement buttonJCE = null;
-
-	TooltipElement tooltip;
-
-	private Map<ServerData, ThreadPingServer> pingThreads = new HashMap<>();
-
-	public ScreenSelectServer(Screen parent) {
-		super(parent);
-	}
-
-	@Override
-	public void init() {
-		I18n stringtranslate = I18n.getInstance();
-		screenTitle = stringtranslate.translateKey("gui.select_server.label.title");
-		loadSavedServers();
-		worldSlotContainer = new ServerSlot();
-		tooltip = new TooltipElement(mc);
-		initButtons();
-		refresh();
-	}
-
-	private void loadSavedServers() {
-		savedServerList = SavedServerList.readFromFile(new File(Global.accessor.getMinecraftDir() + "/servers.dat"));
-		selectedServer = -1;
-	}
-
-	public void initButtons() {
-		I18n trans = I18n.getInstance();
-		
-		int x1 = width / 2 - 154;
-		int y1 = height - 52;
-		int y2 = height - 28;
-		
-		buttonConnect = add(new ButtonElement(0, x1, y1, 100, 20, trans.translateKey("gui.select_server.button.join_server")));
-		buttonDirectConnect = add(new ButtonElement(0, x1 + 104, y1, 100, 20, trans.translateKey("gui.select_server.button.direct_connect")));
-		buttonAdd = add(new ButtonElement(0, x1 + 208, y1, 100, 20, trans.translateKey("gui.select_server.button.add_server")));
-		
-		buttonEdit = add(new ButtonElement(0, x1, y2, 74, 20, trans.translateKey("gui.select_server.button.edit")));
-		buttonDelete = add(new ButtonElement(0, x1 + 78, y2, 74, 20, trans.translateKey("gui.select_server.button.delete")));
-		buttonRefresh = add(new ButtonElement(0, x1 + 156, y2, 74, 20, trans.translateKey("gui.select_server.button.refresh")));
-		buttonCancel = add(new ButtonElement(0, x1 + 234, y2, 74, 20, trans.translateKey("gui.select_server.button.cancel")));
-
-		boolean hasJCE;
-		try {
-			hasJCE = Cipher.getMaxAllowedKeyLength("AES") > 128;
-		} catch (final Exception ignored) {
-			hasJCE = false;
-		}
-		if (!(hasJCE)) buttonJCE = add(new ClickableLabelElement(0, (width - fontRenderer.stringWidth(trans.translateKey("gui.select_server.button.no_jce.warning"))) /2, 5, trans.translateKey("gui.select_server.button.no_jce.warning"), 0xFFFFFFFF));
-		
-		setButtonsEnabled(false);
-	}
-
-	@Override
-	protected void buttonClicked(@NotNull ButtonElement button) {
-		if(!button.enabled) {
-			return;
-		}
-		if(button == buttonDelete) {
-			String serverName = savedServerList.servers.get(selectedServer).nickname;
-			if (serverName != null) {
-				deleting = true;
-				I18n i18n = I18n.getInstance();
-				PopupScreen popup = new PopupBuilder(this, 250)
-					.closeOnEsc(0)
-					.withLabel("gui.select_server.label.delete_title")
-					.withLabel("gui.select_server.label.delete_confirm.1")
-					.withLabelLiteral(i18n.translateKeyAndFormat("gui.select_server.label.delete_confirm.2", serverName))
-					.withLabelLiteral("")
-					.withButtonGroup("endButtons", new String[] { "gui.select_server.button.delete", "gui.select_server.button.cancel" }, new int[] { 1, 0 })
-					.withOnCloseListener((statusCode, results) -> {
-						if (statusCode == 1)
-						{
-							deleteWorld(true, selectedServer);
-						}
-					})
-					.build();
-				mc.displayScreen(popup);
-			}
-		} else if(button == buttonConnect) {
-			selectServer(selectedServer);
-		} else if(button == buttonDirectConnect) {
-			mc.displayScreen(new ScreenConnectToIp(this));
-		} else if(button == buttonAdd) {
-			mc.displayScreen(new ScreenAddServer(this, savedServerList));
-			savedServerList.writeToFile();
-		} else if(button == buttonCancel) {
-			mc.displayScreen(parentScreen);
-		} else if(button == buttonRefresh) {
-			refresh();
-		} else if(button == buttonEdit) {
-			ServerData serverData = savedServerList.servers.get(selectedServer);
-			
-			if(serverData != null) {
-				PopupScreen popup = new PopupBuilder(this, 140)
-						.closeOnEsc(0).closeOnClickOut(0).closeOnEnter(1)
-						.withLabel("gui.select_server.label.edit.1")
-						.withLabel("gui.select_server.label.edit.2")
-						.withTextField("nameField", serverData.nickname)
-						.withLabel("gui.select_server.label.edit.3")
-						.withTextField("ipField", serverData.address)
-						.withButton("showIp", "gui.select_server.button.show_ip", serverData.showIp ? 0 : 1, "gui.select_server.button.show_ip.on", "gui.select_server.button.show_ip.off")
-						.withButtonGroup("endButtons", new String[] { "gui.select_server.button.done", "gui.select_server.button.cancel" }, new int[] { 1, 0 })
-						.withOnCloseListener((statusCode, results) -> {
-							if(statusCode == 1) {
-								serverData.nickname = (String) results.get("nameField");
-								serverData.address = (String) results.get("ipField");
-								serverData.showIp = ((Integer) results.get("showIp")) == 0;
-								savedServerList.writeToFile();
-								refresh();
-							}
-						}).build();
-				mc.displayScreen(popup);
-			}
-		} else if (buttonJCE != null && button == buttonJCE) {
-			mc.displayScreen(new ScreenMissingJCE(parentScreen, new ScreenSelectServer(parentScreen)));
-		}
-	}
-
-	public void handleIncomingServerInfo(ServerData server) {
-		pingThreads.remove(server);
-	}
-	
-	public void refresh() {
-		selectedServer = -1;
-		loadSavedServers();
-		setButtonsEnabled(false);
-
-		for (ServerData server : savedServerList.servers) {
-			if (pingThreads.containsKey(server) && !pingThreads.get(server).isDone()) {
-				continue;
-			}
-			ThreadPingServer tps = new ThreadPingServer(this, server);
-			pingThreads.put(server, tps);
-			tps.start();
-		}
-	}
-
-	@Override
-	public void tick() {
-		super.tick();
-
-		try {
-			for (ThreadPingServer tps : pingThreads.values()) {
-				if (tps.isDone()) {
-					tps.tickNetClientHandler();
-				}
-			}
-		} catch (Exception e) { }
-		for (ServerData server : savedServerList.servers) {
-			ThreadPingServer tps = pingThreads.get(server);
-			if (tps != null) {
-				if (tps.isDone() && tps.getNetHandler() == null) {
-					server.isUp = false;
-					pingThreads.remove(server);
-				}
-			}
-		}
-	}
-
-	public void setButtonsEnabled(boolean selected) {
-		buttonConnect.enabled = selected;
-		buttonDelete.enabled = selected;
-		buttonEdit.enabled = selected;
-	}
-
-	public void selectServer(int i) {
-		mc.displayScreen(null);
-		if (selected) {
-			return;
-		}
-		selected = true;
-		ServerData server = savedServerList.servers.get(selectedServer);
-		ServerAddress address = server.getServerAddress();
-		mc.displayScreen(new ScreenConnecting(mc, address.getIP(), address.getPort()));
-	}
-
-	public void deleteWorld(boolean flag, int i) {
-		if (deleting) {
-			deleting = false;
-			if (flag) {
-				savedServerList.servers.remove(i);
-				savedServerList.writeToFile();
-			}
-			mc.displayScreen(this);
-		}
-	}
-
-	@Override
-	public void render(int mx, int my, float partialTick) {
-		worldSlotContainer.render(mx, my, partialTick);
-		drawStringCenteredShadow(fontRenderer, screenTitle, width / 2, 20, 0xffffff);
-		super.render(mx, my, partialTick);
-	}
-	
-	class ServerSlot extends ScrolledSelectionList {
-
-		public ServerSlot() {
-			super(mc, width, height, 32, height - 64, 36);
-		}
-
-		@Override
-		protected int getItemCount() {
-			return savedServerList.servers.size();
-		}
-
-		@Override
-		protected void selectItem(int itemIndex, boolean doubleClicked) {
-			selectedServer = itemIndex;
-
-			boolean enableButtons = selectedServer >= 0 && selectedServer < getItemCount();
-			setButtonsEnabled(enableButtons);
-
-			/*guiSelectServer.buttonConnect.enabled = guiSelectServer.savedServerList.servers.get(selectedServer).protocolVersion == Global.MP_PROTOCOL_VERSION;
-			if (!guiSelectServer.buttonConnect.enabled) {
-				return;
-			}*/
-			
-			if (doubleClicked && enableButtons) {
-				selectServer(itemIndex);
-			}
-		}
-
-		@Override
-		protected boolean isSelectedItem(int itemIndex) {
-			return itemIndex == selectedServer;
-		}
-
-		@Override
-		protected int getMaxPosition() {
-			return savedServerList.servers.size() * 36;
-		}
-
-		@Override
-		protected void renderHoleBackground() {
-			renderBackground();
-		}
-
-		@Override
-		protected void renderItem(int index, int x, int y, int height, Tessellator tessellator) {
-			ServerData server = savedServerList.servers.get(index);
-			String line1 = server.nickname;
-			String line2 = server.address;
-
-			drawStringShadow(fontRenderer, line1, x + 2, y + 1, 0xffffff);
-			if (server.showIp) drawStringShadow(fontRenderer, line2, x + 2, y + 12 + 10, 0x404040);
-			if (server.isUp && server.protocolVersion != Global.MP_PROTOCOL_VERSION) {
-				String outdated = I18n.getInstance().translateKey("gui.select_server.label.outdated");
-				int width = MathHelper.ceil(fontRenderer.stringWidthDouble(outdated));
-				drawStringShadow(fontRenderer, outdated, x + 220 - width - 4, y + 1, 0xFF0000);
-				width = MathHelper.ceil(fontRenderer.stringWidthDouble(server.version));
-				drawStringShadow(fontRenderer, server.version, x + 220 - width - 4, y + 12, 0xFF0000);
-				drawStringShadow(fontRenderer, server.motd, x + 2, y + 12, 0x808080);
-			} else if (server.isUp) {
-				String latency = server.latency + "ms";
-				int width = MathHelper.ceil(fontRenderer.stringWidthDouble(latency));
-				float green = 1.0f - MathHelper.clamp((float)server.latency / 500.0F, 0.0F, 1.0F);
-				float red = 1.0F - green;
-				int argb = 0xFF000000 | (int)(255.0F * red) << 16 | (int)(255.0F * green) << 8;
-
-				drawStringShadow(fontRenderer, latency, x + 220 - width - 4, y + 1, argb);
-				String players = server.currentPlayers + "/" + server.maxPlayers;
-				width = MathHelper.ceil(fontRenderer.stringWidthDouble(players));
-				drawStringShadow(fontRenderer, players, x + 220 - width - 4, y + 12, 0x808080);
-				drawStringShadow(fontRenderer, server.motd, x + 2, y + 12, 0x808080);
-			} else {
-				String down = I18n.getInstance().translateKey("gui.select_server.label.down");
-				int width = MathHelper.ceil(fontRenderer.stringWidthDouble(down));
-				drawStringShadow(fontRenderer, down, x + 220 - width - 4, y + 1, 0xFF0000);
-			}
-		}
-	}
-}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/ScreenSignEditor.java b/game/client/src/main/java/net/minecraft/client/gui/ScreenSignEditor.java
index 8e15eaf18..df5ce47a4 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/ScreenSignEditor.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/ScreenSignEditor.java
@@ -139,6 +139,7 @@ public class ScreenSignEditor extends Screen
     @Override
 	public void render(int mx, int my, float partialTick)
     {
+        GL11.glColor4f(1f, 1f, 1f, 1f);
         if (inKeyboard && (mc.gameSettings.forceOnscreenKeyboard.value || mc.inputType == InputType.CONTROLLER) && !(mc.currentScreen instanceof ScreenKeyboard)) {
             if (editLine < entitySign.signText.length) {
                 ScreenKeyboard keyboard = new ScreenKeyboard(this, null,
diff --git a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentVehicleHealthBar.java b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentVehicleHealthBar.java
index 049f2df6f..61d7b9701 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentVehicleHealthBar.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/hud/component/HudComponentVehicleHealthBar.java
@@ -95,7 +95,7 @@ public class HudComponentVehicleHealthBar
 
         int health = 11;
         for (int i = 0; i < 10; i++) {
-            int xHeart = x + i * 8;
+            int xHeart = (x + 9 * 8) - i * 8;
             gui.drawGuiIcon(xHeart, y, 9, 9, TextureRegistry.getTexture("minecraft:gui/hud/heart/pig/container"));
             if (i * 2 + 1 < health)
             {
diff --git a/game/client/src/main/java/net/minecraft/client/gui/paged/Page.java b/game/client/src/main/java/net/minecraft/client/gui/paged/Page.java
index a22b93b82..73cd45dd8 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/paged/Page.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/paged/Page.java
@@ -31,6 +31,10 @@ public class Page {
         return this.components;
     }
 
+    public void clearComponents() {
+        this.components.clear();
+    }
+
     public void initComponents(final @NotNull Minecraft mc) {
         for (final @NotNull PageComponent component : this.components) {
             component.init(mc);
@@ -46,7 +50,7 @@ public class Page {
         return height;
     }
 
-    public @NotNull  ItemStack getIcon() {
+    public @NotNull ItemStack getIcon() {
         return this.icon;
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/gui/paged/ScreenPaged.java b/game/client/src/main/java/net/minecraft/client/gui/paged/ScreenPaged.java
index cb1cac4ae..a3659c326 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/paged/ScreenPaged.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/paged/ScreenPaged.java
@@ -17,29 +17,32 @@ import static org.lwjgl.opengl.GL11.*;
 
 public class ScreenPaged
     extends Screen {
-    private static final int TOP_SPACING = 24;
-    private static final int BUTTON_SPACING = 4;
-    private static final int SEPARATOR_WIDTH = 8;
-    private static final int PADDING = 8;
-    private static final int PAGE_BUTTON_HEIGHT = 20;
-
-    private final @NotNull String titleTranslationKey;
-    private final @NotNull String @NotNull [] buttonTranslationKeys;
-
-    private float rightScrollAmount = 0.0f;
-    private float leftScrollAmount = 0.0f;
-    private int top, bottom, rightScrollRegionHeight, leftScrollRegionHeight, pageWidth, leftWidth;
-
-    private @Nullable Float oldRightScrollAmount = null;
-    private @Nullable Float oldLeftScrollAmount = null;
-    private @Nullable Integer clickX;
-    private @Nullable Integer clickY;
-    private @NotNull Page selectedPage;
-    private @Nullable PageComponent clickedComponent;
-    private int clickedComponentY;
-    private @Nullable ItemElement renderItem = null;
-
-    private final @NotNull PageRegistry pageRegistry;
+    protected static final int TOP_SPACING = 24;
+    protected static final int BUTTON_SPACING = 4;
+    protected static final int SEPARATOR_WIDTH = 8;
+    protected static final int PADDING = 8;
+    protected static final int PAGE_BUTTON_HEIGHT = 20;
+    protected static final int ENTRY_LIST_Y_OFFSET = 2;
+
+    protected final @NotNull String titleTranslationKey;
+    protected final @NotNull String @NotNull [] buttonTranslationKeys;
+
+    protected float rightScrollAmount = 0.0f;
+    protected float leftScrollAmount = 0.0f;
+    protected int top, bottom, rightScrollRegionHeight, leftScrollRegionHeight, pageWidth, leftWidth;
+
+    protected @Nullable Float oldRightScrollAmount = null;
+    protected @Nullable Float oldLeftScrollAmount = null;
+    protected @Nullable Integer clickX;
+    protected @Nullable Integer clickY;
+    protected @NotNull Page selectedPage;
+    protected @Nullable PageComponent clickedComponent;
+    protected int clickedComponentY;
+    protected @Nullable ItemElement renderItem = null;
+
+    protected final @NotNull PageRegistry pageRegistry;
+
+    public int rightOffset = 0;
 
     public ScreenPaged(
         final @Nullable Screen parent,
@@ -186,9 +189,8 @@ public class ScreenPaged
         return PAGE_BUTTON_HEIGHT * this.pageRegistry.getPages().size();
     }
 
-    @Override
-    public void render(final int mx, final int my, final float renderPartialTicks) {
-        if (mx > this.leftWidth + SEPARATOR_WIDTH + PADDING && mx < this.width) {
+    public void handleScroll(int mx, int my, float partialTick) {
+        if (mx > this.leftWidth + SEPARATOR_WIDTH + PADDING && mx < this.width - rightOffset) {
             if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)) {
                 this.scrollRightPanel(Mouse.getDWheel() / -0.01f);
             } else {
@@ -203,9 +205,14 @@ public class ScreenPaged
             }
             this.onScrollLeftPanel();
         }
+    }
+
+    @Override
+    public void render(final int mx, final int my, final float renderPartialTicks) {
+        handleScroll(mx, my, renderPartialTicks);
 
         this.leftWidth = (int) (this.width * 0.25f);
-        this.pageWidth = this.width - (this.leftWidth + SEPARATOR_WIDTH + (PADDING * 2));
+        this.pageWidth = this.width - (this.leftWidth + SEPARATOR_WIDTH + (PADDING * 2)) - rightOffset;
         final @NotNull I18n i18n = I18n.getInstance();
 
         this.renderBackground();
@@ -214,9 +221,9 @@ public class ScreenPaged
 
         Scissor.enable(0, this.top, this.width, this.bottom - this.top);
         if (my >= this.top && my <= this.bottom) {
-            this.drawPageItems(this.leftWidth + SEPARATOR_WIDTH + PADDING, this.top - (int) this.rightScrollAmount, this.pageWidth - 8, mx, my);
+            this.drawPageItems(this.leftWidth + SEPARATOR_WIDTH + PADDING, this.top - (int) this.rightScrollAmount + ENTRY_LIST_Y_OFFSET, this.pageWidth - 8, mx, my);
         } else {
-            this.drawPageItems(this.leftWidth + SEPARATOR_WIDTH + PADDING, this.top - (int) this.rightScrollAmount, this.pageWidth - 8, -1, -1);
+            this.drawPageItems(this.leftWidth + SEPARATOR_WIDTH + PADDING, this.top - (int) this.rightScrollAmount + ENTRY_LIST_Y_OFFSET, this.pageWidth - 8, -1, -1);
         }
         Scissor.disable();
 
@@ -286,7 +293,7 @@ public class ScreenPaged
 
         glDisable(GL_TEXTURE_2D);
 
-        final int scrollBarX = this.width - 6;
+        final int scrollBarX = this.width - 6 - rightOffset;
 
         int scrollBarHeightPx = (int) (scrollBarHeightPercent * this.rightScrollRegionHeight);
         if (scrollBarHeightPx < 32) {
@@ -375,7 +382,7 @@ public class ScreenPaged
         final int left = this.leftWidth + SEPARATOR_WIDTH + PADDING;
 
         if (mx >= this.leftWidth + SEPARATOR_WIDTH && my >= this.top && my <= this.bottom) {
-            int componentY = this.top - (int) this.rightScrollAmount;
+            int componentY = this.top - (int) this.rightScrollAmount + ENTRY_LIST_Y_OFFSET;
             for (final @NotNull PageComponent component : this.selectedPage.getComponents()) {
                 if (mx >= left && mx <= left + this.pageWidth - PADDING && my >= componentY && my <= componentY + component.getHeight()) {
                     component.onMouseClick(buttonNum, left, componentY, this.pageWidth - PADDING, mx - left, my - componentY);
@@ -394,10 +401,11 @@ public class ScreenPaged
             for (final @NotNull Page page : this.pageRegistry.getPages()) {
                 if (mx >= 0 && mx <= this.leftWidth && my >= pagesListY && my <= pagesListY + PAGE_BUTTON_HEIGHT) {
                     this.rightScrollAmount = 0;
+                    Page lastPage = this.selectedPage;
                     this.selectedPage = page;
                     this.selectedPage.initComponents(this.mc);
                     this.mc.sndManager.playSound("random.click", SoundCategory.GUI_SOUNDS, 1.0F, 1.0F);
-                    this.mc.texturePackList.refreshIfReady();
+                    onPageChanged(lastPage, page);
                     break;
                 }
                 pagesListY += PAGE_BUTTON_HEIGHT;
@@ -408,6 +416,10 @@ public class ScreenPaged
         this.clickY = my;
     }
 
+    public void onPageChanged(@NotNull Page lastPage, @NotNull Page newPage) {
+        this.mc.texturePackList.refreshIfReady();
+    }
+
     @Override
     public void mouseReleased(final int mx, final int my, final int buttonNum) {
         super.mouseReleased(mx, my, buttonNum);
@@ -460,7 +472,7 @@ public class ScreenPaged
         }
     }
 
-    private void overlayBackground(final int minX, final int maxX, final int minY, final int maxY) {
+    protected void overlayBackground(final int minX, final int maxX, final int minY, final int maxY) {
         if (this.mc.currentWorld == null) {
             final @NotNull Tessellator tessellator = Tessellator.instance;
             this.mc.textureManager.loadTexture("/assets/minecraft/textures/gui/background.png").bind();
diff --git a/game/client/src/main/java/net/minecraft/client/gui/popup/PopupBuilder.java b/game/client/src/main/java/net/minecraft/client/gui/popup/PopupBuilder.java
index bacf4c74e..946de0f74 100644
--- a/game/client/src/main/java/net/minecraft/client/gui/popup/PopupBuilder.java
+++ b/game/client/src/main/java/net/minecraft/client/gui/popup/PopupBuilder.java
@@ -152,6 +152,13 @@ public class PopupBuilder {
         return this;
     }
 
+    public @NotNull PopupBuilder withSwitch(final @NotNull String key, final boolean defaultState, final @NotNull String onTextTranslationKey, final @NotNull String offTextTranslationKey) {;
+        final PopupComponent component = new SwitchComponent(this.width - (HORIZONTAL_MARGIN * 2), defaultState, I18n.getInstance().translateKey(onTextTranslationKey), I18n.getInstance().translateKey(offTextTranslationKey));
+        this.components.add(component);
+        this.componentKeyMap.put(component, key);
+        return this;
+    }
+
     public @NotNull PopupBuilder withBackgroundColor(final int color, final int color2) {
         this.backgroundColor = IntIntPair.of(color, color2);
         return this;
diff --git a/game/client/src/main/java/net/minecraft/client/gui/popup/SwitchComponent.java b/game/client/src/main/java/net/minecraft/client/gui/popup/SwitchComponent.java
new file mode 100644
index 000000000..8d5e9ec26
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/popup/SwitchComponent.java
@@ -0,0 +1,40 @@
+package net.minecraft.client.gui.popup;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.SwitchElement;
+import net.minecraft.core.sound.SoundCategory;
+import org.jetbrains.annotations.NotNull;
+
+public class SwitchComponent extends InteractivePopupComponent<Boolean> {
+    private final @NotNull Minecraft minecraft = Minecraft.getMinecraft();
+    public final SwitchElement switchElement;
+    public SwitchComponent(int width, boolean defaultState, String onText, String offText) {
+        super(width);
+        this.switchElement = new SwitchElement(0, 0, 0, width, getHeight(), defaultState, onText, offText);
+    }
+
+    @Override
+    public @NotNull Boolean getOutput() {
+        return this.switchElement.isOn();
+    }
+
+    @Override
+    public int getHeight() {
+        return 20;
+    }
+
+    @Override
+    public void render(int x, int y, int mouseX, int mouseY) {
+        this.switchElement.xPosition = x;
+        this.switchElement.yPosition = y;
+        this.switchElement.drawButton(this.minecraft, mouseX, mouseY);
+    }
+
+    @Override
+    public void onClick(final int x, final int y, final int button) {
+        if (button == 0 && this.switchElement.mouseClicked(minecraft, x, y)) {
+            this.switchElement.setOn(!this.switchElement.isOn());
+            this.minecraft.sndManager.playSound("random.click", SoundCategory.GUI_SOUNDS, 1.0F, 1.0F);
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/server/ScreenSelectServer.java b/game/client/src/main/java/net/minecraft/client/gui/server/ScreenSelectServer.java
new file mode 100644
index 000000000..ee93d2583
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/server/ScreenSelectServer.java
@@ -0,0 +1,485 @@
+package net.minecraft.client.gui.server;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.ButtonElement;
+import net.minecraft.client.gui.Screen;
+import net.minecraft.client.gui.ScreenConnecting;
+import net.minecraft.client.gui.paged.Page;
+import net.minecraft.client.gui.paged.PageRegistry;
+import net.minecraft.client.gui.paged.ScreenPaged;
+import net.minecraft.client.gui.popup.PopupBuilder;
+import net.minecraft.client.gui.popup.PopupScreen;
+import net.minecraft.client.render.Scissor;
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.client.render.texture.Texture;
+import net.minecraft.client.render.texture.TextureBuffered;
+import net.minecraft.core.Global;
+import net.minecraft.core.item.ItemStack;
+import net.minecraft.core.item.Items;
+import net.minecraft.core.lang.I18n;
+import net.minecraft.core.net.ServerAddress;
+import net.minecraft.core.net.command.TextFormatting;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.lwjgl.input.Keyboard;
+import org.lwjgl.input.Mouse;
+import org.lwjgl.opengl.GL11;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.Date;
+import java.util.UUID;
+
+import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
+import static org.lwjgl.opengl.GL11.glDisable;
+import static org.lwjgl.opengl.GL11.glEnable;
+
+public class ScreenSelectServer extends ScreenPaged {
+    public static final Texture defaultIcon = Minecraft.getMinecraft().textureManager.loadTexture("/assets/minecraft/textures/gui/unknown_server.png");
+    public static final @NotNull PageRegistry CREATE_WORLD_PAGES = new PageRegistry();
+    private static final ItemStack MAP = Items.MAP.getDefaultStack();
+    static {
+        MAP.getData().putBoolean("initialized", true);
+    }
+    public static final @NotNull DateFormat timeFormatter = new SimpleDateFormat();
+    public static final @NotNull Page PAGE_FAVORITES = CREATE_WORLD_PAGES.register(new Page("gui.select_server.page.favorites.title", Items.FLAG.getDefaultStack()));
+//    public static final @NotNull Page PAGE_INTERNET = CREATE_WORLD_PAGES.register(new Page("gui.select_server.page.internet.title", MAP));
+    public static final @NotNull Page PAGE_HISTORY = CREATE_WORLD_PAGES.register(new Page("gui.select_server.page.history.title", Items.BOOK.getDefaultStack()));
+    public static final @NotNull Page PAGE_LAN = CREATE_WORLD_PAGES.register(new Page("gui.select_server.page.lan.title", Items.DOOR_OAK.getDefaultStack()));
+    public static final @NotNull Page PAGE_DIRECT_CONNECT = CREATE_WORLD_PAGES.register(new Page("gui.select_server.page.direct_connect.title", Items.AMMO_ARROW_GOLD.getDefaultStack()));
+
+    private static final @NotNull StringBuilder stringBuilder = new StringBuilder(512);
+
+    public final @NotNull ServerManager serverManager;
+
+    public @Nullable ServerEntry selectedEntry = null;
+
+    public ButtonElement buttonJoin;
+//    public ButtonElement buttonDirectConnect;
+    public ButtonElement buttonAddServer;
+    public ButtonElement buttonEdit;
+    public ButtonElement buttonDelete;
+    public ButtonElement buttonRefresh;
+    public ButtonElement buttonCancel;
+
+    public @Nullable ServerAddress addressToJoin = null;
+    public ServerListenerThread listenerThread;
+    public boolean needToRefreshPages = false;
+
+    protected float panelScrollAmount = 0.0f;
+    protected int panelScrollRegionHeight;
+    protected @Nullable Float oldPanelScrollAmount = null;
+    protected float panelHeight = 100000;
+
+    public ScreenSelectServer(@Nullable Screen parent) {
+        super(parent, CREATE_WORLD_PAGES, PAGE_FAVORITES, "gui.select_server.label.title", new String[] {});
+
+        this.serverManager = ServerManager.getServerManager(this);
+        this.listenerThread = new ServerListenerThread(this);
+        this.listenerThread.start();
+
+        refreshPages();
+    }
+
+    @Override
+    public void init() {
+        super.init();
+
+        setupButtons();
+
+//        this.bottom = buttonJoin.yPosition - BUTTON_SPACING;
+//        this.rightScrollRegionHeight = this.bottom - this.top;
+//        this.leftScrollRegionHeight = this.bottom - this.top;
+    }
+
+    public void refreshPages() {
+//        PAGE_INTERNET.clearComponents();
+        PAGE_FAVORITES.clearComponents();
+        PAGE_HISTORY.clearComponents();
+        PAGE_LAN.clearComponents();
+        PAGE_DIRECT_CONNECT.clearComponents();
+        PAGE_DIRECT_CONNECT.withComponent(new SeverDirectConnectComponent(this));
+
+        serverManager.getFavorites().forEach(e -> PAGE_FAVORITES.withComponent(new ServerEntryComponent(this, e)));
+        serverManager.getLan().forEach(e -> PAGE_LAN.withComponent(new ServerEntryComponent(this, e)));
+        serverManager.getHistory().forEach(e -> PAGE_HISTORY.withComponent(new ServerEntryComponent(this, e)));
+//        serverManager.getInternet().forEach(e -> PAGE_INTERNET.withComponent(new ServerEntryComponent(this, e)));
+    }
+
+    public void setupButtons() {
+        if (selectedPage != PAGE_DIRECT_CONNECT) {
+            this.rightOffset = (int) (width * 0.25f);
+        } else {
+            this.rightOffset = 0;
+        }
+
+        buttons.clear();
+        I18n trans = I18n.getInstance();
+
+        int x1 = width / 2 - 154;
+        int y1 = height - 52;
+        int y2 = height - 24;
+
+        int w = width - rightOffset - leftWidth;
+
+        buttonJoin = add(new ButtonElement(0, width - rightOffset + 10, bottom - 22 * 3, rightOffset - 12, 20, trans.translateKey("gui.select_server.button.join_server")));
+        buttonEdit = add(new ButtonElement(3, width - rightOffset + 10, bottom - 22 * 2, rightOffset - 12, 20, trans.translateKey("gui.select_server.button.edit")));
+        buttonDelete = add(new ButtonElement(4, width - rightOffset + 10, bottom - 22, rightOffset - 12, 20, trans.translateKey("gui.select_server.button.delete")));
+
+        buttonAddServer = add(new ButtonElement(2, x1 + 208, y2, 100, 20, trans.translateKey("gui.select_server.button.add_server")));
+        buttonRefresh = add(new ButtonElement(5, x1 + 104, y2, 100, 20, trans.translateKey("gui.select_server.button.refresh")));
+        buttonCancel = add(new ButtonElement(6, x1, y2, 100, 20, trans.translateKey("gui.select_server.button.cancel")));
+
+        if (selectedPage == PAGE_DIRECT_CONNECT) {
+            buttonAddServer.enabled = buttonAddServer.visible = false;
+            buttonRefresh.enabled = buttonRefresh.visible = false;
+            buttonCancel.xPosition = (width - 100) / 2;
+        }
+
+        this.panelScrollRegionHeight = this.buttonJoin.yPosition - this.top;
+    }
+
+    @Override
+    protected void buttonClicked(@NotNull ButtonElement button) {
+        if (button == buttonCancel) {
+            this.mc.displayScreen(this.getParentScreen());
+            freeResources();
+        } /*else if (button == buttonDirectConnect) {
+            PopupScreen directConnect = new PopupBuilder(this, 160)
+                .withLabel("gui.select_server.popup.add.prompt.ip")
+                .withTextField("ip", "")
+                .withButtonGroup("1", new String[]{"gui.select_server.popup.direct.button.cancel", "gui.select_server.popup.direct.button.save"}, new int[]{0, 1})
+                .closeOnEsc(0)
+                .closeOnEnter(1)
+                .closeOnClickOut(0)
+                .withOnCloseListener((statusCode, results) -> {
+                    if (statusCode == 1) {
+                        String ip = (String) results.get("ip");
+                        ServerAddress address = ServerAddress.resolveServerIP(ip);
+                        if (address != null) {
+                            addressToJoin = address;
+                            if (serverManager.entryMap.containsKey(ip)) {
+                                serverManager.entryMap.get(ip).lastPlayedTime = System.currentTimeMillis();
+                            }
+                        }
+                    }
+                })
+                .build();
+            this.mc.displayScreen(directConnect);
+        }*/ else if (button == buttonAddServer) {
+            PopupScreen add = new PopupBuilder(this, 160)
+                .withLabel("gui.select_server.popup.add.prompt.name")
+                .withTextField("name", "")
+                .withLabel("gui.select_server.popup.add.prompt.ip")
+                .withTextField("ip", "")
+                .withButtonGroup("1", new String[]{"gui.select_server.popup.add.button.cancel", "gui.select_server.popup.add.button.save"}, new int[]{0, 1})
+                .withSwitch("showIp", true, "gui.select_server.popup.add.showip.on", "gui.select_server.popup.add.showip.off")
+                .closeOnEsc(0)
+                .closeOnEnter(1)
+                .closeOnClickOut(0)
+                .withOnCloseListener((statusCode, results) -> {
+                    if (statusCode == 1) {
+                        String name = (String) results.get("name");
+                        if (name.isEmpty()) name = null;
+                        ServerEntry entry = new ServerEntry(UUID.randomUUID(), (String) results.get("ip"));
+                        entry.overrideName = name;
+                        entry.showIp = (Boolean) results.get("showIp");
+                        entry.dirty = true;
+                        entry.requestIcon = true;
+                        serverManager.addEntry(entry);
+                        serverManager.addFavorite(entry);
+                        refreshPages();
+                    }
+                })
+                .build();
+            this.mc.displayScreen(add);
+        } else if (button == buttonEdit && selectedEntry != null) {
+            ServerEntry tEntry = selectedEntry;
+            PopupScreen edit = new PopupBuilder(this, 160)
+                .withLabel("gui.select_server.popup.edit.prompt.name")
+                .withTextField("name", tEntry.overrideName == null ? "" : tEntry.overrideName)
+                .withLabel("gui.select_server.popup.edit.prompt.ip")
+                .withTextField("ip", tEntry.address)
+                .withButtonGroup("1", new String[]{"gui.select_server.popup.edit.button.cancel", "gui.select_server.popup.edit.button.save"}, new int[]{0, 1})
+                .withSwitch("showIp", tEntry.showIp, "gui.select_server.popup.edit.showip.on", "gui.select_server.popup.edit.showip.off")
+                .closeOnEsc(0)
+                .closeOnEnter(1)
+                .closeOnClickOut(0)
+                .withOnCloseListener((statusCode, results) -> {
+                    if (statusCode == 1) {
+                        String name = (String) results.get("name");
+                        if (name.isEmpty()) name = null;
+                        tEntry.overrideName = name;
+                        tEntry.address = (String) results.get("ip");
+                        tEntry.addressResolved = null;
+                        tEntry.showIp = (Boolean) results.get("showIp");
+                        tEntry.dirty = true;
+                        tEntry.requestIcon = true;
+                        refreshPages();
+                    }
+                })
+                .build();
+            this.mc.displayScreen(edit);
+        } else if (button == buttonJoin) {
+            if (selectedEntry != null) {
+                needToRefreshPages = true;
+                serverManager.onJoined(selectedEntry);
+                joinServer(selectedEntry.getServerAddress());
+            }
+        } else if (button == buttonRefresh) {
+            refresh();
+        } else if (button == buttonDelete && selectedEntry != null) {
+            deleteEntry(selectedEntry);
+        }
+    }
+
+    private void scrollPanel(final float amount) {
+        if (amount == 0.0f) return;
+
+        this.panelScrollAmount += amount;
+        this.onScrollPanel();
+    }
+
+    private void onScrollPanel() {
+        final int totalPanelHeight = this.getTotalPanelHeight();
+        if (this.panelScrollAmount < 0 || this.panelScrollRegionHeight > totalPanelHeight) this.panelScrollAmount = 0;
+        else if (this.panelScrollAmount > totalPanelHeight - this.panelScrollRegionHeight)
+            this.panelScrollAmount = totalPanelHeight - this.panelScrollRegionHeight;
+    }
+
+    private int getTotalPanelHeight() {
+        return (int) panelHeight;
+    }
+
+    public void joinServer(@NotNull ServerAddress address) {
+        mc.gameSettings.lastServer.value = address.toString().replaceAll(":", "_");
+        mc.gameSettings.saveOptions();
+        mc.displayScreen(new ScreenConnecting(mc, address.getIP(), address.getPort()));
+        freeResources();
+    }
+
+    public void freeResources() { // Wonder how long until this not being called at the right time causes a memory leak :)
+        serverManager.freeResources();
+        listenerThread.keepAlive = false;
+    }
+
+    public void refresh() {
+        serverManager.idToEntryLock.lock();
+        try {
+            serverManager.idToEntryMap.forEach((uuid, entry) -> entry.dirty = true);
+        } finally {
+            serverManager.idToEntryLock.unlock();
+        }
+    }
+
+    @Override
+    public void tick() {
+        serverManager.idToEntryLock.lock();
+        try {
+            serverManager.idToEntryMap.values().stream().filter(entry -> entry.queuedIcon != null).forEach(entry -> {
+                entry.icon = new TextureBuffered(entry.queuedIcon, false, false, false);
+                entry.queuedIcon = null;
+            });
+        } finally {
+            serverManager.idToEntryLock.unlock();
+        }
+    }
+
+    public void deleteEntry(@NotNull ServerEntry entry) {
+        if (selectedPage == PAGE_FAVORITES) {
+            serverManager.removeFavorite(entry);
+        } else if (selectedPage == PAGE_HISTORY) {
+            serverManager.removeHistory(entry);
+        }
+        refreshPages();
+    }
+
+    @Override
+    public void handleScroll(int mx, int my, float partialTick) {
+        if (mx > this.width - rightOffset && mx < this.width) {
+            if (Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL)) {
+                this.scrollPanel(Mouse.getDWheel() / -0.01f);
+            } else {
+                this.scrollPanel(Mouse.getDWheel() / -0.05f);
+            }
+            this.onScrollPanel();
+            return;
+        }
+        super.handleScroll(mx, my, partialTick);
+    }
+
+    @Override
+    public void render(int mx, int my, float renderPartialTicks) {
+        if (addressToJoin != null) {
+            joinServer(addressToJoin);
+            return;
+        }
+        if (needToRefreshPages) {
+            needToRefreshPages = false;
+            refreshPages();
+        }
+
+        this.buttonJoin.visible = this.buttonJoin.enabled = selectedEntry != null;
+        this.buttonEdit.visible = this.buttonEdit.enabled = selectedEntry != null;
+        this.buttonDelete.visible = this.buttonDelete.enabled = selectedEntry != null;
+
+        super.render(mx, my, renderPartialTicks);
+
+        this.overlayBackground(this.width - rightOffset, this.width - rightOffset + SEPARATOR_WIDTH, this.top, this.bottom);
+        Scissor.enable(this.width - rightOffset, this.top, rightOffset, this.buttonJoin.yPosition - this.top);
+        renderSelectedEntry(mx, my, renderPartialTicks);
+        Scissor.disable();
+    }
+
+    protected void drawPanelScrollBar(final int mouseX, final int mouseY) {
+        int totalPanelHeight = getTotalPanelHeight();
+        final float scrollBarHeightPercent = (float) this.panelScrollRegionHeight / totalPanelHeight;
+
+        if (scrollBarHeightPercent > 1.0f) return;
+
+        glDisable(GL_TEXTURE_2D);
+
+        final int scrollBarX = this.width - 6;
+
+        int scrollBarHeightPx = (int) (scrollBarHeightPercent * this.panelScrollRegionHeight);
+        if (scrollBarHeightPx < 32) {
+            scrollBarHeightPx = 32;
+        }
+
+        final float scrollPercent = this.panelScrollAmount / (totalPanelHeight - this.panelScrollRegionHeight);
+
+        final int scrollBarY = (int) (this.top + (this.panelScrollRegionHeight - scrollBarHeightPx) * scrollPercent);
+
+        final @NotNull Tessellator t = Tessellator.instance;
+
+        t.startDrawingQuads();
+        t.setColorOpaque(0, 0, 0);
+        t.drawRectangle(scrollBarX, this.top, 6, this.panelScrollRegionHeight);
+        t.setColorRGBA_I(0x808080, 255);
+        t.drawRectangle(scrollBarX, scrollBarY, 6, scrollBarHeightPx);
+        t.setColorRGBA_I(0xc0c0c0, 255);
+        t.drawRectangle(scrollBarX + 1, scrollBarY, 5, scrollBarHeightPx - 1);
+        t.draw();
+
+        glEnable(GL_TEXTURE_2D);
+
+        if (this.clickX != null && this.clickY != null) {
+            if (this.clickX >= scrollBarX && this.clickY >= this.top && this.clickX <= scrollBarX + 6 && this.clickY < this.bottom) {
+                if (this.oldPanelScrollAmount == null) {
+                    this.oldPanelScrollAmount = this.panelScrollAmount;
+                }
+                this.panelScrollAmount = this.oldPanelScrollAmount + (this.clickY - mouseY) * (1.0f / scrollBarHeightPercent) * -1.0f;
+                this.onScrollPanel();
+            }
+        } else {
+            this.oldPanelScrollAmount = null;
+        }
+    }
+
+    public void renderSelectedEntry(int mx, int my, float partialTick) {
+        if (selectedEntry != null) {
+            int totalPanelHeight = getTotalPanelHeight();
+            final float scrollBarHeightPercent = (float) this.panelScrollRegionHeight / totalPanelHeight;
+
+            Tessellator t = Tessellator.instance;
+            int minX = this.width - rightOffset + SEPARATOR_WIDTH;
+            int maxX = this.width;
+            int y = (int) (top + 4 - panelScrollAmount);
+            int startY = y;
+            if (selectedEntry.icon != null) {
+                selectedEntry.icon.bind();
+                t.startDrawingQuads();
+                t.drawRectangleWithUV(minX + (maxX - minX - 64)/2, y, 64, 64, 0, 0, 1, 1);
+                y += 64 + 2;
+                t.draw();
+            }
+
+            stringBuilder.setLength(0);
+            if (selectedEntry.showIp) {
+                stringBuilder.append(TextFormatting.GRAY).append(selectedEntry.getServerAddress().getIP()).append(":").append(selectedEntry.getServerAddress().getPort()).append(TextFormatting.RESET).append("\n");
+            }
+//            builder.append(TextFormatting.GRAY);
+            stringBuilder.append(selectedEntry.getName("Unknown"));
+            if (selectedEntry.version != null) {
+                stringBuilder.append(TextFormatting.RESET).append("\n\n");
+                stringBuilder.append(selectedEntry.protocolVersion == Global.MP_PROTOCOL_VERSION ? TextFormatting.YELLOW : TextFormatting.RED).append(selectedEntry.version);
+            }
+            stringBuilder.append(TextFormatting.RESET).append("\n\n");
+            if (selectedEntry.motd != null) {
+                stringBuilder.append(selectedEntry.motd);
+            }
+            if (!selectedEntry.players.isEmpty()) {
+                stringBuilder.append(TextFormatting.RESET).append("\n\n");
+                stringBuilder.append("Players:\n");
+                for (int i = 0; i < selectedEntry.players.size(); i++) {
+                    stringBuilder.append(" - ").append(TextFormatting.GRAY).append(selectedEntry.players.get(i)).append(TextFormatting.RESET).append("\n");
+                }
+            }
+            if (selectedEntry.lastPlayedTime >= 0) {
+                stringBuilder.append(TextFormatting.RESET).append("\n\n");
+                stringBuilder.append("Last Played: ").append(timeFormatter.format(Date.from(Instant.ofEpochMilli(selectedEntry.lastPlayedTime))));
+            }
+            mc.font.renderWidthConstrained(t, stringBuilder, minX + 2, y, maxX - minX - 4 - ((scrollBarHeightPercent > 1.0f) ? 0 : 6)).setShadow().call();
+            panelHeight = (float) (mc.font.getLastEndingY() - startY) + 8;
+
+            drawPanelScrollBar(mx, my);
+        }
+    }
+
+    @Override
+    public void onPageChanged(@NotNull Page lastPage, @NotNull Page newPage) {
+        setSelectedEntry(null);
+        setupButtons();
+    }
+
+    public void setSelectedEntry(@Nullable ServerEntry entry) {
+        this.selectedEntry = entry;
+        this.panelScrollAmount = 0;
+        this.oldPanelScrollAmount = null;
+        this.panelHeight = 0;
+    }
+
+    @Override
+    public void renderBackground() {
+        if (this.mc.currentWorld == null) {
+            this.renderTexturedBackground();
+            final @NotNull Tessellator tessellator = Tessellator.instance;
+            this.mc.textureManager.loadTexture("/assets/minecraft/textures/gui/background.png").bind();
+            GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
+            final float scale = 32F;
+            tessellator.startDrawingQuads();
+            tessellator.setColorOpaque_I(0x202020);
+
+            tessellator.addVertexWithUV(0, this.bottom, 0.0D, 0, (float) (this.bottom + (int) this.rightScrollAmount) / scale);
+            tessellator.addVertexWithUV(this.width - rightOffset, this.bottom, 0.0D, (float) (this.width - rightOffset) / scale, (this.bottom + this.rightScrollAmount) / scale);
+            tessellator.addVertexWithUV(this.width - rightOffset, this.top, 0.0D, (float) (this.width - rightOffset) / scale, (this.top + this.rightScrollAmount) / scale);
+            tessellator.addVertexWithUV(0, this.top, 0.0D, 0, (float) (this.top + (int) this.rightScrollAmount) / scale);
+
+            tessellator.addVertexWithUV(this.width - rightOffset, this.bottom, 0.0D, (this.width - rightOffset) / scale, (this.bottom + this.panelScrollAmount) / scale);
+            tessellator.addVertexWithUV(this.width, this.bottom, 0.0D, (float) (this.width) / scale, (this.bottom + this.panelScrollAmount) / scale);
+            tessellator.addVertexWithUV(this.width, this.top, 0.0D, (float) (this.width) / scale, (this.top + this.panelScrollAmount) / scale);
+            tessellator.addVertexWithUV(this.width - rightOffset, this.top, 0.0D, (this.width - rightOffset) / scale, (this.top + this.panelScrollAmount) / scale);
+
+            tessellator.addVertexWithUV(0, this.bottom, 0.0D, 0, (this.bottom + this.leftScrollAmount) / scale);
+            tessellator.addVertexWithUV(this.leftWidth, this.bottom, 0.0D, (float) this.leftWidth / scale, (this.bottom + this.leftScrollAmount) / scale);
+            tessellator.addVertexWithUV(this.leftWidth, this.top, 0.0D, (float) this.leftWidth / scale, (this.top + this.leftScrollAmount) / scale);
+            tessellator.addVertexWithUV(0, this.top, 0.0D, 0, (this.top + this.leftScrollAmount) / scale);
+
+            tessellator.draw();
+        } else {
+            final int color = this.mc.gameSettings.guiBackgroundColor.value.getARGB();
+            this.drawGradientRect(0, 0, this.width, this.height, color, color);
+            this.drawRect(0, 0, this.width, this.top, 0x5F000000);
+            this.drawRect(0, this.bottom, this.width, this.height, 0x5F000000);
+            this.drawRect(this.leftWidth, this.top, this.leftWidth + SEPARATOR_WIDTH, this.bottom, 0x5F000000);
+        }
+    }
+
+    @Override
+    public void removed() {
+        super.removed();
+        ServerManager.saveServerManager(serverManager);
+    }
+
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/server/ServerEntry.java b/game/client/src/main/java/net/minecraft/client/gui/server/ServerEntry.java
new file mode 100644
index 000000000..aa505df8b
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/server/ServerEntry.java
@@ -0,0 +1,66 @@
+package net.minecraft.client.gui.server;
+
+import net.minecraft.client.render.texture.Texture;
+import net.minecraft.core.net.ServerAddress;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+public class ServerEntry {
+    public @NotNull UUID uuid;
+    public @Nullable String overrideName;
+    public @Nullable String baseName;
+    public @Nullable String motd;
+    public @NotNull String address;
+    public @Nullable ServerAddress addressResolved;
+    public @Nullable Texture icon;
+    public @Nullable BufferedImage queuedIcon; // Exists because only the main thread can manifest Textures
+
+    public int playerCount;
+    public int playerCap;
+    public @NotNull List<String> players = new ArrayList<>();
+
+    public @Nullable String version;
+    public int protocolVersion = -1;
+
+    public long ping;
+    public long lastPlayedTime = -1;
+
+    public boolean isUp = false;
+
+    public boolean showIp = false;
+
+    public boolean requestIcon = false;
+    public boolean dirty = false;
+
+    public ServerEntry(@NotNull UUID uuid, @NotNull String address) {
+        this.uuid = uuid;
+        this.address = address;
+        this.icon = ScreenSelectServer.defaultIcon;
+    }
+
+    public @NotNull String getName(@NotNull String defaultName) {
+        if (overrideName != null && !overrideName.isBlank()) return overrideName;
+        if (baseName != null && !baseName.isBlank()) return baseName;
+        return defaultName;
+    }
+
+    public void free() {
+        if (icon != null && icon != ScreenSelectServer.defaultIcon) {
+            icon.delete();
+            icon = null;
+        }
+    }
+
+    public @NotNull ServerAddress getServerAddress() {
+        if (addressResolved == null) {
+            this.addressResolved = Objects.requireNonNull(ServerAddress.resolveServerIP(this.address));
+        }
+        return addressResolved;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/server/ServerEntryComponent.java b/game/client/src/main/java/net/minecraft/client/gui/server/ServerEntryComponent.java
new file mode 100644
index 000000000..6a905276a
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/server/ServerEntryComponent.java
@@ -0,0 +1,95 @@
+package net.minecraft.client.gui.server;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.paged.PageComponent;
+import net.minecraft.client.render.font.Alignment;
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.core.Global;
+import net.minecraft.core.lang.I18n;
+import net.minecraft.core.net.command.TextFormatting;
+import org.jetbrains.annotations.NotNull;
+
+public class ServerEntryComponent implements PageComponent {
+    private static final StringBuilder stringBuilder = new StringBuilder(512);
+    public final @NotNull ServerEntry entry;
+    public final @NotNull Minecraft mc;
+    public final @NotNull ScreenSelectServer attachedScreen;
+    public ServerEntryComponent(@NotNull ScreenSelectServer attachedScreen, @NotNull ServerEntry entry) {
+        this.entry = entry;
+        this.mc = Minecraft.getMinecraft();
+        this.attachedScreen = attachedScreen;
+    }
+
+    @Override
+    public int getHeight() {
+        return 26;
+    }
+
+    @Override
+    public void render(int x, int y, int width, int relativeMouseX, int relativeMouseY) {
+        Tessellator t = Tessellator.instance;
+        if (entry.icon != null) {
+            entry.icon.bind();
+            t.startDrawingQuads();
+            t.drawRectangleWithUV(x + 1, y + 1, 24, 24, 0, 0, 1, 1);
+            t.draw();
+        }
+
+        mc.font.render(t, entry.getName("Unknown"), x + 24 + 3, y + 2).setShadow().call();
+        if (entry.motd != null) {
+            stringBuilder.setLength(0);
+            stringBuilder.append(TextFormatting.GRAY).append(entry.motd);
+            stringBuilder.setLength(Math.max((width - 16), 0) / mc.font.getFont().charWidth('m'));
+            mc.font.renderWidthConstrained(t, stringBuilder, x + 24 + 3, mc.font.getLastEndingY(), width).setShadow().call();
+        }
+
+        if (!entry.isUp) {
+            stringBuilder.setLength(0);
+            stringBuilder.append(I18n.getInstance().translateKey("gui.select_server.label.down"));
+            mc.font.renderWidthConstrained(t, stringBuilder, x, y + 2, width - 2).setShadow().setColor(0xFF0000).setAlignment(Alignment.RIGHT).call();
+        } else if (entry.protocolVersion != Global.MP_PROTOCOL_VERSION) {
+            stringBuilder.setLength(0);
+            stringBuilder.append(I18n.getInstance().translateKey("gui.select_server.label.outdated"));
+            mc.font.renderWidthConstrained(t, stringBuilder, x, y + 2, width - 2).setShadow().setColor(0xFF0000).setAlignment(Alignment.RIGHT).call();
+//            stringBuilder.setLength(0);
+//            stringBuilder.append(entry.version);
+//            mc.font.renderWidthConstrained(t, stringBuilder, x, mc.font.getLastEndingY(), width - 2).setShadow().setColor(0xFF0000).setAlignment(Alignment.RIGHT).call();
+        } else if (!entry.dirty) {
+            stringBuilder.setLength(0);
+            stringBuilder.append(entry.playerCount).append("/").append(entry.playerCap);
+            mc.font.renderWidthConstrained(t, stringBuilder, x, y + 2, width - 2).setShadow().setColor(0xFFFF00).setAlignment(Alignment.RIGHT).call();
+            stringBuilder.setLength(0);
+            stringBuilder.append(entry.ping).append(" ms");
+            mc.font.renderWidthConstrained(t, stringBuilder, x, mc.font.getLastEndingY(), width - 2).setShadow().setColor(0xFFFF00).setAlignment(Alignment.RIGHT).call();
+        }
+
+        if (entry == attachedScreen.selectedEntry) {
+            attachedScreen.drawBox(x, y, x + width, y + getHeight(), 0xFFFFFFFF, 1);
+        }
+    }
+
+    @Override
+    public void onMouseClick(int mouseButton, int x, int y, int width, int relativeMouseX, int relativeMouseY) {
+        attachedScreen.setSelectedEntry(entry);
+    }
+
+    @Override
+    public void onMouseMove(int x, int y, int width, int relativeMouseX, int relativeMouseY) {
+
+    }
+
+    @Override
+    public void onMouseRelease(int mouseButton, int x, int y, int width, int relativeMouseX, int relativeMouseY) {
+
+    }
+
+    @Override
+    public void onKeyPress(int keyCode, char character) {
+
+    }
+
+    @Override
+    public boolean matchesSearchTerm(String term) {
+        return false;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/server/ServerListenerThread.java b/game/client/src/main/java/net/minecraft/client/gui/server/ServerListenerThread.java
new file mode 100644
index 000000000..7010e1a4c
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/server/ServerListenerThread.java
@@ -0,0 +1,131 @@
+package net.minecraft.client.gui.server;
+
+import com.mojang.logging.LogUtils;
+import net.minecraft.client.net.thread.ThreadPingServer;
+import org.jetbrains.annotations.NotNull;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+import java.net.MulticastSocket;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ServerListenerThread extends Thread{
+    private static final Logger LOGGER = LogUtils.getLogger();
+    public final @NotNull ScreenSelectServer attachedScreen;
+    public final @NotNull ServerManager serverManager;
+    public final @NotNull Map<ServerEntry, ThreadPingServer> pingThreads = new HashMap<>();
+    public boolean keepAlive = true;
+
+    protected MulticastSocket multicastSocket = null;
+    protected byte[] buf = new byte[256];
+
+    public ServerListenerThread(@NotNull ScreenSelectServer attachedScreen) {
+        this.attachedScreen = attachedScreen;
+        this.serverManager = attachedScreen.serverManager;
+    }
+
+    @Override
+    public void run() {
+        boolean multiCastAlive = false;
+        InetAddress group = null;
+        try {
+            multicastSocket = new MulticastSocket(4445);
+            group = InetAddress.getByName("224.0.2.61");
+            multicastSocket.joinGroup(group);
+            multiCastAlive = true;
+        } catch (IOException e) {
+            LOGGER.error("", e);
+        }
+        while (keepAlive) {
+            try {
+                serverManager.idToEntryLock.lock();
+                try {
+                    for (ServerEntry entry : serverManager.idToEntryMap.values()) {
+                        if (entry.dirty && !pingThreads.containsKey(entry)) {
+                            entry.dirty = false;
+                            synchronized (pingThreads) {
+                                ThreadPingServer threadPingServer2 = new ThreadPingServer(this, entry);
+                                pingThreads.put(entry, threadPingServer2);
+                                threadPingServer2.start();
+                            }
+                        }
+                    }
+                } finally {
+                    serverManager.idToEntryLock.unlock();
+                }
+                if (multiCastAlive) {
+                    try {
+                        DatagramPacket packet = new DatagramPacket(buf, buf.length);
+                        multicastSocket.receive(packet);
+                        String received = new String(packet.getData(), 0, packet.getLength());
+                        if ("end".equals(received)) {
+                            multiCastAlive = false;
+                        } else {
+                            receivedLocalServer(packet, received);
+                        }
+                    } catch (IOException e) {
+                        LOGGER.error("", e);
+                    }
+                }
+                Thread.sleep(1000);
+            } catch (InterruptedException e) {
+                LOGGER.error("", e);
+            }
+        }
+        try {
+            if (group != null) multicastSocket.leaveGroup(group);
+        } catch (IOException e) {
+            LOGGER.error("", e);
+        }
+        if (multicastSocket != null) multicastSocket.close();
+    }
+
+    public void receivedLocalServer(DatagramPacket packet, String localServerBroadcast) {
+        String motd = null;
+        if (localServerBroadcast.contains("[MOTD]")) {
+            int start = localServerBroadcast.indexOf("[MOTD]");
+            int end = localServerBroadcast.indexOf("[/MOTD]");
+            if (start != -1 && end != -1) {
+                motd = localServerBroadcast.substring(start + "[MOTD]".length(), end);
+            }
+        }
+        String port = null;
+        if (localServerBroadcast.contains("[AD]")) {
+            int start = localServerBroadcast.indexOf("[AD]");
+            int end = localServerBroadcast.indexOf("[/AD]");
+            if (start != -1 && end != -1) {
+                port = localServerBroadcast.substring(start + "[AD]".length(), end);
+            }
+        }
+        String name = null;
+        if (localServerBroadcast.contains("[NAME]")) {
+            int start = localServerBroadcast.indexOf("[NAME]");
+            int end = localServerBroadcast.indexOf("[/NAME]");
+            if (start != -1 && end != -1) {
+                name = localServerBroadcast.substring(start + "[NAME]".length(), end);
+            }
+        }
+        String ip = packet.getAddress().getHostAddress();
+        if (ip != null && port != null) {
+            int portInt;
+            try {
+                portInt = Integer.parseInt(port);
+            } catch (Exception e) {
+                return;
+            }
+            serverManager.onLocalScanned(ip, portInt, motd, name);
+        }
+    }
+
+    public void handleIncomingServerInfo(ServerEntry server) {
+        synchronized (pingThreads) {
+            ThreadPingServer pingServer = pingThreads.remove(server);
+            if (pingServer != null) {
+                pingServer.interrupt();
+            }
+        }
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/server/ServerManager.java b/game/client/src/main/java/net/minecraft/client/gui/server/ServerManager.java
new file mode 100644
index 000000000..e4a81f47e
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/server/ServerManager.java
@@ -0,0 +1,247 @@
+package net.minecraft.client.gui.server;
+
+import com.mojang.logging.LogUtils;
+import com.mojang.nbt.NbtIo;
+import com.mojang.nbt.tags.CompoundTag;
+import com.mojang.nbt.tags.ListTag;
+import com.mojang.nbt.tags.LongTag;
+import com.mojang.nbt.tags.StringTag;
+import com.mojang.nbt.tags.Tag;
+import net.minecraft.core.Global;
+import net.minecraft.core.net.ServerAddress;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class ServerManager {
+    private static final Logger LOGGER = LogUtils.getLogger();
+    public Lock idToEntryLock = new ReentrantLock();
+    public final @NotNull Map<UUID, ServerEntry> idToEntryMap = new HashMap<>();
+
+
+    private final @NotNull Map<UUID, ServerEntry> favoritesMap = new HashMap<>();
+    private final @NotNull List<ServerEntry> favorites = new ArrayList<>();
+    private final @NotNull Map<UUID, ServerEntry> internetMap = new HashMap<>();
+    private final @NotNull List<ServerEntry> internet = new ArrayList<>();
+    private final @NotNull Map<UUID, ServerEntry> historyMap = new HashMap<>();
+    private final @NotNull List<ServerEntry> history = new ArrayList<>();
+    private final @NotNull Map<UUID, ServerEntry> lanMap = new HashMap<>();
+    private final @NotNull Map<ServerAddress, ServerEntry> lanAddressMap = new HashMap<>();
+    private final @NotNull List<ServerEntry> lan = new ArrayList<>();
+
+    public static @NotNull ServerManager getServerManager(@NotNull ScreenSelectServer attachedScreen) {
+        File file = new File(Global.accessor.getMinecraftDir() + "/servers_new.dat");
+        try {
+            return new ServerManager(attachedScreen, NbtIo.readCompressed(Files.newInputStream(file.toPath())));
+        } catch (IOException e) {
+            LOGGER.error("", e);
+            return new ServerManager(attachedScreen, new CompoundTag());
+        }
+    }
+
+    public static void saveServerManager(@NotNull ServerManager savedServerData) {
+        File file = new File(Global.accessor.getMinecraftDir() + "/servers_new.dat");
+        if (!file.exists()) {
+            try {
+                //noinspection ResultOfMethodCallIgnored
+                file.createNewFile();
+            } catch (IOException e) {
+                LOGGER.error("", e);
+                return;
+            }
+        }
+
+        try {
+            NbtIo.writeCompressed(savedServerData.toTag(), Files.newOutputStream(file.toPath()));
+        } catch (IOException e) {
+            LOGGER.error("", e);
+        }
+    }
+
+    private final @NotNull ScreenSelectServer attachedScreen;
+    private ServerManager(@NotNull ScreenSelectServer attachedScreen, @NotNull CompoundTag compoundTag) {
+        this.attachedScreen = attachedScreen;
+
+        CompoundTag entries = compoundTag.getCompound("ServerData");
+        for (Map.Entry<String, Tag<?>> e : entries.getValue().entrySet()) {
+            UUID uuid = UUID.fromString(e.getKey());
+            CompoundTag entryTag = (CompoundTag) e.getValue();
+            ServerEntry entry = new ServerEntry(uuid, entryTag.getString("Address"));
+            entry.overrideName = entryTag.getStringOrDefault("OverrideName", null);
+            entry.baseName = entryTag.getStringOrDefault("BaseName", null);
+            entry.showIp = entryTag.getBooleanOrDefault("ShowIp", false);
+            entry.dirty = true;
+            entry.requestIcon = true;
+            addEntry(entry);
+        }
+        ListTag favorites = compoundTag.getList("Favorites");
+        for (Tag<?> tag : favorites) {
+            StringTag entryTag = (StringTag) tag;
+            UUID uuid = UUID.fromString(entryTag.getValue());
+            if (this.idToEntryMap.containsKey(uuid)) {
+                ServerEntry entry = this.idToEntryMap.get(uuid);
+                addFavorite(entry);
+            }
+        }
+        CompoundTag h = compoundTag.getCompound("History");
+        for (Map.Entry<String, Tag<?>> e : h.getValue().entrySet()) {
+            UUID uuid = UUID.fromString(e.getKey());
+            if (this.idToEntryMap.containsKey(uuid)) {
+                ServerEntry entry = this.idToEntryMap.get(uuid);
+                entry.lastPlayedTime = ((LongTag)e.getValue()).getValue();
+                if (!this.historyMap.containsKey(entry.uuid)) {
+                    this.historyMap.put(entry.uuid, entry);
+                    this.history.add(entry);
+                }
+            }
+        }
+        history.sort((o1, o2) -> Long.compare(o2.lastPlayedTime, o1.lastPlayedTime));
+
+    }
+
+    private @NotNull CompoundTag toTag() {
+        CompoundTag tag = new CompoundTag();
+
+        Set<ServerEntry> entriesToSave = new HashSet<>();
+
+        ListTag favorites = new ListTag();
+        for (ServerEntry entry : this.favorites) {
+            entriesToSave.add(entry);
+            favorites.addTag(new StringTag(entry.uuid.toString()));
+        }
+
+        CompoundTag history = new CompoundTag();
+        for (ServerEntry entry : this.history) {
+            entriesToSave.add(entry);
+            history.put(entry.uuid.toString(), new LongTag(entry.lastPlayedTime));
+        }
+
+        CompoundTag serverData = new CompoundTag();
+        for (ServerEntry entry : entriesToSave) {
+            CompoundTag eTag = new CompoundTag();
+            eTag.putString("Address", entry.address);
+            if (entry.overrideName != null) eTag.putString("OverrideName", entry.overrideName);
+            if (entry.baseName != null) eTag.putString("BaseName", entry.baseName);
+            if (entry.showIp) eTag.putBoolean("ShowIp", true);
+            serverData.put(entry.uuid.toString(), eTag);
+        }
+
+        tag.put("ServerData", serverData);
+        tag.put("Favorites", favorites);
+        tag.put("History", history);
+
+
+        return tag;
+    }
+
+    public void addEntry(@NotNull ServerEntry entry) {
+        idToEntryLock.lock();
+        try {
+            idToEntryMap.put(entry.uuid, entry);
+        } finally {
+            idToEntryLock.unlock();
+        }
+    }
+
+    public void addFavorite(@NotNull ServerEntry entry) {
+        addEntry(entry);
+        if (!favoritesMap.containsKey(entry.uuid)) {
+            favoritesMap.put(entry.uuid, entry);
+            favorites.add(entry);
+        }
+    }
+
+    public void onJoined(@NotNull ServerEntry entry) {
+        addEntry(entry);
+        entry.lastPlayedTime = System.currentTimeMillis();
+        if (!historyMap.containsKey(entry.uuid)) {
+            historyMap.put(entry.uuid, entry);
+            history.add(entry);
+        }
+        history.sort((o1, o2) -> Long.compare(o2.lastPlayedTime, o1.lastPlayedTime));
+    }
+
+    public void onLocalScanned(@NotNull String ip, int port, @Nullable String motd, @Nullable String name) {
+        ServerAddress address = ServerAddress.resolveServerIP(String.format("%s:%s", ip, port));
+        if (!lanAddressMap.containsKey(address)) {
+            ServerEntry entry = new ServerEntry(UUID.randomUUID(), String.format("%s:%s", ip, port));
+            if (name == null || name.isBlank()) {
+                entry.baseName = "Lan Server";
+            } else {
+                entry.baseName = name;
+            }
+            entry.motd = motd;
+            entry.showIp = true;
+            entry.addressResolved = address;
+            entry.dirty = true;
+            entry.requestIcon = true;
+            addEntry(entry);
+            lan.add(entry);
+            lanMap.put(entry.uuid, entry);
+            lanAddressMap.put(address, entry);
+            attachedScreen.needToRefreshPages = true;
+        }
+    }
+
+    public void removeFavorite(@NotNull ServerEntry entry) {
+        favorites.remove(entry);
+        favoritesMap.remove(entry.uuid);
+        checkValid(entry);
+    }
+
+    public void removeHistory(@NotNull ServerEntry entry) {
+        history.remove(entry);
+        historyMap.remove(entry.uuid);
+        checkValid(entry);
+    }
+
+    public void checkValid(@NotNull ServerEntry entry) {
+        if (
+            !favoritesMap.containsKey(entry.uuid) &&
+            !internetMap.containsKey(entry.uuid) &&
+            !historyMap.containsKey(entry.uuid) &&
+            !lanMap.containsKey(entry.uuid)
+        ) {
+            idToEntryMap.remove(entry.uuid);
+        }
+    }
+
+    public void freeResources() {
+        idToEntryLock.lock();
+        try {
+            idToEntryMap.forEach((uuid, entry) -> entry.free());
+        } finally {
+            idToEntryLock.unlock();
+        }
+    }
+
+    public @NotNull Collection<ServerEntry> getFavorites() {
+        return favorites;
+    }
+
+    public @NotNull Collection<ServerEntry> getInternet() {
+        return internet;
+    }
+
+    public @NotNull Collection<ServerEntry> getHistory() {
+        return history;
+    }
+
+    public @NotNull Collection<ServerEntry> getLan() {
+        return lan;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/gui/server/SeverDirectConnectComponent.java b/game/client/src/main/java/net/minecraft/client/gui/server/SeverDirectConnectComponent.java
new file mode 100644
index 000000000..99ea59d47
--- /dev/null
+++ b/game/client/src/main/java/net/minecraft/client/gui/server/SeverDirectConnectComponent.java
@@ -0,0 +1,103 @@
+package net.minecraft.client.gui.server;
+
+import net.minecraft.client.gui.ButtonElement;
+import net.minecraft.client.gui.TextFieldElement;
+import net.minecraft.client.gui.paged.PageComponent;
+import net.minecraft.client.render.tessellator.Tessellator;
+import net.minecraft.core.lang.I18n;
+import net.minecraft.core.net.ServerAddress;
+import org.jetbrains.annotations.NotNull;
+import org.lwjgl.input.Keyboard;
+
+import java.util.Map;
+import java.util.UUID;
+
+public class SeverDirectConnectComponent implements PageComponent {
+    public final ScreenSelectServer attachedScreen;
+    public TextFieldElement ipField;
+    public ButtonElement connectButton;
+    public SeverDirectConnectComponent(@NotNull ScreenSelectServer attachedScreen) {
+        this.attachedScreen = attachedScreen;
+        this.ipField = new TextFieldElement(attachedScreen, attachedScreen.fontRenderer, 0, 0, 150, 18, "", "");
+        this.connectButton = new ButtonElement(0, 0, 0, 150, 20, I18n.getInstance().translateKey("gui.select_server.popup.direct.button.connect"));
+    }
+    @Override
+    public int getHeight() {
+        return attachedScreen.height / 2;
+    }
+
+    @Override
+    public void render(int x, int y, int width, int relativeMouseX, int relativeMouseY) {
+        int _x = x + width / 2 - this.ipField.width / 2;
+        int _y = attachedScreen.height / 2 - 40;
+
+        attachedScreen.mc.font.render(Tessellator.instance, I18n.getInstance().translateKey("gui.select_server.popup.add.prompt.ip"), _x, _y).setShadow().call();
+        this.ipField.xPosition = _x;
+        this.ipField.yPosition = attachedScreen.mc.font.getLastEndingY() + 1;
+
+        this.ipField.drawTextBox();
+        this.ipField.updateCursor(attachedScreen.mc, x + relativeMouseX, y + relativeMouseY);
+
+        this.connectButton.xPosition = _x;
+        this.connectButton.yPosition = this.ipField.yPosition + this.ipField.height + 2;
+        this.connectButton.drawButton(attachedScreen.mc, x + relativeMouseX, y + relativeMouseY);
+    }
+
+    @Override
+    public void tick() {
+        this.ipField.updateCursorCounter();
+    }
+
+    @Override
+    public void onMouseClick(int mouseButton, int x, int y, int width, int relativeMouseX, int relativeMouseY) {
+        this.ipField.mouseClicked(x + relativeMouseX, y + relativeMouseY, 0);
+        if (this.connectButton.isHovered(x + relativeMouseX, y + relativeMouseY)) {
+            connect();
+        }
+    }
+
+    @Override
+    public void onMouseMove(int x, int y, int width, int relativeMouseX, int relativeMouseY) {
+
+    }
+
+    @Override
+    public void onMouseRelease(int mouseButton, int x, int y, int width, int relativeMouseX, int relativeMouseY) {
+
+    }
+
+    @Override
+    public void onKeyPress(int keyCode, char character) {
+        if (this.ipField.isFocused && keyCode == Keyboard.KEY_RETURN) {
+            connect();
+        } else if (this.ipField.isFocused && keyCode != Keyboard.KEY_ESCAPE) {
+            this.ipField.textboxKeyTyped(character, keyCode);
+        } else if (keyCode == Keyboard.KEY_ESCAPE) {
+            this.ipField.setFocused(false);
+        }
+    }
+
+    private void connect() {
+        String ip = this.ipField.getText();
+        ServerAddress address = ServerAddress.resolveServerIP(ip);
+        if (address != null) {
+            attachedScreen.addressToJoin = address;
+//            attachedScreen.serverManager.idToEntryLock.lock();
+//            try {
+//                for (Map.Entry<UUID, ServerEntry> entry : attachedScreen.serverManager.idToEntryMap.entrySet()) {
+//                    ServerEntry serverEntry = entry.getValue();
+//                    if (serverEntry.getServerAddress().equals(address)) {
+//                        attachedScreen.serverManager.onJoined(serverEntry);
+//                    }
+//                }
+//            } finally {
+//                attachedScreen.serverManager.idToEntryLock.unlock();
+//            }
+        }
+    }
+
+    @Override
+    public boolean matchesSearchTerm(String term) {
+        return false;
+    }
+}
diff --git a/game/client/src/main/java/net/minecraft/client/net/handler/PingPacketHandler.java b/game/client/src/main/java/net/minecraft/client/net/handler/PingPacketHandler.java
index 118b357b6..f5d71fcd5 100644
--- a/game/client/src/main/java/net/minecraft/client/net/handler/PingPacketHandler.java
+++ b/game/client/src/main/java/net/minecraft/client/net/handler/PingPacketHandler.java
@@ -1,33 +1,42 @@
 package net.minecraft.client.net.handler;
 
-import net.minecraft.client.gui.ScreenSelectServer;
+import com.mojang.logging.LogUtils;
+import net.minecraft.client.gui.server.ServerEntry;
+import net.minecraft.client.gui.server.ServerListenerThread;
 import net.minecraft.core.net.NetworkManager;
 import net.minecraft.core.net.ServerAddress;
-import net.minecraft.core.net.ServerData;
 import net.minecraft.core.net.handler.PacketHandler;
 import net.minecraft.core.net.packet.PacketDisconnect;
 import net.minecraft.core.net.packet.Packet;
-import net.minecraft.core.net.packet.PacketPingHandshake;
+import net.minecraft.core.net.packet.PacketServerIcon;
+import org.slf4j.Logger;
 
+import javax.imageio.ImageIO;
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.net.InetAddress;
 import java.net.Socket;
+import java.util.Arrays;
 
 public class PingPacketHandler
     extends PacketHandler
 {
-    private boolean disconnected = false;
-    private final ScreenSelectServer screen;
+    private static final Logger LOGGER = LogUtils.getLogger();
+    public boolean disconnected = false;
+    private final ServerListenerThread listener;
     private final NetworkManager netManager;
-    private final ServerData server;
+    private final ServerEntry server;
+    private final long startTime;
 
-    public PingPacketHandler(ScreenSelectServer screen, ServerData server)
+    public PingPacketHandler(ServerListenerThread listener, ServerEntry server)
         throws IOException {
-        this.screen = screen;
+        this.listener = listener;
         this.server = server;
         ServerAddress address = server.getServerAddress();
         Socket socket = new Socket(InetAddress.getByName(address.getIP()), address.getPort());
         netManager = new NetworkManager(socket, "Ping", this, 100, true);
+        startTime = System.currentTimeMillis();
+        server.isUp = false;
     }
 
     public void processReadPackets()
@@ -60,17 +69,36 @@ public class PingPacketHandler
             server.protocolVersion = parseIntWithDefault(serverInfo[1], -1);
             server.version = serverInfo[2];
             server.motd = serverInfo[3];
-            server.currentPlayers = parseIntWithDefault(serverInfo[4], -1);
-            server.maxPlayers = parseIntWithDefault(serverInfo[5], -1);
-            server.latency = System.currentTimeMillis() - PacketPingHandshake.startTime;
+            server.playerCount = parseIntWithDefault(serverInfo[4], -1);
+            server.playerCap = parseIntWithDefault(serverInfo[5], -1);
+            server.ping = System.currentTimeMillis() - startTime;
 
-            screen.handleIncomingServerInfo(server);
+            if (serverInfo.length >= 7) {
+                server.baseName = serverInfo[6];
+            }
+            server.players.clear();
+            if (serverInfo.length >= 8) {
+                String playersString = serverInfo[7];
+                String[] players = playersString.split(",");
+                server.players.addAll(Arrays.asList(players));
+            }
+
+            listener.handleIncomingServerInfo(server);
 
             netManager.networkShutdown("disconnect.kicked", new Object[0]);
             disconnected = true;
         }
     }
 
+    @Override
+    public void handleServerIconPacket(PacketServerIcon packetServerIcon) {
+        try {
+            server.queuedIcon = ImageIO.read(new ByteArrayInputStream(packetServerIcon.image));
+        } catch (IOException e) {
+            LOGGER.error("", e);
+        }
+    }
+
     private int parseIntWithDefault(String s, int i) {
         try {
             return Integer.parseInt(s.trim());
diff --git a/game/client/src/main/java/net/minecraft/client/net/thread/ThreadPingServer.java b/game/client/src/main/java/net/minecraft/client/net/thread/ThreadPingServer.java
index 9d14230d7..50c89f30b 100644
--- a/game/client/src/main/java/net/minecraft/client/net/thread/ThreadPingServer.java
+++ b/game/client/src/main/java/net/minecraft/client/net/thread/ThreadPingServer.java
@@ -1,47 +1,29 @@
 package net.minecraft.client.net.thread;
 
-import net.minecraft.client.gui.ScreenSelectServer;
+import net.minecraft.client.gui.server.ServerEntry;
+import net.minecraft.client.gui.server.ServerListenerThread;
 import net.minecraft.client.net.handler.PingPacketHandler;
 import net.minecraft.core.Global;
 import net.minecraft.core.net.ServerAddress;
-import net.minecraft.core.net.ServerData;
-import net.minecraft.core.net.handler.PacketHandler;
 import net.minecraft.core.net.packet.PacketPingHandshake;
 
 public class ThreadPingServer
     extends Thread {
 
-    private final ScreenSelectServer screen;
-    private final ServerData server;
-    private boolean done = false;
-    private PingPacketHandler netHandler = null;
+    private final ServerListenerThread listenerThread;
+    private final ServerEntry server;
 
-    public ThreadPingServer(ScreenSelectServer screen, ServerData server) {
-        this.screen = screen;
+    public ThreadPingServer(ServerListenerThread listenerThread, ServerEntry server) {
+        this.listenerThread = listenerThread;
         this.server = server;
-//        Turns out getting the real address like this is horribly horribly slow, taking me sometime 40 seconds to process all of these set names
-//        ServerAddress address = server.getServerAddress();
-//        this.setName("ThreadPingServer - " + address.getIP() + ":" + address.getPort());
 
         this.setName("ThreadPingServer - " + server.address);
     }
 
-    public ServerData getServer() {
+    public ServerEntry getServer() {
         return server;
     }
 
-    public boolean isDone() {
-        return done;
-    }
-
-    public void tickNetClientHandler() {
-        if (netHandler != null && isDone()) netHandler.processReadPackets();
-    }
-
-    public PacketHandler getNetHandler() {
-        return netHandler;
-    }
-
     @Override
     public void run() {
         ServerAddress address = this.server.getServerAddress();
@@ -49,11 +31,18 @@ public class ThreadPingServer
         int port = address.getPort();
 
         try {
-            netHandler = new PingPacketHandler(screen, server);
-            netHandler.addToSendQueue(new PacketPingHandshake(0x01, 0xFA, "BTAPingHost", Global.MP_PROTOCOL_VERSION, hostName, port));
+            PingPacketHandler netHandler = new PingPacketHandler(listenerThread, server);
+            if (server.requestIcon) {
+                netHandler.addToSendQueue(new PacketPingHandshake(0x03, 0xFA, "BTAPingHost", Global.MP_PROTOCOL_VERSION, hostName, port));
+                server.requestIcon = false;
+            } else {
+                netHandler.addToSendQueue(new PacketPingHandshake(0x02, 0xFA, "BTAPingHost", Global.MP_PROTOCOL_VERSION, hostName, port));
+            }
+            while (!netHandler.disconnected) {
+                netHandler.processReadPackets();
+                Thread.sleep(1);
+            }
         } catch (Exception ignored) {
-        } finally {
-            done = true;
         }
     }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
index 3b118fc33..be5894d06 100644
--- a/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
+++ b/game/client/src/main/java/net/minecraft/client/render/WorldRenderer.java
@@ -648,7 +648,7 @@ public class WorldRenderer
         mc.renderer.beginRenderWorld(partialTicks);
         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
         glEnable(GL_CULL_FACE);
-        setupCameraTransform(partialTicks);
+        setupCameraTransform((mc.activeCamera instanceof EntityCamera && ((EntityCamera) mc.activeCamera).mob.isRemoved()) ? 1f : partialTicks);
         Frustum.getFrustum();
         Debug.change("sky");
         if((mc.gameSettings.renderDistance.value > 4) /* Used to use Renderdistance rendersky! */ && !(mc.currentScreen instanceof ScreenModelViewer))
@@ -779,7 +779,7 @@ public class WorldRenderer
 
         Debug.change("misc");
         fogManager.setupFog(1, farPlaneDistance, partialTicks);
-        renderHand(partialTicks);
+        renderHand((mc.activeCamera instanceof EntityCamera && ((EntityCamera) mc.activeCamera).mob.isRemoved()) ? 1f : partialTicks);
         
         glColorMask(true, true, true, true);
         Debug.pop();
diff --git a/game/client/src/main/java/net/minecraft/client/render/camera/EntityCamera.java b/game/client/src/main/java/net/minecraft/client/render/camera/EntityCamera.java
index 744028fb8..c9c99fe9a 100644
--- a/game/client/src/main/java/net/minecraft/client/render/camera/EntityCamera.java
+++ b/game/client/src/main/java/net/minecraft/client/render/camera/EntityCamera.java
@@ -40,7 +40,7 @@ public abstract class EntityCamera
     @Override
     public double getX(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return mob.x;
         }
@@ -53,7 +53,7 @@ public abstract class EntityCamera
     @Override
     public double getY(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return mob.y + mob.getHeadHeight();
         }
@@ -67,7 +67,7 @@ public abstract class EntityCamera
     @Override
     public double getZ(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return mob.z;
         }
@@ -81,7 +81,7 @@ public abstract class EntityCamera
     @Override
     public double getXRot(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return mob.xRot;
         }
@@ -95,7 +95,7 @@ public abstract class EntityCamera
     @Override
     public double getYRot(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return mob.yRot;
         }
diff --git a/game/client/src/main/java/net/minecraft/client/render/camera/EntityCameraSleeping.java b/game/client/src/main/java/net/minecraft/client/render/camera/EntityCameraSleeping.java
index a9524a5cb..ebea638d5 100644
--- a/game/client/src/main/java/net/minecraft/client/render/camera/EntityCameraSleeping.java
+++ b/game/client/src/main/java/net/minecraft/client/render/camera/EntityCameraSleeping.java
@@ -94,7 +94,7 @@ public class EntityCameraSleeping
     @Override
     public double getX(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return x;
         }
@@ -107,7 +107,7 @@ public class EntityCameraSleeping
     @Override
     public double getY(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return y;
         }
@@ -120,7 +120,7 @@ public class EntityCameraSleeping
     @Override
     public double getZ(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return z;
         }
@@ -133,7 +133,7 @@ public class EntityCameraSleeping
     @Override
     public double getXRot(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return xRot;
         }
@@ -146,7 +146,7 @@ public class EntityCameraSleeping
     @Override
     public double getYRot(float partialTick)
     {
-        if (partialTick == 1f)
+        if (partialTick == 1f || mob.isRemoved())
         {
             return yRot;
         }
diff --git a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererChicken.java b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererChicken.java
index 4a6dfa0e1..df1117655 100644
--- a/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererChicken.java
+++ b/game/client/src/main/java/net/minecraft/client/render/entity/MobRendererChicken.java
@@ -42,4 +42,10 @@ public class MobRendererChicken extends MobRenderer<MobChicken> {
         final float flapSpeed = MathHelper.lerp(entity.oFlapSpeed, entity.flapSpeed, partialTick);
         return (MathHelper.sin(flap) + 1.0F) * flapSpeed;
     }
+
+    @Override
+    protected void preRenderTransform(@NotNull MobChicken entity, double x, double y, double z, float yaw, float partialTick) {
+        super.preRenderTransform(entity, x, y, z, yaw, partialTick);
+        GL11.glTranslatef(0, 0.3f, 0);
+    }
 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/font/FontRendererDefault.java b/game/client/src/main/java/net/minecraft/client/render/font/FontRendererDefault.java
index 5974a14e2..8155d2e83 100644
--- a/game/client/src/main/java/net/minecraft/client/render/font/FontRendererDefault.java
+++ b/game/client/src/main/java/net/minecraft/client/render/font/FontRendererDefault.java
@@ -509,9 +509,9 @@ public class FontRendererDefault extends FontRenderer {
             }
 
             if (currLineWidth + currWordWidth + charWidth > maxWidth) { //
-                if (c == ' ') {
+                /*if (c == ' ') {
                     splitLineIntoConstrainedLine_lineBuilder.append(splitLineIntoConstrainedLine_wordBuilder);
-                } else {
+                } else */{
                     splitLineIntoConstrainedLine_wordBuilder.append(c);
                     currWordWidth += charWidth;
                 }
diff --git a/game/client/src/main/java/net/minecraft/client/render/texture/Texture.java b/game/client/src/main/java/net/minecraft/client/render/texture/Texture.java
index ae24d5098..308593ce1 100644
--- a/game/client/src/main/java/net/minecraft/client/render/texture/Texture.java
+++ b/game/client/src/main/java/net/minecraft/client/render/texture/Texture.java
@@ -74,11 +74,12 @@ public class Texture {
 		glBindTexture(GL_TEXTURE_2D, 0);
 	}
 
-	@Override
-	protected void finalize() throws Throwable {
-		super.finalize();
-		delete();
-	}
+	// TODO we CANNOT rely on this, especially as thing become more multithreaded this just destroys the game
+//	@Override
+//	protected void finalize() throws Throwable {
+//		super.finalize();
+//		delete();
+//	}
 
 	public boolean isGenerated() {
 		return id != 0;
diff --git a/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/TextureRegistry.java b/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/TextureRegistry.java
index 5b0931dab..88be83130 100644
--- a/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/TextureRegistry.java
+++ b/game/client/src/main/java/net/minecraft/client/render/texture/stitcher/TextureRegistry.java
@@ -10,7 +10,6 @@ import org.jetbrains.annotations.NotNull;
 import org.slf4j.Logger;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.net.URI;
@@ -22,8 +21,6 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeFormatterBuilder;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/log/eucalyptus_side.png b/game/client/src/main/resources/assets/minecraft/textures/block/log/eucalyptus_side.png
index 79399ed27..922fd1831 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/log/eucalyptus_side.png and b/game/client/src/main/resources/assets/minecraft/textures/block/log/eucalyptus_side.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/block/log/eucalyptus_top.png b/game/client/src/main/resources/assets/minecraft/textures/block/log/eucalyptus_top.png
index d8e2bf542..7aa6eb4c1 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/block/log/eucalyptus_top.png and b/game/client/src/main/resources/assets/minecraft/textures/block/log/eucalyptus_top.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/heart/pig/half.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/heart/pig/half.png
index 4ae53162f..2c2c833ba 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/heart/pig/half.png and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/heart/pig/half.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/heart/pig/half_blinking.png b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/heart/pig/half_blinking.png
index 00fa330a7..b40f41275 100644
Binary files a/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/heart/pig/half_blinking.png and b/game/client/src/main/resources/assets/minecraft/textures/gui/sprites/hud/heart/pig/half_blinking.png differ
diff --git a/game/client/src/main/resources/assets/minecraft/textures/gui/unknown_server.png b/game/client/src/main/resources/assets/minecraft/textures/gui/unknown_server.png
new file mode 100644
index 000000000..a52a8dd93
Binary files /dev/null and b/game/client/src/main/resources/assets/minecraft/textures/gui/unknown_server.png differ
diff --git a/game/client/src/main/resources/credits.txt b/game/client/src/main/resources/credits.txt
index 846d31485..b90eefa5a 100644
--- a/game/client/src/main/resources/credits.txt
+++ b/game/client/src/main/resources/credits.txt
@@ -29,6 +29,7 @@ Cruxy
 
 *Special Thanks
 AndroidDr (@Pedro270707)
+Astronomical360
 DirtPiper
 Doop (@brokendoop)
 Gungun974
diff --git a/game/core/src/main/java/net/minecraft/core/entity/Entity.java b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
index de389dd89..8a5bad614 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/Entity.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/Entity.java
@@ -5,6 +5,7 @@ import java.util.Random;
 
 import net.minecraft.core.block.BlockLogicFluid;
 import net.minecraft.core.block.Blocks;
+import net.minecraft.core.entity.animal.MobPig;
 import net.minecraft.core.entity.vehicle.EntityBoat;
 import net.minecraft.core.block.piston.BlockLogicPistonHead;
 import net.minecraft.core.util.collection.NamespaceID;
@@ -1190,7 +1191,7 @@ public abstract class Entity
 
     public float getShadowHeightOffs()
     {
-        return bbHeight / 2.0F;
+        return 0.25f;
     }
 
     public @NotNull EntityItem dropItem(int itemId, int stackSize)
@@ -1268,7 +1269,7 @@ public abstract class Entity
 
         entityRiderYawDelta -= yaw;
         //entityRiderPitchDelta -= pitch;
-        if(turnWithVehicle) yRot += yaw;
+        if(turnWithVehicle && !(vehicle instanceof MobPig)) yRot += yaw;
         //rotationPitch += pitch;
     }
 
diff --git a/game/core/src/main/java/net/minecraft/core/entity/Mob.java b/game/core/src/main/java/net/minecraft/core/entity/Mob.java
index 5942b6401..8e503d3bd 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/Mob.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/Mob.java
@@ -1227,7 +1227,7 @@ public abstract class Mob extends Entity
 
     public Vec3 getPosition(float partialTick, boolean headOffset)
     {
-        if(partialTick == 1.0F)
+        if(partialTick == 1.0F || isRemoved())
         {
             return Vec3.fromPool(x, y + (headOffset ? getHeadHeight() : 0), z);
         } else
@@ -1242,7 +1242,7 @@ public abstract class Mob extends Entity
     @Override
 	public Vec3 getViewVector(float partialTick)
     {
-        if(partialTick == 1.0F)
+        if(partialTick == 1.0F || isRemoved())
         {
             float z = MathHelper.cos(-yRot * (MathHelper.PI / 180f) - MathHelper.PI);
             float x = MathHelper.sin(-yRot * (MathHelper.PI / 180f) - MathHelper.PI);
diff --git a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
index 0ecda8f7f..68de88aa9 100644
--- a/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
+++ b/game/core/src/main/java/net/minecraft/core/entity/animal/MobPig.java
@@ -85,8 +85,20 @@ public class MobPig
             final float accel = MathHelper.lerp(3, 10f, speedProgress) * (!onGround ? 0.25f : 1f) * (isInWater() ? 0.5f : 1f);
             final float maxSpeed = MathHelper.lerp(3, 10f, speedProgress) * (!(onGround || yd < 0) ? 0.25f : 1f) * (isInWater() ? 0.5f : 1f);
 
-            final double pXd = MathHelper.clamp(passenger.xd, -0.02, 0.02);
-            final double pZd = MathHelper.clamp(passenger.zd, -0.02, 0.02);
+            double pXd = passenger.xd;
+            double pZd = passenger.zd;
+
+            double vel = Math.sqrt(passenger.xd * passenger.xd + passenger.zd * passenger.zd);
+
+            if (vel != 0 && Double.isFinite(vel)) {
+                pXd /= vel;
+                pZd /= vel;
+
+                vel = MathHelper.clamp(vel, -0.02, 0.02);
+
+                pXd *= vel;
+                pZd *= vel;
+            }
 
             this.xd += pXd * accel;
             this.zd += pZd * accel;
diff --git a/game/core/src/main/java/net/minecraft/core/net/ServerAddress.java b/game/core/src/main/java/net/minecraft/core/net/ServerAddress.java
index df4a0c685..d9edc47a2 100644
--- a/game/core/src/main/java/net/minecraft/core/net/ServerAddress.java
+++ b/game/core/src/main/java/net/minecraft/core/net/ServerAddress.java
@@ -1,19 +1,22 @@
 package net.minecraft.core.net;
 
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
 import javax.naming.directory.Attributes;
 import javax.naming.directory.InitialDirContext;
 import java.util.Hashtable;
 
-public class ServerAddress {
-    private final String ipAddress;
+public final class ServerAddress {
+    private final @NotNull String ipAddress;
     private final int serverPort;
 
-    private ServerAddress(String ip, int port) {
+    private ServerAddress(@NotNull String ip, int port) {
         this.ipAddress = ip;
         this.serverPort = port;
     }
 
-    public String getIP() {
+    public @NotNull String getIP() {
         return this.ipAddress;
     }
 
@@ -21,7 +24,7 @@ public class ServerAddress {
         return this.serverPort;
     }
 
-    public static ServerAddress resolveServerIP(String ipToResolve) {
+    public static @Nullable ServerAddress resolveServerIP(@Nullable String ipToResolve) {
         if (ipToResolve == null) {
             return null;
         } else {
@@ -31,7 +34,7 @@ public class ServerAddress {
                 if (ipv6End > 0) {
                     String ipv6IP = ipToResolve.substring(1, ipv6End);
                     String port = ipToResolve.substring(ipv6End + 1).trim();
-                    if (port.startsWith(":") && !port.isEmpty()) {
+                    if (port.startsWith(":")) {
                         port = port.substring(1);
                         ipSplit = new String[]{ipv6IP, port};
                     } else {
@@ -77,4 +80,24 @@ public class ServerAddress {
             return defaultVal;
         }
     }
+
+    @Override
+    public String toString() {
+        return ipAddress + ":" + serverPort;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ServerAddress address = (ServerAddress) o;
+        return serverPort == address.serverPort && ipAddress.equals(address.ipAddress);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = ipAddress.hashCode();
+        result = 31 * result + serverPort;
+        return result;
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandBiome.java b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandBiome.java
index 8c625f5fb..13864118b 100644
--- a/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandBiome.java
+++ b/game/core/src/main/java/net/minecraft/core/net/command/commands/CommandBiome.java
@@ -8,6 +8,7 @@ import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.lang.I18n;
 import net.minecraft.core.net.command.CommandManager;
+import net.minecraft.core.util.helper.MathHelper;
 import net.minecraft.core.util.phys.Vec3;
 import net.minecraft.core.world.biome.Biome;
 import net.minecraft.core.net.command.CommandSource;
@@ -15,6 +16,8 @@ import net.minecraft.core.net.command.arguments.ArgumentTypeBiome;
 import net.minecraft.core.net.command.arguments.ArgumentTypeIntegerCoordinates;
 import net.minecraft.core.net.command.helpers.Coordinates2D;
 import net.minecraft.core.net.command.helpers.IntegerCoordinates;
+import net.minecraft.core.world.chunk.Chunk;
+import net.minecraft.core.world.chunk.ChunkSection;
 
 public class CommandBiome implements CommandManager.CommandRegistry {
     private static final DynamicCommandExceptionType FAILURE = new DynamicCommandExceptionType(arg -> () -> I18n.getInstance().translateKeyAndFormat("command.commands.biome.locate.exception_failure", arg));
@@ -22,6 +25,20 @@ public class CommandBiome implements CommandManager.CommandRegistry {
     public void register(CommandDispatcher<CommandSource> dispatcher) {
         dispatcher.register(ArgumentBuilderLiteral.<CommandSource>literal("biome")
                 .requires(CommandSource::hasAdmin)
+                .then(ArgumentBuilderLiteral.<CommandSource>literal("set")
+                    .then(ArgumentBuilderRequired.<CommandSource, Biome>argument("biome", ArgumentTypeBiome.biome())
+                        .executes(c -> {
+                            Biome biome = c.getArgument("biome", Biome.class);
+                            c.getSource().sendMessage(biome.translationKey);
+                            Vec3 coords = c.getSource().getCoordinates(false);
+                            if (coords != null) {
+                                Chunk chunk = c.getSource().getWorld().getChunkFromBlockCoords(MathHelper.floor(coords.x), MathHelper.floor(coords.z));
+                                for (int s = 0; s < Chunk.CHUNK_SECTIONS; s++) {
+                                    chunk.getSection(s).setSectionBiome(biome);
+                                }
+                            }
+                            return Command.SINGLE_SUCCESS;
+                        })))
                 .then(ArgumentBuilderLiteral.<CommandSource>literal("get")
                         .then(ArgumentBuilderRequired.<CommandSource, IntegerCoordinates>argument("position", ArgumentTypeIntegerCoordinates.intCoordinates())
                                 .executes(c -> {
diff --git a/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java b/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java
index 95cde2c70..6774629b4 100644
--- a/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java
+++ b/game/core/src/main/java/net/minecraft/core/net/handler/PacketHandler.java
@@ -393,4 +393,8 @@ public abstract class PacketHandler
     public void handleRequestCommandManagerPacket(PacketRequestCommandManager requestCommandManagerPacket) {
         handleInvalidPacket(requestCommandManagerPacket);
     }
+
+    public void handleServerIconPacket(PacketServerIcon packetServerIcon) {
+        handleInvalidPacket(packetServerIcon);
+    }
 }
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java b/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java
index d2f511dd4..1a9437e9a 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/Packet.java
@@ -338,6 +338,7 @@ public abstract class Packet
         addMapping(200, true, false, PacketStatistic.class);
         addMapping(201, true, false, PacketSyncIDs.class);
         addMapping(250, true, true, PacketCustomPayload.class);
+        addMapping(253, true, false, PacketServerIcon.class);
         addMapping(254, false, true, PacketPingHandshake.class);
         addMapping(255, true, true, PacketDisconnect.class);
     }
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketPingHandshake.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketPingHandshake.java
index f56fd78d2..40f2a1876 100644
--- a/game/core/src/main/java/net/minecraft/core/net/packet/PacketPingHandshake.java
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketPingHandshake.java
@@ -9,8 +9,6 @@ import java.nio.charset.StandardCharsets;
 
 public class PacketPingHandshake
     extends Packet {
-    public static long startTime = 0;
-
     public int payload;
     public int identifier;
     public String pingHostString;
@@ -50,7 +48,6 @@ public class PacketPingHandshake
         dos.writeByte(protocolVersion);
         writeStringUTF16BE(hostname, dos);
         dos.writeInt(port);
-        startTime = System.currentTimeMillis();
     }
 
     @Override
diff --git a/game/core/src/main/java/net/minecraft/core/net/packet/PacketServerIcon.java b/game/core/src/main/java/net/minecraft/core/net/packet/PacketServerIcon.java
new file mode 100644
index 000000000..c9a23364d
--- /dev/null
+++ b/game/core/src/main/java/net/minecraft/core/net/packet/PacketServerIcon.java
@@ -0,0 +1,41 @@
+package net.minecraft.core.net.packet;
+
+import net.minecraft.core.net.handler.PacketHandler;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class PacketServerIcon extends Packet {
+    public byte[] image;
+    public PacketServerIcon() {
+
+    }
+
+    public PacketServerIcon(byte @NotNull [] image) {
+        this.image = image;
+    }
+
+    @Override
+    public void read(DataInputStream dis) throws IOException {
+        image = new byte[dis.readInt()];
+        dis.read(image);
+    }
+
+    @Override
+    public void write(DataOutputStream dos) throws IOException {
+        dos.writeInt(image.length);
+        dos.write(image);
+    }
+
+    @Override
+    public void handlePacket(PacketHandler packetHandler) {
+        packetHandler.handleServerIconPacket(this);
+    }
+
+    @Override
+    public int getEstimatedSize() {
+        return image.length + 4;
+    }
+}
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
index 6a9ce7265..e0f5fe519 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/Chunk.java
@@ -291,7 +291,7 @@ public class Chunk
         }
 
         section.setBlockId(tilePos.toChunkSectionTilePos(), id);
-        section.setData(tilePos.toChunkSectionTilePos(), id);
+        section.setData(tilePos.toChunkSectionTilePos(), data);
 
         final @NotNull TilePos worldPos = new TilePos(this.pos, tilePos);
         if (!raw && currentBlock != null && !this.world.isClientSide) {
diff --git a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkSection.java b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkSection.java
index ca300f887..59e047386 100644
--- a/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkSection.java
+++ b/game/core/src/main/java/net/minecraft/core/world/chunk/ChunkSection.java
@@ -137,11 +137,16 @@ public class ChunkSection {
         return this.getBiome(new ChunkSectionTilePos(x, y, z));
     }
 
+
+    public void setSectionBiome(Biome biome) {
+        int biomeId = Registries.BIOMES.getNumericIdOfItem(biome);
+        Arrays.fill(this.biome, (byte) biomeId);
+    }
+
     public void setBiome(final @NotNull ChunkSectionTilePos sectionPos, final @NotNull Biome biome) {
         if (!sectionPos.inBounds()) {
             return;
         }
-
         final int biomeId = Registries.BIOMES.getNumericIdOfItem(biome);
         this.biome[makeBiomeIndex(sectionPos)] = (byte) biomeId;
     }
diff --git a/game/core/src/main/resources/lang/en_US/gui.lang b/game/core/src/main/resources/lang/en_US/gui.lang
index c11da97f7..4fa3f6de4 100644
--- a/game/core/src/main/resources/lang/en_US/gui.lang
+++ b/game/core/src/main/resources/lang/en_US/gui.lang
@@ -67,7 +67,7 @@ gui.conflict_warning.label.body.4=To prevent level corruption, the current game
 gui.conflict_warning.button.back=Back to title screen
 
 gui.connect_failed.label.title=Failed to connect to the server
-gui.connect_failed.button.back=Back to title screen
+gui.connect_failed.button.back=Back to server browser
 
 gui.connect_to_ip.button.connect=Connect
 gui.connect_to_ip.button.cancel=Cancel
@@ -105,6 +105,12 @@ gui.world_settings.page.game_rules.button.off=OFF
 gui.world_settings.label.select_world_type=Select World Type
 gui.world_settings.label.world_type=World Type
 
+gui.select_server.page.internet.title=Internet
+gui.select_server.page.favorites.title=Favorites
+gui.select_server.page.history.title=History
+gui.select_server.page.lan.title=Local Network
+gui.select_server.page.direct_connect.title=Direct Connect
+
 gui.dispenser.label.dispenser=Dispenser
 gui.dispenser.label.inventory=Inventory
 
@@ -331,6 +337,24 @@ gui.select_server.button.show_ip.on=ON
 gui.select_server.button.show_ip.off=OFF
 gui.select_server.button.no_jce.warning=§l§eJCE Missing!
 
+gui.select_server.popup.add.prompt.name=Server Name:
+gui.select_server.popup.add.prompt.ip=IP Address:
+gui.select_server.popup.add.button.save=Save
+gui.select_server.popup.add.button.cancel=Cancel
+gui.select_server.popup.add.showip.on=Show IP: ON
+gui.select_server.popup.add.showip.off=Show IP: OFF
+
+gui.select_server.popup.edit.prompt.name=Server Name:
+gui.select_server.popup.edit.prompt.ip=IP Address:
+gui.select_server.popup.edit.button.save=Save
+gui.select_server.popup.edit.button.cancel=Cancel
+gui.select_server.popup.edit.showip.on=Show IP: ON
+gui.select_server.popup.edit.showip.off=Show IP: OFF
+
+gui.select_server.popup.direct.prompt.ip=IP Address:
+gui.select_server.popup.direct.button.connect=Connect
+gui.select_server.popup.direct.button.cancel=Cancel
+
 gui.select_world.label.title=Select World
 gui.select_world.label.world=World %s
 gui.select_world.label.must_be_converted=Must be converted!
diff --git a/game/server/src/main/java/net/minecraft/server/MinecraftServer.java b/game/server/src/main/java/net/minecraft/server/MinecraftServer.java
index 9ec8e158f..c2a7a3597 100644
--- a/game/server/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/game/server/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -58,6 +58,7 @@ import net.minecraft.core.net.command.util.CommandHelper;
 import net.minecraft.core.util.pool.ObjectPool;
 import net.minecraft.core.util.helper.UUIDHelper;
 import net.minecraft.server.entity.player.PlayerServer;
+import net.minecraft.server.net.MulticastPublisher;
 import net.minecraft.server.net.command.ConsoleCommandSource;
 import net.minecraft.core.net.packet.PacketSetTime;
 import net.minecraft.core.player.gamemode.Gamemode;
@@ -112,9 +113,11 @@ import org.slf4j.Logger;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -132,6 +135,7 @@ public class MinecraftServer
 
     public @Nullable Boolean argOffline = null;
     public @Nullable String argWorld = null;
+    public int argPort = -1;
 
     public NetworkListenThread networkServer;
     public PropertyManager propertyManager;
@@ -146,6 +150,7 @@ public class MinecraftServer
     private final List<ServerCommandEntry> commands;
     public Int2ObjectMap<EntityTrackerImpl> entityTrackerMap;
     public boolean onlineMode;
+    public boolean broadcastPlayerList;
     public boolean pvpOn;
     public boolean allowFlight;
     public int spawnProtectionRange;
@@ -160,11 +165,15 @@ public class MinecraftServer
     public static String statsToken;
     public static boolean statsStatus;
     public boolean disablePhotoMode = false;
+    public String name = "";
     public String motd = "";
     private final SkinVariantList skinVariantList = new ServerSkinVariantList();
     public int autoSaveInterval = 5;
     public int chunksSavedPerAutosave = 24;
     public boolean forceSaveAllChunksOnAutosave = false;
+    public byte @Nullable [] iconBytes = null;
+    public final MulticastPublisher multicastPublisher = new MulticastPublisher();
+    public int ticksSinceLastBroadcast = 0;
 
     private boolean spawnAnimals;
     private boolean spawnHostiles;
@@ -219,6 +228,18 @@ public class MinecraftServer
             LOGGER.warn("To start the server with more ram, launch it as \"java -Xmx1024M -Xms1024M -jar minecraft_server.jar\"");
         }
 
+        File icon = new File("icon.png");
+        if (icon.exists()) {
+            try(InputStream stream = Files.newInputStream(new File("icon.png").toPath())) {
+                iconBytes = new byte[stream.available()];
+                for (int i = 0; i < iconBytes.length; i++) {
+                    iconBytes[i] = (byte) stream.read();
+                }
+            } catch (Exception e) {
+                LOGGER.error("", e);
+            }
+        }
+
         // Manually initialize blocks and items
         Blocks.init();
         Items.init();
@@ -232,6 +253,7 @@ public class MinecraftServer
         propertyManager = new PropertyManager(new File("server.properties"));
         String s = propertyManager.getStringProperty("server-ip", "");
         onlineMode = argOffline == null ? propertyManager.getBooleanProperty("online-mode", true) : !argOffline;
+        broadcastPlayerList = propertyManager.getBooleanProperty("broadcast-player-list", true);
         spawnAnimals = propertyManager.getBooleanProperty("spawn-animals", true);
         spawnHostiles = propertyManager.getBooleanProperty("spawn-monsters", true);
         pvpOn = propertyManager.getBooleanProperty("pvp", true);
@@ -248,6 +270,7 @@ public class MinecraftServer
         maxPlayers = propertyManager.getIntProperty("max-players", 20);
         language = propertyManager.getStringProperty("language", "en_US");
         disablePhotoMode = propertyManager.getBooleanProperty("disable-photomode", false);
+        name = propertyManager.getStringProperty("name", "");
         motd = propertyManager.getStringProperty("motd", "A Better than Adventure! Server");
         autoSaveInterval = propertyManager.getIntProperty("autosaveInterval", 5);
         chunksSavedPerAutosave = propertyManager.getIntProperty("maxChunksSavedPerAutosave", 24);
@@ -343,11 +366,16 @@ public class MinecraftServer
         {
             inetaddress = InetAddress.getByName(s);
         }
-        int i = propertyManager.getIntProperty("server-port", 25565);
-        LOGGER.info("Starting Minecraft server on " + (!s.isEmpty() ? s : "*") + ":" + i);
+        int port;
+        if (argPort >= 0) {
+            port = argPort;
+        } else {
+            port = propertyManager.getIntProperty("server-port", 25565);
+        }
+        LOGGER.info("Starting Minecraft server on {}:{}", !s.isEmpty() ? s : "*", port);
         try
         {
-            networkServer = new NetworkListenThread(this, inetaddress, i);
+            networkServer = new NetworkListenThread(this, inetaddress, port);
         }
         catch (IOException ioexception)
         {
@@ -787,6 +815,15 @@ public class MinecraftServer
         {
             LOGGER.warn( "Unexpected exception while parsing console command", exception);
         }
+
+        if (ticksSinceLastBroadcast++ >= 30) {
+            ticksSinceLastBroadcast = 0;
+            try {
+                multicastPublisher.multicast(String.format("[MOTD]%s[/MOTD][AD]%s[/AD][NAME]%s[/NAME]", motd, propertyManager.getIntProperty("server-port", 25565), name));
+            } catch (IOException e) {
+                LOGGER.error("", e);
+            }
+        }
     }
 
     public void addCommand(String s, ICommandListener icommandlistener)
@@ -821,6 +858,7 @@ public class MinecraftServer
         boolean nogui = false;
         Boolean offline = null;
         String world = null;
+        int port = -1;
 
         int pointer = 0;
         while (pointer < args.length) {
@@ -846,6 +884,14 @@ public class MinecraftServer
                     pointer++;
                     break;
                 }
+                case "--port": {
+                    try {
+                        port = Integer.parseInt(args[pointer + 1]);
+                        pointer++;
+                    } catch (final Exception e) {
+                        e.printStackTrace();
+                    }
+                }
                 default:
                     pointer++;
             }
@@ -857,6 +903,7 @@ public class MinecraftServer
 
             server.argOffline = offline;
             server.argWorld = world;
+            server.argPort = port;
 
             if (!nogui && !java.awt.GraphicsEnvironment.isHeadless())
             {
diff --git a/game/server/src/main/java/net/minecraft/server/net/MulticastPublisher.java b/game/server/src/main/java/net/minecraft/server/net/MulticastPublisher.java
new file mode 100644
index 000000000..b15f66ef9
--- /dev/null
+++ b/game/server/src/main/java/net/minecraft/server/net/MulticastPublisher.java
@@ -0,0 +1,24 @@
+package net.minecraft.server.net;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
+
+public class MulticastPublisher {
+    private DatagramSocket socket;
+    private InetAddress group;
+    private byte[] buf;
+
+    public void multicast(String multicastMessage) throws IOException {
+        socket = new DatagramSocket();
+        group = InetAddress.getByName("224.0.2.61");
+        buf = StandardCharsets.UTF_8.encode(multicastMessage).array();
+
+        DatagramPacket packet
+            = new DatagramPacket(buf, buf.length, group, 4445);
+        socket.send(packet);
+        socket.close();
+    }
+}
diff --git a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerLogin.java b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerLogin.java
index 1726a51bf..0b45bd9fb 100644
--- a/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerLogin.java
+++ b/game/server/src/main/java/net/minecraft/server/net/handler/PacketHandlerLogin.java
@@ -5,6 +5,7 @@ import net.minecraft.core.Global;
 import net.minecraft.core.data.registry.Registries;
 import net.minecraft.core.data.registry.recipe.RecipeEntryBase;
 import net.minecraft.core.entity.EntityDispatcher;
+import net.minecraft.core.entity.player.Player;
 import net.minecraft.core.sound.SoundTypes;
 import net.minecraft.server.net.command.ServerCommandSource;
 import net.minecraft.core.net.handler.PacketHandler;
@@ -238,35 +239,69 @@ public class PacketHandlerLogin extends PacketHandler
 
     @Override
     public void handlePingHandshake(PacketPingHandshake pingHandshakePacket) {
-        String msg = "";
-        if (pingHandshakePacket.pingHostString.equals("MC|PingHost")) {
-            msg =
-                "§1" +
-                '\0' +
-                0 +
-                '\0' +
-                "BTA " + Global.VERSION +
-                '\0' +
-                mcServer.motd +
-                '\0' +
-                0 +
-                '\0' +
-                0;
-        } else if (pingHandshakePacket.pingHostString.equals("BTAPingHost")) {
-            msg =
-                "§1" +
-                '\0' +
-                Global.MP_PROTOCOL_VERSION +
-                '\0' +
-                Global.VERSION +
-                '\0' +
-                mcServer.motd +
-                '\0' +
-                mcServer.playerList.playerEntities.size() +
-                '\0' +
-                mcServer.maxPlayers;
+        StringBuilder msg = new StringBuilder();
+        switch (pingHandshakePacket.pingHostString) {
+            case "MC|PingHost":
+                msg
+                    .append("§1").append('\0')
+                    .append(0).append('\0')
+                    .append("BTA ").append(Global.VERSION).append('\0')
+                    .append(mcServer.motd).append('\0')
+                    .append(0).append('\0')
+                    .append(0);
+                break;
+            case "BTAPingHost":
+                switch (pingHandshakePacket.payload) {
+                    case 0x02: {
+                        msg
+                            .append("§1").append('\0')
+                            .append(Global.MP_PROTOCOL_VERSION).append('\0')
+                            .append(Global.VERSION).append('\0')
+                            .append(mcServer.motd).append('\0')
+                            .append(mcServer.playerList.playerEntities.size()).append('\0')
+                            .append(mcServer.maxPlayers).append('\0')
+                            .append(mcServer.name).append('\0');
+                        if (mcServer.broadcastPlayerList) {
+                            for (Player player : mcServer.playerList.playerEntities) {
+                                msg.append(player.username).append(",");
+                            }
+                        }
+                        break;
+                    }
+                    case 0x03: {
+                        msg
+                            .append("§1").append('\0')
+                            .append(Global.MP_PROTOCOL_VERSION).append('\0')
+                            .append(Global.VERSION).append('\0')
+                            .append(mcServer.motd).append('\0')
+                            .append(mcServer.playerList.playerEntities.size()).append('\0')
+                            .append(mcServer.maxPlayers).append('\0')
+                            .append(mcServer.name).append('\0');
+                        if (mcServer.broadcastPlayerList) {
+                            for (Player player : mcServer.playerList.playerEntities) {
+                                msg.append(player.username).append(",");
+                            }
+                        }
+                        if (mcServer.iconBytes != null) {
+                            netManager.addToSendQueue(new PacketServerIcon(mcServer.iconBytes));
+                        }
+                        break;
+                    }
+                    case 0x01:
+                    default: {
+                        msg
+                            .append("§1").append('\0')
+                            .append(Global.MP_PROTOCOL_VERSION).append('\0')
+                            .append(Global.VERSION).append('\0')
+                            .append(mcServer.motd).append('\0')
+                            .append(mcServer.playerList.playerEntities.size()).append('\0')
+                            .append(mcServer.maxPlayers);
+                        break;
+                    }
+                }
+                break;
         }
-        kickUser(msg, true);
+        kickUser(msg.toString(), true);
     }
 
     @Override
