using System; using System.Globalization; using System.IO; using System.Text; using System.Text.Json.Nodes; using Microsoft.VisualBasic; using PCL.Core.App.Localization; using PCL.Core.UI; using PCL.Core.Utils; using PCL.Network; namespace PCL; public static class ModSkin { public struct McSkinInfo { public bool IsSlim; public string LocalFile; public bool IsVaild; } /// /// 要求玩家选择一个皮肤文件,并进行相关校验。 /// public static McSkinInfo McSkinSelect() { var fileName = SystemDialogs.SelectFile(Lang.Text("Launch.Skin.FileDialog.Filter"), Lang.Text("Launch.Skin.FileDialog.Title")); // 验证有效性 if (string.IsNullOrEmpty(fileName)) return new McSkinInfo { IsVaild = false }; try { var image = new MyBitmap(fileName); if (image.pic.Width != 64 || !(image.pic.Height == 32 || image.pic.Height == 64)) { HintService.Hint(Lang.Text("Launch.Skin.InvalidSize"), HintType.Error); return new McSkinInfo { IsVaild = false }; } var fileInfo = new FileInfo(fileName); if (fileInfo.Length > 24 * 1024) { HintService.Hint(Lang.Text("Launch.Skin.FileTooLarge", Lang.Number(fileInfo.Length / 1024d, "N2")), HintType.Error); return new McSkinInfo { IsVaild = false }; } } catch (Exception ex) { ModBase.Log( ex, Lang.Text("Launch.Skin.File.Error"), ModBase.LogLevel.Hint, userSummary: Lang.Text("Launch.Skin.File.Error")); return new McSkinInfo { IsVaild = false }; } // 获取皮肤种类 var isSlim = ModMain.MyMsgBox(Lang.Text("Launch.Skin.Model.SelectMessage"), Lang.Text("Launch.Skin.Model.SelectTitle"), Lang.Text("Launch.Skin.Model.Steve"), Lang.Text("Launch.Skin.Model.Alex"), Lang.Text("Common.Option.IDontKnow"), highLight: false); if (isSlim == 3) { HintService.Hint(Lang.Text("Launch.Skin.Model.UnknownHint")); return new McSkinInfo { IsVaild = false }; } return new McSkinInfo { IsVaild = true, IsSlim = isSlim == 2, LocalFile = fileName }; } /// /// 获取 Uuid 对应的皮肤文件地址,失败将抛出异常。 /// public static string McSkinGetAddress(string uuid, string type) { if (string.IsNullOrEmpty(uuid)) throw new Exception(Lang.Text("Minecraft.Skin.Error.UuidEmpty")); if (uuid.StartsWith("00000")) throw new Exception(Lang.Text("Minecraft.Skin.Error.OfflineNoSkin")); // 尝试读取缓存 var cachePath = Path.Combine(ModBase.pathTemp, $"Cache\\Skin\\Index{type}.ini"); var cacheSkinAddress = ModBase.ReadIni(cachePath, uuid); if (!string.IsNullOrEmpty(cacheSkinAddress)) return cacheSkinAddress; // 获取皮肤地址 var url = type switch { "Mojang" => "https://sessionserver.mojang.com/session/minecraft/profile/", "Ms" => "https://sessionserver.mojang.com/session/minecraft/profile/", "Auth" => ModProfile.selectedProfile.Server.Replace("/authserver", "") + "/sessionserver/session/minecraft/profile/", _ => throw new ArgumentException(Lang.Text("Minecraft.Skin.Error.InvalidSkinType", type ?? "null")) }; var skinString = ModNet.NetGetCodeByRequestRetry(url + uuid); if (string.IsNullOrEmpty((string?)skinString)) throw new Exception(Lang.Text("Minecraft.Skin.Error.SkinReturnEmpty")); // 解析皮肤 Property string skinValue = null; try { var json = (JsonObject)ModBase.GetJson((string)skinString); foreach (var property in json["properties"].AsArray()) if (property["name"]?.ToString() == "textures") { skinValue = property["value"]?.ToString(); break; } if (skinValue is null) throw new Exception(Lang.Text("Minecraft.Skin.Error.PropertyNotFound")); } catch (Exception ex) { ModBase.Log(ex, $"无法完成解析的皮肤返回值,可能是未设置自定义皮肤的用户:{skinString}", ModBase.LogLevel.Developer); throw new Exception(Lang.Text("Minecraft.Skin.Error.NoSkinData"), ex); } // 解码 Base64 并解析 JSON var decoded = Encoding.UTF8.GetString(Convert.FromBase64String(skinValue)); var skinJson = (JsonObject)ModBase.GetJson(decoded.ToLowerInvariant()); if (skinJson["textures"]?["skin"]?["url"] is null) throw new Exception(Lang.Text("Minecraft.Skin.Error.NoCustomSkin")); var skinUrl = skinJson["textures"]["skin"]["url"].ToString(); skinUrl = skinUrl.Contains("minecraft.net/") ? skinUrl.Replace("http://", "https://") : skinUrl; // 保存缓存 ModBase.WriteIni(cachePath, uuid, skinUrl); ModBase.Log($"[Skin] UUID {uuid} 对应的皮肤文件为 {skinUrl}"); return skinUrl; } private static readonly object mcSkinDownloadLock = new(); /// /// 从 Url 下载皮肤。返回本地文件路径,失败将抛出异常。 /// public static string McSkinDownload(string address) { var skinName = ModBase.GetFileNameFromPath(address); var fileAddress = ModBase.pathTemp + @"Cache\Skin\" + ModBase.GetHash(address) + ".png"; lock (mcSkinDownloadLock) { if (!File.Exists(fileAddress)) { FileDownloader.DownloadAsync(address, fileAddress + ModNet.netDownloadEnd).GetAwaiter().GetResult(); File.Delete(fileAddress); FileSystem.Rename(fileAddress + ModNet.netDownloadEnd, fileAddress); ModBase.Log("[Minecraft] 皮肤下载成功:" + fileAddress); } return fileAddress; } } /// /// 获取 Uuid 对应的皮肤,返回"Steve"或"Alex"。 /// public static string McSkinSex(string uuid) { if (uuid.Length != 32) return "Steve"; var a = int.Parse(uuid[7].ToString(), NumberStyles.AllowHexSpecifier); var b = int.Parse(uuid[15].ToString(), NumberStyles.AllowHexSpecifier); var c = int.Parse(uuid[23].ToString(), NumberStyles.AllowHexSpecifier); var d = int.Parse(uuid[31].ToString(), NumberStyles.AllowHexSpecifier); return ((a ^ b ^ c ^ d) % 2) != 0 ? "Alex" : "Steve"; // Math.floorMod(uuid.hashCode(), 18) // Public Function hashCode(ByVal str As String) As Integer // Dim hash As Integer = 0 // Dim n As Integer = str.Length // If n = 0 Then // Return hash // End If // For i As Integer = 0 To n - 1 // hash = hash + Asc(str(i)) * (1 << (n - i - 1)) // Next // Return hash // End Function } }