mirror of
https://github.com/MineLittlePony/MineLittlePony.git
synced 2025-02-21 20:03:10 +01:00
Backport skin server changes and remove the defaults for old legacy skin servers
This commit is contained in:
parent
1cddac4ea4
commit
1e55a7a14a
14 changed files with 455 additions and 177 deletions
|
@ -4,8 +4,6 @@ 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.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
@ -13,6 +11,7 @@ import com.google.common.collect.Maps;
|
||||||
import com.google.common.collect.Streams;
|
import com.google.common.collect.Streams;
|
||||||
import com.minelittlepony.gui.IconicButton;
|
import com.minelittlepony.gui.IconicButton;
|
||||||
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.minecraft.MinecraftProfileTexture.Type;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
|
||||||
import com.mojang.authlib.properties.Property;
|
import com.mojang.authlib.properties.Property;
|
||||||
|
@ -20,15 +19,12 @@ import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
||||||
import com.mumfrey.liteloader.core.LiteLoader;
|
import com.mumfrey.liteloader.core.LiteLoader;
|
||||||
import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
|
import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
|
||||||
import com.voxelmodpack.hdskins.ducks.INetworkPlayerInfo;
|
import com.voxelmodpack.hdskins.ducks.INetworkPlayerInfo;
|
||||||
|
import com.voxelmodpack.hdskins.gui.Feature;
|
||||||
import com.voxelmodpack.hdskins.gui.GuiSkins;
|
import com.voxelmodpack.hdskins.gui.GuiSkins;
|
||||||
import com.voxelmodpack.hdskins.resources.SkinResourceManager;
|
import com.voxelmodpack.hdskins.resources.SkinResourceManager;
|
||||||
import com.voxelmodpack.hdskins.resources.TextureLoader;
|
import com.voxelmodpack.hdskins.resources.TextureLoader;
|
||||||
import com.voxelmodpack.hdskins.resources.texture.ImageBufferDownloadHD;
|
import com.voxelmodpack.hdskins.resources.texture.ImageBufferDownloadHD;
|
||||||
import com.voxelmodpack.hdskins.server.BethlehemSkinServer;
|
|
||||||
import com.voxelmodpack.hdskins.server.LegacySkinServer;
|
|
||||||
import com.voxelmodpack.hdskins.server.ServerType;
|
|
||||||
import com.voxelmodpack.hdskins.server.SkinServer;
|
import com.voxelmodpack.hdskins.server.SkinServer;
|
||||||
import com.voxelmodpack.hdskins.server.ValhallaSkinServer;
|
|
||||||
import com.voxelmodpack.hdskins.util.CallableFutures;
|
import com.voxelmodpack.hdskins.util.CallableFutures;
|
||||||
import com.voxelmodpack.hdskins.util.MoreStreams;
|
import com.voxelmodpack.hdskins.util.MoreStreams;
|
||||||
import com.voxelmodpack.hdskins.util.PlayerUtil;
|
import com.voxelmodpack.hdskins.util.PlayerUtil;
|
||||||
|
@ -58,7 +54,6 @@ 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.nio.charset.StandardCharsets;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -88,7 +83,6 @@ 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 LoadingCache<GameProfile, CompletableFuture<Map<Type, MinecraftProfileTexture>>> skins = CacheBuilder.newBuilder()
|
private LoadingCache<GameProfile, CompletableFuture<Map<Type, MinecraftProfileTexture>>> skins = CacheBuilder.newBuilder()
|
||||||
|
@ -103,11 +97,6 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
|
||||||
private Function<List<SkinServer>, GuiSkins> skinsGuiFunc = GuiSkins::new;
|
private Function<List<SkinServer>, GuiSkins> skinsGuiFunc = GuiSkins::new;
|
||||||
|
|
||||||
private HDSkinManager() {
|
private HDSkinManager() {
|
||||||
|
|
||||||
// register default skin server types
|
|
||||||
addSkinServerType(LegacySkinServer.class);
|
|
||||||
addSkinServerType(ValhallaSkinServer.class);
|
|
||||||
addSkinServerType(BethlehemSkinServer.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSkinsGui(Function<List<SkinServer>, GuiSkins> skinsGuiFunc) {
|
public void setSkinsGui(Function<List<SkinServer>, GuiSkins> skinsGuiFunc) {
|
||||||
|
@ -138,11 +127,13 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
|
||||||
|
|
||||||
for (SkinServer server : skinServers) {
|
for (SkinServer server : skinServers) {
|
||||||
try {
|
try {
|
||||||
server.loadProfileData(profile).getTextures().forEach(textureMap::putIfAbsent);
|
if (!server.supportsFeature(Feature.SYNTHETIC)) {
|
||||||
if (textureMap.size() == Type.values().length) {
|
server.loadProfileData(profile).getTextures().forEach(textureMap::putIfAbsent);
|
||||||
break;
|
if (textureMap.size() == Type.values().length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException | AuthenticationException e) {
|
||||||
logger.trace(e);
|
logger.trace(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,23 +222,6 @@ public final class HDSkinManager implements IResourceManagerReloadListener {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addSkinServerType(Class<? extends SkinServer> type) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addSkinServer(SkinServer skinServer) {
|
void addSkinServer(SkinServer skinServer) {
|
||||||
this.skinServers.add(skinServer);
|
this.skinServers.add(skinServer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.voxelmodpack.hdskins;
|
package com.voxelmodpack.hdskins;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.annotations.Expose;
|
import com.google.gson.annotations.Expose;
|
||||||
import com.mumfrey.liteloader.Configurable;
|
import com.mumfrey.liteloader.Configurable;
|
||||||
|
@ -14,8 +15,7 @@ import com.mumfrey.liteloader.util.ModUtilities;
|
||||||
import com.voxelmodpack.hdskins.gui.EntityPlayerModel;
|
import com.voxelmodpack.hdskins.gui.EntityPlayerModel;
|
||||||
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.server.SkinServer;
|
import com.voxelmodpack.hdskins.server.*;
|
||||||
import com.voxelmodpack.hdskins.server.SkinServerSerializer;
|
|
||||||
import com.voxelmodpack.hdskins.upload.GLWindow;
|
import com.voxelmodpack.hdskins.upload.GLWindow;
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
@ -35,7 +35,10 @@ public class LiteModHDSkins implements InitCompleteListener, ViewportListener, C
|
||||||
}
|
}
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
public List<SkinServer> skin_servers = SkinServer.defaultServers;
|
public List<SkinServer> skin_servers = Lists.newArrayList(
|
||||||
|
new ValhallaSkinServer("https://skins.minelittlepony-mod.com"),
|
||||||
|
new YggdrasilSkinServer()
|
||||||
|
);
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
public boolean experimentalSkinDrop = false;
|
public boolean experimentalSkinDrop = false;
|
||||||
|
@ -54,7 +57,7 @@ public class LiteModHDSkins implements InitCompleteListener, ViewportListener, C
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
return "4.0.0";
|
return "4.0.1";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeConfig() {
|
public void writeConfig() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.voxelmodpack.hdskins;
|
package com.voxelmodpack.hdskins;
|
||||||
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.audio.PositionedSoundRecord;
|
||||||
import net.minecraft.init.Items;
|
import net.minecraft.init.Items;
|
||||||
import net.minecraft.inventory.EntityEquipmentSlot;
|
import net.minecraft.inventory.EntityEquipmentSlot;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
|
@ -10,10 +11,9 @@ import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterators;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
import com.mojang.authlib.exceptions.AuthenticationException;
|
import com.mojang.authlib.exceptions.*;
|
||||||
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
|
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type;
|
||||||
import com.voxelmodpack.hdskins.gui.EntityPlayerModel;
|
import com.voxelmodpack.hdskins.gui.EntityPlayerModel;
|
||||||
|
@ -33,7 +33,6 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class SkinUploader implements Closeable {
|
public class SkinUploader implements Closeable {
|
||||||
|
|
||||||
|
@ -55,11 +54,12 @@ public class SkinUploader implements Closeable {
|
||||||
|
|
||||||
private Type skinType;
|
private Type skinType;
|
||||||
|
|
||||||
private Map<String, String> skinMetadata = new HashMap<String, String>();
|
private Map<String, String> skinMetadata = new HashMap<>();
|
||||||
|
|
||||||
private volatile boolean fetchingSkin = false;
|
private volatile boolean fetchingSkin = false;
|
||||||
private volatile boolean throttlingNeck = false;
|
private volatile boolean throttlingNeck = false;
|
||||||
private volatile boolean offline = false;
|
private volatile boolean offline = false;
|
||||||
|
private volatile boolean pending = false;
|
||||||
|
|
||||||
private volatile boolean sendingSkin = false;
|
private volatile boolean sendingSkin = false;
|
||||||
|
|
||||||
|
@ -78,10 +78,6 @@ public class SkinUploader implements Closeable {
|
||||||
|
|
||||||
private final Minecraft mc = Minecraft.getMinecraft();
|
private final Minecraft mc = Minecraft.getMinecraft();
|
||||||
|
|
||||||
private static <T> Iterator<T> cycle(List<T> list, Predicate<T> filter) {
|
|
||||||
return Iterables.cycle(Iterables.filter(list, filter::test)).iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SkinUploader(List<SkinServer> servers, EntityPlayerModel local, EntityPlayerModel remote, ISkinUploadHandler listener) {
|
public SkinUploader(List<SkinServer> servers, EntityPlayerModel local, EntityPlayerModel remote, ISkinUploadHandler listener) {
|
||||||
|
|
||||||
localPlayer = local;
|
localPlayer = local;
|
||||||
|
@ -91,7 +87,7 @@ public class SkinUploader implements Closeable {
|
||||||
skinMetadata.put("model", "default");
|
skinMetadata.put("model", "default");
|
||||||
|
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
skinServers = cycle(servers, SkinServer::verifyGateway);
|
skinServers = Iterators.cycle(servers);
|
||||||
cycleGateway();
|
cycleGateway();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,17 +186,14 @@ public class SkinUploader implements Closeable {
|
||||||
sendingSkin = true;
|
sendingSkin = true;
|
||||||
status = statusMsg;
|
status = statusMsg;
|
||||||
|
|
||||||
return gateway.uploadSkin(new SkinUpload(mc.getSession(), skinType, localSkin == null ? null : localSkin.toURI(), skinMetadata)).handle((response, throwable) -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
if (throwable == null) {
|
try {
|
||||||
logger.info("Upload completed with: %s", response);
|
gateway.performSkinUpload(new SkinUpload(mc.getSession(), skinType, localSkin == null ? null : localSkin.toURI(), skinMetadata));
|
||||||
setError(null);
|
setError("");
|
||||||
} else {
|
} catch (IOException | AuthenticationException e) {
|
||||||
setError(Throwables.getRootCause(throwable).toString());
|
handleException(e);
|
||||||
}
|
}
|
||||||
|
}, HDSkinManager.skinUploadExecutor).thenRunAsync(this::fetchRemote);
|
||||||
fetchRemote();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<MoreHttpResponses> downloadSkin() {
|
public CompletableFuture<MoreHttpResponses> downloadSkin() {
|
||||||
|
@ -210,42 +203,59 @@ public class SkinUploader implements Closeable {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void fetchRemote() {
|
protected void fetchRemote() {
|
||||||
|
boolean wasPending = pending;
|
||||||
|
pending = false;
|
||||||
fetchingSkin = true;
|
fetchingSkin = true;
|
||||||
throttlingNeck = false;
|
throttlingNeck = false;
|
||||||
offline = false;
|
offline = false;
|
||||||
|
|
||||||
remotePlayer.reloadRemoteSkin(this, (type, location, profileTexture) -> {
|
remotePlayer.reloadRemoteSkin(this, (type, location, profileTexture) -> {
|
||||||
fetchingSkin = false;
|
fetchingSkin = false;
|
||||||
|
if (type == skinType) {
|
||||||
|
fetchingSkin = false;
|
||||||
|
if (wasPending) {
|
||||||
|
Minecraft.getMinecraft().getSoundHandler().playSound(PositionedSoundRecord.getMasterRecord(net.minecraft.init.SoundEvents.ENTITY_VILLAGER_YES, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
listener.onSetRemoteSkin(type, location, profileTexture);
|
listener.onSetRemoteSkin(type, location, profileTexture);
|
||||||
}).handle((a, throwable) -> {
|
}).handleAsync((a, throwable) -> {
|
||||||
fetchingSkin = false;
|
fetchingSkin = false;
|
||||||
|
|
||||||
if (throwable != null) {
|
if (throwable != null) {
|
||||||
throwable = throwable.getCause();
|
handleException(throwable.getCause());
|
||||||
|
} else {
|
||||||
if (throwable instanceof AuthenticationUnavailableException) {
|
retries = 1;
|
||||||
offline = true;
|
|
||||||
} else if (throwable instanceof AuthenticationException) {
|
|
||||||
throttlingNeck = true;
|
|
||||||
} else if (throwable instanceof HttpException) {
|
|
||||||
HttpException ex = (HttpException)throwable;
|
|
||||||
|
|
||||||
logger.error(ex.getReasonPhrase(), ex);
|
|
||||||
|
|
||||||
int code = ex.getStatusCode();
|
|
||||||
|
|
||||||
if (code >= 500) {
|
|
||||||
setError(String.format("A fatal server error has ocurred (check logs for details): \n%s", ex.getReasonPhrase()));
|
|
||||||
} else if (code >= 400 && code != 403 && code != 404) {
|
|
||||||
setError(ex.getReasonPhrase());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.error("Unhandled exception", throwable);
|
|
||||||
setError(throwable.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
});
|
}, Minecraft.getMinecraft()::addScheduledTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleException(Throwable throwable) {
|
||||||
|
throwable = Throwables.getRootCause(throwable);
|
||||||
|
|
||||||
|
fetchingSkin = false;
|
||||||
|
|
||||||
|
if (throwable instanceof AuthenticationUnavailableException) {
|
||||||
|
offline = true;
|
||||||
|
} else if (throwable instanceof InvalidCredentialsException) {
|
||||||
|
setError("hdskins.error.session");
|
||||||
|
} else if (throwable instanceof AuthenticationException) {
|
||||||
|
throttlingNeck = true;
|
||||||
|
} else if (throwable instanceof HttpException) {
|
||||||
|
HttpException ex = (HttpException)throwable;
|
||||||
|
|
||||||
|
int code = ex.getStatusCode();
|
||||||
|
|
||||||
|
if (code >= 500) {
|
||||||
|
logger.error(ex.getReasonPhrase(), ex);
|
||||||
|
setError("A fatal server error has ocurred (check logs for details): \n" + ex.getReasonPhrase());
|
||||||
|
} else if (code >= 400 && code != 403 && code != 404) {
|
||||||
|
setError(ex.getReasonPhrase());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error("Unhandled exception", throwable);
|
||||||
|
setError(throwable.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -282,11 +292,19 @@ public class SkinUploader implements Closeable {
|
||||||
retries++;
|
retries++;
|
||||||
fetchRemote();
|
fetchRemote();
|
||||||
}
|
}
|
||||||
|
} else if (pending) {
|
||||||
|
fetchRemote();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public CompletableFuture<PreviewTextureManager> loadTextures(GameProfile profile) {
|
public CompletableFuture<PreviewTextureManager> loadTextures(GameProfile profile) {
|
||||||
return gateway.getPreviewTextures(profile).thenApply(PreviewTextureManager::new);
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
try {
|
||||||
|
return new PreviewTextureManager(gateway.getPreviewTextures(profile));
|
||||||
|
} catch (IOException | AuthenticationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, HDSkinManager.skinDownloadExecutor); // run on main thread
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ISkinUploadHandler {
|
public interface ISkinUploadHandler {
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
package com.voxelmodpack.hdskins.gui;
|
package com.voxelmodpack.hdskins.gui;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the possible features that a skin server can implement.
|
* Represents the possible features that a skin net can implement.
|
||||||
*/
|
*/
|
||||||
public enum Feature {
|
public enum Feature {
|
||||||
|
/**
|
||||||
|
* Whether this skin server is usable in-game.
|
||||||
|
*
|
||||||
|
* Synthetic skin servers will not be queried for skins when in-game,
|
||||||
|
* but can be still previewed, or accept textures to upload/download.
|
||||||
|
*/
|
||||||
|
SYNTHETIC,
|
||||||
/**
|
/**
|
||||||
* Whether a server has write access.
|
* Whether a server has write access.
|
||||||
* i.e. If the server allows for users to upload a new skin.
|
* i.e. If the server allows for users to upload a new skin.
|
||||||
|
|
|
@ -16,6 +16,7 @@ import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@ServerType("bethlehem")
|
@ServerType("bethlehem")
|
||||||
public class BethlehemSkinServer implements SkinServer {
|
public class BethlehemSkinServer implements SkinServer {
|
||||||
|
|
||||||
|
@ -40,7 +41,7 @@ public class BethlehemSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SkinUploadResponse performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
|
public void performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
|
||||||
SkinServer.verifyServerConnection(upload.getSession(), SERVER_ID);
|
SkinServer.verifyServerConnection(upload.getSession(), SERVER_ID);
|
||||||
|
|
||||||
NetClient client = new NetClient("POST", address);
|
NetClient client = new NetClient("POST", address);
|
||||||
|
@ -53,10 +54,8 @@ public class BethlehemSkinServer implements SkinServer {
|
||||||
|
|
||||||
try (MoreHttpResponses response = client.send()) {
|
try (MoreHttpResponses response = client.send()) {
|
||||||
if (!response.ok()) {
|
if (!response.ok()) {
|
||||||
throw new HttpException(response.getResponse());
|
throw response.exception();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SkinUploadResponse(response.text());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
@ServerType("legacy")
|
@ServerType("legacy")
|
||||||
public class LegacySkinServer implements SkinServer {
|
public class LegacySkinServer implements SkinServer {
|
||||||
|
|
||||||
|
@ -51,21 +52,19 @@ public class LegacySkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<MinecraftTexturesPayload> getPreviewTextures(GameProfile profile) {
|
public MinecraftTexturesPayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException {
|
||||||
return CallableFutures.asyncFailableFuture(() -> {
|
SkinServer.verifyServerConnection(Minecraft.getMinecraft().getSession(), SERVER_ID);
|
||||||
SkinServer.verifyServerConnection(Minecraft.getMinecraft().getSession(), SERVER_ID);
|
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(gateway)) {
|
if (Strings.isNullOrEmpty(gateway)) {
|
||||||
throw gatewayUnsupported();
|
throw gatewayUnsupported();
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = new EnumMap<>(MinecraftProfileTexture.Type.class);
|
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> map = new EnumMap<>(MinecraftProfileTexture.Type.class);
|
||||||
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
for (MinecraftProfileTexture.Type type : MinecraftProfileTexture.Type.values()) {
|
||||||
map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null));
|
map.put(type, new MinecraftProfileTexture(getPath(gateway, type, profile), null));
|
||||||
}
|
}
|
||||||
|
|
||||||
return TexturesPayloadBuilder.createTexturesPayload(profile, map);
|
return TexturesPayloadBuilder.createTexturesPayload(profile, map);
|
||||||
}, HDSkinManager.skinDownloadExecutor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -109,7 +108,7 @@ public class LegacySkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SkinUploadResponse performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
|
public void performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
|
||||||
if (Strings.isNullOrEmpty(gateway)) {
|
if (Strings.isNullOrEmpty(gateway)) {
|
||||||
throw gatewayUnsupported();
|
throw gatewayUnsupported();
|
||||||
}
|
}
|
||||||
|
@ -134,8 +133,6 @@ public class LegacySkinServer implements SkinServer {
|
||||||
if (!response.equalsIgnoreCase("OK") && !response.endsWith("OK")) {
|
if (!response.equalsIgnoreCase("OK") && !response.endsWith("OK")) {
|
||||||
throw new HttpException(response, resp.getResponseCode(), null);
|
throw new HttpException(response, resp.getResponseCode(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SkinUploadResponse(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private UnsupportedOperationException gatewayUnsupported() {
|
private UnsupportedOperationException gatewayUnsupported() {
|
||||||
|
@ -161,11 +158,6 @@ public class LegacySkinServer implements SkinServer {
|
||||||
return String.format("%s/%s/%s.png?%s", address, path, uuid, Long.toString(new Date().getTime() / 1000));
|
return String.format("%s/%s/%s.png?%s", address, path, uuid, Long.toString(new Date().getTime() / 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean verifyGateway() {
|
|
||||||
return !Strings.isNullOrEmpty(gateway);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsFeature(Feature feature) {
|
public boolean supportsFeature(Feature feature) {
|
||||||
switch (feature) {
|
switch (feature) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package com.voxelmodpack.hdskins.server;
|
package com.voxelmodpack.hdskins.server;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.mojang.authlib.GameProfile;
|
import com.mojang.authlib.GameProfile;
|
||||||
|
@ -9,16 +8,12 @@ import com.mojang.authlib.minecraft.MinecraftSessionService;
|
||||||
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
||||||
import com.mojang.util.UUIDTypeAdapter;
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
import com.mumfrey.liteloader.modconfig.Exposable;
|
import com.mumfrey.liteloader.modconfig.Exposable;
|
||||||
import com.voxelmodpack.hdskins.HDSkinManager;
|
|
||||||
import com.voxelmodpack.hdskins.gui.Feature;
|
import com.voxelmodpack.hdskins.gui.Feature;
|
||||||
import com.voxelmodpack.hdskins.util.CallableFutures;
|
|
||||||
import net.minecraft.client.Minecraft;
|
import net.minecraft.client.Minecraft;
|
||||||
import net.minecraft.util.Session;
|
import net.minecraft.util.Session;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public interface SkinServer extends Exposable {
|
public interface SkinServer extends Exposable {
|
||||||
|
|
||||||
|
@ -26,11 +21,6 @@ public interface SkinServer extends Exposable {
|
||||||
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
|
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
List<SkinServer> defaultServers = Lists.newArrayList(new LegacySkinServer(
|
|
||||||
"http://skins.voxelmodpack.com",
|
|
||||||
"http://skinmanager.voxelmodpack.com")
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true for any features that this skin server supports.
|
* Returns true for any features that this skin server supports.
|
||||||
*/
|
*/
|
||||||
|
@ -43,7 +33,7 @@ public interface SkinServer extends Exposable {
|
||||||
*
|
*
|
||||||
* @throws IOException If any authentication or network error occurs.
|
* @throws IOException If any authentication or network error occurs.
|
||||||
*/
|
*/
|
||||||
MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException;
|
MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously uploads a skin to this server.
|
* Synchronously uploads a skin to this server.
|
||||||
|
@ -55,36 +45,18 @@ public interface SkinServer extends Exposable {
|
||||||
* @throws IOException If any authentication or network error occurs.
|
* @throws IOException If any authentication or network error occurs.
|
||||||
* @throws AuthenticationException
|
* @throws AuthenticationException
|
||||||
*/
|
*/
|
||||||
SkinUploadResponse performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException;
|
void performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Asynchronously uploads a skin to the server.
|
|
||||||
*
|
|
||||||
* Returns an incomplete future for chaining other actions to be performed after this method completes.
|
|
||||||
* Actions are dispatched to the default skinUploadExecutor
|
|
||||||
*
|
|
||||||
* @param upload The payload to send.
|
|
||||||
*/
|
|
||||||
default CompletableFuture<SkinUploadResponse> uploadSkin(SkinUpload upload) {
|
|
||||||
return CallableFutures.asyncFailableFuture(() -> performSkinUpload(upload), HDSkinManager.skinUploadExecutor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously loads texture information for the provided profile.
|
* Asynchronously loads texture information for the provided profile.
|
||||||
*
|
*
|
||||||
* Returns an incomplete future for chaining other actions to be performed after this method completes.
|
* Returns an incomplete future for chaining other actions to be performed after this method completes.
|
||||||
* Actions are dispatched to the default skinDownloadExecutor
|
* Actions are dispatched to the default skinDownloadExecutor
|
||||||
|
* @throws AuthenticationException
|
||||||
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
default CompletableFuture<MinecraftTexturesPayload> getPreviewTextures(GameProfile profile) {
|
default MinecraftTexturesPayload getPreviewTextures(GameProfile profile) throws IOException, AuthenticationException {
|
||||||
return CallableFutures.asyncFailableFuture(() -> loadProfileData(profile), HDSkinManager.skinDownloadExecutor);
|
return loadProfileData(profile);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to validate this skin server's state.
|
|
||||||
* Any servers with an invalid gateway format will not be loaded and generate an exception.
|
|
||||||
*/
|
|
||||||
default boolean verifyGateway() {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package com.voxelmodpack.hdskins.server;
|
package com.voxelmodpack.hdskins.server;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.BiMap;
|
||||||
|
import com.google.common.collect.HashBiMap;
|
||||||
import com.google.gson.JsonDeserializationContext;
|
import com.google.gson.JsonDeserializationContext;
|
||||||
import com.google.gson.JsonDeserializer;
|
import com.google.gson.JsonDeserializer;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
|
@ -10,10 +13,36 @@ import com.google.gson.JsonSerializationContext;
|
||||||
import com.google.gson.JsonSerializer;
|
import com.google.gson.JsonSerializer;
|
||||||
import com.voxelmodpack.hdskins.HDSkinManager;
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
|
||||||
public class SkinServerSerializer implements JsonSerializer<SkinServer>, JsonDeserializer<SkinServer> {
|
public class SkinServerSerializer implements JsonSerializer<SkinServer>, JsonDeserializer<SkinServer> {
|
||||||
|
|
||||||
|
public static final SkinServerSerializer instance = new SkinServerSerializer();
|
||||||
|
|
||||||
|
private final BiMap<String, Class<? extends SkinServer>> types = HashBiMap.create(2);
|
||||||
|
|
||||||
|
public SkinServerSerializer() {
|
||||||
|
// register default skin server types
|
||||||
|
addSkinServerType(ValhallaSkinServer.class);
|
||||||
|
addSkinServerType(YggdrasilSkinServer.class);
|
||||||
|
addSkinServerType(LegacySkinServer.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSkinServerType(Class<? extends SkinServer> type) {
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
types.put(st.value(), type);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(SkinServer src, Type typeOfSrc, JsonSerializationContext context) {
|
public JsonElement serialize(SkinServer src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
ServerType serverType = src.getClass().getAnnotation(ServerType.class);
|
ServerType serverType = src.getClass().getAnnotation(ServerType.class);
|
||||||
|
@ -32,6 +61,6 @@ public class SkinServerSerializer implements JsonSerializer<SkinServer>, JsonDes
|
||||||
public SkinServer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
public SkinServer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
String type = json.getAsJsonObject().get("type").getAsString();
|
String type = json.getAsJsonObject().get("type").getAsString();
|
||||||
|
|
||||||
return context.deserialize(json, HDSkinManager.INSTANCE.getSkinServerClass(type));
|
return context.deserialize(json, types.get(type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.voxelmodpack.hdskins.server;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
||||||
|
|
||||||
|
public class TexturePayload {
|
||||||
|
|
||||||
|
private long timestamp;
|
||||||
|
|
||||||
|
private UUID profileId;
|
||||||
|
|
||||||
|
private String profileName;
|
||||||
|
|
||||||
|
private boolean isPublic;
|
||||||
|
|
||||||
|
private Map<String, MinecraftProfileTexture> textures;
|
||||||
|
|
||||||
|
TexturePayload() { }
|
||||||
|
|
||||||
|
public TexturePayload(GameProfile profile, Map<String, MinecraftProfileTexture> textures) {
|
||||||
|
profileId = profile.getId();
|
||||||
|
profileName = profile.getName();
|
||||||
|
timestamp = System.currentTimeMillis();
|
||||||
|
|
||||||
|
isPublic = true;
|
||||||
|
|
||||||
|
this.textures = new HashMap<>(textures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID getProfileId() {
|
||||||
|
return profileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProfileName() {
|
||||||
|
return profileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPublic() {
|
||||||
|
return isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, MinecraftProfileTexture> getTextures() {
|
||||||
|
return textures;
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ import java.util.UUID;
|
||||||
|
|
||||||
@ServerType("valhalla")
|
@ServerType("valhalla")
|
||||||
public class ValhallaSkinServer implements SkinServer {
|
public class ValhallaSkinServer implements SkinServer {
|
||||||
|
private static final String API_PREFIX = "/api/v1";
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private final String address;
|
private final String address;
|
||||||
|
@ -38,8 +39,12 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
this.address = address;
|
this.address = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getApiPrefix() {
|
||||||
|
return address + API_PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException {
|
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException {
|
||||||
try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, new HttpGet(getTexturesURI(profile)))) {
|
try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, new HttpGet(getTexturesURI(profile)))) {
|
||||||
|
|
||||||
if (response.ok()) {
|
if (response.ok()) {
|
||||||
|
@ -51,43 +56,46 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SkinUploadResponse performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
|
public void performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
|
||||||
try {
|
try {
|
||||||
return uploadPlayerSkin(upload);
|
uploadPlayerSkin(upload);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
if (e.getMessage().equals("Authorization failed")) {
|
if (e.getMessage().equals("Authorization failed")) {
|
||||||
accessToken = null;
|
accessToken = null;
|
||||||
return uploadPlayerSkin(upload);
|
uploadPlayerSkin(upload);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse uploadPlayerSkin(SkinUpload upload) throws IOException, AuthenticationException {
|
private void uploadPlayerSkin(SkinUpload upload) throws IOException, AuthenticationException {
|
||||||
authorize(upload.getSession());
|
authorize(upload.getSession());
|
||||||
|
|
||||||
switch (upload.getSchemaAction()) {
|
switch (upload.getSchemaAction()) {
|
||||||
case "none":
|
case "none":
|
||||||
return resetSkin(upload);
|
resetSkin(upload);
|
||||||
|
break;
|
||||||
case "file":
|
case "file":
|
||||||
return uploadFile(upload);
|
uploadFile(upload);
|
||||||
|
break;
|
||||||
case "http":
|
case "http":
|
||||||
case "https":
|
case "https":
|
||||||
return uploadUrl(upload);
|
uploadUrl(upload);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new IOException("Unsupported URI scheme: " + upload.getSchemaAction());
|
throw new IOException("Unsupported URI scheme: " + upload.getSchemaAction());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse resetSkin(SkinUpload upload) throws IOException {
|
private void resetSkin(SkinUpload upload) throws IOException {
|
||||||
return upload(RequestBuilder.delete()
|
upload(RequestBuilder.delete()
|
||||||
.setUri(buildUserTextureUri(upload.getSession().getProfile(), upload.getType()))
|
.setUri(buildUserTextureUri(upload.getSession().getProfile(), upload.getType()))
|
||||||
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse uploadFile(SkinUpload upload) throws IOException {
|
private void uploadFile(SkinUpload upload) throws IOException {
|
||||||
final File file = new File(upload.getImage());
|
final File file = new File(upload.getImage());
|
||||||
|
|
||||||
MultipartEntityBuilder b = MultipartEntityBuilder.create()
|
MultipartEntityBuilder b = MultipartEntityBuilder.create()
|
||||||
|
@ -95,15 +103,15 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
|
|
||||||
upload.getMetadata().forEach(b::addTextBody);
|
upload.getMetadata().forEach(b::addTextBody);
|
||||||
|
|
||||||
return upload(RequestBuilder.put()
|
upload(RequestBuilder.put()
|
||||||
.setUri(buildUserTextureUri(upload.getSession().getProfile(), upload.getType()))
|
.setUri(buildUserTextureUri(upload.getSession().getProfile(), upload.getType()))
|
||||||
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
||||||
.setEntity(b.build())
|
.setEntity(b.build())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse uploadUrl(SkinUpload upload) throws IOException {
|
private void uploadUrl(SkinUpload upload) throws IOException {
|
||||||
return upload(RequestBuilder.post()
|
upload(RequestBuilder.post()
|
||||||
.setUri(buildUserTextureUri(upload.getSession().getProfile(), upload.getType()))
|
.setUri(buildUserTextureUri(upload.getSession().getProfile(), upload.getType()))
|
||||||
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
.addHeader(HttpHeaders.AUTHORIZATION, this.accessToken)
|
||||||
.addParameter("file", upload.getImage().toString())
|
.addParameter("file", upload.getImage().toString())
|
||||||
|
@ -111,9 +119,11 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private SkinUploadResponse upload(HttpUriRequest request) throws IOException {
|
private void upload(HttpUriRequest request) throws IOException {
|
||||||
try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, request)) {
|
try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, request)) {
|
||||||
return response.unwrapAsJson(SkinUploadResponse.class);
|
if (!response.ok()) {
|
||||||
|
throw response.exception();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,7 +132,6 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
GameProfile profile = session.getProfile();
|
GameProfile profile = session.getProfile();
|
||||||
String token = session.getToken();
|
|
||||||
AuthHandshake handshake = authHandshake(profile.getName());
|
AuthHandshake handshake = authHandshake(profile.getName());
|
||||||
|
|
||||||
if (handshake.offline) {
|
if (handshake.offline) {
|
||||||
|
@ -130,7 +139,7 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// join the session server
|
// join the session server
|
||||||
Minecraft.getMinecraft().getSessionService().joinServer(profile, token, handshake.serverId);
|
Minecraft.getMinecraft().getSessionService().joinServer(profile, session.getToken(), handshake.serverId);
|
||||||
|
|
||||||
AuthResponse response = authResponse(profile.getName(), handshake.verifyToken);
|
AuthResponse response = authResponse(profile.getName(), handshake.verifyToken);
|
||||||
if (!response.userId.equals(profile.getId())) {
|
if (!response.userId.equals(profile.getId())) {
|
||||||
|
@ -161,20 +170,20 @@ public class ValhallaSkinServer implements SkinServer {
|
||||||
private URI buildUserTextureUri(GameProfile profile, MinecraftProfileTexture.Type textureType) {
|
private URI buildUserTextureUri(GameProfile profile, MinecraftProfileTexture.Type textureType) {
|
||||||
String user = UUIDTypeAdapter.fromUUID(profile.getId());
|
String user = UUIDTypeAdapter.fromUUID(profile.getId());
|
||||||
String skinType = textureType.name().toLowerCase(Locale.US);
|
String skinType = textureType.name().toLowerCase(Locale.US);
|
||||||
return URI.create(String.format("%s/user/%s/%s", this.address, user, skinType));
|
return URI.create(String.format("%s/user/%s/%s", this.getApiPrefix(), user, skinType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private URI getTexturesURI(GameProfile profile) {
|
private URI getTexturesURI(GameProfile profile) {
|
||||||
Preconditions.checkNotNull(profile.getId(), "profile id required for skins");
|
Preconditions.checkNotNull(profile.getId(), "profile id required for skins");
|
||||||
return URI.create(String.format("%s/user/%s", this.address, UUIDTypeAdapter.fromUUID(profile.getId())));
|
return URI.create(String.format("%s/user/%s", this.getApiPrefix(), UUIDTypeAdapter.fromUUID(profile.getId())));
|
||||||
}
|
}
|
||||||
|
|
||||||
private URI getHandshakeURI() {
|
private URI getHandshakeURI() {
|
||||||
return URI.create(String.format("%s/auth/handshake", this.address));
|
return URI.create(String.format("%s/auth/handshake", this.getApiPrefix()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private URI getResponseURI() {
|
private URI getResponseURI() {
|
||||||
return URI.create(String.format("%s/auth/response", this.address));
|
return URI.create(String.format("%s/auth/response", this.getApiPrefix()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
package com.voxelmodpack.hdskins.server;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.mojang.authlib.GameProfile;
|
||||||
|
import com.mojang.authlib.exceptions.AuthenticationException;
|
||||||
|
import com.mojang.authlib.minecraft.*;
|
||||||
|
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
|
||||||
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
|
import com.voxelmodpack.hdskins.gui.Feature;
|
||||||
|
import com.voxelmodpack.hdskins.util.*;
|
||||||
|
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.util.Session;
|
||||||
|
import org.apache.http.client.methods.RequestBuilder;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||||
|
|
||||||
|
@ServerType("mojang")
|
||||||
|
public class YggdrasilSkinServer implements SkinServer {
|
||||||
|
|
||||||
|
static final SkinServer INSTANCE = new YggdrasilSkinServer();
|
||||||
|
|
||||||
|
private static final Set<Feature> FEATURES = Sets.newHashSet(
|
||||||
|
Feature.SYNTHETIC,
|
||||||
|
Feature.UPLOAD_USER_SKIN,
|
||||||
|
Feature.DOWNLOAD_USER_SKIN,
|
||||||
|
Feature.DELETE_USER_SKIN,
|
||||||
|
Feature.MODEL_VARIANTS,
|
||||||
|
Feature.MODEL_TYPES);
|
||||||
|
|
||||||
|
private transient final String address = "https://api.mojang.com";
|
||||||
|
private transient final String verify = "https://authserver.mojang.com/validate";
|
||||||
|
|
||||||
|
private transient final boolean requireSecure = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsFeature(Feature feature) {
|
||||||
|
return FEATURES.contains(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MinecraftTexturesPayload loadProfileData(GameProfile profile) throws IOException, AuthenticationException {
|
||||||
|
|
||||||
|
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures = new HashMap<>();
|
||||||
|
|
||||||
|
Minecraft client = Minecraft.getMinecraft();
|
||||||
|
MinecraftSessionService session = client.getSessionService();
|
||||||
|
|
||||||
|
profile.getProperties().clear();
|
||||||
|
GameProfile newProfile = session.fillProfileProperties(profile, requireSecure);
|
||||||
|
|
||||||
|
if (newProfile == profile) {
|
||||||
|
throw new AuthenticationException("Mojang API error occured. You may be throttled.");
|
||||||
|
}
|
||||||
|
profile = newProfile;
|
||||||
|
|
||||||
|
try {
|
||||||
|
textures.putAll(session.getTextures(profile, requireSecure));
|
||||||
|
} catch (InsecureTextureException e) {
|
||||||
|
HDSkinManager.logger.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TexturesPayloadBuilder.createTexturesPayload(profile, textures);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performSkinUpload(SkinUpload upload) throws IOException, AuthenticationException {
|
||||||
|
authorize(upload.getSession());
|
||||||
|
|
||||||
|
switch (upload.getSchemaAction()) {
|
||||||
|
case "none":
|
||||||
|
send(appendHeaders(upload, RequestBuilder.delete()));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
send(prepareUpload(upload, RequestBuilder.put()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Minecraft client = Minecraft.getMinecraft();
|
||||||
|
client.getProfileProperties().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestBuilder prepareUpload(SkinUpload upload, RequestBuilder request) throws IOException {
|
||||||
|
request = appendHeaders(upload, request);
|
||||||
|
switch (upload.getSchemaAction()) {
|
||||||
|
case "file":
|
||||||
|
final File file = new File(upload.getImage());
|
||||||
|
|
||||||
|
MultipartEntityBuilder b = MultipartEntityBuilder.create()
|
||||||
|
.addBinaryBody("file", file, ContentType.create("image/png"), file.getName());
|
||||||
|
|
||||||
|
mapMetadata(upload.getMetadata()).forEach(b::addTextBody);
|
||||||
|
|
||||||
|
return request.setEntity(b.build());
|
||||||
|
case "http":
|
||||||
|
case "https":
|
||||||
|
return request
|
||||||
|
.addParameter("file", upload.getImage().toString())
|
||||||
|
.addParameters(MoreHttpResponses.mapAsParameters(mapMetadata(upload.getMetadata())));
|
||||||
|
default:
|
||||||
|
throw new IOException("Unsupported URI scheme: " + upload.getSchemaAction());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequestBuilder appendHeaders(SkinUpload upload, RequestBuilder request) {
|
||||||
|
return request
|
||||||
|
.setUri(URI.create(String.format("%s/user/profile/%s/%s", address,
|
||||||
|
UUIDTypeAdapter.fromUUID(upload.getSession().getProfile().getId()),
|
||||||
|
upload.getType())))
|
||||||
|
.addHeader("authorization", "Bearer " + upload.getSession().getToken());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, String> mapMetadata(Map<String, String> metadata) {
|
||||||
|
return metadata.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
|
||||||
|
entry -> {
|
||||||
|
String value = entry.getValue();
|
||||||
|
if ("model".contentEquals(entry.getKey()) && "default".contentEquals(value)) {
|
||||||
|
return "classic";
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void authorize(Session session) throws IOException {
|
||||||
|
RequestBuilder request = RequestBuilder.post().setUri(verify);
|
||||||
|
request.setEntity(new TokenRequest(session).toEntity());
|
||||||
|
|
||||||
|
send(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(RequestBuilder request) throws IOException {
|
||||||
|
try (MoreHttpResponses response = MoreHttpResponses.execute(HDSkinManager.httpClient, request.build())) {
|
||||||
|
if (!response.ok()) {
|
||||||
|
throw new IOException(response.json(ErrorResponse.class, "Server error wasn't in json: {}").toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new IndentedToStringStyle.Builder(this)
|
||||||
|
.append("address", address)
|
||||||
|
.append("secured", requireSecure)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class TokenRequest {
|
||||||
|
static final Gson GSON = new Gson();
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
private final String accessToken;
|
||||||
|
|
||||||
|
TokenRequest(Session session) {
|
||||||
|
accessToken = session.getToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringEntity toEntity() throws IOException {
|
||||||
|
return new StringEntity(GSON.toJson(this), ContentType.APPLICATION_JSON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ErrorResponse {
|
||||||
|
String error;
|
||||||
|
String errorMessage;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("%s: %s", error, errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,9 @@ package com.voxelmodpack.hdskins.util;
|
||||||
|
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
import com.google.common.io.CharStreams;
|
import com.google.common.io.CharStreams;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.*;
|
||||||
|
import com.mojang.util.UUIDTypeAdapter;
|
||||||
|
import com.voxelmodpack.hdskins.HDSkinManager;
|
||||||
import com.voxelmodpack.hdskins.server.SkinServer;
|
import com.voxelmodpack.hdskins.server.SkinServer;
|
||||||
|
|
||||||
import org.apache.http.Header;
|
import org.apache.http.Header;
|
||||||
|
@ -11,6 +13,7 @@ import org.apache.http.HttpStatus;
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
import org.apache.http.client.methods.HttpUriRequest;
|
import org.apache.http.client.methods.HttpUriRequest;
|
||||||
|
import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
|
|
||||||
|
@ -20,8 +23,7 @@ import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +31,9 @@ import java.util.stream.Stream;
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface MoreHttpResponses extends AutoCloseable {
|
public interface MoreHttpResponses extends AutoCloseable {
|
||||||
|
Gson GSON = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
|
||||||
|
.create();
|
||||||
|
|
||||||
CloseableHttpResponse getResponse();
|
CloseableHttpResponse getResponse();
|
||||||
|
|
||||||
|
@ -36,6 +41,10 @@ public interface MoreHttpResponses extends AutoCloseable {
|
||||||
return getResponseCode() == HttpStatus.SC_OK;
|
return getResponseCode() == HttpStatus.SC_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default boolean json() {
|
||||||
|
return "application/json".contentEquals(contentType().getMimeType());
|
||||||
|
}
|
||||||
|
|
||||||
default int getResponseCode() {
|
default int getResponseCode() {
|
||||||
return getResponse().getStatusLine().getStatusCode();
|
return getResponse().getStatusLine().getStatusCode();
|
||||||
}
|
}
|
||||||
|
@ -44,6 +53,12 @@ public interface MoreHttpResponses extends AutoCloseable {
|
||||||
return Optional.ofNullable(getResponse().getEntity());
|
return Optional.ofNullable(getResponse().getEntity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default ContentType contentType() {
|
||||||
|
return getEntity()
|
||||||
|
.map(ContentType::get)
|
||||||
|
.orElse(ContentType.DEFAULT_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
default String getContentType() {
|
default String getContentType() {
|
||||||
return getEntity().map(HttpEntity::getContentType).map(Header::getValue).orElse("text/plain");
|
return getEntity().map(HttpEntity::getContentType).map(Header::getValue).orElse("text/plain");
|
||||||
}
|
}
|
||||||
|
@ -86,6 +101,22 @@ public interface MoreHttpResponses extends AutoCloseable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default <T> T json(Class<T> type, String errorMessage) throws IOException {
|
||||||
|
return json((Type)type, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
default <T> T json(Type type, String errorMessage) throws IOException {
|
||||||
|
if (!json()) {
|
||||||
|
String text = text();
|
||||||
|
HDSkinManager.logger.error(errorMessage, text);
|
||||||
|
throw new IOException(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (BufferedReader reader = getReader()) {
|
||||||
|
return GSON.fromJson(reader, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
default <T> T unwrapAsJson(Type type) throws IOException {
|
default <T> T unwrapAsJson(Type type) throws IOException {
|
||||||
if (!"application/json".equals(getContentType())) {
|
if (!"application/json".equals(getContentType())) {
|
||||||
throw new IOException("Server returned a non-json response!");
|
throw new IOException("Server returned a non-json response!");
|
||||||
|
@ -95,7 +126,11 @@ public interface MoreHttpResponses extends AutoCloseable {
|
||||||
return json(type);
|
return json(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IOException(json(JsonObject.class).get("message").getAsString());
|
throw exception();
|
||||||
|
}
|
||||||
|
|
||||||
|
default IOException exception() throws IOException {
|
||||||
|
return new IOException(json(JsonObject.class, "Server error wasn't in json: {}").get("message").getAsString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
11
src/hdskins/resources/assets/hdskins/skins/servers.json
Normal file
11
src/hdskins/resources/assets/hdskins/skins/servers.json
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"overwrite": false,
|
||||||
|
"insert": "END",
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"type": "valhalla",
|
||||||
|
"address": "https://skins.minelittlepony-mod.com"
|
||||||
|
},
|
||||||
|
{ "type": "mojang" }
|
||||||
|
]
|
||||||
|
}
|
|
@ -36,10 +36,6 @@ 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_VALHALLA_SERVER = "http://skins.minelittlepony-mod.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");
|
||||||
|
|
||||||
private static MineLittlePony instance;
|
private static MineLittlePony instance;
|
||||||
|
@ -69,10 +65,6 @@ public class MineLittlePony {
|
||||||
|
|
||||||
MetadataSerializer ms = Minecraft.getMinecraft().getResourcePackRepository().rprMetadataSerializer;
|
MetadataSerializer ms = Minecraft.getMinecraft().getResourcePackRepository().rprMetadataSerializer;
|
||||||
ms.registerMetadataSectionType(new PonyDataSerialiser(), IPonyData.class);
|
ms.registerMetadataSectionType(new PonyDataSerialiser(), IPonyData.class);
|
||||||
|
|
||||||
// This also makes it the default gateway server.
|
|
||||||
SkinServer.defaultServers.add(new LegacySkinServer(MINELP_LEGACY_SERVER, MINELP_LEGACY_GATEWAY));
|
|
||||||
SkinServer.defaultServers.add(0, new ValhallaSkinServer(MINELP_VALHALLA_SERVER));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue