Add support for Minecraft 1.8.x

This commit allows BungeeCord to support Minecraft clients both of versions 1.7.x and of 1.8.x. There should be no breakages to any other support, however following their deprecation and uselessness within 1.8, the Tab list APIs have been removed.

Please report any issues to GitHub and be sure to mention client, server and BungeeCord versions.

When used with an appropriate server jar (such as multi protocol Spigot), this will allow clients of many versions to concurrently be connected to the same set of servers.
This commit is contained in:
Thinkofdeath 2014-08-31 09:24:38 +10:00
parent e99bbff22e
commit 26521cf2ff
38 changed files with 857 additions and 533 deletions

View File

@ -15,7 +15,6 @@ import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.scheduler.TaskScheduler;
import net.md_5.bungee.api.tab.CustomTabList;
public abstract class ProxyServer
{
@ -260,14 +259,6 @@ public abstract class ProxyServer
*/
public abstract void broadcast(BaseComponent message);
/**
* Gets a new instance of this proxies custom tab list.
*
* @param player the player to generate this list in the context of
* @return a new {@link CustomTabList} instance
*/
public abstract CustomTabList customTabList(ProxiedPlayer player);
/**
* Gets the commands which are disabled and will not be run on this proxy.
*

View File

@ -3,7 +3,6 @@ package net.md_5.bungee.api.config;
import java.net.InetSocketAddress;
import java.util.Map;
import lombok.Data;
import net.md_5.bungee.api.tab.TabListHandler;
/**
* Class representing the configuration of a server listener. Used for allowing
@ -49,12 +48,9 @@ public class ListenerInfo
*/
private final Map<String, String> forcedHosts;
/**
* Class used to build tab lists for this player.
*
* @deprecated Future Minecraft versions render this API useless
* The type of tab list to use
*/
@Deprecated
private final Class<? extends TabListHandler> tabList;
private final String tabListType;
/**
* Whether to set the local address when connecting to servers.
*/

View File

@ -4,7 +4,6 @@ import java.util.Locale;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.tab.TabListHandler;
import java.util.UUID;
/**
@ -86,25 +85,6 @@ public interface ProxiedPlayer extends Connection, CommandSender
*/
void chat(String message);
/**
* Sets the new tab list for the user. At this stage it is not advisable to
* change after the user has logged in!
*
* @param list the new list
* @deprecated Future Minecraft versions render this API useless
*/
@Deprecated
void setTabList(TabListHandler list);
/**
* Get the current tab list.
*
* @return the tab list in use by this user
* @deprecated Future Minecraft versions render this API useless
*/
@Deprecated
TabListHandler getTabList();
/**
* Get the server which this player will be sent to next time the log in.
*

View File

@ -1,63 +0,0 @@
package net.md_5.bungee.api.tab;
/**
* Represents a custom tab list, which may have slots manipulated.
*
* @deprecated Future Minecraft versions render this API useless
*/
@Deprecated
public interface CustomTabList extends TabListHandler
{
/**
* Blank out this tab list and update immediately.
*/
void clear();
/**
* Gets the columns in this list.
*
* @return the width of this list
*/
int getColumns();
/**
* Gets the rows in this list.
*
* @return the height of this list
*/
int getRows();
/**
* Get the total size of this list.
*
* @return {@link #getRows()} * {@link #getColumns()}
*/
int getSize();
/**
* Set the text in the specified slot and update immediately.
*
* @param row the row to set
* @param column the column to set
* @param text the text to set
* @return the padded text
*/
String setSlot(int row, int column, String text);
/**
* Set the text in the specified slot.
*
* @param row the row to set
* @param column the column to set
* @param text the text to set
* @param update whether or not to invoke {@link #update()} upon completion
* @return the padded text
*/
String setSlot(int row, int column, String text, boolean update);
/**
* Flush all queued changes to the user.
*/
void update();
}

View File

@ -1,43 +0,0 @@
package net.md_5.bungee.api.tab;
import lombok.Getter;
import lombok.NoArgsConstructor;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* @deprecated Future Minecraft versions render this API useless
*/
@Deprecated
@NoArgsConstructor
public abstract class TabListAdapter implements TabListHandler
{
@Getter
private ProxiedPlayer player;
@Override
public void init(ProxiedPlayer player)
{
this.player = player;
}
@Override
public void onConnect()
{
}
@Override
public void onDisconnect()
{
}
@Override
public void onServerChange()
{
}
@Override
public void onPingChange(int ping)
{
}
}

View File

@ -1,53 +0,0 @@
package net.md_5.bungee.api.tab;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* @deprecated Future Minecraft versions render this API useless
*/
@Deprecated
public interface TabListHandler
{
/**
* Called so that this class may set member fields to keep track of its
* internal state. You should not do any packet sending or manipulation of
* the passed player, other than storing it.
*
* @param player the player to be associated with this list
*/
void init(ProxiedPlayer player);
/**
* Called when this player first connects to the proxy.
*/
void onConnect();
/**
* Called when a player first connects to the proxy.
*/
void onServerChange();
/**
* Called when a players ping changes. The new ping will have not updated in
* the player instance until this method returns.
*
* @param ping the player's new ping.
*/
void onPingChange(int ping);
/**
* Called when a player disconnects.
*/
void onDisconnect();
/**
* Called when a list update packet is sent from server to client.
*
* @param name the player which this packet is relevant to
* @param online whether the subject player is online
* @param ping ping of the subject player
* @return whether to send the packet to the client
*/
boolean onListUpdate(String name, boolean online, int ping);
}

View File

@ -7,6 +7,7 @@ import net.md_5.bungee.protocol.packet.Login;
import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.EncryptionRequest;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.SetCompression;
import net.md_5.bungee.protocol.packet.TabCompleteRequest;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
@ -128,4 +129,8 @@ public abstract class AbstractPacketHandler
public void handle(LegacyHandshake legacyHandshake) throws Exception
{
}
public void handle(SetCompression setCompression) throws Exception
{
}
}

View File

