Compare commits

..

50 Commits

Author SHA1 Message Date
2a19bfb6d3 new event TabCompleteRequestEvent and deprecate TabCompleteEvent 2025-04-04 22:58:24 +02:00
451068694a Add CommandsDeclareEvent to declare commands with brigadier API 2025-04-04 22:58:24 +02:00
e7e77e2731 Server branding now includes the backend server name 2025-04-04 22:58:23 +02:00
d6bf6a13f4 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-04-04 22:58:13 +02:00
8dd77af343 Change projet configuration and POM for Pandacube 2025-04-04 22:52:02 +02:00
fb9e1b0499 Remove modules and startup delay
We don’t need them for Pandacube
2025-04-04 22:51:57 +02:00
md_5
f6151dce56
Bump version to 1.21-R0.3-SNAPSHOT 2025-03-28 19:52:19 +11:00
md_5
9667743735
Release 1.21-R0.2 2025-03-28 19:49:50 +11:00
md_5
7587f03306
SPIGOT-8024, #3811, #3812: Add versioned chat serialization (beta) 2025-03-28 07:01:06 +11:00
md_5
77b81f2612
Bump Netty to 4.1.119.Final 2025-03-27 06:58:23 +11:00
md_5
fa6d47732d
Bump version to 1.21-R0.2-SNAPSHOT 2025-03-26 19:38:24 +11:00
md_5
252e7b0027
Release 1.21-R0.1 2025-03-26 19:33:00 +11:00
md_5
c820b3a062
Minecraft 1.21.5 support 2025-03-26 03:05:00 +11:00
Outfluencer
687c302610
Add WriteTimeoutHandler 2025-03-23 08:19:07 +11:00
Outfluencer
4b0262312e
#3805: Remove redundant PluginMessage handler in InitialHandler
There is no state in wich we could decode a PluginMessage in InitialHandler, so we should remove it here.
2025-03-22 17:33:46 +11:00
md_5
6f13c2d6b6
Minecraft 1.21.5-rc1 protocol support 2025-03-21 07:34:08 +11:00
md_5
cd186999e5
Minecraft 1.21.5-pre3 protocol support 2025-03-19 06:20:53 +11:00
Outfluencer
e3c7fd8cc5
#3801: Add support for 1.21.5-pre2 2025-03-13 09:19:09 +11:00
Outfluencer
9476ffccdb
#3797: Expose sendPacketQueued to unsafe interface 2025-03-08 16:56:20 +11:00
md_5
47f8c29a7c
Minecraft 25w10a protocol support 2025-03-08 09:22:17 +11:00
Outfluencer
458246505f
Don't attempt to read packets after channel is closed/closing 2025-03-03 19:58:46 +11:00
Julian Vennen
362bd0f4c4
#3794: Ensure listType is set to compound tag when wrapping tags 2025-03-01 14:48:59 +11:00
md_5
598d73e6f0
Minecraft 25w09b protocol support 2025-03-01 14:21:52 +11:00
md_5
f797bd488f
Minecraft 25w08a protocol support 2025-02-21 19:18:25 +11:00
md_5
0d153feee7
#3791: Handle third party tools rewriting integers badly 2025-02-18 19:17:31 +11:00
Outfluencer
2a78233cc2
#3790: Implement toString for ProxyReloadEvent
Its the only event that doesn't have it
2025-02-18 19:09:54 +11:00
Valentine
591e18753d
#3786: Fix missing compressor release when removing LengthPrependerAndCompressor handler 2025-02-16 07:44:55 +11:00
Outfluencer
556a15a6f8
#3733: Add incoming packet configuration options 2025-02-15 17:26:15 +11:00
Janmm14
774a6fd68c
#3766: Combine packet length prepending and compressor 2025-02-15 15:20:00 +11:00
Outfluencer
0070421549
#3776: Expose ChannelInitializerHolder in protocol module 2025-02-15 14:52:29 +11:00
md_5
4dad940a2f
Extract bungee plugin message channel to constant 2025-02-12 07:09:05 +11:00
Valentine
ed4a80eb0b
#3781: Fix eventLoopCallback 2025-02-09 08:17:16 +11:00
md_5
dd2033bf1a
Update maven-checkstyle-plugin 2025-02-08 17:59:56 +11:00
md_5
cceebdad2a
#3780: Allow empty scoreboard nametag visibility 2025-02-08 17:55:18 +11:00
md_5
05bdf5d3c1
Set compiler proc=full on Java 21+ 2025-02-08 16:00:53 +11:00
Outfluencer
c3e4a6ef5b
#3779: Improve eventloop consistency and isClosing code 2025-02-08 15:50:43 +11:00
md_5
2337acfcc1
Minecraft 25w06a protocol support 2025-02-08 08:30:30 +11:00
Outfluencer
69861e5334
#3778: Null check team collision rule because it does not exist in 1.8 2025-02-07 07:06:45 +11:00
md_5
22aa6f5faf
Use lombok for Team packet 2025-02-02 08:35:39 +11:00
md_5
508c2f7ac3
Minecraft 25w05a protocol support 2025-02-01 12:50:31 +11:00
md_5
9dd5fb626d
Fix duplicate 25w04a packet mapping 2025-01-30 07:43:05 +11:00
BoomEaro
b5ae0196fc
#3771: Make PluginManager methods thread safe 2025-01-29 20:38:20 +11:00
Outfluencer
80bb237289
#3774: Minecraft 25w04a chat component changes 2025-01-29 20:35:46 +11:00
Outfluencer
4fded9828f
#3775: Allow decompressed packets to grow to max capacity
Do not use size as max capacity, as its possible that the entity
rewriter increases the size afterwards. This would result in a kick (it
happens rarely as the entity ids size must differ).
2025-01-29 07:52:09 +11:00
md_5
6b22690971
Minecraft 25w04a protocol support 2025-01-27 20:09:01 +11:00
md_5
60a3bf082f
Preallocate compression output buffer to remove unnecessary resizing 2025-01-27 20:08:58 +11:00
Outfluencer
0aa2871b26
#3761: Whitelist LoginPayloadResponse in UpstreamBridge#shouldHandle
Required for #3758 to function correctly.
2025-01-23 07:11:11 +11:00
Outfluencer
1265a9927b
#3769: Fix possible NoSuchElementException changing compression 2025-01-07 20:46:20 +11:00
md_5
d99570214a
Update date in README.md 2025-01-01 10:19:38 +11:00
Janmm14
15bd33b25b
#3767: Remove explicit direct buffer check 2024-12-14 09:46:35 +11:00
71 changed files with 1810 additions and 600 deletions

View File

@ -23,4 +23,4 @@ Binaries
--------
Precompiled binaries are available for end users on [Jenkins](https://www.spigotmc.org/go/bungeecord-dl).
(c) 2012-2024 SpigotMC Pty. Ltd.
(c) 2012-2025 SpigotMC Pty. Ltd.

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-api</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-API</name>

View File

@ -16,6 +16,7 @@ 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.scheduler.TaskScheduler;
import net.md_5.bungee.protocol.channel.BungeeChannelInitializer;
public abstract class ProxyServer
{
@ -311,4 +312,56 @@ public abstract class ProxyServer
*/
public abstract Title createTitle();
/**
* Get the unsafe methods of this class.
*
* @return the unsafe method interface
*/
public abstract Unsafe unsafe();
public interface Unsafe
{
/**
* Gets the frontend channel initializer
*
* @return the frontend channel initializer
*/
BungeeChannelInitializer getFrontendChannelInitializer();
/**
* Set the frontend channel initializer of this proxy
*
* @param channelInitializer the frontend channelInitializer to set
*/
void setFrontendChannelInitializer(BungeeChannelInitializer channelInitializer);
/**
* Gets the backend channel initializer
*
* @return the backend channel initializer
*/
BungeeChannelInitializer getBackendChannelInitializer();
/**
* Set the backend channel initializer of this proxy
*
* @param channelInitializer the backend channelInitializer to set
*/
void setBackendChannelInitializer(BungeeChannelInitializer channelInitializer);
/**
* Gets the server info channel initializer
*
* @return the server info channel initializer
*/
BungeeChannelInitializer getServerInfoChannelInitializer();
/**
* Set the server info channel initializer of this proxy
*
* @param channelInitializer the server info channelInitializer to set
*/
void setServerInfoChannelInitializer(BungeeChannelInitializer channelInitializer);
}
}

View File

@ -84,5 +84,15 @@ public interface Connection
* @param packet the packet to send
*/
void sendPacket(DefinedPacket packet);
/**
* Queue a packet to this connection.
* If the packet is not registered for the connections current encoder protocol, it will be queued until it is,
* otherwise it will be sent immediately.
*
* @param packet the packet to be queued
* @throws UnsupportedOperationException if used for a PendingConnection
*/
void sendPacketQueued(DefinedPacket packet);
}
}

View File

@ -3,6 +3,7 @@ package net.md_5.bungee.api.event;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Event;
@ -10,6 +11,7 @@ import net.md_5.bungee.api.plugin.Event;
* Called when somebody reloads BungeeCord
*/
@Getter
@ToString(callSuper = false)
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
public class ProxyReloadEvent extends Event

View File

@ -23,6 +23,10 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
@ -59,6 +63,9 @@ public final class PluginManager
private final Multimap<Plugin, Command> commandsByPlugin = ArrayListMultimap.create();
private final Multimap<Plugin, Listener> listenersByPlugin = ArrayListMultimap.create();
private final ReadWriteLock commandsLock = new ReentrantReadWriteLock();
private final Lock listenersLock = new ReentrantLock();
@SuppressWarnings("unchecked")
public PluginManager(ProxyServer proxy)
{
@ -92,6 +99,9 @@ public final class PluginManager
* @param command the command to register
*/
public void registerCommand(Plugin plugin, Command command)
{
commandsLock.writeLock().lock();
try
{
commandMap.put( command.getName().toLowerCase( Locale.ROOT ), command );
for ( String alias : command.getAliases() )
@ -99,6 +109,10 @@ public final class PluginManager
commandMap.put( alias.toLowerCase( Locale.ROOT ), command );
}
commandsByPlugin.put( plugin, command );
} finally
{
commandsLock.writeLock().unlock();
}
}
/**
@ -107,9 +121,16 @@ public final class PluginManager
* @param command the command to unregister
*/
public void unregisterCommand(Command command)
{
commandsLock.writeLock().lock();
try
{
while ( commandMap.values().remove( command ) );
commandsByPlugin.values().remove( command );
} finally
{
commandsLock.writeLock().unlock();
}
}
/**
@ -118,6 +139,9 @@ public final class PluginManager
* @param plugin the plugin to register the commands of
*/
public void unregisterCommands(Plugin plugin)
{
commandsLock.writeLock().lock();
try
{
for ( Iterator<Command> it = commandsByPlugin.get( plugin ).iterator(); it.hasNext(); )
{
@ -125,6 +149,10 @@ public final class PluginManager
while ( commandMap.values().remove( command ) );
it.remove();
}
} finally
{
commandsLock.writeLock().unlock();
}
}
private Command getCommandIfEnabled(String commandName, CommandSender sender)
@ -137,7 +165,14 @@ public final class PluginManager
return null;
}
commandsLock.readLock().lock();
try
{
return commandMap.get( commandLower );
} finally
{
commandsLock.readLock().unlock();
}
}
/**
@ -433,6 +468,9 @@ public final class PluginManager
* @param listener the listener to register events for
*/
public void registerListener(Plugin plugin, Listener listener)
{
listenersLock.lock();
try
{
for ( Method method : listener.getClass().getDeclaredMethods() )
{
@ -441,6 +479,10 @@ public final class PluginManager
}
eventBus.register( listener );
listenersByPlugin.put( plugin, listener );
} finally
{
listenersLock.unlock();
}
}
/**
@ -449,9 +491,16 @@ public final class PluginManager
* @param listener the listener to unregister
*/
public void unregisterListener(Listener listener)
{
listenersLock.lock();
try
{
eventBus.unregister( listener );
listenersByPlugin.values().remove( listener );
} finally
{
listenersLock.unlock();
}
}
/**
@ -460,12 +509,19 @@ public final class PluginManager
* @param plugin target plugin
*/
public void unregisterListeners(Plugin plugin)
{
listenersLock.lock();
try
{
for ( Iterator<Listener> it = listenersByPlugin.get( plugin ).iterator(); it.hasNext(); )
{
eventBus.unregister( it.next() );
it.remove();
}
} finally
{
listenersLock.unlock();
}
}
/**
@ -474,8 +530,15 @@ public final class PluginManager
* @return commands
*/
public Collection<Map.Entry<String, Command>> getCommands()
{
commandsLock.readLock().lock();
try
{
return Collections.unmodifiableCollection( commandMap.entrySet() );
} finally
{
commandsLock.readLock().unlock();
}
}
boolean isTransitiveDepend(PluginDescription plugin, PluginDescription depend)

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-bootstrap</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Bootstrap</name>

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-chat</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Chat</name>

View File

