From 50f701c8e2f83570a9d1d52c8f2591e28e064a3b Mon Sep 17 00:00:00 2001 From: Max Weber Date: Thu, 22 Nov 2018 18:47:14 -0500 Subject: [PATCH] runelite-client: update to use client patch --- runelite-client/pom.xml | 9 +- .../net/runelite/client/rs/ClientLoader.java | 193 +++++++++++++----- .../client/rs/ClientUpdateCheckMode.java | 3 +- 3 files changed, 155 insertions(+), 50 deletions(-) diff --git a/runelite-client/pom.xml b/runelite-client/pom.xml index f173e09e58..56740b5c3e 100644 --- a/runelite-client/pom.xml +++ b/runelite-client/pom.xml @@ -157,6 +157,11 @@ natives-linux-i586 runtime + + io.sigpipe + jbsdiff + 1.0 + net.runelite @@ -171,7 +176,7 @@ net.runelite - injected-client + client-patch ${project.version} runtime @@ -267,7 +272,7 @@ - + net.runelite:* ** diff --git a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java index bbd8ec4439..ddab430d4e 100644 --- a/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java +++ b/runelite-client/src/main/java/net/runelite/client/rs/ClientLoader.java @@ -1,6 +1,7 @@ /* * Copyright (c) 2016-2017, Adam * Copyright (c) 2018, Tomas Slusny + * Copyright (c) 2018 Abex * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -25,23 +26,38 @@ */ package net.runelite.client.rs; +import com.google.common.hash.Hashing; +import com.google.common.io.ByteStreams; +import com.google.common.reflect.TypeToken; +import com.google.gson.Gson; +import io.sigpipe.jbsdiff.InvalidHeaderException; +import io.sigpipe.jbsdiff.Patch; import java.applet.Applet; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URL; -import java.net.URLClassLoader; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import lombok.extern.slf4j.Slf4j; -import net.runelite.http.api.updatecheck.UpdateCheckClient; +import static net.runelite.client.rs.ClientUpdateCheckMode.*; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.commons.compress.compressors.CompressorException; @Slf4j @Singleton public class ClientLoader { - private final UpdateCheckClient updateCheckClient = new UpdateCheckClient(); private final ClientConfigLoader clientConfigLoader; - private final ClientUpdateCheckMode updateCheckMode; + private ClientUpdateCheckMode updateCheckMode; @Inject private ClientLoader( @@ -52,60 +68,145 @@ public class ClientLoader this.clientConfigLoader = clientConfigLoader; } - private static Applet loadRuneLite(final RSConfig config) throws ClassNotFoundException, InstantiationException, IllegalAccessException - { - // the injected client is a runtime scoped dependency - final Class clientClass = ClientLoader.class.getClassLoader().loadClass(config.getInitialClass()); - return loadFromClass(config, clientClass); - } - - private static Applet loadVanilla(final RSConfig config) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException - { - final String codebase = config.getCodeBase(); - final String initialJar = config.getInitialJar(); - final String initialClass = config.getInitialClass(); - final URL url = new URL(codebase + initialJar); - - // Must set parent classloader to null, or it will pull from - // this class's classloader first - final URLClassLoader classloader = new URLClassLoader(new URL[]{url}, null); - final Class clientClass = classloader.loadClass(initialClass); - return loadFromClass(config, clientClass); - } - - private static Applet loadFromClass(final RSConfig config, final Class clientClass) throws IllegalAccessException, InstantiationException - { - final Applet rs = (Applet) clientClass.newInstance(); - rs.setStub(new RSAppletStub(config)); - return rs; - } - public Applet load() { + if (updateCheckMode == NONE) + { + return null; + } + try { - final RSConfig config = clientConfigLoader.fetch(); - final ClientUpdateCheckMode updateMode = updateCheckMode == ClientUpdateCheckMode.AUTO - ? updateCheckClient.isOutdated() ? ClientUpdateCheckMode.VANILLA : ClientUpdateCheckMode.RUNELITE - : updateCheckMode; + RSConfig config = clientConfigLoader.fetch(); - switch (updateMode) + Map zipFile = new HashMap<>(); { - case RUNELITE: - return loadRuneLite(config); - default: - case VANILLA: - return loadVanilla(config); - case NONE: - return null; + String codebase = config.getCodeBase(); + String initialJar = config.getInitialJar(); + URL url = new URL(codebase + initialJar); + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + { + JarInputStream jis = new JarInputStream(response.body().byteStream()); + + byte[] tmp = new byte[4096]; + ByteArrayOutputStream buffer = new ByteArrayOutputStream(756 * 1024); + for (; ; ) + { + JarEntry metadata = jis.getNextJarEntry(); + if (metadata == null) + { + break; + } + + buffer.reset(); + for (; ; ) + { + int n = jis.read(tmp); + if (n <= -1) + { + break; + } + buffer.write(tmp, 0, n); + } + + zipFile.put(metadata.getName(), buffer.toByteArray()); + } + } } + + if (updateCheckMode == AUTO) + { + Map hashes; + try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/hashes.json")) + { + hashes = new Gson().fromJson(new InputStreamReader(is), new TypeToken>() + { + }.getType()); + } + + for (Map.Entry file : hashes.entrySet()) + { + byte[] bytes = zipFile.get(file.getKey()); + + String ourHash = null; + if (bytes != null) + { + ourHash = Hashing.sha512().hashBytes(bytes).toString(); + } + + if (!file.getValue().equals(ourHash)) + { + log.debug("{} had a hash mismatch; falling back to vanilla. {} != {}", file.getKey(), file.getValue(), ourHash); + log.info("Client is outdated!"); + updateCheckMode = VANILLA; + break; + } + } + } + + if (updateCheckMode == AUTO) + { + ByteArrayOutputStream patchOs = new ByteArrayOutputStream(756 * 1024); + int patchCount = 0; + + for (Map.Entry file : zipFile.entrySet()) + { + byte[] bytes; + try (InputStream is = ClientLoader.class.getResourceAsStream("/patch/" + file.getKey() + ".bs")) + { + if (is == null) + { + continue; + } + + bytes = ByteStreams.toByteArray(is); + } + + patchOs.reset(); + Patch.patch(file.getValue(), bytes, patchOs); + file.setValue(patchOs.toByteArray()); + + ++patchCount; + } + + log.debug("Patched {} classes", patchCount); + } + + String initialClass = config.getInitialClass(); + + ClassLoader rsClassLoader = new ClassLoader() + { + @Override + protected Class findClass(String name) throws ClassNotFoundException + { + String path = name.replace('.', '/').concat(".class"); + byte[] data = zipFile.get(path); + if (data == null) + { + throw new ClassNotFoundException(name); + } + + return defineClass(name, data, 0, data.length); + } + }; + + Class clientClass = rsClassLoader.loadClass(initialClass); + + Applet rs = (Applet) clientClass.newInstance(); + rs.setStub(new RSAppletStub(config)); + return rs; } - catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException e) + catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException + | CompressorException | InvalidHeaderException e) { if (e instanceof ClassNotFoundException) { log.error("Unable to load client - class not found. This means you" - + " are not running RuneLite with Maven as the injected client" + + " are not running RuneLite with Maven as the client patch" + " is not in your classpath."); } diff --git a/runelite-client/src/main/java/net/runelite/client/rs/ClientUpdateCheckMode.java b/runelite-client/src/main/java/net/runelite/client/rs/ClientUpdateCheckMode.java index 4957089162..f8afce30be 100644 --- a/runelite-client/src/main/java/net/runelite/client/rs/ClientUpdateCheckMode.java +++ b/runelite-client/src/main/java/net/runelite/client/rs/ClientUpdateCheckMode.java @@ -28,6 +28,5 @@ public enum ClientUpdateCheckMode { AUTO, NONE, - VANILLA, - RUNELITE + VANILLA }