@ -5,6 +5,8 @@ import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf;
import lombok.RequiredArgsConstructor;
import java.util.UUID;
@RequiredArgsConstructor
public abstract class DefinedPacket
{
@ -29,7 +31,7 @@ public abstract class DefinedPacket
return new String( b, Charsets.UTF_8 );
}
public static void writeArray(byte[] b, ByteBuf buf)
public static void writeArrayLegacy(byte[] b, ByteBuf buf)
{
Preconditions.checkArgument( b.length <= Short.MAX_VALUE, "Cannot send array longer than Short.MAX_VALUE (got %s bytes)", b.length );
@ -37,7 +39,7 @@ public abstract class DefinedPacket
buf.writeBytes( b );
}
public static byte[] readArray(ByteBuf buf)
public static byte[] readArrayLegacy(ByteBuf buf)
{
short len = buf.readShort();
Preconditions.checkArgument( len <= Short.MAX_VALUE, "Cannot receive array longer than Short.MAX_VALUE (got %s bytes)", len );
@ -47,6 +49,19 @@ public abstract class DefinedPacket
return ret;
}
public static void writeArray(byte[] b, ByteBuf buf)
{
writeVarInt( b.length, buf );
buf.writeBytes( b );
}
public static byte[] readArray(ByteBuf buf)
{
byte[] ret = new byte[ readVarInt( buf ) ];
buf.readBytes( ret );
return ret;
}
public static void writeStringArray(String[] s, ByteBuf buf)
{
writeVarInt( s.length, buf );
@ -114,6 +129,17 @@ public abstract class DefinedPacket
}
}
public static void writeUUID(UUID value, ByteBuf output)
{
output.writeLong( value.getMostSignificantBits() );
output.writeLong( value.getLeastSignificantBits() );
}
public static UUID readUUID(ByteBuf input)
{
return new UUID( input.readLong(), input.readLong() );
}
public void read(ByteBuf buf)
{
throw new UnsupportedOperationException( "Packet must implement read method" );

View File

@ -0,0 +1,43 @@
package net.md_5.bungee.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import lombok.Setter;
import java.util.zip.Deflater;
public class PacketCompressor extends MessageToByteEncoder<ByteBuf>
{
private final byte[] buffer = new byte[ 8192 ];
private final Deflater deflater = new Deflater();
@Setter
private int threshold = 256;
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception
{
int origSize = msg.readableBytes();
if ( origSize < threshold )
{
DefinedPacket.writeVarInt( 0, out );
out.writeBytes( msg );
} else
{
byte[] data = new byte[ origSize ];
msg.readBytes( data );
DefinedPacket.writeVarInt( data.length, out );
deflater.setInput( data );
deflater.finish();
while ( !deflater.finished() )
{
int count = deflater.deflate( buffer );
out.writeBytes( buffer, 0, count );
}
deflater.reset();
}
}
}

View File

@ -0,0 +1,40 @@
package net.md_5.bungee.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
import java.util.zip.Inflater;
public class PacketDecompressor extends ByteToMessageDecoder
{
private final Inflater inflater = new Inflater();
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception
{
if ( in.readableBytes() == 0 )
{
return;
}
int size = DefinedPacket.readVarInt( in );
if ( size == 0 )
{
out.add( in.readBytes( in.readableBytes() ) );
} else
{
byte[] compressedData = new byte[ in.readableBytes() ];
in.readBytes( compressedData );
inflater.setInput( compressedData );
byte[] data = new byte[ size ];
inflater.inflate( data );
out.add( Unpooled.wrappedBuffer( data ) );
inflater.reset();
}
}
}

View File

@ -25,6 +25,7 @@ import net.md_5.bungee.protocol.packet.Respawn;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.SetCompression;
import net.md_5.bungee.protocol.packet.StatusRequest;
import net.md_5.bungee.protocol.packet.StatusResponse;
import net.md_5.bungee.protocol.packet.TabCompleteRequest;
@ -59,6 +60,7 @@ public enum Protocol
TO_CLIENT.registerPacket( 0x3E, Team.class );
TO_CLIENT.registerPacket( 0x3F, PluginMessage.class );
TO_CLIENT.registerPacket( 0x40, Kick.class );
TO_CLIENT.registerPacket( 0x46, SetCompression.class );
TO_SERVER.registerPacket( 0x00, KeepAlive.class );
TO_SERVER.registerPacket( 0x01, Chat.class );
@ -87,6 +89,7 @@ public enum Protocol
TO_CLIENT.registerPacket( 0x00, Kick.class );
TO_CLIENT.registerPacket( 0x01, EncryptionRequest.class );
TO_CLIENT.registerPacket( 0x02, LoginSuccess.class );
TO_CLIENT.registerPacket( 0x03, SetCompression.class );
TO_SERVER.registerPacket( 0x00, LoginRequest.class );
TO_SERVER.registerPacket( 0x01, EncryptionResponse.class );
@ -94,7 +97,11 @@ public enum Protocol
};
/*========================================================================*/
public static final int MAX_PACKET_ID = 0xFF;
public static List<Integer> supportedVersions = Arrays.asList( ProtocolConstants.MINECRAFT_1_7_2, ProtocolConstants.MINECRAFT_1_7_6, ProtocolConstants.MINECRAFT_14_11_a );
public static List<Integer> supportedVersions = Arrays.asList(
ProtocolConstants.MINECRAFT_1_7_2,
ProtocolConstants.MINECRAFT_1_7_6,
ProtocolConstants.MINECRAFT_SNAPSHOT
);
/*========================================================================*/
public final DirectionData TO_SERVER = new DirectionData( ProtocolConstants.Direction.TO_SERVER );
public final DirectionData TO_CLIENT = new DirectionData( ProtocolConstants.Direction.TO_CLIENT );

View File

@ -5,7 +5,7 @@ public class ProtocolConstants
public static final int MINECRAFT_1_7_2 = 4;
public static final int MINECRAFT_1_7_6 = 5;
public static final int MINECRAFT_14_11_a = 14;
public static final int MINECRAFT_SNAPSHOT = 46;
public enum Direction
{

View File

@ -28,7 +28,7 @@ public class Chat extends DefinedPacket
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
message = readString( buf );
if ( direction == ProtocolConstants.Direction.TO_CLIENT && protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( direction == ProtocolConstants.Direction.TO_CLIENT && protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
position = buf.readByte();
}
@ -38,7 +38,7 @@ public class Chat extends DefinedPacket
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
writeString( message, buf );
if ( direction == ProtocolConstants.Direction.TO_CLIENT && protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( direction == ProtocolConstants.Direction.TO_CLIENT && protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
buf.writeByte( position );
}

View File

@ -7,6 +7,7 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
@Data
@NoArgsConstructor
@ -20,19 +21,33 @@ public class EncryptionRequest extends DefinedPacket
private byte[] verifyToken;
@Override
public void read(ByteBuf buf)
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
serverId = readString( buf );
publicKey = readArray( buf );
verifyToken = readArray( buf );
if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT )
{
publicKey = readArrayLegacy( buf );
verifyToken = readArrayLegacy( buf );
} else
{
publicKey = readArray( buf );
verifyToken = readArray( buf );
}
}
@Override
public void write(ByteBuf buf)
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
writeString( serverId, buf );
writeArray( publicKey, buf );
writeArray( verifyToken, buf );
if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT )
{
writeArrayLegacy( publicKey, buf );
writeArrayLegacy( verifyToken, buf );
} else
{
writeArray( publicKey, buf );
writeArray( verifyToken, buf );
}
}
@Override

View File

@ -7,6 +7,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.ProtocolConstants;
@Data
@NoArgsConstructor
@ -19,17 +20,31 @@ public class EncryptionResponse extends DefinedPacket
private byte[] verifyToken;
@Override
public void read(ByteBuf buf)
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
sharedSecret = readArray( buf );
verifyToken = readArray( buf );
if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT )
{
sharedSecret = readArrayLegacy( buf );
verifyToken = readArrayLegacy( buf );
} else
{
sharedSecret = readArray( buf );
verifyToken = readArray( buf );
}
}
@Override
public void write(ByteBuf buf)
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
writeArray( sharedSecret, buf );
writeArray( verifyToken, buf );
if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT )
{
writeArrayLegacy( sharedSecret, buf );
writeArrayLegacy( verifyToken, buf );
} else
{
writeArray( sharedSecret, buf );
writeArray( verifyToken, buf );
}
}
@Override

