diff --git a/injector/src/main/java/com/openosrs/injector/Injector.java b/injector/src/main/java/com/openosrs/injector/Injector.java index b6a5262d86..1a99787dde 100644 --- a/injector/src/main/java/com/openosrs/injector/Injector.java +++ b/injector/src/main/java/com/openosrs/injector/Injector.java @@ -7,6 +7,7 @@ */ package com.openosrs.injector; +import com.google.common.hash.Hashing; import com.openosrs.injector.injection.InjectData; import com.openosrs.injector.injection.InjectTaskHandler; import com.openosrs.injector.injectors.CreateAnnotations; @@ -27,6 +28,7 @@ import com.openosrs.injector.transformers.Java8Ifier; import com.openosrs.injector.transformers.SourceChanger; import java.io.File; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Comparator; @@ -78,7 +80,8 @@ public class Injector extends InjectData implements InjectTaskHandler OptionSet options = parser.parse(args); String oprsVer = options.valueOf(oprsVerOption); - injector.vanilla = load(options.valueOf(vanillaFileOption)); + File vanillaFile = options.valueOf(vanillaFileOption); + injector.vanilla = load(vanillaFile); injector.deobfuscated = load( new File("../runescape-client/build/libs/runescape-client-" + oprsVer + ".jar")); injector.rsApi = new RSApi(Objects.requireNonNull( @@ -95,7 +98,7 @@ public class Injector extends InjectData implements InjectTaskHandler injector.initToVanilla(); injector.injectVanilla(); - save(injector.getVanilla(), options.valueOf(outFileOption), options.valueOf(outModeOption)); + save(injector.getVanilla(), options.valueOf(outFileOption), options.valueOf(outModeOption), vanillaFile); } public void injectVanilla() @@ -179,7 +182,7 @@ public class Injector extends InjectData implements InjectTaskHandler log.lifecycle("{} {}", name, transformer.getCompletionMsg()); } - private static void save(ClassGroup group, File output, OutputMode mode) + private static void save(ClassGroup group, File output, OutputMode mode, File vanillaFile) { if (output.exists()) { @@ -189,7 +192,7 @@ public class Injector extends InjectData implements InjectTaskHandler } catch (IOException e) { - log.info("Failed to delete output directory contents."); + log.lifecycle("Failed to delete output directory contents."); throw new RuntimeException(e); } } @@ -204,6 +207,18 @@ public class Injector extends InjectData implements InjectTaskHandler JarUtil.save(group, output); break; } + + try + { + String hash = com.google.common.io.Files.asByteSource(vanillaFile).hash(Hashing.sha256()).toString(); + log.lifecycle("Writing vanilla hash: {}", hash); + Files.write(output.getParentFile().toPath().resolve("client.hash"), hash.getBytes(StandardCharsets.UTF_8)); + } + catch (IOException ex) + { + log.lifecycle("Failed to write vanilla hash file"); + throw new RuntimeException(ex); + } } private static void saveFiles(ClassGroup group, File outDir) 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 27324dcbe2..c263d51aa7 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 @@ -27,7 +27,9 @@ package net.runelite.client.rs; import com.google.common.base.Strings; +import com.google.common.hash.Hashing; import com.google.common.io.ByteStreams; +import com.openosrs.client.OpenOSRS; import java.applet.Applet; import java.io.ByteArrayOutputStream; import java.io.File; @@ -37,6 +39,8 @@ import java.io.OutputStream; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.StandardOpenOption; import java.security.cert.Certificate; import java.security.cert.CertificateException; @@ -57,6 +61,7 @@ import net.runelite.client.RuneLite; import net.runelite.client.RuneLiteProperties; import static net.runelite.client.rs.ClientUpdateCheckMode.AUTO; import static net.runelite.client.rs.ClientUpdateCheckMode.NONE; +import static net.runelite.client.rs.ClientUpdateCheckMode.VANILLA; import net.runelite.client.ui.FatalErrorDialog; import net.runelite.client.ui.SplashScreen; import net.runelite.client.util.CountingInputStream; @@ -66,7 +71,6 @@ import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -import org.apache.commons.io.FileUtils; @Slf4j @SuppressWarnings("deprecation") @@ -130,18 +134,44 @@ public class ClientLoader implements Supplier StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); FileLock flock = lockfile.lock()) { - SplashScreen.stage(.40, null, "Loading client"); - File jarFile = updateCheckMode == AUTO ? PATCHED_CACHE : VANILLA_CACHE; - // create the classloader for the jar while we hold the lock, and eagerly load and link all classes - // in the jar. Otherwise the jar can change on disk and can break future classloads. - File oprsInjected = new File(System.getProperty("user.home") + "/.openosrs/cache/injected-client.jar"); - InputStream initialStream = RuneLite.class.getResourceAsStream(INJECTED_CLIENT_NAME); - if (!oprsInjected.exists() || oprsInjected.length() != RuneLite.class.getResource(INJECTED_CLIENT_NAME).getFile().length()) + SplashScreen.stage(.15, null, "Downloading Old School RuneScape"); + try { - FileUtils.copyInputStreamToFile(initialStream, oprsInjected); + updateVanilla(config); + } + catch (IOException ex) + { + // try again with the fallback config and gamepack + if (javConfigUrl.equals(RuneLiteProperties.getJavConfig()) && !config.isFallback()) + { + log.warn("Unable to download game client, attempting to use fallback config", ex); + config = downloadFallbackConfig(); + updateVanilla(config); + } + else + { + throw ex; + } } - classLoader = createJarClassLoader(oprsInjected); + if (!checkVanillaHash()) + { + log.error("Injected client vanilla hash doesn't match, loading vanilla client."); + updateCheckMode = VANILLA; + } + + SplashScreen.stage(.40, null, "Loading client"); + + File oprsInjected = new File(System.getProperty("user.home") + "/.openosrs/cache/injected-client.jar"); + if (updateCheckMode == AUTO) + { + writeInjectedClient(oprsInjected); + } + + File jarFile = updateCheckMode == AUTO ? oprsInjected : VANILLA_CACHE; + // create the classloader for the jar while we hold the lock, and eagerly load and link all classes + // in the jar. Otherwise the jar can change on disk and can break future classloads. + classLoader = createJarClassLoader(jarFile); } SplashScreen.stage(.465, "Starting", "Starting Old School RuneScape"); @@ -153,7 +183,7 @@ public class ClientLoader implements Supplier return rs; } catch (IOException | ClassNotFoundException | InstantiationException | IllegalAccessException - | SecurityException e) + | VerificationException | SecurityException e) { log.error("Error loading RS!", e); @@ -457,6 +487,48 @@ public class ClientLoader implements Supplier */ } + private boolean checkVanillaHash() + { + try (InputStream is = ClientLoader.class.getResourceAsStream("/client.hash")) + { + String storedHash = new String(is.readAllBytes(), StandardCharsets.UTF_8); + String vanillaHash = Hashing.sha256().hashBytes(Files.readAllBytes(VANILLA_CACHE.toPath())).toString(); + log.debug("Stored vanilla hash: {}", storedHash); + log.debug("Actual vanilla hash: {}", vanillaHash); + + return vanillaHash.equals(storedHash); + } + catch (IOException ex) + { + log.error("Failed to compare vanilla hashes, loading vanilla", ex); + } + + return false; + } + + private void writeInjectedClient(File cachedInjected) throws IOException + { + String cachedHash = ""; + try + { + cachedHash = com.google.common.io.Files.asByteSource(cachedInjected).hash(Hashing.sha256()).toString(); + } + catch (IOException ex) + { + log.error("Failed to calculate hash for cached file, falling back to vanilla", ex); + updateCheckMode = VANILLA; + return; + } + + byte[] currentInjected = ByteStreams.toByteArray(ClientLoader.class.getResourceAsStream(INJECTED_CLIENT_NAME)); + String currentHash = Hashing.sha256().hashBytes(currentInjected).toString(); + + if (!cachedInjected.exists() || !currentHash.equals(cachedHash)) + { + Files.write(cachedInjected.toPath(), currentInjected); + } + } + private ClassLoader createJarClassLoader(File jar) throws IOException, ClassNotFoundException { try (JarFile jarFile = new JarFile(jar)) @@ -529,7 +601,7 @@ public class ClientLoader implements Supplier if (rs instanceof Client) { - //.info("client-patch {}", ((Client) rs).getBuildID()); + log.info("injected-client {}", OpenOSRS.SYSTEM_VERSION); } return rs;