@ -13,7 +13,8 @@ import net.md_5.bungee.api.chat.hover.content.Content;
import net.md_5.bungee.api.chat.hover.content.Entity;
import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.chat.VersionedComponentSerializer;
import org.jetbrains.annotations.ApiStatus;
@Getter
@ToString
@ -34,6 +35,7 @@ public final class HoverEvent
* Returns whether this hover event is prior to 1.16
*/
@Setter
@ApiStatus.Internal
private boolean legacy = false;
/**
@ -80,7 +82,7 @@ public final class HoverEvent
return (BaseComponent[]) ( (Text) content ).getValue();
}
TextComponent component = new TextComponent( ComponentSerializer.toString( content ) );
TextComponent component = new TextComponent( VersionedComponentSerializer.getDefault().toString( content ) );
return new BaseComponent[]
{
component

View File

@ -10,27 +10,36 @@ import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import java.util.UUID;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.chat.BaseComponentSerializer;
import net.md_5.bungee.chat.VersionedComponentSerializer;
public class EntitySerializer implements JsonSerializer<Entity>, JsonDeserializer<Entity>
public class EntitySerializer extends BaseComponentSerializer implements JsonSerializer<Entity>, JsonDeserializer<Entity>
{
public EntitySerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public Entity deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
{
JsonObject value = element.getAsJsonObject();
boolean newEntity = value.has( "uuid" );
String idString;
JsonElement id = value.get( "id" );
if ( id.isJsonArray() )
JsonElement uuid = value.get( newEntity ? "uuid" : "id" );
if ( uuid.isJsonArray() )
{
idString = parseUUID( context.deserialize( id, int[].class ) ).toString();
idString = parseUUID( context.deserialize( uuid, int[].class ) ).toString();
} else
{
idString = id.getAsString();
idString = uuid.getAsString();
}
return new Entity(
( value.has( "type" ) ) ? value.get( "type" ).getAsString() : null,
( value.has( newEntity ? "id" : "type" ) ) ? value.get( newEntity ? "id" : "type" ).getAsString() : null,
idString,
( value.has( "name" ) ) ? context.deserialize( value.get( "name" ), BaseComponent.class ) : null
);
@ -40,8 +49,21 @@ public class EntitySerializer implements JsonSerializer<Entity>, JsonDeserialize
public JsonElement serialize(Entity content, Type type, JsonSerializationContext context)
{
JsonObject object = new JsonObject();
switch ( serializer.getVersion() )
{
case V1_21_5:
object.addProperty( "id", ( content.getType() != null ) ? content.getType() : "minecraft:pig" );
object.addProperty( "uuid", content.getId() );
break;
case V1_16:
object.addProperty( "type", ( content.getType() != null ) ? content.getType() : "minecraft:pig" );
object.addProperty( "id", content.getId() );
break;
default:
throw new IllegalArgumentException( "Unknown version " + serializer.getVersion() );
}
if ( content.getName() != null )
{
object.add( "name", context.serialize( content.getName() ) );

View File

@ -10,15 +10,19 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Locale;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentStyle;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Content;
@RequiredArgsConstructor
public class BaseComponentSerializer
{
protected final VersionedComponentSerializer serializer;
protected void deserialize(JsonObject object, BaseComponent component, JsonDeserializationContext context)
{
component.applyStyle( context.deserialize( object, ComponentStyle.class ) );
@ -30,23 +34,82 @@ public class BaseComponentSerializer
}
//Events
JsonObject clickEvent = object.getAsJsonObject( "clickEvent" );
JsonObject clickEvent;
boolean newClickEvent = ( clickEvent = object.getAsJsonObject( "click_event" ) ) != null;
if ( !newClickEvent )
{
clickEvent = object.getAsJsonObject( "clickEvent" );
}
if ( clickEvent != null )
{
component.setClickEvent( new ClickEvent(
ClickEvent.Action.valueOf( clickEvent.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) ),
( clickEvent.has( "value" ) ) ? clickEvent.get( "value" ).getAsString() : "" ) );
ClickEvent.Action action = ClickEvent.Action.valueOf( clickEvent.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );
if ( newClickEvent )
{
switch ( action )
{
case OPEN_URL:
component.setClickEvent( new ClickEvent( action, clickEvent.get( "url" ).getAsString() ) );
break;
case RUN_COMMAND:
case SUGGEST_COMMAND:
component.setClickEvent( new ClickEvent( action, clickEvent.get( "command" ).getAsString() ) );
break;
case CHANGE_PAGE:
int page = clickEvent.get( "page" ).getAsInt();
Preconditions.checkArgument( page >= 0, "Page number has to be positive" );
component.setClickEvent( new ClickEvent( action, Integer.toString( page ) ) );
break;
default:
component.setClickEvent( new ClickEvent( action, ( clickEvent.has( "value" ) ) ? clickEvent.get( "value" ).getAsString() : "" ) );
break;
}
JsonObject hoverEventJson = object.getAsJsonObject( "hoverEvent" );
} else
{
component.setClickEvent( new ClickEvent( action, ( clickEvent.has( "value" ) ) ? clickEvent.get( "value" ).getAsString() : "" ) );
}
}
JsonObject hoverEventJson;
boolean newHoverEvent = ( hoverEventJson = object.getAsJsonObject( "hover_event" ) ) != null;
if ( !newHoverEvent )
{
hoverEventJson = object.getAsJsonObject( "hoverEvent" );
}
if ( hoverEventJson != null )
{
HoverEvent hoverEvent = null;
HoverEvent.Action action = HoverEvent.Action.valueOf( hoverEventJson.get( "action" ).getAsString().toUpperCase( Locale.ROOT ) );
if ( newHoverEvent || hoverEventJson.has( "contents" ) )
{
// value is only used for text in >= 1.21.5 (its inlined now)
JsonElement contents = hoverEventJson.get( newHoverEvent ? "value" : "contents" );
if ( contents != null || ( newHoverEvent && ( action == HoverEvent.Action.SHOW_ITEM || action == HoverEvent.Action.SHOW_ENTITY ) ) )
{
if ( contents == null )
{
// this is the new inline for SHOW_ITEM and SHOW_ENTITY
contents = hoverEventJson;
}
Content[] list;
if ( contents.isJsonArray() )
{
list = context.deserialize( contents, HoverEvent.getClass( action, true ) );
} else
{
list = new Content[]
{
context.deserialize( contents, HoverEvent.getClass( action, false ) )
};
}
hoverEvent = new HoverEvent( action, new ArrayList<>( Arrays.asList( list ) ) );
}
} else
{
JsonElement value = hoverEventJson.get( "value" );
if ( value != null )
{
// Plugins previously had support to pass BaseComponent[] into any action.
// If the GSON is possible to be parsed as BaseComponent, attempt to parse as so.
BaseComponent[] components;
@ -61,23 +124,6 @@ public class BaseComponentSerializer
};
}
hoverEvent = new HoverEvent( action, components );
} else
{
JsonElement contents = hoverEventJson.get( "contents" );
if ( contents != null )
{
Content[] list;
if ( contents.isJsonArray() )
{
list = context.deserialize( contents, HoverEvent.getClass( action, true ) );
} else
{
list = new Content[]
{
context.deserialize( contents, HoverEvent.getClass( action, false ) )
};
}
hoverEvent = new HoverEvent( action, new ArrayList<>( Arrays.asList( list ) ) );
}
}
@ -97,15 +143,15 @@ public class BaseComponentSerializer
protected void serialize(JsonObject object, BaseComponent component, JsonSerializationContext context)
{
boolean first = false;
if ( ComponentSerializer.serializedComponents.get() == null )
if ( VersionedComponentSerializer.serializedComponents.get() == null )
{
first = true;
ComponentSerializer.serializedComponents.set( Collections.newSetFromMap( new IdentityHashMap<BaseComponent, Boolean>() ) );
VersionedComponentSerializer.serializedComponents.set( Collections.newSetFromMap( new IdentityHashMap<BaseComponent, Boolean>() ) );
}
try
{
Preconditions.checkArgument( !ComponentSerializer.serializedComponents.get().contains( component ), "Component loop" );
ComponentSerializer.serializedComponents.get().add( component );
Preconditions.checkArgument( !VersionedComponentSerializer.serializedComponents.get().contains( component ), "Component loop" );
VersionedComponentSerializer.serializedComponents.get().add( component );
ComponentStyleSerializer.serializeTo( component.getStyle(), object );
@ -118,9 +164,38 @@ public class BaseComponentSerializer
if ( component.getClickEvent() != null )
{
JsonObject clickEvent = new JsonObject();
clickEvent.addProperty( "action", component.getClickEvent().getAction().toString().toLowerCase( Locale.ROOT ) );
String actionName = component.getClickEvent().getAction().toString().toLowerCase( Locale.ROOT );
clickEvent.addProperty( "action", actionName.toLowerCase( Locale.ROOT ) );
switch ( serializer.getVersion() )
{
case V1_21_5:
ClickEvent.Action action = ClickEvent.Action.valueOf( actionName.toUpperCase( Locale.ROOT ) );
switch ( action )
{
case OPEN_URL:
clickEvent.addProperty( "url", component.getClickEvent().getValue() );
break;
case RUN_COMMAND:
case SUGGEST_COMMAND:
clickEvent.addProperty( "command", component.getClickEvent().getValue() );
break;
case CHANGE_PAGE:
clickEvent.addProperty( "page", Integer.parseInt( component.getClickEvent().getValue() ) );
break;
default:
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
break;
}
object.add( "click_event", clickEvent );
break;
case V1_16:
clickEvent.addProperty( "value", component.getClickEvent().getValue() );
object.add( "clickEvent", clickEvent );
break;
default:
throw new IllegalArgumentException( "Unknown version " + serializer.getVersion() );
}
}
if ( component.getHoverEvent() != null )
{
@ -131,10 +206,39 @@ public class BaseComponentSerializer
hoverEvent.add( "value", context.serialize( component.getHoverEvent().getContents().get( 0 ) ) );
} else
{
hoverEvent.add( "contents", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
switch ( serializer.getVersion() )
{
case V1_21_5:
if ( component.getHoverEvent().getAction() == HoverEvent.Action.SHOW_ITEM || component.getHoverEvent().getAction() == HoverEvent.Action.SHOW_ENTITY )
{
JsonObject inlined = context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ).getAsJsonObject();
inlined.entrySet().forEach( entry -> hoverEvent.add( entry.getKey(), entry.getValue() ) );
} else
{
hoverEvent.add( "value", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) );
}
break;
case V1_16:
hoverEvent.add( "contents", context.serialize( ( component.getHoverEvent().getContents().size() == 1 )
? component.getHoverEvent().getContents().get( 0 ) : component.getHoverEvent().getContents() ) );
break;
default:
throw new IllegalArgumentException( "Unknown version " + serializer.getVersion() );
}
}
switch ( serializer.getVersion() )
{
case V1_21_5:
object.add( "hover_event", hoverEvent );
break;
case V1_16:
object.add( "hoverEvent", hoverEvent );
break;
default:
throw new IllegalArgumentException( "Unknown version " + serializer.getVersion() );
}
}
if ( component.getExtra() != null )
@ -143,10 +247,10 @@ public class BaseComponentSerializer
}
} finally
{
ComponentSerializer.serializedComponents.get().remove( component );
VersionedComponentSerializer.serializedComponents.get().remove( component );
if ( first )
{
ComponentSerializer.serializedComponents.set( null );
VersionedComponentSerializer.serializedComponents.set( null );
}
}
}

View File

@ -0,0 +1,10 @@
package net.md_5.bungee.chat;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Internal
public enum ChatVersion
{
V1_16,
V1_21_5;
}

View File

@ -1,52 +1,17 @@
package net.md_5.bungee.chat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import java.lang.reflect.Type;
import java.util.Set;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentStyle;
import net.md_5.bungee.api.chat.ItemTag;
import net.md_5.bungee.api.chat.KeybindComponent;
import net.md_5.bungee.api.chat.ScoreComponent;
import net.md_5.bungee.api.chat.SelectorComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import net.md_5.bungee.api.chat.hover.content.Content;
import net.md_5.bungee.api.chat.hover.content.Entity;
import net.md_5.bungee.api.chat.hover.content.EntitySerializer;
import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.ItemSerializer;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.md_5.bungee.api.chat.hover.content.TextSerializer;
public class ComponentSerializer implements JsonDeserializer<BaseComponent>
{
private static final Gson gson = new GsonBuilder().
registerTypeAdapter( BaseComponent.class, new ComponentSerializer() ).
registerTypeAdapter( TextComponent.class, new TextComponentSerializer() ).
registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() ).
registerTypeAdapter( KeybindComponent.class, new KeybindComponentSerializer() ).
registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer() ).
registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer() ).
registerTypeAdapter( ComponentStyle.class, new ComponentStyleSerializer() ).
registerTypeAdapter( Entity.class, new EntitySerializer() ).
registerTypeAdapter( Text.class, new TextSerializer() ).
registerTypeAdapter( Item.class, new ItemSerializer() ).
registerTypeAdapter( ItemTag.class, new ItemTag.Serializer() ).
create();
public static final ThreadLocal<Set<BaseComponent>> serializedComponents = new ThreadLocal<Set<BaseComponent>>();
/**
* Parse a JSON-compliant String as an array of base components. The input
* can be one of either an array of components, or a single component
@ -66,18 +31,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static BaseComponent[] parse(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
if ( jsonElement.isJsonArray() )
{
return gson.fromJson( jsonElement, BaseComponent[].class );
} else
{
return new BaseComponent[]
{
gson.fromJson( jsonElement, BaseComponent.class )
};
}
return VersionedComponentSerializer.getDefault().parse( json );
}
/**
@ -90,9 +44,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static BaseComponent deserialize(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
return deserialize( jsonElement );
return VersionedComponentSerializer.getDefault().deserialize( json );
}
/**
@ -105,20 +57,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static BaseComponent deserialize(JsonElement jsonElement)
{
if ( jsonElement instanceof JsonPrimitive )
{
JsonPrimitive primitive = (JsonPrimitive) jsonElement;
if ( primitive.isString() )
{
return new TextComponent( primitive.getAsString() );
}
} else if ( jsonElement instanceof JsonArray )
{
BaseComponent[] array = gson.fromJson( jsonElement, BaseComponent[].class );
return TextComponent.fromArray( array );
}
return gson.fromJson( jsonElement, BaseComponent.class );
return VersionedComponentSerializer.getDefault().deserialize( jsonElement );
}
/**
@ -131,9 +70,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static ComponentStyle deserializeStyle(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
return deserializeStyle( jsonElement );
return VersionedComponentSerializer.getDefault().deserializeStyle( json );
}
/**
@ -146,17 +83,17 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
*/
public static ComponentStyle deserializeStyle(JsonElement jsonElement)
{
return gson.fromJson( jsonElement, ComponentStyle.class );
return VersionedComponentSerializer.getDefault().deserializeStyle( jsonElement );
}
public static JsonElement toJson(BaseComponent component)
{
return gson.toJsonTree( component );
return VersionedComponentSerializer.getDefault().toJson( component );
}
public static JsonElement toJson(ComponentStyle style)
{
return gson.toJsonTree( style );
return VersionedComponentSerializer.getDefault().toJson( style );
}
/**
@ -167,7 +104,7 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
@Deprecated
public static String toString(Object object)
{
return gson.toJson( object );
return VersionedComponentSerializer.getDefault().toString( object );
}
/**
@ -178,54 +115,27 @@ public class ComponentSerializer implements JsonDeserializer<BaseComponent>
@Deprecated
public static String toString(Content content)
{
return gson.toJson( content );
return VersionedComponentSerializer.getDefault().toString( content );
}
public static String toString(BaseComponent component)
{
return gson.toJson( component );
return VersionedComponentSerializer.getDefault().toString( component );
}
public static String toString(BaseComponent... components)
{
if ( components.length == 1 )
{
return gson.toJson( components[0] );
} else
{
return gson.toJson( new TextComponent( components ) );
}
return VersionedComponentSerializer.getDefault().toString( components );
}
public static String toString(ComponentStyle style)
{
return gson.toJson( style );
return VersionedComponentSerializer.getDefault().toString( style );
}
@Override
public BaseComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
if ( json.isJsonPrimitive() )
{
return new TextComponent( json.getAsString() );
}
JsonObject object = json.getAsJsonObject();
if ( object.has( "translate" ) )
{
return context.deserialize( json, TranslatableComponent.class );
}
if ( object.has( "keybind" ) )
{
return context.deserialize( json, KeybindComponent.class );
}
if ( object.has( "score" ) )
{
return context.deserialize( json, ScoreComponent.class );
}
if ( object.has( "selector" ) )
{
return context.deserialize( json, SelectorComponent.class );
}
return context.deserialize( json, TextComponent.class );
return VersionedComponentSerializer.getDefault().deserialize( json, typeOfT, context );
}
}

View File

@ -13,6 +13,11 @@ import net.md_5.bungee.api.chat.KeybindComponent;
public class KeybindComponentSerializer extends BaseComponentSerializer implements JsonSerializer<KeybindComponent>, JsonDeserializer<KeybindComponent>
{
public KeybindComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public KeybindComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{

View File

@ -13,6 +13,11 @@ import net.md_5.bungee.api.chat.ScoreComponent;
public class ScoreComponentSerializer extends BaseComponentSerializer implements JsonSerializer<ScoreComponent>, JsonDeserializer<ScoreComponent>
{
public ScoreComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public ScoreComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
{

View File

@ -13,6 +13,11 @@ import net.md_5.bungee.api.chat.SelectorComponent;
public class SelectorComponentSerializer extends BaseComponentSerializer implements JsonSerializer<SelectorComponent>, JsonDeserializer<SelectorComponent>
{
public SelectorComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public SelectorComponent deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException
{
@ -27,7 +32,7 @@ public class SelectorComponentSerializer extends BaseComponentSerializer impleme
JsonElement separator = object.get( "separator" );
if ( separator != null )
{
component.setSeparator( ComponentSerializer.deserialize( separator.getAsString() ) );
component.setSeparator( serializer.deserialize( separator.getAsString() ) );
}
deserialize( object, component, context );
@ -43,7 +48,7 @@ public class SelectorComponentSerializer extends BaseComponentSerializer impleme
if ( component.getSeparator() != null )
{
object.addProperty( "separator", ComponentSerializer.toString( component.getSeparator() ) );
object.addProperty( "separator", serializer.toString( component.getSeparator() ) );
}
return object;
}

View File

@ -13,6 +13,11 @@ import net.md_5.bungee.api.chat.TextComponent;
public class TextComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TextComponent>, JsonDeserializer<TextComponent>
{
public TextComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public TextComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{

View File

@ -15,6 +15,11 @@ import net.md_5.bungee.api.chat.TranslatableComponent;
public class TranslatableComponentSerializer extends BaseComponentSerializer implements JsonSerializer<TranslatableComponent>, JsonDeserializer<TranslatableComponent>
{
public TranslatableComponentSerializer(VersionedComponentSerializer serializer)
{
super( serializer );
}
@Override
public TranslatableComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{

View File

@ -0,0 +1,269 @@
package net.md_5.bungee.chat;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import java.lang.reflect.Type;
import java.util.Set;
import lombok.Getter;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentStyle;
import net.md_5.bungee.api.chat.ItemTag;
import net.md_5.bungee.api.chat.KeybindComponent;
import net.md_5.bungee.api.chat.ScoreComponent;
import net.md_5.bungee.api.chat.SelectorComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
import net.md_5.bungee.api.chat.hover.content.Content;
import net.md_5.bungee.api.chat.hover.content.Entity;
import net.md_5.bungee.api.chat.hover.content.EntitySerializer;
import net.md_5.bungee.api.chat.hover.content.Item;
import net.md_5.bungee.api.chat.hover.content.ItemSerializer;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.md_5.bungee.api.chat.hover.content.TextSerializer;
import org.jetbrains.annotations.ApiStatus;
@ApiStatus.Experimental
public class VersionedComponentSerializer implements JsonDeserializer<BaseComponent>
{
@Getter
@ApiStatus.Internal
private final Gson gson;
@Getter
@ApiStatus.Internal
private final ChatVersion version;
public VersionedComponentSerializer(ChatVersion version)
{
this.version = version;
this.gson = new GsonBuilder().
registerTypeAdapter( BaseComponent.class, this ).
registerTypeAdapter( TextComponent.class, new TextComponentSerializer( this ) ).
registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer( this ) ).
registerTypeAdapter( KeybindComponent.class, new KeybindComponentSerializer( this ) ).
registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer( this ) ).
registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer( this ) ).
registerTypeAdapter( ComponentStyle.class, new ComponentStyleSerializer() ).
registerTypeAdapter( Entity.class, new EntitySerializer( this ) ).
registerTypeAdapter( Text.class, new TextSerializer() ).
registerTypeAdapter( Item.class, new ItemSerializer() ).
registerTypeAdapter( ItemTag.class, new ItemTag.Serializer() ).
create();
}
private static final VersionedComponentSerializer v1_16 = new VersionedComponentSerializer( ChatVersion.V1_16 );
private static final VersionedComponentSerializer v1_21_5 = new VersionedComponentSerializer( ChatVersion.V1_21_5 );
public static VersionedComponentSerializer forVersion(ChatVersion version)
{
switch ( version )
{
case V1_16:
return v1_16;
case V1_21_5:
return v1_21_5;
default:
throw new IllegalArgumentException( "Unknown version " + version );
}
}
@Deprecated
@ApiStatus.Internal
public static VersionedComponentSerializer getDefault()
{
return v1_16;
}
@ApiStatus.Internal
public static final ThreadLocal<Set<BaseComponent>> serializedComponents = new ThreadLocal<Set<BaseComponent>>();
/**
* Parse a JSON-compliant String as an array of base components. The input
* can be one of either an array of components, or a single component
* object. If the input is an array, each component will be parsed
* individually and returned in the order that they were parsed. If the
* input is a single component object, a single-valued array with the
* component will be returned.
* <p>
* <strong>NOTE:</strong> {@link #deserialize(String)} is preferred as it
* will parse only one component as opposed to an array of components which
* is non- standard behavior. This method is still appropriate for parsing
* multiple components at once, although such use case is rarely (if at all)
* exhibited in vanilla Minecraft.
*
* @param json the component json to parse
* @return an array of all parsed components
*/
public BaseComponent[] parse(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
if ( jsonElement.isJsonArray() )
{
return gson.fromJson( jsonElement, BaseComponent[].class );
} else
{
return new BaseComponent[]
{
gson.fromJson( jsonElement, BaseComponent.class )
};
}
}
/**
* Deserialize a JSON-compliant String as a single component.
*
* @param json the component json to parse
* @return the deserialized component
* @throws IllegalArgumentException if anything other than a valid JSON
* component string is passed as input
*/
public BaseComponent deserialize(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
return deserialize( jsonElement );
}
/**
* Deserialize a JSON element as a single component.
*
* @param jsonElement the component json to parse
* @return the deserialized component
* @throws IllegalArgumentException if anything other than a valid JSON
* component is passed as input
*/
public BaseComponent deserialize(JsonElement jsonElement)
{
if ( jsonElement instanceof JsonPrimitive )
{
JsonPrimitive primitive = (JsonPrimitive) jsonElement;
if ( primitive.isString() )
{
return new TextComponent( primitive.getAsString() );
}
} else if ( jsonElement instanceof JsonArray )
{
BaseComponent[] array = gson.fromJson( jsonElement, BaseComponent[].class );
return TextComponent.fromArray( array );
}
return gson.fromJson( jsonElement, BaseComponent.class );
}
/**
* Deserialize a JSON-compliant String as a component style.
*
* @param json the component style json to parse
* @return the deserialized component style
* @throws IllegalArgumentException if anything other than a valid JSON
* component style string is passed as input
*/
public ComponentStyle deserializeStyle(String json)
{
JsonElement jsonElement = JsonParser.parseString( json );
return deserializeStyle( jsonElement );
}
/**
* Deserialize a JSON element as a component style.
*
* @param jsonElement the component style json to parse
* @return the deserialized component style
* @throws IllegalArgumentException if anything other than a valid JSON
* component style is passed as input
*/
public ComponentStyle deserializeStyle(JsonElement jsonElement)
{
return gson.fromJson( jsonElement, ComponentStyle.class );
}
public JsonElement toJson(BaseComponent component)
{
return gson.toJsonTree( component );
}
public JsonElement toJson(ComponentStyle style)
{
return gson.toJsonTree( style );
}
/**
* @param object the object to serialize
* @return the JSON string representation of the object
* @deprecated Error-prone, be careful which object you input here
*/
@Deprecated
public String toString(Object object)
{
return gson.toJson( object );
}
/**
* @param content the content to serialize
* @return the JSON string representation of the object
* @deprecated for legacy internal use only
*/
@Deprecated
public String toString(Content content)
{
return gson.toJson( content );
}
public String toString(BaseComponent component)
{
return gson.toJson( component );
}
public String toString(BaseComponent... components)
{
if ( components.length == 1 )
{
return gson.toJson( components[0] );
} else
{
return gson.toJson( new TextComponent( components ) );
}
}
public String toString(ComponentStyle style)
{
return gson.toJson( style );
}
@Override
public BaseComponent deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException
{
if ( json.isJsonPrimitive() )
{
return new TextComponent( json.getAsString() );
}
JsonObject object = json.getAsJsonObject();
if ( object.has( "translate" ) )
{
return context.deserialize( json, TranslatableComponent.class );
}
if ( object.has( "keybind" ) )
{
return context.deserialize( json, KeybindComponent.class );
}
if ( object.has( "score" ) )
{
return context.deserialize( json, ScoreComponent.class );
}
if ( object.has( "selector" ) )
{
return context.deserialize( json, SelectorComponent.class );
}
return context.deserialize( json, TextComponent.class );
}
}

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-config</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Config</name>

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-event</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Event</name>

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-log</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Log</name>

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-native</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Native</name>

