#2479: Allow injection of BungeeCord commands to 1.13 with inject_commands option

This commit is contained in:
md_5 2018-12-20 10:33:36 +11:00
parent 7793894621
commit 02a65e34cf
12 changed files with 768 additions and 32 deletions

View File

@ -11,6 +11,7 @@ import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -428,4 +429,14 @@ public class PluginManager
it.remove(); it.remove();
} }
} }
/**
* Get an unmodifiable collection of all registered commands.
*
* @return commands
*/
public Collection<String> getCommands()
{
return Collections.unmodifiableCollection( commandMap.keySet() );
}
} }

View File

@ -5,9 +5,9 @@ public class Bootstrap
public static void main(String[] args) throws Exception public static void main(String[] args) throws Exception
{ {
if ( Float.parseFloat( System.getProperty( "java.class.version" ) ) < 51.0 ) if ( Float.parseFloat( System.getProperty( "java.class.version" ) ) < 52.0 )
{ {
System.err.println( "*** ERROR *** BungeeCord requires Java 7 or above to function! Please download and install it!" ); System.err.println( "*** ERROR *** BungeeCord requires Java 8 or above to function! Please download and install it!" );
System.out.println( "You can check your Java version with the command: java -version" ); System.out.println( "You can check your Java version with the command: java -version" );
return; return;
} }

View File

@ -133,7 +133,7 @@
<configuration> <configuration>
<signature> <signature>
<groupId>org.codehaus.mojo.signature</groupId> <groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java17</artifactId> <artifactId>java18</artifactId>
<version>1.0</version> <version>1.0</version>
</signature> </signature>
</configuration> </configuration>

View File

@ -19,6 +19,12 @@
<description>Minimal implementation of the Minecraft protocol for use in BungeeCord</description> <description>Minimal implementation of the Minecraft protocol for use in BungeeCord</description>
<dependencies> <dependencies>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>brigadier</artifactId>
<version>1.0.16-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-chat</artifactId> <artifactId>bungeecord-chat</artifactId>

View File

@ -6,6 +6,7 @@ import net.md_5.bungee.protocol.packet.ClientSettings;
import net.md_5.bungee.protocol.packet.ClientStatus; import net.md_5.bungee.protocol.packet.ClientStatus;
import net.md_5.bungee.protocol.packet.Login; import net.md_5.bungee.protocol.packet.Login;
import net.md_5.bungee.protocol.packet.Chat; import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.Commands;
import net.md_5.bungee.protocol.packet.EncryptionRequest; import net.md_5.bungee.protocol.packet.EncryptionRequest;
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter; import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
import net.md_5.bungee.protocol.packet.PlayerListItem; import net.md_5.bungee.protocol.packet.PlayerListItem;
@ -163,4 +164,8 @@ public abstract class AbstractPacketHandler
public void handle(EntityStatus status) throws Exception public void handle(EntityStatus status) throws Exception
{ {
} }
public void handle(Commands commands) throws Exception
{
}
} }

View File

