Improve the skin fix code

- Spoof 404 instead of keeping original URL.
- Move JsonParseException to the package.
- Pass proxy as null to reduce code duplication.

Signed-off-by: TheKodeToad <TheKodeToad@proton.me>
This commit is contained in:
TheKodeToad 2022-11-20 09:16:30 +00:00
parent cfeadf858e
commit ead59c0246
8 changed files with 153 additions and 79 deletions

View File

@ -23,7 +23,8 @@ set(SRC
org/prismlauncher/utils/JsonParser.java org/prismlauncher/utils/JsonParser.java
org/prismlauncher/utils/Parameters.java org/prismlauncher/utils/Parameters.java
org/prismlauncher/utils/ReflectionUtils.java org/prismlauncher/utils/ReflectionUtils.java
org/prismlauncher/utils/UrlUtils.java org/prismlauncher/utils/url/NullConnection.java
org/prismlauncher/utils/url/UrlUtils.java
org/prismlauncher/utils/logging/Level.java org/prismlauncher/utils/logging/Level.java
org/prismlauncher/utils/logging/Log.java org/prismlauncher/utils/logging/Log.java
net/minecraft/Launcher.java net/minecraft/Launcher.java

View File

@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.prismlauncher.exception;
import java.io.IOException;
public class JsonParseException extends IOException {
private static final long serialVersionUID = 1L;
public JsonParseException(String message) {
super(message);
}
}

View File

@ -45,11 +45,11 @@ public final class Fixes {
private static final Fix[] FIXES = { new SkinFix() }; private static final Fix[] FIXES = { new SkinFix() };
public static void apply(Parameters parameters) { public static void apply(Parameters params) {
List<String> fixes = parameters.getList("fixes", Collections.<String>emptyList()); List<String> fixes = params.getList("fixes", Collections.<String>emptyList());
for (Fix fix : FIXES) for (Fix fix : FIXES)
if (fixes.contains(fix.getName()) && fix.isApplicable(parameters)) if (fixes.contains(fix.getName()) && fix.isApplicable(params))
fix.apply(); fix.apply();
} }

View File

@ -45,61 +45,51 @@ import java.util.Map;
import org.prismlauncher.utils.Base64; import org.prismlauncher.utils.Base64;
import org.prismlauncher.utils.JsonParser; import org.prismlauncher.utils.JsonParser;
import org.prismlauncher.utils.UrlUtils; import org.prismlauncher.utils.url.NullConnection;
import org.prismlauncher.utils.url.UrlUtils;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final class Handler extends URLStreamHandler { final class Handler extends URLStreamHandler {
private URL redirect(URL address) throws IOException { private URL redirect(URL address) throws IOException {
String skinOwner = findSkinOwner(address); String skinOwner = findSkinOwner(address);
if (skinOwner != null) if (skinOwner != null)
return convertSkin(address, skinOwner); return convert(skinOwner, "SKIN");
String capeOwner = findCapeOwner(address); String capeOwner = findCapeOwner(address);
if (capeOwner != null) if (capeOwner != null)
return convertCape(address, capeOwner); return convert(capeOwner, "CAPE");
return address; return address;
} }
@Override @Override
protected URLConnection openConnection(URL address) throws IOException { protected URLConnection openConnection(URL address) throws IOException {
address = redirect(address); return openConnection(address, null);
return UrlUtils.openHttpConnection(address);
} }
@Override @Override
protected URLConnection openConnection(URL address, Proxy proxy) throws IOException { protected URLConnection openConnection(URL address, Proxy proxy) throws IOException {
address = redirect(address); address = redirect(address);
if (address == null)
return NullConnection.INSTANCE;
return UrlUtils.openHttpConnection(address, proxy); return UrlUtils.openHttpConnection(address, proxy);
} }
private URL convertSkin(URL defaultUrl, String owner) throws IOException { private URL convert(String owner, String name) throws IOException {
Map<String, Object> textures = getTextures(owner); Map<String, Object> textures = getTextures(owner);
if (textures != null) { if (textures != null) {
textures = (Map<String, Object>) textures.get("SKIN"); textures = (Map<String, Object>) textures.get(name);
return new URL((String) textures.get("url"));
}
return defaultUrl;
}
private URL convertCape(URL defaultUrl, String owner) throws IOException {
Map<String, Object> textures = getTextures(owner);
if (textures != null) {
textures = (Map<String, Object>) textures.get("CAPE");
if (textures == null) if (textures == null)
return defaultUrl; return null;
return new URL((String) textures.get("url")); return new URL((String) textures.get("url"));
} }
return defaultUrl; return null;
} }
private static Map<String, Object> getTextures(String owner) throws IOException { private static Map<String, Object> getTextures(String owner) throws IOException {
@ -116,6 +106,7 @@ final class Handler extends URLStreamHandler {
Map<String, Object> result = (Map<String, Object>) JsonParser Map<String, Object> result = (Map<String, Object>) JsonParser
.parse(new String(Base64.decode((String) property.get("value")))); .parse(new String(Base64.decode((String) property.get("value"))));
result = (Map<String, Object>) result.get("textures"); result = (Map<String, Object>) result.get("textures");
return result; return result;
} }
} }
@ -128,11 +119,11 @@ final class Handler extends URLStreamHandler {
private static String findSkinOwner(URL address) { private static String findSkinOwner(URL address) {
switch (address.getHost()) { switch (address.getHost()) {
case "www.minecraft.net": case "www.minecraft.net":
return stripPng(strip(address.getPath(), "/skin/")); return stripIfPrefixed(address.getPath(), "/skin/");
case "s3.amazonaws.com": case "s3.amazonaws.com":
case "skins.minecraft.net": case "skins.minecraft.net":
return stripPng(strip(address.getPath(), "/MinecraftSkins/")); return stripIfPrefixed(address.getPath(), "/MinecraftSkins/");
} }
return null; return null;
@ -141,26 +132,25 @@ final class Handler extends URLStreamHandler {
private static String findCapeOwner(URL address) { private static String findCapeOwner(URL address) {
switch (address.getHost()) { switch (address.getHost()) {
case "www.minecraft.net": case "www.minecraft.net":
return stripPng(strip(address.getQuery(), "user=")); return stripIfPrefixed(address.getQuery(), "user=");
case "s3.amazonaws.com": case "s3.amazonaws.com":
case "skins.minecraft.net": case "skins.minecraft.net":
return stripPng(strip(address.getPath(), "/MinecraftCloaks/")); return stripIfPrefixed(address.getPath(), "/MinecraftCloaks/");
} }
return null; return null;
} }
private static String stripPng(String string) { private static String stripIfPrefixed(String string, String prefix) {
if (string != null && string.endsWith(".png")) if (string != null && string.startsWith(prefix)) {
return string.substring(0, string.lastIndexOf('.')); string = string.substring(prefix.length());
return string; if (string.endsWith(".png"))
} string = string.substring(0, string.lastIndexOf('.'));
private static String strip(String string, String prefix) { return string;
if (string != null && string.startsWith(prefix)) }
return string.substring(prefix.length());
return null; return null;
} }

View File

@ -41,8 +41,8 @@ import java.net.URLStreamHandlerFactory;
import org.prismlauncher.fix.Fix; import org.prismlauncher.fix.Fix;
import org.prismlauncher.utils.Parameters; import org.prismlauncher.utils.Parameters;
import org.prismlauncher.utils.UrlUtils;
import org.prismlauncher.utils.logging.Log; import org.prismlauncher.utils.logging.Log;
import org.prismlauncher.utils.url.UrlUtils;
/** /**
* Fixes skins by redirecting to other URLs. * Fixes skins by redirecting to other URLs.

View File

@ -59,6 +59,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.prismlauncher.exception.JsonParseException;
/** /**
* Single-file JSON parser to allow for usage in versions without GSON. * Single-file JSON parser to allow for usage in versions without GSON.
*/ */
@ -420,14 +422,4 @@ public final class JsonParser {
return character() == 'n' && read() == 'u' && read() == 'l' && read() == 'l'; return character() == 'n' && read() == 'u' && read() == 'l' && read() == 'l';
} }
public static class JsonParseException extends IOException {
private static final long serialVersionUID = 1L;
public JsonParseException(String message) {
super(message);
}
}
} }

View File

@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-3.0-only
/*
* Prism Launcher - Minecraft Launcher
* Copyright (C) 2022 TheKodeToad <TheKodeToad@proton.me>
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.prismlauncher.utils.url;
import java.io.IOException;
import java.net.HttpURLConnection;
/**
* Spoof 404 response from server to avoid unnecessary requests.
*/
public final class NullConnection extends HttpURLConnection {
public static final NullConnection INSTANCE = new NullConnection();
public NullConnection() {
super(null);
}
@Override
public void connect() throws IOException {
responseCode = 404;
}
@Override
public void disconnect() {
}
@Override
public boolean usingProxy() {
return false;
}
}

View File

@ -33,7 +33,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.prismlauncher.utils; package org.prismlauncher.utils.url;
import java.io.IOException; import java.io.IOException;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
@ -54,7 +54,6 @@ public final class UrlUtils {
private static URLStreamHandler http; private static URLStreamHandler http;
private static MethodHandle openConnection; private static MethodHandle openConnection;
private static MethodHandle openConnectionProxied;
static { static {
try { try {
@ -64,18 +63,10 @@ public final class UrlUtils {
getURLStreamHandler.setAccessible(true); getURLStreamHandler.setAccessible(true);
http = (URLStreamHandler) getURLStreamHandler.invoke(null, "http"); http = (URLStreamHandler) getURLStreamHandler.invoke(null, "http");
// reflection is required due to not having access Method openConnectionReflect = URLStreamHandler.class.getDeclaredMethod("openConnection", URL.class,
// unreflect is used due to the potential frequency of calls Proxy.class);
Method openConnectionReflect = URLStreamHandler.class.getDeclaredMethod("openConnection", URL.class);
openConnectionReflect.setAccessible(true); openConnectionReflect.setAccessible(true);
openConnection = MethodHandles.lookup().unreflect(openConnectionReflect); openConnection = MethodHandles.lookup().unreflect(openConnectionReflect);
// a second method which takes in a proxy is required for a fully-functional
// URLStreamHandler
Method openConnectionReflectProxied = URLStreamHandler.class.getDeclaredMethod("openConnection", URL.class,
Proxy.class);
openConnectionReflectProxied.setAccessible(true);
openConnectionProxied = MethodHandles.lookup().unreflect(openConnectionReflectProxied);
} catch (Throwable e) { } catch (Throwable e) {
Log.error("URL reflection failed - some features may not work", e); Log.error("URL reflection failed - some features may not work", e);
} }
@ -87,20 +78,16 @@ public final class UrlUtils {
* @return <code>true</code> if all features can be used * @return <code>true</code> if all features can be used
*/ */
public static boolean isSupported() { public static boolean isSupported() {
return http != null && openConnection != null && openConnectionProxied != null; return http != null && openConnection != null;
}
public static URLConnection openHttpConnection(URL url) throws IOException {
return openConnection(http, url);
} }
public static URLConnection openHttpConnection(URL url, Proxy proxy) throws IOException { public static URLConnection openHttpConnection(URL url, Proxy proxy) throws IOException {
return openConnection(http, url, proxy); return openConnection(http, url, proxy);
} }
public static URLConnection openConnection(URLStreamHandler handler, URL url) throws IOException { public static URLConnection openConnection(URLStreamHandler handler, URL url, Proxy proxy) throws IOException {
try { try {
return (URLConnection) openConnection.invokeExact(handler, url); return (URLConnection) openConnection.invokeExact(handler, url, proxy);
} catch (IOException | Error | RuntimeException e) { } catch (IOException | Error | RuntimeException e) {
throw e; // rethrow if possible throw e; // rethrow if possible
} catch (Throwable e) { } catch (Throwable e) {
@ -108,14 +95,4 @@ public final class UrlUtils {
} }
} }
public static URLConnection openConnection(URLStreamHandler handler, URL url, Proxy proxy) throws IOException {
try {
return (URLConnection) openConnectionProxied.invokeExact(handler, url, proxy);
} catch (IOException | Error | RuntimeException e) {
throw e;
} catch (Throwable e) {
throw new Error(e);
}
}
} }