From 47134b43ccf840d86d40a32441807ae3c49986c6 Mon Sep 17 00:00:00 2001 From: "Kristian S. Stangeland" Date: Thu, 31 Oct 2013 01:55:55 +0100 Subject: [PATCH] Adding automatic GamePhase detection. This should solve the infamous UNKNOWN ORIGIN problem, especially in MCPC++. --- .../protocol/events/ListenerOptions.java | 8 +++ .../protocol/events/ListeningWhitelist.java | 2 +- .../protocol/injector/LoginPackets.java | 71 +++++++++++++++++++ .../injector/PacketFilterManager.java | 27 +++++-- .../injector/packet/ProxyPacketInjector.java | 8 +-- .../protocol/utility/MinecraftVersion.java | 20 ++++++ 6 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 ProtocolLib/src/main/java/com/comphenix/protocol/injector/LoginPackets.java diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java index 224197e6..e38f32cb 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListenerOptions.java @@ -1,5 +1,7 @@ package com.comphenix.protocol.events; +import com.comphenix.protocol.injector.GamePhase; + /** * Represents additional options a listener may require. * @@ -10,4 +12,10 @@ public enum ListenerOptions { * Retrieve the serialized client packet as it appears on the network stream. */ INTERCEPT_INPUT_BUFFER, + + /** + * Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when + * a packet ID is known to be transmitted during login. + */ + DISABLE_GAMEPHASE_DETECTION; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java index 61ab901c..f163ac2a 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/events/ListeningWhitelist.java @@ -143,7 +143,7 @@ public class ListeningWhitelist { public GamePhase getGamePhase() { return gamePhase; } - + /** * Retrieve every special option associated with this whitelist. * @return Every special option. diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/LoginPackets.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/LoginPackets.java new file mode 100644 index 00000000..5178abef --- /dev/null +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/LoginPackets.java @@ -0,0 +1,71 @@ +package com.comphenix.protocol.injector; + +import org.bukkit.Bukkit; + +import com.comphenix.protocol.Packets; +import com.comphenix.protocol.concurrency.IntegerSet; +import com.comphenix.protocol.events.ConnectionSide; +import com.comphenix.protocol.utility.MinecraftVersion; + +/** + * Packets that are known to be transmitted during login. + *

+ * This may be dynamically extended later. + * @author Kristian + */ +class LoginPackets { + private IntegerSet clientSide = new IntegerSet(Packets.PACKET_COUNT); + private IntegerSet serverSide = new IntegerSet(Packets.PACKET_COUNT); + + public LoginPackets(MinecraftVersion version) { + // Ordinary login + clientSide.add(Packets.Client.HANDSHAKE); + serverSide.add(Packets.Server.KEY_REQUEST); + clientSide.add(Packets.Client.KEY_RESPONSE); + serverSide.add(Packets.Server.KEY_RESPONSE); + clientSide.add(Packets.Client.CLIENT_COMMAND); + serverSide.add(Packets.Server.LOGIN); + + // List ping + clientSide.add(Packets.Client.GET_INFO); + + // In 1.6.2, Minecraft started sending CUSTOM_PAYLOAD in the server list protocol + if (version.compareTo(MinecraftVersion.HORSE_UPDATE) >= 0) { + clientSide.add(Packets.Client.CUSTOM_PAYLOAD); + } + serverSide.add(Packets.Server.KICK_DISCONNECT); + + // MCPC++ contains Forge, which uses packet 250 during login + if (isMCPC()) { + clientSide.add(Packets.Client.CUSTOM_PAYLOAD); + } + } + + /** + * Determine if we are runnign MCPC. + * @return TRUE if we are, FALSE otherwise. + */ + private static boolean isMCPC() { + return Bukkit.getServer().getVersion().contains("MCPC-Plus"); + } + + /** + * Determine if a packet may be sent during login from a given direction. + * @param packetId - the ID of the packet. + * @param side - the direction. + * @return TRUE if it may, FALSE otherwise. + */ + public boolean isLoginPacket(int packetId, ConnectionSide side) { + switch (side) { + case CLIENT_SIDE: + return clientSide.contains(packetId); + case SERVER_SIDE: + return serverSide.contains(packetId); + case BOTH: + return clientSide.contains(packetId) || + serverSide.contains(packetId); + default: + throw new IllegalArgumentException("Unknown connection side: " + side); + } + } +} diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 1f2ff123..140b82ce 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -190,6 +190,9 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // The current Minecraft version private MinecraftVersion minecraftVersion; + // Login packets + private LoginPackets loginPackets; + /** * Only create instances of this class if protocol lib is disabled. */ @@ -222,6 +225,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // The plugin verifier this.pluginVerifier = new PluginVerifier(builder.getLibrary()); this.minecraftVersion = builder.getMinecraftVersion(); + this.loginPackets = new LoginPackets(minecraftVersion); // The write packet interceptor this.interceptWritePacket = new InterceptWritePacket(classLoader, reporter); @@ -367,7 +371,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok playerInjection.checkListener(listener); } if (hasSending) - incrementPhases(sending.getGamePhase()); + incrementPhases(processPhase(sending, ConnectionSide.SERVER_SIDE)); // Handle receivers after senders if (hasReceiving) { @@ -376,7 +380,7 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok enablePacketFilters(listener, ConnectionSide.CLIENT_SIDE, receiving.getWhitelist()); } if (hasReceiving) - incrementPhases(receiving.getGamePhase()); + incrementPhases(processPhase(receiving, ConnectionSide.CLIENT_SIDE)); // Inform our injected hooks packetListeners.add(listener); @@ -384,6 +388,20 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok } } + private GamePhase processPhase(ListeningWhitelist whitelist, ConnectionSide side) { + // Determine if this is a login packet, ensuring that gamephase detection is enabled + if (!whitelist.getGamePhase().hasLogin() && + !whitelist.getOptions().contains(ListenerOptions.DISABLE_GAMEPHASE_DETECTION)) { + + for (int id : whitelist.getWhitelist()) { + if (loginPackets.isLoginPacket(id, side)) { + return GamePhase.BOTH; + } + } + } + return whitelist.getGamePhase(); + } + /** * Invoked when we need to update the input buffer set. */ @@ -483,11 +501,11 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok // Remove listeners and phases if (sending != null && sending.isEnabled()) { sendingRemoved = sendingListeners.removeListener(listener, sending); - decrementPhases(sending.getGamePhase()); + decrementPhases(processPhase(sending, ConnectionSide.SERVER_SIDE)); } if (receiving != null && receiving.isEnabled()) { receivingRemoved = recievedListeners.removeListener(listener, receiving); - decrementPhases(receiving.getGamePhase()); + decrementPhases(processPhase(receiving, ConnectionSide.CLIENT_SIDE)); } // Remove hooks, if needed @@ -500,7 +518,6 @@ public final class PacketFilterManager implements ProtocolManager, ListenerInvok @Override public void removePacketListeners(Plugin plugin) { - // Iterate through every packet listener for (PacketListener listener : packetListeners) { // Remove the listener diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java index 22ef72dc..9cce756b 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/injector/packet/ProxyPacketInjector.java @@ -32,7 +32,6 @@ import net.sf.cglib.proxy.Factory; import net.sf.cglib.proxy.CallbackFilter; import net.sf.cglib.proxy.NoOp; -import com.comphenix.protocol.Packets; import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; @@ -57,7 +56,7 @@ import com.comphenix.protocol.wrappers.WrappedIntHashMap; */ class ProxyPacketInjector implements PacketInjector { public static final ReportType REPORT_CANNOT_FIND_READ_PACKET_METHOD = new ReportType("Cannot find read packet method for ID %s."); - public static final ReportType REPORT_UNKNOWN_ORIGIN_FOR_PACKET = new ReportType("Unknown origin %s for packet %s. Are you using GamePhase.LOGIN?"); + public static final ReportType REPORT_UNKNOWN_ORIGIN_FOR_PACKET = new ReportType("Timeout: Unknown origin %s for packet %s. Are you using GamePhase.LOGIN?"); /** * Represents a way to update the packet ID to class lookup table. @@ -327,9 +326,8 @@ class ProxyPacketInjector implements PacketInjector { if (client != null) { return packetRecieved(packet, client, buffered); } else { - // Hack #2 - Caused by our server socket injector - if (packet.getID() != Packets.Client.GET_INFO) - reporter.reportWarning(this, Report.newBuilder(REPORT_UNKNOWN_ORIGIN_FOR_PACKET).messageParam(input, packet.getID())); + // The timeout elapsed! + reporter.reportWarning(this, Report.newBuilder(REPORT_UNKNOWN_ORIGIN_FOR_PACKET).messageParam(input, packet.getID())); return null; } diff --git a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java index 5f9ccc28..24f33e54 100644 --- a/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java +++ b/ProtocolLib/src/main/java/com/comphenix/protocol/utility/MinecraftVersion.java @@ -40,6 +40,26 @@ public class MinecraftVersion implements Comparable { */ private static final String VERSION_PATTERN = ".*\\(.*MC.\\s*([a-zA-z0-9\\-\\.]+)\\s*\\)"; + /** + * Version 1.7.2 - the update that changed the world. + */ + public static final MinecraftVersion WORLD_UPDATE = new MinecraftVersion("1.7.2"); + + /** + * Version 1.6.1 - the horse update. + */ + public static final MinecraftVersion HORSE_UPDATE = new MinecraftVersion("1.6.1"); + + /** + * Version 1.5.0 - the redstone update. + */ + public static final MinecraftVersion REDSTONE_UPDATE = new MinecraftVersion("1.5.0"); + + /** + * Version 1.4.2 - the scary update (Wither Boss). + */ + public static final MinecraftVersion SCARY_UPDATE = new MinecraftVersion("1.4.2"); + private final int major; private final int minor; private final int build;