From 0155262ddd081c8677654619a09e814088fdd8b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Gonz=C3=A1lez?= Date: Sun, 25 May 2025 20:29:33 +0200 Subject: [PATCH] feat(app/minecraft_skins): save current custom external skin when equipping skins --- packages/app-lib/src/api/minecraft_skins.rs | 53 +++++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/packages/app-lib/src/api/minecraft_skins.rs b/packages/app-lib/src/api/minecraft_skins.rs index 199515166..dc1a3a922 100644 --- a/packages/app-lib/src/api/minecraft_skins.rs +++ b/packages/app-lib/src/api/minecraft_skins.rs @@ -102,7 +102,7 @@ impl Skin { } } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum SkinSource { /// A default Minecraft skin, which may be assigned to players at random by default. @@ -256,7 +256,9 @@ pub async fn get_available_skins() -> crate::Result> { } /// Adds a custom skin to the app database and equips it for the currently selected -/// Minecraft profile. +/// Minecraft profile. If the currently equipped skin is custom but not managed by +/// the app (i.e., it was set externally by another launcher, the Minecraft website, +/// etc.), that skin will be added as a custom skin to the app database as well. #[tracing::instrument] pub async fn add_and_equip_custom_skin( texture_blob: Bytes, @@ -275,9 +277,11 @@ pub async fn add_and_equip_custom_skin( .await? .ok_or(ErrorKind::NoCredentialsError)?; - // We have to equip the skin first, as it's the Mojang API backend who knows - // how to compute the texture key we require, which we can then read from the - // updated player profile + save_current_custom_external_skin(&state, &selected_credentials).await?; + + // We have to equip the new skin before storing it, as it's the Mojang API backend + // who knows how to compute the texture key we require, which we can then read from + // the updated player profile mojang_api::MinecraftSkinOperation::equip( &selected_credentials, stream::iter([Ok::<_, String>(Bytes::clone(&texture_blob))]), @@ -366,6 +370,10 @@ pub async fn set_default_cape(cape: Option) -> crate::Result<()> { /// /// This function does not check that the passed skin, if custom, exists in the app database, /// giving the caller complete freedom to equip any skin at any time. +/// +/// If the currently equipped skin is a custom skin that is not managed by the app (i.e., it was +/// set externally by another launcher, the Minecraft website, etc.), that skin will be added as +/// a custom skin to the app database, in order to allow the app to manage it later. #[tracing::instrument] pub async fn equip_skin(skin: Skin) -> crate::Result<()> { let state = State::get().await?; @@ -374,6 +382,8 @@ pub async fn equip_skin(skin: Skin) -> crate::Result<()> { .await? .ok_or(ErrorKind::NoCredentialsError)?; + save_current_custom_external_skin(&state, &selected_credentials).await?; + let profile = selected_credentials.online_profile().await.ok_or_else(|| { ErrorKind::OnlineMinecraftProfileUnavailable { @@ -485,6 +495,39 @@ async fn sync_cape( Ok(()) } +/// Stores the currently equipped skin as a custom skin, if it is a custom skin that is not +/// managed by the app (i.e., it was externally set). +async fn save_current_custom_external_skin( + state: &State, + selected_credentials: &Credentials, +) -> crate::Result<()> { + if let Some(current_external_skin) = get_available_skins() + .await? + .into_iter() + .find(|skin| skin.is_equipped) + .filter(|skin| skin.source == SkinSource::CustomExternal) + { + CustomMinecraftSkin::add( + selected_credentials.offline_profile.id, + ¤t_external_skin.texture_key, + ¤t_external_skin + .resolve_texture() + .await? + .try_fold(vec![], async |mut texture_blob, chunk| { + texture_blob.extend_from_slice(&chunk); + Ok(texture_blob) + }) + .await?, + current_external_skin.variant, + current_external_skin.cape_id, + &state.pool, + ) + .await?; + } + + Ok(()) +} + fn texture_blob_to_data_url(texture_blob: Vec) -> Arc { let data = if is_png(&texture_blob) { Cow::Owned(texture_blob)