1
0
mirror of https://github.com/SKCraft/Launcher.git synced 2024-11-30 13:13:58 +01:00

Switch to using Minecraft services as skin provider

We do some local processing to draw the head of the skin in 32px
resolution.
This commit is contained in:
Henry Le Grys 2021-02-18 22:39:51 +00:00
parent fd78863697
commit 5d5a462387
7 changed files with 92 additions and 6 deletions

View File

@ -11,7 +11,7 @@ import com.skcraft.launcher.auth.microsoft.model.McAuthResponse;
import com.skcraft.launcher.auth.microsoft.model.McProfileResponse; import com.skcraft.launcher.auth.microsoft.model.McProfileResponse;
import com.skcraft.launcher.auth.microsoft.model.TokenResponse; import com.skcraft.launcher.auth.microsoft.model.TokenResponse;
import com.skcraft.launcher.auth.microsoft.model.XboxAuthorization; import com.skcraft.launcher.auth.microsoft.model.XboxAuthorization;
import com.skcraft.launcher.auth.skin.VisageSkinService; import com.skcraft.launcher.auth.skin.MinecraftSkinService;
import com.skcraft.launcher.util.HttpRequest; import com.skcraft.launcher.util.HttpRequest;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -103,10 +103,9 @@ public class MicrosoftLoginService implements LoginService {
if (previous != null && previous.getAvatarImage() != null) { if (previous != null && previous.getAvatarImage() != null) {
session.setAvatarImage(previous.getAvatarImage()); session.setAvatarImage(previous.getAvatarImage());
} else { } else {
session.setAvatarImage(VisageSkinService.fetchSkinHead(profile.getUuid())); session.setAvatarImage(MinecraftSkinService.fetchSkinHead(profile));
} }
return session; return session;
} }

View File

@ -7,7 +7,9 @@
package com.skcraft.launcher.auth; package com.skcraft.launcher.auth;
import com.fasterxml.jackson.annotation.*; import com.fasterxml.jackson.annotation.*;
import com.skcraft.launcher.auth.skin.VisageSkinService; import com.skcraft.launcher.auth.microsoft.MinecraftServicesAuthorizer;
import com.skcraft.launcher.auth.microsoft.model.McProfileResponse;
import com.skcraft.launcher.auth.skin.MinecraftSkinService;
import com.skcraft.launcher.util.HttpRequest; import com.skcraft.launcher.util.HttpRequest;
import lombok.Data; import lombok.Data;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@ -60,7 +62,10 @@ public class YggdrasilLoginService implements LoginService {
if (previous != null && previous.getAvatarImage() != null) { if (previous != null && previous.getAvatarImage() != null) {
profile.setAvatarImage(previous.getAvatarImage()); profile.setAvatarImage(previous.getAvatarImage());
} else { } else {
profile.setAvatarImage(VisageSkinService.fetchSkinHead(profile.getUuid())); McProfileResponse skinProfile = MinecraftServicesAuthorizer
.getUserProfile("Bearer " + response.getAccessToken());
profile.setAvatarImage(MinecraftSkinService.fetchSkinHead(skinProfile));
} }
return profile; return profile;

View File

@ -28,8 +28,13 @@ public class MinecraftServicesAuthorizer {
public static McProfileResponse getUserProfile(McAuthResponse auth) public static McProfileResponse getUserProfile(McAuthResponse auth)
throws IOException, InterruptedException, AuthenticationException { throws IOException, InterruptedException, AuthenticationException {
return getUserProfile(auth.getAuthorization());
}
public static McProfileResponse getUserProfile(String authorization)
throws IOException, InterruptedException, AuthenticationException {
return HttpRequest.get(MC_SERVICES_PROFILE) return HttpRequest.get(MC_SERVICES_PROFILE)
.header("Authorization", auth.getAuthorization()) .header("Authorization", authorization)
.execute() .execute()
.expectResponseCodeOr(200, req -> { .expectResponseCodeOr(200, req -> {
McServicesError error = req.returnContent().asJson(McServicesError.class); McServicesError error = req.returnContent().asJson(McServicesError.class);

View File

@ -1,12 +1,28 @@
package com.skcraft.launcher.auth.microsoft.model; package com.skcraft.launcher.auth.microsoft.model;
import com.beust.jcommander.internal.Lists;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;
import java.util.List;
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class McProfileResponse { public class McProfileResponse {
@JsonProperty("id") private String uuid; @JsonProperty("id") private String uuid;
private String name; private String name;
private List<Skin> skins = Lists.newArrayList();
public Skin getActiveSkin() {
return skins.stream().filter(skin -> skin.getState().equals("ACTIVE")).findFirst().orElse(null);
}
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public static class Skin {
private String state;
private String url;
private String variant;
}
} }

View File

@ -0,0 +1,30 @@
package com.skcraft.launcher.auth.skin;
import com.skcraft.launcher.auth.microsoft.model.McProfileResponse;
import com.skcraft.launcher.util.HttpRequest;
import lombok.extern.java.Log;
import java.io.IOException;
import java.util.logging.Level;
@Log
public class MinecraftSkinService {
static byte[] downloadSkin(String textureUrl) throws IOException, InterruptedException {
return HttpRequest.get(HttpRequest.url(textureUrl))
.execute()
.expectResponseCode(200)
.returnContent()
.asBytes();
}
public static byte[] fetchSkinHead(McProfileResponse profile) throws InterruptedException {
try {
byte[] skin = downloadSkin(profile.getActiveSkin().getUrl());
return SkinProcessor.renderHead(skin);
} catch (IOException e) {
log.log(Level.WARNING, "Failed to download or process skin.", e);
return null;
}
}
}

View File

@ -0,0 +1,27 @@
package com.skcraft.launcher.auth.skin;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class SkinProcessor {
public static byte[] renderHead(byte[] skinData) throws IOException {
BufferedImage skin = ImageIO.read(new ByteArrayInputStream(skinData));
BufferedImage result = new BufferedImage(32, 32, BufferedImage.TYPE_INT_RGB);
Graphics graphics = result.getGraphics();
// Draw bottom head layer
graphics.drawImage(skin, 0, 0, 32, 32, 8, 8, 16, 16, null);
// Draw top head layer
graphics.drawImage(skin, 0, 0, 32, 32, 40, 8, 48, 16, null);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(result, "png", outputStream);
return outputStream.toByteArray();
}
}

View File

@ -1,12 +1,15 @@
package com.skcraft.launcher.auth.skin; package com.skcraft.launcher.auth.skin;
import com.skcraft.launcher.util.HttpRequest; import com.skcraft.launcher.util.HttpRequest;
import lombok.extern.java.Log;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.util.logging.Level;
import static com.skcraft.launcher.util.HttpRequest.url; import static com.skcraft.launcher.util.HttpRequest.url;
@Log
public class VisageSkinService { public class VisageSkinService {
@Nullable @Nullable
public static byte[] fetchSkinHead(String uuid) throws InterruptedException { public static byte[] fetchSkinHead(String uuid) throws InterruptedException {
@ -19,6 +22,7 @@ public class VisageSkinService {
.returnContent() .returnContent()
.asBytes(); .asBytes();
} catch (IOException e) { } catch (IOException e) {
log.log(Level.WARNING, "Failed to download or process skin from Visage.", e);
return null; return null;
} }
} }