diff --git a/build.gradle b/build.gradle
index 54d564d..a825be4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,8 +8,9 @@ plugins {
 dependencies {
 	implementation 'commons-cli:commons-cli:1.4'
 	implementation 'com.moandjiezana.toml:toml4j:0.7.2'
+    // TODO: Implement tests
     //testImplementation 'junit:junit:4.12'
-    // TODO: add GSON, as toml4j depends on it anyway
+    implementation 'com.google.code.gson:gson:2.8.1'
 }
 
 repositories {
diff --git a/src/main/java/link/infra/packwiz/installer/UpdateManager.java b/src/main/java/link/infra/packwiz/installer/UpdateManager.java
index fa4617b..2e616e3 100644
--- a/src/main/java/link/infra/packwiz/installer/UpdateManager.java
+++ b/src/main/java/link/infra/packwiz/installer/UpdateManager.java
@@ -1,7 +1,20 @@
 package link.infra.packwiz.installer;
 
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
 import java.net.URI;
+import java.nio.file.Paths;
 
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonSyntaxException;
+
+import link.infra.packwiz.installer.metadata.HashInputStream;
+import link.infra.packwiz.installer.metadata.HashTypeAdapter;
+import link.infra.packwiz.installer.metadata.ManifestFile;
+import link.infra.packwiz.installer.request.HandlerManager;
 import link.infra.packwiz.installer.ui.IUserInterface;
 import link.infra.packwiz.installer.ui.InstallProgress;
 
@@ -71,7 +84,42 @@ public class UpdateManager {
 
 	protected void start() {
 		this.checkOptions();
+
+		ui.submitProgress(new InstallProgress("Loading manifest file..."));
+		Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(byte[].class, new HashTypeAdapter()).create();
+		ManifestFile manifest;
+		try {
+			manifest = gson.fromJson(new FileReader(Paths.get(opts.packFolder, opts.manifestFile).toString()),
+					ManifestFile.class);
+		} catch (FileNotFoundException e) {
+			manifest = new ManifestFile();
+		} catch (JsonSyntaxException | JsonIOException e) {
+			ui.handleExceptionAndExit(e);
+			return;
+		}
+
 		ui.submitProgress(new InstallProgress("Loading pack file..."));
+		HashInputStream packFileStream;
+		try {
+			packFileStream = new HashInputStream(HandlerManager.getFileInputStream(opts.downloadURI));
+		} catch (Exception e) {
+			ui.handleExceptionAndExit(e);
+			return;
+		}
+		// TODO: read file
+		try {
+			packFileStream.close();
+		} catch (IOException e) {
+			// TODO Auto-generated catch block
+			e.printStackTrace();
+		}
+		byte[] packFileHash = packFileStream.getHashValue();
+		if (packFileHash.equals(manifest.packFileHash)) {
+			// WOOO it's already up to date
+			// todo: --force?
+		}
+
+		// TODO: save manifest file
 	}
 
 	protected void checkOptions() {
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/HashInputStream.java b/src/main/java/link/infra/packwiz/installer/metadata/HashInputStream.java
new file mode 100644
index 0000000..5579d30
--- /dev/null
+++ b/src/main/java/link/infra/packwiz/installer/metadata/HashInputStream.java
@@ -0,0 +1,60 @@
+package link.infra.packwiz.installer.metadata;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class HashInputStream extends FilterInputStream {
+
+	private MessageDigest md;
+	private byte[] output;
+
+	public HashInputStream(InputStream in) throws NoSuchAlgorithmException {
+		super(in);
+		md = MessageDigest.getInstance("SHA-256");
+	}
+
+	@Override
+	public int read() throws IOException {
+		int value = super.read();
+		if (value == -1) {
+			return value;
+		}
+		md.update((byte) value);
+		return value;
+	}
+
+	@Override
+	public int read(byte[] b, int off, int len) throws IOException {
+		int bytesRead = super.read(b, off, len);
+		if (bytesRead > 0) {
+			md.update(b, off, len);
+		}
+		return bytesRead;
+	}
+
+	@Override
+	public void reset() throws IOException {
+		throw new IOException("HashInputStream doesn't support reset()");
+	}
+
+	@Override
+	public boolean markSupported() {
+		return false;
+	}
+
+	@Override
+	public void mark(int readlimit) {
+		// Do nothing
+	}
+
+	public byte[] getHashValue() {
+		if (output == null) {
+			output = md.digest();
+		}
+		return output;
+	}
+
+}
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/HashTypeAdapter.java b/src/main/java/link/infra/packwiz/installer/metadata/HashTypeAdapter.java
new file mode 100644
index 0000000..40baa21
--- /dev/null
+++ b/src/main/java/link/infra/packwiz/installer/metadata/HashTypeAdapter.java
@@ -0,0 +1,66 @@
+package link.infra.packwiz.installer.metadata;
+
+import java.lang.reflect.Type;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+public class HashTypeAdapter implements JsonSerializer<byte[]>, JsonDeserializer<byte[]> {
+
+	@Override
+	public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
+		return new JsonPrimitive(printHexBinary(src));
+	}
+
+	@Override
+	public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+			throws JsonParseException {
+		return parseHexBinary(json.getAsString());
+	}
+
+	// Why did Java remove this in 1.9????!
+	public static byte[] parseHexBinary(String s) {
+		final int len = s.length();
+	
+		// "111" is not a valid hex encoding.
+		if( len%2 != 0 )
+			throw new IllegalArgumentException("hexBinary needs to be even-length: "+s);
+	
+		byte[] out = new byte[len/2];
+	
+		for( int i=0; i<len; i+=2 ) {
+			int h = hexToBin(s.charAt(i  ));
+			int l = hexToBin(s.charAt(i+1));
+			if( h==-1 || l==-1 )
+				throw new IllegalArgumentException("contains illegal character for hexBinary: "+s);
+	
+			out[i/2] = (byte)(h*16+l);
+		}
+	
+		return out;
+	}
+	
+	private static int hexToBin( char ch ) {
+		if( '0'<=ch && ch<='9' )    return ch-'0';
+		if( 'A'<=ch && ch<='F' )    return ch-'A'+10;
+		if( 'a'<=ch && ch<='f' )    return ch-'a'+10;
+		return -1;
+	}
+	
+	private static final char[] hexCode = "0123456789ABCDEF".toCharArray();
+	
+	public static String printHexBinary(byte[] data) {
+		StringBuilder r = new StringBuilder(data.length*2);
+		for ( byte b : data) {
+			r.append(hexCode[(b >> 4) & 0xF]);
+			r.append(hexCode[(b & 0xF)]);
+		}
+		return r.toString();
+	}
+
+}
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java b/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java
new file mode 100644
index 0000000..e0b3637
--- /dev/null
+++ b/src/main/java/link/infra/packwiz/installer/metadata/ManifestFile.java
@@ -0,0 +1,5 @@
+package link.infra.packwiz.installer.metadata;
+
+public class ManifestFile {
+	public byte[] packFileHash = null;
+}
\ No newline at end of file
diff --git a/src/main/java/link/infra/packwiz/installer/metadata/PackFile.java b/src/main/java/link/infra/packwiz/installer/metadata/PackFile.java
new file mode 100644
index 0000000..d13ae09
--- /dev/null
+++ b/src/main/java/link/infra/packwiz/installer/metadata/PackFile.java
@@ -0,0 +1,5 @@
+package link.infra.packwiz.installer.metadata;
+
+public class PackFile {
+	
+}
\ No newline at end of file