Compare commits

..

11 Commits

Author SHA1 Message Date
3aab2fa1e1 new event TabCompleteRequestEvent and deprecate TabCompleteEvent 2025-07-01 21:24:43 +02:00
8be54e72ed Add CommandsDeclareEvent to declare commands with brigadier API 2025-07-01 21:24:43 +02:00
3e1e95ff49 Server branding now includes the backend server name 2025-07-01 21:24:43 +02:00
b5794d87d9 Multi-session with same Minecraft account with specific permission
Players with permission bungeecord.multiple_connect can have multiple connections with the same Minecraft account.
The UUID and player name is altered to avoid collision with other player:
UUID : xxxxxxxx-xxxx-VIxx-xxxx-xxxxxxxxxxxx
- The UUID version (V above) is now the provided version + 8 (for online player, it is 4, so it becomes C).
- The I digit will follow the index of the duplicated player : first duplicated player is 1, second one is 2.
- The name of the player will be the real player name, followed by the character "." (dot) followed by the duplication index.

Bedrock accounts connected using the Floodgate plugin will not be able to connect multiple times due to the risk of xUID collision.
2025-07-01 21:24:43 +02:00
dc8d29a05b Change projet configuration and POM for Pandacube 2025-07-01 21:24:43 +02:00
43dab76bd5 Remove modules and startup delay
We don’t need them for Pandacube
2025-07-01 21:24:43 +02:00
md_5
7c7cb3de0f
Minecraft 1.21.7 support 2025-07-01 00:10:00 +10:00
FlorianMichael
88436c44a6
#3854: Fix CustomClickAction read and write implementation 2025-06-29 21:45:47 +10:00
Outfluencer
bdd32d5a58
#3808: Do not decode packets that we don't handle 2025-06-28 10:36:23 +10:00
md_5
5e59b6dc85
#3851: Remove output from tests and "base" from dialog json 2025-06-22 09:44:38 +10:00
Outfluencer
bccce74c3c
#3799, #3800: Do not parse the collision and visibility strings of the Team packet 2025-06-22 09:23:07 +10:00
11 changed files with 131 additions and 109 deletions

View File

@ -8,7 +8,6 @@ import net.md_5.bungee.protocol.packet.ClearTitles;
import net.md_5.bungee.protocol.packet.ClientChat;
import net.md_5.bungee.protocol.packet.ClientCommand;
import net.md_5.bungee.protocol.packet.ClientSettings;
import net.md_5.bungee.protocol.packet.ClientStatus;
import net.md_5.bungee.protocol.packet.Commands;
import net.md_5.bungee.protocol.packet.CookieRequest;
import net.md_5.bungee.protocol.packet.CookieResponse;
@ -128,10 +127,6 @@ public abstract class AbstractPacketHandler
{
}
public void handle(ClientStatus clientStatus) throws Exception
{
}
public void handle(PlayerListItem playerListItem) throws Exception
{
}

View File

