mirror of https://github.com/LOOHP/Limbo.git
Implemented Velocity Modern Forwarding validation
- Added ForwardingUtils to deal with Modern Forwarding data - Added validation checks in ClientConnection to safely get the forwarded data
This commit is contained in:
parent
c7378beb2b
commit
69ef34dc54
|
|
@ -1,81 +1,39 @@
|
||||||
package com.loohp.limbo.server;
|
package com.loohp.limbo.server;
|
||||||
|
|
||||||
|
import com.loohp.limbo.Limbo;
|
||||||
|
import com.loohp.limbo.events.player.*;
|
||||||
|
import com.loohp.limbo.events.status.StatusPingEvent;
|
||||||
|
import com.loohp.limbo.file.ServerProperties;
|
||||||
|
import com.loohp.limbo.location.Location;
|
||||||
|
import com.loohp.limbo.player.Player;
|
||||||
|
import com.loohp.limbo.player.PlayerInteractManager;
|
||||||
|
import com.loohp.limbo.server.packets.*;
|
||||||
|
import com.loohp.limbo.server.packets.PacketPlayOutPlayerAbilities.PlayerAbilityFlags;
|
||||||
|
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoAction;
|
||||||
|
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
|
||||||
|
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoData.PlayerInfoDataAddPlayer.PlayerSkinProperty;
|
||||||
|
import com.loohp.limbo.server.packets.PacketPlayOutTabComplete.TabCompleteMatches;
|
||||||
|
import com.loohp.limbo.utils.*;
|
||||||
|
import com.loohp.limbo.utils.MojangAPIUtils.SkinResponse;
|
||||||
|
import com.loohp.limbo.world.BlockPosition;
|
||||||
|
import com.loohp.limbo.world.World;
|
||||||
|
import net.md_5.bungee.api.ChatColor;
|
||||||
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
import net.md_5.bungee.api.chat.ComponentBuilder;
|
||||||
|
import net.md_5.bungee.api.chat.TextComponent;
|
||||||
|
import net.md_5.bungee.chat.ComponentSerializer;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
import java.io.DataInputStream;
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import com.loohp.limbo.Limbo;
|
|
||||||
import com.loohp.limbo.events.player.PlayerJoinEvent;
|
|
||||||
import com.loohp.limbo.events.player.PlayerLoginEvent;
|
|
||||||
import com.loohp.limbo.events.player.PlayerMoveEvent;
|
|
||||||
import com.loohp.limbo.events.player.PlayerQuitEvent;
|
|
||||||
import com.loohp.limbo.events.player.PlayerSelectedSlotChangeEvent;
|
|
||||||
import com.loohp.limbo.events.status.StatusPingEvent;
|
|
||||||
import com.loohp.limbo.file.ServerProperties;
|
|
||||||
import com.loohp.limbo.location.Location;
|
|
||||||
import com.loohp.limbo.player.Player;
|
|
||||||
import com.loohp.limbo.player.PlayerInteractManager;
|
|
||||||
import com.loohp.limbo.server.packets.Packet;
|
|
||||||
import com.loohp.limbo.server.packets.PacketHandshakingIn;
|
|
||||||
import com.loohp.limbo.server.packets.PacketLoginInLoginStart;
|
|
||||||
import com.loohp.limbo.server.packets.PacketLoginOutDisconnect;
|
|
||||||
import com.loohp.limbo.server.packets.PacketLoginOutLoginSuccess;
|
|
||||||
import com.loohp.limbo.server.packets.PacketOut;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayInChat;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayInHeldItemChange;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayInKeepAlive;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayInPosition;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayInPositionAndLook;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayInRotation;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayInTabComplete;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutDeclareCommands;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutDisconnect;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutEntityMetadata;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutGameState;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutHeldItemChange;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutLogin;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutPlayerAbilities;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutPlayerAbilities.PlayerAbilityFlags;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoAction;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoData;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutPlayerInfo.PlayerInfoData.PlayerInfoDataAddPlayer.PlayerSkinProperty;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutPositionAndLook;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutSpawnPosition;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutTabComplete;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutTabComplete.TabCompleteMatches;
|
|
||||||
import com.loohp.limbo.server.packets.PacketPlayOutUpdateViewPosition;
|
|
||||||
import com.loohp.limbo.server.packets.PacketStatusInPing;
|
|
||||||
import com.loohp.limbo.server.packets.PacketStatusInRequest;
|
|
||||||
import com.loohp.limbo.server.packets.PacketStatusOutPong;
|
|
||||||
import com.loohp.limbo.server.packets.PacketStatusOutResponse;
|
|
||||||
import com.loohp.limbo.utils.CustomStringUtils;
|
|
||||||
import com.loohp.limbo.utils.DataTypeIO;
|
|
||||||
import com.loohp.limbo.utils.DeclareCommands;
|
|
||||||
import com.loohp.limbo.utils.GameMode;
|
|
||||||
import com.loohp.limbo.utils.MojangAPIUtils;
|
|
||||||
import com.loohp.limbo.utils.MojangAPIUtils.SkinResponse;
|
|
||||||
import com.loohp.limbo.utils.NamespacedKey;
|
|
||||||
import com.loohp.limbo.world.BlockPosition;
|
|
||||||
import com.loohp.limbo.world.World;
|
|
||||||
|
|
||||||
import net.md_5.bungee.api.ChatColor;
|
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
|
||||||
import net.md_5.bungee.api.chat.TextComponent;
|
|
||||||
import net.md_5.bungee.chat.ComponentSerializer;
|
|
||||||
|
|
||||||
public class ClientConnection extends Thread {
|
public class ClientConnection extends Thread {
|
||||||
|
|
||||||
public static enum ClientState {
|
public static enum ClientState {
|
||||||
|
|
@ -87,6 +45,8 @@ public class ClientConnection extends Thread {
|
||||||
DISCONNECTED;
|
DISCONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final Random random = new Random();
|
||||||
|
|
||||||
private final Socket client_socket;
|
private final Socket client_socket;
|
||||||
private boolean running;
|
private boolean running;
|
||||||
private ClientState state;
|
private ClientState state;
|
||||||
|
|
@ -201,10 +161,11 @@ public class ClientConnection extends Thread {
|
||||||
|
|
||||||
PacketHandshakingIn handshake = new PacketHandshakingIn(input);
|
PacketHandshakingIn handshake = new PacketHandshakingIn(input);
|
||||||
|
|
||||||
boolean isBungeecord = Limbo.getInstance().getServerProperties().isBungeecord();
|
boolean isBungeecord = Limbo.getInstance().getServerProperties().isBungeecord();
|
||||||
|
boolean isVelocityModern = Limbo.getInstance().getServerProperties().isVelocityModern();
|
||||||
String bungeeForwarding = handshake.getServerAddress();
|
String bungeeForwarding = handshake.getServerAddress();
|
||||||
UUID bungeeUUID = null;
|
UUID bungeeUUID = null;
|
||||||
SkinResponse bungeeSkin = null;
|
SkinResponse forwardedSkin = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (handshake.getHandshakeType()) {
|
switch (handshake.getHandshakeType()) {
|
||||||
|
|
@ -250,14 +211,15 @@ public class ClientConnection extends Thread {
|
||||||
|
|
||||||
String skin = skinJson.split("\"value\":\"")[1].split("\"")[0];
|
String skin = skinJson.split("\"value\":\"")[1].split("\"")[0];
|
||||||
String signature = skinJson.split("\"signature\":\"")[1].split("\"")[0];
|
String signature = skinJson.split("\"signature\":\"")[1].split("\"")[0];
|
||||||
bungeeSkin = new SkinResponse(skin, signature);
|
forwardedSkin = new SkinResponse(skin, signature);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Limbo.getInstance().getConsole().sendMessage("If you wish to use bungeecord's IP forwarding, please enable that in your bungeecord config.yml as well!");
|
Limbo.getInstance().getConsole().sendMessage("If you wish to use bungeecord's IP forwarding, please enable that in your bungeecord config.yml as well!");
|
||||||
disconnectDuringLogin(new BaseComponent[] {new TextComponent(ChatColor.RED + "Please connect from the proxy!")});
|
disconnectDuringLogin(new BaseComponent[] {new TextComponent(ChatColor.RED + "Please connect from the proxy!")});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int messageId = this.random.nextInt();
|
||||||
while (client_socket.isConnected()) {
|
while (client_socket.isConnected()) {
|
||||||
int size = DataTypeIO.readVarInt(input);
|
int size = DataTypeIO.readVarInt(input);
|
||||||
int packetId = DataTypeIO.readVarInt(input);
|
int packetId = DataTypeIO.readVarInt(input);
|
||||||
|
|
@ -268,6 +230,13 @@ public class ClientConnection extends Thread {
|
||||||
} else if (packetType.equals(PacketLoginInLoginStart.class)) {
|
} else if (packetType.equals(PacketLoginInLoginStart.class)) {
|
||||||
PacketLoginInLoginStart start = new PacketLoginInLoginStart(input);
|
PacketLoginInLoginStart start = new PacketLoginInLoginStart(input);
|
||||||
String username = start.getUsername();
|
String username = start.getUsername();
|
||||||
|
|
||||||
|
if (Limbo.getInstance().getServerProperties().isVelocityModern()) {
|
||||||
|
PacketLoginOutPluginMessaging loginPluginRequest = new PacketLoginOutPluginMessaging(messageId, ForwardingUtils.VELOCITY_FORWARDING_CHANNEL);
|
||||||
|
sendPacket(loginPluginRequest);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
UUID uuid = isBungeecord ? bungeeUUID : UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
|
UUID uuid = isBungeecord ? bungeeUUID : UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
PacketLoginOutLoginSuccess success = new PacketLoginOutLoginSuccess(uuid, username);
|
PacketLoginOutLoginSuccess success = new PacketLoginOutLoginSuccess(uuid, username);
|
||||||
|
|
@ -279,6 +248,34 @@ public class ClientConnection extends Thread {
|
||||||
player.setSkinLayers((byte) (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40));
|
player.setSkinLayers((byte) (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40));
|
||||||
Limbo.getInstance().addPlayer(player);
|
Limbo.getInstance().addPlayer(player);
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else if (packetType.equals(PacketLoginInPluginMessaging.class)) {
|
||||||
|
PacketLoginInPluginMessaging response = new PacketLoginInPluginMessaging(input, size, packetId);
|
||||||
|
if (response.getMessageId() != messageId) {
|
||||||
|
disconnectDuringLogin(TextComponent.fromLegacyText("Internal error, messageId did not match"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (response.getData() == null) {
|
||||||
|
disconnectDuringLogin(TextComponent.fromLegacyText("Unknown login plugin response packet!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ForwardingUtils.validateVelocityModernResponse(response.getData())) {
|
||||||
|
disconnectDuringLogin(TextComponent.fromLegacyText("Invalid playerinfo forwarding!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ForwardingUtils.VelocityModernForwardingData data = ForwardingUtils.getVelocityDataFrom(response.getData());
|
||||||
|
inetAddress = InetAddress.getByName(data.getIpAddress());
|
||||||
|
forwardedSkin = data.getSkinResponse();
|
||||||
|
|
||||||
|
PacketLoginOutLoginSuccess success = new PacketLoginOutLoginSuccess(data.getUuid(), data.getUsername());
|
||||||
|
sendPacket(success);
|
||||||
|
|
||||||
|
state = ClientState.PLAY;
|
||||||
|
|
||||||
|
player = new Player(this, data.getUsername(), data.getUuid(), Limbo.getInstance().getNextEntityId(), Limbo.getInstance().getServerProperties().getWorldSpawn(), new PlayerInteractManager());
|
||||||
|
player.setSkinLayers((byte) (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x40));
|
||||||
|
Limbo.getInstance().addPlayer(player);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
input.skipBytes(size - DataTypeIO.getVarIntLength(packetId));
|
input.skipBytes(size - DataTypeIO.getVarIntLength(packetId));
|
||||||
|
|
@ -293,6 +290,7 @@ public class ClientConnection extends Thread {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
client_socket.close();
|
client_socket.close();
|
||||||
state = ClientState.DISCONNECTED;
|
state = ClientState.DISCONNECTED;
|
||||||
}
|
}
|
||||||
|
|
@ -312,7 +310,7 @@ public class ClientConnection extends Thread {
|
||||||
sendPacket(join);
|
sendPacket(join);
|
||||||
Limbo.getInstance().getUnsafe().setPlayerGameModeSilently(player, properties.getDefaultGamemode());
|
Limbo.getInstance().getUnsafe().setPlayerGameModeSilently(player, properties.getDefaultGamemode());
|
||||||
|
|
||||||
SkinResponse skinresponce = isBungeecord && bungeeSkin != null ? bungeeSkin : MojangAPIUtils.getSkinFromMojangServer(player.getName());
|
SkinResponse skinresponce = (isVelocityModern || isBungeecord) && forwardedSkin != null ? forwardedSkin : MojangAPIUtils.getSkinFromMojangServer(player.getName());
|
||||||
PlayerSkinProperty skin = skinresponce != null ? new PlayerSkinProperty(skinresponce.getSkin(), skinresponce.getSignature()) : null;
|
PlayerSkinProperty skin = skinresponce != null ? new PlayerSkinProperty(skinresponce.getSkin(), skinresponce.getSignature()) : null;
|
||||||
PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo(PlayerInfoAction.ADD_PLAYER, player.getUniqueId(), new PlayerInfoData.PlayerInfoDataAddPlayer(player.getName(), Optional.ofNullable(skin), properties.getDefaultGamemode(), 0, false, Optional.empty()));
|
PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo(PlayerInfoAction.ADD_PLAYER, player.getUniqueId(), new PlayerInfoData.PlayerInfoDataAddPlayer(player.getName(), Optional.ofNullable(skin), properties.getDefaultGamemode(), 0, false, Optional.empty()));
|
||||||
sendPacket(info);
|
sendPacket(info);
|
||||||
|
|
@ -327,7 +325,7 @@ public class ClientConnection extends Thread {
|
||||||
PacketPlayOutPlayerAbilities abilities = new PacketPlayOutPlayerAbilities(0.05F, 0.1F, flags.toArray(new PlayerAbilityFlags[flags.size()]));
|
PacketPlayOutPlayerAbilities abilities = new PacketPlayOutPlayerAbilities(0.05F, 0.1F, flags.toArray(new PlayerAbilityFlags[flags.size()]));
|
||||||
sendPacket(abilities);
|
sendPacket(abilities);
|
||||||
|
|
||||||
String str = inetAddress.getHostName() + ":" + client_socket.getPort() + "|" + player.getName();
|
String str = inetAddress.getHostName() + ":" + client_socket.getPort() + "|" + player.getName() + "(" + player.getUniqueId() + ")";
|
||||||
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had connected to the Limbo server!");
|
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had connected to the Limbo server!");
|
||||||
|
|
||||||
PacketPlayOutDeclareCommands declare = DeclareCommands.getDeclareCommandsPacket(player);
|
PacketPlayOutDeclareCommands declare = DeclareCommands.getDeclareCommandsPacket(player);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package com.loohp.limbo.utils;
|
||||||
|
|
||||||
|
import com.loohp.limbo.Limbo;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class ForwardingUtils {
|
||||||
|
|
||||||
|
public static final NamespacedKey VELOCITY_FORWARDING_CHANNEL = new NamespacedKey("velocity", "player_info");
|
||||||
|
|
||||||
|
public static boolean validateVelocityModernResponse(byte[] data) throws IOException {
|
||||||
|
ByteArrayInputStream byteIn = new ByteArrayInputStream(data);
|
||||||
|
DataInputStream input = new DataInputStream(byteIn);
|
||||||
|
|
||||||
|
byte[] signature = new byte[32];
|
||||||
|
input.readFully(signature);
|
||||||
|
|
||||||
|
boolean foundValid = false;
|
||||||
|
try {
|
||||||
|
Mac mac = Mac.getInstance("HmacSHA256");
|
||||||
|
for (String secret : Limbo.getInstance().getServerProperties().getForwardingSecrets()) {
|
||||||
|
SecretKey key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||||
|
mac.init(key);
|
||||||
|
mac.update(data, 32, data.length - 32);
|
||||||
|
byte[] sig = mac.doFinal();
|
||||||
|
if (Arrays.equals(signature, sig)) {
|
||||||
|
foundValid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new RuntimeException("Unable to authenticate data", e);
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// Should never happen
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VelocityModernForwardingData getVelocityDataFrom(byte[] data) throws IOException {
|
||||||
|
ByteArrayInputStream byteIn = new ByteArrayInputStream(data);
|
||||||
|
DataInputStream input = new DataInputStream(byteIn);
|
||||||
|
|
||||||
|
input.skipBytes(32);
|
||||||
|
|
||||||
|
int velocityVersion = DataTypeIO.readVarInt(input);
|
||||||
|
if (velocityVersion != 1) {
|
||||||
|
System.out.println("Unsupported Velocity version! Stopping Execution");
|
||||||
|
throw new AssertionError("Unknown Velocity Packet");
|
||||||
|
}
|
||||||
|
String address = DataTypeIO.readString(input, StandardCharsets.UTF_8);
|
||||||
|
UUID uuid = DataTypeIO.readUUID(input);
|
||||||
|
String username = DataTypeIO.readString(input, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
//cycle properties
|
||||||
|
MojangAPIUtils.SkinResponse response = null;
|
||||||
|
int count = DataTypeIO.readVarInt(input);
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
String propertyName = DataTypeIO.readString(input, StandardCharsets.UTF_8);
|
||||||
|
String propertyValue = DataTypeIO.readString(input, StandardCharsets.UTF_8);
|
||||||
|
String propertySignature = "";
|
||||||
|
boolean signatureIncluded = input.readBoolean();
|
||||||
|
if (signatureIncluded) {
|
||||||
|
propertySignature = DataTypeIO.readString(input, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
if (propertyName.equals("textures")) {
|
||||||
|
response = new MojangAPIUtils.SkinResponse(propertyValue, propertySignature);
|
||||||
|
break; // we don't use others properties for now
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new VelocityModernForwardingData(velocityVersion, address, uuid, username, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class VelocityModernForwardingData {
|
||||||
|
|
||||||
|
private final int version;
|
||||||
|
private final String ipAddress;
|
||||||
|
private final UUID uuid;
|
||||||
|
private final String username;
|
||||||
|
private final MojangAPIUtils.SkinResponse skinResponse;
|
||||||
|
|
||||||
|
public VelocityModernForwardingData(int version, String ipAddress, UUID uuid, String username, MojangAPIUtils.SkinResponse skinResponse) {
|
||||||
|
this.version = version;
|
||||||
|
this.ipAddress = ipAddress;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.username = username;
|
||||||
|
this.skinResponse = skinResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getVersion()
|
||||||
|
{
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getIpAddress()
|
||||||
|
{
|
||||||
|
return ipAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getUuid()
|
||||||
|
{
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername()
|
||||||
|
{
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MojangAPIUtils.SkinResponse getSkinResponse()
|
||||||
|
{
|
||||||
|
return skinResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue