mirror of
https://github.com/GeyserMC/Floodgate.git
synced 2025-12-21 07:49:18 +00:00
Added a timestamp to prevent reusing the encrypted Floodgate data
This commit is contained in:
@@ -83,7 +83,6 @@ public class BungeeDataAddon implements InjectorAddon {
|
|||||||
public void onChannelClosed(Channel channel) {
|
public void onChannelClosed(Channel channel) {
|
||||||
FloodgatePlayer player = channel.attr(playerAttribute).get();
|
FloodgatePlayer player = channel.attr(playerAttribute).get();
|
||||||
if (player != null && api.removePlayer(player)) {
|
if (player != null && api.removePlayer(player)) {
|
||||||
api.removeEncryptedData(player.getCorrectUniqueId());
|
|
||||||
logger.translatedInfo("floodgate.ingame.disconnect_name", player.getCorrectUsername());
|
logger.translatedInfo("floodgate.ingame.disconnect_name", player.getCorrectUsername());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import org.geysermc.floodgate.api.player.PropertyKey;
|
|||||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.ResultType;
|
import org.geysermc.floodgate.util.Constants;
|
||||||
import org.geysermc.floodgate.util.ReflectionUtils;
|
import org.geysermc.floodgate.util.ReflectionUtils;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@@ -115,14 +115,20 @@ public class BungeeProxyDataHandler extends ChannelInboundHandlerAdapter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.getResultType() == ResultType.EXCEPTION) {
|
switch (result.getResultType()) {
|
||||||
ctx.channel().attr(kickMessageAttribute).set(
|
case EXCEPTION:
|
||||||
config.getDisconnect().getInvalidKey());
|
ctx.channel().attr(kickMessageAttribute).set(
|
||||||
}
|
config.getDisconnect().getInvalidKey());
|
||||||
|
break;
|
||||||
if (result.getResultType() == ResultType.INVALID_DATA_LENGTH) {
|
case INVALID_DATA_LENGTH:
|
||||||
ctx.channel().attr(kickMessageAttribute)
|
ctx.channel().attr(kickMessageAttribute)
|
||||||
.set(config.getDisconnect().getInvalidArgumentsLength());
|
.set(config.getDisconnect().getInvalidArgumentsLength());
|
||||||
|
break;
|
||||||
|
case TIMESTAMP_DENIED:
|
||||||
|
ctx.channel().attr(kickMessageAttribute).set(Constants.TIMESTAMP_DENIED_MESSAGE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ import net.md_5.bungee.protocol.packet.Handshake;
|
|||||||
import org.geysermc.floodgate.api.ProxyFloodgateApi;
|
import org.geysermc.floodgate.api.ProxyFloodgateApi;
|
||||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||||
|
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
|
||||||
|
import org.geysermc.floodgate.util.BedrockData;
|
||||||
import org.geysermc.floodgate.util.ReflectionUtils;
|
import org.geysermc.floodgate.util.ReflectionUtils;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@@ -92,7 +94,8 @@ public class BungeeServerDataHandler extends MessageToMessageEncoder<Object> {
|
|||||||
FloodgatePlayer player = wrapper.getHandle().attr(playerAttribute).get();
|
FloodgatePlayer player = wrapper.getHandle().attr(playerAttribute).get();
|
||||||
|
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
String encryptedData = api.getEncryptedData(player.getCorrectUniqueId());
|
BedrockData data = player.as(FloodgatePlayerImpl.class).toBedrockData();
|
||||||
|
String encryptedData = api.createEncryptedDataString(data);
|
||||||
|
|
||||||
Handshake handshake = (Handshake) packet;
|
Handshake handshake = (Handshake) packet;
|
||||||
String address = handshake.getHost();
|
String address = handshake.getHost();
|
||||||
|
|||||||
@@ -26,15 +26,11 @@
|
|||||||
package org.geysermc.floodgate.api;
|
package org.geysermc.floodgate.api;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
import org.geysermc.floodgate.crypto.FloodgateCipher;
|
||||||
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
|
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
|
||||||
import org.geysermc.floodgate.util.BedrockData;
|
import org.geysermc.floodgate.util.BedrockData;
|
||||||
|
|
||||||
public final class ProxyFloodgateApi extends SimpleFloodgateApi {
|
public final class ProxyFloodgateApi extends SimpleFloodgateApi {
|
||||||
private final Map<UUID, String> encryptedData = new HashMap<>();
|
|
||||||
private final FloodgateCipher cipher;
|
private final FloodgateCipher cipher;
|
||||||
|
|
||||||
public ProxyFloodgateApi(PluginMessageManager pluginMessageManager, FloodgateCipher cipher) {
|
public ProxyFloodgateApi(PluginMessageManager pluginMessageManager, FloodgateCipher cipher) {
|
||||||
@@ -42,25 +38,16 @@ public final class ProxyFloodgateApi extends SimpleFloodgateApi {
|
|||||||
this.cipher = cipher;
|
this.cipher = cipher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEncryptedData(UUID uuid) {
|
public byte[] createEncryptedData(BedrockData bedrockData) {
|
||||||
return encryptedData.get(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addEncryptedData(UUID uuid, String encryptedData) {
|
|
||||||
this.encryptedData.put(uuid, encryptedData); // just override already existing data I guess
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeEncryptedData(UUID uuid) {
|
|
||||||
encryptedData.remove(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateEncryptedData(UUID uuid, BedrockData bedrockData) {
|
|
||||||
try {
|
try {
|
||||||
byte[] encryptedData = cipher.encryptFromString(bedrockData.toString());
|
return cipher.encryptFromString(bedrockData.toString());
|
||||||
addEncryptedData(uuid, new String(encryptedData, StandardCharsets.UTF_8));
|
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
throw new IllegalStateException("We failed to update the BedrockData, " +
|
throw new IllegalStateException("We failed to create the encrypted data, " +
|
||||||
"but we can't continue without the updated version!", exception);
|
"but creating encrypted data is mandatory!", exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String createEncryptedDataString(BedrockData bedrockData) {
|
||||||
|
return new String(createEncryptedData(bedrockData), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,14 @@ package org.geysermc.floodgate.player;
|
|||||||
import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH;
|
import static org.geysermc.floodgate.util.BedrockData.EXPECTED_LENGTH;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.cache.Cache;
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.util.AttributeKey;
|
import io.netty.util.AttributeKey;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -55,6 +58,12 @@ import org.geysermc.floodgate.util.Utils;
|
|||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public final class FloodgateHandshakeHandler {
|
public final class FloodgateHandshakeHandler {
|
||||||
|
private final Cache<String, Long> handleCache =
|
||||||
|
CacheBuilder.newBuilder()
|
||||||
|
.maximumSize(500)
|
||||||
|
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||||
|
.build();
|
||||||
|
|
||||||
private final HandshakeHandlersImpl handshakeHandlers;
|
private final HandshakeHandlersImpl handshakeHandlers;
|
||||||
private final SimpleFloodgateApi api;
|
private final SimpleFloodgateApi api;
|
||||||
private final FloodgateCipher cipher;
|
private final FloodgateCipher cipher;
|
||||||
@@ -97,6 +106,29 @@ public final class FloodgateHandshakeHandler {
|
|||||||
channel, bedrockData, hostname);
|
channel, bedrockData, hostname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timestamp checks
|
||||||
|
|
||||||
|
long timeDifference = System.currentTimeMillis() - bedrockData.getTimestamp();
|
||||||
|
if (timeDifference > 6000 || timeDifference < 0) {
|
||||||
|
return callHandlerAndReturnResult(
|
||||||
|
ResultType.TIMESTAMP_DENIED,
|
||||||
|
channel, bedrockData, hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
Long cachedTimestamp = handleCache.getIfPresent(bedrockData.getXuid());
|
||||||
|
if (cachedTimestamp != null) {
|
||||||
|
// the cached timestamp is newer than the gotten timestamp
|
||||||
|
// you also can't reuse the data (the timestamp is there to prevent that as well)
|
||||||
|
if (cachedTimestamp >= bedrockData.getTimestamp()) {
|
||||||
|
return callHandlerAndReturnResult(
|
||||||
|
ResultType.TIMESTAMP_DENIED,
|
||||||
|
channel, bedrockData, hostname);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCache.put(bedrockData.getXuid(), bedrockData.getTimestamp());
|
||||||
|
|
||||||
|
|
||||||
LinkedPlayer linkedPlayer;
|
LinkedPlayer linkedPlayer;
|
||||||
|
|
||||||
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
|
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
|
||||||
@@ -212,6 +244,7 @@ public final class FloodgateHandshakeHandler {
|
|||||||
EXCEPTION,
|
EXCEPTION,
|
||||||
NOT_FLOODGATE_DATA,
|
NOT_FLOODGATE_DATA,
|
||||||
INVALID_DATA_LENGTH,
|
INVALID_DATA_LENGTH,
|
||||||
|
TIMESTAMP_DENIED,
|
||||||
SUCCESS
|
SUCCESS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ import lombok.AccessLevel;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import org.geysermc.floodgate.api.FloodgateApi;
|
|
||||||
import org.geysermc.floodgate.api.InstanceHolder;
|
import org.geysermc.floodgate.api.InstanceHolder;
|
||||||
import org.geysermc.floodgate.api.ProxyFloodgateApi;
|
import org.geysermc.floodgate.api.ProxyFloodgateApi;
|
||||||
|
import org.geysermc.floodgate.api.SimpleFloodgateApi;
|
||||||
import org.geysermc.floodgate.api.handshake.HandshakeData;
|
import org.geysermc.floodgate.api.handshake.HandshakeData;
|
||||||
import org.geysermc.floodgate.api.link.PlayerLink;
|
import org.geysermc.floodgate.api.link.PlayerLink;
|
||||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||||
@@ -84,7 +84,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
|
|||||||
BedrockData data,
|
BedrockData data,
|
||||||
HandshakeData handshakeData) {
|
HandshakeData handshakeData) {
|
||||||
|
|
||||||
FloodgateApi api = FloodgateApi.getInstance();
|
SimpleFloodgateApi api = InstanceHolder.castApi(SimpleFloodgateApi.class);
|
||||||
|
|
||||||
UUID javaUniqueId = Utils.getJavaUuid(data.getXuid());
|
UUID javaUniqueId = Utils.getJavaUuid(data.getXuid());
|
||||||
|
|
||||||
@@ -94,18 +94,11 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
|
|||||||
|
|
||||||
LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer();
|
LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer();
|
||||||
|
|
||||||
FloodgatePlayerImpl player = new FloodgatePlayerImpl(
|
return new FloodgatePlayerImpl(
|
||||||
data.getVersion(), data.getUsername(), handshakeData.getJavaUsername(),
|
data.getVersion(), data.getUsername(), handshakeData.getJavaUsername(),
|
||||||
javaUniqueId, data.getXuid(), deviceOs, data.getLanguageCode(), uiProfile,
|
javaUniqueId, data.getXuid(), deviceOs, data.getLanguageCode(), uiProfile,
|
||||||
inputMode, data.getIp(), data.isFromProxy(), api instanceof ProxyFloodgateApi,
|
inputMode, data.getIp(), data.isFromProxy(), api instanceof ProxyFloodgateApi,
|
||||||
linkedPlayer, data.getSubscribeId(), data.getVerifyCode());
|
linkedPlayer, data.getSubscribeId(), data.getVerifyCode());
|
||||||
|
|
||||||
// fromProxy and linked player might have to be changed
|
|
||||||
if (api instanceof ProxyFloodgateApi) {
|
|
||||||
InstanceHolder.castApi(ProxyFloodgateApi.class)
|
|
||||||
.updateEncryptedData(player.getCorrectUniqueId(), player.toBedrockData());
|
|
||||||
}
|
|
||||||
return player;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -31,4 +31,8 @@ public final class Constants {
|
|||||||
public static final String WEBSOCKET_URL = "wss://api.geysermc.org/ws";
|
public static final String WEBSOCKET_URL = "wss://api.geysermc.org/ws";
|
||||||
|
|
||||||
public static final boolean DEBUG_MODE = true;
|
public static final boolean DEBUG_MODE = true;
|
||||||
|
|
||||||
|
public static final String TIMESTAMP_DENIED_MESSAGE =
|
||||||
|
"Something isn't right with this data." +
|
||||||
|
" Try logging in again or contact a server administrator if the issue persists.";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import org.geysermc.floodgate.config.FloodgateConfig;
|
|||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
||||||
import org.geysermc.floodgate.util.BedrockData;
|
import org.geysermc.floodgate.util.BedrockData;
|
||||||
|
import org.geysermc.floodgate.util.Constants;
|
||||||
import org.geysermc.floodgate.util.ReflectionUtils;
|
import org.geysermc.floodgate.util.ReflectionUtils;
|
||||||
import org.geysermc.floodgate.util.SpigotUtils;
|
import org.geysermc.floodgate.util.SpigotUtils;
|
||||||
|
|
||||||
@@ -188,9 +189,15 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//todo use kickMessageAttribute and let this be common logic
|
||||||
|
|
||||||
switch (result.getResultType()) {
|
switch (result.getResultType()) {
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
break;
|
break;
|
||||||
|
case EXCEPTION:
|
||||||
|
logger.info(config.getDisconnect().getInvalidKey());
|
||||||
|
ctx.close();
|
||||||
|
return;
|
||||||
case INVALID_DATA_LENGTH:
|
case INVALID_DATA_LENGTH:
|
||||||
int dataLength = result.getBedrockData().getDataLength();
|
int dataLength = result.getBedrockData().getDataLength();
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -199,6 +206,10 @@ public final class SpigotDataHandler extends ChannelInboundHandlerAdapter {
|
|||||||
);
|
);
|
||||||
ctx.close();
|
ctx.close();
|
||||||
return;
|
return;
|
||||||
|
case TIMESTAMP_DENIED:
|
||||||
|
logger.info(Constants.TIMESTAMP_DENIED_MESSAGE);
|
||||||
|
ctx.close();
|
||||||
|
return;
|
||||||
default: // only continue when SUCCESS
|
default: // only continue when SUCCESS
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ public final class VelocityDataAddon implements InjectorAddon {
|
|||||||
public void onChannelClosed(Channel channel) {
|
public void onChannelClosed(Channel channel) {
|
||||||
FloodgatePlayer player = channel.attr(playerAttribute).get();
|
FloodgatePlayer player = channel.attr(playerAttribute).get();
|
||||||
if (player != null && api.removePlayer(player)) {
|
if (player != null && api.removePlayer(player)) {
|
||||||
api.removeEncryptedData(player.getCorrectUniqueId());
|
|
||||||
logger.translatedInfo("floodgate.ingame.disconnect_name", player.getUsername());
|
logger.translatedInfo("floodgate.ingame.disconnect_name", player.getUsername());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import org.geysermc.floodgate.api.player.PropertyKey;
|
|||||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
import org.geysermc.floodgate.player.FloodgateHandshakeHandler.HandshakeResult;
|
||||||
|
import org.geysermc.floodgate.util.Constants;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter {
|
public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter {
|
||||||
@@ -112,6 +113,9 @@ public final class VelocityProxyDataHandler extends ChannelInboundHandlerAdapter
|
|||||||
ctx.channel().attr(kickMessageAttribute)
|
ctx.channel().attr(kickMessageAttribute)
|
||||||
.set(config.getDisconnect().getInvalidArgumentsLength());
|
.set(config.getDisconnect().getInvalidArgumentsLength());
|
||||||
return;
|
return;
|
||||||
|
case TIMESTAMP_DENIED:
|
||||||
|
ctx.channel().attr(kickMessageAttribute).set(Constants.TIMESTAMP_DENIED_MESSAGE);
|
||||||
|
return;
|
||||||
default: // only continue when SUCCESS
|
default: // only continue when SUCCESS
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
package org.geysermc.floodgate.addon.data;
|
package org.geysermc.floodgate.addon.data;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static org.geysermc.floodgate.util.ReflectionUtils.castedInvoke;
|
import static org.geysermc.floodgate.util.ReflectionUtils.castedInvoke;
|
||||||
import static org.geysermc.floodgate.util.ReflectionUtils.getCastedValue;
|
import static org.geysermc.floodgate.util.ReflectionUtils.getCastedValue;
|
||||||
@@ -46,6 +45,8 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.geysermc.floodgate.api.ProxyFloodgateApi;
|
import org.geysermc.floodgate.api.ProxyFloodgateApi;
|
||||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||||
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
|
||||||
|
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
|
||||||
|
import org.geysermc.floodgate.util.BedrockData;
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -116,8 +117,8 @@ public final class VelocityServerDataHandler extends MessageToMessageEncoder<Obj
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String encryptedData = api.getEncryptedData(player.getCorrectUniqueId());
|
BedrockData data = player.as(FloodgatePlayerImpl.class).toBedrockData();
|
||||||
checkArgument(encryptedData != null, "Encrypted data cannot be null");
|
String encryptedData = api.createEncryptedDataString(data);
|
||||||
|
|
||||||
// use the same system that we use on bungee, our data goes before all the other data
|
// use the same system that we use on bungee, our data goes before all the other data
|
||||||
int addressFinished = address.indexOf('\0');
|
int addressFinished = address.indexOf('\0');
|
||||||
|
|||||||
Reference in New Issue
Block a user