@ -48,6 +48,38 @@ public abstract class DefinedPacket
}
}
public <T> T readLengthPrefixed(Function<ByteBuf, T> reader, ByteBuf buf, int maxSize)
{
int size = readVarInt( buf );
if ( size > maxSize )
{
throw new OverflowPacketException( "Cannot read length prefixed with limit " + maxSize + " (got size of " + size + ")" );
}
return reader.apply( buf.readSlice( size ) );
}
public <T> void writeLengthPrefixed(T value, BiConsumer<T, ByteBuf> writer, ByteBuf buf, int maxSize)
{
ByteBuf tempBuffer = buf.alloc().buffer();
try
{
writer.accept( value, tempBuffer );
if ( tempBuffer.readableBytes() > maxSize )
{
throw new OverflowPacketException( "Cannot write length prefixed with limit " + maxSize + " (got size of " + tempBuffer.readableBytes() + ")" );
}
writeVarInt( tempBuffer.readableBytes(), buf );
buf.writeBytes( tempBuffer );
} finally
{
tempBuffer.release();
}
}
public static void writeString(String s, ByteBuf buf)
{
writeString( s, buf, Short.MAX_VALUE );

View File

@ -131,7 +131,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_21_2, 0x2C ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x2B )
);
TO_CLIENT.registerPacket( Chat.class,
TO_CLIENT.registerPacket(
Chat.class,
Chat::new,
map( ProtocolConstants.MINECRAFT_1_8, 0x02 ),
map( ProtocolConstants.MINECRAFT_1_9, 0x0F ),
@ -337,6 +338,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
Title.class,
Title::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_8, 0x45 ),
map( ProtocolConstants.MINECRAFT_1_12, 0x47 ),
map( ProtocolConstants.MINECRAFT_1_12_1, 0x48 ),
@ -358,6 +360,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
ClearTitles.class,
ClearTitles::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_17, 0x10 ),
map( ProtocolConstants.MINECRAFT_1_19, 0x0D ),
map( ProtocolConstants.MINECRAFT_1_19_3, 0x0C ),
@ -368,6 +371,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
Subtitle.class,
Subtitle::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_17, 0x57 ),
map( ProtocolConstants.MINECRAFT_1_18, 0x58 ),
map( ProtocolConstants.MINECRAFT_1_19_1, 0x5B ),
@ -382,6 +386,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
TitleTimes.class,
TitleTimes::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_17, 0x5A ),
map( ProtocolConstants.MINECRAFT_1_18, 0x5B ),
map( ProtocolConstants.MINECRAFT_1_19_1, 0x5E ),
@ -396,6 +401,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
SystemChat.class,
SystemChat::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_19, 0x5F ),
map( ProtocolConstants.MINECRAFT_1_19_1, 0x62 ),
map( ProtocolConstants.MINECRAFT_1_19_3, 0x60 ),
@ -409,6 +415,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
PlayerListHeaderFooter.class,
PlayerListHeaderFooter::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_8, 0x47 ),
map( ProtocolConstants.MINECRAFT_1_9, 0x48 ),
map( ProtocolConstants.MINECRAFT_1_9_4, 0x47 ),
@ -433,6 +440,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
EntityStatus.class,
EntityStatus::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_8, 0x1A ),
map( ProtocolConstants.MINECRAFT_1_9, 0x1B ),
map( ProtocolConstants.MINECRAFT_1_13, 0x1C ),
@ -466,6 +474,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
GameState.class,
GameState::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_15, 0x1F ),
map( ProtocolConstants.MINECRAFT_1_16, 0x1E ),
map( ProtocolConstants.MINECRAFT_1_16_2, 0x1D ),
@ -482,6 +491,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
ViewDistance.class,
ViewDistance::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_14, 0x41 ),
map( ProtocolConstants.MINECRAFT_1_15, 0x42 ),
map( ProtocolConstants.MINECRAFT_1_16, 0x41 ),
@ -547,6 +557,7 @@ public enum Protocol
TO_CLIENT.registerPacket(
StoreCookie.class,
StoreCookie::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_20_5, 0x6B ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x72 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x71 )
@ -554,29 +565,34 @@ public enum Protocol
TO_CLIENT.registerPacket(
Transfer.class,
Transfer::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_20_5, 0x73 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x7A )
);
TO_CLIENT.registerPacket(
DisconnectReportDetails.class,
DisconnectReportDetails::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_21, 0x7A ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x81 )
);
TO_CLIENT.registerPacket(
ServerLinks.class,
ServerLinks::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_21, 0x7B ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x82 )
);
TO_CLIENT.registerPacket(
ClearDialog.class,
ClearDialog::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_21_6, 0x84 )
);
TO_CLIENT.registerPacket(
ShowDialog.class,
ShowDialog::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_21_6, 0x85 )
);
@ -841,31 +857,37 @@ public enum Protocol
TO_CLIENT.registerPacket(
StoreCookie.class,
StoreCookie::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_20_5, 0x0A )
);
TO_CLIENT.registerPacket(
Transfer.class,
Transfer::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_20_5, 0x0B )
);
TO_CLIENT.registerPacket(
DisconnectReportDetails.class,
DisconnectReportDetails::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_21, 0x0F )
);
TO_CLIENT.registerPacket(
ServerLinks.class,
ServerLinks::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_21, 0x10 )
);
TO_CLIENT.registerPacket(
ClearDialog.class,
ClearDialog::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_21_6, 0x11 )
);
TO_CLIENT.registerPacket(
ShowDialogDirect.class,
ShowDialogDirect::new,
RegisterType.ENCODE,
map( ProtocolConstants.MINECRAFT_1_21_6, 0x12 )
);
@ -957,6 +979,26 @@ public enum Protocol
private final int packetID;
}
private enum RegisterType
{
// packet is registered to be encoded (only sent, never handled)
ENCODE,
// packet is registered to be decoded (only handled never modified)
DECODE,
// packet is registered to be both, encoded and decoded
BOTH;
public boolean encode()
{
return this == BOTH || this == ENCODE;
}
public boolean decode()
{
return this == BOTH || this == DECODE;
}
}
// Helper method
private static ProtocolMapping map(int protocol, int id)
{
@ -1010,6 +1052,11 @@ public enum Protocol
}
private void registerPacket(Class<? extends DefinedPacket> packetClass, Supplier<? extends DefinedPacket> constructor, ProtocolMapping... mappings)
{
registerPacket( packetClass, constructor, RegisterType.BOTH, mappings );
}
private void registerPacket(Class<? extends DefinedPacket> packetClass, Supplier<? extends DefinedPacket> constructor, RegisterType registerType, ProtocolMapping... mappings)
{
int mappingIndex = 0;
ProtocolMapping mapping = mappings[mappingIndex];
@ -1042,9 +1089,16 @@ public enum Protocol
ProtocolData data = protocols.get( protocol );
Preconditions.checkState( data.packetConstructors[mapping.packetID] == null, "Duplicate packet mapping (%s)", mapping.packetID );
Preconditions.checkState( !data.packetMap.containsKey( packetClass ), "Duplicate packet mapping (%s)", mapping.packetID );
data.packetMap.put( packetClass, mapping.packetID );
data.packetConstructors[mapping.packetID] = constructor;
if ( registerType.encode() )
{
data.packetMap.put( packetClass, mapping.packetID );
}
if ( registerType.decode() )
{
data.packetConstructors[mapping.packetID] = constructor;
}
}
}

