mirror of
https://github.com/MineLittlePony/MineLittlePony.git
synced 2025-02-13 08:14:23 +01:00
Add Valhalla implementation
This commit is contained in:
parent
89ecc9f916
commit
77b8357f03
4 changed files with 207 additions and 10 deletions
12
build.gradle
12
build.gradle
|
@ -39,11 +39,18 @@ sourceSets {
|
||||||
ext.refMap = 'hdskins.mixin.refmap.json'
|
ext.refMap = 'hdskins.mixin.refmap.json'
|
||||||
}
|
}
|
||||||
main {
|
main {
|
||||||
compileClasspath += hdskins.output
|
compileClasspath += hdskins.output + hdskins.compileClasspath
|
||||||
ext.refMap = 'minelp.mixin.refmap.json'
|
ext.refMap = 'minelp.mixin.refmap.json'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
// use the same version as httpclient
|
||||||
|
hdskinsCompile('org.apache.httpcomponents:httpmime:4.3.2'){
|
||||||
|
transitive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
litemod.json {
|
litemod.json {
|
||||||
mcversion = '1.12.r2'
|
mcversion = '1.12.r2'
|
||||||
author = 'Verdana, Rene_Z, Mumfrey, Killjoy1221'
|
author = 'Verdana, Rene_Z, Mumfrey, Killjoy1221'
|
||||||
|
@ -64,6 +71,9 @@ litemod.json {
|
||||||
jar {
|
jar {
|
||||||
from sourceSets.hdskins.output
|
from sourceSets.hdskins.output
|
||||||
from litemod
|
from litemod
|
||||||
|
|
||||||
|
// TODO relocate. LiteLoader excludes apache libs from classloading
|
||||||
|
from {configurations.hdskinsCompile.collect{it.isDirectory() ? it : zipTree(it)}}
|
||||||
}
|
}
|
||||||
sourceJar {
|
sourceJar {
|
||||||
// add hdskins sources
|
// add hdskins sources
|
||||||
|
|
|
@ -223,7 +223,6 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static PreviewTexture getPreviewTexture(ResourceLocation skinResource, GameProfile profile, Type type, ResourceLocation def, @Nullable final SkinAvailableCallback callback) {
|
public static PreviewTexture getPreviewTexture(ResourceLocation skinResource, GameProfile profile, Type type, ResourceLocation def, @Nullable final SkinAvailableCallback callback) {
|
||||||
TextureManager textureManager = Minecraft.getMinecraft().getTextureManager();
|
TextureManager textureManager = Minecraft.getMinecraft().getTextureManager();
|
||||||
MinecraftProfileTexture url = INSTANCE.getGatewayServer().getPreviewTexture(type, profile).orElse(null);
|
MinecraftProfileTexture url = INSTANCE.getGatewayServer().getPreviewTexture(type, profile).orElse(null);
|
||||||
|
|
|
@ -1,21 +1,55 @@
|
||||||
package com.voxelmodpack.hdskins.skins;
|
package com.voxelmodpack.hdskins.skins;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
||||||
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.util.Session;
|
import net.minecraft.util.Session;
|
||||||
|
import org.apache.http.HttpHeaders;
|
||||||
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.client.methods.RequestBuilder;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
|
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public class ValhallaSkinServer implements SkinServer {
|
public class ValhallaSkinServer implements SkinServer {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private final String baseURL;
|
private final String baseURL;
|
||||||
|
private final Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
|
||||||
|
.create();
|
||||||
|
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
public ValhallaSkinServer(String baseURL) {
|
public ValhallaSkinServer(String baseURL) {
|
||||||
this.baseURL = baseURL;
|
this.baseURL = baseURL;
|
||||||
|
@ -23,23 +57,175 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile) {
|
public Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile) {
|
||||||
|
|
||||||
|
try (CloseableHttpClient client = HttpClients.createSystem();
|
||||||
|
CloseableHttpResponse response = client.execute(new HttpGet(getTexturesURI(profile)))) {
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
|
||||||
|
|
||||||
|
return Optional.of(readJson(response.getEntity().getContent(), MinecraftTexturesPayload.class));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<MinecraftProfileTexture> getPreviewTexture(MinecraftProfileTexture.Type type, GameProfile profile) {
|
public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image,
|
||||||
return null;
|
MinecraftProfileTexture.Type type, Map<String, String> metadata) {
|
||||||
|
return CallableFutures.asyncFailableFuture(() -> {
|
||||||
|
try (CloseableHttpClient client = HttpClients.createSystem()) {
|
||||||
|
authorize(client, session);
|
||||||
|
|
||||||
|
GameProfile profile = session.getProfile();
|
||||||
|
|
||||||
|
if (image == null) {
|
||||||
|
return resetSkin(client, profile, type);
|
||||||
|
}
|
||||||
|
switch (image.getScheme()) {
|
||||||
|
case "file":
|
||||||
|
return uploadFile(client, new File(image), profile, type, metadata);
|
||||||
|
case "http":
|
||||||
|
case "https":
|
||||||
|
return uploadUrl(client, image, profile, type, metadata);
|
||||||
|
default:
|
||||||
|
throw new IOException("Unsupported URI scheme: " + image.getScheme());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, HDSkinManager.skinUploadExecutor);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private SkinUploadResponse resetSkin(CloseableHttpClient client, GameProfile profile, MinecraftProfileTexture.Type type) throws IOException {
|
||||||
public ListenableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable Path image, MinecraftProfileTexture.Type type, boolean thinArmType) {
|
return upload(client, RequestBuilder.delete()
|
||||||
return null;
|
.setUri(buildUserTextureUri(profile, type))
|
||||||
|
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SkinUploadResponse uploadFile(CloseableHttpClient client, File file, GameProfile profile, MinecraftProfileTexture.Type type,
|
||||||
|
Map<String, String> metadata) throws IOException {
|
||||||
|
MultipartEntityBuilder b = MultipartEntityBuilder.create();
|
||||||
|
b.addBinaryBody("file", file, ContentType.create("image/png"), file.getName());
|
||||||
|
metadata.forEach(b::addTextBody);
|
||||||
|
|
||||||
|
return upload(client, RequestBuilder.put()
|
||||||
|
.setUri(buildUserTextureUri(profile, type))
|
||||||
|
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
||||||
|
.setEntity(b.build())
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SkinUploadResponse uploadUrl(CloseableHttpClient client, URI uri, GameProfile profile, MinecraftProfileTexture.Type type,
|
||||||
|
Map<String, String> metadata) throws IOException {
|
||||||
|
|
||||||
|
return upload(client, RequestBuilder.post()
|
||||||
|
.setUri(buildUserTextureUri(profile, type))
|
||||||
|
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
||||||
|
.addParameter("file", uri.toString())
|
||||||
|
.addParameters(metadata.entrySet().stream()
|
||||||
|
.map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue()))
|
||||||
|
.toArray(NameValuePair[]::new))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SkinUploadResponse upload(CloseableHttpClient client, HttpUriRequest request) throws IOException {
|
||||||
|
try (CloseableHttpResponse response = client.execute(request)) {
|
||||||
|
int code = response.getStatusLine().getStatusCode();
|
||||||
|
JsonObject json = readJson(response.getEntity().getContent(), JsonObject.class);
|
||||||
|
|
||||||
|
return new SkinUploadResponse(code == HttpStatus.SC_OK, json.get("message").getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void authorize(CloseableHttpClient client, Session session) throws IOException, AuthenticationException {
|
||||||
|
if (accessToken != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GameProfile profile = session.getProfile();
|
||||||
|
String token = session.getToken();
|
||||||
|
AuthHandshake handshake = authHandshake(client, profile.getName());
|
||||||
|
|
||||||
|
if (handshake.offline) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// join the session server
|
||||||
|
Minecraft.getMinecraft().getSessionService().joinServer(profile, token, handshake.serverId);
|
||||||
|
|
||||||
|
AuthResponse response = authResponse(client, profile.getName(), handshake.verifyToken);
|
||||||
|
if (!response.userId.equals(profile.getId())) {
|
||||||
|
throw new IOException("UUID mismatch!"); // probably won't ever throw
|
||||||
|
}
|
||||||
|
this.accessToken = response.accessToken;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T readJson(InputStream in, Class<T> cl) throws IOException {
|
||||||
|
try (Reader r = new InputStreamReader(in)) {
|
||||||
|
return gson.fromJson(r, cl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthHandshake authHandshake(CloseableHttpClient client, String name) throws IOException {
|
||||||
|
try (CloseableHttpResponse resp = client.execute(RequestBuilder.post()
|
||||||
|
.setUri(getHandshakeURI())
|
||||||
|
.addParameter("name", name)
|
||||||
|
.build())) {
|
||||||
|
return readJson(resp.getEntity().getContent(), AuthHandshake.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthResponse authResponse(CloseableHttpClient client, String name, long verifyToken) throws IOException {
|
||||||
|
try (CloseableHttpResponse resp = client.execute(RequestBuilder.post()
|
||||||
|
.setUri(getResponseURI())
|
||||||
|
.addParameter("name", name)
|
||||||
|
.addParameter("verifyToken", String.valueOf(verifyToken))
|
||||||
|
.build())) {
|
||||||
|
return readJson(resp.getEntity().getContent(), AuthResponse.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private URI buildUserTextureUri(GameProfile profile, MinecraftProfileTexture.Type textureType) {
|
||||||
|
String user = UUIDTypeAdapter.fromUUID(profile.getId());
|
||||||
|
String skinType = textureType.name().toLowerCase(Locale.US);
|
||||||
|
return URI.create(String.format("%s/user/%s/%s", this.baseURL, user, skinType));
|
||||||
|
}
|
||||||
|
|
||||||
|
private URI getTexturesURI(GameProfile profile) {
|
||||||
|
Preconditions.checkNotNull(profile.getId(), "profile id required for skins");
|
||||||
|
return URI.create(String.format("%s/user/%s", this.baseURL, UUIDTypeAdapter.fromUUID(profile.getId())));
|
||||||
|
}
|
||||||
|
|
||||||
|
private URI getHandshakeURI() {
|
||||||
|
return URI.create(String.format("%s/auth/handshake", this.baseURL));
|
||||||
|
}
|
||||||
|
|
||||||
|
private URI getResponseURI() {
|
||||||
|
return URI.create(String.format("%s/auth/response", this.baseURL));
|
||||||
}
|
}
|
||||||
|
|
||||||
static ValhallaSkinServer from(String server) {
|
static ValhallaSkinServer from(String server) {
|
||||||
Matcher matcher = Pattern.compile("^valhalla:(.*)$").matcher(server);
|
Matcher matcher = Pattern.compile("^valhalla:(.*)$").matcher(server);
|
||||||
if (matcher.find())
|
if (matcher.find()) {
|
||||||
return new ValhallaSkinServer(matcher.group(1));
|
return new ValhallaSkinServer(matcher.group(1));
|
||||||
|
}
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
static class AuthHandshake {
|
||||||
|
|
||||||
|
boolean offline;
|
||||||
|
String serverId;
|
||||||
|
long verifyToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
static class AuthResponse {
|
||||||
|
|
||||||
|
String accessToken;
|
||||||
|
UUID userId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,9 @@ import javax.annotation.Nullable;
|
||||||
* Uploader for Multipart form data
|
* Uploader for Multipart form data
|
||||||
*
|
*
|
||||||
* @author Adam Mummery-Smith
|
* @author Adam Mummery-Smith
|
||||||
|
* @deprecated Use httpmime multipart upload
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class ThreadMultipartPostUpload {
|
public class ThreadMultipartPostUpload {
|
||||||
protected final Map<String, ?> sourceData;
|
protected final Map<String, ?> sourceData;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue