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:
@@ -30,6 +30,7 @@ import com.google.archivepatcher.applier.FileByFileV1DeltaApplier;
|
|||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.hash.Hashing;
|
import com.google.common.hash.Hashing;
|
||||||
import com.google.common.hash.HashingOutputStream;
|
import com.google.common.hash.HashingOutputStream;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
import java.applet.Applet;
|
import java.applet.Applet;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -39,8 +40,6 @@ import java.io.FileOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLClassLoader;
|
|
||||||
import java.nio.channels.Channels;
|
import java.nio.channels.Channels;
|
||||||
import java.nio.channels.FileChannel;
|
import java.nio.channels.FileChannel;
|
||||||
import java.nio.channels.FileLock;
|
import java.nio.channels.FileLock;
|
||||||
@@ -51,8 +50,10 @@ import java.security.cert.CertificateFactory;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
import java.util.jar.JarInputStream;
|
import java.util.jar.JarInputStream;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
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");
|
SplashScreen.stage(.05, null, "Waiting for other clients to start");
|
||||||
|
|
||||||
LOCK_FILE.getParentFile().mkdirs();
|
LOCK_FILE.getParentFile().mkdirs();
|
||||||
|
ClassLoader classLoader;
|
||||||
try (FileChannel lockfile = FileChannel.open(LOCK_FILE.toPath(),
|
try (FileChannel lockfile = FileChannel.open(LOCK_FILE.toPath(),
|
||||||
StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
StandardOpenOption.CREATE, StandardOpenOption.READ, StandardOpenOption.WRITE);
|
||||||
FileLock flock = lockfile.lock())
|
FileLock flock = lockfile.lock())
|
||||||
@@ -132,14 +134,17 @@ public class ClientLoader implements Supplier<Applet>
|
|||||||
SplashScreen.stage(.35, null, "Patching");
|
SplashScreen.stage(.35, null, "Patching");
|
||||||
applyPatch();
|
applyPatch();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
File jarFile = updateCheckMode == AUTO ? PATCHED_CACHE : VANILLA_CACHE;
|
SplashScreen.stage(.40, null, "Loading client");
|
||||||
URL jar = jarFile.toURI().toURL();
|
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");
|
SplashScreen.stage(.465, "Starting", "Starting Old School RuneScape");
|
||||||
|
|
||||||
Applet rs = loadClient(jar);
|
Applet rs = loadClient(classLoader);
|
||||||
|
|
||||||
SplashScreen.stage(.5, null, "Starting core classes");
|
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();
|
String initialClass = config.getInitialClass();
|
||||||
Class<?> clientClass = rsClassLoader.loadClass(initialClass);
|
Class<?> clientClass = classLoader.loadClass(initialClass);
|
||||||
|
|
||||||
Applet rs = (Applet) clientClass.newInstance();
|
Applet rs = (Applet) clientClass.newInstance();
|
||||||
rs.setStub(new RSAppletStub(config));
|
rs.setStub(new RSAppletStub(config));
|
||||||
|
|||||||
Reference in New Issue
Block a user