From ad3eb412ff27d74e5468e965798b90e4d174b2dd Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Sun, 2 Oct 2016 07:54:16 +0200 Subject: [PATCH] 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. --- .../bungee/connection/InitialHandler.java | 106 +++++++++++++++--- 1 file changed, 93 insertions(+), 13 deletions(-) diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index 74362ef9..b3694cdf 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -112,6 +112,15 @@ public class InitialHandler extends PacketHandler implements PendingConnection @Getter private String extraDataInHandshake = ""; + @Getter + private boolean duplication = false; + + @Getter + private String realName = null; + + @Getter + private UUID realId = null; + @Override public boolean shouldHandle(PacketWrapper packet) throws Exception { @@ -408,6 +417,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection } this.loginRequest = loginRequest; + setName( realName = loginRequest.getData() ); int limit = BungeeCord.getInstance().config.getPlayerLimit(); if ( limit > 0 && bungee.getOnlineCount() >= limit ) @@ -416,14 +426,6 @@ public class InitialHandler extends PacketHandler implements PendingConnection return; } - // If offline mode and they are already on, don't allow connect - // We can just check by UUID here as names are based on UUID - if ( !isOnlineMode() && bungee.getPlayer( getUniqueId() ) != null ) - { - disconnect( bungee.getTranslation( "already_connected_proxy" ) ); - return; - } - Callback callback = new Callback() { @@ -440,6 +442,16 @@ public class InitialHandler extends PacketHandler implements PendingConnection { return; } + if ( !realName.equals( name ) ) + { + // Floodgate changes the name attribute with reflexion + setName( realName = name ); + } + if ( uniqueId != null ) + { + // if plugin called setUniqueId() + realId = uniqueId; + } if ( onlineMode ) { thisState = State.ENCRYPT; @@ -494,8 +506,8 @@ public class InitialHandler extends PacketHandler implements PendingConnection if ( obj != null && obj.getId() != null ) { loginProfile = obj; - name = obj.getName(); - uniqueId = Util.getUUID( obj.getId() ); + setName( realName = obj.getName() ); + uniqueId = realId = Util.getUUID( obj.getId() ); finish(); return; } @@ -513,10 +525,25 @@ public class InitialHandler extends PacketHandler implements PendingConnection private void finish() { - offlineId = UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + getName() ).getBytes( Charsets.UTF_8 ) ); - if ( uniqueId == null ) + if ( uniqueId == null ) // offline mode and no plugin used setUniqueId() { - uniqueId = offlineId; + uniqueId = realId = offlineId; + } + + /* + * At this point, player is either authenticated by Mojang (online mode), + * by a plugin (Floodgate ?) or the offline id is set. + */ + ProxiedPlayer existingPlayer = bungee.getPlayer( uniqueId ); + if ( existingPlayer != null && existingPlayer.hasPermission( "bungeecord.multiple_connect" ) ) + { + UUID newId = generateDuplicatedId( uniqueId ); + if ( !uniqueId.equals( newId ) ) + { + uniqueId = newId; + setName( name + "." + getDuplicationIndex( newId ) ); + duplication = true; + } } if ( BungeeCord.getInstance().config.isEnforceSecureProfile() ) @@ -670,6 +697,54 @@ public class InitialHandler extends PacketHandler implements PendingConnection return ( name != null ) ? name : ( loginRequest == null ) ? null : loginRequest.getData(); } + private void setName(String name) + { + this.name = name; + if ( loginRequest != null ) + { + loginRequest.setData( name ); // name transmitted to Spigot server + } + updateOfflineId(); + } + + private UUID generateDuplicatedId(UUID base) + { + // UUID version: offline = 3 ; Java online mode = 4 ; Floodgate xUID = 0 (and must be kept 0) + // UUID variant: offline = 0xx ; Java online mode = 10x ; Floodgate xUID = xxx + if ( base.version() == 0 ) + { + /* + * Floodgate’s xUID converted to UUID are not supported + * because it requires the 64 MSBs to be 0 (or Floodgate API would not + * recognize a Bedrock account) and we cannot modify the 64 LSBs + * without risking a collision with the xUID of another Bedrock account + */ + return base; + } + long MSB = base.getMostSignificantBits(); + long LSB = base.getLeastSignificantBits(); + + MSB &= 0xFFFFFFFF_FFFF_70FFL; // reset bits we need + MSB |= 0x00000000_0000_8000L; // set version to + 8 the current version + + for ( int i = 1; i <= 9; i++ ) + { + long newMSB = MSB | i << 8; + UUID newUUID = new UUID( newMSB, LSB ); + if ( bungee.getPlayer( newUUID ) != null ) + { + continue; + } + return newUUID; + } + return base; // there are too many duplicated connections for this player + } + + private static int getDuplicationIndex(UUID duplicatedId) + { + return (int) ( duplicatedId.getMostSignificantBits() >> 8 ) & 0xF; + } + @Override public int getVersion() { @@ -709,6 +784,11 @@ public class InitialHandler extends PacketHandler implements PendingConnection this.uniqueId = uuid; } + private void updateOfflineId() + { + offlineId = UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + getName() ).getBytes( Charsets.UTF_8 ) ); + } + @Override public String getUUID() {