1
0
mirror of https://github.com/GeyserMC/Floodgate.git synced 2026-01-04 15:31:48 +00:00

Stopped using Jackson, initial version of Skins and fixed a few things

This commit is contained in:
Tim203
2020-11-21 03:15:10 +01:00
parent 9918864452
commit 2d78c3e536
30 changed files with 706 additions and 104 deletions

View File

@@ -28,7 +28,9 @@ package org.geysermc.floodgate;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.InstanceHolder;
@@ -43,6 +45,7 @@ import org.geysermc.floodgate.util.RawSkin;
import org.geysermc.floodgate.util.UiProfile;
@Getter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public final class FloodgatePlayerImpl implements FloodgatePlayer {
private final String version;
private final String username;
@@ -55,6 +58,7 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
private final UiProfile uiProfile;
private final InputMode inputMode;
private final String ip;
private final boolean fromProxy; //todo remove hasBungeeData
private final LinkedPlayer linkedPlayer;
private final RawSkin rawSkin;
@@ -63,43 +67,45 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
*/
@Setter private boolean login = true;
FloodgatePlayerImpl(BedrockData data, RawSkin skin, String prefix, boolean replaceSpaces) {
protected static FloodgatePlayerImpl from(BedrockData data, RawSkin skin,
String prefix, boolean replaceSpaces) {
FloodgateApi api = FloodgateApi.getInstance();
version = data.getVersion();
username = data.getUsername();
int usernameLength = Math.min(data.getUsername().length(), 16 - prefix.length());
String editedUsername = prefix + data.getUsername().substring(0, usernameLength);
String javaUsername = prefix + data.getUsername().substring(0, usernameLength);
if (replaceSpaces) {
editedUsername = editedUsername.replaceAll(" ", "_");
javaUsername = javaUsername.replaceAll(" ", "_");
}
javaUsername = editedUsername;
javaUniqueId = api.createJavaPlayerId(Long.parseLong(data.getXuid()));
xuid = data.getXuid();
deviceOs = DeviceOs.getById(data.getDeviceOs());
languageCode = data.getLanguageCode();
uiProfile = UiProfile.getById(data.getUiProfile());
inputMode = InputMode.getById(data.getInputMode());
ip = data.getIp();
rawSkin = skin;
UUID javaUniqueId = api.createJavaPlayerId(Long.parseLong(data.getXuid()));
DeviceOs deviceOs = DeviceOs.getById(data.getDeviceOs());
UiProfile uiProfile = UiProfile.getById(data.getUiProfile());
InputMode inputMode = InputMode.getById(data.getInputMode());
LinkedPlayer linkedPlayer;
// we'll use the LinkedPlayer provided by Bungee or Velocity (if they included one)
if (data.hasPlayerLink()) {
linkedPlayer = data.getLinkedPlayer();
return;
} else {
// every implementation (Bukkit, Bungee and Velocity) run this constructor async,
// so we should be fine doing this synchronised.
linkedPlayer = fetchLinkedPlayer(api.getPlayerLink(), javaUniqueId);
}
// every implementation (Bukkit, Bungee and Velocity) run this constructor async,
// so we should be fine doing this synchronised.
linkedPlayer = fetchLinkedPlayer(api.getPlayerLink());
FloodgatePlayerImpl player = new FloodgatePlayerImpl(
data.getVersion(), data.getUsername(), javaUsername, javaUniqueId, data.getXuid(),
deviceOs, data.getLanguageCode(), uiProfile, inputMode, data.getIp(),
data.isFromProxy(), linkedPlayer, skin);
// oh oh, now our encrypted data is incorrect. We have to update it...
// oh oh, after fetching the linkedPlayer our encrypted data is incorrect.
// We have to update it...
if (linkedPlayer != null && api instanceof ProxyFloodgateApi) {
InstanceHolder.castApi(ProxyFloodgateApi.class)
.updateEncryptedData(getCorrectUniqueId(), toBedrockData());
.updateEncryptedData(player.getCorrectUniqueId(), player.toBedrockData());
}
return player;
}
public UUID getCorrectUniqueId() {
@@ -115,9 +121,9 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
* Please note that this method loads the LinkedPlayer synchronously.
*
* @return LinkedPlayer or null if the player isn't linked or linking isn't enabled
* @see #fetchLinkedPlayerAsync(PlayerLink) for the asynchronously alternative
* @see #fetchLinkedPlayerAsync(PlayerLink, UUID) for the asynchronously alternative
*/
public LinkedPlayer fetchLinkedPlayer(PlayerLink link) {
public static LinkedPlayer fetchLinkedPlayer(PlayerLink link, UUID javaUniqueId) {
if (!link.isEnabledAndAllowed()) {
return null;
}
@@ -135,18 +141,18 @@ public final class FloodgatePlayerImpl implements FloodgatePlayer {
*
* @return a future holding the LinkedPlayer or null if the player isn't linked or when linking
* isn't enabled
* @see #fetchLinkedPlayer(PlayerLink) for the sync version
* @see #fetchLinkedPlayer(PlayerLink, UUID) for the sync version
*/
public CompletableFuture<LinkedPlayer> fetchLinkedPlayerAsync(PlayerLink link) {
public static CompletableFuture<LinkedPlayer> fetchLinkedPlayerAsync(PlayerLink link,
UUID javaUniqueId) {
return link.isEnabledAndAllowed() ?
link.getLinkedPlayer(javaUniqueId) :
CompletableFuture.completedFuture(null);
}
public BedrockData toBedrockData() {
return new BedrockData(
return BedrockData.of(
version, username, xuid, deviceOs.ordinal(), languageCode,
uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer
);
uiProfile.ordinal(), inputMode.ordinal(), ip, linkedPlayer, fromProxy);
}
}

View File

@@ -107,7 +107,7 @@ public final class HandshakeHandler {
System.out.println(rawSkin);
FloodgatePlayer player =
new FloodgatePlayerImpl(bedrockData, rawSkin, usernamePrefix, replaceSpaces);
FloodgatePlayerImpl.from(bedrockData, rawSkin, usernamePrefix, replaceSpaces);
api.addPlayer(player.getJavaUniqueId(), player);
return new HandshakeResult(ResultType.SUCCESS, dataArray, bedrockData, player);

View File

@@ -25,8 +25,6 @@
package org.geysermc.floodgate.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.security.Key;
import lombok.Getter;
@@ -36,29 +34,19 @@ import lombok.Getter;
*/
@Getter
public class FloodgateConfig {
@JsonProperty(value = "key-file-name")
private String keyFileName;
@JsonProperty(value = "username-prefix")
private String usernamePrefix;
@JsonProperty(value = "replace-spaces")
private boolean replaceSpaces;
private boolean applySkinDirectly; //todo
@JsonProperty(value = "default-locale")
private String defaultLocale;
@JsonProperty(value = "disconnect")
private DisconnectMessages messages;
@JsonProperty(value = "player-link")
private DisconnectMessages disconnect;
private PlayerLinkConfig playerLink;
@JsonProperty
private boolean debug;
private int configVersion;
@JsonProperty("config-version")
private boolean configVersion;
@JsonIgnore
private Key key = null;
public void setKey(Key key) {
@@ -73,23 +61,16 @@ public class FloodgateConfig {
@Getter
public static class DisconnectMessages {
@JsonProperty("invalid-key")
private String invalidKey;
@JsonProperty("invalid-arguments-length")
private String invalidArgumentsLength;
}
@Getter
public static class PlayerLinkConfig {
@JsonProperty("enable")
private boolean enabled;
@JsonProperty("allow-linking")
private boolean allowLinking;
@JsonProperty("link-code-timeout")
private boolean allowed;
private long linkCodeTimeout;
@JsonProperty("type")
private String type;
@JsonProperty("auto-download")
private boolean autoDownload;
}
}

View File

@@ -25,14 +25,12 @@
package org.geysermc.floodgate.config;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
/**
* The Floodgate configuration used by proxy platforms, currently Velocity and Bungeecord.
*/
@Getter
public final class ProxyFloodgateConfig extends FloodgateConfig {
@Getter
@JsonProperty(value = "send-floodgate-data")
private boolean sendFloodgateData;
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (c) 2019-2020 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.config.loader;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.LinkedHashMap;
import java.util.Map;
import org.geysermc.floodgate.config.FloodgateConfig;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.constructor.CustomClassLoaderConstructor;
import org.yaml.snakeyaml.introspector.BeanAccess;
import org.yaml.snakeyaml.introspector.FieldProperty;
import org.yaml.snakeyaml.introspector.Property;
import org.yaml.snakeyaml.introspector.PropertyUtils;
public class ConfigInitializer {
private static final Yaml YAML;
static {
Constructor constructor = new CustomClassLoaderConstructor(
ConfigInitializer.class.getClassLoader());
constructor.setPropertyUtils(new PropertyUtils() {
@Override
protected Map<String, Property> getPropertiesMap(Class<?> type, BeanAccess bAccess) {
Map<String, Property> properties = new LinkedHashMap<>();
Class<?> current = type;
// make ProxyFloodgateConfig work
while (FloodgateConfig.class.isAssignableFrom(current)) {
for (Field field : current.getDeclaredFields()) {
int modifiers = field.getModifiers();
if (!Modifier.isStatic(modifiers) && !Modifier.isTransient(modifiers)) {
String correctName = getCorrectName(field.getName());
// children should override parents
properties.putIfAbsent(correctName, new FieldProperty(field));
}
}
current = current.getSuperclass();
}
return properties;
}
private String getCorrectName(String name) {
// convert sendFloodgateData to send-floodgate-data,
// which is the style of writing config fields
StringBuilder propertyBuilder = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char current = name.charAt(i);
if (Character.isUpperCase(current)) {
propertyBuilder.append('-').append(Character.toLowerCase(current));
} else {
propertyBuilder.append(current);
}
}
return propertyBuilder.toString();
}
});
YAML = new Yaml(constructor);
}
public static <T extends FloodgateConfig> T initializeFrom(InputStream dataStream,
Class<T> configClass) {
return YAML.loadAs(dataStream, configClass);
}
}

View File

@@ -25,8 +25,6 @@
package org.geysermc.floodgate.config.loader;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
@@ -114,9 +112,8 @@ public final class ConfigLoader {
updater.update(defaultConfigPath);
}
FloodgateConfig config =
new ObjectMapper(new YAMLFactory())
.readValue(Files.readAllBytes(configPath), configClass);
FloodgateConfig config = ConfigInitializer.initializeFrom(
Files.newInputStream(configPath), configClass);
try {
configInstance = (T) config;

View File

@@ -32,6 +32,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import lombok.RequiredArgsConstructor;
@@ -57,8 +58,7 @@ public final class ConfigUpdater {
}
Map<String, Object> config = new Yaml().load(configReader);
// currently unused, but can be used when a config name has been changed
Map<String, String> renames = new HashMap<>(0);
Map<String, String> renames = new HashMap<>();
Object versionElement = config.get("config-version");
// not a pre-rewrite config
@@ -84,6 +84,8 @@ public final class ConfigUpdater {
"(across all your servers, including Geyser)." +
"We'll still try to update the config," +
"but please regenerate the keys if it failed before asking for support.");
renames.put("enabled", "enable"); //todo make dump system and add a boolean 'found-legacy-key' or something like that
renames.put("allowed", "allow-linking");
}
try {

View File

@@ -50,7 +50,7 @@ public abstract class CommonPlayerLink implements PlayerLink {
private void init(FloodgateConfig config) {
FloodgateConfig.PlayerLinkConfig linkConfig = config.getPlayerLink();
enabled = linkConfig.isEnabled();
allowLinking = linkConfig.isAllowLinking();
allowLinking = linkConfig.isAllowed();
verifyLinkTimeout = linkConfig.getLinkCodeTimeout();
}

View File

@@ -31,6 +31,7 @@ import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import org.geysermc.common.form.Form;
import org.geysermc.floodgate.util.RawSkin;
public abstract class PluginMessageHandler {
protected final Short2ObjectMap<Form> storedForms = new Short2ObjectOpenHashMap<>();
@@ -39,6 +40,10 @@ public abstract class PluginMessageHandler {
public abstract boolean sendForm(UUID player, Form form);
public abstract boolean sendSkinRequest(UUID player, RawSkin skin);
public abstract void sendSkinResponse(UUID player, String response);
protected byte[] createFormData(Form form) {
short formId = getNextFormId();
if (proxy) {

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2019-2020 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.MessageFormatter.format;
import lombok.RequiredArgsConstructor;
import org.geysermc.floodgate.api.logger.FloodgateLogger;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.geysermc.floodgate.platform.pluginmessage.PluginMessageHandler;
import org.geysermc.floodgate.skin.SkinUploader.UploadResult;
@RequiredArgsConstructor
public abstract class SkinHandler {
private final SkinUploader uploader = new SkinUploader();
private final PluginMessageHandler messageHandler;
private final FloodgateLogger logger;
public final void handleSkinUploadFor(FloodgatePlayer player) {
uploader.uploadSkin(player.getRawSkin())
.whenComplete((uploadResult, throwable) -> {
if (throwable != null) {
logger.error(
"Failed to upload player skin for " + player.getCorrectUsername(),
throwable);
messageHandler.sendSkinResponse(
player.getJavaUniqueId(), throwable.getMessage());
return;
}
if (uploadResult.getError() != null) {
logger.error(format(
"Error while uploading player skin for {}: {}",
player.getCorrectUsername(), uploadResult.getError()));
messageHandler.sendSkinResponse(
player.getJavaUniqueId(), uploadResult.getError());
return;
}
logger.info("Skin upload successful for " + player.getCorrectUsername());
logger.info(uploadResult.getResponse().toString());
messageHandler.sendSkinResponse(
player.getJavaUniqueId(), uploadResult.getResponse().toString());
});
}
protected abstract void applySkin(FloodgatePlayer player, UploadResult result);
}

View File

@@ -0,0 +1,195 @@
/*
* Copyright (c) 2019-2020 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.common.net.HttpHeaders;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;
import okio.BufferedSource;
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 String USER_AGENT = "GeyserMC/Floodgate";
private static final MediaType PNG_TYPE = MediaType.get("image/png");
private static final Gson GSON = new Gson();
private final Executor requestExecutor = Executors.newSingleThreadExecutor();
private final OkHttpClient httpClient =
new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.build();
private long nextResult = 0;
public CompletableFuture<UploadResult> uploadSkin(RawSkin rawSkin) {
return CompletableFuture.supplyAsync(
() -> {
if (System.currentTimeMillis() < nextResult) {
try {
Thread.sleep(nextResult - System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
SkinModel model = rawSkin.alex ? SkinModel.ALEX : SkinModel.STEVE;
BufferedImage image = SkinUtils.toBufferedImage(rawSkin);
// a black 32x32 png was 133 bytes, so we assume that it's at least 133 bytes
ByteArrayOutputStream stream = new ByteArrayOutputStream(133);
try {
ImageIO.write(image, "png", stream);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
RequestBody fileData = RequestBody.create(PNG_TYPE, stream.toByteArray());
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", "skin.png", fileData)
.build();
System.out.println("final file stream size: " + stream.size());
Buffer buffer = new Buffer();
try {
body.writeTo(buffer);
} catch (IOException exception) {
throw new RuntimeException(exception);
}
Request request = new Request.Builder()
.url(UPLOAD_URL + getUploadUrlParameters(model))
.header(HttpHeaders.USER_AGENT, USER_AGENT)
.post(body)
.build();
try (Response response = httpClient.newCall(request).execute()) {
if (response.body() == null) {
throw new RuntimeException("Response didn't have a body!");
}
return parseAndHandleResponse(response.code(), response.body().source());
} catch (IOException exception) {
throw new RuntimeException(exception);
}
},
requestExecutor
);
}
private String getUploadUrlParameters(SkinModel model) {
return "?visibility=1&model=" + model.name;
}
private UploadResult parseAndHandleResponse(int httpCode, BufferedSource response) {
InputStreamReader reader = new InputStreamReader(response.inputStream());
JsonObject jsonResponse = GSON.fromJson(reader, JsonObject.class);
if (jsonResponse == null) {
throw new IllegalStateException("Response cannot be null!");
}
System.out.println(jsonResponse.toString());
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();
private final String name = name().toLowerCase();
public static SkinModel getByOrdinal(int ordinal) {
return VALUES.length > ordinal ? VALUES[ordinal] : STEVE;
}
public static SkinModel getByName(String name) {
return name == null || !name.equalsIgnoreCase("alex") ? STEVE : ALEX;
}
}
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static final class UploadResult {
private final int httpCode;
private final String error;
private final SkinModel model;
private final String skinUrl;
private final String capeUrl;
private final JsonObject response;
public static UploadResult failed(int httpCode, String error) {
return new UploadResult(httpCode, error, 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, model, skinUrl, capeUrl, response);
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2019-2020 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

@@ -34,6 +34,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
public final class ReflectionUtils {
@@ -45,7 +46,9 @@ public final class ReflectionUtils {
* will become PacketHandshakingInSetProtocol if the prefix is set to
* net.minecraft.server.v1_8R3
*/
@Setter private static String prefix = null;
@Getter
@Setter
private static String prefix = null;
static {
Field modifiersField = null;
@@ -388,6 +391,14 @@ public final class ReflectionUtils {
}
}
@Nullable
public static <T> T cast(Object instance, Class<T> castTo) {
if (castTo == null) {
throw new IllegalArgumentException("Cannot cast instance to null");
}
return castTo.cast(instance);
}
/**
* Invoke the given method of the given instance with the given arguments and cast the value.
*

View File

@@ -11,6 +11,10 @@ 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
@@ -26,12 +30,12 @@ disconnect:
player-link:
# Whether to enable the linking system. Turning this off will prevent
# players from using the linking feature even if they are already linked.
enable: false
enabled: false
# Whether to allow the use of /linkaccount and /unlinkaccount
# You can also use allow specific people to use the commands using the
# permissions floodgate.linkaccount and floodgate.unlinkaccount.
# This is only for linking, already connected people will stay connected
allow-linking: true
allowed: true
# The amount of time until a link code expires in seconds
link-code-timeout: 300
# The database type you want to use.

View File

@@ -31,12 +31,12 @@ disconnect:
player-link:
# Whether to enable the linking system. Turning this off will prevent
# players from using the linking feature even if they are already linked.
enable: false
enabled: false
# Whether to allow the use of /linkaccount and /unlinkaccount
# You can also use allow specific people to use the commands using the
# permissions floodgate.linkaccount and floodgate.unlinkaccount.
# This is only for linking, already connected people will stay connected
allow-linking: true
allowed: true
# The amount of time until a link code expires in seconds
link-code-timeout: 300
# The database type you want to use.