diff --git a/build.xml b/build.xml
index cc80e15..8df4beb 100644
--- a/build.xml
+++ b/build.xml
@@ -27,22 +27,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -61,9 +45,6 @@
-
-
-
@@ -78,12 +59,9 @@
-
-
-
-
+
+
@@ -117,10 +95,10 @@
-
+
-
+
diff --git a/lib/LIBS b/lib/LIBS
deleted file mode 100644
index e2f70ea..0000000
--- a/lib/LIBS
+++ /dev/null
@@ -1,6 +0,0 @@
-samskivert.jar
-ant.jar
-commons-codec.jar
-commons-io.jar
-javaws.jar
-snark.jar
diff --git a/src/main/java/com/threerings/getdown/tools/Differ.java b/src/main/java/com/threerings/getdown/tools/Differ.java
index b746545..477ca66 100644
--- a/src/main/java/com/threerings/getdown/tools/Differ.java
+++ b/src/main/java/com/threerings/getdown/tools/Differ.java
@@ -40,8 +40,6 @@ import java.util.zip.ZipEntry;
import java.security.MessageDigest;
-import com.sun.javaws.jardiff.JarDiff;
-
import com.samskivert.io.StreamUtil;
import com.threerings.getdown.data.Application;
diff --git a/src/main/java/com/threerings/getdown/tools/JarDiff.java b/src/main/java/com/threerings/getdown/tools/JarDiff.java
new file mode 100644
index 0000000..c85903a
--- /dev/null
+++ b/src/main/java/com/threerings/getdown/tools/JarDiff.java
@@ -0,0 +1,493 @@
+/*
+ * @(#)JarDiff.java 1.7 05/11/17
+ *
+ * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * -Redistribution of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * -Redistribution in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of Sun Microsystems, Inc. or the names of contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * This software is provided "AS IS," without a warranty of any kind. ALL
+ * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
+ * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
+ * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
+ * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
+ * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
+ * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
+ * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
+ * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
+ * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
+ * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+ *
+ * You acknowledge that this software is not designed, licensed or intended
+ * for use in the design, construction, operation or maintenance of any
+ * nuclear facility.
+ */
+
+package com.threerings.getdown.tools;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.*;
+import java.util.zip.*;
+
+/**
+ * JarDiff is able to create a jar file containing the delta between two jar files (old and new).
+ * The delta jar file can then be applied to the old jar file to reconstruct the new jar file.
+ *
+ *
Refer to the JNLP spec for details on how this is done.
+ *
+ * @version 1.13, 06/26/03
+ */
+public class JarDiff {
+ public static final String VERSION_HEADER = "version 1.0";
+ public static final String INDEX_NAME = "META-INF/INDEX.JD";
+ public static final String REMOVE_COMMAND = "remove";
+ public static final String MOVE_COMMAND = "move";
+
+ private static final int DEFAULT_READ_SIZE = 2048;
+ private static byte[] newBytes = new byte[DEFAULT_READ_SIZE];
+ private static byte[] oldBytes = new byte[DEFAULT_READ_SIZE];
+
+ // The JARDiff.java is the stand-alone jardiff.jar tool. Thus, we do not depend on Globals.java
+ // and other stuff here. Instead, we use an explicit _debug flag.
+ private static boolean _debug;
+
+ /**
+ * Creates a patch from the two passed in files, writing the result to os.
+ */
+ public static void createPatch (String oldPath, String newPath,
+ OutputStream os, boolean minimal) throws IOException
+ {
+ JarFile2 oldJar = new JarFile2(oldPath);
+ JarFile2 newJar = new JarFile2(newPath);
+
+ try {
+ HashMap moved = new HashMap();
+ HashSet implicit = new HashSet();
+ HashSet moveSrc = new HashSet();
+ HashSet newEntries = new HashSet();
+
+ // FIRST PASS
+ // Go through the entries in new jar and
+ // determine which files are candidates for implicit moves
+ // ( files that has the same filename and same content in old.jar
+ // and new.jar )
+ // and for files that cannot be implicitly moved, we will either
+ // find out whether it is moved or new (modified)
+ for (JarEntry newEntry : newJar) {
+ String newname = newEntry.getName();
+
+ // Return best match of contents, will return a name match if possible
+ String oldname = oldJar.getBestMatch(newJar, newEntry);
+ if (oldname == null) {
+ // New or modified entry
+ if (_debug) {
+ System.out.println("NEW: "+ newname);
+ }
+ newEntries.add(newname);
+ } else {
+ // Content already exist - need to do a move
+
+ // Should do implicit move? Yes, if names are the same, and
+ // no move command already exist from oldJar
+ if (oldname.equals(newname) && !moveSrc.contains(oldname)) {
+ if (_debug) {
+ System.out.println(newname + " added to implicit set!");
+ }
+ implicit.add(newname);
+ } else {
+ // The 1.0.1/1.0 JarDiffPatcher cannot handle
+ // multiple MOVE command with same src.
+ // The work around here is if we are going to generate
+ // a MOVE command with duplicate src, we will
+ // instead add the target as a new file. This way
+ // the jardiff can be applied by 1.0.1/1.0
+ // JarDiffPatcher also.
+ if (!minimal && (implicit.contains(oldname) ||
+ moveSrc.contains(oldname) )) {
+
+ // generate non-minimal jardiff
+ // for backward compatibility
+
+ if (_debug) {
+
+ System.out.println("NEW: "+ newname);
+ }
+ newEntries.add(newname);
+ } else {
+ // Use newname as key, since they are unique
+ if (_debug) {
+ System.err.println("moved.put " + newname + " " + oldname);
+ }
+ moved.put(newname, oldname);
+ moveSrc.add(oldname);
+ }
+ // Check if this disables an implicit 'move '
+ if (implicit.contains(oldname) && minimal) {
+
+ if (_debug) {
+ System.err.println("implicit.remove " + oldname);
+
+ System.err.println("moved.put " + oldname + " " + oldname);
+
+ }
+ implicit.remove(oldname);
+ moved.put(oldname, oldname);
+ moveSrc.add(oldname);
+ }
+
+
+ }
+ }
+ }
+
+ // SECOND PASS: = - -
+ // -
+ ArrayList deleted = new ArrayList();
+ for (JarEntry oldEntry : oldJar) {
+ String oldName = oldEntry.getName();
+ if (!implicit.contains(oldName) && !moveSrc.contains(oldName)
+ && !newEntries.contains(oldName)) {
+ if (_debug) {
+ System.err.println("deleted.add " + oldName);
+ }
+ deleted.add(oldName);
+ }
+ }
+
+ //DEBUG
+ if (_debug) {
+ //DEBUG: print out moved map
+ System.out.println("MOVED MAP!!!");
+ for (String newName : moved.keySet()) {
+ String oldName = moved.get(newName);
+ System.out.println("key is " + newName + " value is " + oldName);
+ }
+
+ //DEBUG: print out IMOVE map
+ System.out.println("IMOVE MAP!!!");
+ for (String newName : implicit) {
+ System.out.println("key is " + newName);
+ }
+ }
+
+ JarOutputStream jos = new JarOutputStream(os);
+
+ // Write out all the MOVEs and REMOVEs
+ createIndex(jos, deleted, moved);
+
+ // Put in New and Modified entries
+ for (String newName : newEntries) {
+ if (_debug) {
+ System.out.println("New File: " + newName);
+ }
+ writeEntry(jos, newJar.getEntryByName(newName), newJar);
+ }
+
+ jos.finish();
+ jos.close();
+
+ } catch (IOException ioE){
+ throw ioE;
+ } finally {
+ try {
+ oldJar.getJarFile().close();
+ } catch (IOException e1) {
+ //ignore
+ }
+ try {
+ newJar.getJarFile().close();
+ } catch (IOException e1) {
+ //ignore
+ }
+ } // finally
+ }
+
+ /**
+ * Writes the index file out to jos.
+ * oldEntries gives the names of the files that were removed,
+ * movedMap maps from the new name to the old name.
+ */
+ private static void createIndex (JarOutputStream jos, List oldEntries,
+ Map movedMap)
+ throws IOException
+ {
+ StringWriter writer = new StringWriter();
+
+ writer.write(VERSION_HEADER);
+ writer.write("\r\n");
+
+ // Write out entries that have been removed
+ for (String name : oldEntries) {
+ writer.write(REMOVE_COMMAND);
+ writer.write(" ");
+ writeEscapedString(writer, name);
+ writer.write("\r\n");
+ }
+
+ // And those that have moved
+ for (String newName : movedMap.keySet()) {
+ String oldName = movedMap.get(newName);
+ writer.write(MOVE_COMMAND);
+ writer.write(" ");
+ writeEscapedString(writer, oldName);
+ writer.write(" ");
+ writeEscapedString(writer, newName);
+ writer.write("\r\n");
+ }
+
+ JarEntry je = new JarEntry(INDEX_NAME);
+ byte[] bytes = writer.toString().getBytes("UTF-8");
+
+ writer.close();
+ jos.putNextEntry(je);
+ jos.write(bytes, 0, bytes.length);
+ }
+
+ private static void writeEscapedString (Writer writer, String string)
+ throws IOException
+ {
+ int index = 0;
+ int last = 0;
+ char[] chars = null;
+
+ while ((index = string.indexOf(' ', index)) != -1) {
+ if (last != index) {
+ if (chars == null) {
+ chars = string.toCharArray();
+ }
+ writer.write(chars, last, index - last);
+ }
+ last = index;
+ index++;
+ writer.write('\\');
+ }
+ if (last != 0) {
+ writer.write(chars, last, chars.length - last);
+ }
+ else {
+ // no spaces
+ writer.write(string);
+ }
+ }
+
+ private static void writeEntry (JarOutputStream jos, JarEntry entry, JarFile2 file)
+ throws IOException
+ {
+ writeEntry(jos, entry, file.getJarFile().getInputStream(entry));
+ }
+
+ private static void writeEntry (JarOutputStream jos, JarEntry entry, InputStream data)
+ throws IOException
+ {
+ jos.putNextEntry(entry);
+
+ try {
+ // Read the entry
+ int size = data.read(newBytes);
+
+ while (size != -1) {
+ jos.write(newBytes, 0, size);
+ size = data.read(newBytes);
+ }
+ } catch(IOException ioE) {
+ throw ioE;
+ } finally {
+ try {
+ data.close();
+ } catch(IOException e){
+ //Ignore
+ }
+
+ }
+ }
+
+ /**
+ * JarFile2 wraps a JarFile providing some convenience methods.
+ */
+ private static class JarFile2 implements Iterable
+ {
+ private JarFile _jar;
+ private List _entries;
+ private HashMap _nameToEntryMap;
+ private HashMap> _crcToEntryMap;
+
+ public JarFile2 (String path) throws IOException {
+ _jar = new JarFile(new File(path));
+ index();
+ }
+
+ public JarFile getJarFile () {
+ return _jar;
+ }
+
+ // from interface Iterable
+ public Iterator iterator () {
+ return _entries.iterator();
+ }
+
+ public JarEntry getEntryByName (String name) {
+ return _nameToEntryMap.get(name);
+ }
+
+ /**
+ * Returns true if the two InputStreams differ.
+ */
+ private static boolean differs (InputStream oldIS, InputStream newIS) throws IOException {
+ int newSize = 0;
+ int oldSize;
+ int total = 0;
+ boolean retVal = false;
+
+ try{
+ while (newSize != -1) {
+ newSize = newIS.read(newBytes);
+ oldSize = oldIS.read(oldBytes);
+
+ if (newSize != oldSize) {
+ if (_debug) {
+ System.out.println("\tread sizes differ: " + newSize +
+ " " + oldSize + " total " + total);
+ }
+ retVal = true;
+ break;
+ }
+ if (newSize > 0) {
+ while (--newSize >= 0) {
+ total++;
+ if (newBytes[newSize] != oldBytes[newSize]) {
+ if (_debug) {
+ System.out.println("\tbytes differ at " +
+ total);
+ }
+ retVal = true;
+ break;
+ }
+ if ( retVal ) {
+ //Jump out
+ break;
+ }
+ newSize = 0;
+ }
+ }
+ }
+ } catch(IOException ioE){
+ throw ioE;
+ } finally {
+ try {
+ oldIS.close();
+ } catch(IOException e){
+ //Ignore
+ }
+ try {
+ newIS.close();
+ } catch(IOException e){
+ //Ignore
+ }
+ }
+ return retVal;
+ }
+
+ public String getBestMatch (JarFile2 file, JarEntry entry) throws IOException {
+ // check for same name and same content, return name if found
+ if (contains(file, entry)) {
+ return (entry.getName());
+ }
+
+ // return name of same content file or null
+ return (hasSameContent(file,entry));
+ }
+
+ public boolean contains (JarFile2 f, JarEntry e) throws IOException {
+
+ JarEntry thisEntry = getEntryByName(e.getName());
+
+ // Look up name in 'this' Jar2File - if not exist return false
+ if (thisEntry == null)
+ return false;
+
+ // Check CRC - if no match - return false
+ if (thisEntry.getCrc() != e.getCrc())
+ return false;
+
+ // Check contents - if no match - return false
+ InputStream oldIS = getJarFile().getInputStream(thisEntry);
+ InputStream newIS = f.getJarFile().getInputStream(e);
+ boolean retValue = differs(oldIS, newIS);
+
+ return !retValue;
+ }
+
+ public String hasSameContent (JarFile2 file, JarEntry entry) throws IOException {
+ String thisName = null;
+ Long crcL = new Long(entry.getCrc());
+ // check if this jar contains files with the passed in entry's crc
+ if (_crcToEntryMap.containsKey(crcL)) {
+ // get the Linked List with files with the crc
+ LinkedList ll = _crcToEntryMap.get(crcL);
+ // go through the list and check for content match
+ ListIterator li = ll.listIterator(0);
+ while (li.hasNext()) {
+ JarEntry thisEntry = li.next();
+ // check for content match
+ InputStream oldIS = getJarFile().getInputStream(thisEntry);
+ InputStream newIS = file.getJarFile().getInputStream(entry);
+ if (!differs(oldIS, newIS)) {
+ thisName = thisEntry.getName();
+ return thisName;
+ }
+ }
+ }
+ return thisName;
+ }
+
+ private void index () throws IOException {
+ Enumeration entries = _jar.entries();
+
+ _nameToEntryMap = new HashMap();
+ _crcToEntryMap = new HashMap>();
+ _entries = new ArrayList();
+ if (_debug) {
+ System.out.println("indexing: " + _jar.getName());
+ }
+ if (entries != null) {
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ long crc = entry.getCrc();
+ Long crcL = new Long(crc);
+ if (_debug) {
+ System.out.println("\t" + entry.getName() + " CRC " + crc);
+ }
+
+ _nameToEntryMap.put(entry.getName(), entry);
+ _entries.add(entry);
+
+ // generate the CRC to entries map
+ if (_crcToEntryMap.containsKey(crcL)) {
+ // key exist, add the entry to the correcponding linked list
+ LinkedList ll = _crcToEntryMap.get(crcL);
+ ll.add(entry);
+ _crcToEntryMap.put(crcL, ll);
+
+ } else {
+ // create a new entry in the hashmap for the new key
+ LinkedList ll = new LinkedList();
+ ll.add(entry);
+ _crcToEntryMap.put(crcL, ll);
+ }
+ }
+ }
+ }
+ }
+}