summaryrefslogtreecommitdiff
path: root/src/net/minecraft/GameUpdater.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/net/minecraft/GameUpdater.java')
-rw-r--r--src/net/minecraft/GameUpdater.java707
1 files changed, 707 insertions, 0 deletions
diff --git a/src/net/minecraft/GameUpdater.java b/src/net/minecraft/GameUpdater.java
new file mode 100644
index 0000000..1fb7f50
--- /dev/null
+++ b/src/net/minecraft/GameUpdater.java
@@ -0,0 +1,707 @@
+package net.minecraft;
+
+import java.applet.Applet;
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.*;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.util.Enumeration;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Pack200;
+
+
+public class GameUpdater implements Runnable {
+ public static final int STATE_INIT = 1;
+ public static final int STATE_DETERMINING_PACKAGES = 2;
+ public static final int STATE_CHECKING_CACHE = 3;
+ public static final int STATE_DOWNLOADING = 4;
+ public static final int STATE_EXTRACTING_PACKAGES = 5;
+ public static final int STATE_UPDATING_CLASSPATH = 6;
+ public static final int STATE_SWITCHING_APPLET = 7;
+ public static final int STATE_INITIALIZE_REAL_APPLET = 8;
+ public static final int STATE_START_REAL_APPLET = 9;
+ public static final int STATE_DONE = 10;
+ public int percentage;
+ public int currentSizeDownload;
+ public int totalSizeDownload;
+ public int currentSizeExtract;
+ public int totalSizeExtract;
+ protected URL[] urlList;
+ private static ClassLoader classLoader;
+ protected Thread loaderThread;
+ protected Thread animationThread;
+ public boolean fatalError;
+ public String fatalErrorDescription;
+ protected String subtaskMessage = "";
+ protected int state = 1;
+
+ protected boolean lzmaSupported = false;
+
+ protected boolean pack200Supported = false;
+ protected String[] genericErrorMessage = new String[]{"An error occured while loading the applet.", "Please contact support to resolve this issue.", "<placeholder for error message>"};
+
+ protected boolean certificateRefused;
+
+ protected String[] certificateRefusedMessage = new String[]{"Permissions for Applet Refused.", "Please accept the permissions dialog to allow", "the applet to continue the loading process."};
+
+ protected static boolean natives_loaded = false;
+ private final String latestVersion;
+ private final String mainGameUrl;
+
+ public GameUpdater(String latestVersion, String mainGameUrl) {
+ this.latestVersion = latestVersion;
+ this.mainGameUrl = mainGameUrl;
+ }
+
+ public void init() {
+ this.state = 1;
+
+ try {
+ Class.forName("LZMA.LzmaInputStream");
+ this.lzmaSupported = true;
+ } catch (Throwable throwable) {
+ }
+
+
+ try {
+ Pack200.class.getSimpleName();
+ this.pack200Supported = true;
+ } catch (Throwable throwable) {
+ }
+ }
+
+
+ private String generateStacktrace(Exception exception) {
+ Writer result = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(result);
+ exception.printStackTrace(printWriter);
+ return result.toString();
+ }
+
+
+ protected String getDescriptionForState() {
+ switch (this.state) {
+ case 1:
+ return "Initializing loader";
+ case 2:
+ return "Determining packages to load";
+ case 3:
+ return "Checking cache for existing files";
+ case 4:
+ return "Downloading packages";
+ case 5:
+ return "Extracting downloaded packages";
+ case 6:
+ return "Updating classpath";
+ case 7:
+ return "Switching applet";
+ case 8:
+ return "Initializing real applet";
+ case 9:
+ return "Starting real applet";
+ case 10:
+ return "Done loading";
+ }
+ return "unknown state";
+ }
+
+
+ protected String trimExtensionByCapabilities(String file) {
+ if (!this.pack200Supported) {
+ file = file.replaceAll(".pack", "");
+ }
+
+ if (!this.lzmaSupported) {
+ file = file.replaceAll(".lzma", "");
+ }
+ return file;
+ }
+
+ protected void loadJarURLs() throws Exception {
+ this.state = 2;
+ String jarList = "lwjgl.jar, jinput.jar, lwjgl_util.jar, " + this.mainGameUrl;
+ jarList = trimExtensionByCapabilities(jarList);
+
+ StringTokenizer jar = new StringTokenizer(jarList, ", ");
+ int jarCount = jar.countTokens() + 1;
+
+ this.urlList = new URL[jarCount];
+
+ URL path = new URL("http://s3.amazonaws.com/MinecraftDownload/");
+
+ for (int i = 0; i < jarCount - 1; i++) {
+ this.urlList[i] = new URL(path, jar.nextToken());
+ }
+
+ String osName = System.getProperty("os.name");
+ String nativeJar = null;
+
+ if (osName.startsWith("Win")) {
+ nativeJar = "windows_natives.jar.lzma";
+ } else if (osName.startsWith("Linux")) {
+ nativeJar = "linux_natives.jar.lzma";
+ } else if (osName.startsWith("Mac")) {
+ nativeJar = "macosx_natives.jar.lzma";
+ } else if (osName.startsWith("Solaris") || osName.startsWith("SunOS")) {
+ nativeJar = "solaris_natives.jar.lzma";
+ } else {
+ fatalErrorOccured("OS (" + osName + ") not supported", null);
+ }
+
+ if (nativeJar == null) {
+ fatalErrorOccured("no lwjgl natives files found", null);
+ } else {
+ nativeJar = trimExtensionByCapabilities(nativeJar);
+ this.urlList[jarCount - 1] = new URL(path, nativeJar);
+ }
+ }
+
+
+ public void run() {
+ init();
+ this.state = 3;
+
+ this.percentage = 5;
+
+ try {
+ loadJarURLs();
+
+ String path = AccessController.doPrivileged(new PrivilegedExceptionAction<String>() {
+ public Object run() throws Exception {
+ return Util.getWorkingDirectory() + File.separator + "bin" + File.separator;
+ }
+ });
+
+ File dir = new File(path);
+
+ if (!dir.exists()) {
+ dir.mkdirs();
+ }
+
+ if (this.latestVersion != null) {
+ File versionFile = new File(dir, "version");
+
+ boolean cacheAvailable = false;
+ if (versionFile.exists() && (
+ this.latestVersion.equals("-1") || this.latestVersion.equals(readVersionFile(versionFile)))) {
+ cacheAvailable = true;
+ this.percentage = 90;
+ }
+
+
+ if (!cacheAvailable) {
+ downloadJars(path);
+ extractJars(path);
+ extractNatives(path);
+
+ if (this.latestVersion != null) {
+ this.percentage = 90;
+ writeVersionFile(versionFile, this.latestVersion);
+ }
+ }
+ }
+
+ updateClassPath(dir);
+ this.state = 10;
+ } catch (AccessControlException ace) {
+ fatalErrorOccured(ace.getMessage(), ace);
+ this.certificateRefused = true;
+ } catch (Exception e) {
+ fatalErrorOccured(e.getMessage(), e);
+ } finally {
+ this.loaderThread = null;
+ }
+ }
+
+ protected String readVersionFile(File file) throws Exception {
+ DataInputStream dis = new DataInputStream(new FileInputStream(file));
+ String version = dis.readUTF();
+ dis.close();
+ return version;
+ }
+
+ protected void writeVersionFile(File file, String version) throws Exception {
+ DataOutputStream dos = new DataOutputStream(new FileOutputStream(file));
+ dos.writeUTF(version);
+ dos.close();
+ }
+
+
+ protected void updateClassPath(File dir) throws Exception {
+ this.state = 6;
+
+ this.percentage = 95;
+
+ URL[] urls = new URL[this.urlList.length];
+ for (int i = 0; i < this.urlList.length; i++) {
+ urls[i] = (new File(dir, getJarName(this.urlList[i]))).toURI().toURL();
+ }
+
+ if (classLoader == null) {
+ classLoader = new URLClassLoader(urls) {
+ protected PermissionCollection getPermissions(CodeSource codesource) {
+ PermissionCollection perms = null;
+
+
+ try {
+ Method method = SecureClassLoader.class.getDeclaredMethod("getPermissions", CodeSource.class);
+ method.setAccessible(true);
+ perms = (PermissionCollection) method.invoke(getClass().getClassLoader(), new Object[]{codesource});
+
+ String host = "www.minecraft.net";
+
+ if (host != null && host.length() > 0) {
+ perms.add(new SocketPermission(host, "connect,accept"));
+ } else {
+ codesource.getLocation().getProtocol().equals("file");
+ }
+
+
+ perms.add(new FilePermission("<<ALL FILES>>", "read"));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return perms;
+ }
+ };
+ }
+
+ String path = dir.getAbsolutePath();
+ if (!path.endsWith(File.separator)) path = path + File.separator;
+ unloadNatives(path);
+
+ System.setProperty("org.lwjgl.librarypath", path + "natives");
+ System.setProperty("net.java.games.input.librarypath", path + "natives");
+
+ natives_loaded = true;
+ }
+
+
+ private void unloadNatives(String nativePath) {
+ if (!natives_loaded) {
+ return;
+ }
+
+ try {
+ Field field = ClassLoader.class.getDeclaredField("loadedLibraryNames");
+ field.setAccessible(true);
+ Vector<String> libs = (Vector<String>) field.get(getClass().getClassLoader());
+
+ String path = (new File(nativePath)).getCanonicalPath();
+
+ for (int i = 0; i < libs.size(); i++) {
+ String s = libs.get(i);
+
+ if (s.startsWith(path)) {
+ libs.remove(i);
+ i--;
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ public Applet createApplet() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ Class<Applet> appletClass = (Class) classLoader.loadClass("net.minecraft.client.MinecraftApplet");
+ return appletClass.newInstance();
+ }
+
+
+ protected void downloadJars(String path) throws Exception {
+ this.state = 4;
+
+
+ int[] fileSizes = new int[this.urlList.length];
+
+
+ for (int i = 0; i < this.urlList.length; i++) {
+ System.out.println(this.urlList[i]);
+ URLConnection urlconnection = this.urlList[i].openConnection();
+ urlconnection.setDefaultUseCaches(false);
+ if (urlconnection instanceof HttpURLConnection) {
+ ((HttpURLConnection) urlconnection).setRequestMethod("HEAD");
+ }
+ fileSizes[i] = urlconnection.getContentLength();
+ this.totalSizeDownload += fileSizes[i];
+ }
+
+ int initialPercentage = this.percentage = 10;
+
+
+ byte[] buffer = new byte[65536];
+ for (int j = 0; j < this.urlList.length; j++) {
+
+ int unsuccessfulAttempts = 0;
+ int maxUnsuccessfulAttempts = 3;
+ boolean downloadFile = true;
+
+
+ while (downloadFile) {
+ downloadFile = false;
+
+ URLConnection urlconnection = this.urlList[j].openConnection();
+
+ if (urlconnection instanceof HttpURLConnection) {
+ urlconnection.setRequestProperty("Cache-Control", "no-cache");
+ urlconnection.connect();
+ }
+
+ String currentFile = getFileName(this.urlList[j]);
+ InputStream inputstream = getJarInputStream(currentFile, urlconnection);
+ FileOutputStream fos = new FileOutputStream(path + currentFile);
+
+
+ long downloadStartTime = System.currentTimeMillis();
+ int downloadedAmount = 0;
+ int fileSize = 0;
+ String downloadSpeedMessage = "";
+ int bufferSize;
+ while ((bufferSize = inputstream.read(buffer, 0, buffer.length)) != -1) {
+ fos.write(buffer, 0, bufferSize);
+ this.currentSizeDownload += bufferSize;
+ fileSize += bufferSize;
+ this.percentage = initialPercentage + this.currentSizeDownload * 45 / this.totalSizeDownload;
+ this.subtaskMessage = "Retrieving: " + currentFile + " " + (this.currentSizeDownload * 100 / this.totalSizeDownload) + "%";
+
+ downloadedAmount += bufferSize;
+ long timeLapse = System.currentTimeMillis() - downloadStartTime;
+
+ if (timeLapse >= 1000L) {
+
+ float downloadSpeed = downloadedAmount / (float) timeLapse;
+
+ downloadSpeed = (int) (downloadSpeed * 100.0F) / 100.0F;
+
+ downloadSpeedMessage = " @ " + downloadSpeed + " KB/sec";
+
+ downloadedAmount = 0;
+
+ downloadStartTime += 1000L;
+ }
+
+ this.subtaskMessage = this.subtaskMessage + downloadSpeedMessage;
+ }
+
+ inputstream.close();
+ fos.close();
+
+
+ if (urlconnection instanceof HttpURLConnection &&
+ fileSize != fileSizes[j]) {
+ if (fileSizes[j] > 0) {
+
+
+ unsuccessfulAttempts++;
+
+ if (unsuccessfulAttempts < maxUnsuccessfulAttempts) {
+ downloadFile = true;
+ this.currentSizeDownload -= fileSize;
+ continue;
+ }
+ throw new Exception("failed to download " + currentFile);
+ }
+ }
+ }
+ }
+
+ this.subtaskMessage = "";
+ }
+
+
+ protected InputStream getJarInputStream(String currentFile, final URLConnection urlconnection) throws Exception {
+ final InputStream[] is = new InputStream[1];
+
+
+ for (int j = 0; j < 3 && is[0] == null; j++) {
+ Thread t = new Thread() {
+ public void run() {
+ try {
+ is[0] = urlconnection.getInputStream();
+ } catch (IOException iOException) {
+ }
+ }
+ };
+
+
+ t.setName("JarInputStreamThread");
+ t.start();
+
+ int iterationCount = 0;
+ while (is[0] == null && iterationCount++ < 5) {
+ try {
+ t.join(1000L);
+ } catch (InterruptedException interruptedException) {
+ }
+ }
+
+
+ if (is[0] == null) {
+ try {
+ t.interrupt();
+ t.join();
+ } catch (InterruptedException interruptedException) {
+ }
+ }
+ }
+
+
+ if (is[0] == null) {
+ if (currentFile.equals("minecraft.jar")) {
+ throw new Exception("Unable to download " + currentFile);
+ }
+ throw new Exception("Unable to download " + currentFile);
+ }
+
+
+ return is[0];
+ }
+
+
+ protected void extractLZMA(String in, String out) throws Exception {
+ File f = new File(in);
+ FileInputStream fileInputHandle = new FileInputStream(f);
+
+
+ Class<?> clazz = Class.forName("LZMA.LzmaInputStream");
+ Constructor<?> constructor = clazz.getDeclaredConstructor(InputStream.class);
+ InputStream inputHandle = (InputStream) constructor.newInstance(new Object[]{fileInputHandle});
+
+
+ OutputStream outputHandle = new FileOutputStream(out);
+
+ byte[] buffer = new byte[16384];
+
+ int ret = inputHandle.read(buffer);
+ while (ret >= 1) {
+ outputHandle.write(buffer, 0, ret);
+ ret = inputHandle.read(buffer);
+ }
+
+ inputHandle.close();
+ outputHandle.close();
+
+ outputHandle = null;
+ inputHandle = null;
+
+
+ f.delete();
+ }
+
+
+ protected void extractPack(String in, String out) throws Exception {
+ File f = new File(in);
+ FileOutputStream fostream = new FileOutputStream(out);
+ JarOutputStream jostream = new JarOutputStream(fostream);
+
+ Pack200.Unpacker unpacker = Pack200.newUnpacker();
+ unpacker.unpack(f, jostream);
+ jostream.close();
+
+
+ f.delete();
+ }
+
+
+ protected void extractJars(String path) throws Exception {
+ this.state = 5;
+
+ float increment = 10.0F / this.urlList.length;
+
+ for (int i = 0; i < this.urlList.length; i++) {
+ this.percentage = 55 + (int) (increment * (i + 1));
+ String filename = getFileName(this.urlList[i]);
+
+ if (filename.endsWith(".pack.lzma")) {
+ this.subtaskMessage = "Extracting: " + filename + " to " + filename.replaceAll(".lzma", "");
+ extractLZMA(path + filename, path + filename.replaceAll(".lzma", ""));
+
+ this.subtaskMessage = "Extracting: " + filename.replaceAll(".lzma", "") + " to " + filename.replaceAll(".pack.lzma", "");
+ extractPack(path + filename.replaceAll(".lzma", ""), path + filename.replaceAll(".pack.lzma", ""));
+ } else if (filename.endsWith(".pack")) {
+ this.subtaskMessage = "Extracting: " + filename + " to " + filename.replace(".pack", "");
+ extractPack(path + filename, path + filename.replace(".pack", ""));
+ } else if (filename.endsWith(".lzma")) {
+ this.subtaskMessage = "Extracting: " + filename + " to " + filename.replace(".lzma", "");
+ extractLZMA(path + filename, path + filename.replace(".lzma", ""));
+ }
+ }
+ }
+
+
+ protected void extractNatives(String path) throws Exception {
+ this.state = 5;
+
+ int initialPercentage = this.percentage;
+
+ String nativeJar = getJarName(this.urlList[this.urlList.length - 1]);
+
+ Certificate[] certificate = Launcher.class.getProtectionDomain().getCodeSource().getCertificates();
+
+ if (certificate == null) {
+ URL location = Launcher.class.getProtectionDomain().getCodeSource().getLocation();
+
+ JarURLConnection jurl = (JarURLConnection) (new URL("jar:" + location.toString() + "!/net/minecraft/Launcher.class")).openConnection();
+ jurl.setDefaultUseCaches(true);
+ try {
+ certificate = jurl.getCertificates();
+ } catch (Exception exception) {
+ }
+ }
+
+
+ File nativeFolder = new File(path + "natives");
+ if (!nativeFolder.exists()) {
+ nativeFolder.mkdir();
+ }
+
+ JarFile jarFile = new JarFile(path + nativeJar, true);
+ Enumeration<JarEntry> entities = jarFile.entries();
+
+ this.totalSizeExtract = 0;
+
+
+ while (entities.hasMoreElements()) {
+ JarEntry entry = entities.nextElement();
+
+
+ if (entry.isDirectory() || entry.getName().indexOf('/') != -1) {
+ continue;
+ }
+ this.totalSizeExtract = (int) (this.totalSizeExtract + entry.getSize());
+ }
+
+ this.currentSizeExtract = 0;
+
+ entities = jarFile.entries();
+
+ while (entities.hasMoreElements()) {
+ JarEntry entry = entities.nextElement();
+
+ if (entry.isDirectory() || entry.getName().indexOf('/') != -1) {
+ continue;
+ }
+
+ File file = new File(path + "natives" + File.separator + entry.getName());
+ if (file.exists() &&
+ !file.delete()) {
+ continue;
+ }
+
+
+ InputStream in = jarFile.getInputStream(jarFile.getEntry(entry.getName()));
+ OutputStream out = new FileOutputStream(path + "natives" + File.separator + entry.getName());
+
+
+ byte[] buffer = new byte[65536];
+ int bufferSize;
+ while ((bufferSize = in.read(buffer, 0, buffer.length)) != -1) {
+ out.write(buffer, 0, bufferSize);
+ this.currentSizeExtract += bufferSize;
+
+ this.percentage = initialPercentage + this.currentSizeExtract * 20 / this.totalSizeExtract;
+ this.subtaskMessage = "Extracting: " + entry.getName() + " " + (this.currentSizeExtract * 100 / this.totalSizeExtract) + "%";
+ }
+
+ validateCertificateChain(certificate, entry.getCertificates());
+
+ in.close();
+ out.close();
+ }
+ this.subtaskMessage = "";
+
+ jarFile.close();
+
+ File f = new File(path + nativeJar);
+ f.delete();
+ }
+
+
+ protected static void validateCertificateChain(Certificate[] ownCerts, Certificate[] native_certs) throws Exception {
+ if (ownCerts == null)
+ return;
+ if (native_certs == null)
+ throw new Exception("Unable to validate certificate chain. Native entry did not have a certificate chain at all");
+
+ if (ownCerts.length != native_certs.length)
+ throw new Exception("Unable to validate certificate chain. Chain differs in length [" + ownCerts.length + " vs " + native_certs.length + "]");
+
+ for (int i = 0; i < ownCerts.length; i++) {
+ if (!ownCerts[i].equals(native_certs[i])) {
+ throw new Exception("Certificate mismatch: " + ownCerts[i] + " != " + native_certs[i]);
+ }
+ }
+ }
+
+ protected String getJarName(URL url) {
+ String fileName = url.getFile();
+
+ if (fileName.contains("?")) {
+ fileName = fileName.substring(0, fileName.indexOf("?"));
+ }
+ if (fileName.endsWith(".pack.lzma")) {
+ fileName = fileName.replaceAll(".pack.lzma", "");
+ } else if (fileName.endsWith(".pack")) {
+ fileName = fileName.replaceAll(".pack", "");
+ } else if (fileName.endsWith(".lzma")) {
+ fileName = fileName.replaceAll(".lzma", "");
+ }
+
+ return fileName.substring(fileName.lastIndexOf('/') + 1);
+ }
+
+ protected String getFileName(URL url) {
+ String fileName = url.getFile();
+ if (fileName.contains("?")) {
+ fileName = fileName.substring(0, fileName.indexOf("?"));
+ }
+ return fileName.substring(fileName.lastIndexOf('/') + 1);
+ }
+
+ protected void fatalErrorOccured(String error, Exception e) {
+ e.printStackTrace();
+ this.fatalError = true;
+ this.fatalErrorDescription = "Fatal error occured (" + this.state + "): " + error;
+ System.out.println(this.fatalErrorDescription);
+ if (e != null) {
+ System.out.println(generateStacktrace(e));
+ }
+ }
+
+
+ public boolean canPlayOffline() {
+ try {
+ String path = AccessController.doPrivileged(new PrivilegedExceptionAction<String>() {
+ public Object run() throws Exception {
+ return Util.getWorkingDirectory() + File.separator + "bin" + File.separator;
+ }
+ });
+
+ File dir = new File(path);
+ if (!dir.exists()) return false;
+
+ dir = new File(dir, "version");
+ if (!dir.exists()) return false;
+
+ if (dir.exists()) {
+ String version = readVersionFile(dir);
+ if (version != null && version.length() > 0) {
+ return true;
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ return false;
+ }
+}