View File

@ -21,7 +21,7 @@ public class KeepAlive extends DefinedPacket
@Override
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
if ( direction == ProtocolConstants.Direction.TO_SERVER && protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
randomId = readVarInt( buf );
} else
@ -33,7 +33,7 @@ public class KeepAlive extends DefinedPacket
@Override
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
if ( direction == ProtocolConstants.Direction.TO_SERVER && protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
writeVarInt( randomId, buf );
} else

View File

@ -7,6 +7,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.ProtocolConstants;
@Data
@NoArgsConstructor
@ -21,9 +22,10 @@ public class Login extends DefinedPacket
private short difficulty;
private short maxPlayers;
private String levelType;
private boolean reducedDebugInfo;
@Override
public void read(ByteBuf buf)
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
entityId = buf.readInt();
gameMode = buf.readUnsignedByte();
@ -31,10 +33,14 @@ public class Login extends DefinedPacket
difficulty = buf.readUnsignedByte();
maxPlayers = buf.readUnsignedByte();
levelType = readString( buf );
if ( protocolVersion >= 29 )
{
reducedDebugInfo = buf.readBoolean();
}
}
@Override
public void write(ByteBuf buf)
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
buf.writeInt( entityId );
buf.writeByte( gameMode );
@ -42,6 +48,10 @@ public class Login extends DefinedPacket
buf.writeByte( difficulty );
buf.writeByte( maxPlayers );
writeString( levelType, buf );
if ( protocolVersion >= 29 )
{
buf.writeBoolean( reducedDebugInfo );
}
}
@Override

View File

@ -2,49 +2,144 @@ package net.md_5.bungee.protocol.packet;
import net.md_5.bungee.protocol.DefinedPacket;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.ProtocolConstants;
import java.util.UUID;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class PlayerListItem extends DefinedPacket
{
private String username;
private boolean online;
private int ping;
private Action action;
private Item[] items;
@Override
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
username = readString( buf );
online = buf.readBoolean();
if ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT )
{
ping = readVarInt( buf );
items = new Item[ 1 ];
Item item = items[ 0 ] = new Item();
item.displayName = item.username = readString( buf );
action = !buf.readBoolean() ? Action.REMOVE_PLAYER : Action.ADD_PLAYER;
item.ping = buf.readShort();
} else
{
ping = buf.readShort();
action = Action.values()[ DefinedPacket.readVarInt( buf )];
items = new Item[ DefinedPacket.readVarInt( buf ) ];
for ( int i = 0; i < items.length; i++ )
{
Item item = items[ i ] = new Item();
item.setUuid( DefinedPacket.readUUID( buf ) );
switch ( action )
{
case ADD_PLAYER:
item.username = DefinedPacket.readString( buf );
item.properties = new String[ DefinedPacket.readVarInt( buf ) ][];
for ( int j = 0; j < item.properties.length; j++ )
{
String name = DefinedPacket.readString( buf );
String value = DefinedPacket.readString( buf );
if ( buf.readBoolean() )
{
item.properties[ j] = new String[]
{
name, value, DefinedPacket.readString( buf )
};
} else
{
item.properties[ j ] = new String[]
{
name, value
};
}
}
item.gamemode = DefinedPacket.readVarInt( buf );
item.ping = DefinedPacket.readVarInt( buf );
if ( buf.readBoolean() )
{
item.displayName = DefinedPacket.readString( buf );
}
break;
case UPDATE_GAMEMODE:
item.gamemode = DefinedPacket.readVarInt( buf );
break;
case UPDATE_LATENCY:
item.ping = DefinedPacket.readVarInt( buf );
break;
case UPDATE_DISPLAY_NAME:
if ( buf.readBoolean() )
{
item.displayName = DefinedPacket.readString( buf );
}
}
}
}
}
@Override
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
writeString( username, buf );
buf.writeBoolean( online );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT )
{
writeVarInt( ping, buf );
Item item = items[0]; // Only one at a time
writeString( item.displayName, buf );
buf.writeBoolean( action != Action.REMOVE_PLAYER );
buf.writeShort( item.ping );
} else
{
buf.writeShort( ping );
DefinedPacket.writeVarInt( action.ordinal(), buf );
DefinedPacket.writeVarInt( items.length, buf );
for ( Item item : items )
{
DefinedPacket.writeUUID( item.uuid, buf );
switch ( action )
{
case ADD_PLAYER:
DefinedPacket.writeString( item.username, buf );
DefinedPacket.writeVarInt( item.properties.length, buf );
for ( String[] prop : item.properties )
{
DefinedPacket.writeString( prop[ 0], buf );
DefinedPacket.writeString( prop[ 1], buf );
if ( prop.length >= 3 )
{
buf.writeBoolean( true );
DefinedPacket.writeString( prop[ 2], buf );
} else
{
buf.writeBoolean( false );
}
}
DefinedPacket.writeVarInt( item.gamemode, buf );
DefinedPacket.writeVarInt( item.ping, buf );
buf.writeBoolean( item.displayName != null );
if ( item.displayName != null )
{
DefinedPacket.writeString( item.displayName, buf );
}
break;
case UPDATE_GAMEMODE:
DefinedPacket.writeVarInt( item.gamemode, buf );
break;
case UPDATE_LATENCY:
DefinedPacket.writeVarInt( item.ping, buf );
break;
case UPDATE_DISPLAY_NAME:
buf.writeBoolean( item.displayName != null );
if ( item.displayName != null )
{
DefinedPacket.writeString( item.displayName, buf );
}
break;
}
}
}
}
@ -53,4 +148,36 @@ public class PlayerListItem extends DefinedPacket
{
handler.handle( this );
}
public static enum Action
{
ADD_PLAYER,
UPDATE_GAMEMODE,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
REMOVE_PLAYER;
}
@Data
public static class Item
{
// ALL
private UUID uuid;
// ADD_PLAYER
private String username;
private String[][] properties;
// ADD_PLAYER & UPDATE_GAMEMODE
private int gamemode;
// ADD_PLAYER & UPDATE_LATENCY
private int ping;
// ADD_PLAYER & UPDATE_DISPLAY_NAME
private String displayName;
}
}

View File

@ -12,6 +12,7 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.MinecraftInput;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.ProtocolConstants;
@Data
@NoArgsConstructor
@ -24,17 +25,30 @@ public class PluginMessage extends DefinedPacket
private byte[] data;
@Override
public void read(ByteBuf buf)
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
tag = readString( buf );
data = readArray( buf );
if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT )
{
data = readArrayLegacy( buf );
} else
{
data = new byte[ buf.readableBytes() ];
buf.readBytes( data );
}
}
@Override
public void write(ByteBuf buf)
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
writeString( tag, buf );
writeArray( data, buf );
if ( protocolVersion < ProtocolConstants.MINECRAFT_SNAPSHOT )
{
writeArrayLegacy( data, buf );
} else
{
buf.writeBytes( data );
}
}
@Override

