diff --git a/src/main/java/link/infra/packwiz/installer/UpdateManager.java b/src/main/java/link/infra/packwiz/installer/UpdateManager.java index 2b9e002..c640721 100644 --- a/src/main/java/link/infra/packwiz/installer/UpdateManager.java +++ b/src/main/java/link/infra/packwiz/installer/UpdateManager.java @@ -266,17 +266,21 @@ public class UpdateManager { } try { + Object hash; + String fileHashFormat; + if (f.linkedFile != null) { + hash = f.linkedFile.getHash(); + fileHashFormat = f.linkedFile.download.hashFormat; + } else { + hash = f.getHash(); + fileHashFormat = f.hashFormat; + } + Source src = f.getSource(indexUri); - GeneralHashingSource fileSource = HashUtils.getHasher(f.hashFormat).getHashingSource(src); + GeneralHashingSource fileSource = HashUtils.getHasher(fileHashFormat).getHashingSource(src); Buffer data = new Buffer(); Okio.buffer(fileSource).readAll(data); - Object hash; - if (f.linkedFile != null) { - hash = f.linkedFile.getHash(); - } else { - hash = f.getHash(); - } if (fileSource.hashIsEqual(hash)) { Files.createDirectories(Paths.get(opts.packFolder, f.getDestURI().toString()).getParent()); Files.copy(data.inputStream(), Paths.get(opts.packFolder, f.getDestURI().toString()), StandardCopyOption.REPLACE_EXISTING); @@ -338,6 +342,7 @@ public class UpdateManager { } else { progress = "Failed to download: " + ret.err.getMessage(); } + ret.err.printStackTrace(); } else if (ret.file != null) { progress = "Downloaded " + ret.file.getName(); } else { diff --git a/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java b/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java index 873ccc3..043ef3d 100644 --- a/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java +++ b/src/main/java/link/infra/packwiz/installer/metadata/IndexFile.java @@ -96,7 +96,8 @@ public class IndexFile { public URI getDestURI() { if (metafile && linkedFile != null) { - return file.resolve(linkedFile.filename); + // TODO: URIs are bad + return file.resolve(linkedFile.filename.replace(" ", "%20")); } else { return file; } diff --git a/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java b/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java index 118f5e7..a4fa417 100644 --- a/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java +++ b/src/main/java/link/infra/packwiz/installer/metadata/ModFile.java @@ -60,7 +60,7 @@ public class ModFile { if (download.hashFormat == null) { throw new Exception("Metadata file doesn't have a hash format"); } - return HashUtils.getHash(download.hash, download.hashFormat); + return HashUtils.getHash(download.hashFormat, download.hash); } public boolean isOptional() { diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java index ecd95bc..86452ed 100644 --- a/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java +++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/HashUtils.java @@ -6,13 +6,14 @@ import java.util.Map; public class HashUtils { private static final Map hashTypeConversion = new HashMap(); static { - hashTypeConversion.put("sha256", new HasherHashingSource("sha256")); + hashTypeConversion.put("sha256", new HashingSourceHasher("sha256")); + hashTypeConversion.put("murmur2", new Murmur2Hasher()); } public static IHasher getHasher(String type) throws Exception { IHasher hasher = hashTypeConversion.get(type); if (hasher == null) { - throw new Exception("Hash type not supported!"); + throw new Exception("Hash type not supported: " + type); } return hasher; } @@ -21,7 +22,8 @@ public class HashUtils { if (hashTypeConversion.containsKey(type)) { return hashTypeConversion.get(type).getHash(value); } - throw new Exception("Hash type not supported!"); + + throw new Exception("Hash type not supported: " + type); } } \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/HashingSourceHasher.java similarity index 94% rename from src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java rename to src/main/java/link/infra/packwiz/installer/metadata/hash/HashingSourceHasher.java index 7387c10..5df4211 100644 --- a/src/main/java/link/infra/packwiz/installer/metadata/hash/HasherHashingSource.java +++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/HashingSourceHasher.java @@ -3,10 +3,10 @@ package link.infra.packwiz.installer.metadata.hash; import okio.HashingSource; import okio.Source; -public class HasherHashingSource implements IHasher { +public class HashingSourceHasher implements IHasher { String type; - public HasherHashingSource(String type) { + public HashingSourceHasher(String type) { this.type = type; } diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/Murmur2Hasher.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/Murmur2Hasher.java new file mode 100644 index 0000000..ccdeff1 --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/Murmur2Hasher.java @@ -0,0 +1,94 @@ +package link.infra.packwiz.installer.metadata.hash; + +import java.io.IOException; + +import okio.Buffer; +import okio.Source; + +public class Murmur2Hasher implements IHasher { + private class Murmur2GeneralHashingSource extends GeneralHashingSource { + Murmur2Hash value; + Buffer internalBuffer = new Buffer(); + Buffer tempBuffer = new Buffer(); + Source delegate; + + public Murmur2GeneralHashingSource(Source delegate) { + super(delegate); + this.delegate = delegate; + } + + @Override + public long read(Buffer sink, long byteCount) throws IOException { + long out = delegate.read(tempBuffer, byteCount); + if (out > -1) { + sink.write(tempBuffer.clone(), out); + internalBuffer.write(tempBuffer, out); + } + return out; + } + + @Override + public Object getHash() { + if (value == null) { + byte[] data = computeNormalizedArray(internalBuffer.readByteArray()); + value = new Murmur2Hash(Murmur2Lib.hash32(data, data.length, 1)); + } + return value; + } + + // Credit to https://github.com/modmuss50/CAV2/blob/master/murmur.go + private byte[] computeNormalizedArray(byte[] input) { + byte[] output = new byte[input.length]; + int num = 0; + for (int i = 0; i < input.length; i++) { + byte b = input[i]; + if (!(b == 9 || b == 10 || b == 13 || b == 32)) { + output[num] = b; + num++; + } + } + byte[] outputTrimmed = new byte[num]; + System.arraycopy(output, 0, outputTrimmed, 0, num); + return outputTrimmed; + } + + } + + private class Murmur2Hash { + int value; + private Murmur2Hash(String value) { + // Parsing as long then casting to int converts values gt int max value but lt uint max value + // into negatives. I presume this is how the murmur2 code handles this. + this.value = (int)Long.parseLong(value); + } + + private Murmur2Hash(int value) { + this.value = value; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Murmur2Hash)) { + return false; + } + Murmur2Hash objHash = (Murmur2Hash) obj; + return value == objHash.value; + } + + @Override + public String toString() { + return "murmur2: " + value; + } + } + + @Override + public GeneralHashingSource getHashingSource(Source delegate) { + return new Murmur2GeneralHashingSource(delegate); + } + + @Override + public Object getHash(String value) { + return new Murmur2Hash(value); + } + +} \ No newline at end of file diff --git a/src/main/java/link/infra/packwiz/installer/metadata/hash/Murmur2Lib.java b/src/main/java/link/infra/packwiz/installer/metadata/hash/Murmur2Lib.java new file mode 100644 index 0000000..bb50d6e --- /dev/null +++ b/src/main/java/link/infra/packwiz/installer/metadata/hash/Murmur2Lib.java @@ -0,0 +1,166 @@ +// Obtained from https://github.com/prasanthj/hasher/blob/master/src/main/java/hasher/Murmur2.java +/** + * Copyright 2014 Prasanth Jayachandran + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package link.infra.packwiz.installer.metadata.hash; + +/** + * Murmur2 32 and 64 bit variants. + * 32-bit Java port of https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp#37 + * 64-bit Java port of https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash2.cpp#96 + */ +public class Murmur2Lib { + // Constants for 32-bit variant + private static final int M_32 = 0x5bd1e995; + private static final int R_32 = 24; + + // Constants for 64-bit variant + private static final long M_64 = 0xc6a4a7935bd1e995L; + private static final int R_64 = 47; + private static final int DEFAULT_SEED = 0; + + /** + * Murmur2 32-bit variant. + * + * @param data - input byte array + * @return - hashcode + */ + public static int hash32(byte[] data) { + return hash32(data, data.length, DEFAULT_SEED); + } + + /** + * Murmur2 32-bit variant. + * + * @param data - input byte array + * @param length - length of array + * @param seed - seed. (default 0) + * @return - hashcode + */ + public static int hash32(byte[] data, int length, int seed) { + int h = seed ^ length; + int len_4 = length >> 2; + + // body + for (int i = 0; i < len_4; i++) { + int i_4 = i << 2; + int k = (data[i_4] & 0xff) + | ((data[i_4 + 1] & 0xff) << 8) + | ((data[i_4 + 2] & 0xff) << 16) + | ((data[i_4 + 3] & 0xff) << 24); + + // mix functions + k *= M_32; + k ^= k >>> R_32; + k *= M_32; + h *= M_32; + h ^= k; + } + + // tail + int len_m = len_4 << 2; + int left = length - len_m; + if (left != 0) { + if (left >= 3) { + h ^= (int) data[length - 3] << 16; + } + if (left >= 2) { + h ^= (int) data[length - 2] << 8; + } + if (left >= 1) { + h ^= (int) data[length - 1]; + } + + h *= M_32; + } + + // finalization + h ^= h >>> 13; + h *= M_32; + h ^= h >>> 15; + + return h; + } + + /** + * Murmur2 64-bit variant. + * + * @param data - input byte array + * @return - hashcode + */ + public static long hash64(final byte[] data) { + return hash64(data, data.length, DEFAULT_SEED); + } + + /** + * Murmur2 64-bit variant. + * + * @param data - input byte array + * @param length - length of array + * @param seed - seed. (default 0) + * @return - hashcode + */ + public static long hash64(final byte[] data, int length, int seed) { + long h = (seed & 0xffffffffl) ^ (length * M_64); + int length8 = length >> 3; + + // body + for (int i = 0; i < length8; i++) { + final int i8 = i << 3; + long k = ((long) data[i8] & 0xff) + | (((long) data[i8 + 1] & 0xff) << 8) + | (((long) data[i8 + 2] & 0xff) << 16) + | (((long) data[i8 + 3] & 0xff) << 24) + | (((long) data[i8 + 4] & 0xff) << 32) + | (((long) data[i8 + 5] & 0xff) << 40) + | (((long) data[i8 + 6] & 0xff) << 48) + | (((long) data[i8 + 7] & 0xff) << 56); + + // mix functions + k *= M_64; + k ^= k >>> R_64; + k *= M_64; + h ^= k; + h *= M_64; + } + + // tail + int tailStart = length8 << 3; + switch (length - tailStart) { + case 7: + h ^= (long) (data[tailStart + 6] & 0xff) << 48; + case 6: + h ^= (long) (data[tailStart + 5] & 0xff) << 40; + case 5: + h ^= (long) (data[tailStart + 4] & 0xff) << 32; + case 4: + h ^= (long) (data[tailStart + 3] & 0xff) << 24; + case 3: + h ^= (long) (data[tailStart + 2] & 0xff) << 16; + case 2: + h ^= (long) (data[tailStart + 1] & 0xff) << 8; + case 1: + h ^= (long) (data[tailStart] & 0xff); + h *= M_64; + } + + // finalization + h ^= h >>> R_64; + h *= M_64; + h ^= h >>> R_64; + + return h; + } +} \ No newline at end of file