1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2025-12-19 14:59:20 +00:00

Moved skin uploading to the global api

This commit is contained in:
Tim203
2021-02-12 22:26:42 +01:00
parent 700b377e43
commit fff10e7084
25 changed files with 282 additions and 649 deletions

View File

@@ -29,7 +29,6 @@ import io.netty.channel.Channel;
import java.util.UUID; import java.util.UUID;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.LinkedPlayer; import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
/** /**
* For advanced users only! You shouldn't play with this unless you know what you're doing.<br> * For advanced users only! You shouldn't play with this unless you know what you're doing.<br>
@@ -76,18 +75,6 @@ public interface HandshakeData {
*/ */
void setLinkedPlayer(LinkedPlayer player); void setLinkedPlayer(LinkedPlayer player);
/**
* Returns the skin of the client. Can be null even though the player is a Floodgate player.
*/
RawSkin getRawSkin();
/**
* Manually set the skin of the client.
*
* @param rawSkin the skin of the client
*/
void setRawSkin(RawSkin rawSkin);
/** /**
* Returns the hostname used in the handshake packet. This is the hostname after Floodgate * Returns the hostname used in the handshake packet. This is the hostname after Floodgate
* removed the data. * removed the data.

View File

@@ -32,7 +32,6 @@ import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.DeviceOs;
import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.InputMode;
import org.geysermc.floodgate.util.LinkedPlayer; import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.UiProfile; import org.geysermc.floodgate.util.UiProfile;
public interface FloodgatePlayer { public interface FloodgatePlayer {
@@ -107,11 +106,6 @@ public interface FloodgatePlayer {
*/ */
LinkedPlayer getLinkedPlayer(); LinkedPlayer getLinkedPlayer();
/**
* Returns the raw skin of the Bedrock player
*/
RawSkin getRawSkin();
default boolean sendForm(Form form) { default boolean sendForm(Form form) {
return FloodgateApi.getInstance().sendForm(getCorrectUniqueId(), form); return FloodgateApi.getInstance().sendForm(getCorrectUniqueId(), form);
} }

View File