View File

@ -37,9 +37,6 @@ public class NativeCipher implements BungeeCipher
@Override
public void cipher(ByteBuf in, ByteBuf out) throws GeneralSecurityException
{
// Smoke tests
in.memoryAddress();
out.memoryAddress();
Preconditions.checkState( ctx != 0, "Invalid pointer to AES key!" );
// Store how many bytes we can cipher

View File

@ -48,14 +48,17 @@ public class NativeZlib implements BungeeZlib
@Override
public void process(ByteBuf in, ByteBuf out) throws DataFormatException
{
// Smoke tests
in.memoryAddress();
out.memoryAddress();
Preconditions.checkState( ctx != 0, "Invalid pointer to compress!" );
while ( !nativeCompress.finished && ( compress || in.isReadable() ) )
{
if ( compress )
{
out.ensureWritable( OUTPUT_BUFFER_SIZE );
} else
{
Preconditions.checkArgument( out.isWritable(), "Output buffer overrun" );
}
int processed;
try

View File

@ -64,7 +64,7 @@ public class NativeZlibTest
zlib.process( originalBuf, compressed );
ByteBuf uncompressed = Unpooled.directBuffer();
ByteBuf uncompressed = Unpooled.directBuffer( dataBuf.length, dataBuf.length );
zlib.init( false, 0 );
zlib.process( compressed, uncompressed );

17
pom.xml
View File

@ -5,7 +5,7 @@
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>pom</packaging>
<name>BungeeCord-Parent</name>
@ -71,7 +71,7 @@
<properties>
<build.number>unknown</build.number>
<lombok.version>1.18.32</lombok.version>
<lombok.version>1.18.36</lombok.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -82,7 +82,7 @@
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-bom</artifactId>
<version>4.1.115.Final</version>
<version>4.1.119.Final</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -156,7 +156,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>3.3.1</version>
<version>3.6.0</version>
<executions>
<execution>
<phase>process-classes</phase>
@ -274,6 +274,15 @@
</pluginManagement>
</build>
</profile>
<profile>
<id>jdk-21-proc</id>
<activation>
<jdk>[21,)</jdk>
</activation>
<properties>
<maven.compiler.proc>full</maven.compiler.proc>
</properties>
</profile>
<profile>
<id>dist</id>
<build>

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-protocol</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Protocol</name>

View File

@ -0,0 +1,19 @@
package net.md_5.bungee.protocol;
import net.md_5.bungee.chat.ChatVersion;
import net.md_5.bungee.chat.VersionedComponentSerializer;
public class ChatSerializer
{
public static VersionedComponentSerializer forVersion(int protocolVersion)
{
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
return VersionedComponentSerializer.forVersion( ChatVersion.V1_21_5 );
} else
{
return VersionedComponentSerializer.forVersion( ChatVersion.V1_16 );
}
}
}

View File

@ -15,12 +15,12 @@ import java.util.Arrays;
import java.util.BitSet;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentStyle;
import net.md_5.bungee.chat.ComponentSerializer;
import se.llbit.nbt.ErrorTag;
import se.llbit.nbt.NamedTag;
import se.llbit.nbt.SpecificTag;
@ -69,6 +69,15 @@ public abstract class DefinedPacket
buf.writeBytes( b );
}
public static <T> T readStringMapKey(ByteBuf buf, Map<String, T> map)
{
String string = readString( buf );
T result = map.get( string );
Preconditions.checkArgument( result != null, "Unknown string key %s", string );
return result;
}
public static String readString(ByteBuf buf)
{
return readString( buf, Short.MAX_VALUE );
@ -110,12 +119,12 @@ public abstract class DefinedPacket
SpecificTag nbt = (SpecificTag) readTag( buf, protocolVersion );
JsonElement json = TagUtil.toJson( nbt );
return ComponentSerializer.deserialize( json );
return ChatSerializer.forVersion( protocolVersion ).deserialize( json );
} else
{
String string = readString( buf, maxStringLength );
return ComponentSerializer.deserialize( string );
return ChatSerializer.forVersion( protocolVersion ).deserialize( string );
}
}
@ -124,7 +133,7 @@ public abstract class DefinedPacket
SpecificTag nbt = (SpecificTag) readTag( buf, protocolVersion );
JsonElement json = TagUtil.toJson( nbt );
return ComponentSerializer.deserializeStyle( json );
return ChatSerializer.forVersion( protocolVersion ).deserializeStyle( json );
}
public static void writeEitherBaseComponent(Either<String, BaseComponent> message, ByteBuf buf, int protocolVersion)
@ -142,13 +151,13 @@ public abstract class DefinedPacket
{
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3 )
{
JsonElement json = ComponentSerializer.toJson( message );
JsonElement json = ChatSerializer.forVersion( protocolVersion ).toJson( message );
SpecificTag nbt = TagUtil.fromJson( json );
writeTag( nbt, buf, protocolVersion );
} else
{
String string = ComponentSerializer.toString( message );
String string = ChatSerializer.forVersion( protocolVersion ).toString( message );
writeString( string, buf );
}
@ -156,7 +165,7 @@ public abstract class DefinedPacket
public static void writeComponentStyle(ComponentStyle style, ByteBuf buf, int protocolVersion)
{
JsonElement json = ComponentSerializer.toJson( style );
JsonElement json = ChatSerializer.forVersion( protocolVersion ).toJson( style );
SpecificTag nbt = TagUtil.fromJson( json );
writeTag( nbt, buf, protocolVersion );
@ -282,6 +291,31 @@ public abstract class DefinedPacket
}
}
public static void setVarInt(int value, ByteBuf output, int pos, int len)
{
switch ( len )
{
case 1:
output.setByte( pos, value );
break;
case 2:
output.setShort( pos, ( value & 0x7F | 0x80 ) << 8 | ( value >>> 7 & 0x7F ) );
break;
case 3:
output.setMedium( pos, ( value & 0x7F | 0x80 ) << 16 | ( value >>> 7 & 0x7F | 0x80 ) << 8 | ( value >>> 14 & 0x7F ) );
break;
case 4:
output.setInt( pos, ( value & 0x7F | 0x80 ) << 24 | ( value >>> 7 & 0x7F | 0x80 ) << 16 | ( value >>> 14 & 0x7F | 0x80 ) << 8 | ( value >>> 21 & 0x7F ) );
break;
case 5:
output.setInt( pos, ( value & 0x7F | 0x80 ) << 24 | ( value >>> 7 & 0x7F | 0x80 ) << 16 | ( value >>> 14 & 0x7F | 0x80 ) << 8 | ( value >>> 21 & 0x7F | 0x80 ) );
output.setByte( pos + 4, value >>> 28 );
break;
default:
throw new IllegalArgumentException( "Invalid varint len: " + len );
}
}
public static int readVarShort(ByteBuf buf)
{
int low = buf.readUnsignedShort();

View File

@ -99,7 +99,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19_4, 0x23 ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x24 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x26 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x27 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x27 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x26 )
);
TO_CLIENT.registerPacket(
Login.class,
@ -117,7 +118,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19_4, 0x28 ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x29 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x2B ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x2C )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x2C ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x2B )
);
TO_CLIENT.registerPacket( Chat.class,
Chat::new,
@ -149,7 +151,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x43 ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x45 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x47 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x4C )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x4C ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x4B )
);
TO_CLIENT.registerPacket(
BossBar.class,
@ -160,7 +163,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_17, 0x0D ),
map( ProtocolConstants.MINECRAFT_1_19, 0x0A ),
map( ProtocolConstants.MINECRAFT_1_19_4, 0x0B ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x0A )
map( ProtocolConstants.MINECRAFT_1_20_2, 0x0A ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x09 )
);
TO_CLIENT.registerPacket(
PlayerListItem.class, // PlayerInfo
@ -191,7 +195,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19, 0x0E ),
map( ProtocolConstants.MINECRAFT_1_19_3, 0x0D ),
map( ProtocolConstants.MINECRAFT_1_19_4, 0x0F ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x10 )
map( ProtocolConstants.MINECRAFT_1_20_2, 0x10 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x0F )
);
TO_CLIENT.registerPacket(
ScoreboardObjective.class,
@ -210,7 +215,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x5A ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x5C ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x5E ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x64 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x64 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x63 )
);
TO_CLIENT.registerPacket(
ScoreboardScore.class,
@ -229,14 +235,16 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x5D ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x5F ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x61 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x68 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x68 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x67 )
);
TO_CLIENT.registerPacket(
ScoreboardScoreReset.class,
ScoreboardScoreReset::new,
map( ProtocolConstants.MINECRAFT_1_20_3, 0x42 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x44 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x49 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x49 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x48 )
);
TO_CLIENT.registerPacket(
ScoreboardDisplay.class,
@ -255,7 +263,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x53 ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x55 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x57 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x5C )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x5C ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x5B )
);
TO_CLIENT.registerPacket(
Team.class,
@ -274,7 +283,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x5C ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x5E ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x60 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x67 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x67 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x66 )
);
TO_CLIENT.registerPacket(
PluginMessage.class,
@ -292,7 +302,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19_3, 0x15 ),
map( ProtocolConstants.MINECRAFT_1_19_4, 0x17 ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x18 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x19 )
map( ProtocolConstants.MINECRAFT_1_20_5, 0x19 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x18 )
);
TO_CLIENT.registerPacket(
Kick.class,
@ -310,7 +321,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19_3, 0x17 ),
map( ProtocolConstants.MINECRAFT_1_19_4, 0x1A ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x1B ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x1D )
map( ProtocolConstants.MINECRAFT_1_20_5, 0x1D ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x1C )
);
TO_CLIENT.registerPacket(
Title.class,
@ -330,7 +342,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x61 ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x63 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x65 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x6C )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x6C ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x6B )
);
TO_CLIENT.registerPacket(
ClearTitles.class,
@ -339,7 +352,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19, 0x0D ),
map( ProtocolConstants.MINECRAFT_1_19_3, 0x0C ),
map( ProtocolConstants.MINECRAFT_1_19_4, 0x0E ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x0F )
map( ProtocolConstants.MINECRAFT_1_20_2, 0x0F ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x0E )
);
TO_CLIENT.registerPacket(
Subtitle.class,
@ -352,7 +366,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x5F ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x61 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x63 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x6A )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x6A ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x69 )
);
TO_CLIENT.registerPacket(
TitleTimes.class,
@ -365,7 +380,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x62 ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x64 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x66 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x6D )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x6D ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x6C )
);
TO_CLIENT.registerPacket(
SystemChat.class,
@ -377,7 +393,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x67 ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x69 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x6C ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x73 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x73 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x72 )
);
TO_CLIENT.registerPacket(
PlayerListHeaderFooter.class,
@ -400,7 +417,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x68 ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x6A ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x6D ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x74 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x74 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x73 )
);
TO_CLIENT.registerPacket(
EntityStatus.class,
@ -418,7 +436,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19_3, 0x19 ),
map( ProtocolConstants.MINECRAFT_1_19_4, 0x1C ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x1D ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x1F )
map( ProtocolConstants.MINECRAFT_1_20_5, 0x1F ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x1E )
);
TO_CLIENT.registerPacket(
Commands.class,
@ -431,7 +450,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19, 0x0F ),
map( ProtocolConstants.MINECRAFT_1_19_3, 0x0E ),
map( ProtocolConstants.MINECRAFT_1_19_4, 0x10 ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x11 )
map( ProtocolConstants.MINECRAFT_1_20_2, 0x11 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x10 )
);
TO_CLIENT.registerPacket(
GameState.class,
@ -446,7 +466,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19_4, 0x1F ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x20 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x22 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x23 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x23 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x22 )
);
TO_CLIENT.registerPacket(
ViewDistance.class,
@ -462,7 +483,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x51 ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x53 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x55 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x59 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x59 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x58 )
);
TO_CLIENT.registerPacket(
ServerData.class,
@ -474,7 +496,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x47 ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x49 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x4B ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x50 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x50 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x4F )
);
TO_CLIENT.registerPacket(
PlayerListItemRemove.class,
@ -483,7 +506,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19_4, 0x39 ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x3B ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x3D ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x3F )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x3F ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x3E )
);
TO_CLIENT.registerPacket(
PlayerListItemUpdate.class,
@ -492,7 +516,8 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_19_4, 0x3A ),
map( ProtocolConstants.MINECRAFT_1_20_2, 0x3C ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x3E ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x40 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x40 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x3F )
);
TO_CLIENT.registerPacket(
StartConfiguration.class,
@ -500,18 +525,21 @@ public enum Protocol
map( ProtocolConstants.MINECRAFT_1_20_2, 0x65 ),
map( ProtocolConstants.MINECRAFT_1_20_3, 0x67 ),
map( ProtocolConstants.MINECRAFT_1_20_5, 0x69 ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x70 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x70 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x6F )
);
TO_CLIENT.registerPacket(
CookieRequest.class,
CookieRequest::new,
map( ProtocolConstants.MINECRAFT_1_20_5, 0x16 )
map( ProtocolConstants.MINECRAFT_1_20_5, 0x16 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x15 )
);
TO_CLIENT.registerPacket(
StoreCookie.class,
StoreCookie::new,
map( ProtocolConstants.MINECRAFT_1_20_5, 0x6B ),
map( ProtocolConstants.MINECRAFT_1_21_2, 0x72 )
map( ProtocolConstants.MINECRAFT_1_21_2, 0x72 ),
map( ProtocolConstants.MINECRAFT_1_21_5, 0x71 )
);
TO_CLIENT.registerPacket(
Transfer.class,
@ -965,6 +993,8 @@ public enum Protocol
}
ProtocolData data = protocols.get( protocol );
Preconditions.checkState( data.packetConstructors[mapping.packetID] == null, "Duplicate packet mapping (%s)", mapping.packetID );
data.packetMap.put( packetClass, mapping.packetID );
data.packetConstructors[mapping.packetID] = constructor;
}

