175 lines
8.8 KiB
Java
175 lines
8.8 KiB
Java
/*
|
|
* Decompiled with CFR 0.152.
|
|
*
|
|
* Could not load the following classes:
|
|
* com.google.common.cache.CacheBuilder
|
|
* com.google.common.cache.CacheLoader
|
|
* com.google.common.cache.LoadingCache
|
|
* com.google.common.hash.Hashing
|
|
* com.mojang.authlib.GameProfile
|
|
* com.mojang.authlib.SignatureState
|
|
* com.mojang.authlib.minecraft.MinecraftProfileTexture
|
|
* com.mojang.authlib.minecraft.MinecraftProfileTexture$Type
|
|
* com.mojang.authlib.minecraft.MinecraftProfileTextures
|
|
* com.mojang.authlib.properties.Property
|
|
* com.mojang.logging.LogUtils
|
|
* it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
|
* org.jspecify.annotations.Nullable
|
|
* org.slf4j.Logger
|
|
*/
|
|
package net.minecraft.client.resources;
|
|
|
|
import com.google.common.cache.CacheBuilder;
|
|
import com.google.common.cache.CacheLoader;
|
|
import com.google.common.cache.LoadingCache;
|
|
import com.google.common.hash.Hashing;
|
|
import com.mojang.authlib.GameProfile;
|
|
import com.mojang.authlib.SignatureState;
|
|
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
|
|
import com.mojang.authlib.minecraft.MinecraftProfileTextures;
|
|
import com.mojang.authlib.properties.Property;
|
|
import com.mojang.logging.LogUtils;
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
|
|
import java.nio.file.Path;
|
|
import java.time.Duration;
|
|
import java.util.Map;
|
|
import java.util.Optional;
|
|
import java.util.UUID;
|
|
import java.util.concurrent.CompletableFuture;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.function.Supplier;
|
|
import net.minecraft.SharedConstants;
|
|
import net.minecraft.client.renderer.texture.SkinTextureDownloader;
|
|
import net.minecraft.client.resources.DefaultPlayerSkin;
|
|
import net.minecraft.core.ClientAsset;
|
|
import net.minecraft.resources.Identifier;
|
|
import net.minecraft.server.Services;
|
|
import net.minecraft.util.Util;
|
|
import net.minecraft.world.entity.player.PlayerModelType;
|
|
import net.minecraft.world.entity.player.PlayerSkin;
|
|
import org.jspecify.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
public class SkinManager {
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private final Services services;
|
|
private final SkinTextureDownloader skinTextureDownloader;
|
|
private final LoadingCache<CacheKey, CompletableFuture<Optional<PlayerSkin>>> skinCache;
|
|
private final TextureCache skinTextures;
|
|
private final TextureCache capeTextures;
|
|
private final TextureCache elytraTextures;
|
|
|
|
public SkinManager(Path skinsDirectory, final Services services, SkinTextureDownloader skinTextureDownloader, final Executor mainThreadExecutor) {
|
|
this.services = services;
|
|
this.skinTextureDownloader = skinTextureDownloader;
|
|
this.skinTextures = new TextureCache(skinsDirectory, MinecraftProfileTexture.Type.SKIN);
|
|
this.capeTextures = new TextureCache(skinsDirectory, MinecraftProfileTexture.Type.CAPE);
|
|
this.elytraTextures = new TextureCache(skinsDirectory, MinecraftProfileTexture.Type.ELYTRA);
|
|
this.skinCache = CacheBuilder.newBuilder().expireAfterAccess(Duration.ofSeconds(15L)).build((CacheLoader)new CacheLoader<CacheKey, CompletableFuture<Optional<PlayerSkin>>>(){
|
|
|
|
public CompletableFuture<Optional<PlayerSkin>> load(CacheKey key) {
|
|
return ((CompletableFuture)CompletableFuture.supplyAsync(() -> {
|
|
Property packedTextures = key.packedTextures();
|
|
if (packedTextures == null) {
|
|
return MinecraftProfileTextures.EMPTY;
|
|
}
|
|
MinecraftProfileTextures textures = services.sessionService().unpackTextures(packedTextures);
|
|
if (textures.signatureState() == SignatureState.INVALID) {
|
|
LOGGER.warn("Profile contained invalid signature for textures property (profile id: {})", (Object)key.profileId());
|
|
}
|
|
return textures;
|
|
}, Util.backgroundExecutor().forName("unpackSkinTextures")).thenComposeAsync(textures -> SkinManager.this.registerTextures(key.profileId(), (MinecraftProfileTextures)textures), mainThreadExecutor)).handle((playerSkin, throwable) -> {
|
|
if (throwable != null) {
|
|
LOGGER.warn("Failed to load texture for profile {}", (Object)key.profileId, throwable);
|
|
}
|
|
return Optional.ofNullable(playerSkin);
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
public Supplier<PlayerSkin> createLookup(GameProfile profile, boolean requireSecure) {
|
|
CompletableFuture<Optional<PlayerSkin>> future = this.get(profile);
|
|
PlayerSkin defaultSkin = DefaultPlayerSkin.get(profile);
|
|
if (SharedConstants.DEBUG_DEFAULT_SKIN_OVERRIDE) {
|
|
return () -> defaultSkin;
|
|
}
|
|
Optional currentValue = future.getNow(null);
|
|
if (currentValue != null) {
|
|
PlayerSkin playerSkin = currentValue.filter(skin -> !requireSecure || skin.secure()).orElse(defaultSkin);
|
|
return () -> playerSkin;
|
|
}
|
|
return () -> future.getNow(Optional.empty()).filter(skin -> !requireSecure || skin.secure()).orElse(defaultSkin);
|
|
}
|
|
|
|
public CompletableFuture<Optional<PlayerSkin>> get(GameProfile profile) {
|
|
if (SharedConstants.DEBUG_DEFAULT_SKIN_OVERRIDE) {
|
|
PlayerSkin defaultSkin = DefaultPlayerSkin.get(profile);
|
|
return CompletableFuture.completedFuture(Optional.of(defaultSkin));
|
|
}
|
|
Property packedTextures = this.services.sessionService().getPackedTextures(profile);
|
|
return (CompletableFuture)this.skinCache.getUnchecked((Object)new CacheKey(profile.id(), packedTextures));
|
|
}
|
|
|
|
private CompletableFuture<PlayerSkin> registerTextures(UUID profileId, MinecraftProfileTextures textures) {
|
|
PlayerModelType model;
|
|
CompletableFuture<ClientAsset.Texture> skinTexture;
|
|
MinecraftProfileTexture skinInfo = textures.skin();
|
|
if (skinInfo != null) {
|
|
skinTexture = this.skinTextures.getOrLoad(skinInfo);
|
|
model = PlayerModelType.byLegacyServicesName(skinInfo.getMetadata("model"));
|
|
} else {
|
|
PlayerSkin defaultSkin = DefaultPlayerSkin.get(profileId);
|
|
skinTexture = CompletableFuture.completedFuture(defaultSkin.body());
|
|
model = defaultSkin.model();
|
|
}
|
|
MinecraftProfileTexture capeInfo = textures.cape();
|
|
CompletableFuture<Object> capeTexture = capeInfo != null ? this.capeTextures.getOrLoad(capeInfo) : CompletableFuture.completedFuture(null);
|
|
MinecraftProfileTexture elytraInfo = textures.elytra();
|
|
CompletableFuture<Object> elytraTexture = elytraInfo != null ? this.elytraTextures.getOrLoad(elytraInfo) : CompletableFuture.completedFuture(null);
|
|
return CompletableFuture.allOf(skinTexture, capeTexture, elytraTexture).thenApply(unused -> new PlayerSkin((ClientAsset.Texture)skinTexture.join(), (ClientAsset.Texture)capeTexture.join(), (ClientAsset.Texture)elytraTexture.join(), model, textures.signatureState() == SignatureState.SIGNED));
|
|
}
|
|
|
|
private class TextureCache {
|
|
private final Path root;
|
|
private final MinecraftProfileTexture.Type type;
|
|
private final Map<String, CompletableFuture<ClientAsset.Texture>> textures = new Object2ObjectOpenHashMap();
|
|
|
|
private TextureCache(Path root, MinecraftProfileTexture.Type type) {
|
|
this.root = root;
|
|
this.type = type;
|
|
}
|
|
|
|
public CompletableFuture<ClientAsset.Texture> getOrLoad(MinecraftProfileTexture texture) {
|
|
String hash = texture.getHash();
|
|
CompletableFuture<ClientAsset.Texture> future = this.textures.get(hash);
|
|
if (future == null) {
|
|
future = this.registerTexture(texture);
|
|
this.textures.put(hash, future);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
private CompletableFuture<ClientAsset.Texture> registerTexture(MinecraftProfileTexture textureInfo) {
|
|
String hash = Hashing.sha1().hashUnencodedChars((CharSequence)textureInfo.getHash()).toString();
|
|
Identifier textureId = this.getTextureLocation(hash);
|
|
Path file = this.root.resolve(hash.length() > 2 ? hash.substring(0, 2) : "xx").resolve(hash);
|
|
return SkinManager.this.skinTextureDownloader.downloadAndRegisterSkin(textureId, file, textureInfo.getUrl(), this.type == MinecraftProfileTexture.Type.SKIN);
|
|
}
|
|
|
|
private Identifier getTextureLocation(String textureHash) {
|
|
String root = switch (this.type) {
|
|
default -> throw new MatchException(null, null);
|
|
case MinecraftProfileTexture.Type.SKIN -> "skins";
|
|
case MinecraftProfileTexture.Type.CAPE -> "capes";
|
|
case MinecraftProfileTexture.Type.ELYTRA -> "elytra";
|
|
};
|
|
return Identifier.withDefaultNamespace(root + "/" + textureHash);
|
|
}
|
|
}
|
|
|
|
private record CacheKey(UUID profileId, @Nullable Property packedTextures) {
|
|
}
|
|
}
|
|
|