clientloader: don't lazy load client classes

Currently if the patched jar is modified on disk after the client
starts, the client can classload in from the modified jar instead, which
is problematic when running multiple different versions at the same
time.
This commit is contained in:
Adam
2019-12-05 12:01:22 -05:00
parent f90ad59a3d
commit 3e19d45673

View File

@@ -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<Applet>
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<Applet>
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<Applet>
}
}
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<JarEntry> 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));