Newer
Older
KeeperJerry_Launcher / LauncherAuthlib / source / yggdrasil / YggdrasilMinecraftSessionService.java
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;
		}
	}
}