diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/JsonTextureProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/JsonTextureProvider.java new file mode 100644 index 00000000..be19ca1e --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/JsonTextureProvider.java @@ -0,0 +1,44 @@ +package pro.gravit.launchserver.auth.texture; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import pro.gravit.launcher.HTTPRequest; +import pro.gravit.launcher.Launcher; +import pro.gravit.launcher.profiles.Texture; + +import java.io.IOException; +import java.net.URL; +import java.util.UUID; + +public class JsonTextureProvider extends TextureProvider { + public String url; + private transient final Logger logger = LogManager.getLogger(); + + @Override + public void close() throws IOException { + //None + } + + @Override + public Texture getCloakTexture(UUID uuid, String username, String client) throws IOException { + logger.warn("Ineffective get cloak texture for {}", username); + return getTextures(uuid, username, client).cloak; + } + + @Override + public Texture getSkinTexture(UUID uuid, String username, String client) throws IOException { + logger.warn("Ineffective get skin texture for {}", username); + return getTextures(uuid, username, client).skin; + } + + @Override + public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) { + try { + var result = HTTPRequest.jsonRequest(null, "GET", new URL(RequestTextureProvider.getTextureURL(url, uuid, username, client))); + return Launcher.gsonManager.gson.fromJson(result, SkinAndCloakTextures.class); + } catch (IOException e) { + logger.error("JsonTextureProvider", e); + return new SkinAndCloakTextures(null, null); + } + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/RequestTextureProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/RequestTextureProvider.java index 241787dc..3dc394f0 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/RequestTextureProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/RequestTextureProvider.java @@ -43,7 +43,7 @@ private static Texture getTexture(String url, Path local, boolean cloak) throws } } - private static String getTextureURL(String url, UUID uuid, String username, String client) { + public static String getTextureURL(String url, UUID uuid, String username, String client) { return CommonHelper.replace(url, "username", IOHelper.urlEncode(username), "uuid", IOHelper.urlEncode(uuid.toString()), "hash", IOHelper.urlEncode(Launcher.toHash(uuid)), "client", IOHelper.urlEncode(client == null ? "unknown" : client)); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/TextureProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/TextureProvider.java index cd98f4c5..c8487a48 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/TextureProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/texture/TextureProvider.java @@ -17,6 +17,7 @@ public static void registerProviders() { // Auth providers that doesn't do nothing :D providers.register("request", RequestTextureProvider.class); + providers.register("json", JsonTextureProvider.class); registredProv = true; } } @@ -29,4 +30,34 @@ public static void registerProviders() { public abstract Texture getSkinTexture(UUID uuid, String username, String client) throws IOException; + + public static class SkinAndCloakTextures { + public final Texture skin; + public final Texture cloak; + + public SkinAndCloakTextures(Texture skin, Texture cloak) { + this.skin = skin; + this.cloak = cloak; + } + } + + public SkinAndCloakTextures getTextures(UUID uuid, String username, String client) { + + Texture skin; + try { + skin = getSkinTexture(uuid, username, client); + } catch (IOException e) { + skin = null; + } + + // Get cloak texture + Texture cloak; + try { + cloak = getCloakTexture(uuid, username, client); + } catch (IOException e) { + cloak = null; + } + + return new SkinAndCloakTextures(skin, cloak); + } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java index 81eece80..0894c9bb 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/AuthManager.java @@ -6,7 +6,6 @@ import pro.gravit.launcher.events.request.AuthRequestEvent; import pro.gravit.launcher.profiles.ClientProfile; import pro.gravit.launcher.profiles.PlayerProfile; -import pro.gravit.launcher.profiles.Texture; import pro.gravit.launcher.request.auth.AuthRequest; import pro.gravit.launcher.request.auth.password.*; import pro.gravit.launchserver.LaunchServer; @@ -309,23 +308,10 @@ public PlayerProfile getPlayerProfile(AuthProviderPair pair, User user) { private PlayerProfile getPlayerProfile(UUID uuid, String username, String client, TextureProvider textureProvider) { // Get skin texture - Texture skin; - try { - skin = textureProvider.getSkinTexture(uuid, username, client); - } catch (IOException e) { - skin = null; - } - - // Get cloak texture - Texture cloak; - try { - cloak = textureProvider.getCloakTexture(uuid, username, client); - } catch (IOException e) { - cloak = null; - } + TextureProvider.SkinAndCloakTextures textures = textureProvider.getTextures(uuid, username, client); // Return combined profile - return new PlayerProfile(uuid, username, skin, cloak); + return new PlayerProfile(uuid, username, textures.skin, textures.cloak); } public AuthRequest.AuthPasswordInterface decryptPassword(AuthRequest.AuthPasswordInterface password) throws AuthException { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/Launcher.java b/LauncherAPI/src/main/java/pro/gravit/launcher/Launcher.java index df5bd8da..384623cb 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/Launcher.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/Launcher.java @@ -25,9 +25,15 @@ public final class Launcher { public static final String SKIN_DIGEST_PROPERTY = "skinDigest"; + public static final String SKIN_METADATA_PROPERTY = "skinMetadata"; + public static final String CLOAK_URL_PROPERTY = "cloakURL"; public static final String CLOAK_DIGEST_PROPERTY = "cloakDigest"; + + public static final String CLOAK_METADATA_PROPERTY = "cloakMetadata"; + + // Used to determine from clientside is launched from launcher public static final AtomicBoolean LAUNCHED = new AtomicBoolean(false); public static final int PROTOCOL_MAGIC_LEGACY = 0x724724_00 + 24; diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/Texture.java b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/Texture.java index 6d9be5c0..830835c7 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/Texture.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/Texture.java @@ -11,6 +11,7 @@ import java.io.InputStream; import java.net.URL; import java.nio.file.Path; +import java.util.Map; import java.util.Objects; public final class Texture extends StreamObject { @@ -22,11 +23,14 @@ public final class Texture extends StreamObject { public final byte[] digest; + public final Map metadata; + @Deprecated public Texture(HInput input) throws IOException { url = IOHelper.verifyURL(input.readASCII(2048)); digest = input.readByteArray(-DIGEST_ALGO.bytes); + metadata = null; } public Texture(String url, boolean cloak) throws IOException { @@ -43,6 +47,7 @@ public Texture(String url, boolean cloak) throws IOException { // Get digest of texture digest = SecurityHelper.digest(DIGEST_ALGO, new URL(url)); + metadata = null; // May be auto-detect? } public Texture(String url, Path local, boolean cloak) throws IOException { @@ -51,12 +56,20 @@ public Texture(String url, Path local, boolean cloak) throws IOException { IOHelper.readTexture(input, cloak); // Verify texture } this.digest = Objects.requireNonNull(SecurityHelper.digest(DIGEST_ALGO, local), "digest"); + this.metadata = null; } public Texture(String url, byte[] digest) { this.url = IOHelper.verifyURL(url); this.digest = Objects.requireNonNull(digest, "digest"); + this.metadata = null; + } + + public Texture(String url, byte[] digest, Map metadata) { + this.url = url; + this.digest = digest; + this.metadata = metadata; } @Override diff --git a/LauncherAuthlib/src/main/java/com/mojang/authlib/minecraft/MinecraftProfileTexture.java b/LauncherAuthlib/src/main/java/com/mojang/authlib/minecraft/MinecraftProfileTexture.java index b0daaef1..311196b1 100644 --- a/LauncherAuthlib/src/main/java/com/mojang/authlib/minecraft/MinecraftProfileTexture.java +++ b/LauncherAuthlib/src/main/java/com/mojang/authlib/minecraft/MinecraftProfileTexture.java @@ -2,6 +2,7 @@ import java.util.Collections; import java.util.EnumSet; +import java.util.Map; import java.util.Set; public class MinecraftProfileTexture { @@ -10,6 +11,7 @@ public class MinecraftProfileTexture { // Instance private final String url; private final String hash; + private final Map metadata; public MinecraftProfileTexture(String url) { this(url, baseName(url)); @@ -18,6 +20,13 @@ public MinecraftProfileTexture(String url) { public MinecraftProfileTexture(String url, String hash) { this.url = url; this.hash = hash; + this.metadata = null; + } + + public MinecraftProfileTexture(String url, String hash, Map metadata) { + this.url = url; + this.hash = hash; + this.metadata = metadata; } private static String baseName(String url) { @@ -37,7 +46,10 @@ public String getHash() { } public String getMetadata(String key) { - return null; + if (metadata == null) { + return null; + } + return metadata.get(key); } public String getUrl() { diff --git a/LauncherAuthlib/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService.java b/LauncherAuthlib/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService.java index 434626ed..2f96927d 100644 --- a/LauncherAuthlib/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService.java +++ b/LauncherAuthlib/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService.java @@ -1,9 +1,11 @@ package com.mojang.authlib.yggdrasil; import com.google.common.collect.Iterables; +import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.reflect.TypeToken; import com.mojang.authlib.AuthenticationService; import com.mojang.authlib.GameProfile; import com.mojang.authlib.exceptions.AuthenticationException; @@ -21,6 +23,7 @@ import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.SecurityHelper; +import java.lang.reflect.Type; import java.net.InetAddress; import java.util.Base64; import java.util.EnumMap; @@ -29,6 +32,7 @@ public class YggdrasilMinecraftSessionService extends BaseMinecraftSessionService { public static final boolean NO_TEXTURES = Boolean.getBoolean("launcher.com.mojang.authlib.noTextures"); + private static final Gson gson = new Gson(); public YggdrasilMinecraftSessionService(AuthenticationService service) { super(service); @@ -53,6 +57,10 @@ public static void fillTextureProperties(GameProfile profile, PlayerProfile pp) if (pp.skin != null) { properties.put(Launcher.SKIN_URL_PROPERTY, new Property(Launcher.SKIN_URL_PROPERTY, pp.skin.url, "")); properties.put(Launcher.SKIN_DIGEST_PROPERTY, new Property(Launcher.SKIN_DIGEST_PROPERTY, SecurityHelper.toHex(pp.skin.digest), "")); + if (pp.skin.metadata != null) { + String metadata = serializeMetadataMap(pp.skin.metadata); + properties.put(Launcher.SKIN_METADATA_PROPERTY, new Property(Launcher.SKIN_METADATA_PROPERTY, metadata, "")); + } if (debug) { LogHelper.debug("fillTextureProperties, Has skin texture for username '%s'", profile.getName()); } @@ -60,12 +68,29 @@ public static void fillTextureProperties(GameProfile profile, PlayerProfile pp) if (pp.cloak != null) { properties.put(Launcher.CLOAK_URL_PROPERTY, new Property(Launcher.CLOAK_URL_PROPERTY, pp.cloak.url, "")); properties.put(Launcher.CLOAK_DIGEST_PROPERTY, new Property(Launcher.CLOAK_DIGEST_PROPERTY, SecurityHelper.toHex(pp.cloak.digest), "")); + if (pp.cloak.metadata != null) { + String metadata = serializeMetadataMap(pp.cloak.metadata); + properties.put(Launcher.CLOAK_METADATA_PROPERTY, new Property(Launcher.SKIN_METADATA_PROPERTY, metadata, "")); + } if (debug) { LogHelper.debug("fillTextureProperties, Has cloak texture for username '%s'", profile.getName()); } } } + private static String serializeMetadataMap(Map map) { + if (map == null) { + return null; + } + return gson.toJson(map); + } + + private static Map deserializeMetadataMap(String value) { + Type typeOfMap = new TypeToken>() { + }.getType(); + return gson.fromJson(value, typeOfMap); + } + private static void getTexturesMojang(Map textures, String texturesBase64, GameProfile profile) { // Decode textures payload JsonObject texturesJSON; @@ -148,14 +173,16 @@ public Map getTextures(Ga // Add skin URL to textures map Property skinURL = Iterables.getFirst(profile.getProperties().get(Launcher.SKIN_URL_PROPERTY), null); Property skinDigest = Iterables.getFirst(profile.getProperties().get(Launcher.SKIN_DIGEST_PROPERTY), null); + Property skinMetadata = Iterables.getFirst(profile.getProperties().get(Launcher.SKIN_METADATA_PROPERTY), null); if (skinURL != null && skinDigest != null) - textures.put(MinecraftProfileTexture.Type.SKIN, new MinecraftProfileTexture(skinURL.getValue(), skinDigest.getValue())); + textures.put(MinecraftProfileTexture.Type.SKIN, new MinecraftProfileTexture(skinURL.getValue(), skinDigest.getValue(), skinMetadata == null ? null : deserializeMetadataMap(skinMetadata.getValue()))); // Add cloak URL to textures map Property cloakURL = Iterables.getFirst(profile.getProperties().get(Launcher.CLOAK_URL_PROPERTY), null); Property cloakDigest = Iterables.getFirst(profile.getProperties().get(Launcher.CLOAK_DIGEST_PROPERTY), null); + Property cloakMetadata = Iterables.getFirst(profile.getProperties().get(Launcher.CLOAK_METADATA_PROPERTY), null); if (cloakURL != null && cloakDigest != null) - textures.put(MinecraftProfileTexture.Type.CAPE, new MinecraftProfileTexture(cloakURL.getValue(), cloakDigest.getValue())); + textures.put(MinecraftProfileTexture.Type.CAPE, new MinecraftProfileTexture(cloakURL.getValue(), cloakDigest.getValue(), cloakMetadata == null ? null : deserializeMetadataMap(cloakMetadata.getValue()))); // Try to find missing textures in textures payload (now always true because launcher is not passing elytra skins) if (textures.size() != MinecraftProfileTexture.PROFILE_TEXTURE_COUNT) {