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/Parameters.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/Log.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() };
public static void apply(Parameters parameters) {
List<String> fixes = parameters.getList("fixes", Collections.<String>emptyList());
public static void apply(Parameters params) {
List<String> fixes = params.getList("fixes", Collections.<String>emptyList());
for (Fix fix : FIXES)
if (fixes.contains(fix.getName()) && fix.isApplicable(parameters))
if (fixes.contains(fix.getName()) && fix.isApplicable(params))
fix.apply();
}

View File

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

View File

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

View File

@ -59,6 +59,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.prismlauncher.exception.JsonParseException;
/**
* 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';
}
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/>.
*/
package org.prismlauncher.utils;
package org.prismlauncher.utils.url;
import java.io.IOException;
import java.lang.invoke.MethodHandle;
@ -54,7 +54,6 @@ public final class UrlUtils {
private static URLStreamHandler http;
private static MethodHandle openConnection;
private static MethodHandle openConnectionProxied;
static {
try {
@ -64,18 +63,10 @@ public final class UrlUtils {
getURLStreamHandler.setAccessible(true);
http = (URLStreamHandler) getURLStreamHandler.invoke(null, "http");
// reflection is required due to not having access
// unreflect is used due to the potential frequency of calls
Method openConnectionReflect = URLStreamHandler.class.getDeclaredMethod("openConnection", URL.class);
Method openConnectionReflect = URLStreamHandler.class.getDeclaredMethod("openConnection", URL.class,
Proxy.class);
openConnectionReflect.setAccessible(true);
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) {
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
*/
public static boolean isSupported() {
return http != null && openConnection != null && openConnectionProxied != null;
}
public static URLConnection openHttpConnection(URL url) throws IOException {
return openConnection(http, url);
return http != null && openConnection != null;
}
public static URLConnection openHttpConnection(URL url, Proxy proxy) throws IOException {
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 {
return (URLConnection) openConnection.invokeExact(handler, url);
return (URLConnection) openConnection.invokeExact(handler, url, proxy);
} catch (IOException | Error | RuntimeException e) {
throw e; // rethrow if possible
} 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);
}
}
}