Forge xz library supported
This commit is contained in:
@@ -62,9 +62,7 @@ public final class GameLibrariesTask extends Task {
|
|||||||
version.getLibraries().stream().filter(Library::appliesToCurrentEnvironment).forEach(library -> {
|
version.getLibraries().stream().filter(Library::appliesToCurrentEnvironment).forEach(library -> {
|
||||||
File file = dependencyManager.getGameRepository().getLibraryFile(version, library);
|
File file = dependencyManager.getGameRepository().getLibraryFile(version, library);
|
||||||
if (!file.exists())
|
if (!file.exists())
|
||||||
dependencies.add(new FileDownloadTask(
|
dependencies.add(new LibraryDownloadTask(dependencyManager, file, library));
|
||||||
NetworkUtils.toURL(dependencyManager.getDownloadProvider().injectURL(library.getDownload().getUrl())),
|
|
||||||
file, dependencyManager.getProxy(), library.getDownload().getSha1()));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
package org.jackhuang.hmcl.download.game;
|
||||||
|
|
||||||
|
import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
|
||||||
|
import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||||
|
import org.jackhuang.hmcl.game.Library;
|
||||||
|
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||||
|
import org.jackhuang.hmcl.task.Scheduler;
|
||||||
|
import org.jackhuang.hmcl.task.Schedulers;
|
||||||
|
import org.jackhuang.hmcl.task.Task;
|
||||||
|
import org.jackhuang.hmcl.util.*;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarInputStream;
|
||||||
|
import java.util.jar.JarOutputStream;
|
||||||
|
import java.util.jar.Pack200;
|
||||||
|
|
||||||
|
public final class LibraryDownloadTask extends Task {
|
||||||
|
private final FileDownloadTask xzTask;
|
||||||
|
private final FileDownloadTask task;
|
||||||
|
private final File jar;
|
||||||
|
private final File xzFile;
|
||||||
|
private final Library library;
|
||||||
|
|
||||||
|
public LibraryDownloadTask(AbstractDependencyManager dependencyManager, File file, Library library) {
|
||||||
|
String url = dependencyManager.getDownloadProvider().injectURL(library.getDownload().getUrl());
|
||||||
|
jar = file;
|
||||||
|
this.library = library;
|
||||||
|
xzFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + ".pack.xz");
|
||||||
|
|
||||||
|
xzTask = new FileDownloadTask(NetworkUtils.toURL(url + ".pack.xz"),
|
||||||
|
xzFile, dependencyManager.getProxy(), null, 1);
|
||||||
|
|
||||||
|
task = new FileDownloadTask(NetworkUtils.toURL(url + ".pack.xz"),
|
||||||
|
file, dependencyManager.getProxy(), library.getDownload().getSha1());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends Task> getDependents() {
|
||||||
|
return Collections.singleton(xzTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRelyingOnDependents() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Scheduler getScheduler() {
|
||||||
|
return Schedulers.io();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute() throws Exception {
|
||||||
|
if (isDependentsSucceeded()) {
|
||||||
|
unpackLibrary(jar, FileUtils.readBytes(xzFile));
|
||||||
|
if (!checksumValid(jar, library.getChecksums()))
|
||||||
|
throw new IOException("Checksum failed for " + library);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends Task> getDependencies() {
|
||||||
|
return isDependentsSucceeded() ? Collections.emptySet() : Collections.singleton(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean checksumValid(File libPath, List<String> checksums) {
|
||||||
|
try {
|
||||||
|
if ((checksums == null) || (checksums.isEmpty())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
byte[] fileData = FileUtils.readBytes(libPath);
|
||||||
|
boolean valid = checksums.contains(DigestUtils.sha1Hex(fileData));
|
||||||
|
if ((!valid) && (libPath.getName().endsWith(".jar"))) {
|
||||||
|
}
|
||||||
|
return validateJar(fileData, checksums);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean validateJar(byte[] data, List<String> checksums) throws IOException {
|
||||||
|
HashMap<String, String> files = new HashMap<>();
|
||||||
|
String[] hashes = null;
|
||||||
|
JarInputStream jar = new JarInputStream(new ByteArrayInputStream(data));
|
||||||
|
JarEntry entry = jar.getNextJarEntry();
|
||||||
|
while (entry != null) {
|
||||||
|
byte[] eData = IOUtils.readFullyAsByteArray(jar);
|
||||||
|
if (entry.getName().equals("checksums.sha1")) {
|
||||||
|
hashes = new String(eData, Charset.forName("UTF-8")).split("\n");
|
||||||
|
}
|
||||||
|
if (!entry.isDirectory()) {
|
||||||
|
files.put(entry.getName(), DigestUtils.sha1Hex(eData));
|
||||||
|
}
|
||||||
|
entry = jar.getNextJarEntry();
|
||||||
|
}
|
||||||
|
jar.close();
|
||||||
|
if (hashes != null) {
|
||||||
|
boolean passed = !checksums.contains(files.get("checksums.sha1"));
|
||||||
|
if (passed) {
|
||||||
|
for (String hash : hashes) {
|
||||||
|
if ((!hash.trim().equals("")) && (hash.contains(" "))) {
|
||||||
|
String[] e = hash.split(" ");
|
||||||
|
String validChecksum = e[0];
|
||||||
|
String target = hash.substring(validChecksum.length() + 1);
|
||||||
|
String checksum = files.get(target);
|
||||||
|
if ((!files.containsKey(target)) || (checksum == null)) {
|
||||||
|
Logging.LOG.warning(" " + target + " : missing");
|
||||||
|
passed = false;
|
||||||
|
break;
|
||||||
|
} else if (!checksum.equals(validChecksum)) {
|
||||||
|
Logging.LOG.warning(" " + target + " : failed (" + checksum + ", " + validChecksum + ")");
|
||||||
|
passed = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return passed;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void unpackLibrary(File dest, byte[] src) throws IOException {
|
||||||
|
if (dest.exists())
|
||||||
|
if (!dest.delete())
|
||||||
|
throw new IOException("Unable to delete file " + dest);
|
||||||
|
|
||||||
|
byte[] decompressed = IOUtils.readFullyAsByteArray(new XZCompressorInputStream(new ByteArrayInputStream(src)));
|
||||||
|
|
||||||
|
String end = new String(decompressed, decompressed.length - 4, 4);
|
||||||
|
if (!end.equals("SIGN"))
|
||||||
|
throw new IOException("Unpacking failed, signature missing " + end);
|
||||||
|
|
||||||
|
int x = decompressed.length;
|
||||||
|
int len = decompressed[(x - 8)] & 0xFF | (decompressed[(x - 7)] & 0xFF) << 8 | (decompressed[(x - 6)] & 0xFF) << 16 | (decompressed[(x - 5)] & 0xFF) << 24;
|
||||||
|
|
||||||
|
File temp = FileUtils.createTempFile("minecraft", ".pack");
|
||||||
|
|
||||||
|
byte[] checksums = Arrays.copyOfRange(decompressed, decompressed.length - len - 8, decompressed.length - 8);
|
||||||
|
|
||||||
|
OutputStream out = new FileOutputStream(temp);
|
||||||
|
out.write(decompressed, 0, decompressed.length - len - 8);
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
try (FileOutputStream jarBytes = new FileOutputStream(dest); JarOutputStream jos = new JarOutputStream(jarBytes)) {
|
||||||
|
Pack200.newUnpacker().unpack(temp, jos);
|
||||||
|
|
||||||
|
JarEntry checksumsFile = new JarEntry("checksums.sha1");
|
||||||
|
checksumsFile.setTime(0L);
|
||||||
|
jos.putNextEntry(checksumsFile);
|
||||||
|
jos.write(checksums);
|
||||||
|
jos.closeEntry();
|
||||||
|
}
|
||||||
|
|
||||||
|
temp.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ public class ClassicVersion extends Version {
|
|||||||
public ClassicLibrary(String name) {
|
public ClassicLibrary(String name) {
|
||||||
super("", "", "", null, null,
|
super("", "", "", null, null,
|
||||||
new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null),
|
new LibrariesDownloadInfo(new LibraryDownloadInfo("bin/" + name + ".jar"), null),
|
||||||
false, null, null, null);
|
false, null, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ public class Library implements Comparable<Library> {
|
|||||||
private final boolean lateload;
|
private final boolean lateload;
|
||||||
private final Map<OperatingSystem, String> natives;
|
private final Map<OperatingSystem, String> natives;
|
||||||
private final List<CompatibilityRule> rules;
|
private final List<CompatibilityRule> rules;
|
||||||
|
private final List<String> checksums;
|
||||||
|
|
||||||
private final String path;
|
private final String path;
|
||||||
|
|
||||||
@@ -60,10 +61,10 @@ public class Library implements Comparable<Library> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload) {
|
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload) {
|
||||||
this(groupId, artifactId, version, classifier, url, downloads, lateload, null, null, null);
|
this(groupId, artifactId, version, classifier, url, downloads, lateload, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
public Library(String groupId, String artifactId, String version, String classifier, String url, LibrariesDownloadInfo downloads, boolean lateload, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||||
this.groupId = groupId;
|
this.groupId = groupId;
|
||||||
this.artifactId = artifactId;
|
this.artifactId = artifactId;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
@@ -80,6 +81,7 @@ public class Library implements Comparable<Library> {
|
|||||||
this.lateload = lateload;
|
this.lateload = lateload;
|
||||||
this.natives = natives;
|
this.natives = natives;
|
||||||
this.rules = rules;
|
this.rules = rules;
|
||||||
|
this.checksums = checksums;
|
||||||
|
|
||||||
LibraryDownloadInfo temp = null;
|
LibraryDownloadInfo temp = null;
|
||||||
if (downloads != null)
|
if (downloads != null)
|
||||||
@@ -141,6 +143,10 @@ public class Library implements Comparable<Library> {
|
|||||||
return lateload;
|
return lateload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getChecksums() {
|
||||||
|
return checksums;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new ToStringBuilder(this).append("name", getName()).toString();
|
return new ToStringBuilder(this).append("name", getName()).toString();
|
||||||
@@ -169,15 +175,15 @@ public class Library implements Comparable<Library> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Library fromName(String name) {
|
public static Library fromName(String name) {
|
||||||
return fromName(name, null, null, null, null, null);
|
return fromName(name, null, null, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Library fromName(String name, String url, LibrariesDownloadInfo downloads, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
public static Library fromName(String name, String url, LibrariesDownloadInfo downloads, List<String> checksums, ExtractRules extract, Map<OperatingSystem, String> natives, List<CompatibilityRule> rules) {
|
||||||
String[] arr = name.split(":", 4);
|
String[] arr = name.split(":", 4);
|
||||||
if (arr.length != 3 && arr.length != 4)
|
if (arr.length != 3 && arr.length != 4)
|
||||||
throw new IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version(:classifier).");
|
throw new IllegalArgumentException("Library name is malformed. Correct example: group:artifact:version(:classifier).");
|
||||||
|
|
||||||
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, false, extract, natives, rules);
|
return new Library(arr[0].replace("\\", "/"), arr[1], arr[2], arr.length >= 4 ? arr[3] : null, url, downloads, false, checksums, extract, natives, rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Serializer implements JsonDeserializer<Library>, JsonSerializer<Library> {
|
public static class Serializer implements JsonDeserializer<Library>, JsonSerializer<Library> {
|
||||||
@@ -198,6 +204,8 @@ public class Library implements Comparable<Library> {
|
|||||||
jsonObject.get("name").getAsString(),
|
jsonObject.get("name").getAsString(),
|
||||||
jsonObject.has("url") ? jsonObject.get("url").getAsString() : null,
|
jsonObject.has("url") ? jsonObject.get("url").getAsString() : null,
|
||||||
context.deserialize(jsonObject.get("downloads"), LibrariesDownloadInfo.class),
|
context.deserialize(jsonObject.get("downloads"), LibrariesDownloadInfo.class),
|
||||||
|
context.deserialize(jsonObject.get("checksums"), new TypeToken<List<String>>() {
|
||||||
|
}.getType()),
|
||||||
context.deserialize(jsonObject.get("extract"), ExtractRules.class),
|
context.deserialize(jsonObject.get("extract"), ExtractRules.class),
|
||||||
context.deserialize(jsonObject.get("natives"), new TypeToken<Map<OperatingSystem, String>>() {
|
context.deserialize(jsonObject.get("natives"), new TypeToken<Map<OperatingSystem, String>>() {
|
||||||
}.getType()),
|
}.getType()),
|
||||||
|
|||||||
@@ -153,7 +153,10 @@ public final class TaskExecutor {
|
|||||||
try {
|
try {
|
||||||
task.getScheduler().schedule(task::execute).get();
|
task.getScheduler().schedule(task::execute).get();
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
throw (Exception) e.getCause();
|
if (e.getCause() instanceof Exception)
|
||||||
|
throw (Exception) e.getCause();
|
||||||
|
else
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task instanceof TaskResult<?>) {
|
if (task instanceof TaskResult<?>) {
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ allprojects {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile "com.google.code.gson:gson:2.8.2"
|
compile "com.google.code.gson:gson:2.8.2"
|
||||||
compile "org.apache.commons:commons-compress:1.15"
|
compile "org.apache.commons:commons-compress:1.15"
|
||||||
|
compile "org.tukaani:xz:1.2"
|
||||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user