View File

@ -33,7 +33,7 @@ public class ScoreboardObjective extends DefinedPacket
value = readString( buf );
}
action = buf.readByte();
if ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a && ( action == 0 || action == 2 ) )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT && ( action == 0 || action == 2 ) )
{
value = readString( buf );
type = readString( buf );
@ -49,7 +49,7 @@ public class ScoreboardObjective extends DefinedPacket
writeString( value, buf );
}
buf.writeByte( action );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a && ( action == 0 || action == 2 ) )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT && ( action == 0 || action == 2 ) )
{
writeString( value, buf );
writeString( type, buf );

View File

@ -29,14 +29,18 @@ public class ScoreboardScore extends DefinedPacket
{
itemName = readString( buf );
action = buf.readByte();
if ( action != 1 )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
scoreName = readString( buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( action != 1 )
{
value = readVarInt( buf );
} else
}
} else
{
if ( action != 1 )
{
scoreName = readString( buf );
value = buf.readInt();
}
}
@ -47,14 +51,18 @@ public class ScoreboardScore extends DefinedPacket
{
writeString( itemName, buf );
buf.writeByte( action );
if ( action != 1 )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
writeString( scoreName, buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( action != 1 )
{
writeVarInt( value, buf );
} else
}
} else
{
if ( action != 1 )
{
writeString( scoreName, buf );
buf.writeInt( value );
}
}

View File

@ -0,0 +1,38 @@
package net.md_5.bungee.protocol.packet;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class SetCompression extends DefinedPacket
{
private int threshold;
@Override
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
threshold = DefinedPacket.readVarInt( buf );
}
@Override
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
DefinedPacket.writeVarInt( threshold, buf );
}
@Override
public void handle(AbstractPacketHandler handler) throws Exception
{
handler.handle( this );
}
}

View File

@ -7,6 +7,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.ProtocolConstants;
@Data
@NoArgsConstructor
@ -16,17 +17,34 @@ public class TabCompleteRequest extends DefinedPacket
{
private String cursor;
private boolean hasPositon;
private long position;
@Override
public void read(ByteBuf buf)
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
cursor = readString( buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
if ( hasPositon = buf.readBoolean() )
{
position = buf.readLong();
}
}
}
@Override
public void write(ByteBuf buf)
public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
{
writeString( cursor, buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
buf.writeBoolean( hasPositon );
if ( hasPositon )
{
buf.writeLong( position );
}
}
}
@Override

View File

@ -50,7 +50,7 @@ public class Team extends DefinedPacket
prefix = readString( buf );
suffix = readString( buf );
friendlyFire = buf.readByte();
if ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
nameTagVisibility = readString( buf );
color = buf.readByte();
@ -58,7 +58,7 @@ public class Team extends DefinedPacket
}
if ( mode == 0 || mode == 3 || mode == 4 )
{
int len = ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a ) ? readVarInt( buf ) : buf.readShort();
int len = ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT ) ? readVarInt( buf ) : buf.readShort();
players = new String[ len ];
for ( int i = 0; i < len; i++ )
{
@ -78,7 +78,7 @@ public class Team extends DefinedPacket
writeString( prefix, buf );
writeString( suffix, buf );
buf.writeByte( friendlyFire );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
writeString( nameTagVisibility, buf );
buf.writeByte( color );
@ -86,7 +86,7 @@ public class Team extends DefinedPacket
}
if ( mode == 0 || mode == 3 || mode == 4 )
{
if ( protocolVersion >= ProtocolConstants.MINECRAFT_14_11_a )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
writeVarInt( players.length, buf );
} else

View File