View File

@ -49,6 +49,7 @@ public class ProtocolConstants
public static final int MINECRAFT_1_21 = 767;
public static final int MINECRAFT_1_21_2 = 768;
public static final int MINECRAFT_1_21_4 = 769;
public static final int MINECRAFT_1_21_5 = 770;
public static final List<String> SUPPORTED_VERSIONS;
public static final List<Integer> SUPPORTED_VERSION_IDS;
@ -112,13 +113,14 @@ public class ProtocolConstants
ProtocolConstants.MINECRAFT_1_20_5,
ProtocolConstants.MINECRAFT_1_21,
ProtocolConstants.MINECRAFT_1_21_2,
ProtocolConstants.MINECRAFT_1_21_4
ProtocolConstants.MINECRAFT_1_21_4,
ProtocolConstants.MINECRAFT_1_21_5
);
if ( SNAPSHOT_SUPPORT )
{
// supportedVersions.add( "1.21.x" );
// supportedVersionIds.add( ProtocolConstants.MINECRAFT_1_21_4 );
// supportedVersionIds.add( ProtocolConstants.MINECRAFT_1_21_5 );
}
SUPPORTED_VERSIONS = supportedVersions.build();

View File

@ -82,13 +82,27 @@ public final class TagUtil
{
List<JsonElement> jsonArray = ( (JsonArray) json ).asList();
if ( jsonArray.isEmpty() )
Integer listType = null;
for ( JsonElement jsonEl : jsonArray )
{
int type = fromJson( jsonEl ).tagType();
if ( listType == null )
{
listType = type;
} else if ( listType != type )
{
listType = Tag.TAG_COMPOUND;
break;
}
}
if ( listType == null )
{
return new ListTag( Tag.TAG_END, Collections.emptyList() );
}
SpecificTag listTag;
int listType = fromJson( jsonArray.get( 0 ) ).tagType();
switch ( listType )
{
case Tag.TAG_BYTE:
@ -124,7 +138,7 @@ public final class TagUtil
for ( JsonElement jsonEl : jsonArray )
{
SpecificTag subTag = fromJson( jsonEl );
if ( !( subTag instanceof CompoundTag ) )
if ( listType == Tag.TAG_COMPOUND && !( subTag instanceof CompoundTag ) )
{
CompoundTag wrapper = new CompoundTag();
wrapper.add( "", subTag );

View File

@ -1,26 +0,0 @@
package net.md_5.bungee.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List;
/**
* Prepend length of the message as a Varint21 using an extra buffer for the
* length, avoiding copying packet data
*/
@ChannelHandler.Sharable
public class Varint21LengthFieldExtraBufPrepender extends MessageToMessageEncoder<ByteBuf>
{
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception
{
int bodyLen = msg.readableBytes();
ByteBuf lenBuf = ctx.alloc().ioBuffer( Varint21LengthFieldPrepender.varintSize( bodyLen ) );
DefinedPacket.writeVarInt( bodyLen, lenBuf );
out.add( lenBuf );
out.add( msg.retain() );
}
}

View File

@ -1,58 +0,0 @@
package net.md_5.bungee.protocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List;
import lombok.Setter;
/**
* Prepend length of the message as a Varint21 by writing length and data to a
* new buffer
*/
public class Varint21LengthFieldPrepender extends MessageToMessageEncoder<ByteBuf>
{
@Setter
private boolean compose = true;
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> list) throws Exception
{
int bodyLen = msg.readableBytes();
int headerLen = varintSize( bodyLen );
if ( compose )
{
ByteBuf buf = ctx.alloc().directBuffer( headerLen );
DefinedPacket.writeVarInt( bodyLen, buf );
list.add( ctx.alloc().compositeDirectBuffer( 2 ).addComponents( true, buf, msg.retain() ) );
} else
{
ByteBuf buf = ctx.alloc().directBuffer( headerLen + bodyLen );
DefinedPacket.writeVarInt( bodyLen, buf );
buf.writeBytes( msg );
list.add( buf );
}
}
static int varintSize(int paramInt)
{
if ( ( paramInt & 0xFFFFFF80 ) == 0 )
{
return 1;
}
if ( ( paramInt & 0xFFFFC000 ) == 0 )
{
return 2;
}
if ( ( paramInt & 0xFFE00000 ) == 0 )
{
return 3;
}
if ( ( paramInt & 0xF0000000 ) == 0 )
{
return 4;
}
return 5;
}
}

View File

@ -0,0 +1,54 @@
package net.md_5.bungee.protocol.channel;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
/**
* This class hold a netty channel initializer that calls the given {@link ChannelAcceptor}.
* Use {@link BungeeChannelInitializer#create(ChannelAcceptor)} to create a new instance.
* <p>
* Please note that this API is unsafe and doesn't provide any guarantees about
* the stability of the channel pipeline or the API itself. Use at your own
* risk.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public abstract class BungeeChannelInitializer
{
public abstract ChannelAcceptor getChannelAcceptor();
public abstract ChannelInitializer<Channel> getChannelInitializer();
/**
* Creates a new instance of BungeeChannelInitializer
*
* @param acceptor the {@link ChannelAcceptor} that will accept the channel
* and initializer the pipeline
* @return {@link BungeeChannelInitializer} containing a cached
* {@link ChannelInitializer} that will call the acceptor
*/
public static BungeeChannelInitializer create(ChannelAcceptor acceptor)
{
return new BungeeChannelInitializer()
{
@Getter
private final ChannelAcceptor channelAcceptor = acceptor;
@Getter // cache the ChannelInitializer
private final ChannelInitializer<Channel> channelInitializer = new ChannelInitializer<Channel>()
{
@Override
protected void initChannel(Channel channel) throws Exception
{
if ( !getChannelAcceptor().accept( channel ) )
{
channel.close();
}
}
};
};
}
}

View File

@ -0,0 +1,16 @@
package net.md_5.bungee.protocol.channel;
import io.netty.channel.Channel;
@FunctionalInterface
public interface ChannelAcceptor
{
/**
* Inside this method the pipeline should be initialized.
*
* @param channel the channel to be accepted and initialized
* @return if the channel was accepted
*/
boolean accept(Channel channel);
}

View File

@ -25,6 +25,7 @@ public class ClientChat extends DefinedPacket
private boolean signedPreview;
private ChatChain chain;
private SeenMessages seenMessages;
private byte checksum;
@Override
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
@ -57,6 +58,11 @@ public class ClientChat extends DefinedPacket
chain = new ChatChain();
chain.read( buf, direction, protocolVersion );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
checksum = buf.readByte();
}
}
@Override
@ -87,6 +93,11 @@ public class ClientChat extends DefinedPacket
{
chain.write( buf, direction, protocolVersion );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
buf.writeByte( checksum );
}
}
@Override

View File

@ -28,6 +28,7 @@ public class ClientCommand extends DefinedPacket
private boolean signedPreview;
private ChatChain chain;
private SeenMessages seenMessages;
private byte checksum;
@Override
public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion)
@ -68,6 +69,11 @@ public class ClientCommand extends DefinedPacket
chain = new ChatChain();
chain.read( buf, direction, protocolVersion );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
checksum = buf.readByte();
}
}
@Override
@ -101,6 +107,11 @@ public class ClientCommand extends DefinedPacket
{
chain.write( buf, direction, protocolVersion );
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
buf.writeByte( checksum );
}
}
@Override

View File

@ -313,6 +313,7 @@ public class Commands extends DefinedPacket
private static final ArgumentSerializer[] IDS_1_19_4;
private static final ArgumentSerializer[] IDS_1_20_3;
private static final ArgumentSerializer[] IDS_1_20_5;
private static final ArgumentSerializer[] IDS_1_21_5;
private static final Map<Class<?>, ProperArgumentSerializer<?>> PROPER_PROVIDERS = new HashMap<>();
//
private static final ArgumentSerializer<Void> VOID = new ArgumentSerializer<Void>()
@ -927,6 +928,65 @@ public class Commands extends DefinedPacket
get( "minecraft:loot_predicate", VOID ),
get( "minecraft:loot_modifier", VOID )
};
IDS_1_21_5 = new ArgumentSerializer[]
{
get( "brigadier:bool", VOID ),
get( "brigadier:float", FLOAT_RANGE ),
get( "brigadier:double", DOUBLE_RANGE ),
get( "brigadier:integer", INTEGER_RANGE ),
get( "brigadier:long", LONG_RANGE ),
get( "brigadier:string", STRING ),
get( "minecraft:entity", BYTE ),
get( "minecraft:game_profile", VOID ),
get( "minecraft:block_pos", VOID ),
get( "minecraft:column_pos", VOID ),
get( "minecraft:vec3", VOID ),
get( "minecraft:vec2", VOID ),
get( "minecraft:block_state", VOID ),
get( "minecraft:block_predicate", VOID ),
get( "minecraft:item_stack", VOID ),
get( "minecraft:item_predicate", VOID ),
get( "minecraft:color", VOID ),
get( "minecraft:component", VOID ),
get( "minecraft:style", VOID ),
get( "minecraft:message", VOID ),
get( "minecraft:nbt_compound_tag", VOID ),
get( "minecraft:nbt_tag", VOID ),
get( "minecraft:nbt_path", VOID ),
get( "minecraft:objective", VOID ),
get( "minecraft:objective_criteria", VOID ),
get( "minecraft:operation", VOID ),
get( "minecraft:particle", VOID ),
get( "minecraft:angle", VOID ),
get( "minecraft:rotation", VOID ),
get( "minecraft:scoreboard_slot", VOID ),
get( "minecraft:score_holder", BYTE ),
get( "minecraft:swizzle", VOID ),
get( "minecraft:team", VOID ),
get( "minecraft:item_slot", VOID ),
get( "minecraft:item_slots", VOID ),
get( "minecraft:resource_location", VOID ),
get( "minecraft:function", VOID ),
get( "minecraft:entity_anchor", VOID ),
get( "minecraft:int_range", VOID ),
get( "minecraft:float_range", VOID ),
get( "minecraft:dimension", VOID ),
get( "minecraft:gamemode", VOID ),
get( "minecraft:time", INTEGER ),
get( "minecraft:resource_or_tag", RAW_STRING ),
get( "minecraft:resource_or_tag_key", RAW_STRING ),
get( "minecraft:resource", RAW_STRING ),
get( "minecraft:resource_key", RAW_STRING ),
get( "minecraft:resource_selector", RAW_STRING ),
get( "minecraft:template_mirror", VOID ),
get( "minecraft:template_rotation", VOID ),
get( "minecraft:uuid", VOID ),
get( "minecraft:heightmap", VOID ),
get( "minecraft:loot_table", VOID ),
get( "minecraft:loot_predicate", VOID ),
get( "minecraft:loot_modifier", VOID )
};
}
private static void register(String name, ArgumentSerializer serializer)
@ -1346,7 +1406,10 @@ public class Commands extends DefinedPacket
{
key = readVarInt( buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_20_5 )
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
reader = IDS_1_21_5[(Integer) key];
} else if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_20_5 )
{
reader = IDS_1_20_5[(Integer) key];
} else if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_20_3 )

View File

