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.minecraft.MinecraftSessionService; import com.mojang.authlib.minecraft.MinecraftProfileTexture.Type; 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 javax.annotation.Nullable; import net.minecraft.Optionull; import net.minecraft.Util; import net.minecraft.client.renderer.texture.SkinTextureDownloader; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.slf4j.Logger; @OnlyIn(Dist.CLIENT) public class SkinManager { static final Logger LOGGER = LogUtils.getLogger(); private final MinecraftSessionService sessionService; private final LoadingCache>> skinCache; private final SkinManager.TextureCache skinTextures; private final SkinManager.TextureCache capeTextures; private final SkinManager.TextureCache elytraTextures; public SkinManager(Path p_299617_, final MinecraftSessionService p_118814_, final Executor p_299732_) { this.sessionService = p_118814_; this.skinTextures = new SkinManager.TextureCache(p_299617_, Type.SKIN); this.capeTextures = new SkinManager.TextureCache(p_299617_, Type.CAPE); this.elytraTextures = new SkinManager.TextureCache(p_299617_, Type.ELYTRA); this.skinCache = CacheBuilder.newBuilder() .expireAfterAccess(Duration.ofSeconds(15L)) .build( new CacheLoader>>() { public CompletableFuture> load(SkinManager.CacheKey p_298169_) { return CompletableFuture.supplyAsync(() -> { Property property = p_298169_.packedTextures(); if (property == null) { return MinecraftProfileTextures.EMPTY; } else { MinecraftProfileTextures minecraftprofiletextures = p_118814_.unpackTextures(property); if (minecraftprofiletextures.signatureState() == SignatureState.INVALID) { SkinManager.LOGGER .warn("Profile contained invalid signature for textures property (profile id: {})", p_298169_.profileId()); } return minecraftprofiletextures; } }, Util.backgroundExecutor().forName("unpackSkinTextures")) .thenComposeAsync(p_308313_ -> SkinManager.this.registerTextures(p_298169_.profileId(), p_308313_), p_299732_) .handle((p_374684_, p_374685_) -> { if (p_374685_ != null) { SkinManager.LOGGER.warn("Failed to load texture for profile {}", p_298169_.profileId, p_374685_); } return Optional.ofNullable(p_374684_); }); } } ); } public Supplier lookupInsecure(GameProfile p_298295_) { CompletableFuture> completablefuture = this.getOrLoad(p_298295_); PlayerSkin playerskin = DefaultPlayerSkin.get(p_298295_); return () -> completablefuture.getNow(Optional.empty()).orElse(playerskin); } public PlayerSkin getInsecureSkin(GameProfile p_298019_) { PlayerSkin playerskin = this.getOrLoad(p_298019_).getNow(Optional.empty()).orElse(null); return playerskin != null ? playerskin : DefaultPlayerSkin.get(p_298019_); } public CompletableFuture> getOrLoad(GameProfile p_298661_) { Property property = this.sessionService.getPackedTextures(p_298661_); return this.skinCache.getUnchecked(new SkinManager.CacheKey(p_298661_.getId(), property)); } CompletableFuture registerTextures(UUID p_312099_, MinecraftProfileTextures p_313047_) { MinecraftProfileTexture minecraftprofiletexture = p_313047_.skin(); CompletableFuture completablefuture; PlayerSkin.Model playerskin$model; if (minecraftprofiletexture != null) { completablefuture = this.skinTextures.getOrLoad(minecraftprofiletexture); playerskin$model = PlayerSkin.Model.byName(minecraftprofiletexture.getMetadata("model")); } else { PlayerSkin playerskin = DefaultPlayerSkin.get(p_312099_); completablefuture = CompletableFuture.completedFuture(playerskin.texture()); playerskin$model = playerskin.model(); } String s = Optionull.map(minecraftprofiletexture, MinecraftProfileTexture::getUrl); MinecraftProfileTexture minecraftprofiletexture1 = p_313047_.cape(); CompletableFuture completablefuture1 = minecraftprofiletexture1 != null ? this.capeTextures.getOrLoad(minecraftprofiletexture1) : CompletableFuture.completedFuture(null); MinecraftProfileTexture minecraftprofiletexture2 = p_313047_.elytra(); CompletableFuture completablefuture2 = minecraftprofiletexture2 != null ? this.elytraTextures.getOrLoad(minecraftprofiletexture2) : CompletableFuture.completedFuture(null); return CompletableFuture.allOf(completablefuture, completablefuture1, completablefuture2) .thenApply( p_308309_ -> new PlayerSkin( completablefuture.join(), s, completablefuture1.join(), completablefuture2.join(), playerskin$model, p_313047_.signatureState() == SignatureState.SIGNED ) ); } @OnlyIn(Dist.CLIENT) static record CacheKey(UUID profileId, @Nullable Property packedTextures) { } @OnlyIn(Dist.CLIENT) static class TextureCache { private final Path root; private final Type type; private final Map> textures = new Object2ObjectOpenHashMap<>(); TextureCache(Path p_297921_, Type p_298775_) { this.root = p_297921_; this.type = p_298775_; } public CompletableFuture getOrLoad(MinecraftProfileTexture p_300959_) { String s = p_300959_.getHash(); CompletableFuture completablefuture = this.textures.get(s); if (completablefuture == null) { completablefuture = this.registerTexture(p_300959_); this.textures.put(s, completablefuture); } return completablefuture; } private CompletableFuture registerTexture(MinecraftProfileTexture p_300607_) { String s = Hashing.sha1().hashUnencodedChars(p_300607_.getHash()).toString(); ResourceLocation resourcelocation = this.getTextureLocation(s); Path path = this.root.resolve(s.length() > 2 ? s.substring(0, 2) : "xx").resolve(s); return SkinTextureDownloader.downloadAndRegisterSkin(resourcelocation, path, p_300607_.getUrl(), this.type == Type.SKIN); } private ResourceLocation getTextureLocation(String p_297392_) { String s = switch (this.type) { case SKIN -> "skins"; case CAPE -> "capes"; case ELYTRA -> "elytra"; }; return ResourceLocation.withDefaultNamespace(s + "/" + p_297392_); } } }