@ -36,6 +36,7 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
@ -65,7 +66,6 @@ import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.api.tab.CustomTabList;
import net.md_5.bungee.command.*;
import net.md_5.bungee.conf.YamlConfig;
import net.md_5.bungee.log.LoggingOutputStream;
@ -76,7 +76,6 @@ import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.query.RemoteQuery;
import net.md_5.bungee.tab.Custom;
import net.md_5.bungee.util.CaseInsensitiveMap;
import org.fusesource.jansi.AnsiConsole;
@ -113,6 +112,8 @@ public class BungeeCord extends ProxyServer
* Fully qualified connections.
*/
private final Map<String, UserConnection> connections = new CaseInsensitiveMap<>();
// Used to help with packet rewriting
private final Map<UUID, UserConnection> connectionsByOfflineUUID = new HashMap<>();
private final ReadWriteLock connectionLock = new ReentrantReadWriteLock();
/**
* Plugin manager.
@ -144,7 +145,7 @@ public class BungeeCord extends ProxyServer
private ConnectionThrottle connectionThrottle;
private final ModuleManager moduleManager = new ModuleManager();
{
// TODO: Proper fallback when we interface the manager
getPluginManager().registerCommand( null, new CommandReload() );
@ -468,6 +469,18 @@ public class BungeeCord extends ProxyServer
}
}
public UserConnection getPlayerByOfflineUUID(UUID name)
{
connectionLock.readLock().lock();
try
{
return connectionsByOfflineUUID.get( name );
} finally
{
connectionLock.readLock().unlock();
}
}
@Override
public ProxiedPlayer getPlayer(UUID uuid)
{
@ -577,6 +590,7 @@ public class BungeeCord extends ProxyServer
try
{
connections.put( con.getName(), con );
connectionsByOfflineUUID.put( con.getPendingConnection().getOfflineId(), con );
} finally
{
connectionLock.writeLock().unlock();
@ -589,19 +603,13 @@ public class BungeeCord extends ProxyServer
try
{
connections.remove( con.getName() );
connectionsByOfflineUUID.remove( con.getPendingConnection().getOfflineId() );
} finally
{
connectionLock.writeLock().unlock();
}
}
@Override
public CustomTabList customTabList(ProxiedPlayer player)
{
return new Custom( player );
}
@Override
public Collection<String> getDisabledCommands()
{
return config.getDisabledCommands();

View File

@ -31,6 +31,7 @@ import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.protocol.packet.LoginSuccess;
import net.md_5.bungee.protocol.packet.SetCompression;
@RequiredArgsConstructor
public class ServerConnector extends PacketHandler
@ -102,6 +103,13 @@ public class ServerConnector extends PacketHandler
throw CancelSendSignal.INSTANCE;
}
@Override
public void handle(SetCompression setCompression) throws Exception
{
user.setCompressionThreshold( setCompression.getThreshold() );
ch.setCompressionThreshold( setCompression.getThreshold() );
}
@Override
public void handle(Login login) throws Exception
{
@ -139,7 +147,7 @@ public class ServerConnector extends PacketHandler
// Set tab list size, this sucks balls, TODO: what shall we do about packet mutability
Login modLogin = new Login( login.getEntityId(), login.getGameMode(), (byte) login.getDimension(), login.getDifficulty(),
(byte) user.getPendingConnection().getListener().getTabListSize(), login.getLevelType() );
(byte) user.getPendingConnection().getListener().getTabListSize(), login.getLevelType(), login.isReducedDebugInfo() );
user.unsafe().sendPacket( modLogin );
@ -148,7 +156,7 @@ public class ServerConnector extends PacketHandler
user.unsafe().sendPacket( new PluginMessage( "MC|Brand", out.toArray() ) );
} else
{
user.getTabList().onServerChange();
user.getTabListHandler().onServerChange();
Scoreboard serverScoreboard = user.getServerSentScoreboard();
for ( Objective objective : serverScoreboard.getObjectives() )

View File

@ -30,7 +30,6 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PermissionCheckEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.entitymap.EntityMap;
@ -46,6 +45,11 @@ import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.ClientSettings;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.protocol.packet.SetCompression;
import net.md_5.bungee.tab.Global;
import net.md_5.bungee.tab.GlobalPing;
import net.md_5.bungee.tab.ServerUnique;
import net.md_5.bungee.tab.TabList;
import net.md_5.bungee.util.CaseInsensitiveSet;
@RequiredArgsConstructor
@ -73,8 +77,6 @@ public final class UserConnection implements ProxiedPlayer
private final Collection<ServerInfo> pendingConnects = new HashSet<>();
/*========================================================================*/
@Getter
private TabListHandler tabList;
@Getter
@Setter
private int sentPingId;
@Getter
@ -86,6 +88,13 @@ public final class UserConnection implements ProxiedPlayer
@Getter
@Setter
private ServerInfo reconnectServer;
@Getter
private TabList tabListHandler;
@Getter
@Setter
private int gamemode;
@Getter
private int compressionThreshold = -1;
/*========================================================================*/
private final Collection<String> groups = new CaseInsensitiveSet();
private final Collection<String> permissions = new CaseInsensitiveSet();
@ -121,14 +130,19 @@ public final class UserConnection implements ProxiedPlayer
this.entityRewrite = EntityMap.getEntityMap( getPendingConnection().getVersion() );
this.displayName = name;
try
switch ( getPendingConnection().getListener().getTabListType() )
{
this.tabList = getPendingConnection().getListener().getTabList().getDeclaredConstructor().newInstance();
} catch ( ReflectiveOperationException ex )
{
throw new RuntimeException( ex );
case "GLOBAL":
tabListHandler = new Global( this );
break;
case "SERVER":
tabListHandler = new ServerUnique( this );
break;
default:
tabListHandler = new GlobalPing( this );
break;
}
this.tabList.init( this );
Collection<String> g = bungee.getConfigurationAdapter().getGroups( name );
for ( String s : g )
@ -137,13 +151,6 @@ public final class UserConnection implements ProxiedPlayer
}
}
@Override
public void setTabList(TabListHandler tabList)
{
tabList.init( this );
this.tabList = tabList;
}
public void sendPacket(PacketWrapper packet)
{
ch.write( packet );
@ -160,9 +167,7 @@ public final class UserConnection implements ProxiedPlayer
{
Preconditions.checkNotNull( name, "displayName" );
Preconditions.checkArgument( name.length() <= 16, "Display name cannot be longer than 16 characters" );
getTabList().onDisconnect();
displayName = name;
getTabList().onConnect();
}
@Override
@ -461,4 +466,14 @@ public final class UserConnection implements ProxiedPlayer
{
return ( locale == null && settings != null ) ? locale = Locale.forLanguageTag( settings.getLocale().replaceAll( "_", "-" ) ) : locale;
}
public void setCompressionThreshold(int compressionThreshold)
{
if ( this.compressionThreshold == -1 )
{
this.compressionThreshold = compressionThreshold;
unsafe.sendPacket( new SetCompression( compressionThreshold ) );
ch.setCompressionThreshold( compressionThreshold );
}
}
}

View File

@ -22,10 +22,6 @@ import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ConfigurationAdapter;
import net.md_5.bungee.api.config.ListenerInfo;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.tab.TabListHandler;
import net.md_5.bungee.tab.Global;
import net.md_5.bungee.tab.GlobalPing;
import net.md_5.bungee.tab.ServerUnique;
import net.md_5.bungee.util.CaseInsensitiveMap;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
@ -40,8 +36,7 @@ public class YamlConfig implements ConfigurationAdapter
private enum DefaultTabList
{
GLOBAL( Global.class ), GLOBAL_PING( GlobalPing.class ), SERVER( ServerUnique.class );
private final Class<? extends TabListHandler> clazz;
GLOBAL(), GLOBAL_PING(), SERVER();
}
private final Yaml yaml;
private Map config;
@ -224,7 +219,7 @@ public class YamlConfig implements ConfigurationAdapter
boolean query = get( "query_enabled", false, val );
int queryPort = get( "query_port", 25577, val );
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, forced, value.clazz, setLocalAddress, pingPassthrough, queryPort, query );
ListenerInfo info = new ListenerInfo( address, motd, maxPlayers, tabListSize, defaultServer, fallbackServer, forceDefault, forced, value.toString(), setLocalAddress, pingPassthrough, queryPort, query );
ret.add( info );
}

View File

@ -31,6 +31,8 @@ import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.protocol.packet.SetCompression;
import net.md_5.bungee.tab.TabList;
@RequiredArgsConstructor
public class DownstreamBridge extends PacketHandler
@ -100,11 +102,8 @@ public class DownstreamBridge extends PacketHandler
@Override
public void handle(PlayerListItem playerList) throws Exception
{
if ( !con.getTabList().onListUpdate( playerList.getUsername(), playerList.isOnline(), playerList.getPing() ) )
{
throw CancelSendSignal.INSTANCE;
}
con.getTabListHandler().onUpdate( TabList.rewrite( playerList ) );
throw CancelSendSignal.INSTANCE; // Always throw because of profile rewriting
}
@Override
@ -448,6 +447,13 @@ public class DownstreamBridge extends PacketHandler
throw CancelSendSignal.INSTANCE;
}
@Override
public void handle(SetCompression setCompression) throws Exception
{
con.setCompressionThreshold( setCompression.getThreshold() );
server.getCh().setCompressionThreshold( setCompression.getThreshold() );
}
@Override
public String toString()
{

View File

@ -34,7 +34,7 @@ public class UpstreamBridge extends PacketHandler
this.con = con;
BungeeCord.getInstance().addConnection( con );
con.getTabList().onConnect();
con.getTabListHandler().onConnect();
con.unsafe().sendPacket( BungeeCord.getInstance().registerChannels() );
}
@ -50,7 +50,7 @@ public class UpstreamBridge extends PacketHandler
// We lost connection to the client
PlayerDisconnectEvent event = new PlayerDisconnectEvent( con );
bungee.getPluginManager().callEvent( event );
con.getTabList().onDisconnect();
con.getTabListHandler().onDisconnect();
BungeeCord.getInstance().removeConnection( con );
if ( con.getServer() != null )
@ -62,10 +62,7 @@ public class UpstreamBridge extends PacketHandler
@Override
public void handle(PacketWrapper packet) throws Exception
{
if ( con.getPendingConnection().getVersion() <= ProtocolConstants.MINECRAFT_1_7_6 )
{
con.getEntityRewrite().rewriteServerbound( packet.buf, con.getClientEntityId(), con.getServerEntityId() );
}
con.getEntityRewrite().rewriteServerbound( packet.buf, con.getClientEntityId(), con.getServerEntityId() );
if ( con.getServer() != null )
{
con.getServer().getCh().write( packet );
@ -78,7 +75,7 @@ public class UpstreamBridge extends PacketHandler
if ( alive.getRandomId() == con.getSentPingId() )
{
int newPing = (int) ( System.currentTimeMillis() - con.getSentPingTime() );
con.getTabList().onPingChange( newPing );
con.getTabListHandler().onPingChange( newPing );
con.setPing( newPing );
}
}

View File

@ -30,8 +30,8 @@ public abstract class EntityMap
return new EntityMap_1_7_2();
case ProtocolConstants.MINECRAFT_1_7_6:
return new EntityMap_1_7_6();
case ProtocolConstants.MINECRAFT_14_11_a:
return new EntityMap_14_11_a();
case ProtocolConstants.MINECRAFT_SNAPSHOT:
return new EntityMap_14_21_b();
}
throw new RuntimeException( "Version " + version + " has no entity map" );
}

