package com.mojang.authlib.yggdrasil;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.Map;
import java.util.UUID;
import com.mojang.authlib.AuthenticationService;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
import com.mojang.authlib.minecraft.BaseMinecraftSessionService;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import launcher.client.ClientLauncher;
import launcher.client.PlayerProfile;
import launcher.helper.LogHelper;
import launcher.helper.SecurityHelper;
import launcher.request.auth.CheckServerRequest;
import launcher.request.auth.JoinServerRequest;
import launcher.request.uuid.ProfileByUUIDRequest;
public final class YggdrasilMinecraftSessionService extends BaseMinecraftSessionService {
private static final boolean DISABLE_TEXTURES = Boolean.getBoolean("launcher.authlib.disableTextures");
// Constants
private static final String SKIN_HASH_PROPERTY = "l_skinHash";
private static final String CLOAK_HASH_PROPERTY = "l_cloakHash";
public YggdrasilMinecraftSessionService(AuthenticationService service) {
super(service);
LogHelper.debug("Patched MinecraftSessionService created");
}
@Override
public GameProfile fillProfileProperties(GameProfile profile, boolean requireSecure) {
// Verify has UUID
UUID uuid = profile.getUUID();
LogHelper.debug("fillProfileProperties, UUID: %s", uuid);
if (uuid == null) {
return profile;
}
// Make profile request
PlayerProfile pp;
try {
pp = new ProfileByUUIDRequest(uuid).request();
} catch (Exception e) {
LogHelper.debug("Couldn't fetch profile properties for '%s': %s", profile, e);
return profile;
}
// Verify is found
if (pp == null) {
LogHelper.debug("Couldn't fetch profile properties for '%s' as the profile does not exist", profile);
return profile;
}
// Create new game profile from player profile
LogHelper.debug("Successfully fetched profile properties for '%s'", profile);
fillTextureProperties(profile, pp);
return toGameProfile(pp);
}
@Override
public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getTextures(GameProfile profile, boolean requireSecure) {
LogHelper.debug("getTextures, Username: '%s'", profile.getName());
Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> textures = new EnumMap<>(MinecraftProfileTexture.Type.class);
// Add skin URL to textures map
Iterator<Property> skinURL = profile.getProperties().get(ClientLauncher.SKIN_URL_PROPERTY).iterator();
Iterator<Property> skinHash = profile.getProperties().get(SKIN_HASH_PROPERTY).iterator();
if (skinURL.hasNext() && skinHash.hasNext()) {
String urlValue = skinURL.next().getValue();
String hashValue = skinHash.next().getValue();
textures.put(MinecraftProfileTexture.Type.SKIN, new MinecraftProfileTexture(urlValue, hashValue));
}
// Add cloak URL to textures map
Iterator<Property> cloakURL = profile.getProperties().get(ClientLauncher.CLOAK_URL_PROPERTY).iterator();
Iterator<Property> cloakHash = profile.getProperties().get(CLOAK_HASH_PROPERTY).iterator();
if (cloakURL.hasNext() && cloakHash.hasNext()) {
String urlValue = cloakURL.next().getValue();
String hashValue = cloakHash.next().getValue();
textures.put(MinecraftProfileTexture.Type.CAPE, new MinecraftProfileTexture(urlValue, hashValue));
}
// Return filled textures
return textures;
}
@Override
public GameProfile hasJoinedServer(GameProfile profile, String serverID) throws AuthenticationUnavailableException {
String username = profile.getName();
LogHelper.debug("checkServer, Username: '%s', server ID: %s", username, serverID);
// Make checkServer request
PlayerProfile pp;
try {
pp = new CheckServerRequest(username, serverID).request();
} catch (Exception e) {
LogHelper.error(e);
throw new AuthenticationUnavailableException(e);
}
// Return profile if found
return pp == null ? null : toGameProfile(pp);
}
@Override
public void joinServer(GameProfile profile, String accessToken, String serverID) throws AuthenticationException {
if (!ClientLauncher.isLaunched()) {
throw new AuthenticationException("Bad Login (Cheater)");
}
// Join server
String username = profile.getName();
LogHelper.debug("joinServer, Username: '%s', access token: %s, server ID: %s", username, accessToken, serverID);
// Make joinServer request
boolean success;
try {
success = new JoinServerRequest(username, accessToken, serverID).request();
} catch (Exception e) {
throw new AuthenticationUnavailableException(e);
}
// Verify is success
if (!success) {
throw new AuthenticationException("Bad Login (Clientside)");
}
}
public static void fillTextureProperties(GameProfile profile, PlayerProfile pp) {
if (DISABLE_TEXTURES) {
return; // :(
}
// Get property map
PropertyMap properties = profile.getProperties();
// Hash skin
String skinHash = hash(pp.skinURL);
if (skinHash != null) {
properties.put(ClientLauncher.SKIN_URL_PROPERTY, new Property(ClientLauncher.SKIN_URL_PROPERTY, pp.skinURL, ""));
properties.put(SKIN_HASH_PROPERTY, new Property(SKIN_HASH_PROPERTY, skinHash, ""));
}
// Hash cloak
String cloakHash = hash(pp.cloakURL);
if (cloakHash != null) {
properties.put(ClientLauncher.CLOAK_URL_PROPERTY, new Property(ClientLauncher.CLOAK_URL_PROPERTY, pp.cloakURL, ""));
properties.put(CLOAK_HASH_PROPERTY, new Property(CLOAK_HASH_PROPERTY, cloakHash, ""));
}
}
public static GameProfile toGameProfile(PlayerProfile pp) {
GameProfile profile = new GameProfile(pp.uuid, pp.username);
fillTextureProperties(profile, pp);
return profile;
}
private static String hash(String url) {
if (url == null) {
return null;
}
// Try get digest of the texture
try {
return SecurityHelper.toHex(SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256, new URL(url)));
} catch (FileNotFoundException e) {
return null; // Just didn't has texture
} catch (IOException e) {
LogHelper.error("Can't hash texture: %s, %s", url, e.toString());
return null;
}
}
}