Compare commits
7
Commits
v3.5.6.241
...
v3.5.6.242
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b88d7c4fd2 | ||
|
|
c0838468b1 | ||
|
|
90cfe3d0b7 | ||
|
|
baf1d22bc4 | ||
|
|
7ca5744559 | ||
|
|
57e728991b | ||
|
|
e08ba4d421 |
No files matched your search
@@ -55,8 +55,8 @@ public final class Main {
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("java.net.useSystemProxies", "true");
|
||||
System.setProperty("javafx.autoproxy.disable", "true");
|
||||
System.getProperties().putIfAbsent("java.net.useSystemProxies", "true");
|
||||
System.getProperties().putIfAbsent("javafx.autoproxy.disable", "true");
|
||||
System.getProperties().putIfAbsent("http.agent", "HMCL/" + Metadata.VERSION);
|
||||
|
||||
checkDirectoryPath();
|
||||
|
||||
@@ -50,6 +50,10 @@ import org.jackhuang.hmcl.ui.wizard.WizardProvider;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -168,8 +172,23 @@ public class DecoratorController {
|
||||
break;
|
||||
case NETWORK:
|
||||
String backgroundImageUrl = config().getBackgroundImageUrl();
|
||||
if (backgroundImageUrl != null && NetworkUtils.isURL(backgroundImageUrl))
|
||||
image = tryLoadImage(backgroundImageUrl).orElse(null);
|
||||
if (backgroundImageUrl != null) {
|
||||
try {
|
||||
URLConnection connection = NetworkUtils.createConnection(new URL(backgroundImageUrl));
|
||||
if (connection instanceof HttpURLConnection) {
|
||||
connection = NetworkUtils.resolveConnection((HttpURLConnection) connection);
|
||||
}
|
||||
|
||||
try (InputStream input = connection.getInputStream()) {
|
||||
image = new Image(input);
|
||||
if (image.isError()) {
|
||||
throw image.getException();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.log(WARNING, "Couldn't load background image", e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CLASSIC:
|
||||
image = newBuiltinImage("/assets/img/background-classic.jpg");
|
||||
|
||||
@@ -164,7 +164,9 @@ public class VersionPage extends DecoratorAnimatedPage implements DecoratorPage
|
||||
}
|
||||
|
||||
private void clearAssets() {
|
||||
FileUtils.deleteDirectoryQuietly(new File(getProfile().getRepository().getBaseDirectory(), "assets"));
|
||||
File baseDirectory = getProfile().getRepository().getBaseDirectory();
|
||||
FileUtils.deleteDirectoryQuietly(new File(baseDirectory, "assets"));
|
||||
FileUtils.deleteDirectoryQuietly(new File(baseDirectory, "resources"));
|
||||
}
|
||||
|
||||
private void clearJunkFiles() {
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
package org.jackhuang.hmcl.download.game;
|
||||
|
||||
import org.jackhuang.hmcl.download.AbstractDependencyManager;
|
||||
import org.jackhuang.hmcl.download.ArtifactMalformedException;
|
||||
import org.jackhuang.hmcl.download.DefaultCacheRepository;
|
||||
import org.jackhuang.hmcl.game.Library;
|
||||
import org.jackhuang.hmcl.task.DownloadException;
|
||||
@@ -26,13 +25,12 @@ import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.DigestUtils;
|
||||
import org.jackhuang.hmcl.util.Pack200Utils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
import org.tukaani.xz.XZInputStream;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
@@ -41,7 +39,6 @@ import java.util.*;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
@@ -51,10 +48,8 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
protected final File jar;
|
||||
protected final DefaultCacheRepository cacheRepository;
|
||||
protected final AbstractDependencyManager dependencyManager;
|
||||
private final File xzFile;
|
||||
protected final Library library;
|
||||
protected final String url;
|
||||
protected boolean xz;
|
||||
private final Library originalLibrary;
|
||||
private boolean cached = false;
|
||||
|
||||
@@ -72,8 +67,6 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
|
||||
url = library.getDownload().getUrl();
|
||||
jar = file;
|
||||
|
||||
xzFile = new File(file.getAbsoluteFile().getParentFile(), file.getName() + ".pack.xz");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,8 +94,6 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
throw new CancellationException();
|
||||
else
|
||||
throw new LibraryDownloadException(library, t);
|
||||
} else {
|
||||
if (xz) unpackLibrary(jar, Files.readAllBytes(xzFile.toPath()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,36 +117,13 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
}
|
||||
}
|
||||
|
||||
if (Pack200Utils.isSupported() && testURLExistence(url)) {
|
||||
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url + ".pack.xz");
|
||||
task = new FileDownloadTask(urls, xzFile, null);
|
||||
task.setCacheRepository(cacheRepository);
|
||||
task.setCaching(true);
|
||||
xz = true;
|
||||
} else {
|
||||
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
|
||||
task = new FileDownloadTask(urls, jar,
|
||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
|
||||
task.setCacheRepository(cacheRepository);
|
||||
task.setCaching(true);
|
||||
task.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);
|
||||
xz = false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean testURLExistence(String rawUrl) {
|
||||
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(rawUrl);
|
||||
for (URL url : urls) {
|
||||
URL xzURL = NetworkUtils.toURL(url.toString() + ".pack.xz");
|
||||
for (int retry = 0; retry < 3; retry++) {
|
||||
try {
|
||||
return NetworkUtils.urlExists(xzURL);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to test for url existence: " + url + ".pack.xz", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false; // maybe some ugly implementation will give timeout for not existent url.
|
||||
List<URL> urls = dependencyManager.getDownloadProvider().injectURLWithCandidates(url);
|
||||
task = new FileDownloadTask(urls, jar,
|
||||
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
|
||||
task.setCacheRepository(cacheRepository);
|
||||
task.setCaching(true);
|
||||
task.addIntegrityCheckHandler(FileDownloadTask.ZIP_INTEGRITY_CHECK_HANDLER);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -167,7 +135,7 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
public void postExecute() throws Exception {
|
||||
if (!cached) {
|
||||
try {
|
||||
cacheRepository.cacheLibrary(library, jar.toPath(), xz);
|
||||
cacheRepository.cacheLibrary(library, jar.toPath(), false);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to cache downloaded library " + library, e);
|
||||
}
|
||||
@@ -232,44 +200,4 @@ public class LibraryDownloadTask extends Task<Void> {
|
||||
}
|
||||
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;
|
||||
try {
|
||||
decompressed = IOUtils.readFullyAsByteArray(new XZInputStream(new ByteArrayInputStream(src)));
|
||||
} catch (IOException e) {
|
||||
throw new ArtifactMalformedException("Library " + dest + " is malformed");
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Path temp = Files.createTempFile("minecraft", ".pack");
|
||||
|
||||
byte[] checksums = Arrays.copyOfRange(decompressed, decompressed.length - len - 8, decompressed.length - 8);
|
||||
|
||||
try (OutputStream out = Files.newOutputStream(temp)) {
|
||||
out.write(decompressed, 0, decompressed.length - len - 8);
|
||||
}
|
||||
|
||||
try (FileOutputStream jarBytes = new FileOutputStream(dest); JarOutputStream jos = new JarOutputStream(jarBytes)) {
|
||||
Pack200Utils.unpack(temp.toFile(), jos);
|
||||
|
||||
JarEntry checksumsFile = new JarEntry("checksums.sha1");
|
||||
checksumsFile.setTime(0L);
|
||||
jos.putNextEntry(checksumsFile);
|
||||
jos.write(checksums);
|
||||
jos.closeEntry();
|
||||
}
|
||||
|
||||
Files.delete(temp);
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,10 @@ public final class AssetIndex {
|
||||
return virtual || mapToResources;
|
||||
}
|
||||
|
||||
public boolean needMapToResources() {
|
||||
return mapToResources;
|
||||
}
|
||||
|
||||
public Map<String, AssetObject> getObjects() {
|
||||
return Collections.unmodifiableMap(objects);
|
||||
}
|
||||
|
||||
@@ -459,6 +459,8 @@ public class DefaultGameRepository implements GameRepository {
|
||||
return assetsDir;
|
||||
|
||||
if (index.isVirtual()) {
|
||||
Path resourcesDir = getBaseDirectory().toPath().resolve("resources");
|
||||
|
||||
int cnt = 0;
|
||||
int tot = index.getObjects().entrySet().size();
|
||||
for (Map.Entry<String, AssetObject> entry : index.getObjects().entrySet()) {
|
||||
@@ -468,6 +470,12 @@ public class DefaultGameRepository implements GameRepository {
|
||||
cnt++;
|
||||
if (!Files.isRegularFile(target))
|
||||
FileUtils.copyFile(original, target);
|
||||
|
||||
if (index.needMapToResources()) {
|
||||
target = resourcesDir.resolve(entry.getKey());
|
||||
if (!Files.isRegularFile(target))
|
||||
FileUtils.copyFile(original, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -111,9 +111,8 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
.header("X-API-KEY", apiKey)
|
||||
.getJson(new TypeToken<Response<List<CurseAddon>>>() {
|
||||
}.getType());
|
||||
Stream<RemoteMod> res = response.getData().stream().map(CurseAddon::toMod);
|
||||
if (searchFilter.isEmpty()) {
|
||||
return new SearchResult(res, (int)Math.ceil((double)response.pagination.totalCount / pageSize));
|
||||
return new SearchResult(response.getData().stream().map(CurseAddon::toMod), (int)Math.ceil((double)response.pagination.totalCount / pageSize));
|
||||
}
|
||||
|
||||
// https://github.com/HMCL-dev/HMCL/issues/1549
|
||||
@@ -125,7 +124,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
|
||||
StringUtils.LevCalculator levCalculator = new StringUtils.LevCalculator();
|
||||
|
||||
return new SearchResult(res.map(remoteMod -> {
|
||||
return new SearchResult(response.getData().stream().map(CurseAddon::toMod).map(remoteMod -> {
|
||||
String lowerCaseResult = remoteMod.getTitle().toLowerCase();
|
||||
int diff = levCalculator.calc(lowerCaseSearchFilter, lowerCaseResult);
|
||||
|
||||
@@ -136,7 +135,7 @@ public final class CurseForgeRemoteModRepository implements RemoteModRepository
|
||||
}
|
||||
|
||||
return pair(remoteMod, diff);
|
||||
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), res, response.pagination.totalCount);
|
||||
}).sorted(Comparator.comparingInt(Pair::getValue)).map(Pair::getKey), response.getData().stream().map(CurseAddon::toMod), response.pagination.totalCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -94,6 +94,7 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
break download;
|
||||
}
|
||||
|
||||
List<String> redirects = null;
|
||||
try {
|
||||
beforeDownload(url);
|
||||
|
||||
@@ -103,7 +104,9 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
if (checkETag) repository.injectConnection(conn);
|
||||
|
||||
if (conn instanceof HttpURLConnection) {
|
||||
conn = NetworkUtils.resolveConnection((HttpURLConnection) conn);
|
||||
redirects = new ArrayList<>();
|
||||
|
||||
conn = NetworkUtils.resolveConnection((HttpURLConnection) conn, redirects);
|
||||
int responseCode = ((HttpURLConnection) conn).getResponseCode();
|
||||
|
||||
if (responseCode == HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
@@ -164,13 +167,13 @@ public abstract class FetchTask<T> extends Task<T> {
|
||||
} catch (FileNotFoundException ex) {
|
||||
failedURL = url;
|
||||
exception = ex;
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", not found", ex);
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", not found" + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex);
|
||||
|
||||
break; // we will not try this URL again
|
||||
} catch (IOException ex) {
|
||||
failedURL = url;
|
||||
exception = ex;
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + (++repeat), ex);
|
||||
Logging.LOG.log(Level.WARNING, "Failed to download " + url + ", repeat times: " + (++repeat) + ((redirects == null || redirects.isEmpty()) ? "" : ", redirects: " + redirects), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,8 +293,12 @@ public class CacheRepository {
|
||||
ETagIndex indexOnDisk = JsonUtils.fromMaybeMalformedJson(new String(IOUtils.readFullyWithoutClosing(Channels.newInputStream(channel)), UTF_8), ETagIndex.class);
|
||||
Map<String, ETagItem> newIndex = joinETagIndexes(indexOnDisk == null ? null : indexOnDisk.eTag, index.values());
|
||||
channel.truncate(0);
|
||||
ETagIndex writeTo = new ETagIndex(newIndex.values());
|
||||
channel.write(ByteBuffer.wrap(JsonUtils.GSON.toJson(writeTo).getBytes(UTF_8)));
|
||||
ByteBuffer writeTo = ByteBuffer.wrap(JsonUtils.GSON.toJson(new ETagIndex(newIndex.values())).getBytes(UTF_8));
|
||||
while (writeTo.hasRemaining()) {
|
||||
if (channel.write(writeTo) == 0) {
|
||||
throw new IOException("No value is written");
|
||||
}
|
||||
}
|
||||
this.index = newIndex;
|
||||
} finally {
|
||||
lock.release();
|
||||
|
||||
@@ -29,7 +29,6 @@ import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.StringUtils.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class NetworkUtils {
|
||||
@@ -131,16 +130,20 @@ public final class NetworkUtils {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static HttpURLConnection resolveConnection(HttpURLConnection conn) throws IOException {
|
||||
return resolveConnection(conn, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is a work-around that aims to solve problem when "Location" in
|
||||
* stupid server's response is not encoded.
|
||||
*
|
||||
*
|
||||
* @see <a href="https://github.com/curl/curl/issues/473">Issue with libcurl</a>
|
||||
* @param conn the stupid http connection.
|
||||
* @return manually redirected http connection.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public static HttpURLConnection resolveConnection(HttpURLConnection conn) throws IOException {
|
||||
public static HttpURLConnection resolveConnection(HttpURLConnection conn, List<String> redirects) throws IOException {
|
||||
int redirect = 0;
|
||||
while (true) {
|
||||
conn.setUseCaches(false);
|
||||
@@ -154,6 +157,9 @@ public final class NetworkUtils {
|
||||
String newURL = conn.getHeaderField("Location");
|
||||
conn.disconnect();
|
||||
|
||||
if (redirects != null) {
|
||||
redirects.add(newURL);
|
||||
}
|
||||
if (redirect > 20) {
|
||||
throw new IOException("Too much redirects");
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user