View File

@ -4,14 +4,15 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.buffer.ByteBuf;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
import java.util.UUID;
class EntityMap_14_11_a extends EntityMap
class EntityMap_14_21_b extends EntityMap
{
EntityMap_14_11_a()
EntityMap_14_21_b()
{
addRewrite( 0x04, ProtocolConstants.Direction.TO_CLIENT, true ); // Entity Equipment
addRewrite( 0x0A, ProtocolConstants.Direction.TO_CLIENT, true ); // Use bed
@ -38,6 +39,7 @@ class EntityMap_14_11_a extends EntityMap
addRewrite( 0x25, ProtocolConstants.Direction.TO_CLIENT, true ); // Block Break Animation
addRewrite( 0x2C, ProtocolConstants.Direction.TO_CLIENT, true ); // Spawn Global Entity
addRewrite( 0x43, ProtocolConstants.Direction.TO_CLIENT, true ); // Camera
addRewrite( 0x49, ProtocolConstants.Direction.TO_CLIENT, true ); // Update Entity NBT
addRewrite( 0x02, ProtocolConstants.Direction.TO_SERVER, true ); // Use Entity
addRewrite( 0x0B, ProtocolConstants.Direction.TO_SERVER, true ); // Entity Action
@ -84,23 +86,24 @@ class EntityMap_14_11_a extends EntityMap
}
} else if ( packetId == 0x0E /* Spawn Object */ )
{
DefinedPacket.readVarInt( packet );
int idLength = packet.readerIndex() - readerIndex - packetIdLength;
int type = packet.getByte( readerIndex + packetIdLength + idLength );
DefinedPacket.readVarInt( packet );
int type = packet.readUnsignedByte();
if ( type == 60 || type == 90 )
{
int readId = packet.getInt( packetIdLength + idLength + 15 );
packet.skipBytes( 14 );
int position = packet.readerIndex();
int readId = packet.readInt();
int changedId = -1;
if ( readId == oldId )
{
packet.setInt( packetIdLength + idLength + 15, newId );
packet.setInt( position, newId );
changedId = newId;
} else if ( readId == newId )
{
packet.setInt( packetIdLength + idLength + 15, oldId );
changedId = newId;
packet.setInt( position, oldId );
changedId = oldId;
}
if ( changedId != -1 )
{
@ -118,36 +121,17 @@ class EntityMap_14_11_a extends EntityMap
}
} else if ( packetId == 0x0C /* Spawn Player */ )
{
DefinedPacket.readVarInt( packet );
DefinedPacket.readVarInt( packet ); // Entity ID
int idLength = packet.readerIndex() - readerIndex - packetIdLength;
String uuid = DefinedPacket.readString( packet );
String username = DefinedPacket.readString( packet );
int props = DefinedPacket.readVarInt( packet );
if ( props == 0 )
UUID uuid = DefinedPacket.readUUID( packet );
ProxiedPlayer player;
if ( ( player = BungeeCord.getInstance().getPlayerByOfflineUUID( uuid ) ) != null )
{
UserConnection player = (UserConnection) BungeeCord.getInstance().getPlayer( username );
if ( player != null )
{
LoginResult profile = player.getPendingConnection().getLoginProfile();
if ( profile != null && profile.getProperties() != null
&& profile.getProperties().length >= 1 )
{
ByteBuf rest = packet.slice().copy();
packet.readerIndex( readerIndex );
packet.writerIndex( readerIndex + packetIdLength + idLength );
DefinedPacket.writeString( player.getUniqueId().toString(), packet );
DefinedPacket.writeString( username, packet );
DefinedPacket.writeVarInt( profile.getProperties().length, packet );
for ( LoginResult.Property property : profile.getProperties() )
{
DefinedPacket.writeString( property.getName(), packet );
DefinedPacket.writeString( property.getValue(), packet );
DefinedPacket.writeString( property.getSignature(), packet );
}
packet.writeBytes( rest );
rest.release();
}
}
int previous = packet.writerIndex();
packet.readerIndex( readerIndex );
packet.writerIndex( readerIndex + packetIdLength + idLength );
DefinedPacket.writeUUID( player.getUniqueId(), packet );
packet.writerIndex( previous );
}
} else if ( packetId == 0x42 /* Combat Event */ )
{
@ -167,4 +151,29 @@ class EntityMap_14_11_a extends EntityMap
}
packet.readerIndex( readerIndex );
}
@Override
public void rewriteServerbound(ByteBuf packet, int oldId, int newId)
{
super.rewriteServerbound( packet, oldId, newId );
//Special cases
int readerIndex = packet.readerIndex();
int packetId = DefinedPacket.readVarInt( packet );
int packetIdLength = packet.readerIndex() - readerIndex;
if ( packetId == 0x18 /* Spectate */ )
{
UUID uuid = DefinedPacket.readUUID( packet );
ProxiedPlayer player;
if ( ( player = BungeeCord.getInstance().getPlayer( uuid ) ) != null )
{
int previous = packet.writerIndex();
packet.readerIndex( readerIndex );
packet.writerIndex( readerIndex + packetIdLength );
DefinedPacket.writeUUID( ( (UserConnection) player ).getPendingConnection().getOfflineId(), packet );
packet.writerIndex( previous );
}
}
packet.readerIndex( readerIndex );
}
}

View File

@ -1,5 +1,7 @@
package net.md_5.bungee.netty;
import net.md_5.bungee.protocol.PacketCompressor;
import net.md_5.bungee.protocol.PacketDecompressor;
import net.md_5.bungee.protocol.PacketWrapper;
import com.google.common.base.Preconditions;
import io.netty.channel.Channel;
@ -71,4 +73,28 @@ public class ChannelWrapper
{
return ch;
}
public void setCompressionThreshold(int compressionThreshold)
{
if ( ch.pipeline().get( PacketCompressor.class ) == null && compressionThreshold != -1 )
{
addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor() );
}
if ( compressionThreshold != -1 )
{
ch.pipeline().get( PacketCompressor.class ).setThreshold( compressionThreshold );
} else
{
ch.pipeline().remove( "compress" );
}
if ( ch.pipeline().get( PacketDecompressor.class ) == null && compressionThreshold != -1 )
{
addBefore( PipelineUtils.PACKET_DECODER, "decompress", new PacketDecompressor() );
}
if ( compressionThreshold == -1 )
{
ch.pipeline().remove( "decompress" );
}
}
}

View File

@ -1,158 +0,0 @@
package net.md_5.bungee.tab;
import com.google.common.base.Preconditions;
import java.util.Collection;
import java.util.HashSet;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.tab.CustomTabList;
import net.md_5.bungee.api.tab.TabListAdapter;
import net.md_5.bungee.protocol.packet.PlayerListItem;
public class Custom extends TabListAdapter implements CustomTabList
{
private static final int ROWS = 20;
private static final int COLUMNS = 3;
private static final char[] FILLER = new char[]
{
'0', '1', '2', '2', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
};
private static final int MAX_LEN = 16;
/*========================================================================*/
private final Collection<String> sentStuff = new HashSet<>();
/*========================================================================*/
private final String[][] sent = new String[ ROWS ][ COLUMNS ];
private final String[][] slots = new String[ ROWS ][ COLUMNS ];
private int rowLim;
private int colLim;
public Custom(ProxiedPlayer player)
{
this.init( player );
}
@Override
public synchronized String setSlot(int row, int column, String text)
{
return setSlot( row, column, text, true );
}
@Override
public synchronized String setSlot(int row, int column, String text, boolean update)
{
Preconditions.checkArgument( row > 0 && row <= ROWS, "row out of range" );
Preconditions.checkArgument( column > 0 && column <= COLUMNS, "column out of range" );
if ( text != null )
{
Preconditions.checkArgument( text.length() <= MAX_LEN - 2, "text must be <= %s chars", MAX_LEN - 2 );
Preconditions.checkArgument( !ChatColor.stripColor( text ).isEmpty(), "Text cannot consist entirely of colour codes" );
text = attempt( text );
sentStuff.add( text );
if ( rowLim < row || colLim < column )
{
rowLim = row;
colLim = column;
}
}
slots[--row][--column] = text;
if ( update )
{
update();
}
return text;
}
private String attempt(String s)
{
for ( char c : FILLER )
{
String attempt = s + Character.toString( ChatColor.COLOR_CHAR ) + c;
if ( !sentStuff.contains( attempt ) )
{
return attempt;
}
}
if ( s.length() <= MAX_LEN - 4 )
{
return attempt( s + Character.toString( ChatColor.COLOR_CHAR ) + FILLER[0] );
}
throw new IllegalArgumentException( "List already contains all variants of string" );
}
@Override
public synchronized void update()
{
clear();
for ( int i = 0; i < rowLim; i++ )
{
for ( int j = 0; j < colLim; j++ )
{
String text = ( slots[i][j] != null ) ? slots[i][j] : new StringBuilder().append( base( i ) ).append( base( j ) ).toString();
sent[i][j] = text;
getPlayer().unsafe().sendPacket( new PlayerListItem( text, true, (short) 0 ) );
}
}
}
@Override
public synchronized void clear()
{
for ( int i = 0; i < rowLim; i++ )
{
for ( int j = 0; j < colLim; j++ )
{
if ( sent[i][j] != null )
{
String text = sent[i][j];
sent[i][j] = null;
getPlayer().unsafe().sendPacket( new PlayerListItem( text, false, (short) 9999 ) );
}
}
}
}
@Override
public synchronized int getRows()
{
return ROWS;
}
@Override
public synchronized int getColumns()
{
return COLUMNS;
}
@Override
public synchronized int getSize()
{
return ROWS * COLUMNS;
}
@Override
public boolean onListUpdate(String name, boolean online, int ping)
{
return false;
}
private static char[] base(int n)
{
String hex = Integer.toHexString( n + 1 );
char[] alloc = new char[ hex.length() * 2 ];
for ( int i = 0; i < alloc.length; i++ )
{
if ( i % 2 == 0 )
{
alloc[i] = ChatColor.COLOR_CHAR;
} else
{
alloc[i] = hex.charAt( i / 2 );
}
}
return alloc;
}
}

View File

@ -1,24 +1,30 @@
package net.md_5.bungee.tab;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.tab.TabListAdapter;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.PlayerListItem;
public class Global extends TabListAdapter
import java.util.Collection;
public class Global extends TabList
{
private boolean sentPing;
@Override
public void onConnect()
public Global(ProxiedPlayer player)
{
for ( ProxiedPlayer p : ProxyServer.getInstance().getPlayers() )
{
getPlayer().unsafe().sendPacket( new PlayerListItem( p.getDisplayName(), true, (short) p.getPing() ) );
}
BungeeCord.getInstance().broadcast( new PlayerListItem( getPlayer().getDisplayName(), true, (short) getPlayer().getPing() ) );
super( player );
}
@Override
public void onUpdate(PlayerListItem playerListItem)
{
}
@Override
@ -27,19 +33,112 @@ public class Global extends TabListAdapter
if ( !sentPing )
{
sentPing = true;
BungeeCord.getInstance().broadcast( new PlayerListItem( getPlayer().getDisplayName(), true, (short) getPlayer().getPing() ) );
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.UPDATE_LATENCY );
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid( player.getUniqueId() );
item.setUsername( player.getName() );
item.setDisplayName( ComponentSerializer.toString( TextComponent.fromLegacyText( player.getDisplayName() ) ) );
item.setPing( player.getPing() );
packet.setItems( new PlayerListItem.Item[]
{
item
} );
BungeeCord.getInstance().broadcast( packet );
}
}
@Override
public void onServerChange()
{
}
@Override
public void onConnect()
{
PlayerListItem playerListItem = new PlayerListItem();
playerListItem.setAction( PlayerListItem.Action.ADD_PLAYER );
Collection<ProxiedPlayer> players = BungeeCord.getInstance().getPlayers();
PlayerListItem.Item[] items = new PlayerListItem.Item[ players.size() ];
playerListItem.setItems( items );
int i = 0;
for ( ProxiedPlayer p : players )
{
PlayerListItem.Item item = items[i++] = new PlayerListItem.Item();
item.setUuid( p.getUniqueId() );
item.setUsername( p.getName() );
item.setDisplayName( ComponentSerializer.toString( TextComponent.fromLegacyText( p.getDisplayName() ) ) );
LoginResult loginResult = ( (UserConnection) p ).getPendingConnection().getLoginProfile();
String[][] props = new String[ loginResult.getProperties().length ][];
for ( int j = 0; j < props.length; j++ )
{
props[ j] = new String[]
{
loginResult.getProperties()[j].getName(),
loginResult.getProperties()[j].getValue(),
loginResult.getProperties()[j].getSignature()
};
}
item.setProperties( props );
item.setGamemode( ( (UserConnection) p ).getGamemode() );
item.setPing( p.getPing() );
}
if ( player.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
player.unsafe().sendPacket( playerListItem );
} else
{
// Split up the packet
for ( PlayerListItem.Item item : playerListItem.getItems() )
{
PlayerListItem packet = new PlayerListItem();
packet.setAction( playerListItem.getAction() );
PlayerListItem.Item[] it = new PlayerListItem.Item[ 1 ];
it[0] = item;
packet.setItems( it );
player.unsafe().sendPacket( packet );
}
}
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.ADD_PLAYER );
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid( player.getUniqueId() );
item.setUsername( player.getName() );
item.setDisplayName( ComponentSerializer.toString( TextComponent.fromLegacyText( player.getDisplayName() ) ) );
LoginResult loginResult = ( (UserConnection) player ).getPendingConnection().getLoginProfile();
String[][] props = new String[ loginResult.getProperties().length ][];
for ( int j = 0; j < props.length; j++ )
{
props[ j] = new String[]
{
loginResult.getProperties()[j].getName(),
loginResult.getProperties()[j].getValue(),
loginResult.getProperties()[j].getSignature()
};
}
item.setProperties( props );
item.setGamemode( ( (UserConnection) player ).getGamemode() );
item.setPing( player.getPing() );
packet.setItems( new PlayerListItem.Item[]
{
item
} );
BungeeCord.getInstance().broadcast( packet );
}
@Override
public void onDisconnect()
{
BungeeCord.getInstance().broadcast( new PlayerListItem( getPlayer().getDisplayName(), false, (short) 9999 ) );
}
@Override
public boolean onListUpdate(String name, boolean online, int ping)
{
return false;
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.REMOVE_PLAYER );
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid( player.getUniqueId() );
item.setUsername( player.getName() );
packet.setItems( new PlayerListItem.Item[]
{
item
} );
BungeeCord.getInstance().broadcast( packet );
}
}

