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 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.

View File

@@ -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);
}

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.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

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.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});

View File

@@ -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>

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

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.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) {

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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);
}

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 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;
}

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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,19 +67,16 @@ 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()) {
@@ -95,5 +87,4 @@ public final class SpigotSkinApplier implements SkinApplier {
}
});
}
}
}

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.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

View File

@@ -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);
});