@ -6,8 +6,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.ChatSerializer;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants;
@ -26,7 +26,7 @@ public class Kick extends DefinedPacket
{
if ( protocol == Protocol.LOGIN )
{
message = ComponentSerializer.deserialize( readString( buf ) );
message = ChatSerializer.forVersion( protocolVersion ).deserialize( readString( buf ) );
} else
{
message = readBaseComponent( buf, protocolVersion );
@ -38,7 +38,7 @@ public class Kick extends DefinedPacket
{
if ( protocol == Protocol.LOGIN )
{
writeString( ComponentSerializer.toString( message ), buf );
writeString( ChatSerializer.forVersion( protocolVersion ).toString( message ), buf );
} else
{
writeBaseComponent( message, buf, protocolVersion );

View File

@ -22,19 +22,21 @@ import net.md_5.bungee.protocol.ProtocolConstants;
public class PluginMessage extends DefinedPacket
{
public static final String BUNGEE_CHANNEL_LEGACY = "BungeeCord";
public static final String BUNGEE_CHANNEL_MODERN = "bungeecord:main";
public static final Function<String, String> MODERNISE = new Function<String, String>()
{
@Override
public String apply(String tag)
{
// Transform as per Bukkit
if ( tag.equals( "BungeeCord" ) )
if ( tag.equals( PluginMessage.BUNGEE_CHANNEL_LEGACY ) )
{
return "bungeecord:main";
return PluginMessage.BUNGEE_CHANNEL_MODERN;
}
if ( tag.equals( "bungeecord:main" ) )
if ( tag.equals( PluginMessage.BUNGEE_CHANNEL_MODERN ) )
{
return "BungeeCord";
return PluginMessage.BUNGEE_CHANNEL_LEGACY;
}
// Code that gets to here is UNLIKELY to be viable on the Bukkit side of side things,

View File

@ -1,10 +1,15 @@
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;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.AbstractPacketHandler;
import net.md_5.bungee.protocol.DefinedPacket;
@ -26,8 +31,8 @@ public class Team extends DefinedPacket
private Either<String, BaseComponent> displayName;
private Either<String, BaseComponent> prefix;
private Either<String, BaseComponent> suffix;
private String nameTagVisibility;
private String collisionRule;
private NameTagVisibility nameTagVisibility;
private CollisionRule collisionRule;
private int color;
private byte friendlyFire;
private String[] players;
@ -60,10 +65,18 @@ public class Team extends DefinedPacket
displayName = readEitherBaseComponent( buf, protocolVersion, false );
}
friendlyFire = buf.readByte();
nameTagVisibility = readString( buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
nameTagVisibility = NameTagVisibility.BY_ID[readVarInt( buf )];
collisionRule = CollisionRule.BY_ID[readVarInt( buf )];
} else
{
nameTagVisibility = readStringMapKey( buf, NameTagVisibility.BY_NAME );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_9 )
{
collisionRule = readString( buf );
collisionRule = readStringMapKey( buf, CollisionRule.BY_NAME );
}
}
color = ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ) ? readVarInt( buf ) : buf.readByte();
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 )
@ -97,10 +110,17 @@ public class Team extends DefinedPacket
writeEitherBaseComponent( suffix, buf, protocolVersion );
}
buf.writeByte( friendlyFire );
writeString( nameTagVisibility, buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_21_5 )
{
writeVarInt( nameTagVisibility.ordinal(), buf );
writeVarInt( collisionRule.ordinal(), buf );
} else
{
writeString( nameTagVisibility.getKey(), buf );
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_9 )
{
writeString( collisionRule, buf );
writeString( collisionRule.getKey(), buf );
}
}
if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 )
@ -128,4 +148,66 @@ public class Team extends DefinedPacket
{
handler.handle( this );
}
@Getter
@RequiredArgsConstructor
public enum NameTagVisibility
{
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( "" );
//
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
}
}
@Getter
@RequiredArgsConstructor
public enum CollisionRule
{
ALWAYS( "always" ),
NEVER( "never" ),
PUSH_OTHER_TEAMS( "pushOtherTeams" ),
PUSH_OWN_TEAM( "pushOwnTeam" );
//
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();
}
}
}

View File

@ -2,9 +2,20 @@ package net.md_5.bungee.protocol;
import static org.junit.jupiter.api.Assertions.*;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import org.junit.jupiter.api.Test;
import se.llbit.nbt.ByteArrayTag;
import se.llbit.nbt.CompoundTag;
import se.llbit.nbt.IntArrayTag;
import se.llbit.nbt.IntTag;
import se.llbit.nbt.ListTag;
import se.llbit.nbt.LongArrayTag;
import se.llbit.nbt.SpecificTag;
import se.llbit.nbt.StringTag;
import se.llbit.nbt.Tag;
public class TagUtilTest
{
@ -26,4 +37,190 @@ public class TagUtilTest
{
testDissembleReassemble( "{\"text\":\"\",\"extra\":[\"hello\",{\"text\":\"there\",\"color\":\"#ff0000\"},{\"text\":\"friend\",\"font\":\"minecraft:default\"}]}" );
}
public void testCreateMixedList(JsonArray array)
{
SpecificTag tag = TagUtil.fromJson( array );
assertInstanceOf( ListTag.class, tag );
ListTag list = (ListTag) tag;
assertEquals( SpecificTag.TAG_COMPOUND, list.getType() );
assertEquals( array.size(), list.size() );
for ( int i = 0; i < list.size(); i++ )
{
assertTrue( i < array.size() );
Tag element = list.get( i );
assertInstanceOf( CompoundTag.class, element );
CompoundTag compound = (CompoundTag) element;
JsonElement expected = array.get( i );
if ( expected instanceof JsonObject )
{
assertEquals( TagUtil.fromJson( expected ), compound );
} else
{
assertEquals( 1, compound.size() );
Tag value = compound.get( "" );
if ( expected instanceof JsonPrimitive )
{
JsonPrimitive primitive = (JsonPrimitive) expected;
if ( primitive.isNumber() )
{
Number number = primitive.getAsNumber();
if ( number instanceof Integer )
{
assertInstanceOf( IntTag.class, value );
IntTag integer = (IntTag) value;
assertEquals( array.get( i ).getAsInt(), integer.getData() );
}
} else if ( primitive.isString() )
{
assertInstanceOf( StringTag.class, value );
StringTag string = (StringTag) value;
assertEquals( array.get( i ).getAsString(), string.getData() );
}
}
}
}
}
@Test
public void testMixedListWithInt()
{
JsonArray array = new JsonArray();
array.add( 1 );
array.add( "a" );
testCreateMixedList( array );
}
@Test
public void testMixedListWithString()
{
JsonArray array = new JsonArray();
array.add( "a" );
array.add( 1L );
testCreateMixedList( array );
}
@Test
public void testMixedListWithObject()
{
JsonObject compound = new JsonObject();
compound.addProperty( "a", "b" );
JsonArray array = new JsonArray();
array.add( compound );
array.add( 1L );
testCreateMixedList( array );
}
@Test
public void testCreateEmptyList()
{
JsonArray array = new JsonArray();
SpecificTag tag = TagUtil.fromJson( array );
assertInstanceOf( ListTag.class, tag );
ListTag list = (ListTag) tag;
assertEquals( 0, list.size() );
assertEquals( Tag.TAG_END, list.getType() );
}
@Test
public void testCreateByteArray()
{
JsonArray array = new JsonArray();
array.add( ( (byte) 1 ) );
array.add( ( (byte) 2 ) );
SpecificTag tag = TagUtil.fromJson( array );
assertInstanceOf( ByteArrayTag.class, tag );
ByteArrayTag byteArray = (ByteArrayTag) tag;
assertEquals( 2, byteArray.getData().length );
for ( int i = 0; i < byteArray.getData().length; i++ )
{
assertTrue( i < array.size() );
byte item = byteArray.getData()[i];
assertEquals( array.get( i ).getAsByte(), item );
}
}
@Test
public void testCreateIntArray()
{
JsonArray array = new JsonArray();
array.add( 1 );
array.add( 2 );
SpecificTag tag = TagUtil.fromJson( array );
assertInstanceOf( IntArrayTag.class, tag );
IntArrayTag intArray = (IntArrayTag) tag;
assertEquals( 2, intArray.getData().length );
for ( int i = 0; i < intArray.getData().length; i++ )
{
assertTrue( i < array.size() );
int item = intArray.getData()[i];
assertEquals( array.get( i ).getAsInt(), item );
}
}
@Test
public void testCreateLongArray()
{
JsonArray array = new JsonArray();
array.add( 1L );
array.add( 2L );
SpecificTag tag = TagUtil.fromJson( array );
assertInstanceOf( LongArrayTag.class, tag );
LongArrayTag intArray = (LongArrayTag) tag;
assertEquals( 2, intArray.getData().length );
for ( int i = 0; i < intArray.getData().length; i++ )
{
assertTrue( i < array.size() );
long item = intArray.getData()[i];
assertEquals( array.get( i ).getAsLong(), item );
}
}
@Test
public void testCreateStringList()
{
JsonArray array = new JsonArray();
array.add( "a" );
array.add( "b" );
SpecificTag tag = TagUtil.fromJson( array );
assertInstanceOf( ListTag.class, tag );
ListTag list = (ListTag) tag;
assertEquals( SpecificTag.TAG_STRING, list.getType() );
assertEquals( 2, list.size() );
for ( int i = 0; i < list.size(); i++ )
{
assertTrue( i < array.size() );
Tag item = list.get( i );
assertInstanceOf( StringTag.class, item );
StringTag string = (StringTag) item;
assertEquals( array.get( i ).getAsString(), string.getData() );
}
}
}

View File

@ -9,8 +9,8 @@ public class PluginMessageTest
@Test
public void testModerniseChannel()
{
assertEquals( "bungeecord:main", PluginMessage.MODERNISE.apply( "BungeeCord" ) );
assertEquals( "BungeeCord", PluginMessage.MODERNISE.apply( "bungeecord:main" ) );
assertEquals( PluginMessage.BUNGEE_CHANNEL_MODERN, PluginMessage.MODERNISE.apply( PluginMessage.BUNGEE_CHANNEL_LEGACY ) );
assertEquals( PluginMessage.BUNGEE_CHANNEL_LEGACY, PluginMessage.MODERNISE.apply( PluginMessage.BUNGEE_CHANNEL_MODERN ) );
assertEquals( "legacy:foo", PluginMessage.MODERNISE.apply( "FoO" ) );
assertEquals( "foo:bar", PluginMessage.MODERNISE.apply( "foo:bar" ) );
}

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-proxy</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Proxy</name>

View File

@ -6,8 +6,6 @@ import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
@ -52,31 +50,17 @@ import lombok.Getter;
import lombok.Setter;
import lombok.Synchronized;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ReconnectHandler;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.Title;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ComponentStyle;
import net.md_5.bungee.api.chat.KeybindComponent;
import net.md_5.bungee.api.chat.ScoreComponent;
import net.md_5.bungee.api.chat.SelectorComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.TranslatableComponent;
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.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.chat.ComponentStyleSerializer;
import net.md_5.bungee.chat.KeybindComponentSerializer;
import net.md_5.bungee.chat.ScoreComponentSerializer;
import net.md_5.bungee.chat.SelectorComponentSerializer;
import net.md_5.bungee.chat.TextComponentSerializer;
import net.md_5.bungee.chat.TranslatableComponentSerializer;
import net.md_5.bungee.command.CommandBungee;
import net.md_5.bungee.command.CommandEnd;
import net.md_5.bungee.command.CommandIP;
@ -94,6 +78,7 @@ import net.md_5.bungee.log.LoggingOutputStream;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.channel.BungeeChannelInitializer;
import net.md_5.bungee.protocol.packet.PluginMessage;
import net.md_5.bungee.query.RemoteQuery;
import net.md_5.bungee.scheduler.BungeeScheduler;
@ -163,22 +148,12 @@ public class BungeeCord extends ProxyServer
private final ConsoleReader consoleReader;
@Getter
private final Logger logger;
public final Gson gson = new GsonBuilder()
.registerTypeAdapter( BaseComponent.class, new ComponentSerializer() )
.registerTypeAdapter( TextComponent.class, new TextComponentSerializer() )
.registerTypeAdapter( TranslatableComponent.class, new TranslatableComponentSerializer() )
.registerTypeAdapter( KeybindComponent.class, new KeybindComponentSerializer() )
.registerTypeAdapter( ScoreComponent.class, new ScoreComponentSerializer() )
.registerTypeAdapter( SelectorComponent.class, new SelectorComponentSerializer() )
.registerTypeAdapter( ComponentStyle.class, new ComponentStyleSerializer() )
.registerTypeAdapter( ServerPing.PlayerInfo.class, new PlayerInfoSerializer() )
.registerTypeAdapter( Favicon.class, Favicon.getFaviconTypeAdapter() ).create();
@Getter
private ConnectionThrottle connectionThrottle;
{
// TODO: Proper fallback when we interface the manager
registerChannel( "BungeeCord" );
registerChannel( PluginMessage.BUNGEE_CHANNEL_LEGACY );
}
public static BungeeCord getInstance()
@ -186,6 +161,21 @@ public class BungeeCord extends ProxyServer
return (BungeeCord) ProxyServer.getInstance();
}
private final Unsafe unsafe = new Unsafe()
{
@Getter
@Setter
private BungeeChannelInitializer frontendChannelInitializer;
@Getter
@Setter
private BungeeChannelInitializer backendChannelInitializer;
@Getter
@Setter
private BungeeChannelInitializer serverInfoChannelInitializer;
};
@SuppressFBWarnings("DM_DEFAULT_ENCODING")
public BungeeCord() throws IOException
{
@ -354,7 +344,7 @@ public class BungeeCord extends ProxyServer
.channel( PipelineUtils.getServerChannel( info.getSocketAddress() ) )
.option( ChannelOption.SO_REUSEADDR, true ) // TODO: Move this elsewhere!
.childAttr( PipelineUtils.LISTENER, info )
.childHandler( PipelineUtils.SERVER_CHILD )
.childHandler( unsafe().getFrontendChannelInitializer().getChannelInitializer() )
.group( eventLoops )
.localAddress( info.getSocketAddress() )
.bind().addListener( listener );
@ -825,4 +815,10 @@ public class BungeeCord extends ProxyServer
{
return new BungeeTitle();
}
@Override
public Unsafe unsafe()
{
return unsafe;
}
}

View File

@ -186,7 +186,7 @@ public class BungeeServerInfo implements ServerInfo
new Bootstrap()
.channel( PipelineUtils.getChannel( socketAddress ) )
.group( BungeeCord.getInstance().eventLoops )
.handler( PipelineUtils.BASE_SERVERSIDE )
.handler( ProxyServer.getInstance().unsafe().getServerInfoChannelInitializer().getChannelInitializer() )
.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, BungeeCord.getInstance().getConfig().getRemotePingTimeout() )
.remoteAddress( socketAddress )
.connect()

View File

