geforkt von Mirrors/FastAsyncWorldEdit
Retrieve the latest asset release version automatically (#1947)
* feat: retrieve the latest release version automatically (cherry picked from commit a8885a349a567849f6db29565cc65b14e3dab155) * feat/fix: validate hash of downloaded asset file * chore: address review comments Usage of nio Path instead of direct File access Usage of HexFormat instead of custom implementation No need for usage of channels * chore: simplified sha-1 calculation logic Co-authored-by: Pierre Maurice Schwang <mail@pschwang.eu>
Dieser Commit ist enthalten in:
Ursprung
c819031d1f
Commit
1706d64ddf
@ -5,6 +5,9 @@ import com.fastasyncworldedit.core.configuration.Settings;
|
|||||||
import com.fastasyncworldedit.core.extent.filter.block.SingleFilterBlock;
|
import com.fastasyncworldedit.core.extent.filter.block.SingleFilterBlock;
|
||||||
import com.fastasyncworldedit.core.util.image.ImageUtil;
|
import com.fastasyncworldedit.core.util.image.ImageUtil;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
import com.google.gson.stream.JsonReader;
|
import com.google.gson.stream.JsonReader;
|
||||||
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
import com.sk89q.worldedit.extent.clipboard.Clipboard;
|
||||||
@ -24,19 +27,27 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.security.DigestOutputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.HexFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -321,6 +332,8 @@ public class TextureUtil implements TextureHolder {
|
|||||||
new BiomeColor(253, "Unknown Biome", 0.8f, 0.4f, 0x92BD59, 0x77AB2F),
|
new BiomeColor(253, "Unknown Biome", 0.8f, 0.4f, 0x92BD59, 0x77AB2F),
|
||||||
new BiomeColor(254, "Unknown Biome", 0.8f, 0.4f, 0x92BD59, 0x77AB2F),
|
new BiomeColor(254, "Unknown Biome", 0.8f, 0.4f, 0x92BD59, 0x77AB2F),
|
||||||
new BiomeColor(255, "Unknown Biome", 0.8f, 0.4f, 0x92BD59, 0x77AB2F)};
|
new BiomeColor(255, "Unknown Biome", 0.8f, 0.4f, 0x92BD59, 0x77AB2F)};
|
||||||
|
|
||||||
|
private static final String VERSION_MANIFEST = "https://piston-meta.mojang.com/mc/game/version_manifest.json";
|
||||||
private final BlockType[] layerBuffer = new BlockType[2];
|
private final BlockType[] layerBuffer = new BlockType[2];
|
||||||
protected int[] blockColors = new int[BlockTypes.size()];
|
protected int[] blockColors = new int[BlockTypes.size()];
|
||||||
protected long[] blockDistance = new long[BlockTypes.size()];
|
protected long[] blockDistance = new long[BlockTypes.size()];
|
||||||
@ -352,17 +365,43 @@ public class TextureUtil implements TextureHolder {
|
|||||||
try {
|
try {
|
||||||
LOGGER.info("Downloading asset jar from Mojang, please wait...");
|
LOGGER.info("Downloading asset jar from Mojang, please wait...");
|
||||||
new File(Fawe.platform().getDirectory() + "/" + Settings.settings().PATHS.TEXTURES + "/").mkdirs();
|
new File(Fawe.platform().getDirectory() + "/" + Settings.settings().PATHS.TEXTURES + "/").mkdirs();
|
||||||
try (BufferedInputStream in = new BufferedInputStream(
|
|
||||||
new URL("https://piston-data.mojang.com/v1/objects/c0898ec7c6a5a2eaa317770203a1554260699994/client.jar")
|
try {
|
||||||
.openStream());
|
VersionMetadata metadata = getLatestVersion();
|
||||||
FileOutputStream fileOutputStream = new FileOutputStream(
|
LOGGER.info("Latest release version is {}", metadata.version());
|
||||||
Fawe.platform().getDirectory() + "/" + Settings.settings().PATHS.TEXTURES + "/1.19.2.jar")) {
|
HashedResource resource = getLatestClientJarUrl(metadata);
|
||||||
byte[] dataBuffer = new byte[1024];
|
|
||||||
int bytesRead;
|
Path out = Path.of(
|
||||||
while ((bytesRead = in.read(dataBuffer, 0, 1024)) != -1) {
|
Fawe.platform().getDirectory().getPath(),
|
||||||
fileOutputStream.write(dataBuffer, 0, bytesRead);
|
Settings.settings().PATHS.TEXTURES,
|
||||||
|
metadata.version() + ".jar"
|
||||||
|
);
|
||||||
|
// Copy resource to local fs
|
||||||
|
try (final InputStream stream = new URL(resource.resource()).openStream();
|
||||||
|
final OutputStream writer = Files.newOutputStream(out)) {
|
||||||
|
stream.transferTo(writer);
|
||||||
}
|
}
|
||||||
LOGGER.info("Asset jar down has been downloaded successfully.");
|
// Validate sha-1 hash
|
||||||
|
try {
|
||||||
|
final String sha1 = calculateSha1(out);
|
||||||
|
if (!sha1.equals(resource.hash())) {
|
||||||
|
Files.deleteIfExists(out);
|
||||||
|
LOGGER.error(
|
||||||
|
"Hash comparison of final file failed (Expected: '{}', Calculated: '{}')",
|
||||||
|
resource.hash(), sha1
|
||||||
|
);
|
||||||
|
LOGGER.error("To prevent possibly malicious intentions, the downloaded file has been removed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
LOGGER.warn("Couldn't verify integrity of downloaded client file");
|
||||||
|
LOGGER.warn(
|
||||||
|
"Please verify that the downloaded files '{}' hash is equal to '{}'",
|
||||||
|
out, resource.hash()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOGGER.info("Asset jar has been downloaded and validated successfully.");
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
LOGGER.error(
|
LOGGER.error(
|
||||||
"Could not download version jar. Please do so manually by creating a `FastAsyncWorldEdit/textures` " +
|
"Could not download version jar. Please do so manually by creating a `FastAsyncWorldEdit/textures` " +
|
||||||
@ -555,6 +594,66 @@ public class TextureUtil implements TextureHolder {
|
|||||||
return totalDistSqr / area;
|
return totalDistSqr / area;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the minecraft versions manifest (containing all released versions) and returns the first {@code release}
|
||||||
|
* version (latest)
|
||||||
|
*
|
||||||
|
* @return {@link VersionMetadata} containing the id (= version) and url to the client manifest itself
|
||||||
|
* @throws IOException If any http / i/o operation fails.
|
||||||
|
* @since TODO
|
||||||
|
*/
|
||||||
|
private static VersionMetadata getLatestVersion() throws IOException {
|
||||||
|
try (BufferedInputStream in = new BufferedInputStream(new URL(VERSION_MANIFEST).openStream());
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
|
||||||
|
final JsonElement element = JsonParser.parseReader(reader);
|
||||||
|
for (final JsonElement versions : element.getAsJsonObject().getAsJsonArray("versions")) {
|
||||||
|
JsonObject version = versions.getAsJsonObject();
|
||||||
|
if (!version.get("type").getAsString().equals("release")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final String clientJsonUrl = version.get("url").getAsString();
|
||||||
|
final String id = version.get("id").getAsString();
|
||||||
|
return new VersionMetadata(id, clientJsonUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException("Failed to get latest version metadata");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the url to the client.jar based on the previously retrieved {@link VersionMetadata}
|
||||||
|
*
|
||||||
|
* @param metadata The version metadata containing the url to the client.jar
|
||||||
|
* @return The full url to the client.jar including the expected file hash for validation purposes
|
||||||
|
* @throws IOException If any http / i/o operation fails.
|
||||||
|
* @since TODO
|
||||||
|
*/
|
||||||
|
private static HashedResource getLatestClientJarUrl(VersionMetadata metadata) throws IOException {
|
||||||
|
try (BufferedInputStream in = new BufferedInputStream(new URL(metadata.url()).openStream());
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
|
||||||
|
final JsonObject object = JsonParser.parseReader(reader).getAsJsonObject();
|
||||||
|
final JsonObject client = object.getAsJsonObject("downloads").getAsJsonObject("client");
|
||||||
|
return new HashedResource(client.get("url").getAsString(), client.get("sha1").getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the sha-1 hash based on the content of the provided file.
|
||||||
|
*
|
||||||
|
* @param path The path of the resource to generate the sha-1 hash for
|
||||||
|
* @return The hash of the file contents
|
||||||
|
* @throws NoSuchAlgorithmException If the SHA-1 algorithm could not be resolved
|
||||||
|
* @throws IOException If any I/O operation failed
|
||||||
|
* @since TODO
|
||||||
|
*/
|
||||||
|
private static String calculateSha1(Path path) throws NoSuchAlgorithmException, IOException {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
|
try (final BufferedInputStream stream = new BufferedInputStream(Files.newInputStream(path));
|
||||||
|
final DigestOutputStream digestOutputStream = new DigestOutputStream(OutputStream.nullOutputStream(), digest)) {
|
||||||
|
stream.transferTo(digestOutputStream);
|
||||||
|
return HexFormat.of().formatHex(digest.digest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TextureUtil getTextureUtil() {
|
public TextureUtil getTextureUtil() {
|
||||||
return this;
|
return this;
|
||||||
@ -1130,4 +1229,12 @@ public class TextureUtil implements TextureHolder {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private record VersionMetadata(String version, String url) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private record HashedResource(String resource, String hash) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren