package net.md_5.bungee.connection;
import com.google.common.base.Preconditions;
import java.util.ArrayList;
import java.util.List;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.Util;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.TabCompleteEvent;
import net.md_5.bungee.forge.ForgeConstants;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.ClientSettings;
import net.md_5.bungee.protocol.packet.KeepAlive;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.protocol.packet.TabCompleteRequest;
import net.md_5.bungee.protocol.packet.TabCompleteResponse;
public class UpstreamBridge extends PacketHandler
{
private final ProxyServer bungee;
private final UserConnection con;
public UpstreamBridge(ProxyServer bungee, UserConnection con)
{
this.bungee = bungee;
this.con = con;
BungeeCord.getInstance().addConnection( con );
con.getTabListHandler().onConnect();
con.unsafe().sendPacket( BungeeCord.getInstance().registerChannels() );
}
@Override
public void exception(Throwable t) throws Exception
{
con.disconnect( Util.exception( t ) );
}
@Override
public void disconnected(ChannelWrapper channel) throws Exception
{
// We lost connection to the client
PlayerDisconnectEvent event = new PlayerDisconnectEvent( con );
bungee.getPluginManager().callEvent( event );
con.getTabListHandler().onDisconnect();
BungeeCord.getInstance().removeConnection( con );
if ( con.getServer() != null )
{
// Manually remove from everyone's tab list
// since the packet from the server arrives
// too late
// TODO: This should only done with server_unique
// tab list (which is the only one supported
// currently)
PlayerListItem packet = new PlayerListItem();
packet.setAction( PlayerListItem.Action.REMOVE_PLAYER );
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUuid( con.getUniqueId() );
packet.setItems( new PlayerListItem.Item[]
{
item
} );
for ( ProxiedPlayer player : con.getServer().getInfo().getPlayers() )
{
player.unsafe().sendPacket( packet );
}
con.getServer().disconnect( "Quitting" );
}
}
@Override
public boolean shouldHandle(PacketWrapper packet) throws Exception
{
return con.getServer() != null || packet.packet instanceof PluginMessage;
}
@Override
public void handle(PacketWrapper packet) throws Exception
{
if ( con.getServer() != null )
{
con.getEntityRewrite().rewriteServerbound( packet.buf, con.getClientEntityId(), con.getServerEntityId() );
con.getServer().getCh().write( packet );
}
}
@Override
public void handle(KeepAlive alive) throws Exception
{
if ( alive.getRandomId() == con.getSentPingId() )
{
int newPing = (int) ( System.currentTimeMillis() - con.getSentPingTime() );
con.getTabListHandler().onPingChange( newPing );
con.setPing( newPing );
}
}
@Override
public void handle(Chat chat) throws Exception
{
int maxLength = ( con.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_11 ) ? 256 : 100;
Preconditions.checkArgument( chat.getMessage().length() <= maxLength, "Chat message too long" ); // Mojang limit, check on updates
ChatEvent chatEvent = new ChatEvent( con, con.getServer(), chat.getMessage() );
if ( !bungee.getPluginManager().callEvent( chatEvent ).isCancelled() )
{
chat.setMessage( chatEvent.getMessage() );
if ( !chatEvent.isCommand() || !bungee.getPluginManager().dispatchCommand( con, chat.getMessage().substring( 1 ) ) )
{
con.getServer().unsafe().sendPacket( chat );
}
}
throw CancelSendSignal.INSTANCE;
}
@Override
public void handle(TabCompleteRequest tabComplete) throws Exception
{
List<String> suggestions = new ArrayList<>();
if ( tabComplete.getCursor().startsWith( "/" ) )
{
bungee.getPluginManager().dispatchCommand( con, tabComplete.getCursor().substring( 1 ), suggestions );
}
TabCompleteEvent tabCompleteEvent = new TabCompleteEvent( con, con.getServer(), tabComplete.getCursor(), suggestions );
bungee.getPluginManager().callEvent( tabCompleteEvent );
if ( tabCompleteEvent.isCancelled() )
{
throw CancelSendSignal.INSTANCE;
}
List<String> results = tabCompleteEvent.getSuggestions();
if ( !results.isEmpty() )
{
con.unsafe().sendPacket( new TabCompleteResponse( results ) );
throw CancelSendSignal.INSTANCE;
}
}
@Override
public void handle(ClientSettings settings) throws Exception
{
con.setSettings( settings );
}
@Override
public void handle(PluginMessage pluginMessage) throws Exception
{
if ( pluginMessage.getTag().equals( "BungeeCord" ) )
{
throw CancelSendSignal.INSTANCE;
}
// Hack around Forge race conditions
if ( pluginMessage.getTag().equals( "FML" ) && pluginMessage.getStream().readUnsignedByte() == 1 )
{
throw CancelSendSignal.INSTANCE;
}
// We handle forge handshake messages if forge support is enabled.
if ( pluginMessage.getTag().equals( ForgeConstants.FML_HANDSHAKE_TAG ) )
{
// Let our forge client handler deal with this packet.
con.getForgeClientHandler().handle( pluginMessage );
throw CancelSendSignal.INSTANCE;
}
if ( con.getServer() != null && !con.getServer().isForgeServer() && pluginMessage.getData().length > Short.MAX_VALUE )
{
// Drop the packet if the server is not a Forge server and the message was > 32kiB (as suggested by @jk-5)
// Do this AFTER the mod list, so we get that even if the intial server isn't modded.
throw CancelSendSignal.INSTANCE;
}
PluginMessageEvent event = new PluginMessageEvent( con, con.getServer(), pluginMessage.getTag(), pluginMessage.getData().clone() );
if ( bungee.getPluginManager().callEvent( event ).isCancelled() )
{
throw CancelSendSignal.INSTANCE;
}
// TODO: Unregister as well?
if ( PluginMessage.SHOULD_RELAY.apply( pluginMessage ) )
{
con.getPendingConnection().getRelayMessages().add( pluginMessage );
}
}
@Override
public String toString()
{
return "[" + con.getName() + "] -> UpstreamBridge";
}
}