@ -156,7 +156,7 @@ public class BungeeTitle implements Title
{
if ( player.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_17 )
{
( (UserConnection) player ).sendPacketQueued( packet.newPacket );
player.unsafe().sendPacketQueued( packet.newPacket );
} else
{
player.unsafe().sendPacket( packet.oldPacket );

View File

@ -14,6 +14,7 @@ import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants;
import net.md_5.bungee.protocol.packet.PluginMessage;
@RequiredArgsConstructor
@ -40,6 +41,18 @@ public class ServerConnection implements Server
{
ch.write( packet );
}
@Override
public void sendPacketQueued(DefinedPacket packet)
{
if ( ch.getEncodeVersion() >= ProtocolConstants.MINECRAFT_1_20_2 )
{
ServerConnection.this.sendPacketQueued( packet );
} else
{
sendPacket( packet );
}
}
};
public void sendPacketQueued(DefinedPacket packet)
@ -53,6 +66,8 @@ public class ServerConnection implements Server
Protocol encodeProtocol = ch.getEncodeProtocol();
if ( !encodeProtocol.TO_SERVER.hasPacket( packet.getClass(), ch.getEncodeVersion() ) )
{
// we should limit this so bad api usage won't oom the server.
Preconditions.checkState( packetQueue.size() <= 4096, "too many queued packets" );
packetQueue.add( packet );
} else
{

View File

@ -25,7 +25,6 @@ import net.md_5.bungee.api.score.Objective;
import net.md_5.bungee.api.score.Score;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.CancelSendSignal;
import net.md_5.bungee.connection.DownstreamBridge;
import net.md_5.bungee.connection.LoginResult;
@ -104,6 +103,7 @@ public class ServerConnector extends PacketHandler
@Override
public void connected(ChannelWrapper channel) throws Exception
{
channel.setVersion( user.getPendingConnection().getVersion() );
this.ch = channel;
this.handshakeHandler = new ForgeServerHandler( user, ch, target );
@ -117,7 +117,7 @@ public class ServerConnector extends PacketHandler
LoginResult profile = user.getPendingConnection().getLoginProfile();
if ( profile != null && profile.getProperties() != null && profile.getProperties().length > 0 )
{
newHost += "\00" + BungeeCord.getInstance().gson.toJson( profile.getProperties() );
newHost += "\00" + LoginResult.GSON.toJson( profile.getProperties() );
}
copiedHandshake.setHost( newHost );
} else if ( !user.getExtraDataInHandshake().isEmpty() )
@ -301,7 +301,7 @@ public class ServerConnector extends PacketHandler
{
user.unsafe().sendPacket( new ScoreboardObjective(
objective.getName(),
( user.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_13 ) ? Either.right( ComponentSerializer.deserialize( objective.getValue() ) ) : Either.left( objective.getValue() ),
( user.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_13 ) ? Either.right( user.getChatSerializer().deserialize( objective.getValue() ) ) : Either.left( objective.getValue() ),
ScoreboardObjective.HealthDisplay.fromString( objective.getType() ),
(byte) 1, null )
);

View File

@ -3,10 +3,8 @@ package net.md_5.bungee;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.util.internal.PlatformDependent;
import java.net.InetSocketAddress;
@ -40,7 +38,7 @@ 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.chat.ComponentSerializer;
import net.md_5.bungee.chat.VersionedComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.entitymap.EntityMap;
import net.md_5.bungee.forge.ForgeClientHandler;
@ -49,9 +47,8 @@ import net.md_5.bungee.forge.ForgeServerHandler;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.HandlerBoss;
import net.md_5.bungee.netty.PipelineUtils;
import net.md_5.bungee.protocol.ChatSerializer;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.MinecraftDecoder;
import net.md_5.bungee.protocol.MinecraftEncoder;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.ProtocolConstants;
@ -137,6 +134,8 @@ public final class UserConnection implements ProxiedPlayer
private String displayName;
@Getter
private EntityMap entityRewrite;
@Getter
private VersionedComponentSerializer chatSerializer;
private Locale locale;
/*========================================================================*/
@Getter
@ -154,11 +153,24 @@ public final class UserConnection implements ProxiedPlayer
{
ch.write( packet );
}
@Override
public void sendPacketQueued(DefinedPacket packet)
{
if ( pendingConnection.getVersion() >= ProtocolConstants.MINECRAFT_1_20_2 )
{
UserConnection.this.sendPacketQueued( packet );
} else
{
sendPacket( packet );
}
}
};
public boolean init()
{
this.entityRewrite = EntityMap.getEntityMap( getPendingConnection().getVersion() );
this.chatSerializer = ChatSerializer.forVersion( getPendingConnection().getVersion() );
this.displayName = name;
@ -195,6 +207,8 @@ public final class UserConnection implements ProxiedPlayer
Protocol encodeProtocol = ch.getEncodeProtocol();
if ( !encodeProtocol.TO_CLIENT.hasPacket( packet.getClass(), getPendingConnection().getVersion() ) )
{
// we should limit this so bad api usage won't oom the server.
Preconditions.checkState( packetQueue.size() <= 4096, "too many queued packets" );
packetQueue.add( packet );
} else
{
@ -362,17 +376,6 @@ public final class UserConnection implements ProxiedPlayer
pendingConnects.add( target );
ChannelInitializer initializer = new ChannelInitializer()
{
@Override
protected void initChannel(Channel ch) throws Exception
{
PipelineUtils.BASE_SERVERSIDE.initChannel( ch );
ch.pipeline().addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) );
ch.pipeline().addAfter( PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, false, getPendingConnection().getVersion() ) );
ch.pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target ) );
}
};
ChannelFutureListener listener = new ChannelFutureListener()
{
@Override
@ -401,13 +404,16 @@ public final class UserConnection implements ProxiedPlayer
{
sendMessage( bungee.getTranslation( "fallback_kick", connectionFailMessage( future.cause() ) ) );
}
} else
{
future.channel().pipeline().get( HandlerBoss.class ).setHandler( new ServerConnector( bungee, UserConnection.this, target ) );
}
}
};
Bootstrap b = new Bootstrap()
.channel( PipelineUtils.getChannel( target.getAddress() ) )
.group( ch.getHandle().eventLoop() )
.handler( initializer )
.handler( bungee.unsafe().getBackendChannelInitializer().getChannelInitializer() )
.option( ChannelOption.CONNECT_TIMEOUT_MILLIS, request.getConnectTimeout() )
.remoteAddress( target.getAddress() );
// Windows is bugged, multi homed users will just have to live with random connecting IPs
@ -554,7 +560,7 @@ public final class UserConnection implements ProxiedPlayer
sendPacketQueued( new SystemChat( message, position.ordinal() ) );
} else
{
sendPacketQueued( new Chat( ComponentSerializer.toString( message ), (byte) position.ordinal(), sender ) );
sendPacketQueued( new Chat( chatSerializer.toString( message ), (byte) position.ordinal(), sender ) );
}
}

View File

@ -1,57 +0,0 @@
package net.md_5.bungee.compress;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List;
import java.util.zip.Deflater;
import lombok.Getter;
import lombok.Setter;
import net.md_5.bungee.jni.zlib.BungeeZlib;
import net.md_5.bungee.protocol.DefinedPacket;
public class PacketCompressor extends MessageToMessageEncoder<ByteBuf>
{
@Getter
private final BungeeZlib zlib = CompressFactory.zlib.newInstance();
@Setter
private int threshold = 256;
@Setter
private boolean compose = true;
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception
{
zlib.init( true, Deflater.DEFAULT_COMPRESSION );
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception
{
zlib.free();
}
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception
{
int origSize = msg.readableBytes();
if ( origSize < threshold )
{
if ( compose )
{
// create a virtual buffer to avoid copying of data
out.add( ctx.alloc().compositeDirectBuffer( 2 ).addComponents( true, ctx.alloc().directBuffer( 1 ).writeByte( 0 ), msg.retain() ) );
} else
{
out.add( ctx.alloc().directBuffer( origSize + 1 ).writeByte( 0 ).writeBytes( msg ) );
}
} else
{
ByteBuf buf = ctx.alloc().directBuffer( BungeeZlib.OUTPUT_BUFFER_SIZE );
DefinedPacket.writeVarInt( origSize, buf );
zlib.process( msg, buf );
out.add( buf );
}
}
}

View File

@ -7,10 +7,12 @@ import io.netty.handler.codec.MessageToMessageDecoder;
import java.util.List;
import net.md_5.bungee.jni.zlib.BungeeZlib;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.OverflowPacketException;
public class PacketDecompressor extends MessageToMessageDecoder<ByteBuf>
{
private static final int MAX_DECOMPRESSED_LEN = 1 << 23;
private final BungeeZlib zlib = CompressFactory.zlib.newInstance();
@Override
@ -34,8 +36,14 @@ public class PacketDecompressor extends MessageToMessageDecoder<ByteBuf>
out.add( in.retain() );
} else
{
ByteBuf decompressed = ctx.alloc().directBuffer();
if ( size > MAX_DECOMPRESSED_LEN )
{
throw new OverflowPacketException( "Packet may not be larger than " + MAX_DECOMPRESSED_LEN + " bytes" );
}
// Do not use size as max capacity, as its possible that the entity rewriter increases the size afterwards
// This would result in a kick (it happens rarely as the entity ids size must differ)
ByteBuf decompressed = ctx.alloc().directBuffer( size, MAX_DECOMPRESSED_LEN );
try
{
zlib.process( in, decompressed );

View File

@ -69,6 +69,8 @@ public class Configuration implements ProxyConfig
private boolean preventProxyConnections;
private boolean forgeSupport;
private boolean rejectTransfers;
private int maxPacketsPerSecond = 1 << 12;
private int maxPacketDataPerSecond = 1 << 25;
public void load()
{
@ -105,6 +107,8 @@ public class Configuration implements ProxyConfig
preventProxyConnections = adapter.getBoolean( "prevent_proxy_connections", preventProxyConnections );
forgeSupport = adapter.getBoolean( "forge_support", forgeSupport );
rejectTransfers = adapter.getBoolean( "reject_transfers", rejectTransfers );
maxPacketsPerSecond = adapter.getInt( "max_packets_per_second", maxPacketsPerSecond );
maxPacketDataPerSecond = adapter.getInt( "max_packets_data_per_second", maxPacketDataPerSecond );
disabledCommands = new CaseInsensitiveSet( (Collection<String>) adapter.getList( "disabled_commands", Arrays.asList( "disabledcommandhere" ) ) );

View File

@ -189,7 +189,9 @@ public class YamlConfig implements ConfigurationAdapter
@Override
public int getInt(String path, int def)
{
return get( path, def );
// #3791: Sometimes third-party tools rewrite large ints into doubles
Number number = get( path, def );
return number.intValue();
}
@Override

View File

@ -51,7 +51,6 @@ import net.md_5.bungee.api.score.Position;
import net.md_5.bungee.api.score.Score;
import net.md_5.bungee.api.score.Scoreboard;
import net.md_5.bungee.api.score.Team;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.entitymap.EntityMap;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler;
@ -201,7 +200,7 @@ public class DownstreamBridge extends PacketHandler
switch ( objective.getAction() )
{
case 0:
serverScoreboard.addObjective( new Objective( objective.getName(), ( objective.getValue().isLeft() ) ? objective.getValue().getLeft() : ComponentSerializer.toString( objective.getValue().getRight() ), objective.getType().toString() ) );
serverScoreboard.addObjective( new Objective( objective.getName(), ( objective.getValue().isLeft() ) ? objective.getValue().getLeft() : con.getChatSerializer().toString( objective.getValue().getRight() ), objective.getType().toString() ) );
break;
case 1:
serverScoreboard.removeObjective( objective.getName() );
@ -210,7 +209,7 @@ public class DownstreamBridge extends PacketHandler
Objective oldObjective = serverScoreboard.getObjective( objective.getName() );
if ( oldObjective != null )
{
oldObjective.setValue( ( objective.getValue().isLeft() ) ? objective.getValue().getLeft() : ComponentSerializer.toString( objective.getValue().getRight() ) );
oldObjective.setValue( ( objective.getValue().isLeft() ) ? objective.getValue().getLeft() : con.getChatSerializer().toString( objective.getValue().getRight() ) );
oldObjective.setType( objective.getType().toString() );
}
break;
@ -284,13 +283,16 @@ public class DownstreamBridge extends PacketHandler
{
if ( team.getMode() == 0 || team.getMode() == 2 )
{
t.setDisplayName( team.getDisplayName().getLeftOrCompute( ComponentSerializer::toString ) );
t.setPrefix( team.getPrefix().getLeftOrCompute( ComponentSerializer::toString ) );
t.setSuffix( team.getSuffix().getLeftOrCompute( ComponentSerializer::toString ) );
t.setDisplayName( team.getDisplayName().getLeftOrCompute( (component) -> con.getChatSerializer().toString( component ) ) );
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() );
t.setCollisionRule( team.getCollisionRule() );
t.setNameTagVisibility( team.getNameTagVisibility().getKey() );
t.setColor( team.getColor() );
if ( team.getCollisionRule() != null )
{
t.setCollisionRule( team.getCollisionRule().getKey() );
}
}
if ( team.getPlayers() != null )
{
@ -337,7 +339,7 @@ public class DownstreamBridge extends PacketHandler
throw CancelSendSignal.INSTANCE;
}
if ( pluginMessage.getTag().equals( "BungeeCord" ) )
if ( pluginMessage.getTag().equals( PluginMessage.BUNGEE_CHANNEL_LEGACY ) )
{
ByteArrayDataOutput out = ByteStreams.newDataOutput();
String subChannel = in.readUTF();
@ -361,7 +363,7 @@ public class DownstreamBridge extends PacketHandler
out.write( data );
byte[] payload = out.toByteArray();
target.getServer().sendData( "BungeeCord", payload );
target.getServer().sendData( PluginMessage.BUNGEE_CHANNEL_LEGACY, payload );
}
// Null out stream, important as we don't want to send to ourselves
@ -393,7 +395,7 @@ public class DownstreamBridge extends PacketHandler
{
if ( server != this.server.getInfo() )
{
server.sendData( "BungeeCord", payload );
server.sendData( PluginMessage.BUNGEE_CHANNEL_LEGACY, payload );
}
}
break;
@ -402,7 +404,7 @@ public class DownstreamBridge extends PacketHandler
{
if ( server != this.server.getInfo() )
{
server.sendData( "BungeeCord", payload, false );
server.sendData( PluginMessage.BUNGEE_CHANNEL_LEGACY, payload, false );
}
}
break;
@ -410,7 +412,7 @@ public class DownstreamBridge extends PacketHandler
ServerInfo server = bungee.getServerInfo( target );
if ( server != null )
{
server.sendData( "BungeeCord", payload );
server.sendData( PluginMessage.BUNGEE_CHANNEL_LEGACY, payload );
}
break;
}
@ -558,7 +560,7 @@ public class DownstreamBridge extends PacketHandler
case "MessageRaw":
{
String target = in.readUTF();
BaseComponent[] message = ComponentSerializer.parse( in.readUTF() );
BaseComponent[] message = con.getChatSerializer().parse( in.readUTF() );
if ( target.equals( "ALL" ) )
{
for ( ProxiedPlayer player : bungee.getPlayers() )
@ -625,7 +627,7 @@ public class DownstreamBridge extends PacketHandler
ProxiedPlayer player = bungee.getPlayer( in.readUTF() );
if ( player != null )
{
BaseComponent[] kickReason = ComponentSerializer.parse( in.readUTF() );
BaseComponent[] kickReason = con.getChatSerializer().parse( in.readUTF() );
player.disconnect( kickReason );
}
break;
@ -638,7 +640,7 @@ public class DownstreamBridge extends PacketHandler
byte[] b = out.toByteArray();
if ( b.length != 0 )
{
server.sendData( "BungeeCord", b );
server.sendData( PluginMessage.BUNGEE_CHANNEL_LEGACY, b );
}
}

View File

@ -2,6 +2,7 @@ package net.md_5.bungee.connection;
import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import io.netty.channel.EventLoop;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@ -121,6 +122,12 @@ public class InitialHandler extends PacketHandler implements PendingConnection
{
ch.write( packet );
}
@Override
public void sendPacketQueued(DefinedPacket packet)
{
throw new UnsupportedOperationException( "Not supported" );
}
};
@Getter
private boolean onlineMode = BungeeCord.getInstance().config.isOnlineMode();
@ -196,12 +203,6 @@ public class InitialHandler extends PacketHandler implements PendingConnection
}
}
@Override
public void handle(PluginMessage pluginMessage) throws Exception
{
this.relayMessage( pluginMessage );
}
@Override
public void handle(LegacyHandshake legacyHandshake) throws Exception
{
@ -237,11 +238,6 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override
public void done(ProxyPingEvent result, Throwable error)
{
if ( ch.isClosing() )
{
return;
}
ServerPing legacy = result.getResponse();
String kickMessage;
@ -265,7 +261,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
}
};
bungee.getPluginManager().callEvent( new ProxyPingEvent( InitialHandler.this, result, callback ) );
bungee.getPluginManager().callEvent( new ProxyPingEvent( InitialHandler.this, result, eventLoopCallback( callback ) ) );
}
};
@ -318,7 +314,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override
public void done(ProxyPingEvent pingResult, Throwable error)
{
Gson gson = BungeeCord.getInstance().gson;
Gson gson = PingHandler.gson;
unsafe.sendPacket( new StatusResponse( gson.toJson( pingResult.getResponse() ) ) );
if ( bungee.getConnectionThrottle() != null )
{
@ -327,7 +323,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
}
};
bungee.getPluginManager().callEvent( new ProxyPingEvent( InitialHandler.this, result, callback ) );
bungee.getPluginManager().callEvent( new ProxyPingEvent( InitialHandler.this, result, eventLoopCallback( callback ) ) );
}
};
@ -486,10 +482,6 @@ public class InitialHandler extends PacketHandler implements PendingConnection
disconnect( ( reason != null ) ? reason : TextComponent.fromLegacy( bungee.getTranslation( "kick_message" ) ) );
return;
}
if ( ch.isClosing() )
{
return;
}
if ( !realName.equals( name ) )
{
// Floodgate changes the name attribute with reflexion
@ -513,7 +505,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
};
// fire pre login event
bungee.getPluginManager().callEvent( new PreLoginEvent( InitialHandler.this, callback ) );
bungee.getPluginManager().callEvent( new PreLoginEvent( InitialHandler.this, eventLoopCallback( callback ) ) );
}
@Override
@ -526,7 +518,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
BungeeCipher decrypt = EncryptionUtil.getCipher( false, sharedKey );
ch.addBefore( PipelineUtils.FRAME_DECODER, PipelineUtils.DECRYPT_HANDLER, new CipherDecoder( decrypt ) );
BungeeCipher encrypt = EncryptionUtil.getCipher( true, sharedKey );
ch.addBefore( PipelineUtils.FRAME_PREPENDER, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) );
ch.addBefore( PipelineUtils.FRAME_PREPENDER_AND_COMPRESS, PipelineUtils.ENCRYPT_HANDLER, new CipherEncoder( encrypt ) );
// disable use of composite buffers if we use natives
ch.updateComposite();
@ -552,7 +544,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
{
if ( error == null )
{
LoginResult obj = BungeeCord.getInstance().gson.fromJson( result, LoginResult.class );
LoginResult obj = LoginResult.GSON.fromJson( result, LoginResult.class );
if ( obj != null && obj.getId() != null )
{
loginProfile = obj;
@ -648,18 +640,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
disconnect( ( reason != null ) ? reason : TextComponent.fromLegacy( bungee.getTranslation( "kick_message" ) ) );
return;
}
if ( ch.isClosing() )
{
return;
}
ch.getHandle().eventLoop().execute( new Runnable()
{
@Override
public void run()
{
if ( !ch.isClosing() )
{
userCon = new UserConnection( bungee, ch, getName(), InitialHandler.this );
userCon.setCompressionThreshold( BungeeCord.getInstance().config.getCompressionThreshold() );
@ -670,13 +651,10 @@ public class InitialHandler extends PacketHandler implements PendingConnection
}
finish2();
}
}
} );
}
};
// fire login event
bungee.getPluginManager().callEvent( new LoginEvent( InitialHandler.this, complete ) );
bungee.getPluginManager().callEvent( new LoginEvent( InitialHandler.this, eventLoopCallback( complete ) ) );
}
private void finish2()
@ -707,18 +685,12 @@ public class InitialHandler extends PacketHandler implements PendingConnection
@Override
public void done(PostLoginEvent result, Throwable error)
{
// #3612: Don't progress further if disconnected during event
if ( ch.isClosing() )
{
return;
}
userCon.connect( result.getTarget(), null, true, ServerConnectEvent.Reason.JOIN_PROXY );
}
};
// fire post-login event
bungee.getPluginManager().callEvent( new PostLoginEvent( userCon, initialServer, complete ) );
bungee.getPluginManager().callEvent( new PostLoginEvent( userCon, initialServer, eventLoopCallback( complete ) ) );
}
@Override
@ -731,6 +703,9 @@ public class InitialHandler extends PacketHandler implements PendingConnection
}
Preconditions.checkState( future != null, "Unexpected custom LoginPayloadResponse" );
future.complete( response.getData() );
// we should never pass this to the backend
throw CancelSendSignal.INSTANCE;
}
@Override
@ -997,4 +972,32 @@ public class InitialHandler extends PacketHandler implements PendingConnection
unsafe.sendPacket( new LoginPayloadRequest( id, channel, data ) );
return future;
}
// this method is used for event execution
// if this connection is disconnected during an event-call, the original callback is not called
// if the event was executed async, we execute the callback on the eventloop again
// otherwise netty will schedule any pipeline related call by itself, this decreases performance
private <T> Callback<T> eventLoopCallback(Callback<T> callback)
{
return (result, error) ->
{
EventLoop eventLoop = ch.getHandle().eventLoop();
if ( eventLoop.inEventLoop() )
{
if ( !ch.isClosing() )
{
callback.done( result, error );
}
return;
}
eventLoop.execute( () ->
{
if ( !ch.isClosing() )
{
callback.done( result, error );
}
} );
};
}
}

