1
0
mirror of https://github.com/SKCraft/Launcher.git synced 2024-11-27 12:46:22 +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.TokenResponse;
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 lombok.Data;
import lombok.RequiredArgsConstructor;
@ -103,10 +103,9 @@ public class MicrosoftLoginService implements LoginService {
if (previous != null && previous.getAvatarImage() != null) {
session.setAvatarImage(previous.getAvatarImage());
} else {
session.setAvatarImage(VisageSkinService.fetchSkinHead(profile.getUuid()));
session.setAvatarImage(MinecraftSkinService.fetchSkinHead(profile));
}
return session;
}

View File

@ -7,7 +7,9 @@
package com.skcraft.launcher.auth;
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 lombok.Data;
import lombok.RequiredArgsConstructor;
@ -60,7 +62,10 @@ public class YggdrasilLoginService implements LoginService {
if (previous != null && previous.getAvatarImage() != null) {
profile.setAvatarImage(previous.getAvatarImage());
} else {
profile.setAvatarImage(VisageSkinService.fetchSkinHead(profile.getUuid()));
McProfileResponse skinProfile = MinecraftServicesAuthorizer
.getUserProfile("Bearer " + response.getAccessToken());
profile.setAvatarImage(MinecraftSkinService.fetchSkinHead(skinProfile));
}
return profile;

View File

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

View File

@ -1,12 +1,28 @@
package com.skcraft.launcher.auth.microsoft.model;
import com.beust.jcommander.internal.Lists;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.List;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class McProfileResponse {
@JsonProperty("id") private String uuid;
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;
import com.skcraft.launcher.util.HttpRequest;
import lombok.extern.java.Log;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.logging.Level;
import static com.skcraft.launcher.util.HttpRequest.url;
@Log
public class VisageSkinService {
@Nullable
public static byte[] fetchSkinHead(String uuid) throws InterruptedException {
@ -19,6 +22,7 @@ public class VisageSkinService {
.returnContent()
.asBytes();
} catch (IOException e) {
log.log(Level.WARNING, "Failed to download or process skin from Visage.", e);
return null;
}
}