View File

@ -1,6 +1,9 @@
package net.md_5.bungee.tab;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.protocol.packet.PlayerListItem;
public class GlobalPing extends Global
@ -10,13 +13,29 @@ public class GlobalPing extends Global
/*========================================================================*/
private int lastPing;
public GlobalPing(ProxiedPlayer player)
{
super( player );
}
@Override
public void onPingChange(int ping)
{
if ( ping - PING_THRESHOLD > lastPing && ping + PING_THRESHOLD < lastPing )
{
lastPing = ping;
BungeeCord.getInstance().broadcast( new PlayerListItem( getPlayer().getDisplayName(), true, (short) ping ) );
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.UPDATE_LATENCY );
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid( player.getUniqueId() );
item.setUsername( player.getName() );
item.setDisplayName( ComponentSerializer.toString( TextComponent.fromLegacyText( player.getDisplayName() ) ) );
item.setPing( player.getPing() );
packet.setItems( new PlayerListItem.Item[]
{
item
} );
BungeeCord.getInstance().broadcast( packet );
}
}
}

View File

@ -2,38 +2,105 @@ package net.md_5.bungee.tab;
import java.util.Collection;
import java.util.HashSet;
import net.md_5.bungee.api.tab.TabListAdapter;
import java.util.UUID;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.PlayerListItem;
public class ServerUnique extends TabListAdapter
public class ServerUnique extends TabList
{
private final Collection<String> usernames = new HashSet<>();
private final Collection<UUID> uuids = new HashSet<>();
private final Collection<String> usernames = new HashSet<>(); // Support for <=1.7.9
public ServerUnique(ProxiedPlayer player)
{
super( player );
}
@Override
public void onUpdate(PlayerListItem playerListItem)
{
for ( PlayerListItem.Item item : playerListItem.getItems() )
{
if ( playerListItem.getAction() == PlayerListItem.Action.ADD_PLAYER )
{
if ( item.getUuid() != null )
{
uuids.add( item.getUuid() );
} else
{
usernames.add( item.getUsername() );
}
} else if ( playerListItem.getAction() == PlayerListItem.Action.REMOVE_PLAYER )
{
if ( item.getUuid() != null )
{
uuids.remove( item.getUuid() );
} else
{
usernames.remove( item.getUsername() );
}
}
}
player.unsafe().sendPacket( playerListItem );
}
@Override
public void onPingChange(int ping)
{
}
@Override
public void onServerChange()
{
synchronized ( usernames )
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.REMOVE_PLAYER );
PlayerListItem.Item[] items = new PlayerListItem.Item[ uuids.size() + usernames.size() ];
int i = 0;
for ( UUID uuid : uuids )
{
for ( String username : usernames )
{
getPlayer().unsafe().sendPacket( new PlayerListItem( username, false, (short) 9999 ) );
}
usernames.clear();
PlayerListItem.Item item = items[i++] = new PlayerListItem.Item();
item.setUuid( uuid );
}
for ( String username : usernames )
{
PlayerListItem.Item item = items[i++] = new PlayerListItem.Item();
item.setUsername( username );
item.setDisplayName( username );
}
packet.setItems( items );
if ( player.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_SNAPSHOT )
{
player.unsafe().sendPacket( packet );
} else
{
// Split up the packet
for ( PlayerListItem.Item item : packet.getItems() )
{
PlayerListItem p2 = new PlayerListItem();
p2.setAction( packet.getAction() );
PlayerListItem.Item[] it = new PlayerListItem.Item[ 1 ];
it[0] = item;
p2.setItems( it );
player.unsafe().sendPacket( p2 );
}
}
uuids.clear();
usernames.clear();
}
@Override
public boolean onListUpdate(String name, boolean online, int ping)
public void onConnect()
{
}
@Override
public void onDisconnect()
{
if ( online )
{
usernames.add( name );
} else
{
usernames.remove( name );
}
return true;
}
}

View File

@ -0,0 +1,59 @@
package net.md_5.bungee.tab;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.packet.PlayerListItem;
@RequiredArgsConstructor
public abstract class TabList
{
protected final ProxiedPlayer player;
public abstract void onUpdate(PlayerListItem playerListItem);
public abstract void onPingChange(int ping);
public abstract void onServerChange();
public abstract void onConnect();
public abstract void onDisconnect();
public static PlayerListItem rewrite(PlayerListItem playerListItem)
{
for ( PlayerListItem.Item item : playerListItem.getItems() )
{
if ( item.getUuid() == null ) // Old style ping
{
continue;
}
UserConnection player = BungeeCord.getInstance().getPlayerByOfflineUUID( item.getUuid() );
if ( player != null )
{
item.setUuid( player.getUniqueId() );
LoginResult loginResult = player.getPendingConnection().getLoginProfile();
String[][] props = new String[ loginResult.getProperties().length ][];
for ( int i = 0; i < props.length; i++ )
{
props[ i] = new String[]
{
loginResult.getProperties()[i].getName(),
loginResult.getProperties()[i].getValue(),
loginResult.getProperties()[i].getSignature()
};
}
item.setProperties( props );
if ( playerListItem.getAction() == PlayerListItem.Action.ADD_PLAYER || playerListItem.getAction() == PlayerListItem.Action.UPDATE_GAMEMODE )
{
player.setGamemode( item.getGamemode() );
}
player.setPing( player.getPing() );
}
}
return playerListItem;
}
}