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:
@@ -29,7 +29,6 @@ import io.netty.channel.Channel;
|
||||
import java.util.UUID;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
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>
|
||||
@@ -76,18 +75,6 @@ public interface HandshakeData {
|
||||
*/
|
||||
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
|
||||
* removed the data.
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.util.DeviceOs;
|
||||
import org.geysermc.floodgate.util.InputMode;
|
||||
import org.geysermc.floodgate.util.LinkedPlayer;
|
||||
import org.geysermc.floodgate.util.RawSkin;
|
||||
import org.geysermc.floodgate.util.UiProfile;
|
||||
|
||||
public interface FloodgatePlayer {
|
||||
@@ -107,11 +106,6 @@ public interface FloodgatePlayer {
|
||||
*/
|
||||
LinkedPlayer getLinkedPlayer();
|
||||
|
||||
/**
|
||||
* Returns the raw skin of the Bedrock player
|
||||
*/
|
||||
RawSkin getRawSkin();
|
||||
|
||||
default boolean sendForm(Form form) {
|
||||
return FloodgateApi.getInstance().sendForm(getCorrectUniqueId(), form);
|
||||
}
|
||||
|
||||
@@ -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.PlayerDisconnectEvent;
|
||||
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.connection.InitialHandler;
|
||||
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.logger.FloodgateLogger;
|
||||
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.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.LanguageManager;
|
||||
import org.geysermc.floodgate.util.ReflectionUtils;
|
||||
@@ -74,10 +68,6 @@ public final class BungeeListener implements Listener {
|
||||
@Inject private LanguageManager languageManager;
|
||||
@Inject private FloodgateLogger logger;
|
||||
|
||||
@Inject private ProxyFloodgateConfig config;
|
||||
@Inject private PluginMessageManager pluginMessageManager;
|
||||
@Inject private SkinHandler skinHandler;
|
||||
|
||||
@Inject
|
||||
@Named("playerAttribute")
|
||||
private AttributeKey<FloodgatePlayer> playerAttribute;
|
||||
@@ -86,27 +76,6 @@ public final class BungeeListener implements Listener {
|
||||
@Named("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)
|
||||
public void onPreLogin(PreLoginEvent event) {
|
||||
// well, no reason to check if the player will be kicked anyway
|
||||
|
||||
@@ -35,14 +35,13 @@ import net.md_5.bungee.connection.LoginResult.Property;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.skin.SkinApplier;
|
||||
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class BungeeSkinApplier implements SkinApplier {
|
||||
private final FloodgateLogger logger;
|
||||
|
||||
@Override
|
||||
public void applySkin(FloodgatePlayer uuid, UploadResult result) {
|
||||
public void applySkin(FloodgatePlayer uuid, JsonObject skinResult) {
|
||||
ProxiedPlayer player = ProxyServer.getInstance().getPlayer(uuid.getCorrectUniqueId());
|
||||
|
||||
InitialHandler handler;
|
||||
@@ -61,11 +60,10 @@ public final class BungeeSkinApplier implements SkinApplier {
|
||||
loginResult = new LoginResult(null, null, null);
|
||||
}
|
||||
|
||||
JsonObject response = result.getResponse();
|
||||
Property property = new Property(
|
||||
"textures",
|
||||
response.get("value").getAsString(),
|
||||
response.get("signature").getAsString()
|
||||
skinResult.get("value").getAsString(),
|
||||
skinResult.get("signature").getAsString()
|
||||
);
|
||||
|
||||
loginResult.setProperties(new Property[]{property});
|
||||
|
||||
@@ -35,6 +35,16 @@
|
||||
<artifactId>fastutil-short-object-maps</artifactId>
|
||||
<version>8.3.1</version>
|
||||
</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>
|
||||
<groupId>org.geysermc.floodgate</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
|
||||
@@ -33,7 +33,6 @@ import org.geysermc.floodgate.api.handshake.HandshakeData;
|
||||
import org.geysermc.floodgate.config.FloodgateConfig;
|
||||
import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.floodgate.util.LinkedPlayer;
|
||||
import org.geysermc.floodgate.util.RawSkin;
|
||||
import org.geysermc.floodgate.util.Utils;
|
||||
|
||||
@Getter
|
||||
@@ -45,7 +44,6 @@ public class HandshakeDataImpl implements HandshakeData {
|
||||
private final UUID javaUniqueId;
|
||||
|
||||
@Setter private LinkedPlayer linkedPlayer;
|
||||
@Setter private RawSkin rawSkin;
|
||||
@Setter private String hostname;
|
||||
@Setter private String bedrockIp;
|
||||
@Setter private String disconnectReason;
|
||||
@@ -56,14 +54,12 @@ public class HandshakeDataImpl implements HandshakeData {
|
||||
BedrockData bedrockData,
|
||||
FloodgateConfig config,
|
||||
LinkedPlayer linkedPlayer,
|
||||
RawSkin rawSkin,
|
||||
String hostname) {
|
||||
|
||||
this.channel = channel;
|
||||
this.floodgatePlayer = floodgatePlayer;
|
||||
this.bedrockData = bedrockData;
|
||||
this.linkedPlayer = linkedPlayer;
|
||||
this.rawSkin = rawSkin;
|
||||
this.hostname = hostname;
|
||||
|
||||
String javaUsername = null;
|
||||
|
||||
@@ -37,7 +37,6 @@ public class FloodgateConfig {
|
||||
private String keyFileName;
|
||||
private String usernamePrefix;
|
||||
private boolean replaceSpaces;
|
||||
private boolean applySkinDirectly; //todo how is this possible for proxies?
|
||||
|
||||
private String defaultLocale;
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ import org.geysermc.floodgate.inject.CommonPlatformInjector;
|
||||
import org.geysermc.floodgate.player.FloodgateHandshakeHandler;
|
||||
import org.geysermc.floodgate.pluginmessage.PluginMessageManager;
|
||||
import org.geysermc.floodgate.skin.SkinApplier;
|
||||
import org.geysermc.floodgate.skin.SkinHandler;
|
||||
import org.geysermc.floodgate.skin.SkinUploadManager;
|
||||
import org.geysermc.floodgate.util.LanguageManager;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@@ -141,16 +141,12 @@ public class CommonModule extends AbstractModule {
|
||||
SimpleFloodgateApi api,
|
||||
FloodgateCipher cipher,
|
||||
FloodgateConfigHolder configHolder,
|
||||
SkinUploadManager skinUploadManager,
|
||||
@Named("playerAttribute") AttributeKey<FloodgatePlayer> playerAttribute,
|
||||
FloodgateLogger logger) {
|
||||
return new FloodgateHandshakeHandler(
|
||||
handshakeHandlers,
|
||||
api,
|
||||
cipher,
|
||||
configHolder,
|
||||
playerAttribute,
|
||||
logger
|
||||
);
|
||||
|
||||
return new FloodgateHandshakeHandler(handshakeHandlers, api, cipher, configHolder,
|
||||
skinUploadManager, playerAttribute, logger);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@@ -161,11 +157,11 @@ public class CommonModule extends AbstractModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public SkinHandler skinHandler(
|
||||
PluginMessageManager pluginMessageManager,
|
||||
public SkinUploadManager skinUploadManager(
|
||||
FloodgateApi api,
|
||||
SkinApplier skinApplier,
|
||||
FloodgateLogger logger) {
|
||||
return new SkinHandler(pluginMessageManager, skinApplier, logger);
|
||||
return new SkinUploadManager(api, skinApplier, logger);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -46,13 +46,11 @@ 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.config.FloodgateConfigHolder;
|
||||
import org.geysermc.floodgate.crypto.AesCipher;
|
||||
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.InvalidFormatException;
|
||||
import org.geysermc.floodgate.util.LinkedPlayer;
|
||||
import org.geysermc.floodgate.util.RawSkin;
|
||||
import org.geysermc.floodgate.util.Utils;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@@ -61,6 +59,7 @@ public final class FloodgateHandshakeHandler {
|
||||
private final SimpleFloodgateApi api;
|
||||
private final FloodgateCipher cipher;
|
||||
private final FloodgateConfigHolder configHolder;
|
||||
private final SkinUploadManager skinUploadManager;
|
||||
private final AttributeKey<FloodgatePlayer> playerAttribute;
|
||||
private final FloodgateLogger logger;
|
||||
|
||||
@@ -85,22 +84,8 @@ public final class FloodgateHandshakeHandler {
|
||||
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 {
|
||||
byte[] floodgateData;
|
||||
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);
|
||||
}
|
||||
byte[] floodgateData = data.getBytes(Charsets.UTF_8);
|
||||
|
||||
// actual decryption
|
||||
String decrypted = cipher.decryptToString(floodgateData);
|
||||
@@ -112,14 +97,6 @@ public final class FloodgateHandshakeHandler {
|
||||
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;
|
||||
|
||||
// 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(
|
||||
channel, true, bedrockData.clone(), configHolder.get(),
|
||||
linkedPlayer != null ? linkedPlayer.clone() : null, rawSkin, hostname);
|
||||
linkedPlayer != null ? linkedPlayer.clone() : null, hostname);
|
||||
handshakeHandlers.callHandshakeHandlers(handshakeData);
|
||||
|
||||
if (!handshakeData.shouldDisconnect()) {
|
||||
skinUploadManager.addConnectionIfNeeded(bedrockData.getSubscribeId(),
|
||||
bedrockData.getVerifyCode());
|
||||
}
|
||||
|
||||
UUID javaUuid = Utils.getJavaUuid(bedrockData.getXuid());
|
||||
handshakeData.setHostname(correctHostname(
|
||||
handshakeData.getHostname(), bedrockData, javaUuid
|
||||
@@ -149,7 +131,8 @@ public final class FloodgateHandshakeHandler {
|
||||
channel.attr(playerAttribute).set(player);
|
||||
|
||||
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);
|
||||
|
||||
return new HandshakeResult(ResultType.SUCCESS, handshakeData, bedrockData, player);
|
||||
@@ -185,7 +168,7 @@ public final class FloodgateHandshakeHandler {
|
||||
String hostname) {
|
||||
|
||||
HandshakeData handshakeData = new HandshakeDataImpl(channel, bedrockData != null,
|
||||
bedrockData, configHolder.get(), null, null, hostname);
|
||||
bedrockData, configHolder.get(), null, hostname);
|
||||
handshakeHandlers.callHandshakeHandlers(handshakeData);
|
||||
|
||||
if (bedrockData != null) {
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.geysermc.floodgate.util.BedrockData;
|
||||
import org.geysermc.floodgate.util.DeviceOs;
|
||||
import org.geysermc.floodgate.util.InputMode;
|
||||
import org.geysermc.floodgate.util.LinkedPlayer;
|
||||
import org.geysermc.floodgate.util.RawSkin;
|
||||
import org.geysermc.floodgate.util.UiProfile;
|
||||
import org.geysermc.floodgate.util.Utils;
|
||||
|
||||
@@ -67,7 +66,9 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
|
||||
private final boolean fromProxy;
|
||||
private final boolean proxy; // if current platform is a proxy
|
||||
private final LinkedPlayer linkedPlayer;
|
||||
private final RawSkin rawSkin;
|
||||
|
||||
private final int subscribeId;
|
||||
private final String verifyCode;
|
||||
|
||||
@Getter(AccessLevel.PRIVATE)
|
||||
public Map<PropertyKey, Object> propertyKeyToValue;
|
||||
@@ -92,16 +93,14 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
|
||||
InputMode inputMode = InputMode.getById(data.getInputMode());
|
||||
|
||||
LinkedPlayer linkedPlayer = handshakeData.getLinkedPlayer();
|
||||
RawSkin skin = handshakeData.getRawSkin();
|
||||
|
||||
FloodgatePlayerImpl player = new FloodgatePlayerImpl(
|
||||
data.getVersion(), data.getUsername(), handshakeData.getJavaUsername(),
|
||||
javaUniqueId, data.getXuid(), deviceOs, data.getLanguageCode(), uiProfile,
|
||||
inputMode, data.getIp(), data.isFromProxy(), api instanceof ProxyFloodgateApi,
|
||||
linkedPlayer, skin);
|
||||
linkedPlayer, data.getSubscribeId(), data.getVerifyCode());
|
||||
|
||||
// RawSkin should be removed, fromProxy should be changed
|
||||
// and encrypted data can be changed after fetching the linkedPlayer
|
||||
// fromProxy and linked player might have to be changed
|
||||
if (api instanceof ProxyFloodgateApi) {
|
||||
InstanceHolder.castApi(ProxyFloodgateApi.class)
|
||||
.updateEncryptedData(player.getCorrectUniqueId(), player.toBedrockData());
|
||||
@@ -155,9 +154,9 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
|
||||
}
|
||||
|
||||
public BedrockData toBedrockData() {
|
||||
return BedrockData.of(
|
||||
version, username, xuid, deviceOs.ordinal(), languageCode,
|
||||
uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer, proxy);
|
||||
return BedrockData.of(version, username, xuid, deviceOs.ordinal(), languageCode,
|
||||
uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer, proxy, subscribeId,
|
||||
verifyCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -25,36 +25,22 @@
|
||||
|
||||
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.JsonSyntaxException;
|
||||
import com.google.inject.Inject;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
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.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.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 {
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
@Inject private FloodgateApi api;
|
||||
@Inject private PluginMessageUtils pluginMessageUtils;
|
||||
@Inject private SkinHandler skinHandler;
|
||||
@Inject private FloodgateConfig config;
|
||||
@Inject private SkinApplier skinApplier;
|
||||
@Inject private FloodgateLogger logger;
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
@@ -71,58 +57,21 @@ public class SkinChannel implements PluginMessageChannel {
|
||||
String sourceUsername,
|
||||
Identity sourceIdentity) {
|
||||
|
||||
if (data.length < 1) {
|
||||
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
|
||||
// we can only get skins from Geyser (client)
|
||||
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();
|
||||
}
|
||||
@@ -131,75 +80,27 @@ public class SkinChannel implements PluginMessageChannel {
|
||||
public Result handleServerCall(byte[] data, UUID targetUuid, String targetUsername) {
|
||||
FloodgatePlayer floodgatePlayer = api.getPlayer(targetUuid);
|
||||
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()) {
|
||||
return Result.kick("Cannot receive Skin request from Player");
|
||||
String[] split = message.split("\0");
|
||||
// value and signature
|
||||
if (split.length != 2) {
|
||||
return Result.kick("Got invalid skin data");
|
||||
}
|
||||
|
||||
// 1 byte for isRequest and 9 for RawSkin itself
|
||||
if (data.length < Base64Utils.getEncodedLength(9 + 1)) {
|
||||
return Result.kick("Skin request data has to be at least 10 byte long.");
|
||||
}
|
||||
String value = split[0];
|
||||
String signature = split[1];
|
||||
|
||||
boolean request = data[0] == 1;
|
||||
JsonObject result = new JsonObject();
|
||||
result.addProperty("value", value);
|
||||
result.addProperty("signature", signature);
|
||||
|
||||
if (!request) {
|
||||
return Result.kick("Proxy sent a response instead of a request?");
|
||||
}
|
||||
|
||||
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);
|
||||
floodgatePlayer.addProperty(PropertyKey.SKIN_UPLOADED, result);
|
||||
skinApplier.applySkin(floodgatePlayer, result);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
|
||||
package org.geysermc.floodgate.skin;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
|
||||
|
||||
public interface SkinApplier {
|
||||
void applySkin(FloodgatePlayer floodgatePlayer, UploadResult result);
|
||||
void applySkin(FloodgatePlayer floodgatePlayer, JsonObject skinResult);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ package org.geysermc.floodgate.util;
|
||||
public final class Constants {
|
||||
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 String WEBSOCKET_URL = "wss://api.geysermc.org/ws";
|
||||
|
||||
public static final boolean DEBUG_MODE = true;
|
||||
}
|
||||
|
||||
@@ -11,10 +11,6 @@ username-prefix: "*"
|
||||
# Should spaces be replaced with '_' in bedrock usernames?
|
||||
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
|
||||
# default-locale: en_US
|
||||
|
||||
|
||||
@@ -36,13 +36,11 @@ import org.geysermc.floodgate.api.SimpleFloodgateApi;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.player.FloodgatePlayerImpl;
|
||||
import org.geysermc.floodgate.skin.SkinHandler;
|
||||
import org.geysermc.floodgate.util.LanguageManager;
|
||||
import org.geysermc.floodgate.util.SpigotCommandUtil;
|
||||
|
||||
public final class SpigotListener implements Listener {
|
||||
@Inject private SimpleFloodgateApi api;
|
||||
@Inject private SkinHandler skinHandler;
|
||||
@Inject private LanguageManager languageManager;
|
||||
@Inject private FloodgateLogger logger;
|
||||
|
||||
@@ -62,7 +60,6 @@ public final class SpigotListener implements Listener {
|
||||
"floodgate.ingame.login_name",
|
||||
player.getCorrectUsername(), player.getCorrectUniqueId()
|
||||
);
|
||||
skinHandler.handleServerSkinUpload(player);
|
||||
languageManager.loadLocale(player.getLanguageCode());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.geysermc.floodgate.SpigotPlugin;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
import org.geysermc.floodgate.api.logger.FloodgateLogger;
|
||||
import org.geysermc.floodgate.config.FloodgateConfigHolder;
|
||||
import org.geysermc.floodgate.inject.CommonPlatformInjector;
|
||||
import org.geysermc.floodgate.inject.spigot.SpigotInjector;
|
||||
import org.geysermc.floodgate.listener.SpigotListenerRegistration;
|
||||
@@ -141,10 +140,8 @@ public final class SpigotPlatformModule extends AbstractModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
public SkinApplier skinApplier(
|
||||
SpigotVersionSpecificMethods versionSpecificMethods,
|
||||
FloodgateConfigHolder configHolder) {
|
||||
return new SpigotSkinApplier(versionSpecificMethods, plugin, configHolder);
|
||||
public SkinApplier skinApplier(SpigotVersionSpecificMethods versionSpecificMethods) {
|
||||
return new SpigotSkinApplier(versionSpecificMethods, plugin);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -43,6 +43,8 @@ public class SpigotPluginMessageRegistration implements PluginMessageRegistratio
|
||||
(channel1, player, message) ->
|
||||
channel.handleServerCall(message, player.getUniqueId(), player.getName()));
|
||||
|
||||
//todo actually do something with the result, lol
|
||||
|
||||
messenger.registerOutgoingPluginChannel(plugin, channel.getIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,7 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.geysermc.floodgate.SpigotPlugin;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.config.FloodgateConfigHolder;
|
||||
import org.geysermc.floodgate.skin.SkinApplier;
|
||||
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
|
||||
import org.geysermc.floodgate.util.ReflectionUtils;
|
||||
import org.geysermc.floodgate.util.SpigotVersionSpecificMethods;
|
||||
|
||||
@@ -52,19 +50,16 @@ public final class SpigotSkinApplier implements SkinApplier {
|
||||
|
||||
private final SpigotVersionSpecificMethods versionSpecificMethods;
|
||||
private final SpigotPlugin plugin;
|
||||
private final FloodgateConfigHolder configHolder;
|
||||
|
||||
public SpigotSkinApplier(
|
||||
SpigotVersionSpecificMethods versionSpecificMethods,
|
||||
SpigotPlugin plugin,
|
||||
FloodgateConfigHolder configHolder) {
|
||||
SpigotPlugin plugin) {
|
||||
this.versionSpecificMethods = versionSpecificMethods;
|
||||
this.plugin = plugin;
|
||||
this.configHolder = configHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applySkin(FloodgatePlayer floodgatePlayer, UploadResult result) {
|
||||
public void applySkin(FloodgatePlayer floodgatePlayer, JsonObject skinResult) {
|
||||
Player player = Bukkit.getPlayer(floodgatePlayer.getCorrectUniqueId());
|
||||
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());
|
||||
}
|
||||
|
||||
JsonObject response = result.getResponse();
|
||||
|
||||
PropertyMap properties = profile.getProperties();
|
||||
|
||||
//todo check if removing all texture properties breaks some stuff
|
||||
properties.removeAll("textures");
|
||||
Property property = new Property(
|
||||
"textures",
|
||||
response.get("value").getAsString(),
|
||||
response.get("signature").getAsString());
|
||||
skinResult.get("value").getAsString(),
|
||||
skinResult.get("signature").getAsString());
|
||||
properties.put("textures", property);
|
||||
|
||||
if (configHolder.get().isApplySkinDirectly()) {
|
||||
// By running as a task, we don't run into async issues
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (!p.equals(player) && p.canSee(player)) {
|
||||
versionSpecificMethods.hidePlayer(p, player);
|
||||
versionSpecificMethods.showPlayer(p, player);
|
||||
}
|
||||
// By running as a task, we don't run into async issues
|
||||
plugin.getServer().getScheduler().runTask(plugin, () -> {
|
||||
for (Player p : Bukkit.getOnlinePlayers()) {
|
||||
if (!p.equals(player) && p.canSee(player)) {
|
||||
versionSpecificMethods.hidePlayer(p, player);
|
||||
versionSpecificMethods.showPlayer(p, player);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPostConnectEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
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.logger.FloodgateLogger;
|
||||
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.VelocityCommandUtil;
|
||||
|
||||
@@ -82,10 +76,6 @@ public final class VelocityListener {
|
||||
@Inject private LanguageManager languageManager;
|
||||
@Inject private FloodgateLogger logger;
|
||||
|
||||
@Inject private ProxyFloodgateConfig config;
|
||||
@Inject private PluginMessageManager pluginMessageManager;
|
||||
@Inject private SkinHandler skinHandler;
|
||||
|
||||
@Inject
|
||||
@Named("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)
|
||||
public void onDisconnect(DisconnectEvent event) {
|
||||
VelocityCommandUtil.AUDIENCE_CACHE.remove(event.getPlayer().getUniqueId()); //todo
|
||||
|
||||
@@ -33,22 +33,19 @@ import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.geysermc.floodgate.api.player.FloodgatePlayer;
|
||||
import org.geysermc.floodgate.skin.SkinApplier;
|
||||
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class VelocitySkinApplier implements SkinApplier {
|
||||
private final ProxyServer server;
|
||||
|
||||
@Override
|
||||
public void applySkin(FloodgatePlayer floodgatePlayer, UploadResult result) {
|
||||
public void applySkin(FloodgatePlayer floodgatePlayer, JsonObject skinResult) {
|
||||
server.getPlayer(floodgatePlayer.getCorrectUniqueId()).ifPresent(player -> {
|
||||
JsonObject response = result.getResponse();
|
||||
|
||||
List<Property> properties = new ArrayList<>(player.getGameProfileProperties());
|
||||
properties.add(new Property(
|
||||
"textures",
|
||||
response.get("value").getAsString(),
|
||||
response.get("signature").getAsString()
|
||||
skinResult.get("value").getAsString(),
|
||||
skinResult.get("signature").getAsString()
|
||||
));
|
||||
player.setGameProfileProperties(properties);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user