View File

@ -51,6 +51,7 @@ public class ProtocolConstants
public static final int MINECRAFT_1_21_4 = 769;
public static final int MINECRAFT_1_21_5 = 770;
public static final int MINECRAFT_1_21_6 = 771;
public static final int MINECRAFT_1_21_7 = 772;
public static final List<String> SUPPORTED_VERSIONS;
public static final List<Integer> SUPPORTED_VERSION_IDS;
@ -116,7 +117,8 @@ public class ProtocolConstants
ProtocolConstants.MINECRAFT_1_21_2,
ProtocolConstants.MINECRAFT_1_21_4,
ProtocolConstants.MINECRAFT_1_21_5,
ProtocolConstants.MINECRAFT_1_21_6
ProtocolConstants.MINECRAFT_1_21_6,
ProtocolConstants.MINECRAFT_1_21_7
);
if ( SNAPSHOT_SUPPORT )

View File

@ -323,6 +323,8 @@ public final class TagUtil
}
return jsonLongArray;
case Tag.END:
return JsonNull.INSTANCE;
default:
throw new IllegalArgumentException( "Unknown NBT tag: " + tag );
}

View File

@ -1,37 +0,0 @@
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;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class ClientStatus extends DefinedPacket
{
private byte payload;
@Override
public void read(ByteBuf buf)
{
payload = buf.readByte();
}
@Override
public void write(ByteBuf buf)
{
buf.writeByte( payload );
}
@Override
public void handle(AbstractPacketHandler handler) throws Exception
{
handler.handle( this );
}
}