@@ -37,7 +37,6 @@ import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.LoginEvent;
import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent;
import net.md_5.bungee.api.event.PreLoginEvent; import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.api.event.ServerConnectedEvent;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventHandler;
@@ -46,12 +45,7 @@ import net.md_5.bungee.netty.ChannelWrapper;
import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.player.FloodgatePlayerImpl; import org.geysermc.floodgate.player.FloodgatePlayerImpl;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.pluginmessage.channel.SkinChannel;
import org.geysermc.floodgate.skin.SkinHandler;
import org.geysermc.floodgate.util.BungeeCommandUtil; import org.geysermc.floodgate.util.BungeeCommandUtil;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
@@ -74,10 +68,6 @@ public final class BungeeListener implements Listener {
@Inject private LanguageManager languageManager; @Inject private LanguageManager languageManager;
@Inject private FloodgateLogger logger; @Inject private FloodgateLogger logger;
@Inject private ProxyFloodgateConfig config;
@Inject private PluginMessageManager pluginMessageManager;
@Inject private SkinHandler skinHandler;
@Inject @Inject
@Named("playerAttribute") @Named("playerAttribute")
private AttributeKey<FloodgatePlayer> playerAttribute; private AttributeKey<FloodgatePlayer> playerAttribute;
@@ -86,27 +76,6 @@ public final class BungeeListener implements Listener {
@Named("kickMessageAttribute") @Named("kickMessageAttribute")
private AttributeKey<String> kickMessageAttribute; private AttributeKey<String> kickMessageAttribute;
@EventHandler
public void onServerConnected(ServerConnectedEvent event) {
FloodgatePlayer player = api.getPlayer(event.getPlayer().getUniqueId());
if (player == null) {
return;
}
// only ask for skin upload if it hasn't been uploaded already
if (player.hasProperty(PropertyKey.SKIN_UPLOADED)) {
return;
}
// send skin request to server if data forwarding allows that
if (config.isSendFloodgateData()) {
pluginMessageManager.getChannel(SkinChannel.class)
.sendSkinRequest(player.getCorrectUniqueId(), player.getRawSkin());
} else {
skinHandler.handleSkinUploadFor(player, null);
}
}
@EventHandler(priority = EventPriority.LOWEST) @EventHandler(priority = EventPriority.LOWEST)
public void onPreLogin(PreLoginEvent event) { public void onPreLogin(PreLoginEvent event) {
// well, no reason to check if the player will be kicked anyway // well, no reason to check if the player will be kicked anyway

View File

@@ -35,14 +35,13 @@ import net.md_5.bungee.connection.LoginResult.Property;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
@RequiredArgsConstructor @RequiredArgsConstructor
public final class BungeeSkinApplier implements SkinApplier { public final class BungeeSkinApplier implements SkinApplier {
private final FloodgateLogger logger; private final FloodgateLogger logger;
@Override @Override
public void applySkin(FloodgatePlayer uuid, UploadResult result) { public void applySkin(FloodgatePlayer uuid, JsonObject skinResult) {
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid.getCorrectUniqueId()); ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid.getCorrectUniqueId());
InitialHandler handler; InitialHandler handler;
@@ -61,11 +60,10 @@ public final class BungeeSkinApplier implements SkinApplier {
loginResult = new LoginResult(null, null, null); loginResult = new LoginResult(null, null, null);
} }
JsonObject response = result.getResponse();
Property property = new Property( Property property = new Property(
"textures", "textures",
response.get("value").getAsString(), skinResult.get("value").getAsString(),
response.get("signature").getAsString() skinResult.get("signature").getAsString()
); );
loginResult.setProperties(new Property[]{property}); loginResult.setProperties(new Property[]{property});

View File

@@ -35,6 +35,16 @@
<artifactId>fastutil-short-object-maps</artifactId> <artifactId>fastutil-short-object-maps</artifactId>
<version>8.3.1</version> <version>8.3.1</version>
</dependency> </dependency>
<dependency>
<groupId>com.nukkitx.fastutil</groupId>
<artifactId>fastutil-int-object-maps</artifactId>
<version>8.3.1</version>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-Websocket</artifactId>
<version>1.5.1</version>
</dependency>
<dependency> <dependency>
<groupId>org.geysermc.floodgate</groupId> <groupId>org.geysermc.floodgate</groupId>
<artifactId>api</artifactId> <artifactId>api</artifactId>

View File

@@ -33,7 +33,6 @@ import org.geysermc.floodgate.api.handshake.HandshakeData;
import org.geysermc.floodgate.config.FloodgateConfig; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.LinkedPlayer; import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.Utils; import org.geysermc.floodgate.util.Utils;
@Getter @Getter
@@ -45,7 +44,6 @@ public class HandshakeDataImpl implements HandshakeData {
private final UUID javaUniqueId; private final UUID javaUniqueId;
@Setter private LinkedPlayer linkedPlayer; @Setter private LinkedPlayer linkedPlayer;
@Setter private RawSkin rawSkin;
@Setter private String hostname; @Setter private String hostname;
@Setter private String bedrockIp; @Setter private String bedrockIp;
@Setter private String disconnectReason; @Setter private String disconnectReason;
@@ -56,14 +54,12 @@ public class HandshakeDataImpl implements HandshakeData {
BedrockData bedrockData, BedrockData bedrockData,
FloodgateConfig config, FloodgateConfig config,
LinkedPlayer linkedPlayer, LinkedPlayer linkedPlayer,
RawSkin rawSkin,
String hostname) { String hostname) {
this.channel = channel; this.channel = channel;
this.floodgatePlayer = floodgatePlayer; this.floodgatePlayer = floodgatePlayer;
this.bedrockData = bedrockData; this.bedrockData = bedrockData;
this.linkedPlayer = linkedPlayer; this.linkedPlayer = linkedPlayer;
this.rawSkin = rawSkin;
this.hostname = hostname; this.hostname = hostname;
String javaUsername = null; String javaUsername = null;

View File

@@ -37,7 +37,6 @@ public class FloodgateConfig {
private String keyFileName; private String keyFileName;
private String usernamePrefix; private String usernamePrefix;
private boolean replaceSpaces; private boolean replaceSpaces;
private boolean applySkinDirectly; //todo how is this possible for proxies?
private String defaultLocale; private String defaultLocale;

View File

@@ -54,7 +54,7 @@ import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.player.FloodgateHandshakeHandler; import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager; import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinHandler; import org.geysermc.floodgate.skin.SkinUploadManager;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -141,16 +141,12 @@ public class CommonModule extends AbstractModule {
SimpleFloodgateApi api, SimpleFloodgateApi api,
FloodgateCipher cipher, FloodgateCipher cipher,
FloodgateConfigHolder configHolder, FloodgateConfigHolder configHolder,
SkinUploadManager skinUploadManager,
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute, @Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute,
FloodgateLogger logger) { FloodgateLogger logger) {
return new FloodgateHandshakeHandler(
handshakeHandlers, return new FloodgateHandshakeHandler(handshakeHandlers, api, cipher, configHolder,
api, skinUploadManager, playerAttribute, logger);
cipher,
configHolder,
playerAttribute,
logger
);
} }
@Provides @Provides
@@ -161,11 +157,11 @@ public class CommonModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
public SkinHandler skinHandler( public SkinUploadManager skinUploadManager(
PluginMessageManager pluginMessageManager, FloodgateApi api,
SkinApplier skinApplier, SkinApplier skinApplier,
FloodgateLogger logger) { FloodgateLogger logger) {
return new SkinHandler(pluginMessageManager, skinApplier, logger); return new SkinUploadManager(api, skinApplier, logger);
} }
@Provides @Provides

View File

@@ -46,13 +46,11 @@ import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey; import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.config.FloodgateConfigHolder; import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.crypto.AesCipher;
import org.geysermc.floodgate.crypto.FloodgateCipher; import org.geysermc.floodgate.crypto.FloodgateCipher;
import org.geysermc.floodgate.util.Base64Utils; import org.geysermc.floodgate.skin.SkinUploadManager;
import org.geysermc.floodgate.util.BedrockData; import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.InvalidFormatException; import org.geysermc.floodgate.util.InvalidFormatException;
import org.geysermc.floodgate.util.LinkedPlayer; import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.Utils; import org.geysermc.floodgate.util.Utils;
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -61,6 +59,7 @@ public final class FloodgateHandshakeHandler {
private final SimpleFloodgateApi api; private final SimpleFloodgateApi api;
private final FloodgateCipher cipher; private final FloodgateCipher cipher;
private final FloodgateConfigHolder configHolder; private final FloodgateConfigHolder configHolder;
private final SkinUploadManager skinUploadManager;
private final AttributeKey<FloodgatePlayer> playerAttribute; private final AttributeKey<FloodgatePlayer> playerAttribute;
private final FloodgateLogger logger; private final FloodgateLogger logger;
@@ -85,22 +84,8 @@ public final class FloodgateHandshakeHandler {
channel, null, hostname); channel, null, hostname);
} }
// header + base64 iv - 0x21 - encrypted data - 0x21 - RawSkin
int expectedHeaderLength = FloodgateCipher.HEADER_LENGTH +
Base64Utils.getEncodedLength(AesCipher.IV_LENGTH);
int lastSplitIndex = data.lastIndexOf(0x21);
try { try {
byte[] floodgateData; byte[] floodgateData = data.getBytes(Charsets.UTF_8);
byte[] rawSkinData = null;
// if it has a RawSkin
if (lastSplitIndex - expectedHeaderLength > 0) {
floodgateData = data.substring(0, lastSplitIndex).getBytes(Charsets.UTF_8);
rawSkinData = data.substring(lastSplitIndex + 1).getBytes(Charsets.UTF_8);
} else {
floodgateData = data.getBytes(Charsets.UTF_8);
}
// actual decryption // actual decryption
String decrypted = cipher.decryptToString(floodgateData); String decrypted = cipher.decryptToString(floodgateData);
@@ -112,14 +97,6 @@ public final class FloodgateHandshakeHandler {
channel, bedrockData, hostname); channel, bedrockData, hostname);
} }
RawSkin rawSkin = null;
// only decompile the skin after knowing that the floodgateData is legit
// note that we don't store a hash or anything in the BedrockData,
// so a mitm can change skins
if (rawSkinData != null) {
rawSkin = RawSkin.decode(rawSkinData);
}
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)
@@ -133,9 +110,14 @@ public final class FloodgateHandshakeHandler {
HandshakeData handshakeData = new HandshakeDataImpl( HandshakeData handshakeData = new HandshakeDataImpl(
channel, true, bedrockData.clone(), configHolder.get(), channel, true, bedrockData.clone(), configHolder.get(),
linkedPlayer != null ? linkedPlayer.clone() : null, rawSkin, hostname); linkedPlayer != null ? linkedPlayer.clone() : null, hostname);
handshakeHandlers.callHandshakeHandlers(handshakeData); handshakeHandlers.callHandshakeHandlers(handshakeData);
if (!handshakeData.shouldDisconnect()) {
skinUploadManager.addConnectionIfNeeded(bedrockData.getSubscribeId(),
bedrockData.getVerifyCode());
}
UUID javaUuid = Utils.getJavaUuid(bedrockData.getXuid()); UUID javaUuid = Utils.getJavaUuid(bedrockData.getXuid());
handshakeData.setHostname(correctHostname( handshakeData.setHostname(correctHostname(
handshakeData.getHostname(), bedrockData, javaUuid handshakeData.getHostname(), bedrockData, javaUuid
@@ -149,7 +131,8 @@ public final class FloodgateHandshakeHandler {
channel.attr(playerAttribute).set(player); channel.attr(playerAttribute).set(player);
int port = ((InetSocketAddress) channel.remoteAddress()).getPort(); int port = ((InetSocketAddress) channel.remoteAddress()).getPort();
InetSocketAddress socketAddress = new InetSocketAddress(handshakeData.getBedrockIp(), port); InetSocketAddress socketAddress = new InetSocketAddress(handshakeData.getBedrockIp(),
port);
player.addProperty(PropertyKey.SOCKET_ADDRESS, socketAddress); player.addProperty(PropertyKey.SOCKET_ADDRESS, socketAddress);
return new HandshakeResult(ResultType.SUCCESS, handshakeData, bedrockData, player); return new HandshakeResult(ResultType.SUCCESS, handshakeData, bedrockData, player);
@@ -185,7 +168,7 @@ public final class FloodgateHandshakeHandler {
String hostname) { String hostname) {
HandshakeData handshakeData = new HandshakeDataImpl(channel, bedrockData != null, HandshakeData handshakeData = new HandshakeDataImpl(channel, bedrockData != null,
bedrockData, configHolder.get(), null, null, hostname); bedrockData, configHolder.get(), null, hostname);
handshakeHandlers.callHandshakeHandlers(handshakeData); handshakeHandlers.callHandshakeHandlers(handshakeData);
if (bedrockData != null) { if (bedrockData != null) {

View File

@@ -46,7 +46,6 @@ import org.geysermc.floodgate.util.BedrockData;
import org.geysermc.floodgate.util.DeviceOs; import org.geysermc.floodgate.util.DeviceOs;
import org.geysermc.floodgate.util.InputMode; import org.geysermc.floodgate.util.InputMode;
import org.geysermc.floodgate.util.LinkedPlayer; import org.geysermc.floodgate.util.LinkedPlayer;
import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.UiProfile; import org.geysermc.floodgate.util.UiProfile;
import org.geysermc.floodgate.util.Utils; import org.geysermc.floodgate.util.Utils;
@@ -67,7 +66,9 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
private final boolean fromProxy; private final boolean fromProxy;
private final boolean proxy; // if current platform is a proxy private final boolean proxy; // if current platform is a proxy
private final LinkedPlayer linkedPlayer; private final LinkedPlayer linkedPlayer;
private final RawSkin rawSkin;
private final int subscribeId;
private final String verifyCode;
@Getter(AccessLevel.PRIVATE) @Getter(AccessLevel.PRIVATE)
public Map<PropertyKey, Object> propertyKeyToValue; public Map<PropertyKey, Object> propertyKeyToValue;
@@ -92,16 +93,14 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
InputMode inputMode = InputMode.getById(data.getInputMode()); InputMode inputMode = InputMode.getById(data.getInputMode());
LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer(); LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer();
RawSkin skin = handshakeData.getRawSkin();
FloodgatePlayerImpl player = new FloodgatePlayerImpl( FloodgatePlayerImpl player = 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, skin); linkedPlayer, data.getSubscribeId(), data.getVerifyCode());
// RawSkin should be removed, fromProxy should be changed // fromProxy and linked player might have to be changed
// and encrypted data can be changed after fetching the linkedPlayer
if (api instanceof ProxyFloodgateApi) { if (api instanceof ProxyFloodgateApi) {
InstanceHolder.castApi(ProxyFloodgateApi.class) InstanceHolder.castApi(ProxyFloodgateApi.class)
.updateEncryptedData(player.getCorrectUniqueId(), player.toBedrockData()); .updateEncryptedData(player.getCorrectUniqueId(), player.toBedrockData());
@@ -155,9 +154,9 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
} }
public BedrockData toBedrockData() { public BedrockData toBedrockData() {
return BedrockData.of( return BedrockData.of(version, username, xuid, deviceOs.ordinal(), languageCode,
version, username, xuid, deviceOs.ordinal(), languageCode, uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer, proxy, subscribeId,
uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer, proxy); verifyCode);
} }
@Override @Override

View File

@@ -25,36 +25,22 @@
package org.geysermc.floodgate.pluginmessage.channel; package org.geysermc.floodgate.pluginmessage.channel;
import com.google.gson.Gson;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.UUID; import java.util.UUID;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey; import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageUtils; import org.geysermc.floodgate.config.FloodgateConfig;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.pluginmessage.PluginMessageChannel; import org.geysermc.floodgate.pluginmessage.PluginMessageChannel;
import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinHandler;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
import org.geysermc.floodgate.util.Base64Utils;
import org.geysermc.floodgate.util.RawSkin;
public class SkinChannel implements PluginMessageChannel { public class SkinChannel implements PluginMessageChannel {
private static final Gson GSON = new Gson();
@Inject private FloodgateApi api; @Inject private FloodgateApi api;
@Inject private PluginMessageUtils pluginMessageUtils; @Inject private FloodgateConfig config;
@Inject private SkinHandler skinHandler;
@Inject private SkinApplier skinApplier; @Inject private SkinApplier skinApplier;
@Inject private FloodgateLogger logger;
@Override @Override
public String getIdentifier() { public String getIdentifier() {
@@ -71,58 +57,21 @@ public class SkinChannel implements PluginMessageChannel {
String sourceUsername, String sourceUsername,
Identity sourceIdentity) { Identity sourceIdentity) {
if (data.length < 1) { // we can only get skins from Geyser (client)
return Result.kick("Got invalid Skin request/response");
}
boolean request = data[0] == 1;
if (!request && data.length < 2) {
return Result.kick("Got invalid Skin response");
}
if (sourceIdentity == Identity.SERVER) {
if (request) {
return Result.kick("Got Skin request from Server?");
}
FloodgatePlayer floodgatePlayer = api.getPlayer(targetUuid);
if (floodgatePlayer == null) {
return Result.kick("Server issued Skin request for non-Floodgate player");
}
// 1 = failed, 0 = successful.
// we'll try it again on the next server if it failed
if (data[1] != 0) {
return Result.handled();
}
// we only have to continue if the player doesn't already have a skin uploaded
if (floodgatePlayer.hasProperty(PropertyKey.SKIN_UPLOADED)) {
return Result.handled();
}
byte[] responseData = new byte[data.length - 2];
System.arraycopy(data, 2, responseData, 0, responseData.length);
JsonObject response;
try {
Reader reader = new InputStreamReader(new ByteArrayInputStream(responseData));
response = GSON.fromJson(reader, JsonObject.class);
} catch (JsonIOException | JsonSyntaxException throwable) {
logger.error("Failed to read Skin response", throwable);
return Result.handled();
}
floodgatePlayer.addProperty(PropertyKey.SKIN_UPLOADED, response);
skinApplier.applySkin(floodgatePlayer, UploadResult.success(response));
}
// Players (Geyser) can't send requests nor responses
if (sourceIdentity == Identity.PLAYER) { if (sourceIdentity == Identity.PLAYER) {
return Result.kick("Got Skin " + (request ? "request" : "response") + " from Player?"); Result result = handleServerCall(data, targetUuid, targetUsername);
// aka translate 'handled' into 'forward' when send-floodgate-data is enabled
if (!result.isAllowed() && result.getReason() == null) {
if (config.isProxy() && ((ProxyFloodgateConfig) config).isSendFloodgateData()) {
return Result.forward();
}
}
return result;
}
// Servers can't send skin data
if (sourceIdentity == Identity.SERVER) {
return Result.kick("Got skin data from a server?");
} }
return Result.handled(); return Result.handled();
} }
@@ -131,75 +80,27 @@ public class SkinChannel implements PluginMessageChannel {
public Result handleServerCall(byte[] data, UUID targetUuid, String targetUsername) { public Result handleServerCall(byte[] data, UUID targetUuid, String targetUsername) {
FloodgatePlayer floodgatePlayer = api.getPlayer(targetUuid); FloodgatePlayer floodgatePlayer = api.getPlayer(targetUuid);
if (floodgatePlayer == null) { if (floodgatePlayer == null) {
return Result.kick("Non-Floodgate player sent a Skin plugin message"); return Result.kick("Player sent skins data for a non-Floodgate player");
} }
// non-proxy servers can only handle requests (from proxies) String message = new String(data, StandardCharsets.UTF_8);
if (!floodgatePlayer.isFromProxy()) { String[] split = message.split("\0");
return Result.kick("Cannot receive Skin request from Player"); // value and signature
if (split.length != 2) {
return Result.kick("Got invalid skin data");
} }
// 1 byte for isRequest and 9 for RawSkin itself String value = split[0];
if (data.length < Base64Utils.getEncodedLength(9 + 1)) { String signature = split[1];
return Result.kick("Skin request data has to be at least 10 byte long.");
}
boolean request = data[0] == 1; JsonObject result = new JsonObject();
result.addProperty("value", value);
result.addProperty("signature", signature);
if (!request) { floodgatePlayer.addProperty(PropertyKey.SKIN_UPLOADED, result);
return Result.kick("Proxy sent a response instead of a request?"); skinApplier.applySkin(floodgatePlayer, result);
}
RawSkin rawSkin = null;
try {
rawSkin = RawSkin.decode(data, 1);
} catch (Exception exception) {
logger.error("Failed to decode RawSkin", exception);
}
// we let it continue since SkinHandler sends the plugin message for us
skinHandler.handleServerSkinUpload(floodgatePlayer, rawSkin);
return Result.handled(); return Result.handled();
} }
public boolean sendSkinRequest(UUID player, RawSkin skin) {
byte[] skinRequestData = createSkinRequestData(skin.encode());
return pluginMessageUtils.sendMessage(player, true, getIdentifier(), skinRequestData);
}
public void sendSkinResponse(UUID player, boolean failed, String response) {
byte[] skinRequestData = createSkinResponseData(failed, response);
pluginMessageUtils.sendMessage(player, false, getIdentifier(), skinRequestData);
}
public byte[] createSkinRequestData(byte[] data) {
// data format:
// 0 = is request
// remaining = request data
byte[] output = new byte[data.length + 1];
output[0] = 1;
System.arraycopy(data, 0, output, 1, data.length);
return output;
}
public byte[] createSkinResponseData(boolean failed, String data) {
// data format:
// 0 = is request
// 1 = has failed
// remaining = response data
byte[] rawData = data.getBytes(StandardCharsets.UTF_8);
byte[] output = new byte[rawData.length + 2];
output[0] = 0;
output[1] = (byte) (failed ? 1 : 0);
System.arraycopy(rawData, 0, output, 2, rawData.length);
return output;
}
} }

View File

@@ -25,9 +25,9 @@
package org.geysermc.floodgate.skin; package org.geysermc.floodgate.skin;
import com.google.gson.JsonObject;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
public interface SkinApplier { public interface SkinApplier {
void applySkin(FloodgatePlayer floodgatePlayer, UploadResult result); void applySkin(FloodgatePlayer floodgatePlayer, JsonObject skinResult);
} }

View File

@@ -1,114 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.skin;
import java.util.function.BiConsumer;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.pluginmessage.channel.SkinChannel;
import org.geysermc.floodgate.util.RawSkin;
@RequiredArgsConstructor
public class SkinHandler {
private final PluginMessageManager pluginMessageManager;
private final SkinUploader uploader = new SkinUploader();
private final SkinApplier skinApplier;
private final FloodgateLogger logger;
public final void handleSkinUploadFor(
FloodgatePlayer player,
BiConsumer<Boolean, String> consumer) {
handleSkinUploadFor(player, player.getRawSkin(), consumer);
}
public final void handleSkinUploadFor(
FloodgatePlayer player,
RawSkin rawSkin,
BiConsumer<Boolean, String> consumer) {
if (player == null || rawSkin == null) {
if (consumer != null) {
consumer.accept(true, "Skin or Player is null");
}
return;
}
uploader.uploadSkin(rawSkin)
.whenComplete((uploadResult, throwable) -> {
if (throwable != null) {
logger.error(
"Failed to upload player skin for " + player.getCorrectUsername(),
throwable);
if (consumer != null) {
consumer.accept(true, throwable.getMessage());
}
return;
}
if (uploadResult.getError() != null) {
logger.error("Error while uploading player skin for {}: {}",
player.getCorrectUsername(), uploadResult.getError());
if (consumer != null) {
consumer.accept(true, uploadResult.getError());
}
return;
}
logger.info("Skin upload successful for " + player.getCorrectUsername());
if (consumer != null) {
consumer.accept(false, uploadResult.getResponse().toString());
}
player.addProperty(PropertyKey.SKIN_UPLOADED, uploadResult.getResponse());
skinApplier.applySkin(player, uploadResult);
});
}
public void handleServerSkinUpload(FloodgatePlayer player) {
handleServerSkinUpload(player, player.getRawSkin());
}
public void handleServerSkinUpload(FloodgatePlayer player, RawSkin rawSkin) {
if (player == null || rawSkin == null) {
return;
}
handleSkinUploadFor(player, rawSkin,
(failed, response) -> {
if (player.isFromProxy()) {
pluginMessageManager.getChannel(SkinChannel.class)
.sendSkinResponse(player.getCorrectUniqueId(), failed, response);
}
});
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.skin;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import lombok.AllArgsConstructor;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
@AllArgsConstructor
public final class SkinUploadManager {
private final Int2ObjectMap<SkinUploadSocket> connections = new Int2ObjectOpenHashMap<>();
private final FloodgateApi api;
private final SkinApplier applier;
private final FloodgateLogger logger;
public void addConnectionIfNeeded(int id, String verifyCode) {
connections.computeIfAbsent(id, (ignored) -> {
SkinUploadSocket socket =
new SkinUploadSocket(id, verifyCode, this, api, applier, logger);
socket.connect();
return socket;
});
}
public void removeConnection(int id, SkinUploadSocket socket) {
connections.remove(id, socket);
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.skin;
import static org.geysermc.floodgate.util.Constants.WEBSOCKET_URL;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.net.URI;
import lombok.Getter;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.util.Utils;
import org.geysermc.floodgate.util.WebsocketEventType;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
final class SkinUploadSocket extends WebSocketClient {
private static final Gson gson = new Gson();
private final SkinUploadManager uploadManager;
private final FloodgateApi api;
private final SkinApplier applier;
private final FloodgateLogger logger;
@Getter private final int id;
@Getter private final String verifyCode;
@Getter private int subscribersCount;
public SkinUploadSocket(
int id,
String verifyCode,
SkinUploadManager uploadManager,
FloodgateApi api,
SkinApplier applier,
FloodgateLogger logger) {
super(getWebsocketUri(id, verifyCode));
this.id = id;
this.verifyCode = verifyCode;
this.uploadManager = uploadManager;
this.api = api;
this.applier = applier;
this.logger = logger;
}
private static URI getWebsocketUri(int id, String verifyCode) {
try {
return new URI(WEBSOCKET_URL + "?subscribed_to=" + id + "&verify_code=" + verifyCode);
} catch (Exception exception) {
throw new RuntimeException(
"Error while creating uri. Id = " + id + ", verify_code = " + verifyCode,
exception);
}
}
@Override
public void onOpen(ServerHandshake handshakedata) {
setConnectionLostTimeout(11);
}
@Override
public void onMessage(String data) {
JsonObject message = gson.fromJson(data, JsonObject.class);
if (message.has("error")) {
logger.error("Skin uploader got an error: {}", message.get("error").getAsString());
}
int typeId = message.get("event_id").getAsInt();
WebsocketEventType type = WebsocketEventType.getById(typeId);
if (type == null) {
logger.warn("Got unknown type {}. Ensure that Floodgate is up-to-date", typeId);
return;
}
if (type == WebsocketEventType.SUBSCRIBERS_COUNT) {
subscribersCount = message.get("subscribers_count").getAsInt();
} else if (type == WebsocketEventType.SKIN_UPLOADED) {
String xuid = message.get("xuid").getAsString();
FloodgatePlayer player = api.getPlayer(Utils.getJavaUuid(xuid));
if (player != null) {
applier.applySkin(player, message);
}
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
if (reason != null && !reason.isEmpty()) {
JsonObject message = gson.fromJson(reason, JsonObject.class);
// info means that the uploader itself did nothing wrong
if (message.has("info")) {
String info = message.get("info").getAsString();
logger.debug("Got disconnected from the skin uploader: {}", info);
}
// error means that the uploader did something wrong
if (message.has("error")) {
String error = message.get("error").getAsString();
logger.info("Got disconnected from the skin uploader: {}", error);
}
}
uploadManager.removeConnection(id, this);
}
@Override
public void onError(Exception exception) {
logger.error("Got an error", exception);
}
}

View File

@@ -1,159 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.skin;
import com.google.gson.JsonObject;
import java.awt.image.BufferedImage;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.annotation.Nonnull;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.geysermc.floodgate.util.HttpUtils;
import org.geysermc.floodgate.util.HttpUtils.HttpResponse;
import org.geysermc.floodgate.util.RawSkin;
public final class SkinUploader {
private static final String UPLOAD_URL = "https://api.mineskin.org/generate/upload";
private static final int MAX_TRIES = 3;
private final Executor requestExecutor = Executors.newSingleThreadExecutor();
private long nextResult;
public CompletableFuture<UploadResult> uploadSkin(@Nonnull RawSkin rawSkin) {
return CompletableFuture.supplyAsync(() -> uploadSkinInner(rawSkin, 0), requestExecutor);
}
private UploadResult uploadSkinInner(RawSkin rawSkin, int times) {
if (System.currentTimeMillis() < nextResult) {
try {
Thread.sleep(nextResult - System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
SkinModel model = rawSkin.alex ? SkinModel.ALEX : SkinModel.STEVE;
String url = UPLOAD_URL + getUploadUrlParameters(model);
BufferedImage image = SkinUtils.toBufferedImage(rawSkin);
try {
UploadResult result = parseAndHandleResponse(HttpUtils.post(url, image));
if (result.getHttpCode() == 429) {
if (times + 1 >= MAX_TRIES) {
return result;
}
uploadSkinInner(rawSkin, times + 1);
}
return result;
} catch (RuntimeException exception) {
return UploadResult.exception(exception);
}
}
private String getUploadUrlParameters(SkinModel model) {
return "?visibility=1&model=" + model.getName();
}
private UploadResult parseAndHandleResponse(HttpResponse response) {
int httpCode = response.getHttpCode();
JsonObject jsonResponse = response.getResponse();
if (jsonResponse == null) {
throw new IllegalStateException("Response cannot be null!");
}
nextResult = jsonResponse.get("nextRequest").getAsLong();
if (httpCode >= 200 && httpCode < 300) {
return UploadResult.success(httpCode, jsonResponse);
} else {
return UploadResult.failed(httpCode, jsonResponse.get("error").getAsString());
}
}
public enum SkinModel {
STEVE, ALEX;
public static final SkinModel[] VALUES = values();
@Getter private final String name = name().toLowerCase(Locale.ROOT);
public static SkinModel getByOrdinal(int ordinal) {
return VALUES.length > ordinal ? VALUES[ordinal] : STEVE;
}
public static SkinModel getByName(String name) {
return "alex".equalsIgnoreCase(name) ? ALEX : STEVE;
}
}
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static final class UploadResult {
private final int httpCode;
private final String error;
private final boolean exception;
private final SkinModel model;
private final String skinUrl;
private final String capeUrl;
private final JsonObject response;
public static UploadResult exception(Throwable throwable) {
return new UploadResult(-1, throwable.getMessage(), true, null, null, null, null);
}
public static UploadResult failed(int httpCode, String error) {
return new UploadResult(httpCode, error, false, SkinModel.STEVE, null, null, null);
}
public static UploadResult success(int httpCode, JsonObject body) {
SkinModel model = SkinModel.getByName(body.get("model").getAsString());
JsonObject data = body.getAsJsonObject("data");
JsonObject textureData = data.getAsJsonObject("texture");
JsonObject urls = textureData.getAsJsonObject("urls");
String skinUrl = urls.get("skin").getAsString();
String capeUrl = urls.has("cape") ? urls.get("cape").getAsString() : null;
JsonObject response = new JsonObject();
response.addProperty("value", textureData.get("value").getAsString());
response.addProperty("signature", textureData.get("signature").getAsString());
return new UploadResult(httpCode, null, false, model, skinUrl, capeUrl, response);
}
public static UploadResult success(JsonObject response) {
return new UploadResult(200, null, false, null, null, null, response);
}
}
}

View File

@@ -1,65 +0,0 @@
/*
* Copyright (c) 2019-2021 GeyserMC. http://geysermc.org
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author GeyserMC
* @link https://github.com/GeyserMC/Floodgate
*/
package org.geysermc.floodgate.skin;
import java.awt.image.BufferedImage;
import org.geysermc.floodgate.util.RawSkin;
public class SkinUtils {
/**
* Get the ARGB int for a given index in some image data
*
* @param index Index to get
* @param data Image data to find in
* @return An int representing ARGB
*/
private static int getARGB(int index, byte[] data) {
return (data[index + 3] & 0xFF) << 24 | (data[index] & 0xFF) << 16 |
(data[index + 1] & 0xFF) << 8 | (data[index + 2] & 0xFF);
}
/**
* Convert a byte[] to a BufferedImage
*
* @param imageData The byte[] to convert
* @param width The width of the target image
* @param height The height of the target image
* @return The converted BufferedImage
*/
public static BufferedImage toBufferedImage(byte[] imageData, int width, int height) {
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
image.setRGB(x, y, getARGB((y * width + x) * 4, imageData));
}
}
return image;
}
public static BufferedImage toBufferedImage(RawSkin rawSkin) {
return toBufferedImage(rawSkin.data, rawSkin.width, rawSkin.height);
}
}

View File

@@ -28,6 +28,7 @@ package org.geysermc.floodgate.util;
public final class Constants { public final class Constants {
public static final String DATABASE_NAME_FORMAT = "^floodgate-[a-zA-Z0-9_]{0,16}-database.jar$"; public static final String DATABASE_NAME_FORMAT = "^floodgate-[a-zA-Z0-9_]{0,16}-database.jar$";
public static final int LOGIN_SUCCESS_PACKET_ID = 2; public static final int LOGIN_SUCCESS_PACKET_ID = 2;
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;
} }

View File

@@ -11,10 +11,6 @@ username-prefix: "*"
# Should spaces be replaced with '_' in bedrock usernames? # Should spaces be replaced with '_' in bedrock usernames?
replace-spaces: true replace-spaces: true
# Should Floodgate apply the transferred Bedrock to Java skin directly to all Java players?
# This might cause some
apply-skin-directly: true
# The default locale for Floodgate. By default, Floodgate uses the system locale # The default locale for Floodgate. By default, Floodgate uses the system locale
# default-locale: en_US # default-locale: en_US

View File

@@ -36,13 +36,11 @@ import org.geysermc.floodgate.api.SimpleFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.player.FloodgatePlayerImpl; import org.geysermc.floodgate.player.FloodgatePlayerImpl;
import org.geysermc.floodgate.skin.SkinHandler;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.SpigotCommandUtil; import org.geysermc.floodgate.util.SpigotCommandUtil;
public final class SpigotListener implements Listener { public final class SpigotListener implements Listener {
@Inject private SimpleFloodgateApi api; @Inject private SimpleFloodgateApi api;
@Inject private SkinHandler skinHandler;
@Inject private LanguageManager languageManager; @Inject private LanguageManager languageManager;
@Inject private FloodgateLogger logger; @Inject private FloodgateLogger logger;
@@ -62,7 +60,6 @@ public final class SpigotListener implements Listener {
"floodgate.ingame.login_name", "floodgate.ingame.login_name",
player.getCorrectUsername(), player.getCorrectUniqueId() player.getCorrectUsername(), player.getCorrectUniqueId()
); );
skinHandler.handleServerSkinUpload(player);
languageManager.loadLocale(player.getLanguageCode()); languageManager.loadLocale(player.getLanguageCode());
} }
} }

View File

@@ -35,7 +35,6 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.SpigotPlugin; import org.geysermc.floodgate.SpigotPlugin;
import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.inject.CommonPlatformInjector; import org.geysermc.floodgate.inject.CommonPlatformInjector;
import org.geysermc.floodgate.inject.spigot.SpigotInjector; import org.geysermc.floodgate.inject.spigot.SpigotInjector;
import org.geysermc.floodgate.listener.SpigotListenerRegistration; import org.geysermc.floodgate.listener.SpigotListenerRegistration;
@@ -141,10 +140,8 @@ public final class SpigotPlatformModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
public SkinApplier skinApplier( public SkinApplier skinApplier(SpigotVersionSpecificMethods versionSpecificMethods) {
SpigotVersionSpecificMethods versionSpecificMethods, return new SpigotSkinApplier(versionSpecificMethods, plugin);
FloodgateConfigHolder configHolder) {
return new SpigotSkinApplier(versionSpecificMethods, plugin, configHolder);
} }
@Provides @Provides

View File

@@ -43,6 +43,8 @@ public class SpigotPluginMessageRegistration implements PluginMessageRegistratio
(channel1, player, message) -> (channel1, player, message) ->
channel.handleServerCall(message, player.getUniqueId(), player.getName())); channel.handleServerCall(message, player.getUniqueId(), player.getName()));
//todo actually do something with the result, lol
messenger.registerOutgoingPluginChannel(plugin, channel.getIdentifier()); messenger.registerOutgoingPluginChannel(plugin, channel.getIdentifier());
} }
} }

View File

@@ -34,9 +34,7 @@ import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.geysermc.floodgate.SpigotPlugin; import org.geysermc.floodgate.SpigotPlugin;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.config.FloodgateConfigHolder;
import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
import org.geysermc.floodgate.util.ReflectionUtils; import org.geysermc.floodgate.util.ReflectionUtils;
import org.geysermc.floodgate.util.SpigotVersionSpecificMethods; import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
@@ -52,19 +50,16 @@ public final class SpigotSkinApplier implements SkinApplier {
private final SpigotVersionSpecificMethods versionSpecificMethods; private final SpigotVersionSpecificMethods versionSpecificMethods;
private final SpigotPlugin plugin; private final SpigotPlugin plugin;
private final FloodgateConfigHolder configHolder;
public SpigotSkinApplier( public SpigotSkinApplier(
SpigotVersionSpecificMethods versionSpecificMethods, SpigotVersionSpecificMethods versionSpecificMethods,
SpigotPlugin plugin, SpigotPlugin plugin) {
FloodgateConfigHolder configHolder) {
this.versionSpecificMethods = versionSpecificMethods; this.versionSpecificMethods = versionSpecificMethods;
this.plugin = plugin; this.plugin = plugin;
this.configHolder = configHolder;
} }
@Override @Override
public void applySkin(FloodgatePlayer floodgatePlayer, UploadResult result) { public void applySkin(FloodgatePlayer floodgatePlayer, JsonObject skinResult) {
Player player = Bukkit.getPlayer(floodgatePlayer.getCorrectUniqueId()); Player player = Bukkit.getPlayer(floodgatePlayer.getCorrectUniqueId());
GameProfile profile = ReflectionUtils.castedInvoke(player, GET_PROFILE_METHOD); GameProfile profile = ReflectionUtils.castedInvoke(player, GET_PROFILE_METHOD);
@@ -72,28 +67,24 @@ public final class SpigotSkinApplier implements SkinApplier {
throw new IllegalStateException("The GameProfile cannot be null! " + player.getName()); throw new IllegalStateException("The GameProfile cannot be null! " + player.getName());
} }
JsonObject response = result.getResponse();
PropertyMap properties = profile.getProperties(); PropertyMap properties = profile.getProperties();
//todo check if removing all texture properties breaks some stuff //todo check if removing all texture properties breaks some stuff
properties.removeAll("textures"); properties.removeAll("textures");
Property property = new Property( Property property = new Property(
"textures", "textures",
response.get("value").getAsString(), skinResult.get("value").getAsString(),
response.get("signature").getAsString()); skinResult.get("signature").getAsString());
properties.put("textures", property); properties.put("textures", property);
if (configHolder.get().isApplySkinDirectly()) { // By running as a task, we don't run into async issues
// By running as a task, we don't run into async issues plugin.getServer().getScheduler().runTask(plugin, () -> {
plugin.getServer().getScheduler().runTask(plugin, () -> { for (Player p : Bukkit.getOnlinePlayers()) {
for (Player p : Bukkit.getOnlinePlayers()) { if (!p.equals(player) && p.canSee(player)) {
if (!p.equals(player) && p.canSee(player)) { versionSpecificMethods.hidePlayer(p, player);
versionSpecificMethods.hidePlayer(p, player); versionSpecificMethods.showPlayer(p, player);
versionSpecificMethods.showPlayer(p, player);
}
} }
}); }
} });
} }
} }

View File

@@ -40,7 +40,6 @@ import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.event.connection.LoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent; import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.player.GameProfileRequestEvent; import com.velocitypowered.api.event.player.GameProfileRequestEvent;
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.util.GameProfile; import com.velocitypowered.api.util.GameProfile;
import io.netty.channel.Channel; import io.netty.channel.Channel;
@@ -52,11 +51,6 @@ import net.kyori.adventure.text.Component;
import org.geysermc.floodgate.api.ProxyFloodgateApi; import org.geysermc.floodgate.api.ProxyFloodgateApi;
import org.geysermc.floodgate.api.logger.FloodgateLogger; import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.api.player.PropertyKey;
import org.geysermc.floodgate.config.ProxyFloodgateConfig;
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
import org.geysermc.floodgate.pluginmessage.channel.SkinChannel;
import org.geysermc.floodgate.skin.SkinHandler;
import org.geysermc.floodgate.util.LanguageManager; import org.geysermc.floodgate.util.LanguageManager;
import org.geysermc.floodgate.util.VelocityCommandUtil; import org.geysermc.floodgate.util.VelocityCommandUtil;
@@ -82,10 +76,6 @@ public final class VelocityListener {
@Inject private LanguageManager languageManager; @Inject private LanguageManager languageManager;
@Inject private FloodgateLogger logger; @Inject private FloodgateLogger logger;
@Inject private ProxyFloodgateConfig config;
@Inject private PluginMessageManager pluginMessageManager;
@Inject private SkinHandler skinHandler;
@Inject @Inject
@Named("playerAttribute") @Named("playerAttribute")
private AttributeKey<FloodgatePlayer> playerAttribute; private AttributeKey<FloodgatePlayer> playerAttribute;
@@ -142,27 +132,6 @@ public final class VelocityListener {
} }
} }
@Subscribe
public void onServerPostConnect(ServerPostConnectEvent event) {
FloodgatePlayer player = api.getPlayer(event.getPlayer().getUniqueId());
if (player == null) {
return;
}
// only ask for skin upload if it hasn't been uploaded already
if (player.hasProperty(PropertyKey.SKIN_UPLOADED)) {
return;
}
// send skin request to server if data forwarding allows that
if (config.isSendFloodgateData()) {
pluginMessageManager.getChannel(SkinChannel.class)
.sendSkinRequest(player.getCorrectUniqueId(), player.getRawSkin());
} else {
skinHandler.handleSkinUploadFor(player, null);
}
}
@Subscribe(order = PostOrder.LAST) @Subscribe(order = PostOrder.LAST)
public void onDisconnect(DisconnectEvent event) { public void onDisconnect(DisconnectEvent event) {
VelocityCommandUtil.AUDIENCE_CACHE.remove(event.getPlayer().getUniqueId()); //todo VelocityCommandUtil.AUDIENCE_CACHE.remove(event.getPlayer().getUniqueId()); //todo

View File

@@ -33,22 +33,19 @@ import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.skin.SkinApplier; import org.geysermc.floodgate.skin.SkinApplier;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
@RequiredArgsConstructor @RequiredArgsConstructor
public class VelocitySkinApplier implements SkinApplier { public class VelocitySkinApplier implements SkinApplier {
private final ProxyServer server; private final ProxyServer server;
@Override @Override
public void applySkin(FloodgatePlayer floodgatePlayer, UploadResult result) { public void applySkin(FloodgatePlayer floodgatePlayer, JsonObject skinResult) {
server.getPlayer(floodgatePlayer.getCorrectUniqueId()).ifPresent(player -> { server.getPlayer(floodgatePlayer.getCorrectUniqueId()).ifPresent(player -> {
JsonObject response = result.getResponse();
List<Property> properties = new ArrayList<>(player.getGameProfileProperties()); List<Property> properties = new ArrayList<>(player.getGameProfileProperties());
properties.add(new Property( properties.add(new Property(
"textures", "textures",
response.get("value").getAsString(), skinResult.get("value").getAsString(),
response.get("signature").getAsString() skinResult.get("signature").getAsString()
)); ));
player.setGameProfileProperties(properties); player.setGameProfileProperties(properties);
}); });