@ -73,6 +73,19 @@ public abstract class DefinedPacket
return ret; return ret;
} }
public static int[] readVarIntArray(ByteBuf buf)
{
int len = readVarInt( buf );
int[] ret = new int[ len ];
for ( int i = 0; i < len; i++ )
{
ret[i] = readVarInt( buf );
}
return ret;
}
public static void writeStringArray(List<String> s, ByteBuf buf) public static void writeStringArray(List<String> s, ByteBuf buf)
{ {
writeVarInt( s.size(), buf ); writeVarInt( s.size(), buf );

View File

@ -14,6 +14,7 @@ import lombok.RequiredArgsConstructor;
import net.md_5.bungee.protocol.packet.BossBar; import net.md_5.bungee.protocol.packet.BossBar;
import net.md_5.bungee.protocol.packet.Chat; import net.md_5.bungee.protocol.packet.Chat;
import net.md_5.bungee.protocol.packet.ClientSettings; import net.md_5.bungee.protocol.packet.ClientSettings;
import net.md_5.bungee.protocol.packet.Commands;
import net.md_5.bungee.protocol.packet.EncryptionRequest; import net.md_5.bungee.protocol.packet.EncryptionRequest;
import net.md_5.bungee.protocol.packet.EncryptionResponse; import net.md_5.bungee.protocol.packet.EncryptionResponse;
import net.md_5.bungee.protocol.packet.EntityStatus; import net.md_5.bungee.protocol.packet.EntityStatus;
@ -179,6 +180,13 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_12, 0x1B ), map( ProtocolConstants.MINECRAFT_1_12, 0x1B ),
map( ProtocolConstants.MINECRAFT_1_13, 0x1C ) map( ProtocolConstants.MINECRAFT_1_13, 0x1C )
); );
if ( Boolean.getBoolean( "net.md-5.bungee.protocol.register_commands" ) )
{
TO_CLIENT.registerPacket(
Commands.class,
map( ProtocolConstants.MINECRAFT_1_13, 0x11 )
);
}
TO_SERVER.registerPacket( TO_SERVER.registerPacket(
KeepAlive.class, KeepAlive.class,

View File

@ -0,0 +1,638 @@
package net.md_5.bungee.protocol.packet;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.FloatArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import io.netty.buffer.ByteBuf;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
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;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class Commands extends DefinedPacket
{
private static final int FLAG_TYPE = 0x3;
private static final int FLAG_EXECUTABLE = 0x4;
private static final int FLAG_REDIRECT = 0x8;
private static final int FLAG_SUGGESTIONS = 0x10;
//
private static final int NODE_ROOT = 0;
private static final int NODE_LITERAL = 1;
private static final int NODE_ARGUMENT = 2;
//
private RootCommandNode root;
@Override
public void read(ByteBuf buf)
{
int nodeCount = readVarInt( buf );
NetworkNode[] nodes = new NetworkNode[ nodeCount ];
Deque<NetworkNode> nodeQueue = new ArrayDeque<>( nodes.length );
for ( int i = 0; i < nodeCount; i++ )
{
byte flags = buf.readByte();
int[] children = readVarIntArray( buf );
int redirectNode = ( ( flags & FLAG_REDIRECT ) != 0 ) ? readVarInt( buf ) : 0;
ArgumentBuilder argumentBuilder;
switch ( flags & FLAG_TYPE )
{
case NODE_ROOT:
argumentBuilder = null;
break;
case NODE_LITERAL:
argumentBuilder = LiteralArgumentBuilder.literal( readString( buf ) );
break;
case NODE_ARGUMENT:
String name = readString( buf );
String parser = readString( buf );
argumentBuilder = RequiredArgumentBuilder.argument( name, ArgumentRegistry.read( parser, buf ) );
if ( ( flags & FLAG_SUGGESTIONS ) != 0 )
{
String suggster = readString( buf );
( (RequiredArgumentBuilder) argumentBuilder ).suggests( SuggestionRegistry.getProvider( suggster ) );
}
break;
default:
throw new IllegalArgumentException( "Unhandled node type " + flags );
}
NetworkNode node = new NetworkNode( argumentBuilder, flags, redirectNode, children );
nodes[i] = node;
nodeQueue.add( node );
}
boolean mustCycle;
do
{
if ( nodeQueue.isEmpty() )
{
int rootIndex = readVarInt( buf );
root = (RootCommandNode<?>) nodes[rootIndex].command;
return;
}
mustCycle = false;
for ( Iterator<NetworkNode> iter = nodeQueue.iterator(); iter.hasNext(); )
{
NetworkNode node = iter.next();
if ( node.buildSelf( nodes ) )
{
iter.remove();
mustCycle = true;
}
}
} while ( mustCycle );
throw new IllegalStateException( "Did not finish building root node" );
}
@Override
public void write(ByteBuf buf)
{
Map<CommandNode, Integer> indexMap = new LinkedHashMap<>();
Deque<CommandNode> nodeQueue = new ArrayDeque<>();
nodeQueue.add( root );
while ( !nodeQueue.isEmpty() )
{
CommandNode command = nodeQueue.pollFirst();
if ( !indexMap.containsKey( command ) )
{
// Index the new node
int currentIndex = indexMap.size();
indexMap.put( command, currentIndex );
// Queue children and redirect for processing
nodeQueue.addAll( command.getChildren() );
if ( command.getRedirect() != null )
{
nodeQueue.add( command.getRedirect() );
}
}
}
// Write out size
writeVarInt( indexMap.size(), buf );
int currentIndex = 0;
for ( Map.Entry<CommandNode, Integer> entry : indexMap.entrySet() )
{
// Using a LinkedHashMap, but sanity check this assumption
Preconditions.checkState( entry.getValue() == currentIndex++, "Iteration out of order!" );
CommandNode node = entry.getKey();
byte flags = 0;
if ( node.getRedirect() != null )
{
flags |= FLAG_REDIRECT;
}
if ( node.getCommand() != null )
{
flags |= FLAG_EXECUTABLE;
}
if ( node instanceof RootCommandNode )
{
flags |= NODE_ROOT;
} else if ( node instanceof LiteralCommandNode )
{
flags |= NODE_LITERAL;
} else if ( node instanceof ArgumentCommandNode )
{
flags |= NODE_ARGUMENT;
if ( ( (ArgumentCommandNode) node ).getCustomSuggestions() != null )
{
flags |= FLAG_SUGGESTIONS;
}
} else
{
throw new IllegalArgumentException( "Unhandled node type " + node );
}
buf.writeByte( flags );
writeVarInt( node.getChildren().size(), buf );
for ( CommandNode child : (Collection<CommandNode>) node.getChildren() )
{
writeVarInt( indexMap.get( child ), buf );
}
if ( node.getRedirect() != null )
{
writeVarInt( indexMap.get( node.getRedirect() ), buf );
}
if ( node instanceof LiteralCommandNode )
{
writeString( ( (LiteralCommandNode) node ).getLiteral(), buf );
} else if ( node instanceof ArgumentCommandNode )
{
ArgumentCommandNode argumentNode = (ArgumentCommandNode) node;
writeString( argumentNode.getName(), buf );
ArgumentRegistry.write( argumentNode.getType(), buf );
if ( argumentNode.getCustomSuggestions() != null )
{
writeString( SuggestionRegistry.getKey( argumentNode.getCustomSuggestions() ), buf );
}
}
}
// Get, check, and write the root index (should be first)
int rootIndex = indexMap.get( root );
Preconditions.checkState( rootIndex == 0, "How did root not land up at index 0?!?" );
writeVarInt( rootIndex, buf );
}
@Override
public void handle(AbstractPacketHandler handler) throws Exception
{
handler.handle( this );
}
@Data
private static class NetworkNode
{
private final ArgumentBuilder argumentBuilder;
private final byte flags;
private final int redirectNode;
private final int[] children;
private CommandNode command;
private boolean buildSelf(NetworkNode[] otherNodes)
{
// First cycle
if ( command == null )
{
// Root node is merely the root
if ( argumentBuilder == null )
{
command = new RootCommandNode();
} else
{
// Add the redirect
if ( ( flags & FLAG_REDIRECT ) != 0 )
{
if ( otherNodes[redirectNode].command == null )
{
return false;
}
argumentBuilder.redirect( otherNodes[redirectNode].command );
}
// Add dummy executable
if ( ( flags & FLAG_EXECUTABLE ) != 0 )
{
argumentBuilder.executes( new Command()
{
@Override
public int run(CommandContext context) throws CommandSyntaxException
{
return 0;
}
} );
}
// Build our self command
command = argumentBuilder.build();
}
}
// Check that we have processed all children thus far
for ( int childIndex : children )
{
if ( otherNodes[childIndex].command == null )
{
// If not, we have to do another cycle
return false;
}
}
for ( int childIndex : children )
{
CommandNode<?> child = otherNodes[childIndex].command;
Preconditions.checkArgument( !( child instanceof RootCommandNode ), "Cannot have RootCommandNode as child" );
command.addChild( child );
}
return true;
}
}
@Data
private static class ArgumentRegistry
{
private static final Map<String, ArgumentSerializer> PROVIDERS = new HashMap<>();
private static final Map<Class<?>, ProperArgumentSerializer<?>> PROPER_PROVIDERS = new HashMap<>();
//
private static final ArgumentSerializer<Void> VOID = new ArgumentSerializer<Void>()
{
@Override
protected Void read(ByteBuf buf)
{
return null;
}
@Override
protected void write(ByteBuf buf, Void t)
{
}
};
private static final ArgumentSerializer<Boolean> BOOLEAN = new ArgumentSerializer<Boolean>()
{
@Override
protected Boolean read(ByteBuf buf)
{
return buf.readBoolean();
}
@Override
protected void write(ByteBuf buf, Boolean t)
{
buf.writeBoolean( t );
}
};
private static final ArgumentSerializer<Byte> BYTE = new ArgumentSerializer<Byte>()
{
@Override
protected Byte read(ByteBuf buf)
{
return buf.readByte();
}
@Override
protected void write(ByteBuf buf, Byte t)
{
buf.writeByte( t );
}
};
private static final ArgumentSerializer<FloatArgumentType> FLOAT = new ArgumentSerializer<FloatArgumentType>()
{
@Override
protected FloatArgumentType read(ByteBuf buf)
{
byte flags = buf.readByte();
float min = ( flags & 0x1 ) != 0 ? buf.readFloat() : -Float.MAX_VALUE;
float max = ( flags & 0x2 ) != 0 ? buf.readFloat() : -Float.MAX_VALUE;
return FloatArgumentType.floatArg( min, max );
}
@Override
protected void write(ByteBuf buf, FloatArgumentType t)
{
boolean hasMin = t.getMinimum() != -Float.MAX_VALUE;
boolean hasMax = t.getMaximum() != Float.MAX_VALUE;
buf.writeByte( binaryFlag( hasMin, hasMax ) );
if ( hasMin )
{
buf.writeFloat( t.getMinimum() );
}
if ( hasMax )
{
buf.writeFloat( t.getMaximum() );
}
}
};
private static final ArgumentSerializer<DoubleArgumentType> DOUBLE = new ArgumentSerializer<DoubleArgumentType>()
{
@Override
protected DoubleArgumentType read(ByteBuf buf)
{
byte flags = buf.readByte();
double min = ( flags & 0x1 ) != 0 ? buf.readDouble() : -Double.MAX_VALUE;
double max = ( flags & 0x2 ) != 0 ? buf.readDouble() : -Double.MAX_VALUE;
return DoubleArgumentType.doubleArg( min, max );
}
@Override
protected void write(ByteBuf buf, DoubleArgumentType t)
{
boolean hasMin = t.getMinimum() != -Double.MAX_VALUE;
boolean hasMax = t.getMaximum() != Double.MAX_VALUE;
buf.writeByte( binaryFlag( hasMin, hasMax ) );
if ( hasMin )
{
buf.writeDouble( t.getMinimum() );
}
if ( hasMax )
{
buf.writeDouble( t.getMaximum() );
}
}
};
private static final ArgumentSerializer<IntegerArgumentType> INTEGER = new ArgumentSerializer<IntegerArgumentType>()
{
@Override
protected IntegerArgumentType read(ByteBuf buf)
{
byte flags = buf.readByte();
int min = ( flags & 0x1 ) != 0 ? buf.readInt() : Integer.MIN_VALUE;
int max = ( flags & 0x2 ) != 0 ? buf.readInt() : Integer.MAX_VALUE;
return IntegerArgumentType.integer( min, max );
}
@Override
protected void write(ByteBuf buf, IntegerArgumentType t)
{
boolean hasMin = t.getMinimum() != Integer.MIN_VALUE;
boolean hasMax = t.getMaximum() != Integer.MAX_VALUE;
buf.writeByte( binaryFlag( hasMin, hasMax ) );
if ( hasMin )
{
buf.writeInt( t.getMinimum() );
}
if ( hasMax )
{
buf.writeInt( t.getMaximum() );
}
}
};
private static final ProperArgumentSerializer<StringArgumentType> STRING = new ProperArgumentSerializer<StringArgumentType>()
{
@Override
protected StringArgumentType read(ByteBuf buf)
{
int val = readVarInt( buf );
switch ( val )
{
case 0:
return StringArgumentType.word();
case 1:
return StringArgumentType.string();
case 2:
return StringArgumentType.greedyString();
default:
throw new IllegalArgumentException( "Unknown string type " + val );
}
}
@Override
protected void write(ByteBuf buf, StringArgumentType t)
{
writeVarInt( t.getType().ordinal(), buf );
}
@Override
protected String getKey()
{
return "brigadier:string";
}
};
static
{
PROVIDERS.put( "brigadier:bool", VOID );
PROVIDERS.put( "brigadier:float", FLOAT );
PROVIDERS.put( "brigadier:double", DOUBLE );
PROVIDERS.put( "brigadier:integer", INTEGER );
PROVIDERS.put( "brigadier:string", STRING );
PROPER_PROVIDERS.put( StringArgumentType.class, STRING );
PROVIDERS.put( "minecraft:entity", BYTE );
PROVIDERS.put( "minecraft:game_profile", VOID );
PROVIDERS.put( "minecraft:block_pos", VOID );
PROVIDERS.put( "minecraft:column_pos", VOID );
PROVIDERS.put( "minecraft:vec3", VOID );
PROVIDERS.put( "minecraft:vec2", VOID );
PROVIDERS.put( "minecraft:block_state", VOID );
PROVIDERS.put( "minecraft:block_predicate", VOID );
PROVIDERS.put( "minecraft:item_stack", VOID );
PROVIDERS.put( "minecraft:item_predicate", VOID );
PROVIDERS.put( "minecraft:color", VOID );
PROVIDERS.put( "minecraft:component", VOID );
PROVIDERS.put( "minecraft:message", VOID );
PROVIDERS.put( "minecraft:nbt", VOID );
PROVIDERS.put( "minecraft:nbt_path", VOID );
PROVIDERS.put( "minecraft:objective", VOID );
PROVIDERS.put( "minecraft:objective_criteria", VOID );
PROVIDERS.put( "minecraft:operation", VOID );
PROVIDERS.put( "minecraft:particle", VOID );
PROVIDERS.put( "minecraft:rotation", VOID );
PROVIDERS.put( "minecraft:scoreboard_slot", VOID );
PROVIDERS.put( "minecraft:score_holder", BYTE );
PROVIDERS.put( "minecraft:swizzle", VOID );
PROVIDERS.put( "minecraft:team", VOID );
PROVIDERS.put( "minecraft:item_slot", VOID );
PROVIDERS.put( "minecraft:resource_location", VOID );
PROVIDERS.put( "minecraft:mob_effect", VOID );
PROVIDERS.put( "minecraft:function", VOID );
PROVIDERS.put( "minecraft:entity_anchor", VOID );
PROVIDERS.put( "minecraft:int_range", VOID );
PROVIDERS.put( "minecraft:float_range", VOID );
PROVIDERS.put( "minecraft:item_enchantment", VOID );
PROVIDERS.put( "minecraft:entity_summon", VOID );
PROVIDERS.put( "minecraft:dimension", VOID );
}
private static ArgumentType<?> read(String key, ByteBuf buf)
{
ArgumentSerializer reader = PROVIDERS.get( key );
Preconditions.checkArgument( reader != null, "No provider for argument " + key );
Object val = reader.read( buf );
return val != null && PROPER_PROVIDERS.containsKey( val.getClass() ) ? (ArgumentType<?>) val : new DummyType( key, reader, val );
}
private static void write(ArgumentType<?> arg, ByteBuf buf)
{
ProperArgumentSerializer proper = PROPER_PROVIDERS.get( arg.getClass() );
if ( proper != null )
{
writeString( proper.getKey(), buf );
proper.write( buf, arg );
} else
{
Preconditions.checkArgument( arg instanceof DummyType, "Non dummy arg " + arg.getClass() );
DummyType dummy = (DummyType) arg;
writeString( dummy.key, buf );
dummy.serializer.write( buf, dummy.value );
}
}
@Data
private static class DummyType<T> implements ArgumentType<T>
{
private final String key;
private final ArgumentSerializer<T> serializer;
private final T value;
@Override
public T parse(StringReader reader) throws CommandSyntaxException
{
throw new UnsupportedOperationException( "Not supported." );
}
}
private static abstract class ArgumentSerializer<T>
{
protected abstract T read(ByteBuf buf);
protected abstract void write(ByteBuf buf, T t);
}
private static abstract class ProperArgumentSerializer<T> extends ArgumentSerializer<T>
{
protected abstract String getKey();
}
}
@Data
public static class SuggestionRegistry
{
public static final SuggestionProvider ASK_SERVER = new DummyProvider( "minecraft:ask_server" );
private static final Map<String, SuggestionProvider<DummyProvider>> PROVIDERS = new HashMap<>();
static
{
PROVIDERS.put( "minecraft:ask_server", ASK_SERVER );
registerDummy( "minecraft:all_recipes" );
registerDummy( "minecraft:available_sounds" );
registerDummy( "minecraft:summonable_entities" );
}
private static void registerDummy(String name)
{
PROVIDERS.put( name, new DummyProvider( name ) );
}
private static SuggestionProvider<DummyProvider> getProvider(String key)
{
SuggestionProvider<DummyProvider> provider = PROVIDERS.get( key );
Preconditions.checkArgument( provider != null, "Unknown completion provider " + key );
return provider;
}
private static String getKey(SuggestionProvider<DummyProvider> provider)
{
Preconditions.checkArgument( provider instanceof DummyProvider, "Non dummy provider " + provider );
return ( (DummyProvider) provider ).key;
}
@Data
private static final class DummyProvider implements SuggestionProvider<DummyProvider>
{
private final String key;
@Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<DummyProvider> context, SuggestionsBuilder builder) throws CommandSyntaxException
{
return builder.buildFuture();
}
}
}
private static byte binaryFlag(boolean first, boolean second)
{
byte ret = 0;
if ( first )
{
ret = (byte) ( ret | 0x1 );
}
if ( second )
{
ret = (byte) ( ret | 0x2 );
}
return ret;
}
}

View File

@ -1,10 +1,13 @@
package net.md_5.bungee.protocol.packet; package net.md_5.bungee.protocol.packet;
import com.mojang.brigadier.LiteralMessage;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.Suggestions;
import net.md_5.bungee.protocol.DefinedPacket; import net.md_5.bungee.protocol.DefinedPacket;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -18,17 +21,14 @@ public class TabCompleteResponse extends DefinedPacket
{ {
private int transactionId; private int transactionId;
private int start; private Suggestions suggestions;
private int length; //
private List<CommandMatch> matches;
private List<String> commands; private List<String> commands;
public TabCompleteResponse(int transactionId, int start, int length, List<CommandMatch> matches) public TabCompleteResponse(int transactionId, Suggestions suggestions)
{ {
this.transactionId = transactionId; this.transactionId = transactionId;
this.start = start; this.suggestions = suggestions;
this.length = length;
this.matches = matches;
} }
public TabCompleteResponse(List<String> commands) public TabCompleteResponse(List<String> commands)
@ -42,18 +42,21 @@ public class TabCompleteResponse extends DefinedPacket
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ) if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 )
{ {
transactionId = readVarInt( buf ); transactionId = readVarInt( buf );
start = readVarInt( buf ); int start = readVarInt( buf );
length = readVarInt( buf ); int length = readVarInt( buf );
StringRange range = StringRange.between( start, start + length );
int cnt = readVarInt( buf ); int cnt = readVarInt( buf );
matches = new LinkedList<>(); List<Suggestion> matches = new LinkedList<>();
for ( int i = 0; i < cnt; i++ ) for ( int i = 0; i < cnt; i++ )
{ {
String match = readString( buf ); String match = readString( buf );
String tooltip = buf.readBoolean() ? readString( buf ) : null; String tooltip = buf.readBoolean() ? readString( buf ) : null;
matches.add( new CommandMatch( match, tooltip ) ); matches.add( new Suggestion( range, match, new LiteralMessage( tooltip ) ) );
} }
suggestions = new Suggestions( range, matches );
} }
if ( protocolVersion < ProtocolConstants.MINECRAFT_1_13 ) if ( protocolVersion < ProtocolConstants.MINECRAFT_1_13 )
@ -68,15 +71,18 @@ public class TabCompleteResponse extends DefinedPacket
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ) if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 )
{ {
writeVarInt( transactionId, buf ); writeVarInt( transactionId, buf );
writeVarInt( start, buf ); writeVarInt( suggestions.getRange().getStart(), buf );
writeVarInt( length, buf ); writeVarInt( suggestions.getRange().getLength(), buf );
writeVarInt( matches.size(), buf ); writeVarInt( suggestions.getList().size(), buf );
for ( CommandMatch match : matches ) for ( Suggestion suggestion : suggestions.getList() )
{ {
writeString( match.match, buf ); writeString( suggestion.getText(), buf );
buf.writeBoolean( match.tooltip != null ); buf.writeBoolean( suggestion.getTooltip() != null );
writeString( match.tooltip, buf ); if ( suggestion.getTooltip() != null )
{
writeString( suggestion.getTooltip().getString(), buf );
}
} }
} }
@ -91,14 +97,4 @@ public class TabCompleteResponse extends DefinedPacket
{ {
handler.handle( this ); handler.handle( this );
} }
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class CommandMatch
{
private String match;
private String tooltip;
}
} }

View File

@ -60,6 +60,7 @@ public class Configuration implements ProxyConfig
private int compressionThreshold = 256; private int compressionThreshold = 256;
private boolean preventProxyConnections; private boolean preventProxyConnections;
private boolean forgeSupport; private boolean forgeSupport;
private boolean injectCommands;
public void load() public void load()
{ {
@ -90,6 +91,11 @@ public class Configuration implements ProxyConfig
compressionThreshold = adapter.getInt( "network_compression_threshold", compressionThreshold ); compressionThreshold = adapter.getInt( "network_compression_threshold", compressionThreshold );
preventProxyConnections = adapter.getBoolean( "prevent_proxy_connections", preventProxyConnections ); preventProxyConnections = adapter.getBoolean( "prevent_proxy_connections", preventProxyConnections );
forgeSupport = adapter.getBoolean( "forge_support", forgeSupport ); forgeSupport = adapter.getBoolean( "forge_support", forgeSupport );
injectCommands = adapter.getBoolean( "inject_commands", injectCommands );
if ( injectCommands )
{
System.setProperty( "net.md-5.bungee.protocol.register_commands", "true" );
}
disabledCommands = new CaseInsensitiveSet( (Collection<String>) adapter.getList( "disabled_commands", Arrays.asList( "disabledcommandhere" ) ) ); disabledCommands = new CaseInsensitiveSet( (Collection<String>) adapter.getList( "disabled_commands", Arrays.asList( "disabledcommandhere" ) ) );

View File

@ -3,11 +3,17 @@ package net.md_5.bungee.connection;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.tree.LiteralCommandNode;
import java.io.DataInput; import java.io.DataInput;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.ServerConnection; import net.md_5.bungee.ServerConnection;
import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.event.ServerDisconnectEvent; import net.md_5.bungee.api.event.ServerDisconnectEvent;
@ -32,6 +38,7 @@ import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.BossBar; import net.md_5.bungee.protocol.packet.BossBar;
import net.md_5.bungee.protocol.packet.Commands;
import net.md_5.bungee.protocol.packet.KeepAlive; import net.md_5.bungee.protocol.packet.KeepAlive;
import net.md_5.bungee.protocol.packet.PlayerListItem; import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.Respawn; import net.md_5.bungee.protocol.packet.Respawn;
@ -526,6 +533,35 @@ public class DownstreamBridge extends PacketHandler
con.setDimension( respawn.getDimension() ); con.setDimension( respawn.getDimension() );
} }
@Override
public void handle(Commands commands) throws Exception
{
boolean modified = false;
if ( BungeeCord.getInstance().config.isInjectCommands() )
{
for ( String command : bungee.getPluginManager().getCommands() )
{
if ( commands.getRoot().getChild( command ) == null )
{
LiteralCommandNode dummy = LiteralArgumentBuilder.literal( command )
.then( RequiredArgumentBuilder.argument( "args", StringArgumentType.greedyString() )
.suggests( Commands.SuggestionRegistry.ASK_SERVER ) )
.build();
commands.getRoot().addChild( dummy );
modified = true;
}
}
}
if ( modified )
{
con.unsafe().sendPacket( commands );
throw CancelSendSignal.INSTANCE;
}
}
@Override @Override
public String toString() public String toString()
{ {

View File

@ -1,8 +1,12 @@
package net.md_5.bungee.connection; package net.md_5.bungee.connection;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.mojang.brigadier.context.StringRange;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.Suggestions;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import net.md_5.bungee.BungeeCord; import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.UserConnection; import net.md_5.bungee.UserConnection;
@ -172,6 +176,19 @@ public class UpstreamBridge extends PacketHandler
if ( con.getPendingConnection().getVersion() < ProtocolConstants.MINECRAFT_1_13 ) if ( con.getPendingConnection().getVersion() < ProtocolConstants.MINECRAFT_1_13 )
{ {
con.unsafe().sendPacket( new TabCompleteResponse( results ) ); con.unsafe().sendPacket( new TabCompleteResponse( results ) );
} else if ( BungeeCord.getInstance().config.isInjectCommands() )
{
int start = tabComplete.getCursor().lastIndexOf( ' ' ) + 1;
int end = tabComplete.getCursor().length();
StringRange range = StringRange.between( start, end );
List<Suggestion> brigadier = new LinkedList<>();
for ( String s : results )
{
brigadier.add( new Suggestion( range, s ) );
}
con.unsafe().sendPacket( new TabCompleteResponse( tabComplete.getTransactionId(), new Suggestions( range, brigadier ) ) );
} }
throw CancelSendSignal.INSTANCE; throw CancelSendSignal.INSTANCE;
} }