Merge pull request #74 from MineLittlePony/valhalla

Valhalla Implementation
This commit is contained in:
Matthew Messinger 2018-07-14 10:54:30 -04:00 committed by GitHub
commit 8e663c81d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 564 additions and 177 deletions

View file

@ -1,3 +1,11 @@
language: java language: java
install: true
jdk: jdk:
- oraclejdk8 - oraclejdk8
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/

View file

@ -13,11 +13,13 @@ buildscript {
dependencies { dependencies {
classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT'
classpath 'org.spongepowered:mixingradle:0.6-SNAPSHOT' classpath 'org.spongepowered:mixingradle:0.6-SNAPSHOT'
classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
} }
} }
apply plugin: 'net.minecraftforge.gradle.liteloader' apply plugin: 'net.minecraftforge.gradle.liteloader'
apply plugin: 'org.spongepowered.mixin' apply plugin: 'org.spongepowered.mixin'
apply plugin: 'com.github.johnrengelman.shadow'
group = 'com.minelittlepony' group = 'com.minelittlepony'
version = '1.12.2.2-SNAPSHOT' version = '1.12.2.2-SNAPSHOT'
@ -44,6 +46,13 @@ sourceSets {
} }
} }
dependencies {
// use the same version as httpclient
compile('org.apache.httpcomponents:httpmime:4.3.2') {
transitive = false
}
}
litemod { litemod {
doFirst { doFirst {
json { json {
@ -56,7 +65,6 @@ litemod {
description.hdskinsmod = '''\ description.hdskinsmod = '''\
Separate skin server for Mine Little Pony that also supports HD skins. Separate skin server for Mine Little Pony that also supports HD skins.
Access via button on the main menu.''' Access via button on the main menu.'''
mixinConfigs += [ mixinConfigs += [
'minelp.mixin.json', 'minelp.mixin.json',
'hdskins.mixin.json' 'hdskins.mixin.json'
@ -68,6 +76,24 @@ litemod {
jar { jar {
from sourceSets.hdskins.output from sourceSets.hdskins.output
from litemod from litemod
classifier 'base'
extension 'jar'
}
shadowJar {
extension 'litemod'
classifier "mc$minecraft.version"
baseName "mod-${project.name.toLowerCase()}"
from sourceSets.hdskins.output
from litemod
dependencies {
exclude dependency('deobf.com.mumfrey:liteloader:')
exclude dependency('deobf.org.ow2.asm:')
exclude 'META-INF/**'
}
relocate 'org.apache.http.entity.mime', 'com.voxelmodpack.repack.org.apache.http.entity.mime'
exclude 'dummyThing'
} }
sourceJar { sourceJar {
// add hdskins sources // add hdskins sources
@ -86,10 +112,16 @@ task skinZip(type: Zip) {
version 'v1' version 'v1'
} }
artifacts {
archives shadowJar
}
reobf { reobf {
srgJar { srgJar {
mappingType = 'SEARGE' mappingType = 'SEARGE'
} }
shadowJar {}
} }
mixin { mixin {
defaultObfuscationEnv notch defaultObfuscationEnv notch

View file

@ -1,8 +1,11 @@
package com.voxelmodpack.hdskins; package com.voxelmodpack.hdskins;
import com.google.common.base.Preconditions;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
@ -21,7 +24,10 @@ import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
import com.voxelmodpack.hdskins.gui.GuiSkins; import com.voxelmodpack.hdskins.gui.GuiSkins;
import com.voxelmodpack.hdskins.resource.SkinResourceManager; import com.voxelmodpack.hdskins.resource.SkinResourceManager;
import com.voxelmodpack.hdskins.skins.AsyncCacheLoader; import com.voxelmodpack.hdskins.skins.AsyncCacheLoader;
import com.voxelmodpack.hdskins.skins.LegacySkinServer;
import com.voxelmodpack.hdskins.skins.ServerType;
import com.voxelmodpack.hdskins.skins.SkinServer; import com.voxelmodpack.hdskins.skins.SkinServer;
import com.voxelmodpack.hdskins.skins.ValhallaSkinServer;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.IImageBuffer; import net.minecraft.client.renderer.IImageBuffer;
import net.minecraft.client.renderer.texture.ITextureObject; import net.minecraft.client.renderer.texture.ITextureObject;
@ -31,13 +37,15 @@ import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourceManagerReloadListener; import net.minecraft.client.resources.IResourceManagerReloadListener;
import net.minecraft.client.resources.SkinManager.SkinAvailableCallback; import net.minecraft.client.resources.SkinManager.SkinAvailableCallback;
import net.minecraft.util.ResourceLocation; import net.minecraft.util.ResourceLocation;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@ -50,7 +58,6 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public final class HDSkinManager implements IResourceManagerReloadListener { public final class HDSkinManager implements IResourceManagerReloadListener {
@ -68,6 +75,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
private List<ISkinCacheClearListener> clearListeners = Lists.newArrayList(); private List<ISkinCacheClearListener> clearListeners = Lists.newArrayList();
private BiMap<String, Class<? extends SkinServer>> skinServerTypes = HashBiMap.create(2);
private List<SkinServer> skinServers = Lists.newArrayList(); private List<SkinServer> skinServers = Lists.newArrayList();
private Map<UUID, Map<Type, ResourceLocation>> skinCache = Maps.newHashMap(); private Map<UUID, Map<Type, ResourceLocation>> skinCache = Maps.newHashMap();
@ -81,10 +89,17 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
private List<ISkinModifier> skinModifiers = Lists.newArrayList(); private List<ISkinModifier> skinModifiers = Lists.newArrayList();
private SkinResourceManager resources = new SkinResourceManager(); private SkinResourceManager resources = new SkinResourceManager();
// private ExecutorService executor = Executors.newCachedThreadPool(); // private ExecutorService executor = Executors.newCachedThreadPool();
private Class<? extends GuiSkins> skinsClass = null; private Class<? extends GuiSkins> skinsClass = null;
private HDSkinManager() {
// register default skin server types
addSkinServerType(LegacySkinServer.class);
addSkinServerType(ValhallaSkinServer.class);
}
public void setPrefferedSkinsGuiClass(Class<? extends GuiSkins> clazz) { public void setPrefferedSkinsGuiClass(Class<? extends GuiSkins> clazz) {
skinsClass = clazz; skinsClass = clazz;
} }
@ -102,35 +117,42 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
} }
public Optional<ResourceLocation> getSkinLocation(GameProfile profile1, final Type type, boolean loadIfAbsent) { public Optional<ResourceLocation> getSkinLocation(GameProfile profile1, final Type type, boolean loadIfAbsent) {
if (!enabled) if (!enabled) {
return Optional.empty(); return Optional.empty();
}
ResourceLocation skin = this.resources.getPlayerTexture(profile1, type); ResourceLocation skin = this.resources.getPlayerTexture(profile1, type);
if (skin != null) if (skin != null) {
return Optional.of(skin); return Optional.of(skin);
}
// try to recreate a broken gameprofile // try to recreate a broken gameprofile
// happens when server sends a random profile with skin and displayname // happens when server sends a random profile with skin and displayname
Property textures = Iterables.getFirst(profile1.getProperties().get("textures"), null); Property textures = Iterables.getFirst(profile1.getProperties().get("textures"), null);
if (textures != null) { if (textures != null) {
MinecraftTexturesPayload texturePayload = GSON.fromJson(new String(Base64.decodeBase64(textures.getValue())), MinecraftTexturesPayload.class); String json = new String(Base64.getDecoder().decode(textures.getValue()), StandardCharsets.UTF_8);
MinecraftTexturesPayload texturePayload = GSON.fromJson(json, MinecraftTexturesPayload.class);
if (texturePayload != null) { if (texturePayload != null) {
// name is optional // name is optional
String name = texturePayload.getProfileName(); String name = texturePayload.getProfileName();
UUID uuid = texturePayload.getProfileId(); UUID uuid = texturePayload.getProfileId();
// uuid is required // uuid is required
if (uuid != null) if (uuid != null) {
profile1 = new GameProfile(uuid, name); profile1 = new GameProfile(uuid, name);
}
// probably uses this texture for a reason. Don't mess with it. // probably uses this texture for a reason. Don't mess with it.
if (!texturePayload.getTextures().isEmpty() && texturePayload.getProfileId() == null) if (!texturePayload.getTextures().isEmpty() && texturePayload.getProfileId() == null) {
return Optional.empty(); return Optional.empty();
}
} }
} }
final GameProfile profile = profile1; final GameProfile profile = profile1;
// cannot get texture without id! // cannot get texture without id!
if (profile.getId() == null) return Optional.empty(); if (profile.getId() == null) {
return Optional.empty();
}
if (!this.skinCache.containsKey(profile.getId())) { if (!this.skinCache.containsKey(profile.getId())) {
this.skinCache.put(profile.getId(), Maps.newHashMap()); this.skinCache.put(profile.getId(), Maps.newHashMap());
@ -148,7 +170,7 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
} }
private String bustCache(String url) { private String bustCache(String url) {
return url + (url.indexOf('?') > -1 ? '&' : '?') + Long.toString(new Date().getTime()/1000); return url + (url.indexOf('?') > -1 ? '&' : '?') + Long.toString(new Date().getTime() / 1000);
} }
private void loadTexture(GameProfile profile, final Type type, final SkinAvailableCallback callback) { private void loadTexture(GameProfile profile, final Type type, final SkinAvailableCallback callback) {
@ -194,8 +216,9 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
for (SkinServer server : skinServers) { for (SkinServer server : skinServers) {
Optional<MinecraftTexturesPayload> profileData = server.loadProfileData(profile); Optional<MinecraftTexturesPayload> profileData = server.loadProfileData(profile);
profileData.map(MinecraftTexturesPayload::getTextures).ifPresent(it -> it.forEach(textures::putIfAbsent)); profileData.map(MinecraftTexturesPayload::getTextures).ifPresent(it -> it.forEach(textures::putIfAbsent));
if (textures.size() == Type.values().length) if (textures.size() == Type.values().length) {
break; break;
}
} }
return textures; return textures;
@ -211,10 +234,25 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
return textures; return textures;
} }
public void addSkinServer(SkinServer skinServer) { public void addSkinServerType(Class<? extends SkinServer> type) {
this.skinServers.add(0, skinServer); Preconditions.checkArgument(!type.isInterface(), "type cannot be an interface");
Preconditions.checkArgument(!Modifier.isAbstract(type.getModifiers()), "type cannot be abstract");
ServerType st = type.getAnnotation(ServerType.class);
if (st == null) {
throw new IllegalArgumentException("class is not annotated with @ServerType");
}
this.skinServerTypes.put(st.value(), type);
} }
public Class<? extends SkinServer> getSkinServerClass(String type) {
return this.skinServerTypes.get(type);
}
public void addSkinServer(SkinServer skinServer) {
this.skinServers.add(skinServer);
}
@Deprecated
public SkinServer getGatewayServer() { public SkinServer getGatewayServer() {
return this.skinServers.get(0); return this.skinServers.get(0);
} }
@ -223,32 +261,8 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
this.enabled = enabled; this.enabled = enabled;
} }
@Nullable public static PreviewTextureManager getPreviewTextureManager(GameProfile profile) {
public static PreviewTexture getPreviewTexture(ResourceLocation skinResource, GameProfile profile, Type type, ResourceLocation def, @Nullable final SkinAvailableCallback callback) { return new PreviewTextureManager(INSTANCE.getGatewayServer().getPreviewTextures(profile));
TextureManager textureManager = Minecraft.getMinecraft().getTextureManager();
MinecraftProfileTexture url = INSTANCE.getGatewayServer().getPreviewTexture(type, profile).orElse(null);
if (url == null)
return null;
IImageBuffer buffer = new ImageBufferDownloadHD();
PreviewTexture skinTexture = new PreviewTexture(url.getMetadata("model"), url.getUrl(), def, type == Type.SKIN ? new IImageBuffer() {
@Override
@Nullable
public BufferedImage parseUserSkin(BufferedImage image) {
return buffer.parseUserSkin(image);
}
@Override
public void skinAvailable() {
if (callback != null) {
callback.skinAvailable(type, skinResource, new MinecraftProfileTexture(url.getUrl(), Maps.newHashMap()));
}
}
} : null);
textureManager.loadTexture(skinResource, skinTexture);
return skinTexture;
} }
public void addClearListener(ISkinCacheClearListener listener) { public void addClearListener(ISkinCacheClearListener listener) {

View file

@ -12,10 +12,10 @@ public class PreviewTexture extends ThreadDownloadImageData {
private String model; private String model;
public PreviewTexture(String model, String url, ResourceLocation fallbackTexture, @Nullable IImageBuffer imageBuffer) { public PreviewTexture(@Nullable String model, String url, ResourceLocation fallbackTexture, @Nullable IImageBuffer imageBuffer) {
super(null, url, fallbackTexture, imageBuffer); super(null, url, fallbackTexture, imageBuffer);
this.model = model; this.model = model == null ? "default" : model;
} }
public boolean isTextureUploaded() { public boolean isTextureUploaded() {

View file

@ -0,0 +1,54 @@
package com.voxelmodpack.hdskins;
import com.google.common.collect.Maps;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.IImageBuffer;
import net.minecraft.client.resources.SkinManager;
import net.minecraft.util.ResourceLocation;
import java.awt.image.BufferedImage;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Manager for fetching preview textures. This ensures that multiple calls
* to the skin server aren't done when fetching preview textures.
*/
public class PreviewTextureManager {
private final Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures;
PreviewTextureManager(Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures) {
this.textures = textures;
}
@Nullable
public PreviewTexture getPreviewTexture(ResourceLocation location, MinecraftProfileTexture.Type type, ResourceLocation def,
@Nullable SkinManager.SkinAvailableCallback callback) {
if (!textures.containsKey(type)) {
return null;
}
MinecraftProfileTexture texture = textures.get(type);
IImageBuffer buffer = new ImageBufferDownloadHD();
PreviewTexture skinTexture = new PreviewTexture(texture.getMetadata("model"), texture.getUrl(), def,
type == MinecraftProfileTexture.Type.SKIN ? new IImageBuffer() {
@Override
@Nullable
public BufferedImage parseUserSkin(BufferedImage image) {
return buffer.parseUserSkin(image);
}
@Override
public void skinAvailable() {
if (callback != null) {
callback.skinAvailable(type, location, new MinecraftProfileTexture(texture.getUrl(), Maps.newHashMap()));
}
}
} : null);
Minecraft.getMinecraft().getTextureManager().loadTexture(location, skinTexture);
return skinTexture;
}
}

View file

@ -8,6 +8,7 @@ import com.voxelmodpack.hdskins.DynamicTextureImage;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.ImageBufferDownloadHD; import com.voxelmodpack.hdskins.ImageBufferDownloadHD;
import com.voxelmodpack.hdskins.PreviewTexture; import com.voxelmodpack.hdskins.PreviewTexture;
import com.voxelmodpack.hdskins.PreviewTextureManager;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.client.renderer.texture.TextureManager;
@ -76,8 +77,10 @@ public class EntityPlayerModel extends EntityLivingBase {
this.textureManager.deleteTexture(this.remoteElytraResource); this.textureManager.deleteTexture(this.remoteElytraResource);
} }
this.remoteSkinTexture = HDSkinManager.getPreviewTexture(this.remoteSkinResource, this.profile, Type.SKIN, getBlankSkin(), listener); PreviewTextureManager ptm = HDSkinManager.getPreviewTextureManager(this.profile);
this.remoteElytraTexture = HDSkinManager.getPreviewTexture(this.remoteElytraResource, this.profile, Type.ELYTRA, getBlankElytra(), null);
this.remoteSkinTexture = ptm.getPreviewTexture(this.remoteSkinResource, Type.SKIN, getBlankSkin(), listener);
this.remoteElytraTexture = ptm.getPreviewTexture(this.remoteElytraResource, Type.ELYTRA, getBlankElytra(), null);
} }

View file

@ -4,15 +4,15 @@ import static com.mojang.authlib.minecraft.MinecraftProfileTexture.Type.ELYTRA;
import static com.mojang.authlib.minecraft.MinecraftProfileTexture.Type.SKIN; import static com.mojang.authlib.minecraft.MinecraftProfileTexture.Type.SKIN;
import static net.minecraft.client.renderer.GlStateManager.*; import static net.minecraft.client.renderer.GlStateManager.*;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.base.Splitter;
import com.google.common.util.concurrent.Futures; import com.google.common.collect.ImmutableMap;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mumfrey.liteloader.util.log.LiteLoaderLogger; import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.skins.SkinServer;
import com.voxelmodpack.hdskins.skins.SkinUploadResponse; import com.voxelmodpack.hdskins.skins.SkinUploadResponse;
import com.voxelmodpack.hdskins.upload.awt.ThreadOpenFilePNG; import com.voxelmodpack.hdskins.upload.awt.ThreadOpenFilePNG;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Gui; import net.minecraft.client.gui.Gui;
import net.minecraft.client.gui.GuiButton; import net.minecraft.client.gui.GuiButton;
@ -37,13 +37,17 @@ import org.lwjgl.opengl.GL11;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URI;
import java.nio.DoubleBuffer; import java.nio.DoubleBuffer;
import java.nio.file.Path; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.swing.*; import javax.swing.JFileChooser;
import javax.swing.UIManager;
public class GuiSkins extends GuiScreen {
public class GuiSkins extends GuiScreen implements FutureCallback<SkinUploadResponse> {
private static final int MAX_SKIN_DIMENSION = 1024; private static final int MAX_SKIN_DIMENSION = 1024;
private int updateCounter = 0; private int updateCounter = 0;
@ -55,6 +59,8 @@ public class GuiSkins extends GuiScreen implements FutureCallback<SkinUploadResp
private GuiButton btnModeSkinnySkin; private GuiButton btnModeSkinnySkin;
private GuiButton btnModeElytra; private GuiButton btnModeElytra;
private GuiButton btnAbout;
protected EntityPlayerModel localPlayer; protected EntityPlayerModel localPlayer;
protected EntityPlayerModel remotePlayer; protected EntityPlayerModel remotePlayer;
@ -86,7 +92,7 @@ public class GuiSkins extends GuiScreen implements FutureCallback<SkinUploadResp
public GuiSkins() { public GuiSkins() {
Minecraft minecraft = Minecraft.getMinecraft(); Minecraft minecraft = Minecraft.getMinecraft();
// this.screenTitle = manager; // this.screenTitle = manager;
GameProfile profile = minecraft.getSession().getProfile(); GameProfile profile = minecraft.getSession().getProfile();
this.localPlayer = getModel(profile); this.localPlayer = getModel(profile);
@ -192,8 +198,10 @@ public class GuiSkins extends GuiScreen implements FutureCallback<SkinUploadResp
this.buttonList.add(this.btnModeSkin = new GuiItemStackButton(4, 2, 2, skin)); this.buttonList.add(this.btnModeSkin = new GuiItemStackButton(4, 2, 2, skin));
skin = new ItemStack(Items.LEATHER_LEGGINGS); skin = new ItemStack(Items.LEATHER_LEGGINGS);
Items.LEATHER_LEGGINGS.setColor(skin, 0xfff500); Items.LEATHER_LEGGINGS.setColor(skin, 0xfff500);
this.buttonList.add(this.btnModeSkinnySkin = new GuiItemStackButton(6, 2, 21, skin));
this.buttonList.add(this.btnModeElytra = new GuiItemStackButton(5, 2, 52, new ItemStack(Items.ELYTRA))); this.buttonList.add(this.btnModeElytra = new GuiItemStackButton(5, 2, 52, new ItemStack(Items.ELYTRA)));
this.buttonList.add(this.btnModeSkinnySkin = new GuiItemStackButton(6, 2, 21, skin));
this.buttonList.add(this.btnAbout = new GuiButton(-1, this.width - 25, this.height - 25, 20, 20, "?"));
this.btnUpload.enabled = false; this.btnUpload.enabled = false;
this.btnBrowse.enabled = !this.mc.isFullScreen(); this.btnBrowse.enabled = !this.mc.isFullScreen();
@ -201,6 +209,7 @@ public class GuiSkins extends GuiScreen implements FutureCallback<SkinUploadResp
this.btnModeSkin.enabled = this.thinArmType; this.btnModeSkin.enabled = this.thinArmType;
this.btnModeSkinnySkin.enabled = !this.thinArmType; this.btnModeSkinnySkin.enabled = !this.thinArmType;
this.btnModeElytra.enabled = this.textureType == SKIN; this.btnModeElytra.enabled = this.textureType == SKIN;
} }
private void enableDnd() { private void enableDnd() {
@ -439,6 +448,10 @@ public class GuiSkins extends GuiScreen implements FutureCallback<SkinUploadResp
} }
this.drawHoveringText(I18n.format(text), mouseX, y); this.drawHoveringText(I18n.format(text), mouseX, y);
} }
if (this.btnAbout.isMouseOver()) {
SkinServer gateway = HDSkinManager.INSTANCE.getGatewayServer();
this.drawHoveringText(Splitter.on("\r\n").splitToList(gateway.toString()), mouseX, mouseY);
}
if (this.fetchingSkin) { if (this.fetchingSkin) {
String opacity1; String opacity1;
@ -490,7 +503,8 @@ public class GuiSkins extends GuiScreen implements FutureCallback<SkinUploadResp
enableDepth(); enableDepth();
} }
private void renderPlayerModel(EntityPlayerModel thePlayer, float xPosition, float yPosition, float scale, float mouseY, float mouseX, float partialTick) { private void renderPlayerModel(EntityPlayerModel thePlayer, float xPosition, float yPosition, float scale, float mouseY, float mouseX,
float partialTick) {
enableColorMaterial(); enableColorMaterial();
pushMatrix(); pushMatrix();
translate(xPosition, yPosition, 300.0F); translate(xPosition, yPosition, 300.0F);
@ -551,38 +565,44 @@ public class GuiSkins extends GuiScreen implements FutureCallback<SkinUploadResp
private void clearUploadedSkin(Session session) { private void clearUploadedSkin(Session session) {
this.uploadingSkin = true; this.uploadingSkin = true;
this.skinUploadMessage = I18n.format("hdskins.request"); this.skinUploadMessage = I18n.format("hdskins.request");
Futures.addCallback(HDSkinManager.INSTANCE.getGatewayServer().uploadSkin(session, null, this.textureType, this.thinArmType), this); HDSkinManager.INSTANCE.getGatewayServer()
.uploadSkin(session, null, this.textureType, getMetadata())
.thenAccept(this::onUploadComplete)
.exceptionally(this::onFailure);
} }
private void uploadSkin(Session session, @Nullable File skinFile) { private void uploadSkin(Session session, @Nullable File skinFile) {
this.uploadingSkin = true; this.uploadingSkin = true;
this.skinUploadMessage = I18n.format("hdskins.upload"); this.skinUploadMessage = I18n.format("hdskins.upload");
Path path = skinFile == null ? null : skinFile.toPath(); URI path = skinFile == null ? null : skinFile.toURI();
Futures.addCallback(HDSkinManager.INSTANCE.getGatewayServer().uploadSkin(session, path, this.textureType, this.thinArmType), this); HDSkinManager.INSTANCE.getGatewayServer()
.uploadSkin(session, path, this.textureType, getMetadata())
.thenAccept(this::onUploadComplete)
.exceptionally(this::onFailure);
}
private Map<String, String> getMetadata() {
return ImmutableMap.of("model", this.thinArmType ? "slim" : "default");
} }
private void setUploadError(@Nullable String error) { private void setUploadError(@Nullable String error) {
this.uploadError = error != null && error.startsWith("ERROR: ") ? error.substring(7) : error; this.uploadError = error;
this.btnUpload.enabled = true; this.btnUpload.enabled = true;
} }
@Override
public void onSuccess(@Nullable SkinUploadResponse result) {
if (result != null)
onUploadComplete(result);
}
@Override private Void onFailure(Throwable t) {
public void onFailure(Throwable t) {
LogManager.getLogger().warn("Upload failed", t); LogManager.getLogger().warn("Upload failed", t);
this.setUploadError(t.toString()); this.setUploadError(t.toString());
this.uploadingSkin = false; this.uploadingSkin = false;
return null;
} }
private void onUploadComplete(SkinUploadResponse response) { private void onUploadComplete(SkinUploadResponse response) {
LiteLoaderLogger.info("Upload completed with: %s", response); LiteLoaderLogger.info("Upload completed with: %s", response);
this.uploadingSkin = false; this.uploadingSkin = false;
if (!"OK".equalsIgnoreCase(response.getMessage())) { if (!response.isSuccess()) {
this.setUploadError(response.getMessage()); this.setUploadError(response.getMessage());
} else { } else {
this.pendingRemoteSkinRefresh = true; this.pendingRemoteSkinRefresh = true;

View file

@ -1,7 +1,9 @@
package com.voxelmodpack.hdskins.mod; package com.voxelmodpack.hdskins.mod;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import com.mumfrey.liteloader.core.LiteLoader; import com.mumfrey.liteloader.core.LiteLoader;
import com.mumfrey.liteloader.modconfig.AdvancedExposable;
import com.mumfrey.liteloader.modconfig.ConfigPanel; import com.mumfrey.liteloader.modconfig.ConfigPanel;
import com.mumfrey.liteloader.modconfig.ConfigStrategy; import com.mumfrey.liteloader.modconfig.ConfigStrategy;
import com.mumfrey.liteloader.modconfig.ExposableOptions; import com.mumfrey.liteloader.modconfig.ExposableOptions;
@ -9,26 +11,25 @@ import com.mumfrey.liteloader.util.ModUtilities;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.gui.EntityPlayerModel; import com.voxelmodpack.hdskins.gui.EntityPlayerModel;
import com.voxelmodpack.hdskins.gui.GLWindow; import com.voxelmodpack.hdskins.gui.GLWindow;
import com.voxelmodpack.hdskins.gui.GuiSkins;
import com.voxelmodpack.hdskins.gui.HDSkinsConfigPanel; import com.voxelmodpack.hdskins.gui.HDSkinsConfigPanel;
import com.voxelmodpack.hdskins.gui.RenderPlayerModel; import com.voxelmodpack.hdskins.gui.RenderPlayerModel;
import com.voxelmodpack.hdskins.skins.SkinServer; import com.voxelmodpack.hdskins.skins.SkinServer;
import com.voxelmodpack.hdskins.skins.SkinServerSerializer;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.gui.ScaledResolution;
import net.minecraft.client.resources.IReloadableResourceManager; import net.minecraft.client.resources.IReloadableResourceManager;
import java.io.File; import java.io.File;
import java.lang.reflect.Method;
import java.util.List; import java.util.List;
@ExposableOptions(strategy = ConfigStrategy.Unversioned, filename = "hdskins") @ExposableOptions(strategy = ConfigStrategy.Unversioned, filename = "hdskins")
public class LiteModHDSkinsMod implements HDSkinsMod { public class LiteModHDSkinsMod implements HDSkinsMod, AdvancedExposable {
@Expose @Expose
public List<String> skin_servers = SkinServer.defaultServers; public List<SkinServer> skin_servers = SkinServer.defaultServers;
@Expose @Expose
public boolean experimentalSkinDrop = true; public boolean experimentalSkinDrop = false;
@Override @Override
public String getName() { public String getName() {
@ -46,17 +47,6 @@ public class LiteModHDSkinsMod implements HDSkinsMod {
// register config // register config
LiteLoader.getInstance().registerExposable(this, null); LiteLoader.getInstance().registerExposable(this, null);
// try it initialize voxelmenu button
try {
Class<?> ex = Class.forName("com.thevoxelbox.voxelmenu.GuiMainMenuVoxelBox");
Method mRegisterCustomScreen = ex.getDeclaredMethod("registerCustomScreen", Class.class, String.class);
mRegisterCustomScreen.invoke(null, GuiSkins.class, "HD Skins Manager");
} catch (ClassNotFoundException var4) {
// voxelmenu's not here, man
} catch (Exception var5) {
var5.printStackTrace();
}
IReloadableResourceManager irrm = (IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager(); IReloadableResourceManager irrm = (IReloadableResourceManager) Minecraft.getMinecraft().getResourceManager();
irrm.registerReloadListener(HDSkinManager.INSTANCE); irrm.registerReloadListener(HDSkinManager.INSTANCE);
@ -70,6 +60,16 @@ public class LiteModHDSkinsMod implements HDSkinsMod {
HDSkinManager.clearSkinCache(); HDSkinManager.clearSkinCache();
} }
@Override
public void setupGsonSerialiser(GsonBuilder gsonBuilder) {
gsonBuilder.registerTypeAdapter(SkinServer.class, new SkinServerSerializer());
}
@Override
public File getConfigFile(File configFile, File configFileLocation, String defaultFileName) {
return null;
}
@Override @Override
public Class<? extends ConfigPanel> getConfigPanelClass() { public Class<? extends ConfigPanel> getConfigPanelClass() {
return HDSkinsConfigPanel.class; return HDSkinsConfigPanel.class;
@ -80,14 +80,7 @@ public class LiteModHDSkinsMod implements HDSkinsMod {
ModUtilities.addRenderer(EntityPlayerModel.class, new RenderPlayerModel<>(minecraft.getRenderManager())); ModUtilities.addRenderer(EntityPlayerModel.class, new RenderPlayerModel<>(minecraft.getRenderManager()));
// register skin servers. // register skin servers.
for (String s : skin_servers) { skin_servers.forEach(HDSkinManager.INSTANCE::addSkinServer);
try {
HDSkinManager.INSTANCE.addSkinServer(SkinServer.from(s));
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
} }
@Override @Override

View file

@ -1,7 +0,0 @@
@MethodsReturnNonnullByDefault
@ParametersAreNonnullByDefault
package com.voxelmodpack.hdskins.mod;
import mcp.MethodsReturnNonnullByDefault;
import javax.annotation.ParametersAreNonnullByDefault;

View file

@ -0,0 +1,26 @@
package com.voxelmodpack.hdskins.skins;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class CallableFutures {
public static <T> CompletableFuture<T> asyncFailableFuture(Callable<T> call, Executor exec) {
CompletableFuture<T> ret = new CompletableFuture<>();
exec.execute(() -> {
try {
ret.complete(call.call());
} catch (Throwable e) {
ret.completeExceptionally(e);
}
});
return ret;
}
public static <T> CompletableFuture<T> failedFuture(Exception e) {
CompletableFuture<T> ret = new CompletableFuture<>();
ret.completeExceptionally(e);
return ret;
}
}

View file

@ -0,0 +1,18 @@
package com.voxelmodpack.hdskins.skins;
import org.apache.commons.lang3.SystemUtils;
import org.apache.commons.lang3.builder.ToStringStyle;
public class IndentedToStringStyle extends ToStringStyle {
public static final ToStringStyle INSTANCE = new IndentedToStringStyle();
private IndentedToStringStyle() {
this.setContentStart(null);
this.setFieldSeparator(SystemUtils.LINE_SEPARATOR + " ");
this.setFieldSeparatorAtStart(true);
this.setContentEnd(null);
this.setUseIdentityHashCode(false);
this.setUseShortClassName(true);
}
}

View file

@ -1,10 +1,8 @@
package com.voxelmodpack.hdskins.skins; package com.voxelmodpack.hdskins.skins;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.Futures; import com.google.gson.annotations.Expose;
import com.google.common.util.concurrent.ListenableFuture;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture; import com.mojang.authlib.minecraft.MinecraftProfileTexture;
@ -15,43 +13,50 @@ import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.upload.ThreadMultipartPostUpload; import com.voxelmodpack.hdskins.upload.ThreadMultipartPostUpload;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.util.Session; import net.minecraft.util.Session;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.nio.file.Path; import java.util.Collections;
import java.util.EnumMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Matcher; import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ServerType("legacy")
public class LegacySkinServer implements SkinServer { public class LegacySkinServer implements SkinServer {
private static final String SERVER_ID = "7853dfddc358333843ad55a2c7485c4aa0380a51"; private static final String SERVER_ID = "7853dfddc358333843ad55a2c7485c4aa0380a51";
private static final Logger logger = LogManager.getLogger(); private static final Logger logger = LogManager.getLogger();
@Expose
private final String address; private final String address;
@Expose
private final String gateway; private final String gateway;
public LegacySkinServer(String address) {
this(address, null);
}
public LegacySkinServer(String address, @Nullable String gateway) { public LegacySkinServer(String address, @Nullable String gateway) {
this.address = address; this.address = address;
this.gateway = gateway; this.gateway = gateway;
} }
@Override @Override
public Optional<MinecraftProfileTexture> getPreviewTexture(MinecraftProfileTexture.Type type, GameProfile profile) { public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getPreviewTextures(GameProfile profile) {
if (Strings.isNullOrEmpty(this.gateway)) if (Strings.isNullOrEmpty(this.gateway)) {
return Optional.empty(); return Collections.emptyMap();
return Optional.of(new MinecraftProfileTexture(getPath(this.gateway, type, profile), null)); }
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = new EnumMap<>(MinecraftProfileTexture.Type.class);
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null));
}
return map;
} }
@Override @Override
@ -88,19 +93,25 @@ public class LegacySkinServer implements SkinServer {
} }
@Override @Override
public ListenableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable Path image, MinecraftProfileTexture.Type type, boolean thinSkinType) { public CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image,
MinecraftProfileTexture.Type type, Map<String, String> metadata) {
if (Strings.isNullOrEmpty(this.gateway)) if (Strings.isNullOrEmpty(this.gateway)) {
return Futures.immediateFailedFuture(new NullPointerException("gateway url is blank")); return CallableFutures.failedFuture(new NullPointerException("gateway url is blank"));
}
return HDSkinManager.skinUploadExecutor.submit(() -> { return CallableFutures.asyncFailableFuture(() -> {
verifyServerConnection(session, SERVER_ID); verifyServerConnection(session, SERVER_ID);
String model = metadata.getOrDefault("model", "default");
Map<String, ?> data = image == null ? getClearData(session, type) : getUploadData(session, type, (thinSkinType ? "slim" : "default"), image); Map<String, ?> data = image == null ? getClearData(session, type) : getUploadData(session, type, model, image);
ThreadMultipartPostUpload upload = new ThreadMultipartPostUpload(this.gateway, data); ThreadMultipartPostUpload upload = new ThreadMultipartPostUpload(this.gateway, data);
String response = upload.uploadMultipart(); String response = upload.uploadMultipart();
if (response.startsWith("ERROR: ")) {
response = response.substring(7);
}
return new SkinUploadResponse(response.equalsIgnoreCase("OK"), response); return new SkinUploadResponse(response.equalsIgnoreCase("OK"), response);
});
}, HDSkinManager.skinUploadExecutor);
} }
private static Map<String, ?> getData(Session session, MinecraftProfileTexture.Type type, String model, String param, Object val) { private static Map<String, ?> getData(Session session, MinecraftProfileTexture.Type type, String model, String param, Object val) {
@ -116,7 +127,7 @@ public class LegacySkinServer implements SkinServer {
return getData(session, type, "default", "clear", "1"); return getData(session, type, "default", "clear", "1");
} }
private static Map<String, ?> getUploadData(Session session, MinecraftProfileTexture.Type type, String model, Path skinFile) { private static Map<String, ?> getUploadData(Session session, MinecraftProfileTexture.Type type, String model, URI skinFile) {
return getData(session, type, model, type.toString().toLowerCase(Locale.US), skinFile); return getData(session, type, model, type.toString().toLowerCase(Locale.US), skinFile);
} }
@ -131,24 +142,11 @@ public class LegacySkinServer implements SkinServer {
service.joinServer(session.getProfile(), session.getToken(), serverId); service.joinServer(session.getProfile(), session.getToken(), serverId);
} }
/**
* Should be in the format {@code legacy:http://address;http://gateway}. Gateway is optional.
*/
static LegacySkinServer from(String parsed) {
Matcher matcher = Pattern.compile("^legacy:(.+?)(?:;(.*))?$").matcher(parsed);
if (matcher.find()) {
String addr = matcher.group(1);
String gate = matcher.group(2);
return new LegacySkinServer(addr, gate);
}
throw new IllegalArgumentException("server format string was not correct");
}
@Override @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return new ToStringBuilder(this, IndentedToStringStyle.INSTANCE)
.add("address", address) .append("address", this.address)
.add("gateway", gateway) .append("gateway", this.gateway)
.toString(); .build();
} }
} }

View file

@ -0,0 +1,13 @@
package com.voxelmodpack.hdskins.skins;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ServerType {
String value();
}

View file

@ -1,39 +1,33 @@
package com.voxelmodpack.hdskins.skins; package com.voxelmodpack.hdskins.skins;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
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 net.minecraft.util.Session; import net.minecraft.util.Session;
import java.nio.file.Path; import java.net.URI;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public interface SkinServer { public interface SkinServer {
List<String> defaultServers = Lists.newArrayList("legacy:http://skins.voxelmodpack.com;http://skinmanager.voxelmodpack.com"); List<SkinServer> defaultServers = Lists.newArrayList(new LegacySkinServer(
"http://skins.voxelmodpack.com",
"http://skinmanager.voxelmodpack.com"));
Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile); Optional<MinecraftTexturesPayload> loadProfileData(GameProfile profile);
Optional<MinecraftProfileTexture> getPreviewTexture(MinecraftProfileTexture.Type type, GameProfile profile); default Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getPreviewTextures(GameProfile profile) {
return loadProfileData(profile).map(MinecraftTexturesPayload::getTextures).orElse(Collections.emptyMap());
ListenableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable Path image, MinecraftProfileTexture.Type type, boolean thinArmType);
static SkinServer from(String server) {
int i = server.indexOf(':');
if (i >= 0) {
String type = server.substring(0, i);
switch (type) {
case "legacy":
return LegacySkinServer.from(server);
case "valhalla": {
return ValhallaSkinServer.from(server);
}
}
}
throw new IllegalArgumentException();
} }
CompletableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable URI image,
MinecraftProfileTexture.Type type, Map<String, String> metadata);
} }

View file

@ -0,0 +1,34 @@
package com.voxelmodpack.hdskins.skins;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonIOException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.voxelmodpack.hdskins.HDSkinManager;
import java.lang.reflect.Type;
public class SkinServerSerializer implements JsonSerializer<SkinServer>, JsonDeserializer<SkinServer> {
@Override
public JsonElement serialize(SkinServer src, Type typeOfSrc, JsonSerializationContext context) {
ServerType serverType = src.getClass().getAnnotation(ServerType.class);
if (serverType == null) {
throw new JsonIOException("Skin server class did not have a type: " + typeOfSrc);
}
JsonObject obj = context.serialize(src).getAsJsonObject();
obj.addProperty("type", serverType.value());
return obj;
}
@Override
public SkinServer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String type = json.getAsJsonObject().get("type").getAsString();
Class<? extends SkinServer> clas = HDSkinManager.INSTANCE.getSkinServerClass(type);
return context.deserialize(json, clas);
}
}

View file

@ -2,8 +2,6 @@ package com.voxelmodpack.hdskins.skins;
import com.google.common.base.MoreObjects; import com.google.common.base.MoreObjects;
import javax.annotation.Nullable;
public class SkinUploadResponse { public class SkinUploadResponse {
private final boolean success; private final boolean success;
@ -18,7 +16,6 @@ public class SkinUploadResponse {
return success; return success;
} }
@Nullable
public String getMessage() { public String getMessage() {
return message; return message;
} }

View file

@ -1,45 +1,231 @@
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.google.gson.annotations.Expose;
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.commons.lang3.builder.ToStringBuilder;
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.regex.Matcher; import java.util.UUID;
import java.util.regex.Pattern; import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ServerType("valhalla")
public class ValhallaSkinServer implements SkinServer { public class ValhallaSkinServer implements SkinServer {
@SuppressWarnings("unused") @Expose
private final String baseURL; private final String address;
private final Gson gson = new GsonBuilder()
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
.create();
public ValhallaSkinServer(String baseURL) { private transient String accessToken;
this.baseURL = baseURL;
public ValhallaSkinServer(String address) {
this.address = address;
} }
@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);
}
private SkinUploadResponse resetSkin(CloseableHttpClient client, GameProfile profile, MinecraftProfileTexture.Type type) throws IOException {
return upload(client, RequestBuilder.delete()
.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.address, 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.address, UUIDTypeAdapter.fromUUID(profile.getId())));
}
private URI getHandshakeURI() {
return URI.create(String.format("%s/auth/handshake", this.address));
}
private URI getResponseURI() {
return URI.create(String.format("%s/auth/response", this.address));
} }
@Override @Override
public ListenableFuture<SkinUploadResponse> uploadSkin(Session session, @Nullable Path image, MinecraftProfileTexture.Type type, boolean thinArmType) { public String toString() {
return null; return new ToStringBuilder(this, IndentedToStringStyle.INSTANCE)
.append("address", this.address)
.toString();
} }
static ValhallaSkinServer from(String server) { @SuppressWarnings("WeakerAccess")
Matcher matcher = Pattern.compile("^valhalla:(.*)$").matcher(server); static class AuthHandshake {
if (matcher.find())
return new ValhallaSkinServer(matcher.group(1)); boolean offline;
throw new IllegalArgumentException(); String serverId;
long verifyToken;
}
@SuppressWarnings("WeakerAccess")
static class AuthResponse {
String accessToken;
UUID userId;
} }
} }

View file

@ -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;

View file

@ -7,6 +7,7 @@ import com.minelittlepony.pony.data.PonyDataSerialzier;
import com.minelittlepony.render.PonySkullRenderer; import com.minelittlepony.render.PonySkullRenderer;
import com.mumfrey.liteloader.core.LiteLoader; import com.mumfrey.liteloader.core.LiteLoader;
import com.voxelmodpack.hdskins.HDSkinManager; import com.voxelmodpack.hdskins.HDSkinManager;
import com.voxelmodpack.hdskins.skins.LegacySkinServer;
import com.voxelmodpack.hdskins.skins.SkinServer; import com.voxelmodpack.hdskins.skins.SkinServer;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.entity.RenderManager; import net.minecraft.client.renderer.entity.RenderManager;
@ -27,7 +28,8 @@ public class MineLittlePony {
public static final String MOD_NAME = "Mine Little Pony"; public static final String MOD_NAME = "Mine Little Pony";
public static final String MOD_VERSION = "@VERSION@"; public static final String MOD_VERSION = "@VERSION@";
private static final String MINELP_LEGACY_SERVER = "legacy:http://minelpskins.voxelmodpack.com;http://minelpskinmanager.voxelmodpack.com"; private static final String MINELP_LEGACY_SERVER = "http://minelpskins.voxelmodpack.com";
private static final String MINELP_LEGACY_GATEWAY = "http://minelpskinmanager.voxelmodpack.com";
private static final KeyBinding SETTINGS_GUI = new KeyBinding("Settings", Keyboard.KEY_F9, "Mine Little Pony"); private static final KeyBinding SETTINGS_GUI = new KeyBinding("Settings", Keyboard.KEY_F9, "Mine Little Pony");
@ -57,7 +59,7 @@ public class MineLittlePony {
ms.registerMetadataSectionType(new PonyDataSerialzier(), IPonyData.class); ms.registerMetadataSectionType(new PonyDataSerialzier(), IPonyData.class);
// This also makes it the default gateway server. // This also makes it the default gateway server.
SkinServer.defaultServers.add(MINELP_LEGACY_SERVER); SkinServer.defaultServers.add(new LegacySkinServer(MINELP_LEGACY_SERVER, MINELP_LEGACY_GATEWAY));
} }
/** /**