diff --git a/example/src/main/java/org/geysermc/mcprotocollib/network/example/ClientSessionListener.java b/example/src/main/java/org/geysermc/mcprotocollib/network/example/ClientSessionListener.java index 066a973e..029e6365 100644 --- a/example/src/main/java/org/geysermc/mcprotocollib/network/example/ClientSessionListener.java +++ b/example/src/main/java/org/geysermc/mcprotocollib/network/example/ClientSessionListener.java @@ -1,5 +1,6 @@ package org.geysermc.mcprotocollib.network.example; +import net.kyori.adventure.text.Component; import org.geysermc.mcprotocollib.network.Session; import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent; import org.geysermc.mcprotocollib.network.event.session.DisconnectedEvent; @@ -14,23 +15,23 @@ public class ClientSessionListener extends SessionAdapter { @Override public void packetReceived(Session session, Packet packet) { - if (packet instanceof PingPacket) { - String id = ((PingPacket) packet).getPingId(); + if (packet instanceof PingPacket pingPacket) { + String id = pingPacket.getPingId(); log.info("CLIENT Received: {}", id); if (id.equals("hello")) { session.send(new PingPacket("exit")); } else if (id.equals("exit")) { - session.disconnect("Finished"); + session.disconnect(Component.text("Finished")); } } } @Override public void packetSent(Session session, Packet packet) { - if (packet instanceof PingPacket) { - log.info("CLIENT Sent: {}", ((PingPacket) packet).getPingId()); + if (packet instanceof PingPacket pingPacket) { + log.info("CLIENT Sent: {}", pingPacket.getPingId()); } } diff --git a/example/src/main/java/org/geysermc/mcprotocollib/network/example/ServerSessionListener.java b/example/src/main/java/org/geysermc/mcprotocollib/network/example/ServerSessionListener.java index 1e9e82e8..e0352154 100644 --- a/example/src/main/java/org/geysermc/mcprotocollib/network/example/ServerSessionListener.java +++ b/example/src/main/java/org/geysermc/mcprotocollib/network/example/ServerSessionListener.java @@ -14,16 +14,16 @@ public class ServerSessionListener extends SessionAdapter { @Override public void packetReceived(Session session, Packet packet) { - if (packet instanceof PingPacket) { - log.info("SERVER Received: {}", ((PingPacket) packet).getPingId()); + if (packet instanceof PingPacket pingPacket) { + log.info("SERVER Received: {}", pingPacket.getPingId()); session.send(packet); } } @Override public void packetSent(Session session, Packet packet) { - if (packet instanceof PingPacket) { - log.info("SERVER Sent: {}", ((PingPacket) packet).getPingId()); + if (packet instanceof PingPacket pingPacket) { + log.info("SERVER Sent: {}", pingPacket.getPingId()); } } diff --git a/example/src/main/java/org/geysermc/mcprotocollib/protocol/example/MinecraftProtocolTest.java b/example/src/main/java/org/geysermc/mcprotocollib/protocol/example/MinecraftProtocolTest.java index 86234a4d..b3e7e93f 100644 --- a/example/src/main/java/org/geysermc/mcprotocollib/protocol/example/MinecraftProtocolTest.java +++ b/example/src/main/java/org/geysermc/mcprotocollib/protocol/example/MinecraftProtocolTest.java @@ -112,9 +112,9 @@ public class MinecraftProtocolTest { event.getSession().addListener(new SessionAdapter() { @Override public void packetReceived(Session session, Packet packet) { - if (packet instanceof ServerboundChatPacket) { + if (packet instanceof ServerboundChatPacket chatPacket) { GameProfile profile = event.getSession().getFlag(MinecraftConstants.PROFILE_KEY); - log.info("{}: {}", profile.getName(), ((ServerboundChatPacket) packet).getMessage()); + log.info("{}: {}", profile.getName(), chatPacket.getMessage()); Component msg = Component.text("Hello, ") .color(NamedTextColor.GREEN) @@ -207,10 +207,10 @@ public class MinecraftProtocolTest { public void packetReceived(Session session, Packet packet) { if (packet instanceof ClientboundLoginPacket) { session.send(new ServerboundChatPacket("Hello, this is a test of MCProtocolLib.", Instant.now().toEpochMilli(), 0L, null, 0, new BitSet())); - } else if (packet instanceof ClientboundSystemChatPacket) { - Component message = ((ClientboundSystemChatPacket) packet).getContent(); + } else if (packet instanceof ClientboundSystemChatPacket systemChatPacket) { + Component message = systemChatPacket.getContent(); log.info("Received Message: {}", message); - session.disconnect("Finished"); + session.disconnect(Component.text("Finished")); } } diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/network/AbstractServer.java b/protocol/src/main/java/org/geysermc/mcprotocollib/network/AbstractServer.java index 48ee44cd..02511b4e 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/network/AbstractServer.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/network/AbstractServer.java @@ -1,5 +1,6 @@ package org.geysermc.mcprotocollib.network; +import net.kyori.adventure.text.Component; import org.geysermc.mcprotocollib.network.event.server.ServerBoundEvent; import org.geysermc.mcprotocollib.network.event.server.ServerClosedEvent; import org.geysermc.mcprotocollib.network.event.server.ServerClosingEvent; @@ -119,7 +120,7 @@ public abstract class AbstractServer implements Server { public void removeSession(Session session) { this.sessions.remove(session); if (session.isConnected()) { - session.disconnect("Connection closed."); + session.disconnect(Component.translatable("disconnect.endOfStream")); } this.callEvent(new SessionRemovedEvent(this, session)); @@ -164,7 +165,7 @@ public abstract class AbstractServer implements Server { this.callEvent(new ServerClosingEvent(this)); for (Session session : this.getSessions()) { if (session.isConnected()) { - session.disconnect("Server closed."); + session.disconnect(Component.translatable("multiplayer.disconnect.server_shutdown")); } } diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/network/Session.java b/protocol/src/main/java/org/geysermc/mcprotocollib/network/Session.java index 30bfe812..7ccc0d22 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/network/Session.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/network/Session.java @@ -1,6 +1,7 @@ package org.geysermc.mcprotocollib.network; import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.mcprotocollib.network.codec.PacketCodecHelper; import org.geysermc.mcprotocollib.network.crypt.PacketEncryption; @@ -259,12 +260,39 @@ public interface Session { */ void send(Packet packet); + /** + * Disconnects the session. + * This method just wraps the reason into a {@link Component}. + * It is recommended to use Components instead as they provide more flexibility. + * + * @param reason Reason for disconnecting. + * @see #disconnect(String, Throwable) + */ + default void disconnect(@NonNull String reason) { + this.disconnect(reason, null); + } + + /** + * Disconnects the session. + * This method just wraps the reason into a {@link Component}. + * It is recommended to use Components instead as they provide more flexibility. + * + * @param reason Reason for disconnecting. + * @param cause Throwable responsible for disconnecting. + * @see #disconnect(Component, Throwable) + */ + default void disconnect(@NonNull String reason, @Nullable Throwable cause) { + this.disconnect(Component.text(reason), cause); + } + /** * Disconnects the session. * * @param reason Reason for disconnecting. */ - void disconnect(@Nullable String reason); + default void disconnect(@NonNull Component reason) { + this.disconnect(reason, null); + } /** * Disconnects the session. @@ -272,20 +300,5 @@ public interface Session { * @param reason Reason for disconnecting. * @param cause Throwable responsible for disconnecting. */ - void disconnect(@Nullable String reason, Throwable cause); - - /** - * Disconnects the session. - * - * @param reason Reason for disconnecting. - */ - void disconnect(@Nullable Component reason); - - /** - * Disconnects the session. - * - * @param reason Reason for disconnecting. - * @param cause Throwable responsible for disconnecting. - */ - void disconnect(@Nullable Component reason, Throwable cause); + void disconnect(@NonNull Component reason, @Nullable Throwable cause); } diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpClientSession.java b/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpClientSession.java index 6c1019c7..f1dc98ba 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpClientSession.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpClientSession.java @@ -28,8 +28,8 @@ import io.netty.handler.proxy.Socks5ProxyHandler; import io.netty.resolver.dns.DnsNameResolver; import io.netty.resolver.dns.DnsNameResolverBuilder; import io.netty.util.concurrent.DefaultThreadFactory; -import org.geysermc.mcprotocollib.network.ProxyInfo; import org.geysermc.mcprotocollib.network.BuiltinFlags; +import org.geysermc.mcprotocollib.network.ProxyInfo; import org.geysermc.mcprotocollib.network.codec.PacketCodecHelper; import org.geysermc.mcprotocollib.network.helper.TransportHelper; import org.geysermc.mcprotocollib.network.packet.PacketProtocol; @@ -256,11 +256,6 @@ public class TcpClientSession extends TcpSession { } } - @Override - public void disconnect(String reason, Throwable cause) { - super.disconnect(reason, cause); - } - private static void createTcpEventLoopGroup() { if (EVENT_LOOP_GROUP != null) { return; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpSession.java b/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpSession.java index f3a70e72..63b6ce29 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpSession.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpSession.java @@ -3,17 +3,15 @@ package org.geysermc.mcprotocollib.network.tcp; import io.netty.channel.Channel; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ConnectTimeoutException; import io.netty.channel.DefaultEventLoopGroup; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.timeout.ReadTimeoutException; import io.netty.handler.timeout.ReadTimeoutHandler; -import io.netty.handler.timeout.WriteTimeoutException; import io.netty.handler.timeout.WriteTimeoutHandler; import io.netty.util.concurrent.DefaultThreadFactory; import net.kyori.adventure.text.Component; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.geysermc.mcprotocollib.network.Flag; import org.geysermc.mcprotocollib.network.Session; @@ -27,7 +25,6 @@ import org.geysermc.mcprotocollib.network.event.session.SessionListener; import org.geysermc.mcprotocollib.network.packet.Packet; import org.geysermc.mcprotocollib.network.packet.PacketProtocol; -import java.net.ConnectException; import java.net.SocketAddress; import java.util.Collections; import java.util.HashMap; @@ -35,6 +32,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; public abstract class TcpSession extends SimpleChannelInboundHandler implements Session { /** @@ -279,22 +277,7 @@ public abstract class TcpSession extends SimpleChannelInboundHandler imp } @Override - public void disconnect(String reason) { - this.disconnect(Component.text(reason)); - } - - @Override - public void disconnect(String reason, Throwable cause) { - this.disconnect(Component.text(reason), cause); - } - - @Override - public void disconnect(Component reason) { - this.disconnect(reason, null); - } - - @Override - public void disconnect(final Component reason, final Throwable cause) { + public void disconnect(@NonNull Component reason, @Nullable Throwable cause) { if (this.disconnected) { return; } @@ -303,11 +286,9 @@ public abstract class TcpSession extends SimpleChannelInboundHandler imp if (this.channel != null && this.channel.isOpen()) { this.callEvent(new DisconnectingEvent(this, reason, cause)); - this.channel.flush().close().addListener((ChannelFutureListener) future -> - callEvent(new DisconnectedEvent(TcpSession.this, - reason != null ? reason : Component.text("Connection closed."), cause))); + this.channel.flush().close().addListener((ChannelFutureListener) future -> callEvent(new DisconnectedEvent(TcpSession.this, reason, cause))); } else { - this.callEvent(new DisconnectedEvent(this, reason != null ? reason : Component.text("Connection closed."), cause)); + this.callEvent(new DisconnectedEvent(this, reason, cause)); } } @@ -385,21 +366,17 @@ public abstract class TcpSession extends SimpleChannelInboundHandler imp @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (ctx.channel() == this.channel) { - this.disconnect("Connection closed."); + this.disconnect(Component.translatable("disconnect.endOfStream")); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - String message; - if (cause instanceof ConnectTimeoutException || (cause instanceof ConnectException && cause.getMessage().contains("connection timed out"))) { - message = "Connection timed out."; - } else if (cause instanceof ReadTimeoutException) { - message = "Read timed out."; - } else if (cause instanceof WriteTimeoutException) { - message = "Write timed out."; + Component message; + if (cause instanceof TimeoutException) { + message = Component.translatable("disconnect.timeout"); } else { - message = cause.toString(); + message = Component.translatable("disconnect.genericReason", Component.text("Internal Exception: " + cause)); } this.disconnect(message, cause); diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ClientListener.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ClientListener.java index 31f40b3e..01144966 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ClientListener.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ClientListener.java @@ -3,6 +3,7 @@ package org.geysermc.mcprotocollib.protocol; import lombok.AllArgsConstructor; import lombok.NonNull; import lombok.SneakyThrows; +import net.kyori.adventure.text.Component; import org.geysermc.mcprotocollib.auth.GameProfile; import org.geysermc.mcprotocollib.auth.SessionService; import org.geysermc.mcprotocollib.network.Session; @@ -77,10 +78,12 @@ public class ClientListener extends SessionAdapter { SessionService sessionService = session.getFlag(MinecraftConstants.SESSION_SERVICE_KEY, new SessionService()); String serverId = SessionService.getServerId(helloPacket.getServerId(), helloPacket.getPublicKey(), key); + + // TODO: Add generic error, disabled multiplayer and banned from playing online errors try { sessionService.joinServer(profile, accessToken, serverId); } catch (IOException e) { - session.disconnect("Login failed: Authentication error: " + e.getMessage(), e); + session.disconnect(Component.translatable("disconnect.loginFailedInfo", Component.text(e.getMessage())), e); return; } @@ -109,7 +112,7 @@ public class ClientListener extends SessionAdapter { handler.handle(session, time); } - session.disconnect("Finished"); + session.disconnect(Component.translatable("multiplayer.status.finished")); } } else if (protocol.getState() == ProtocolState.GAME) { if (packet instanceof ClientboundKeepAlivePacket keepAlivePacket && session.getFlag(MinecraftConstants.AUTOMATIC_KEEP_ALIVE_MANAGEMENT, true)) { @@ -122,7 +125,7 @@ public class ClientListener extends SessionAdapter { if (session.getFlag(MinecraftConstants.FOLLOW_TRANSFERS, true)) { TcpClientSession newSession = new TcpClientSession(transferPacket.getHost(), transferPacket.getPort(), session.getPacketProtocol()); newSession.setFlags(session.getFlags()); - session.disconnect("Transferring"); + session.disconnect(Component.translatable("disconnect.transfer")); newSession.connect(true, true); } } @@ -137,7 +140,7 @@ public class ClientListener extends SessionAdapter { if (session.getFlag(MinecraftConstants.FOLLOW_TRANSFERS, true)) { TcpClientSession newSession = new TcpClientSession(transferPacket.getHost(), transferPacket.getPort(), session.getPacketProtocol()); newSession.setFlags(session.getFlags()); - session.disconnect("Transferring"); + session.disconnect(Component.translatable("disconnect.transfer")); newSession.connect(true, true); } } diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ServerListener.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ServerListener.java index b7763e28..f63659ce 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ServerListener.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ServerListener.java @@ -98,15 +98,15 @@ public class ServerListener extends SessionAdapter { case STATUS -> protocol.setState(ProtocolState.STATUS); case TRANSFER -> { if (!session.getFlag(MinecraftConstants.ACCEPT_TRANSFERS_KEY, false)) { - session.disconnect("Server does not accept transfers."); + session.disconnect(Component.translatable("multiplayer.disconnect.transfers_disabled")); } } case LOGIN -> { protocol.setState(ProtocolState.LOGIN); if (intentionPacket.getProtocolVersion() > protocol.getCodec().getProtocolVersion()) { - session.disconnect("Outdated server! I'm still on " + protocol.getCodec().getMinecraftVersion() + "."); + session.disconnect(Component.translatable("multiplayer.disconnect.incompatible", Component.text(protocol.getCodec().getMinecraftVersion()))); } else if (intentionPacket.getProtocolVersion() < protocol.getCodec().getProtocolVersion()) { - session.disconnect("Outdated client! Please use " + protocol.getCodec().getMinecraftVersion() + "."); + session.disconnect(Component.translatable("multiplayer.disconnect.outdated_client", Component.text(protocol.getCodec().getMinecraftVersion()))); } } default -> throw new UnsupportedOperationException("Invalid client intent: " + intentionPacket.getIntent()); @@ -125,8 +125,7 @@ public class ServerListener extends SessionAdapter { PrivateKey privateKey = KEY_PAIR.getPrivate(); if (!Arrays.equals(this.challenge, keyPacket.getEncryptedChallenge(privateKey))) { - session.disconnect("Invalid challenge!"); - return; + throw new IllegalStateException("Protocol error"); } SecretKey key = keyPacket.getSecretKey(privateKey); @@ -183,6 +182,7 @@ public class ServerListener extends SessionAdapter { protocol.setState(ProtocolState.CONFIGURATION); } else if (packet instanceof ServerboundPingRequestPacket pingRequestPacket) { session.send(new ClientboundPongResponsePacket(pingRequestPacket.getPingTime())); + session.disconnect(Component.translatable("multiplayer.status.request_handled")); } } else if (protocol.getState() == ProtocolState.CONFIGURATION) { if (packet instanceof ServerboundFinishConfigurationPacket) { @@ -230,12 +230,13 @@ public class ServerListener extends SessionAdapter { try { profile = sessionService.getProfileByServer(username, SessionService.getServerId(SERVER_ID, KEY_PAIR.getPublic(), this.key)); } catch (IOException e) { - this.session.disconnect("Failed to make session service request.", e); + session.disconnect(Component.translatable("multiplayer.disconnect.authservers_down"), e); return; } if (profile == null) { - this.session.disconnect("Failed to verify username."); + session.disconnect(Component.translatable("multiplayer.disconnect.unverified_username")); + return; } } else { profile = new GameProfile(UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes()), username); diff --git a/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/MinecraftProtocolTest.java b/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/MinecraftProtocolTest.java index c56cb864..49a45c0b 100644 --- a/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/MinecraftProtocolTest.java +++ b/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/MinecraftProtocolTest.java @@ -83,11 +83,11 @@ public class MinecraftProtocolTest { session.addListener(new DisconnectListener()); session.connect(); - handler.status.await(4, SECONDS); + assertTrue(handler.status.await(4, SECONDS), "Did not receive server info in time."); assertNotNull(handler.info, "Failed to get server info."); assertEquals(SERVER_INFO, handler.info, "Received incorrect server info."); } finally { - session.disconnect("Status test complete."); + session.disconnect(Component.text("Status test complete.")); } } @@ -100,11 +100,11 @@ public class MinecraftProtocolTest { session.addListener(new DisconnectListener()); session.connect(); - listener.login.await(4, SECONDS); + assertTrue(listener.login.await(4, SECONDS), "Did not receive login packet in time."); assertNotNull(listener.packet, "Failed to log in."); assertEquals(JOIN_GAME_PACKET, listener.packet, "Received incorrect join packet."); } finally { - session.disconnect("Login test complete."); + session.disconnect(Component.text("Login test complete.")); } } @@ -125,8 +125,8 @@ public class MinecraftProtocolTest { @Override public void packetReceived(Session session, Packet packet) { - if (packet instanceof ClientboundLoginPacket) { - this.packet = (ClientboundLoginPacket) packet; + if (packet instanceof ClientboundLoginPacket loginPacket) { + this.packet = loginPacket; this.login.countDown(); } }