View File

@ -1,5 +1,6 @@
package net.md_5.bungee.connection;
import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import lombok.Data;
import net.md_5.bungee.protocol.Property;
@ -9,6 +10,8 @@ import net.md_5.bungee.protocol.Property;
public class LoginResult
{
public static final Gson GSON = new Gson();
//
private String id;
private String name;
private Property[] properties;

View File

@ -3,12 +3,14 @@ package net.md_5.bungee.connection;
import com.google.gson.Gson;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.RequiredArgsConstructor;
import net.md_5.bungee.BungeeCord;
import net.md_5.bungee.BungeeServerInfo;
import net.md_5.bungee.PlayerInfoSerializer;
import net.md_5.bungee.api.Callback;
import net.md_5.bungee.api.Favicon;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.ServerPing;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.chat.VersionedComponentSerializer;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.netty.PacketHandler;
import net.md_5.bungee.netty.PipelineUtils;
@ -26,6 +28,9 @@ import net.md_5.bungee.util.QuietException;
public class PingHandler extends PacketHandler
{
static final Gson gson = VersionedComponentSerializer.getDefault().getGson().newBuilder()
.registerTypeAdapter( ServerPing.PlayerInfo.class, new PlayerInfoSerializer() )
.registerTypeAdapter( Favicon.class, Favicon.getFaviconTypeAdapter() ).create();
private final ServerInfo target;
private final Callback<ServerPing> callback;
private final int protocol;
@ -38,7 +43,7 @@ public class PingHandler extends PacketHandler
MinecraftEncoder encoder = new MinecraftEncoder( Protocol.HANDSHAKE, false, protocol );
channel.getHandle().pipeline().addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.STATUS, false, ProxyServer.getInstance().getProtocolVersion() ) );
channel.getHandle().pipeline().addAfter( PipelineUtils.FRAME_PREPENDER, PipelineUtils.PACKET_ENCODER, encoder );
channel.getHandle().pipeline().addAfter( PipelineUtils.FRAME_PREPENDER_AND_COMPRESS, PipelineUtils.PACKET_ENCODER, encoder );
channel.write( new Handshake( protocol, target.getAddress().getHostString(), target.getAddress().getPort(), 1 ) );
@ -65,7 +70,6 @@ public class PingHandler extends PacketHandler
@SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR")
public void handle(StatusResponse statusResponse) throws Exception
{
Gson gson = BungeeCord.getInstance().gson;
ServerPing serverPing = gson.fromJson( statusResponse.getResponse(), ServerPing.class );
( (BungeeServerInfo) target ).cachePing( serverPing );
callback.done( serverPing, null );

View File

@ -103,7 +103,7 @@ public class UpstreamBridge extends PacketHandler
if ( player.getPendingConnection().getVersion() >= ProtocolConstants.MINECRAFT_1_19_3 )
{
// need to queue, because players in config state could receive it
( (UserConnection) player ).sendPacketQueued( newPacket );
player.unsafe().sendPacketQueued( newPacket );
} else
{
player.unsafe().sendPacket( oldPacket );
@ -132,7 +132,7 @@ public class UpstreamBridge extends PacketHandler
@Override
public boolean shouldHandle(PacketWrapper packet) throws Exception
{
return con.getServer() != null || packet.packet instanceof PluginMessage || packet.packet instanceof CookieResponse;
return con.getServer() != null || packet.packet instanceof PluginMessage || packet.packet instanceof CookieResponse || packet.packet instanceof LoginPayloadResponse;
}
@Override
@ -312,7 +312,7 @@ public class UpstreamBridge extends PacketHandler
@Override
public void handle(PluginMessage pluginMessage) throws Exception
{
if ( pluginMessage.getTag().equals( "BungeeCord" ) )
if ( pluginMessage.getTag().equals( PluginMessage.BUNGEE_CHANNEL_LEGACY ) || pluginMessage.getTag().equals( PluginMessage.BUNGEE_CHANNEL_MODERN ) )
{
throw CancelSendSignal.INSTANCE;
}

View File

@ -93,6 +93,8 @@ public abstract class EntityMap
return EntityMap_1_16_2.INSTANCE_1_21_2;
case ProtocolConstants.MINECRAFT_1_21_4:
return EntityMap_1_16_2.INSTANCE_1_21_4;
case ProtocolConstants.MINECRAFT_1_21_5:
return EntityMap_1_16_2.INSTANCE_1_21_5;
}
throw new RuntimeException( "Version " + version + " has no entity map" );
}

View File

@ -25,6 +25,7 @@ class EntityMap_1_16_2 extends EntityMap
static final EntityMap_1_16_2 INSTANCE_1_20_5 = new EntityMap_1_16_2( -1, 0x37 );
static final EntityMap_1_16_2 INSTANCE_1_21_2 = new EntityMap_1_16_2( -1, 0x39 );
static final EntityMap_1_16_2 INSTANCE_1_21_4 = new EntityMap_1_16_2( -1, 0x3B );
static final EntityMap_1_16_2 INSTANCE_1_21_5 = new EntityMap_1_16_2( -1, 0x3C );
//
private final int spawnPlayerId;
private final int spectateId;

View File

@ -11,7 +11,6 @@ import java.util.logging.Level;
import lombok.Getter;
import lombok.Setter;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.compress.PacketCompressor;
import net.md_5.bungee.compress.PacketDecompressor;
import net.md_5.bungee.netty.cipher.CipherEncoder;
import net.md_5.bungee.protocol.DefinedPacket;
@ -19,7 +18,6 @@ import net.md_5.bungee.protocol.MinecraftDecoder;
import net.md_5.bungee.protocol.MinecraftEncoder;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.Varint21LengthFieldPrepender;
import net.md_5.bungee.protocol.packet.Kick;
public class ChannelWrapper
@ -132,6 +130,11 @@ public class ChannelWrapper
{
if ( !closed )
{
if ( !closing )
{
ch.config().setAutoRead( false );
}
closed = closing = true;
if ( packet != null && ch.isActive() )
@ -150,6 +153,7 @@ public class ChannelWrapper
if ( !closing )
{
closing = true;
ch.config().setAutoRead( false );
// Minecraft client can take some time to switch protocols.
// Sending the wrong disconnect packet whilst a protocol switch is in progress will crash it.
@ -180,26 +184,29 @@ public class ChannelWrapper
public void setCompressionThreshold(int compressionThreshold)
{
if ( ch.pipeline().get( PacketCompressor.class ) == null && compressionThreshold >= 0 )
{
addBefore( PipelineUtils.PACKET_ENCODER, "compress", new PacketCompressor() );
}
LengthPrependerAndCompressor compressor = ch.pipeline().get( LengthPrependerAndCompressor.class );
PacketDecompressor decompressor = ch.pipeline().get( PacketDecompressor.class );
if ( compressionThreshold >= 0 )
{
ch.pipeline().get( PacketCompressor.class ).setThreshold( compressionThreshold );
if ( !compressor.isCompress() )
{
compressor.setCompress( true );
}
compressor.setThreshold( compressionThreshold );
if ( decompressor == null )
{
addBefore( PipelineUtils.PACKET_DECODER, "decompress", decompressor = new PacketDecompressor() );
}
} else
{
ch.pipeline().remove( "compress" );
}
if ( ch.pipeline().get( PacketDecompressor.class ) == null && compressionThreshold >= 0 )
{
addBefore( PipelineUtils.PACKET_DECODER, "decompress", new PacketDecompressor() );
}
if ( compressionThreshold < 0 )
compressor.setCompress( false );
if ( decompressor != null )
{
ch.pipeline().remove( "decompress" );
}
}
// disable use of composite buffers if we use natives
updateComposite();
}
@ -210,26 +217,16 @@ public class ChannelWrapper
public void updateComposite()
{
CipherEncoder cipherEncoder = ch.pipeline().get( CipherEncoder.class );
PacketCompressor packetCompressor = ch.pipeline().get( PacketCompressor.class );
Varint21LengthFieldPrepender prepender = ch.pipeline().get( Varint21LengthFieldPrepender.class );
LengthPrependerAndCompressor prependerAndCompressor = ch.pipeline().get( LengthPrependerAndCompressor.class );
boolean compressorCompose = cipherEncoder == null || cipherEncoder.getCipher().allowComposite();
boolean prependerCompose = compressorCompose && ( packetCompressor == null || packetCompressor.getZlib().allowComposite() );
if ( prepender != null )
if ( prependerAndCompressor != null )
{
ProxyServer.getInstance().getLogger().log( Level.FINE, "set prepender compose to {0} for {1}", new Object[]
{
prependerCompose, ch
} );
prepender.setCompose( prependerCompose );
}
if ( packetCompressor != null )
{
ProxyServer.getInstance().getLogger().log( Level.FINE, "set packetCompressor compose to {0} for {1}", new Object[]
{
compressorCompose, ch
} );
packetCompressor.setCompose( compressorCompose );
prependerAndCompressor.setCompose( compressorCompose );
}
}

View File

@ -7,17 +7,23 @@ import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.WriteTimeoutException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.logging.Level;
import lombok.Setter;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.connection.CancelSendSignal;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.PingHandler;
import net.md_5.bungee.connection.UpstreamBridge;
import net.md_5.bungee.protocol.BadPacketException;
import net.md_5.bungee.protocol.OverflowPacketException;
import net.md_5.bungee.protocol.PacketWrapper;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.packet.Kick;
import net.md_5.bungee.util.PacketLimiter;
import net.md_5.bungee.util.QuietException;
/**
@ -28,6 +34,8 @@ import net.md_5.bungee.util.QuietException;
public class HandlerBoss extends ChannelInboundHandlerAdapter
{
@Setter
private PacketLimiter limiter;
private ChannelWrapper channel;
private PacketHandler handler;
private boolean healthCheck;
@ -107,6 +115,23 @@ public class HandlerBoss extends ChannelInboundHandlerAdapter
}
PacketWrapper packet = (PacketWrapper) msg;
try
{
// check if the player exceeds packet limits, put inside try final, so we always release.
if ( limiter != null && !limiter.incrementAndCheck( packet.buf.readableBytes() ) )
{
// we shouldn't tell the player what limits he exceeds by default
// but if someone applies custom message we should allow them to display counter and bytes
channel.close( handler instanceof UpstreamBridge ? new Kick( TextComponent.fromLegacy( ProxyServer.getInstance().getTranslation( "packet_limit_kick", limiter.getCounter(), limiter.getDataCounter() ) ) ) : null );
// but the server admin should know
ProxyServer.getInstance().getLogger().log( Level.WARNING, "{0} exceeded packet limit ({1} packets and {2} bytes per second)", new Object[]
{
handler, limiter.getCounter(), limiter.getDataCounter()
} );
return;
}
if ( packet.packet != null )
{
Protocol nextProtocol = packet.packet.nextProtocol();
@ -119,8 +144,6 @@ public class HandlerBoss extends ChannelInboundHandlerAdapter
if ( handler != null )
{
boolean sendPacket = handler.shouldHandle( packet );
try
{
if ( sendPacket && packet.packet != null )
{
try
@ -135,12 +158,12 @@ public class HandlerBoss extends ChannelInboundHandlerAdapter
{
handler.handle( packet );
}
}
} finally
{
packet.trySingleRelease();
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
@ -154,6 +177,9 @@ public class HandlerBoss extends ChannelInboundHandlerAdapter
if ( cause instanceof ReadTimeoutException )
{
ProxyServer.getInstance().getLogger().log( Level.WARNING, "{0} - read timed out", handler );
} else if ( cause instanceof WriteTimeoutException )
{
ProxyServer.getInstance().getLogger().log( Level.WARNING, "{0} - write timed out", handler );
} else if ( cause instanceof DecoderException )
{
if ( cause instanceof CorruptedFrameException )

View File

@ -0,0 +1,196 @@
package net.md_5.bungee.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import java.util.List;
import java.util.zip.Deflater;
import lombok.Setter;
import net.md_5.bungee.compress.CompressFactory;
import net.md_5.bungee.jni.zlib.BungeeZlib;
import net.md_5.bungee.protocol.DefinedPacket;
/**
* prepends length of message and optionally compresses message beforehand
* <br>
* combining these operations allows to keep space infront of compressed data for length varint
*/
public class LengthPrependerAndCompressor extends MessageToMessageEncoder<ByteBuf>
{
// reasonable to not support length varints > 4 byte (268435455 byte > 268MB)
// if ever changed to smaller than 4, also change varintSize method to check for that
private static final byte MAX_SUPPORTED_VARINT_LENGTH_LEN = 4;
private static final byte FLAG_COMPRESS = 0x01;
/**
* overridden by FLAG_TWO_BUFFERS if set
*/
private static final byte FLAG_COMPOSE = 0x02;
/**
* overwrites FLAG_COMPOSE if set
*/
private static final byte FLAG_TWO_BUFFERS = 0x04;
public LengthPrependerAndCompressor(boolean compose, boolean twoBuffers)
{
setCompose( compose );
setTwoBuffers( twoBuffers );
}
private BungeeZlib zlib;
@Setter
private int threshold = 256;
private byte flags = FLAG_COMPOSE;
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception
{
int oldBodyLen = msg.readableBytes();
final byte flags = this.flags;
if ( ( flags & FLAG_COMPRESS ) != 0 )
{
if ( oldBodyLen < threshold )
{
byte lengthLen = varintSize( oldBodyLen + 1 );
if ( ( flags & FLAG_TWO_BUFFERS ) != 0 )
{
ByteBuf lenBuf = ctx.alloc().directBuffer( lengthLen );
DefinedPacket.writeVarInt( oldBodyLen + 1, lenBuf );
lenBuf.writeByte( 0 ); // indicates uncompressed
out.add( lenBuf );
out.add( msg.retain() );
} else if ( ( flags & FLAG_COMPOSE ) != 0 )
{
// create a virtual buffer to avoid copying of data
ByteBuf pre = ctx.alloc().directBuffer( lengthLen + 1 );
DefinedPacket.writeVarInt( oldBodyLen + 1, pre );
pre.writeByte( 0 ); // indicates uncompressed
out.add( ctx.alloc().compositeDirectBuffer( 2 ).addComponents( true, pre, msg.retain() ) );
} else
{
ByteBuf buf = ctx.alloc().directBuffer( lengthLen + 1 + oldBodyLen );
DefinedPacket.writeVarInt( oldBodyLen + 1, buf );
out.add( buf.writeByte( 0 ).writeBytes( msg ) ); // 0 indicates uncompressed
}
} else
{
ByteBuf buf = ctx.alloc().directBuffer( BungeeZlib.OUTPUT_BUFFER_SIZE + MAX_SUPPORTED_VARINT_LENGTH_LEN + varintSize( oldBodyLen ) );
buf.writerIndex( MAX_SUPPORTED_VARINT_LENGTH_LEN ); // Reserve space for packet length varint
DefinedPacket.writeVarInt( oldBodyLen, buf ); // write uncompressed length
zlib.process( msg, buf ); // compress data to buf
// write varint length of compressed directly infront of compressed data
// leaves potential unused bytes at buffer start
int writerIndex = buf.writerIndex();
int compressedLen = writerIndex - MAX_SUPPORTED_VARINT_LENGTH_LEN;
byte lengthLen = varintSize( compressedLen );
int lengthStart = MAX_SUPPORTED_VARINT_LENGTH_LEN - lengthLen;
DefinedPacket.setVarInt( compressedLen, buf, lengthStart, lengthLen );
buf.readerIndex( lengthStart ); // set start of buffer to ignore potential unused bytes before length
out.add( buf );
}
} else
{
byte lengthLen = varintSize( oldBodyLen );
if ( ( flags & FLAG_TWO_BUFFERS ) != 0 )
{
ByteBuf lenBuf = ctx.alloc().directBuffer( lengthLen );
DefinedPacket.writeVarInt( oldBodyLen, lenBuf );
out.add( lenBuf );
out.add( msg.retain() );
} else if ( ( flags & FLAG_COMPOSE ) != 0 )
{
// create a virtual buffer to avoid copying of data
ByteBuf pre = ctx.alloc().directBuffer( lengthLen );
DefinedPacket.writeVarInt( oldBodyLen, pre );
out.add( ctx.alloc().compositeDirectBuffer( 2 ).addComponents( true, pre, msg.retain() ) );
} else
{
ByteBuf buf = ctx.alloc().directBuffer( lengthLen + oldBodyLen );
DefinedPacket.writeVarInt( oldBodyLen, buf );
out.add( buf.writeBytes( msg ) ); // 0 indicates uncompressed
}
}
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception
{
if ( zlib != null )
{
zlib.free();
zlib = null;
}
}
public void setCompose(boolean compose)
{
if ( compose )
{
flags |= FLAG_COMPOSE;
} else
{
flags &= ~FLAG_COMPOSE;
}
}
public boolean isCompress()
{
return ( flags & FLAG_COMPRESS ) != 0;
}
public void setCompress(boolean compress)
{
if ( compress )
{
BungeeZlib zlib = this.zlib;
if ( zlib == null )
{
this.zlib = zlib = CompressFactory.zlib.newInstance();
}
zlib.init( true, Deflater.DEFAULT_COMPRESSION );
flags |= FLAG_COMPRESS;
} else
{
flags &= ~FLAG_COMPRESS;
if ( zlib != null )
{
zlib.free();
zlib = null;
}
}
}
public void setTwoBuffers(boolean twoBuffers)
{
if ( twoBuffers )
{
flags |= FLAG_TWO_BUFFERS;
} else
{
flags &= ~FLAG_TWO_BUFFERS;
}
}
private static byte varintSize(int value)
{
if ( ( value & 0xFFFFFF80 ) == 0 )
{
return 1;
}
if ( ( value & 0xFFFFC000 ) == 0 )
{
return 2;
}
if ( ( value & 0xFFE00000 ) == 0 )
{
return 3;
}
if ( ( value & 0xF0000000 ) == 0 )
{
return 4;
}
throw new IllegalArgumentException( "Packet length " + value + " longer than supported (max. 268435455 for 4 byte varint)" );
}
}

View File

@ -4,7 +4,6 @@ import com.google.common.base.Preconditions;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.ServerChannel;
@ -24,6 +23,7 @@ import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.channel.unix.DomainSocketAddress;
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
import io.netty.incubator.channel.uring.IOUring;
import io.netty.incubator.channel.uring.IOUringDatagramChannel;
import io.netty.incubator.channel.uring.IOUringEventLoopGroup;
@ -49,59 +49,80 @@ import net.md_5.bungee.protocol.MinecraftDecoder;
import net.md_5.bungee.protocol.MinecraftEncoder;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.Varint21FrameDecoder;
import net.md_5.bungee.protocol.Varint21LengthFieldExtraBufPrepender;
import net.md_5.bungee.protocol.Varint21LengthFieldPrepender;
import net.md_5.bungee.protocol.channel.BungeeChannelInitializer;
import net.md_5.bungee.protocol.channel.ChannelAcceptor;
import net.md_5.bungee.util.PacketLimiter;
public class PipelineUtils
{
public static final AttributeKey<ListenerInfo> LISTENER = AttributeKey.valueOf( "ListerInfo" );
public static final ChannelInitializer<Channel> SERVER_CHILD = new ChannelInitializer<Channel>()
private static void setChannelInitializerHolders()
{
@Override
protected void initChannel(Channel ch) throws Exception
ProxyServer.getInstance().unsafe().setFrontendChannelInitializer( BungeeChannelInitializer.create( ch ->
{
SocketAddress remoteAddress = ( ch.remoteAddress() == null ) ? ch.parent().localAddress() : ch.remoteAddress();
if ( BungeeCord.getInstance().getConnectionThrottle() != null && BungeeCord.getInstance().getConnectionThrottle().throttle( remoteAddress ) )
{
ch.close();
return;
return false;
}
ListenerInfo listener = ch.attr( LISTENER ).get();
if ( BungeeCord.getInstance().getPluginManager().callEvent( new ClientConnectEvent( remoteAddress, listener ) ).isCancelled() )
{
ch.close();
return;
return false;
}
BASE.initChannel( ch );
BASE.accept( ch );
ch.pipeline().addBefore( FRAME_DECODER, LEGACY_DECODER, new LegacyDecoder() );
ch.pipeline().addAfter( FRAME_DECODER, PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, true, ProxyServer.getInstance().getProtocolVersion() ) );
ch.pipeline().addAfter( FRAME_PREPENDER, PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, true, ProxyServer.getInstance().getProtocolVersion() ) );
ch.pipeline().addBefore( FRAME_PREPENDER, LEGACY_KICKER, legacyKicker );
ch.pipeline().get( HandlerBoss.class ).setHandler( new InitialHandler( BungeeCord.getInstance(), listener ) );
ch.pipeline().addAfter( FRAME_PREPENDER_AND_COMPRESS, PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, true, ProxyServer.getInstance().getProtocolVersion() ) );
ch.pipeline().addBefore( FRAME_PREPENDER_AND_COMPRESS, LEGACY_KICKER, legacyKicker );
HandlerBoss handlerBoss = ch.pipeline().get( HandlerBoss.class );
handlerBoss.setHandler( new InitialHandler( BungeeCord.getInstance(), listener ) );
int packetLimit = BungeeCord.getInstance().getConfig().getMaxPacketsPerSecond();
int packetDataLimit = BungeeCord.getInstance().getConfig().getMaxPacketDataPerSecond();
if ( packetLimit > 0 || packetDataLimit > 0 )
{
handlerBoss.setLimiter( new PacketLimiter( packetLimit, packetDataLimit ) );
}
if ( listener.isProxyProtocol() )
{
ch.pipeline().addFirst( new HAProxyMessageDecoder() );
}
return true;
} ) );
ProxyServer.getInstance().unsafe().setBackendChannelInitializer( BungeeChannelInitializer.create( ch ->
{
PipelineUtils.BASE_SERVERSIDE.accept( ch );
ch.pipeline().addAfter( PipelineUtils.FRAME_DECODER, PipelineUtils.PACKET_DECODER, new MinecraftDecoder( Protocol.HANDSHAKE, false, ProxyServer.getInstance().getProtocolVersion() ) );
ch.pipeline().addAfter( PipelineUtils.FRAME_PREPENDER_AND_COMPRESS, PipelineUtils.PACKET_ENCODER, new MinecraftEncoder( Protocol.HANDSHAKE, false, ProxyServer.getInstance().getProtocolVersion() ) );
return true;
} ) );
ProxyServer.getInstance().unsafe().setServerInfoChannelInitializer( BungeeChannelInitializer.create( BASE_SERVERSIDE ) );
}
};
public static final Base BASE = new Base( false );
public static final Base BASE_SERVERSIDE = new Base( true );
private static final ChannelAcceptor BASE = new Base( false );
private static final ChannelAcceptor BASE_SERVERSIDE = new Base( true );
private static final KickStringWriter legacyKicker = new KickStringWriter();
private static final Varint21LengthFieldExtraBufPrepender serverFramePrepender = new Varint21LengthFieldExtraBufPrepender();
public static final String TIMEOUT_HANDLER = "timeout";
public static final String WRITE_TIMEOUT_HANDLER = "write-timeout";
public static final String PACKET_DECODER = "packet-decoder";
public static final String PACKET_ENCODER = "packet-encoder";
public static final String BOSS_HANDLER = "inbound-boss";
public static final String ENCRYPT_HANDLER = "encrypt";
public static final String DECRYPT_HANDLER = "decrypt";
public static final String FRAME_DECODER = "frame-decoder";
public static final String FRAME_PREPENDER = "frame-prepender";
public static final String FRAME_PREPENDER_AND_COMPRESS = "frame-prepender-compress";
public static final String LEGACY_DECODER = "legacy-decoder";
public static final String LEGACY_KICKER = "legacy-kick";
@ -137,6 +158,8 @@ public class PipelineUtils
}
}
}
setChannelInitializerHolders();
}
public static EventLoopGroup newEventLoopGroup(int threads, ThreadFactory factory)
@ -179,13 +202,13 @@ public class PipelineUtils
@NoArgsConstructor // for backwards compatibility
@AllArgsConstructor
public static final class Base extends ChannelInitializer<Channel>
public static final class Base implements ChannelAcceptor
{
private boolean toServer = false;
@Override
public void initChannel(Channel ch) throws Exception
public boolean accept(Channel ch)
{
try
{
@ -199,11 +222,14 @@ public class PipelineUtils
ch.pipeline().addLast( FRAME_DECODER, new Varint21FrameDecoder() );
ch.pipeline().addLast( TIMEOUT_HANDLER, new ReadTimeoutHandler( BungeeCord.getInstance().config.getTimeout(), TimeUnit.MILLISECONDS ) );
ch.pipeline().addLast( WRITE_TIMEOUT_HANDLER, new WriteTimeoutHandler( BungeeCord.getInstance().config.getTimeout(), TimeUnit.MILLISECONDS ) );
// No encryption bungee -> server, therefore use extra buffer to avoid copying everything for length prepending
// Not used bungee -> client as header would need to be encrypted separately through expensive JNI call
ch.pipeline().addLast( FRAME_PREPENDER, ( toServer ) ? serverFramePrepender : new Varint21LengthFieldPrepender() );
// TODO: evaluate difference compose vs two buffers
ch.pipeline().addLast( FRAME_PREPENDER_AND_COMPRESS, new LengthPrependerAndCompressor( true, toServer ) );
ch.pipeline().addLast( BOSS_HANDLER, new HandlerBoss() );
return true;
}
}
}

