In vielen Packets, die z.B. den Spieler spawnen oder ihn zu Tab-Liste hinzufügen wird ein GameProfile gesendet.
Ab Minecraft 1.8 steht in diesem Profil ein Link zu Textur drinnen, in Base64 kodiert.
Das sieht dann z.B. so aus:
- Code: Alles auswählen
- eyJ0aW1lc3RhbXAiOjE0MTYxNjc5NDExMzQsInByb2ZpbGVJZCI6IjA2OWE3OWY0NDRlOTQ3MjZhNWJlZmNhOTBlMzhhYWY1IiwicHJvZmlsZU5hbWUiOiJOb3RjaCIsImlzUHVibGljIjp0cnVlLCJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTExNmU2OWE4NDVlMjI3ZjdjYTFmZGRlOGMzNTdjOGM4MjFlYmQ0YmE2MTkzODJlYTRhMWY4N2Q0YWU5NCJ9LCJDQVBFIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2Y2ODhlMGU2OTliM2Q5ZmU0NDhiNWJiNTBhM2EyODhmOWM1ODk3NjJiM2RhZTgzMDg4NDIxMjJkY2I4MSJ9fX0=
Decodiert man diesen Code (z.B. auf base64decode.org), erhält man das:
- Code: Alles auswählen
- {
- "timestamp": 1416167941134,
- "profileId": "069a79f444e94726a5befca90e38aaf5",
- "profileName": "Notch",
- "isPublic": true,
- "textures": {
- "SKIN": {
- "url": "http://textures.minecraft.net/texture/a116e69a845e227f7ca1fdde8c357c8c821ebd4ba619382ea4a1f87d4ae94"
- },
- "CAPE": {
- "url": "http://textures.minecraft.net/texture/3f688e0e699b3d9fe448b5bb50a3a288f9c589762b3dae8308842122dcb81"
- }
- }
- }
Die Daten eines kompletten GameProfiles bekommt man über
- Code: Alles auswählen
- https://sessionserver.mojang.com/session/minecraft/profile/<uuid ohne '-'>
Ich habe hier eine Klasse geschrieben, die diese Daten direkt in ein GameProfile serialisiert:
- Code: Alles auswählen
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.lang.reflect.Type;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map.Entry;
- import java.util.UUID;
- import org.bukkit.craftbukkit.libs.com.google.gson.Gson;
- import org.bukkit.craftbukkit.libs.com.google.gson.GsonBuilder;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonDeserializationContext;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonDeserializer;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonElement;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonObject;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonParseException;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonParser;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonSerializationContext;
- import org.bukkit.craftbukkit.libs.com.google.gson.JsonSerializer;
- import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
- import com.mojang.authlib.GameProfile;
- import com.mojang.authlib.properties.Property;
- import com.mojang.authlib.properties.PropertyMap;
- import com.mojang.util.UUIDTypeAdapter;
- public class GameProfileBuilder {
- private static final String SERVICE_URL = "https://sessionserver.mojang.com/session/minecraft/profile/%s?unsigned=false";
- private static final String JSON_SKIN = "{\"timestamp\":%d,\"profileId\":\"%s\",\"profileName\":\"%s\",\"isPublic\":true,\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}";
- private static final String JSON_CAPE = "{\"timestamp\":%d,\"profileId\":\"%s\",\"profileName\":\"%s\",\"isPublic\":true,\"textures\":{\"SKIN\":{\"url\":\"%s\"},\"CAPE\":{\"url\":\"%s\"}}}";
- private static Gson gson = new GsonBuilder().disableHtmlEscaping().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).registerTypeAdapter(GameProfile.class, new GameProfileSerializer()).registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer()).create();
- private static HashMap<UUID, CachedProfile> cache = new HashMap<UUID, CachedProfile>();
- private static long cacheTime = -1;
- /**
- * Don't run in main thread!
- *
- * Fetches the GameProfile from the Mojang servers
- *
- * @param uuid The player uuid
- * @return The GameProfile
- * @throws IOException If something wents wrong while fetching
- * @see GameProfile
- */
- public static GameProfile fetch(UUID uuid) throws IOException {
- return fetch(uuid, false);
- }
- /**
- * Don't run in main thread!
- *
- * Fetches the GameProfile from the Mojang servers
- * @param uuid The player uuid
- * @param forceNew If true the cache is ignored
- * @return The GameProfile
- * @throws IOException If something wents wrong while fetching
- * @see GameProfile
- */
- public static GameProfile fetch(UUID uuid, boolean forceNew) throws IOException {
- if (!forceNew && cache.containsKey(uuid) && cache.get(uuid).isValid()) {
- return cache.get(uuid).profile;
- } else {
- HttpURLConnection connection = (HttpURLConnection) new URL(String.format(SERVICE_URL, UUIDTypeAdapter.fromUUID(uuid))).openConnection();
- connection.setReadTimeout(5000);
- if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
- String json = new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine();
- GameProfile result = gson.fromJson(json, GameProfile.class);
- cache.put(uuid, new CachedProfile(result));
- return result;
- } else {
- if (!forceNew && cache.containsKey(uuid)) {
- return cache.get(uuid).profile;
- }
- JsonObject error = (JsonObject) new JsonParser().parse(new BufferedReader(new InputStreamReader(connection.getErrorStream())).readLine());
- throw new IOException(error.get("error").getAsString() + ": " + error.get("errorMessage").getAsString());
- }
- }
- }
- /**
- * Builds a GameProfile for the specified args
- *
- * @param uuid The uuid
- * @param name The name
- * @param skin The url from the skin image
- * @return A GameProfile built from the arguments
- * @see GameProfile
- */
- public static GameProfile getProfile(UUID uuid, String name, String skin) {
- return getProfile(uuid, name, skin, null);
- }
- /**
- * Builds a GameProfile for the specified args
- *
- * @param uuid The uuid
- * @param name The name
- * @param skinUrl Url from the skin image
- * @param capeUrl Url from the cape image
- * @return A GameProfile built from the arguments
- * @see GameProfile
- */
- public static GameProfile getProfile(UUID uuid, String name, String skinUrl, String capeUrl) {
- GameProfile profile = new GameProfile(uuid, name);
- boolean cape = capeUrl != null && !capeUrl.isEmpty();
- List<Object> args = new ArrayList<Object>();
- args.add(System.currentTimeMillis());
- args.add(UUIDTypeAdapter.fromUUID(uuid));
- args.add(name);
- args.add(skinUrl);
- if (cape) args.add(capeUrl);
- profile.getProperties().put("textures", new Property("textures", Base64Coder.encodeString(String.format(cape ? JSON_CAPE : JSON_SKIN, args.toArray(new Object[args.size()])))));
- return profile;
- }
- /**
- * Sets the time as long as you want to keep the gameprofiles in cache (-1 = never remove it)
- * @param time cache time (default = -1)
- */
- public static void setCacheTime(long time) {
- cacheTime = time;
- }
- private static class GameProfileSerializer implements JsonSerializer<GameProfile>, JsonDeserializer<GameProfile> {
- public GameProfile deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException {
- JsonObject object = (JsonObject) json;
- UUID id = object.has("id") ? (UUID) context.deserialize(object.get("id"), UUID.class) : null;
- String name = object.has("name") ? object.getAsJsonPrimitive("name").getAsString() : null;
- GameProfile profile = new GameProfile(id, name);
- if (object.has("properties")) {
- for (Entry<String, Property> prop : ((PropertyMap) context.deserialize(object.get("properties"), PropertyMap.class)).entries()) {
- profile.getProperties().put(prop.getKey(), prop.getValue());
- }
- }
- return profile;
- }
- public JsonElement serialize(GameProfile profile, Type type, JsonSerializationContext context) {
- JsonObject result = new JsonObject();
- if (profile.getId() != null)
- result.add("id", context.serialize(profile.getId()));
- if (profile.getName() != null)
- result.addProperty("name", profile.getName());
- if (!profile.getProperties().isEmpty())
- result.add("properties", context.serialize(profile.getProperties()));
- return result;
- }
- }
- private static class CachedProfile {
- private long timestamp = System.currentTimeMillis();
- private GameProfile profile;
- public CachedProfile(GameProfile profile) {
- this.profile = profile;
- }
- public boolean isValid() {
- return cacheTime < 0 ? true : (System.currentTimeMillis() - timestamp) < cacheTime;
- }
- }
- }
- fetch(UUID uuid, boolean forceNew) - muss Asynchron aufgerufen werden, gibt ein Komplettes GameProfile für die angegebene UUID zurück (falls forceNew immer neu, sonst ausm Cache), wirft Exception bei Fehlschlagen (TooManyRequests, etc)
- getProfile(UUID uuid, String name, String skinUrl, String capeUrl) - gibt ein GameProfile für die angegebenen Daten zurück (cape ist Optional)
- setCacheTime(long time) - setzt die Zeit in Millisekunden, wie lange die GameProfile gecacht werden. Standartmässig werden sie nie aus dem cache entfernt (time = -1)
Achtung: Capes auf Servern anzubieten ist illegal (siehe EULA). (Ob es legal ist, einem Spieler, der sich als ein anderer Verkleidet auch dessen Cape zu geben, weiss ich nicht)