From cb32711077af62b527abdc4af5aabce44f4e5d21 Mon Sep 17 00:00:00 2001 From: TheKodeToad Date: Sun, 25 Dec 2022 11:40:49 +0000 Subject: [PATCH] Slim skin fix - thanks to @craftycodie and @DelofJ ! Signed-off-by: TheKodeToad --- libraries/launcher/CMakeLists.txt | 5 +- .../org/prismlauncher/fix/online/Handler.java | 12 ++- .../prismlauncher/fix/online/OnlineFixes.java | 2 +- .../org/prismlauncher/fix/online/SkinFix.java | 94 +++++++++------- .../prismlauncher/utils/api/MojangApi.java | 101 ++++++++++++++++++ .../org/prismlauncher/utils/api/Texture.java | 61 +++++++++++ .../utils/url/CustomUrlConnection.java | 79 ++++++++++++++ .../utils/{ => url}/UrlUtils.java | 12 ++- 8 files changed, 322 insertions(+), 44 deletions(-) create mode 100644 libraries/launcher/org/prismlauncher/utils/api/MojangApi.java create mode 100644 libraries/launcher/org/prismlauncher/utils/api/Texture.java create mode 100644 libraries/launcher/org/prismlauncher/utils/url/CustomUrlConnection.java rename libraries/launcher/org/prismlauncher/utils/{ => url}/UrlUtils.java (89%) diff --git a/libraries/launcher/CMakeLists.txt b/libraries/launcher/CMakeLists.txt index 71996ae33..bf8f0baa2 100644 --- a/libraries/launcher/CMakeLists.txt +++ b/libraries/launcher/CMakeLists.txt @@ -23,9 +23,12 @@ set(SRC org/prismlauncher/utils/JsonParser.java org/prismlauncher/utils/Parameters.java org/prismlauncher/utils/ReflectionUtils.java - org/prismlauncher/utils/UrlUtils.java + org/prismlauncher/utils/api/MojangApi.java + org/prismlauncher/utils/api/Texture.java org/prismlauncher/utils/logging/Level.java org/prismlauncher/utils/logging/Log.java + org/prismlauncher/utils/url/UrlUtils.java + org/prismlauncher/utils/url/CustomUrlConnection.java net/minecraft/Launcher.java ) add_jar(NewLaunch ${SRC}) diff --git a/libraries/launcher/org/prismlauncher/fix/online/Handler.java b/libraries/launcher/org/prismlauncher/fix/online/Handler.java index b5b4de28e..618683119 100644 --- a/libraries/launcher/org/prismlauncher/fix/online/Handler.java +++ b/libraries/launcher/org/prismlauncher/fix/online/Handler.java @@ -41,7 +41,7 @@ import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; -import org.prismlauncher.utils.UrlUtils; +import org.prismlauncher.utils.url.UrlUtils; final class Handler extends URLStreamHandler { @@ -52,8 +52,14 @@ final class Handler extends URLStreamHandler { @Override protected URLConnection openConnection(URL address, Proxy proxy) throws IOException { - address = SkinFix.redirect(address); - return UrlUtils.openHttpConnection(address, proxy); + URLConnection result; + + // try skin fix + result = SkinFix.openConnection(address, proxy); + if (result != null) + return result; + + return UrlUtils.openConnection(address, proxy); } } diff --git a/libraries/launcher/org/prismlauncher/fix/online/OnlineFixes.java b/libraries/launcher/org/prismlauncher/fix/online/OnlineFixes.java index c344a8e73..5f9ff1014 100644 --- a/libraries/launcher/org/prismlauncher/fix/online/OnlineFixes.java +++ b/libraries/launcher/org/prismlauncher/fix/online/OnlineFixes.java @@ -40,8 +40,8 @@ import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import org.prismlauncher.utils.Base64; -import org.prismlauncher.utils.UrlUtils; import org.prismlauncher.utils.logging.Log; +import org.prismlauncher.utils.url.UrlUtils; /** * Fixes skins by redirecting to other URLs. diff --git a/libraries/launcher/org/prismlauncher/fix/online/SkinFix.java b/libraries/launcher/org/prismlauncher/fix/online/SkinFix.java index f1e480776..b05e1f208 100644 --- a/libraries/launcher/org/prismlauncher/fix/online/SkinFix.java +++ b/libraries/launcher/org/prismlauncher/fix/online/SkinFix.java @@ -1,63 +1,85 @@ package org.prismlauncher.fix.online; +import java.awt.AlphaComposite; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.Proxy; import java.net.URL; -import java.util.Map; +import java.net.URLConnection; -import org.prismlauncher.utils.Base64; -import org.prismlauncher.utils.JsonParser; +import javax.imageio.ImageIO; + +import org.prismlauncher.utils.api.MojangApi; +import org.prismlauncher.utils.api.Texture; +import org.prismlauncher.utils.url.CustomUrlConnection; +import org.prismlauncher.utils.url.UrlUtils; -@SuppressWarnings("unchecked") final class SkinFix { - static URL redirect(URL address) throws IOException { + static URLConnection openConnection(URL address, Proxy proxy) throws IOException { String skinOwner = findSkinOwner(address); if (skinOwner != null) - return convert(skinOwner, "SKIN"); + // we need to correct the skin + return getSkinConnection(skinOwner, proxy); String capeOwner = findCapeOwner(address); - if (capeOwner != null) - return convert(capeOwner, "CAPE"); - - return address; - } - - private static URL convert(String owner, String name) throws IOException { - Map textures = getTextures(owner); - - if (textures != null) { - textures = (Map) textures.get(name); - if (textures == null) + if (capeOwner != null) { + // since we do not need to process the image, open a direct connection bypassing Handler + Texture texture = MojangApi.getTexture(MojangApi.getUuid(capeOwner), "CAPE"); + if (texture == null) return null; - return new URL((String) textures.get("url")); + return UrlUtils.openConnection(texture.getUrl(), proxy); } return null; } - private static Map getTextures(String owner) throws IOException { - try (InputStream in = new URL("https://api.mojang.com/users/profiles/minecraft/" + owner).openStream()) { - Map map = (Map) JsonParser.parse(in); - String id = (String) map.get("id"); + private static URLConnection getSkinConnection(String owner, Proxy proxy) throws IOException { + Texture texture = MojangApi.getTexture(MojangApi.getUuid(owner), "SKIN"); + if (texture == null) + return null; - try (InputStream profileIn = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + id) - .openStream()) { - Map profile = (Map) JsonParser.parse(profileIn); + URLConnection connection = UrlUtils.openConnection(texture.getUrl(), proxy); + try (InputStream in = connection.getInputStream()) { + // thank you craftycodie! + // this is heavily based on + // https://github.com/Mojang/LegacyLauncher/pull/33/files#diff-b61023785a9260651ca0a223573ea9acb5be5eec478bff626dafb7abe13ffebaR99 + BufferedImage image = ImageIO.read(in); + Graphics2D graphics = image.createGraphics(); + graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); - for (Map property : (Iterable>) profile.get("properties")) { - if (property.get("name").equals("textures")) { - Map result = (Map) JsonParser - .parse(new String(Base64.decode((String) property.get("value")))); - result = (Map) result.get("textures"); + BufferedImage subimage; - return result; - } - } - - return null; + if (image.getHeight() > 32) { + // flatten second layers + subimage = image.getSubimage(0, 32, 56, 16); + graphics.drawImage(subimage, 0, 16, null); } + + if (texture.isSlim()) { + // convert slim to wide + subimage = image.getSubimage(45, 16, 9, 16); + graphics.drawImage(subimage, 46, 16, null); + + subimage = image.getSubimage(49, 16, 2, 4); + graphics.drawImage(subimage, 50, 16, null); + + subimage = image.getSubimage(53, 20, 2, 12); + graphics.drawImage(subimage, 54, 20, null); + } + + graphics.dispose(); + + // crop the image + ByteArrayOutputStream out = new ByteArrayOutputStream(); + image = image.getSubimage(0, 0, 64, 32); + ImageIO.write(image, "png", out); + + return new CustomUrlConnection(out.toByteArray()); } } diff --git a/libraries/launcher/org/prismlauncher/utils/api/MojangApi.java b/libraries/launcher/org/prismlauncher/utils/api/MojangApi.java new file mode 100644 index 000000000..dc2794107 --- /dev/null +++ b/libraries/launcher/org/prismlauncher/utils/api/MojangApi.java @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.utils.api; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Map; + +import org.prismlauncher.utils.Base64; +import org.prismlauncher.utils.JsonParser; + +/** + * Basic access to Mojang's Minecraft API. + */ +@SuppressWarnings("unchecked") +public final class MojangApi { + + public static String getUuid(String username) throws IOException { + try (InputStream in = new URL("https://api.mojang.com/users/profiles/minecraft/" + username).openStream()) { + Map map = (Map) JsonParser.parse(in); + return (String) map.get("id"); + } + } + + public static Texture getTexture(String player, String name) throws IOException { + Map map = getTextures(player); + + if (map != null) { + map = (Map) map.get(name); + if (map == null) + return null; + + URL url = new URL((String) map.get("url")); + boolean slim = false; + + if (name.equals("SKIN")) { + map = (Map) map.get("metadata"); + if (map != null && "slim".equals(map.get("model"))) + slim = true; + } + + return new Texture(url, slim); + } + + return null; + } + + public static Map getTextures(String player) throws IOException { + try (InputStream profileIn = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + player) + .openStream()) { + Map profile = (Map) JsonParser.parse(profileIn); + + for (Map property : (Iterable>) profile.get("properties")) { + if (property.get("name").equals("textures")) { + Map result = (Map) JsonParser + .parse(new String(Base64.decode((String) property.get("value")))); + result = (Map) result.get("textures"); + + return result; + } + } + + return null; + } + } + +} diff --git a/libraries/launcher/org/prismlauncher/utils/api/Texture.java b/libraries/launcher/org/prismlauncher/utils/api/Texture.java new file mode 100644 index 000000000..1b5c2204e --- /dev/null +++ b/libraries/launcher/org/prismlauncher/utils/api/Texture.java @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.utils.api; + +import java.net.URL; + +/** + * Represents a texture from the Mojang API. + */ +public final class Texture { + + private final URL url; + private final boolean slim; + + public Texture(URL url, boolean slim) { + this.url = url; + this.slim = slim; + } + + public URL getUrl() { + return url; + } + + public boolean isSlim() { + return slim; + } + +} diff --git a/libraries/launcher/org/prismlauncher/utils/url/CustomUrlConnection.java b/libraries/launcher/org/prismlauncher/utils/url/CustomUrlConnection.java new file mode 100644 index 000000000..8bc0cff11 --- /dev/null +++ b/libraries/launcher/org/prismlauncher/utils/url/CustomUrlConnection.java @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2022 TheKodeToad + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.prismlauncher.utils.url; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; + +public class CustomUrlConnection extends HttpURLConnection { + + private InputStream in; + + public CustomUrlConnection(byte[] data) { + this(new ByteArrayInputStream(data)); + } + + public CustomUrlConnection(InputStream in) { + super(null); + this.in = in; + } + + @Override + public void connect() throws IOException { + responseCode = 200; + } + + @Override + public void disconnect() { + try { + in.close(); + } catch (IOException e) { + } + } + + @Override + public InputStream getInputStream() throws IOException { + return in; + } + + @Override + public boolean usingProxy() { + return false; + } + +} diff --git a/libraries/launcher/org/prismlauncher/utils/UrlUtils.java b/libraries/launcher/org/prismlauncher/utils/url/UrlUtils.java similarity index 89% rename from libraries/launcher/org/prismlauncher/utils/UrlUtils.java rename to libraries/launcher/org/prismlauncher/utils/url/UrlUtils.java index 1ab0eb918..69ebbd50b 100644 --- a/libraries/launcher/org/prismlauncher/utils/UrlUtils.java +++ b/libraries/launcher/org/prismlauncher/utils/url/UrlUtils.java @@ -33,7 +33,7 @@ * along with this program. If not, see . */ -package org.prismlauncher.utils; +package org.prismlauncher.utils.url; import java.io.IOException; import java.lang.invoke.MethodHandle; @@ -81,11 +81,17 @@ public final class UrlUtils { return http != null && openConnection != null; } - public static URLConnection openHttpConnection(URL url, Proxy proxy) throws IOException { + public static URLConnection openConnection(URL url, Proxy proxy) throws IOException { if (http == null) throw new UnsupportedOperationException(); - return openConnection(http, url, proxy); + if (url.getProtocol().equals("http")) + return openConnection(http, url, proxy); + + // fall back to Java's default method + // at this point, this should not cause a StackOverflowError unless we've missed + // a protocol out from the if statements + return url.openConnection(); } public static URLConnection openConnection(URLStreamHandler handler, URL url, Proxy proxy) throws IOException {