clientloader: check vanilla hash before loading injected client

This commit is contained in:
ThatGamerBlue
2021-06-12 17:28:08 +01:00
parent 0e8b9c4191
commit 58a86899ee
2 changed files with 103 additions and 16 deletions

View File

@@ -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)

View File

@@ -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<Applet>
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<Applet>
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<Applet>
*/
}
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<Applet>
if (rs instanceof Client)
{
//.info("client-patch {}", ((Client) rs).getBuildID());
log.info("injected-client {}", OpenOSRS.SYSTEM_VERSION);
}
return rs;