View File

@ -0,0 +1,45 @@
package net.md_5.bungee.util;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class PacketLimiter
{
// max amount of packets allowed per second
private final int limit;
// max amount of data allowed per second
private final int dataLimit;
@Getter
private int counter;
@Getter
private int dataCounter;
private long nextSecond;
/**
* Counts the received packet amount and size.
*
* @param size size of the packet
* @return return false if the player should be kicked
*/
public boolean incrementAndCheck(int size)
{
counter++;
dataCounter += size;
if ( ( limit > 0 && counter > limit ) || ( dataLimit > 0 && dataCounter > dataLimit ) )
{
long now = System.currentTimeMillis();
if ( nextSecond > now )
{
return false;
}
nextSecond = now + 1000;
counter = 0;
dataCounter = 0;
}
return true;
}
}

View File

@ -41,3 +41,4 @@ command_ip=\u00a79IP of {0} is {1}
illegal_chat_characters=\u00a7cIllegal characters in chat ({0})
kick_message=\u00a7cYou have been kicked off the proxy.
reject_transfer=\u00a7cYour transfer was rejected.
packet_limit_kick=\u00a7cYou have sent too many packets

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-query</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-Query</name>

View File

@ -6,12 +6,12 @@
<parent>
<groupId>fr.pandacube.bungeecord</groupId>
<artifactId>bungeecord-parent</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>bungeecord-slf4j</artifactId>
<version>1.21-R0.1-SNAPSHOT</version>
<version>1.21-R0.3-SNAPSHOT</version>
<packaging>jar</packaging>
<name>BungeeCord-SLF4J</name>