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 fb04be2ec2..10c9f76bc8 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 @@ -30,6 +30,7 @@ import com.google.archivepatcher.applier.FileByFileV1DeltaApplier; import com.google.common.base.Strings; import com.google.common.hash.Hashing; import com.google.common.hash.HashingOutputStream; +import com.google.common.io.ByteStreams; import com.google.common.io.Files; import java.applet.Applet; import java.io.ByteArrayOutputStream; @@ -39,8 +40,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URL; -import java.net.URLClassLoader; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; @@ -51,8 +50,10 @@ import java.security.cert.CertificateFactory; import java.util.Arrays; import java.util.Collection; import java.util.Map; +import java.util.Enumeration; import java.util.function.Supplier; import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.jar.JarInputStream; import javax.swing.SwingUtilities; import lombok.extern.slf4j.Slf4j; @@ -120,6 +121,7 @@ public class ClientLoader implements Supplier SplashScreen.stage(.05, null, "Waiting for other clients to start"); LOCK_FILE.getParentFile().mkdirs(); + ClassLoader classLoader; try (FileChannel lockfile = FileChannel.open(LOCK_FILE.toPath(), StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE); FileLock flock = lockfile.lock()) @@ -132,14 +134,17 @@ public class ClientLoader implements Supplier SplashScreen.stage(.35, null, "Patching"); applyPatch(); } - } - File jarFile = updateCheckMode == AUTO ? PATCHED_CACHE : VANILLA_CACHE; - URL jar = jarFile.toURI().toURL(); + 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. + classLoader = createJarClassLoader(jarFile); + } SplashScreen.stage(.465, "Starting", "Starting Old School RuneScape"); - Applet rs = loadClient(jar); + Applet rs = loadClient(classLoader); SplashScreen.stage(.5, null, "Starting core classes"); @@ -424,12 +429,62 @@ public class ClientLoader implements Supplier } } - private Applet loadClient(URL url) throws ClassNotFoundException, IllegalAccessException, InstantiationException + private ClassLoader createJarClassLoader(File jar) throws IOException, ClassNotFoundException { - URLClassLoader rsClassLoader = new URLClassLoader(new URL[]{url}, ClientLoader.class.getClassLoader()); + try (JarFile jarFile = new JarFile(jar)) + { + ClassLoader classLoader = new ClassLoader(ClientLoader.class.getClassLoader()) + { + @Override + protected Class findClass(String name) throws ClassNotFoundException + { + String entryName = name.replace('.', '/').concat(".class"); + JarEntry jarEntry = jarFile.getJarEntry(entryName); + if (jarEntry == null) + { + throw new ClassNotFoundException(name); + } + try + { + InputStream inputStream = jarFile.getInputStream(jarEntry); + if (inputStream == null) + { + throw new ClassNotFoundException(name); + } + + byte[] bytes = ByteStreams.toByteArray(inputStream); + return defineClass(name, bytes, 0, bytes.length); + } + catch (IOException e) + { + throw new ClassNotFoundException(null, e); + } + } + }; + + // load all of the classes in this jar; after the jar is closed the classloader + // will no longer be able to look up classes + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) + { + JarEntry jarEntry = entries.nextElement(); + String name = jarEntry.getName(); + if (name.endsWith(".class")) + { + name = name.substring(0, name.length() - 6); + classLoader.loadClass(name); + } + } + + return classLoader; + } + } + + private Applet loadClient(ClassLoader classLoader) throws ClassNotFoundException, IllegalAccessException, InstantiationException + { String initialClass = config.getInitialClass(); - Class clientClass = rsClassLoader.loadClass(initialClass); + Class clientClass = classLoader.loadClass(initialClass); Applet rs = (Applet) clientClass.newInstance(); rs.setStub(new RSAppletStub(config));