@@ -141,9 +141,9 @@ public class OfflineAccountSkinPane extends StackPane {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
canvas.updateSkin(
|
canvas.updateSkin(
|
||||||
result.getSkin() != null ? new Image(result.getSkin().getInputStream()) : getDefaultTexture(),
|
result.getSkin() != null ? result.getSkin().getImage() : getDefaultTexture(),
|
||||||
result.getModel() == TextureModel.ALEX,
|
result.getModel() == TextureModel.ALEX,
|
||||||
result.getCape() != null ? new Image(result.getCape().getInputStream()) : null);
|
result.getCape() != null ? result.getCape().getImage() : null);
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}, skinItem.selectedDataProperty(), cslApiField.textProperty(), skinSelector.valueProperty(), capeSelector.valueProperty());
|
}, skinItem.selectedDataProperty(), cslApiField.textProperty(), skinSelector.valueProperty(), capeSelector.valueProperty());
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
api("org.glavo:simple-png-javafx:0.3.0")
|
||||||
api("com.google.code.gson:gson:2.8.1")
|
api("com.google.code.gson:gson:2.8.1")
|
||||||
api("com.moandjiezana.toml:toml4j:0.7.2")
|
api("com.moandjiezana.toml:toml4j:0.7.2")
|
||||||
api("org.tukaani:xz:1.8")
|
api("org.tukaani:xz:1.8")
|
||||||
|
|||||||
@@ -17,15 +17,12 @@
|
|||||||
*/
|
*/
|
||||||
package org.jackhuang.hmcl.auth.offline;
|
package org.jackhuang.hmcl.auth.offline;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javafx.scene.image.Image;
|
||||||
import javax.imageio.ImageIO;
|
import javafx.scene.image.PixelReader;
|
||||||
import java.awt.image.BufferedImage;
|
import org.jackhuang.hmcl.util.Hex;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -33,29 +30,21 @@ import java.util.Map;
|
|||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
import static java.util.Objects.requireNonNull;
|
||||||
|
|
||||||
public class Texture {
|
public final class Texture {
|
||||||
private final String hash;
|
private final String hash;
|
||||||
private final byte[] data;
|
private final Image image;
|
||||||
|
|
||||||
public Texture(String hash, byte[] data) {
|
public Texture(String hash, Image image) {
|
||||||
this.hash = requireNonNull(hash);
|
this.hash = requireNonNull(hash);
|
||||||
this.data = requireNonNull(data);
|
this.image = requireNonNull(image);
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getData() {
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHash() {
|
public String getHash() {
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream getInputStream() {
|
public Image getImage() {
|
||||||
return new ByteArrayInputStream(data);
|
return image;
|
||||||
}
|
|
||||||
|
|
||||||
public int getLength() {
|
|
||||||
return data.length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Map<String, Texture> textures = new HashMap<>();
|
private static final Map<String, Texture> textures = new HashMap<>();
|
||||||
@@ -68,15 +57,17 @@ public class Texture {
|
|||||||
return textures.get(hash);
|
return textures.get(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String computeTextureHash(BufferedImage img) {
|
private static String computeTextureHash(Image img) {
|
||||||
MessageDigest digest;
|
MessageDigest digest;
|
||||||
try {
|
try {
|
||||||
digest = MessageDigest.getInstance("SHA-256");
|
digest = MessageDigest.getInstance("SHA-256");
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
int width = img.getWidth();
|
|
||||||
int height = img.getHeight();
|
PixelReader reader = img.getPixelReader();
|
||||||
|
int width = (int) img.getWidth();
|
||||||
|
int height = (int) img.getHeight();
|
||||||
byte[] buf = new byte[4096];
|
byte[] buf = new byte[4096];
|
||||||
|
|
||||||
putInt(buf, 0, width);
|
putInt(buf, 0, width);
|
||||||
@@ -84,7 +75,7 @@ public class Texture {
|
|||||||
int pos = 8;
|
int pos = 8;
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
putInt(buf, pos, img.getRGB(x, y));
|
putInt(buf, pos, reader.getArgb(x, y));
|
||||||
if (buf[pos + 0] == 0) {
|
if (buf[pos + 0] == 0) {
|
||||||
buf[pos + 1] = buf[pos + 2] = buf[pos + 3] = 0;
|
buf[pos + 1] = buf[pos + 2] = buf[pos + 3] = 0;
|
||||||
}
|
}
|
||||||
@@ -99,8 +90,7 @@ public class Texture {
|
|||||||
digest.update(buf, 0, pos);
|
digest.update(buf, 0, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] sha256 = digest.digest();
|
return Hex.encodeHex(digest.digest());
|
||||||
return String.format("%0" + (sha256.length << 1) + "x", new BigInteger(1, sha256));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void putInt(byte[] array, int offset, int x) {
|
private static void putInt(byte[] array, int offset, int x) {
|
||||||
@@ -112,25 +102,28 @@ public class Texture {
|
|||||||
|
|
||||||
public static Texture loadTexture(InputStream in) throws IOException {
|
public static Texture loadTexture(InputStream in) throws IOException {
|
||||||
if (in == null) return null;
|
if (in == null) return null;
|
||||||
BufferedImage img;
|
Image img;
|
||||||
try (InputStream is = in) {
|
try (InputStream is = in) {
|
||||||
img = ImageIO.read(is);
|
img = new Image(is);
|
||||||
}
|
|
||||||
if (img == null) {
|
|
||||||
throw new IIOException("No image found");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String hash = computeTextureHash(img);
|
if (img.isError()) {
|
||||||
|
throw new IOException("No image found", img.getException());
|
||||||
|
}
|
||||||
|
return loadTexture(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Texture loadTexture(Image image) {
|
||||||
|
if (image == null) return null;
|
||||||
|
|
||||||
|
String hash = computeTextureHash(image);
|
||||||
|
|
||||||
Texture existent = textures.get(hash);
|
Texture existent = textures.get(hash);
|
||||||
if (existent != null) {
|
if (existent != null) {
|
||||||
return existent;
|
return existent;
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
Texture texture = new Texture(hash, image);
|
||||||
ImageIO.write(img, "png", buf);
|
|
||||||
Texture texture = new Texture(hash, buf.toByteArray());
|
|
||||||
|
|
||||||
existent = textures.putIfAbsent(hash, texture);
|
existent = textures.putIfAbsent(hash, texture);
|
||||||
|
|
||||||
if (existent != null) {
|
if (existent != null) {
|
||||||
@@ -139,9 +132,4 @@ public class Texture {
|
|||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Texture loadTexture(String url) throws IOException {
|
|
||||||
if (url == null) return null;
|
|
||||||
return loadTexture(new URL(url).openStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package org.jackhuang.hmcl.auth.offline;
|
package org.jackhuang.hmcl.auth.offline;
|
||||||
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import org.glavo.png.javafx.PNGJavaFXUtils;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
||||||
import org.jackhuang.hmcl.util.KeyUtils;
|
import org.jackhuang.hmcl.util.KeyUtils;
|
||||||
@@ -26,6 +27,7 @@ import org.jackhuang.hmcl.util.gson.JsonUtils;
|
|||||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||||
import org.jackhuang.hmcl.util.io.HttpServer;
|
import org.jackhuang.hmcl.util.io.HttpServer;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.*;
|
import java.security.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -127,7 +129,8 @@ public class YggdrasilServer extends HttpServer {
|
|||||||
|
|
||||||
if (Texture.hasTexture(hash)) {
|
if (Texture.hasTexture(hash)) {
|
||||||
Texture texture = Texture.getTexture(hash);
|
Texture texture = Texture.getTexture(hash);
|
||||||
Response response = newFixedLengthResponse(Response.Status.OK, "image/png", texture.getInputStream(), texture.getLength());
|
byte[] data = PNGJavaFXUtils.writeImageToArray(texture.getImage());
|
||||||
|
Response response = newFixedLengthResponse(Response.Status.OK, "image/png", new ByteArrayInputStream(data), data.length);
|
||||||
response.addHeader("Etag", String.format("\"%s\"", hash));
|
response.addHeader("Etag", String.format("\"%s\"", hash));
|
||||||
response.addHeader("Cache-Control", "max-age=2592000, public");
|
response.addHeader("Cache-Control", "max-age=2592000, public");
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
Reference in New Issue
Block a user