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.
This commit is contained in:
Marc Baloup 2016-10-02 07:54:16 +02:00
parent a669080b0c
commit 2373a33cdc

View File

@ -137,6 +137,15 @@ public class InitialHandler extends PacketHandler implements PendingConnection
private boolean transferred; private boolean transferred;
private UserConnection userCon; private UserConnection userCon;
@Getter
private boolean duplication = false;
@Getter
private String realName = null;
@Getter
private UUID realId = null;
@Override @Override
public boolean shouldHandle(PacketWrapper packet) throws Exception public boolean shouldHandle(PacketWrapper packet) throws Exception
{ {
@ -448,6 +457,7 @@ public class InitialHandler extends PacketHandler implements PendingConnection
} }
this.loginRequest = loginRequest; this.loginRequest = loginRequest;
setName( realName = loginRequest.getData() );
int limit = BungeeCord.getInstance().config.getPlayerLimit(); int limit = BungeeCord.getInstance().config.getPlayerLimit();
if ( limit > 0 && bungee.getOnlineCount() >= limit ) if ( limit > 0 && bungee.getOnlineCount() >= limit )
@ -456,14 +466,6 @@ public class InitialHandler extends PacketHandler implements PendingConnection
return; 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<PreLoginEvent> callback = new Callback<PreLoginEvent>() Callback<PreLoginEvent> callback = new Callback<PreLoginEvent>()
{ {
@ -480,6 +482,16 @@ public class InitialHandler extends PacketHandler implements PendingConnection
{ {
return; 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 ) if ( onlineMode )
{ {
thisState = State.ENCRYPT; thisState = State.ENCRYPT;
@ -534,8 +546,8 @@ public class InitialHandler extends PacketHandler implements PendingConnection
if ( obj != null && obj.getId() != null ) if ( obj != null && obj.getId() != null )
{ {
loginProfile = obj; loginProfile = obj;
name = obj.getName(); setName( realName = obj.getName() );
uniqueId = Util.getUUID( obj.getId() ); uniqueId = realId = Util.getUUID( obj.getId() );
finish(); finish();
return; return;
} }
@ -553,10 +565,25 @@ public class InitialHandler extends PacketHandler implements PendingConnection
private void finish() private void finish()
{ {
offlineId = UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + getName() ).getBytes( StandardCharsets.UTF_8 ) ); if ( uniqueId == null ) // offline mode and no plugin used setUniqueId()
if ( uniqueId == null )
{ {
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;
}
} }
rewriteId = ( bungee.config.isIpForward() ) ? uniqueId : offlineId; rewriteId = ( bungee.config.isIpForward() ) ? uniqueId : offlineId;
@ -764,6 +791,59 @@ public class InitialHandler extends PacketHandler implements PendingConnection
return ( name != null ) ? name : ( loginRequest == null ) ? null : loginRequest.getData(); 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 )
{
/*
* Floodgates 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;
}
private void updateOfflineId()
{
offlineId = UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + getName() ).getBytes( StandardCharsets.UTF_8 ) );
}
@Override @Override
public int getVersion() public int getVersion()
{ {