View File

@ -26,19 +26,14 @@ public class CustomClickAction extends DefinedPacket
public void read(ByteBuf buf, Protocol protocol, ProtocolConstants.Direction direction, int protocolVersion)
{
id = readString( buf );
data = readNullable( (buf0) -> (TypedTag) readTag( buf0, protocolVersion, new NBTLimiter( 32768L, 16 ) ), buf );
data = readLengthPrefixed( (buf0) -> (TypedTag) readTag( buf0, protocolVersion, new NBTLimiter( 32768L, 16 ) ), buf, 65536 );
}
@Override
public void write(ByteBuf buf, Protocol protocol, ProtocolConstants.Direction direction, int protocolVersion)
{
writeString( id, buf );
writeNullable( data, (data0, buf0) -> writeTag( data0, buf0, protocolVersion ), buf );
}
@Override
public void write(ByteBuf buf)
{
writeLengthPrefixed( data, (data0, buf0) -> writeTag( data0, buf0, protocolVersion ), buf, 65536 );
}
@Override

View File

@ -1,9 +1,6 @@
package net.md_5.bungee.protocol.packet;
import com.google.common.collect.ImmutableMap;
import io.netty.buffer.ByteBuf;
import java.util.Arrays;
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -31,8 +28,10 @@ public class Team extends DefinedPacket
private Either<String, BaseComponent> displayName;
private Either<String, BaseComponent> prefix;
private Either<String, BaseComponent> suffix;
private NameTagVisibility nameTagVisibility;
private CollisionRule collisionRule;
//
private Either<String, NameTagVisibility> nameTagVisibility;
private Either<String, CollisionRule> collisionRule;
//
private int color;
private byte friendlyFire;
private String[] players;
@ -67,15 +66,14 @@ public class Team extends DefinedPacket
friendlyFire = buf.readByte();
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
nameTagVisibility = NameTagVisibility.BY_ID[readVarInt( buf )];
collisionRule = CollisionRule.BY_ID[readVarInt( buf )];
nameTagVisibility = Either.right( NameTagVisibility.BY_ID[readVarInt( buf )] );
collisionRule = Either.right( CollisionRule.BY_ID[readVarInt( buf )] );
} else
{
nameTagVisibility = readStringMapKey( buf, NameTagVisibility.BY_NAME );
nameTagVisibility = Either.left( readString( buf ) );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_9 )
{
collisionRule = readStringMapKey( buf, CollisionRule.BY_NAME );
collisionRule = Either.left( readString( buf ) );
}
}
color = ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ) ? readVarInt( buf ) : buf.readByte();
@ -112,14 +110,14 @@ public class Team extends DefinedPacket
buf.writeByte( friendlyFire );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
writeVarInt( nameTagVisibility.ordinal(), buf );
writeVarInt( collisionRule.ordinal(), buf );
writeVarInt( nameTagVisibility.getRight().ordinal(), buf );
writeVarInt( collisionRule.getRight().ordinal(), buf );
} else
{
writeString( nameTagVisibility.getKey(), buf );
writeString( nameTagVisibility.getLeft(), buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_9 )
{
writeString( collisionRule.getKey(), buf );
writeString( collisionRule.getLeft(), buf );
}
}
@ -157,29 +155,11 @@ public class Team extends DefinedPacket
ALWAYS( "always" ),
NEVER( "never" ),
HIDE_FOR_OTHER_TEAMS( "hideForOtherTeams" ),
HIDE_FOR_OWN_TEAM( "hideForOwnTeam" ),
// 1.9 (and possibly other versions) appear to treat unknown values differently (always render rather than subject to spectator mode, friendly invisibles, etc).
// we allow the empty value to achieve this in case it is potentially useful even though this is unsupported and its usage may be a bug (#3780).
UNKNOWN( "" );
HIDE_FOR_OWN_TEAM( "hideForOwnTeam" );
//
private final String key;
//
private static final Map<String, NameTagVisibility> BY_NAME;
private static final NameTagVisibility[] BY_ID;
static
{
NameTagVisibility[] values = NameTagVisibility.values();
ImmutableMap.Builder<String, NameTagVisibility> builder = ImmutableMap.builderWithExpectedSize( values.length );
for ( NameTagVisibility e : values )
{
builder.put( e.key, e );
}
BY_NAME = builder.build();
BY_ID = Arrays.copyOf( values, values.length - 1 ); // Ignore dummy UNKNOWN value
}
private static final NameTagVisibility[] BY_ID = values();
}
@Getter
@ -194,20 +174,6 @@ public class Team extends DefinedPacket
//
private final String key;
//
private static final Map<String, CollisionRule> BY_NAME;
private static final CollisionRule[] BY_ID;
static
{
CollisionRule[] values = BY_ID = CollisionRule.values();
ImmutableMap.Builder<String, CollisionRule> builder = ImmutableMap.builderWithExpectedSize( values.length );
for ( CollisionRule e : values )
{
builder.put( e.key, e );
}
BY_NAME = builder.build();
}
private static final CollisionRule[] BY_ID = CollisionRule.values();
}
}

View File

@ -289,11 +289,11 @@ public class DownstreamBridge extends PacketHandler
t.setPrefix( team.getPrefix().getLeftOrCompute( (component) -> con.getChatSerializer().toString( component ) ) );
t.setSuffix( team.getSuffix().getLeftOrCompute( (component) -> con.getChatSerializer().toString( component ) ) );
t.setFriendlyFire( team.getFriendlyFire() );
t.setNameTagVisibility( team.getNameTagVisibility().getKey() );
t.setNameTagVisibility( team.getNameTagVisibility().isLeft() ? team.getNameTagVisibility().getLeft() : team.getNameTagVisibility().getRight().getKey() );
t.setColor( team.getColor() );
if ( team.getCollisionRule() != null )
{
t.setCollisionRule( team.getCollisionRule().getKey() );
t.setCollisionRule( team.getCollisionRule().isLeft() ? team.getCollisionRule().getLeft() : team.getCollisionRule().getRight().getKey() );
}
}
if ( team.getPlayers() != null )

View File

@ -121,6 +121,7 @@ public class DialogSerializer implements JsonDeserializer<Dialog>, JsonSerialize
JsonObject base = (JsonObject) context.serialize( src.getBase() );
object.asMap().putAll( base.asMap() );
object.remove( "base" );
return object;
} finally

View File

@ -1,5 +1,6 @@
package net.md_5.bungee.dialog;
import static org.junit.jupiter.api.Assertions.*;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.dialog.Dialog;
@ -11,15 +12,26 @@ import org.junit.jupiter.api.Test;
public class SimpleTest
{
public static void testDissembleReassemble(Dialog notice)
{
String json = VersionedComponentSerializer.getDefault().getDialogSerializer().toString( notice );
Dialog parsed = VersionedComponentSerializer.getDefault().getDialogSerializer().deserialize( json );
assertEquals( notice, parsed );
}
@Test
public void testNotice()
public void testSimple()
{
String json = "{type:\"minecraft:notice\",title:\"Hello\"}";
Dialog deserialized = VersionedComponentSerializer.getDefault().getDialogSerializer().deserialize( json );
System.err.println( deserialized );
String serialized = VersionedComponentSerializer.getDefault().getDialogSerializer().toString( deserialized );
Dialog notice = new NoticeDialog( new DialogBase( new ComponentBuilder( "Hello" ).color( ChatColor.RED ).build() ) );
String newJson = VersionedComponentSerializer.getDefault().getDialogSerializer().toString( notice );
System.err.println( newJson );
assertEquals( "{\"type\":\"minecraft:notice\",\"title\":{\"text\":\"Hello\"}}", serialized );
}
@Test
public void testNotice()
{
testDissembleReassemble( new NoticeDialog( new DialogBase( new ComponentBuilder( "Hello" ).color( ChatColor.RED ).build() ) ) );
}
}