龙腾猫跃 7 месяцев назад
Родитель
Сommit
af8be24506

+ 3 - 0
Plain Craft Launcher 2/Controls/MyHint.xaml.vb

@@ -107,11 +107,14 @@ Public Class MyHint
     End Sub
 
     '触发点击事件
+    Public Event Click(sender As Object, e As MouseButtonEventArgs)
     Private IsMouseDown As Boolean = False
     Private Sub MyHint_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
         If Not IsMouseDown Then Return
         IsMouseDown = False
         Log("[Control] 按下提示条" & If(String.IsNullOrEmpty(Name), "", ":" & Name))
+        RaiseEvent Click(sender, e)
+        If e.Handled Then Return
         e.Handled = True
         RaiseCustomEvent() '自定义事件
     End Sub

+ 20 - 0
Plain Craft Launcher 2/FormMain.xaml.vb

@@ -11,6 +11,16 @@ Public Class FormMain
         Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
         '统计更新日志条目
 #If BETA Then
+        If LastVersion < 386 Then 'Release 2.12.5
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法下载 NeoForge 26.1"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:部分关键词搜不到资源,特别是中文 Mod 搜索经常没有结果"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:降低游戏的内存占用"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:老版本 Windows 无法启动游戏"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:连接 Mojang 的服务可能失败,提示错误码 421"))
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法访问爱发电"))
+            FeatureCount += 24
+            BugCount += 19
+        End If
         If LastVersion < 383 Then 'Release 2.12.3
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:部分关键词搜不到资源,特别是中文 Mod 搜索经常没有结果"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:下载可能完全卡住,或是下载进度反复回退"))
@@ -140,6 +150,16 @@ Public Class FormMain
         '3:BUG+ IMP* FEAT-
         '2:BUG* IMP-
         '1:BUG-
+        If LastVersion < 385 Then 'Snapshot 2.12.5
+            FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法下载 NeoForge 26.1"))
+            If LastVersion = 384 Then
+                FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:下载可能失败,提示下载管理刷新线程出错"))
+                FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法保存选择的 Minecraft 文件夹"))
+                FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:使用中文搜索 Mod 时, 部分结果会忽略筛选条件"))
+            End If
+            FeatureCount += 3
+            BugCount += 6
+        End If
         If LastVersion < 384 Then 'Snapshot 2.12.4
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:部分关键词搜不到资源,特别是中文 Mod 搜索经常没有结果"))
             FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:降低游戏的内存占用"))

+ 11 - 8
Plain Craft Launcher 2/Modules/Base/ModBase.vb

@@ -14,13 +14,13 @@ Public Module ModBase
 #Region "声明"
 
     '下列版本信息由更新器自动修改
-    Public Const VersionBaseName As String = "2.12.4" '不含分支前缀的显示用版本名
-    Public Const VersionStandardCode As String = "2.12.4." & VersionBranchCode '标准格式的四段式版本号
+    Public Const VersionBaseName As String = "2.12.5" '不含分支前缀的显示用版本名
+    Public Const VersionStandardCode As String = "2.12.5." & VersionBranchCode '标准格式的四段式版本号
     Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
 #If BETA Then
     Public Const VersionCode As Integer = 383 'Release
 #Else
-    Public Const VersionCode As Integer = 384 'Snapshot
+    Public Const VersionCode As Integer = 385 'Snapshot
 #End If
     '自动生成的版本信息
     Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -1336,11 +1336,12 @@ Re:
         Dim Files As String()
         Try
             Files = Directory.GetFiles(Path)
-        Catch ex As DirectoryNotFoundException '#4549
-            If Not Directory.Exists(Path) Then Return 0 '可能已被其他线程删除
-            Log(ex, $"疑似为孤立符号链接,尝试直接删除({Path})", LogLevel.Developer)
-            Directory.Delete(Path)
-            Return 0
+        Catch ex As DirectoryNotFoundException '#4549,也可能已被其他线程删除
+            If Directory.Exists(Path) Then
+                Log(ex, $"疑似为孤立符号链接,尝试直接删除({Path})", LogLevel.Developer)
+                Directory.Delete(Path)
+            End If
+            Return DeletedCount
         End Try
         For Each FilePath As String In Files
             Dim RetriedFile As Boolean = False
@@ -1348,6 +1349,8 @@ RetryFile:
             Try
                 File.Delete(FilePath)
                 DeletedCount += 1
+            Catch ex As DirectoryNotFoundException
+                Return DeletedCount
             Catch ex As Exception
                 If Not RetriedFile Then
                     RetriedFile = True

+ 16 - 8
Plain Craft Launcher 2/Modules/Base/ModNet.vb

@@ -182,10 +182,19 @@ RequestFinished:
             Throw New Exception($"预处理下载文件路径失败({LocalPath})", ex)
         End Try
         '下载
+        Dim RetryCount As Integer = 0
+Retry:
         Try
             File.WriteAllBytes(LocalPath, SendRequest(Url, HttpMethod.Get, SimulateBrowserHeaders:=SimulateBrowserHeaders))
+        Catch ex As ThreadInterruptedException
+            File.Delete(LocalPath)
+            Throw
         Catch ex As Exception
             File.Delete(LocalPath)
+            If RetryCount < 2 Then
+                RetryCount += 1
+                GoTo Retry
+            End If
             Throw New WebException($"通过网络请求直接下载小文件失败({LocalPath})", ex)
         End Try
     End Sub
@@ -284,6 +293,9 @@ RequestFinished:
             Throw
         Catch ex As ResponsedWebException
             Throw
+        Catch ex As FileNotFoundException
+            '由于 CacheCow 读取缓存失败,然后系统调用不存在的 System.Net.Http.Formatting.resources 抛出(#8150)
+            Throw New Exception($"网络请求时读取缓存失败", ex)
         Catch ex As Exception
             RecordIPReliability(HostIp, -1)
             If TypeOf ex Is OperationCanceledException OrElse TypeOf ex Is TimeoutException Then 'CancellationToken 超时
@@ -416,7 +428,7 @@ RequestFinished:
     Public ReadOnly Property NetTaskSpeedLimitHigh As Long
         Get
             If _NetTaskSpeedLimitHigh Is Nothing Then
-                Dim Setting = PCL.Settings.Get("ToolDownloadSpeed")
+                Dim Setting As Integer = Settings.Get("ToolDownloadSpeed")
                 If Setting <= 14 Then
                     _NetTaskSpeedLimitHigh = (Setting + 1) * 0.1 * 1024 * 1024L
                 ElseIf Setting <= 31 Then
@@ -1959,13 +1971,9 @@ Retry:
     ''' 是否有正在进行中、需要在下载管理页面显示的下载任务?
     ''' </summary>
     Public Function HasDownloadingTask(Optional IgnoreCustomDownload As Boolean = False) As Boolean
-        For Each Task In LoaderTaskbar.ToList()
-            If (Task.Show AndAlso Task.State = LoadState.Loading) AndAlso
-               (Not IgnoreCustomDownload OrElse Not Task.Name.ToString.Contains("自定义下载")) Then
-                Return True
-            End If
-        Next
-        Return False
+        Return LoaderTaskbar.Any(
+        Function(Task) Task.Show AndAlso Task.State = LoadState.Loading AndAlso
+                       (Not IgnoreCustomDownload OrElse Not Task.Name.ToString.Contains("自定义下载")))
     End Function
 
 #End Region

+ 81 - 71
Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb

@@ -1,35 +1,38 @@
 Public Module ModComp
 
-    Public Enum CompType
-        ''' <summary>
-        ''' 允许任意种类,或种类未知。
-        ''' </summary>
-        Any = -1
+    <Flags> Public Enum CompType
         ''' <summary> 
         ''' Mod。
         ''' </summary>
-        [Mod] = 0
+        [Mod] = 1
         ''' <summary>
         ''' 整合包。
         ''' </summary>
-        ModPack = 1
+        ModPack = 2
         ''' <summary>
         ''' 资源包。
         ''' </summary>
-        ResourcePack = 2
+        ResourcePack = 4
         ''' <summary>
         ''' 光影包。
         ''' </summary>
-        Shader = 3
+        Shader = 8
         ''' <summary>
-        ''' CurseForge:数据包。
-        ''' Modrinth:数据包,或数据包与 Mod 的混合。
+        ''' 数据包。
         ''' </summary>
-        DataPack = 4
+        DataPack = 16
         ''' <summary>
         ''' 服务端插件。
         ''' </summary>
-        Plugin = 5
+        Plugin = 32
+        ''' <summary>
+        ''' 同时包含数据包以及 Mod。
+        ''' </summary>
+        ModOrDataPack = [Mod] Or DataPack
+        ''' <summary>
+        ''' 允许任意种类,或种类未知。
+        ''' </summary>
+        Any = [Mod] Or ModPack Or ResourcePack Or Shader Or DataPack Or Plugin
     End Enum
     Public Enum CompModLoaderType
         'https://docs.curseforge.com/?http#tocS_ModLoaderType
@@ -132,10 +135,10 @@
         ''' </summary>
         Public ReadOnly FromCurseForge As Boolean
         ''' <summary>
-        ''' 工程的种类。
-        ''' 由于 Modrinth 混合使用 Mod 和数据包,结果不一定准确。
+        ''' 工程中包含的文件种类。
+        ''' 若为 Modrinth 工程,可能为 Mod 或 数据包。
         ''' </summary>
-        Public ReadOnly Type As CompType
+        Public ReadOnly Types As CompType
         ''' <summary>
         ''' 工程的短名。例如 technical-enchant。
         ''' </summary>
@@ -191,6 +194,12 @@
         ''' 例如:261(26.1.x)、180(1.18.x)。
         ''' </summary>
         Public ReadOnly Drops As List(Of Integer)
+        ''' <summary>
+        ''' Modrinth API 返回的原始版本列表。
+        ''' 仅用于 Modrinth 工程的二次筛选,不会被缓存,不会被预处理。
+        ''' 若非从 Modrinth 获取则为 Nothing。
+        ''' </summary>
+        Public ReadOnly UnsafeGameVersions As List(Of String)
 
         '数据库信息
 
@@ -203,7 +212,7 @@
             Get
                 If Not LoadedDatabase Then
                     LoadedDatabase = True
-                    If Type = CompType.Mod OrElse Type = CompType.DataPack Then
+                    If Types.HasFlag(CompType.Mod) OrElse Types.HasFlag(CompType.DataPack) Then
                         _DatabaseEntry = CompDatabase.FirstOrDefault(Function(c) If(FromCurseForge, c.CurseForgeSlug, c.ModrinthSlug) = Slug)
                     End If
                 End If
@@ -240,7 +249,7 @@
             If Data.ContainsKey("Tags") Then
 #Region "CompJson"
                 FromCurseForge = Data("DataSource") = "CurseForge"
-                Type = Data("Type").ToObject(Of Integer)
+                Types = Data("Types").ToObject(Of Integer)
                 Slug = Data("Slug")
                 Id = Data("Id")
                 If Data.ContainsKey("CurseForgeFileIds") Then CurseForgeFileIds = CType(Data("CurseForgeFileIds"), JArray).Select(Function(t) t.ToObject(Of Integer)).ToList
@@ -284,23 +293,23 @@
                     If LogoUrl = "" Then LogoUrl = Nothing
                     'Type
                     If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then
-                        Type = CompType.Mod
+                        Types = CompType.Mod
                     ElseIf Website.Contains("/modpacks/") Then
-                        Type = CompType.ModPack
+                        Types = CompType.ModPack
                     ElseIf Website.Contains("/resourcepacks/") Then
-                        Type = CompType.ResourcePack
+                        Types = CompType.ResourcePack
                     ElseIf Website.Contains("/texture-packs/") Then
-                        Type = CompType.ResourcePack
+                        Types = CompType.ResourcePack
                     ElseIf Website.Contains("/shaders/") Then
-                        Type = CompType.Shader
+                        Types = CompType.Shader
                     Else
-                        Type = CompType.DataPack
+                        Types = CompType.DataPack
                     End If
                     'FileIndexes / VanillaMajorVersions / ModLoaders
                     ModLoaders = New List(Of CompModLoaderType)
                     Dim Files As New List(Of KeyValuePair(Of Integer, List(Of String))) 'FileId, GameVersions
                     For Each File In If(Data("latestFiles"), New JArray)
-                        Dim NewFile As New CompFile(File, Type)
+                        Dim NewFile As New CompFile(File, Types)
                         If Not NewFile.Available Then Continue For
                         ModLoaders.AddRange(NewFile.ModLoaders)
                         Dim GameVersions = File("gameVersions").ToObject(Of List(Of String))
@@ -408,14 +417,14 @@
                     Website = $"https://modrinth.com/{Data("project_type")}/{Slug}"
                     'GameVersions
                     '搜索结果的键为 versions,获取特定工程的键为 game_versions
-                    Drops = If(CType(If(Data("game_versions"), Data("versions")), JArray), New JArray).
-                        Select(Function(v) McVersion.VersionToDrop(v)).Where(Function(v) v > 0).Distinct.OrderByDescending(Function(v) v).ToList
+                    UnsafeGameVersions = If(CType(If(Data("game_versions"), Data("versions")), JArray), New JArray).Select(Function(v) v.ToString).Distinct.ToList
+                    Drops = UnsafeGameVersions.Select(Function(v) McVersion.VersionToDrop(v)).Where(Function(v) v > 0).Distinct.OrderByDescending(Function(v) v).ToList
                     'Type
                     Select Case Data("project_type").ToString
-                        Case "modpack" : Type = CompType.ModPack
-                        Case "resourcepack" : Type = CompType.ResourcePack
-                        Case "shader" : Type = CompType.Shader
-                        Case Else : Type = CompType.Mod 'Modrinth 将数据包标为 Mod
+                        Case "modpack" : Types = CompType.ModPack
+                        Case "resourcepack" : Types = CompType.ResourcePack
+                        Case "shader" : Types = CompType.Shader
+                        Case Else : Types = CompType.Mod 'Modrinth 将数据包标为 Mod,但 categories 字段里有 datapack
                     End Select
                     'Tags & ModLoaders
                     Tags = New List(Of String)
@@ -437,7 +446,7 @@
                             Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric)
                             Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt)
                             Case "neoforge" : ModLoaders.Add(CompModLoaderType.NeoForge)
-                            Case "datapack" : Type = CompType.DataPack '若包含数据包版本,则优先标为 DataPack
+                            Case "datapack" : Types = CompType.DataPack
                             '共用
                             Case "technology" : Tags.Add("科技")
                             Case "magic" : Tags.Add("魔法")
@@ -452,9 +461,9 @@
                             Case "game-mechanics" : Tags.Add("游戏机制")
                             Case "transportation" : Tags.Add("运输")
                             Case "storage" : Tags.Add("仓储")
-                            Case "decoration" : If Type <> CompType.ResourcePack Then Tags.Add("装饰")
-                            Case "mobs" : If Type <> CompType.ResourcePack Then Tags.Add("生物")
-                            Case "equipment" : If Type <> CompType.ResourcePack Then Tags.Add("装备")
+                            Case "decoration" : If Types <> CompType.ResourcePack Then Tags.Add("装饰")
+                            Case "mobs" : If Types <> CompType.ResourcePack Then Tags.Add("生物")
+                            Case "equipment" : If Types <> CompType.ResourcePack Then Tags.Add("装备")
                             Case "social" : Tags.Add("服务器")
                             Case "library" : Tags.Add("支持库")
                             '整合包
@@ -504,6 +513,7 @@
                             Case "vanilla" : Tags.Add("原版可用")
                         End Select
                     Next
+                    If Types = CompType.DataPack AndAlso ModLoaders.Any Then Types = CompType.DataPack Or CompType.Mod
 #End Region
                 End If
                 If Not Tags.Any() Then Tags.Add("其他")
@@ -519,7 +529,7 @@
         Public Function ToJson() As JObject
             Dim Json As New JObject
             Json("DataSource") = If(FromCurseForge, "CurseForge", "Modrinth")
-            Json("Type") = CInt(Type)
+            Json("Types") = CInt(Types)
             Json("Slug") = Slug
             Json("Id") = Id
             If CurseForgeFileIds IsNot Nothing Then Json("CurseForgeFileIds") = New JArray(CurseForgeFileIds)
@@ -549,7 +559,7 @@
                 For i = 0 To Drops.Count - 1 '版本号一定为降序
                     '获取当前连续的版本号段
                     Dim StartDrop As Integer = Drops(i), EndDrop As Integer = Drops(i)
-                    If StartDrop < 100 Then '如果支持新版本,则不显示 1.9-
+                    If StartDrop < 120 Then '如果支持新版本,则不显示 1.11-
                         If Segments.Any() AndAlso Not IsOld Then
                             Exit For
                         Else
@@ -567,14 +577,14 @@
                     If StartDrop = EndDrop Then
                         Segments.Add(StartName)
                     ElseIf AllDrops IsNot Nothing AndAlso StartDrop >= AllDrops.First Then
-                        If EndDrop < 100 Then
+                        If EndDrop < 120 Then
                             Segments.Clear()
                             Segments.Add("全版本")
                             Exit For
                         Else
                             Segments.Add(EndName & "+")
                         End If
-                    ElseIf EndDrop < 100 Then
+                    ElseIf EndDrop < 120 Then
                         Segments.Add(StartName & "-")
                         Exit For
                     ElseIf AllDrops Is Nothing OrElse AllDrops.IndexOf(EndDrop) - AllDrops.IndexOf(StartDrop) = 1 Then
@@ -762,7 +772,7 @@ NoSubtitle:
             'Mod 加载器一致
             If ModLoaders.Count <> Project.ModLoaders.Count OrElse ModLoaders.Except(Project.ModLoaders).Any() Then Return False
             '若不为光影,则要求 MC 版本一致
-            If Type <> CompType.Shader AndAlso (Drops.Count <> Project.Drops.Count OrElse Drops.Except(Project.Drops).Any()) Then Return False
+            If Types <> CompType.Shader AndAlso (Drops.Count <> Project.Drops.Count OrElse Drops.Except(Project.Drops).Any()) Then Return False
             '最近更新时间差距在一周以内
             If Math.Abs((LastUpdate - Project.LastUpdate).TotalDays) > 7 Then Return False
             'MCMOD 翻译名 / 原名 / 描述文本 / Slug 的英文部分相同
@@ -813,8 +823,8 @@ NoSubtitle:
         ''' </summary>
         Public ReadOnly Property CanContinue As Boolean
             Get
-                If Tag.StartsWithF("/") OrElse Not Source.HasFlag(CompSourceType.CurseForge) Then Storage.CurseForgeTotal = 0
-                If Tag.EndsWithF("/") OrElse Not Source.HasFlag(CompSourceType.Modrinth) Then Storage.ModrinthTotal = 0
+                If Tag.StartsWithF("/") OrElse Not Sources.HasFlag(CompSourceType.CurseForge) Then Storage.CurseForgeTotal = 0
+                If Tag.EndsWithF("/") OrElse Not Sources.HasFlag(CompSourceType.Modrinth) Then Storage.ModrinthTotal = 0
                 If Storage.CurseForgeTotal = -1 OrElse Storage.ModrinthTotal = -1 Then Return True
                 Return Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal
             End Get
@@ -845,7 +855,7 @@ NoSubtitle:
         ''' <summary>
         ''' 允许的来源。
         ''' </summary>
-        Public Source As CompSourceType = CompSourceType.Any
+        Public Sources As CompSourceType = CompSourceType.Any
         Public Sub New(Type As CompType, Storage As CompProjectStorage, TargetResultCount As Integer)
             Me.Type = Type
             Me.Storage = Storage
@@ -904,7 +914,7 @@ NoSubtitle:
             Dim request = TryCast(obj, CompProjectRequest)
             Return request IsNot Nothing AndAlso
                 Type = request.Type AndAlso TargetResultCount = request.TargetResultCount AndAlso
-                Tag = request.Tag AndAlso ModLoader = request.ModLoader AndAlso Source = request.Source AndAlso
+                Tag = request.Tag AndAlso ModLoader = request.ModLoader AndAlso Sources = request.Sources AndAlso
                 GameVersion = request.GameVersion AndAlso SearchText = request.SearchText
         End Function
         Public Shared Operator =(left As CompProjectRequest, right As CompProjectRequest) As Boolean
@@ -914,7 +924,7 @@ NoSubtitle:
             Return Not left = right
         End Operator
         Public Overrides Function GetHashCode() As Integer
-            Return (Type, Tag, ModLoader, GameVersion, SearchText, Source).GetHashCode()
+            Return (Type, Tag, ModLoader, GameVersion, SearchText, Sources).GetHashCode()
         End Function
 
     End Class
@@ -1019,7 +1029,7 @@ NoSubtitle:
                 Return Candidates
             End Function
             'CurseForge
-            If Request.Source.HasFlag(CompSourceType.CurseForge) Then
+            If Request.Sources.HasFlag(CompSourceType.CurseForge) Then
                 '数据库搜索
                 Static CurseForgeSearchEntries As List(Of SearchEntry(Of CompDatabaseEntry)) =
                 CompDatabase.Where(Function(Entry) Entry.CurseForgeSlug IsNot Nothing).Select(
@@ -1043,7 +1053,7 @@ NoSubtitle:
                 End If
             End If
             'Modrinth
-            If Request.Source.HasFlag(CompSourceType.Modrinth) Then
+            If Request.Sources.HasFlag(CompSourceType.Modrinth) Then
                 '数据库搜索
                 Static ModrinthSearchEntries As List(Of SearchEntry(Of CompDatabaseEntry)) =
                 CompDatabase.Where(Function(Entry) Entry.ModrinthSlug IsNot Nothing).Select(
@@ -1094,7 +1104,7 @@ NextPage:
         Try
 
             'CurseForge 搜索
-            If Request.Source.HasFlag(CompSourceType.CurseForge) AndAlso
+            If Request.Sources.HasFlag(CompSourceType.CurseForge) AndAlso
                Not (Storage.CurseForgeTotal > -1 AndAlso Storage.CurseForgeTotal <= Storage.CurseForgeOffset) AndAlso '剩余的未显示的搜索结果不足
                (Not IsChineseSearch OrElse (IsChineseSearch AndAlso Not String.IsNullOrEmpty(CurseForgeAltSearchText))) Then '如果是中文搜索,就只在有对应搜索关键词的时候才继续
                 WorkThreads.Add(RunInNewThread(
@@ -1125,7 +1135,7 @@ NextPage:
             End If
 
             'Modrinth 搜索
-            If Request.Source.HasFlag(CompSourceType.Modrinth) AndAlso
+            If Request.Sources.HasFlag(CompSourceType.Modrinth) AndAlso
                Not (Storage.ModrinthTotal > -1 AndAlso Storage.ModrinthTotal <= Storage.ModrinthOffset) AndAlso '剩余的未显示的搜索结果不足
                (Not IsChineseSearch OrElse (IsChineseSearch AndAlso Not String.IsNullOrEmpty(ModrinthAltSearchText))) Then '如果是中文搜索,就只在有对应搜索关键词的时候才继续
                 WorkThreads.Add(RunInNewThread(
@@ -1153,7 +1163,7 @@ NextPage:
             End If
 
             'Modrinth 直接获取工程
-            If Request.Source.HasFlag(CompSourceType.Modrinth) AndAlso
+            If Request.Sources.HasFlag(CompSourceType.Modrinth) AndAlso
                Not (Storage.ModrinthTotal > -1 AndAlso Storage.ModrinthTotal <= Storage.ModrinthOffset) AndAlso '剩余的未显示的搜索结果不足
                ModrinthSlugs.Any Then '有直接获取的 Slug
                 WorkThreads.Add(RunInNewThread(
@@ -1163,7 +1173,16 @@ NextPage:
                         Log("[Comp] 开始 Modrinth 直接获取:" & ModrinthUrl)
                         Dim ProjectList As New List(Of CompProject)
                         For Each JsonEntry As JObject In DlModRequest(ModrinthUrl)
-                            ProjectList.Add(New CompProject(JsonEntry))
+                            Dim Project As New CompProject(JsonEntry)
+                            '应用筛选
+                            If Request.Type <> CompType.Any AndAlso Not Project.Types.HasFlag(Request.Type) Then Continue For
+                            If Not String.IsNullOrEmpty(Request.Tag) AndAlso
+                                Not JsonEntry("categories").Any(Function(c) c.ToString = Request.Tag.AfterLast("/")) Then Continue For 'Project.Tags 已经转换成中文了,只能从 json 判
+                            If Request.ModLoader <> CompModLoaderType.Any AndAlso Not IgnoreModLoaderFilter AndAlso
+                                Not Project.ModLoaders.Any(Function(m) m = Request.ModLoader) Then Continue For
+                            If Not String.IsNullOrEmpty(Request.GameVersion) AndAlso
+                                Not Project.UnsafeGameVersions.Any(Function(d) d = Request.GameVersion) Then Continue For
+                            ProjectList.Add(Project)
                         Next
                         '更新结果
                         ProjectList.ForEach(Sub(p) RawResults.Add(p))
@@ -1196,9 +1215,9 @@ NextPage:
                 Else
                     If IsChineseSearch AndAlso Not (Request.Type = CompType.Mod OrElse Request.Type = CompType.DataPack) Then
                         Throw New Exception(MSG_NO_CHINESE_SEARCH_RESULT)
-                    ElseIf Request.Source = CompSourceType.CurseForge AndAlso Request.Tag.StartsWithF("/") Then
+                    ElseIf Request.Sources = CompSourceType.CurseForge AndAlso Request.Tag.StartsWithF("/") Then
                         Throw New Exception("CurseForge 不兼容所选的类型")
-                    ElseIf Request.Source = CompSourceType.Modrinth AndAlso Request.Tag.EndsWithF("/") Then
+                    ElseIf Request.Sources = CompSourceType.Modrinth AndAlso Request.Tag.EndsWithF("/") Then
                         Throw New Exception("Modrinth 不兼容所选的类型")
                     Else
                         Throw New Exception("没有搜索结果")
@@ -1418,7 +1437,7 @@ NextPage:
         '实例化
 
         ''' <summary>
-        ''' 从文件 Json 中初始化实例。若出错会抛出异常。
+        ''' 从 json 中初始化实例。若出错会抛出异常。
         ''' </summary>
         Public Sub New(Data As JObject, DefaultType As CompType)
             Type = DefaultType
@@ -1505,25 +1524,16 @@ NextPage:
                         Size = File("size")
                         Hash = File("hashes")("sha1")
                     End If
-                    'ModLoaders
+                    'Loaders
                     '结果可能混杂着 Mod、数据包和服务端插件
                     Dim RawLoaders As List(Of String) = Data("loaders").Select(Function(v) v.ToString).ToList
                     ModLoaders = New List(Of CompModLoaderType)
-                    If Type = CompType.Mod Then '以尽量宽容的方式检测加载器,以免同时兼容两种的项被删除
-                        If RawLoaders.Intersect({"bukkit", "folia", "paper", "purpur", "spigot"}).Any() Then Type = CompType.Plugin 'Veinminer Enchantment 同时支持服务端与 Fabric
-                        If RawLoaders.Contains("datapack") Then Type = CompType.DataPack
-                        If RawLoaders.Contains("forge") Then ModLoaders.Add(CompModLoaderType.Forge) : Type = CompType.Mod
-                        If RawLoaders.Contains("neoforge") Then ModLoaders.Add(CompModLoaderType.NeoForge) : Type = CompType.Mod
-                        If RawLoaders.Contains("fabric") Then ModLoaders.Add(CompModLoaderType.Fabric) : Type = CompType.Mod
-                        If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt) : Type = CompType.Mod
-                    ElseIf Type = CompType.DataPack Then
-                        If RawLoaders.Intersect({"bukkit", "folia", "paper", "purpur", "spigot"}).Any() Then Type = CompType.Plugin
-                        If RawLoaders.Contains("forge") Then ModLoaders.Add(CompModLoaderType.Forge) : Type = CompType.Mod
-                        If RawLoaders.Contains("neoforge") Then ModLoaders.Add(CompModLoaderType.NeoForge) : Type = CompType.Mod
-                        If RawLoaders.Contains("fabric") Then ModLoaders.Add(CompModLoaderType.Fabric) : Type = CompType.Mod
-                        If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt) : Type = CompType.Mod
-                        If RawLoaders.Contains("datapack") Then Type = CompType.DataPack
-                    End If
+                    If RawLoaders.Intersect({"bukkit", "folia", "paper", "purpur", "spigot"}).Any() Then Type = CompType.Plugin 'Veinminer Enchantment 同时支持服务端与 Fabric
+                    If RawLoaders.Contains("datapack") Then Type = CompType.DataPack
+                    If RawLoaders.Contains("forge") Then ModLoaders.Add(CompModLoaderType.Forge) : Type = CompType.Mod
+                    If RawLoaders.Contains("neoforge") Then ModLoaders.Add(CompModLoaderType.NeoForge) : Type = CompType.Mod
+                    If RawLoaders.Contains("fabric") Then ModLoaders.Add(CompModLoaderType.Fabric) : Type = CompType.Mod
+                    If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt) : Type = CompType.Mod
                     'Dependencies
                     If Data.ContainsKey("dependencies") Then
                         RawDependencies = Data("dependencies").
@@ -1694,7 +1704,7 @@ NextPage:
                 'Modrinth
                 ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version?include_changelog=false")
             End If
-            CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).
+            CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Types)).
                 Where(Function(a) a.Available).ToList.
                 Distinct(Function(a, b) a.Id = b.Id) 'CurseForge 可能会重复返回相同项(#1330)
         End If

+ 3 - 2
Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb

@@ -858,10 +858,11 @@
                 VersionName = ApiName
                 Version = New Version(ApiName.BeforeFirst("-"))
                 If Version.Major >= 24 Then
-                    Inherit = Version.Major & "." & Version.Minor
+                    Inherit = $"{Version.Major}.{Version.Minor}.{Version.Build}"
                 Else
-                    Inherit = "1." & Version.Major & If(Version.Minor > 0, "." & Version.Minor, "")
+                    Inherit = $"1.{Version.Major}.{Version.Minor}"
                 End If
+                If Inherit.EndsWithF(".0") Then Inherit = Left(Inherit, Inherit.Length - 2)
                 If VersionName.Contains("+") Then Inherit &= "-" & VersionName.AfterFirst("+")
             End If
         End Sub

+ 2 - 3
Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb

@@ -1446,9 +1446,10 @@ NextInstance:
             Args = Args.Where(Function(a) Not RegexCheck(a,
                 "( )*-XX:[+-]?(Use\w+GC|ZGenerational|UseCompactObjectHeaders|G1\w+Percent|G1\w+Size|(Max|Min)(GCPauseMillis|HeapFreeRatio))")).ToList
             '添加 GC 参数
+            Args.Add("-XX:+UnlockExperimentalVMOptions")
+            If McLaunchJavaSelected.MajorVersion >= 24 AndAlso McLaunchJavaSelected.Is64Bit Then Args.Add("-XX:+UseCompactObjectHeaders")
             If UseG1GC AndAlso SetupType = 4 Then
                 '优化的 G1GC
-                Args.Add("-XX:+UnlockExperimentalVMOptions")
                 Args.Add("-XX:+UseG1GC")
                 Args.Add("-XX:G1NewSizePercent=20")
                 Args.Add("-XX:G1ReservePercent=20")
@@ -1459,10 +1460,8 @@ NextInstance:
                 If McLaunchJavaSelected.MajorVersion = 8 Then Args.Add("-XX:+ParallelRefProcEnabled")
                 If McLaunchJavaSelected.MajorVersion >= 12 Then Args.Add("-XX:MinHeapFreeRatio=25") '需要比 G1NewSizePercent 大一点
                 If McLaunchJavaSelected.MajorVersion >= 12 Then Args.Add("-XX:MaxHeapFreeRatio=40") '需要比 MinHeapFreeRatio 大一些
-                If McLaunchJavaSelected.MajorVersion >= 24 AndAlso McLaunchJavaSelected.Is64Bit Then Args.Add("-XX:+UseCompactObjectHeaders")
             ElseIf UseG1GC Then
                 'Mojang G1GC
-                Args.Add("-XX:+UnlockExperimentalVMOptions")
                 Args.Add("-XX:+UseG1GC")
                 Args.Add("-XX:G1NewSizePercent=20")
                 Args.Add("-XX:G1ReservePercent=20")

+ 1 - 1
Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb

@@ -1119,7 +1119,7 @@ ExitDataLoad:
         Public Shared Function IsFormatFit(Version As String) As Boolean
             If Version Is Nothing Then Return False
             If RegexCheck(Version, "^1\.\d") Then Return True
-            If Val(RegexSeek(Version, "^[2-9]\d\.\d+")) > 25 Then Return True
+            If Val(RegexSeek(Version, "^[2-9]\d\.\d+")) >= 26 Then Return True
             Return False
         End Function
         ''' <summary>

+ 1 - 1
Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb

@@ -1,7 +1,7 @@
 Imports System.IO.Compression
 
 Public Module ModMod
-    Private Const LocalModCacheVersion As Integer = 12
+    Private Const LocalModCacheVersion As Integer = 13
 
     Public Class McMod
 

+ 2 - 2
Plain Craft Launcher 2/My Project/AssemblyInfo.vb

@@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices
 ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
 ' 方法是按如下所示使用“*”
 
-<Assembly: AssemblyVersion("2.12.4.0")>
-<Assembly: AssemblyFileVersion("2.12.4.0")>
+<Assembly: AssemblyVersion("2.12.5.0")>
+<Assembly: AssemblyFileVersion("2.12.5.0")>
 <Assembly: NeutralResourcesLanguage("")>

+ 8 - 8
Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb

@@ -94,36 +94,36 @@
         End If
         '打开详情页
         Dim TargetType As CompType
-        Dim TargetVersion As String = Nothing
+        Dim TargetInstance As String = Nothing
         Dim TargetLoader As CompModLoaderType = CompModLoaderType.Any
         If FrmMain.PageCurrent.Page = FormMain.PageType.Download Then
             '从下载页进入
             Select Case FrmMain.PageCurrentSub
                 Case FormMain.PageSubType.DownloadMod
                     TargetType = CompType.Mod
-                    TargetVersion = FrmDownloadMod.Content.Loader.Input.GameVersion
+                    TargetInstance = FrmDownloadMod.Content.Loader.Input.GameVersion
                     TargetLoader = FrmDownloadMod.Content.Loader.Input.ModLoader
                 Case FormMain.PageSubType.DownloadPack
                     TargetType = CompType.ModPack
-                    TargetVersion = FrmDownloadPack.Content.Loader.Input.GameVersion
+                    TargetInstance = FrmDownloadPack.Content.Loader.Input.GameVersion
                 Case FormMain.PageSubType.DownloadDataPack
                     TargetType = CompType.DataPack
-                    TargetVersion = FrmDownloadDataPack.Content.Loader.Input.GameVersion
+                    TargetInstance = FrmDownloadDataPack.Content.Loader.Input.GameVersion
                 Case FormMain.PageSubType.DownloadResourcePack
                     TargetType = CompType.ResourcePack
-                    TargetVersion = FrmDownloadResourcePack.Content.Loader.Input.GameVersion
+                    TargetInstance = FrmDownloadResourcePack.Content.Loader.Input.GameVersion
                 Case FormMain.PageSubType.DownloadShader
                     TargetType = CompType.Shader
-                    TargetVersion = FrmDownloadShader.Content.Loader.Input.GameVersion
+                    TargetInstance = FrmDownloadShader.Content.Loader.Input.GameVersion
             End Select
         Else
             '从详情页进入(查看前置)
             TargetType = CompType.Any '允许任意类别
-            TargetVersion = FrmMain.PageCurrent.Additional(2)
+            TargetInstance = FrmMain.PageCurrent.Additional(2)
             TargetLoader = FrmMain.PageCurrent.Additional(3)
         End If
         FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail,
-                           .Additional = {sender.Tag, New List(Of String), TargetVersion, TargetLoader, TargetType}})
+                           .Additional = {sender.Tag, New List(Of String), TargetInstance, TargetLoader, TargetType}})
     End Sub
 
     '鼠标点击判定

+ 4 - 3
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml

@@ -34,15 +34,16 @@
                     <TextBlock VerticalAlignment="Center" Grid.Row="2" HorizontalAlignment="Left" Text="版本" Margin="0,0,18,0" />
                     <local:MyComboBox x:Name="TextSearchVersion" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" IsEditable="True">
                         <local:MyComboBoxItem Content="全部 (也可自行输入)" IsSelected="True" />
+                        <local:MyComboBoxItem Content="26.1" />
                         <local:MyComboBoxItem Content="1.21.11" />
+                        <local:MyComboBoxItem Content="1.21.8" />
+                        <local:MyComboBoxItem Content="1.21.4" />
                         <local:MyComboBoxItem Content="1.21.1" />
-                        <local:MyComboBoxItem Content="1.20.6" />
                         <local:MyComboBoxItem Content="1.20.1" />
-                        <local:MyComboBoxItem Content="1.19.4" />
+                        <local:MyComboBoxItem Content="1.19.2" />
                         <local:MyComboBoxItem Content="1.18.2" />
                         <local:MyComboBoxItem Content="1.16.5" />
                         <local:MyComboBoxItem Content="1.12.2" />
-                        <local:MyComboBoxItem Content="1.8.9" />
                         <local:MyComboBoxItem Content="1.7.10" />
                     </local:MyComboBox>
                     <local:MyComboBox x:Name="ComboSearchLoader" Visibility="Collapsed" Grid.Row="2" Grid.Column="2" Margin="10,0,0,0">

+ 1 - 1
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb

@@ -127,7 +127,7 @@ Public Class PageComp
             .GameVersion = GameVersion
             .Tag = ComboSearchTag.SelectedItem.Tag
             .ModLoader = If(PageType = CompType.Mod, Val(ComboSearchLoader.SelectedItem.Tag), CompModLoaderType.Any)
-            .Source = CType(Val(ComboSearchSource.SelectedItem.Tag), CompSourceType)
+            .Sources = CType(Val(ComboSearchSource.SelectedItem.Tag), CompSourceType)
         End With
         Return Request
     End Function

+ 1 - 0
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml

@@ -20,6 +20,7 @@
                 </StackPanel>
             </local:MyCard>
             <StackPanel Grid.Row="1" Grid.RowSpan="2" Name="PanMain" Grid.IsSharedSizeScope="True">
+                <local:MyHint x:Name="HintAlternative" Theme="Blue" Margin="0,0,0,15" />
                 <local:MyCard Margin="0,0,0,15" x:Name="CardFilter">
                     <StackPanel x:Name="PanFilter" Margin="10,10,0,10" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Left" />
                 </local:MyCard>

+ 23 - 7
Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb

@@ -21,14 +21,15 @@
         Project = FrmMain.PageCurrent.Additional(0)
         TargetInstance = FrmMain.PageCurrent.Additional(2)
         TargetLoader = FrmMain.PageCurrent.Additional(3)
-        PageType = FrmMain.PageCurrent.Additional(4)
+        TargetType = FrmMain.PageCurrent.Additional(4)
     End Sub
     Private Project As CompProject
     Private TargetInstance As String, TargetLoader As CompModLoaderType
     ''' <summary>
-    ''' 当前页面应展示的内容类别。可能为 Any。
+    ''' 当前页面应展示的内容类别。
+    ''' 若当前页面是在查看前置时进入,则也可能为 Any。
     ''' </summary>
-    Private PageType As CompType
+    Private TargetType As CompType
     '自动重试
     Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged
         Select Case CompFileLoader.State
@@ -74,13 +75,13 @@
     '筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表)
     Private Function GetResults() As List(Of CompFile)
         Dim Results As List(Of CompFile) = CompFileLoader.Output
-        Select Case PageType
+        Select Case TargetType
             Case CompType.Any
                 Results = Results.Where(Function(r) r.Type <> CompType.Plugin).ToList
             Case CompType.Shader, CompType.ResourcePack
                 '不筛选光影和资源包,否则原版光影会因为是资源包格式而被过滤(#6473)
             Case Else
-                Results = Results.Where(Function(r) r.Type = PageType).ToList
+                Results = Results.Where(Function(r) r.Type = TargetType).ToList
         End Select
         Return Results
     End Function
@@ -204,7 +205,7 @@ GroupDone:
                 If Not Pair.Value.Any() Then Continue For
                 If Pair.Key = TargetCardName.Replace("(所选版本)", "") Then Continue For
                 '增加卡片
-                Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(PageType = CompType.ModPack, 9, 8)} '9 是安装,8 是另存为
+                Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(TargetType = CompType.ModPack, 9, 8)} '9 是安装,8 是另存为
                 Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Pair.Value}
                 NewCard.Children.Add(NewStack)
                 NewCard.SwapControl = NewStack
@@ -213,7 +214,7 @@ GroupDone:
                 If Pair.Key = TargetCardName OrElse
                    (FrmMain.PageCurrent.Additional IsNot Nothing AndAlso '#2761
                    CType(FrmMain.PageCurrent.Additional(1), List(Of String)).Contains(NewCard.Title)) Then
-                    MyCard.StackInstall(NewStack, If(PageType = CompType.ModPack, 9, 8), Pair.Key) '9 是安装,8 是另存为
+                    MyCard.StackInstall(NewStack, If(TargetType = CompType.ModPack, 9, 8), Pair.Key) '9 是安装,8 是另存为
                 Else
                     NewCard.IsSwapped = True
                 End If
@@ -226,6 +227,14 @@ GroupDone:
             If PanResults.Children.Count = 1 Then
                 CType(PanResults.Children(0), MyCard).IsSwapped = False
             End If
+            '替代提示
+            If Project.Types = CompType.ModOrDataPack AndAlso (TargetType = CompType.Mod OrElse TargetType = CompType.DataPack) Then
+                HintAlternative.Visibility = Visibility.Visible
+                HintAlternative.Text = If(TargetType = CompType.Mod,
+                    "以下是该项目的 Mod 版本。点击这里查看其数据包版本。", "以下是该项目的数据包版本。点击这里查看其 Mod 版本。")
+            Else
+                HintAlternative.Visibility = Visibility.Collapsed
+            End If
         Catch ex As Exception
             Log(ex, "可视化工程下载列表出错", LogLevel.Feedback)
         End Try
@@ -494,4 +503,11 @@ GroupDone:
         ClipboardSet(CompItem.LabTitle.Text & CompItem.LabTitleRaw.Text)
     End Sub
 
+    'Mod / 数据包 互相跳转
+    Private Sub HintAlternative_Click() Handles HintAlternative.Click
+        TargetType = If(TargetType = CompType.Mod, CompType.DataPack, CompType.Mod)
+        FrmMain.PageCurrent.Additional(4) = TargetType '加载器会从这里重新拿数据
+        PageLoaderRestart(IsForceRestart:=True)
+    End Sub
+
 End Class

+ 1 - 1
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb

@@ -47,7 +47,7 @@
     Private Sub BtnEdit_Click(sender As Object, e As EventArgs) Handles BtnEdit.Click
         If McLoginLoader.State = LoadState.Loading Then
             Log("[Launch] 要求更换角色,但登录加载器繁忙", LogLevel.Debug)
-            If CType(McLoginLoader.Input, McLoginServer).ForceReselectProfile Then
+            If TypeOf McLoginLoader.Input Is McLoginServer AndAlso CType(McLoginLoader.Input, McLoginServer).ForceReselectProfile Then
                 Hint("正在尝试更换,请稍候!")
             Else
                 Hint("正在登录中,请稍后再更换角色!", HintType.Red)

+ 47 - 42
Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb

@@ -85,51 +85,56 @@
         Hint("正在更改皮肤……")
         IsChanging = True
         '开始实际获取
-        RunInNewThread(
-        Sub()
-            Try
+        RunInNewThread(Sub() EditSkin(SkinInfo), "Ms Skin Upload")
+    End Sub
+    Private Sub EditSkin(SkinInfo As McSkinInfo)
 Retry:
-                Do While McLoginMsLoader.State = LoadState.Loading '等待登录结束
-                    Thread.Sleep(10)
-                Loop
-                If McLoginMsLoader.State = LoadState.Failed Then Throw New Exception("登录失败", McLoginMsLoader.Error)
-                Dim AccessToken As String = Settings.Get("CacheMsV2Access")
-                Dim Uuid As String = Settings.Get("CacheMsV2Uuid")
-                Dim Result As String = NetRequestByClientRetry("https://api.minecraftservices.com/minecraft/profile/skins", HttpMethod.Post,
-                    Content:=New Net.Http.MultipartFormDataContent From {
-                        {New Net.Http.StringContent(If(SkinInfo.IsSlim, "slim", "classic")), "variant"},
-                        {New Net.Http.ByteArrayContent(ReadFileBytes(SkinInfo.LocalFile)), "file", GetFileNameFromPath(SkinInfo.LocalFile)}
-                    },
-                    Headers:={{"Authorization", "Bearer " & AccessToken}, {"Accept", "*/*"}, {"User-Agent", "MojangSharp/0.1"}})
-                If Result.Contains("request requires user authentication") Then
-                    Hint("正在登录,将在登录完成后继续更改皮肤……")
-                    McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True)
-                    GoTo Retry
-                ElseIf Result.Contains("""error""") Then
-                    Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Red)
+        Try
+            Do While McLoginMsLoader.State = LoadState.Loading '等待登录结束
+                Thread.Sleep(10)
+            Loop
+            If McLoginMsLoader.State = LoadState.Failed Then Throw New Exception("登录失败", McLoginMsLoader.Error)
+            Dim AccessToken As String = Settings.Get("CacheMsV2Access")
+            Dim Uuid As String = Settings.Get("CacheMsV2Uuid")
+            Dim Result As String = NetRequestByClientRetry("https://api.minecraftservices.com/minecraft/profile/skins", HttpMethod.Post,
+                Content:=New Net.Http.MultipartFormDataContent From {
+                    {New Net.Http.StringContent(If(SkinInfo.IsSlim, "slim", "classic")), "variant"},
+                    {New Net.Http.ByteArrayContent(ReadFileBytes(SkinInfo.LocalFile)), "file", GetFileNameFromPath(SkinInfo.LocalFile)}
+                },
+                Headers:={{"Authorization", "Bearer " & AccessToken}, {"Accept", "*/*"}, {"User-Agent", "MojangSharp/0.1"}})
+            If Result.Contains("request requires user authentication") Then
+                Hint("正在重新登录,将在登录后自动更改皮肤……")
+                McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True)
+                GoTo Retry
+            ElseIf Result.Contains("""error""") Then
+                Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Red)
+                Return
+            End If
+            '获取新皮肤地址
+            Log("[Skin] 皮肤修改返回值:" & vbCrLf & Result)
+            Dim ResultJson As JObject = GetJson(Result)
+            If ResultJson.ContainsKey("errorMessage") Then Throw New Exception(ResultJson("errorMessage").ToString) '#5309
+            For Each Skin As JObject In ResultJson("skins")
+                If Skin("state").ToString = "ACTIVE" Then
+                    MySkin.ReloadCache(Skin("url"))
                     Return
                 End If
-                '获取新皮肤地址
-                Log("[Skin] 皮肤修改返回值:" & vbCrLf & Result)
-                Dim ResultJson As JObject = GetJson(Result)
-                If ResultJson.ContainsKey("errorMessage") Then Throw New Exception(ResultJson("errorMessage").ToString) '#5309
-                For Each Skin As JObject In ResultJson("skins")
-                    If Skin("state").ToString = "ACTIVE" Then
-                        MySkin.ReloadCache(Skin("url"))
-                        Return
-                    End If
-                Next
-                Throw New Exception("未知错误(" & Result & ")")
-            Catch ex As Exception
-                If TypeOf ex Is OperationCanceledException OrElse TypeOf ex Is TimeoutException Then
-                    Hint("更改皮肤失败:连接皮肤服务器超时,请稍后再试,或使用 VPN 改善网络环境", HintType.Red)
-                Else
-                    Log(ex, "更改皮肤失败", LogLevel.Hint)
-                End If
-            Finally
-                IsChanging = False
-            End Try
-        End Sub, "Ms Skin Upload")
+            Next
+            Throw New Exception("未知错误(" & Result & ")")
+        Catch ex As Exception
+            If TypeOf ex Is OperationCanceledException OrElse TypeOf ex Is TimeoutException Then
+                Hint("更改皮肤失败:连接皮肤服务器超时,请稍后再试,或使用 VPN 改善网络环境", HintType.Red)
+            ElseIf TypeOf ex Is ResponsedWebException AndAlso CType(ex, ResponsedWebException).StatusCode = HttpStatusCode.Unauthorized Then
+                Log(ex, "更改皮肤时遭遇 401 错误", LogLevel.Developer)
+                Hint("正在重新登录,将在登录后自动更改皮肤……")
+                McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True)
+                GoTo Retry
+            Else
+                Log(ex, "更改皮肤失败", LogLevel.Hint)
+            End If
+        Finally
+            IsChanging = False
+        End Try
     End Sub
 
     '保存皮肤

+ 28 - 0
Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml

@@ -186,6 +186,8 @@
                         <TextBlock Margin="0,1.5,6,0" Text="ZYL" />
                         <TextBlock Margin="0,1.5,6,0" Text="qixi" />
                         <TextBlock Margin="0,1.5,6,0" Text="555" />
+                        <TextBlock Margin="0,1.5,6,0" Text="轩₩" />
+                        <TextBlock Margin="0,1.5,6,0" Text="zzw" />
                         <TextBlock Margin="0,1.5,6,0" Text="JJJT" />
                         <TextBlock Margin="0,1.5,6,0" Text="Rou" />
                         <TextBlock Margin="0,1.5,6,0" Text="wyb" />
@@ -202,6 +204,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="xsqs" />
                         <TextBlock Margin="0,1.5,6,0" Text="啊?" />
                         <TextBlock Margin="0,1.5,6,0" Text="空青" />
+                        <TextBlock Margin="0,1.5,6,0" Text="奇才" />
                         <TextBlock Margin="0,1.5,6,0" Text="无风" />
                         <TextBlock Margin="0,1.5,6,0" Text="听雨" />
                         <TextBlock Margin="0,1.5,6,0" Text="方格" />
@@ -315,6 +318,9 @@
                         <TextBlock Margin="0,1.5,6,0" Text="笨挽" />
                         <TextBlock Margin="0,1.5,6,0" Text="悦木" />
                         <TextBlock Margin="0,1.5,6,0" Text="林霖" />
+                        <TextBlock Margin="0,1.5,6,0" Text="谷雨" />
+                        <TextBlock Margin="0,1.5,6,0" Text="辞七" />
+                        <TextBlock Margin="0,1.5,6,0" Text="血狐" />
                         <TextBlock Margin="0,1.5,6,0" Text="Alex" />
                         <TextBlock Margin="0,1.5,6,0" Text="JIUZ" />
                         <TextBlock Margin="0,1.5,6,0" Text="hina" />
@@ -365,6 +371,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="KSfall" />
                         <TextBlock Margin="0,1.5,6,0" Text="司马Q" />
                         <TextBlock Margin="0,1.5,6,0" Text="FedEx" />
+                        <TextBlock Margin="0,1.5,6,0" Text="&amp;#39;" />
                         <TextBlock Margin="0,1.5,6,0" Text="0NaCl" />
                         <TextBlock Margin="0,1.5,6,0" Text="pisces" />
                         <TextBlock Margin="0,1.5,6,0" Text="ZDHK" />
@@ -381,6 +388,8 @@
                         <TextBlock Margin="0,1.5,6,0" Text="伊蕾娜" />
                         <TextBlock Margin="0,1.5,6,0" Text="竹节虫" />
                         <TextBlock Margin="0,1.5,6,0" Text="狼崽子" />
+                        <TextBlock Margin="0,1.5,6,0" Text="栀熙阳" />
+                        <TextBlock Margin="0,1.5,6,0" Text="晚雾鹤" />
                         <TextBlock Margin="0,1.5,6,0" Text="刘杰世" />
                         <TextBlock Margin="0,1.5,6,0" Text="汐阶屿" />
                         <TextBlock Margin="0,1.5,6,0" Text="ーーー" />
@@ -394,6 +403,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="ximou" />
                         <TextBlock Margin="0,1.5,6,0" Text="小萌新" />
                         <TextBlock Margin="0,1.5,6,0" Text="寒凌川" />
+                        <TextBlock Margin="0,1.5,6,0" Text="江漪焺" />
                         <TextBlock Margin="0,1.5,6,0" Text="沨旻泽" />
                         <TextBlock Margin="0,1.5,6,0" Text="李睿洋" />
                         <TextBlock Margin="0,1.5,6,0" Text="Frutaa" />
@@ -451,22 +461,29 @@
                         <TextBlock Margin="0,1.5,6,0" Text="白清河" />
                         <TextBlock Margin="0,1.5,6,0" Text="晚江诗" />
                         <TextBlock Margin="0,1.5,6,0" Text="諸星洋" />
+                        <TextBlock Margin="0,1.5,6,0" Text="哦哦哦" />
+                        <TextBlock Margin="0,1.5,6,0" Text="咕谷谷" />
+                        <TextBlock Margin="0,1.5,6,0" Text="长白山" />
                         <TextBlock Margin="0,1.5,6,0" Text="UN_41" />
                         <TextBlock Margin="0,1.5,6,0" Text="bguae" />
                         <TextBlock Margin="0,1.5,6,0" Text="yoyal8" />
                         <TextBlock Margin="0,1.5,6,0" Text="hjsa30" />
                         <TextBlock Margin="0,1.5,6,0" Text="Chpoy" />
+                        <TextBlock Margin="0,1.5,6,0" Text="Zh11h" />
                         <TextBlock Margin="0,1.5,6,0" Text="Ander." />
                         <TextBlock Margin="0,1.5,6,0" Text="aKALA" />
                         <TextBlock Margin="0,1.5,6,0" Text="muzsh" />
                         <TextBlock Margin="0,1.5,6,0" Text="gyj120" />
+                        <TextBlock Margin="0,1.5,6,0" Text="Young" />
                         <TextBlock Margin="0,1.5,6,0" Text="IC1805" />
                         <TextBlock Margin="0,1.5,6,0" Text="凌·秋烨" />
                         <TextBlock Margin="0,1.5,6,0" Text="logical" />
                         <TextBlock Margin="0,1.5,6,0" Text="bit...bit" />
+                        <TextBlock Margin="0,1.5,6,0" Text="wujian" />
                         <TextBlock Margin="0,1.5,6,0" Text="N0rdiK" />
                         <TextBlock Margin="0,1.5,6,0" Text="Sphinx" />
                         <TextBlock Margin="0,1.5,6,0" Text="kimma" />
+                        <TextBlock Margin="0,1.5,6,0" Text="Jupiter" />
                         <TextBlock Margin="0,1.5,6,0" Text="techzjc" />
                         <TextBlock Margin="0,1.5,6,0" Text="Keiskei" />
                         <TextBlock Margin="0,1.5,6,0" Text="EMO龚" />
@@ -500,6 +517,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="Decade" />
                         <TextBlock Margin="0,1.5,6,0" Text="laobide" />
                         <TextBlock Margin="0,1.5,6,0" Text="ccb大神" />
+                        <TextBlock Margin="0,1.5,6,0" Text="plastick" />
                         <TextBlock Margin="0,1.5,6,0" Text="chicken" />
                         <TextBlock Margin="0,1.5,6,0" Text="阿毛mjy" />
                         <TextBlock Margin="0,1.5,6,0" Text="CRF1SH" />
@@ -508,6 +526,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="-Guard-" />
                         <TextBlock Margin="0,1.5,6,0" Text="Stridder" />
                         <TextBlock Margin="0,1.5,6,0" Text="猫yi巴草" />
+                        <TextBlock Margin="0,1.5,6,0" Text="秦爅QIN" />
                         <TextBlock Margin="0,1.5,6,0" Text="Lennyes" />
                         <TextBlock Margin="0,1.5,6,0" Text="Aspirike" />
                         <TextBlock Margin="0,1.5,6,0" Text="Paprosa" />
@@ -536,6 +555,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="氮气炸锅" />
                         <TextBlock Margin="0,1.5,6,0" Text="小小小马" />
                         <TextBlock Margin="0,1.5,6,0" Text="爱莉希雅" />
+                        <TextBlock Margin="0,1.5,6,0" Text="未梦领来" />
                         <TextBlock Margin="0,1.5,6,0" Text="容我三思" />
                         <TextBlock Margin="0,1.5,6,0" Text="正经云泽" />
                         <TextBlock Margin="0,1.5,6,0" Text="不会起名" />
@@ -634,6 +654,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="THOPING" />
                         <TextBlock Margin="0,1.5,6,0" Text="EXcloudZ" />
                         <TextBlock Margin="0,1.5,6,0" Text="想rua狐狸" />
+                        <TextBlock Margin="0,1.5,6,0" Text="AmorSick" />
                         <TextBlock Margin="0,1.5,6,0" Text="Leisurelyz" />
                         <TextBlock Margin="0,1.5,6,0" Text="white白鸽" />
                         <TextBlock Margin="0,1.5,6,0" Text="有用的小B" />
@@ -707,9 +728,13 @@
                         <TextBlock Margin="0,1.5,6,0" Text="帕秋莉~go" />
                         <TextBlock Margin="0,1.5,6,0" Text="玉玉小南娘" />
                         <TextBlock Margin="0,1.5,6,0" Text="欣喜的掌声" />
+                        <TextBlock Margin="0,1.5,6,0" Text="小云爱中国" />
+                        <TextBlock Margin="0,1.5,6,0" Text="内存漫游者" />
+                        <TextBlock Margin="0,1.5,6,0" Text="压力暴大了" />
                         <TextBlock Margin="0,1.5,6,0" Text="Myst-FOEY" />
                         <TextBlock Margin="0,1.5,6,0" Text="qaz155808" />
                         <TextBlock Margin="0,1.5,6,0" Text="289775340" />
+                        <TextBlock Margin="0,1.5,6,0" Text="baicai1339" />
                         <TextBlock Margin="0,1.5,6,0" Text="Executarea" />
                         <TextBlock Margin="0,1.5,6,0" Text="rosewhisky" />
                         <TextBlock Margin="0,1.5,6,0" Text="mreaaaaay" />
@@ -732,6 +757,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="DeathHunk" />
                         <TextBlock Margin="0,1.5,6,0" Text="时时雨Sama" />
                         <TextBlock Margin="0,1.5,6,0" Text="永远的Elysia" />
+                        <TextBlock Margin="0,1.5,6,0" Text="Frankie_233" />
                         <TextBlock Margin="0,1.5,6,0" Text="2127864647" />
                         <TextBlock Margin="0,1.5,6,0" Text="Hashtag233" />
                         <TextBlock Margin="0,1.5,6,0" Text="yu🐟 ζ°)))彡" />
@@ -768,6 +794,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="酱汁饼大可爱" />
                         <TextBlock Margin="0,1.5,6,0" Text="下水道的鼠鼠" />
                         <TextBlock Margin="0,1.5,6,0" Text="萨卡兹大蟑螂" />
+                        <TextBlock Margin="0,1.5,6,0" Text="吃麦片的老鼠" />
                         <TextBlock Margin="0,1.5,6,0" Text="小石潭记qwq" />
                         <TextBlock Margin="0,1.5,6,0" Text="centuryplant" />
                         <TextBlock Margin="0,1.5,6,0" Text="Suber_agiley" />
@@ -786,6 +813,7 @@
                         <TextBlock Margin="0,1.5,6,0" Text="guduzihaoge" />
                         <TextBlock Margin="0,1.5,6,0" Text="minecraft风竹" />
                         <TextBlock Margin="0,1.5,6,0" Text="huangweijian" />
+                        <TextBlock Margin="0,1.5,6,0" Text="Tincyclopedia" />
                         <TextBlock Margin="0,1.5,6,0" Text="Kecdl_Chacha" />
                         <TextBlock Margin="0,1.5,6,0" Text="CrimsonCloud" />
                         <TextBlock Margin="0,1.5,6,0" Text="minecraftac11" />

+ 29 - 21
Plain Craft Launcher 2/Pages/PageSetup/Settings.vb

@@ -211,7 +211,6 @@
         ''' 该设置的值的实际类型。
         ''' </summary>
         Public Type As Type
-
         Public Sub New(Key As String, Value As Object, Optional Source As Sources = Sources.Normal, Optional Encrypted As Boolean = False, Optional OnChanged As Action(Of Object) = Nothing)
             Try
                 Me.Key = Key
@@ -224,22 +223,13 @@
                 Log(ex, "初始化设置项失败", LogLevel.Feedback) '#5095 的 fallback
             End Try
         End Sub
-    End Class
 
-    ''' <summary>
-    ''' 改变某个设置项的值。
-    ''' </summary>
-    Public Shared Sub [Set](Key As String, Value As Object, Optional Instance As McInstance = Nothing)
-        Dim Entry As Setting = Nothing
-        If Not Entries.TryGetValue(Key, Entry) Then Throw New KeyNotFoundException("未找到设置项:" & Key)
-        Try
-            '判断是否一致
-            If Entry.Value Is Nothing Then [Get](Key, Instance)
-            Value = CTypeDynamic(Value, Entry.Type)
-            If Entry.Value = Value Then Return
-            '设置新值
-            Entry.Value = Value
-            If Entry.Encrypted Then
+        ''' <summary>
+        ''' 立即将当前的 Value 写入对应的注册表或文件。
+        ''' </summary>
+        Public Sub Save(Optional Instance As McInstance = Nothing)
+            Dim Value As String = Me.Value
+            If Encrypted Then
                 Try
                     If Value Is Nothing Then Value = ""
                     Value = SecretEncrypt(Value, "PCL" & Identify)
@@ -247,17 +237,34 @@
                     Log(ex, "加密设置失败:" & Key, LogLevel.Developer)
                 End Try
             End If
-            Select Case Entry.Source
+            Select Case Source
                 Case Sources.Normal
                     WriteIni("Setup", Key, Value)
                 Case Sources.Registry
                     WriteReg(Key, Value)
                 Case Sources.Instance
-                    If Instance Is Nothing Then Throw New Exception($"更改版本设置 {Key} 时未提供目标版本")
+                    If Instance Is Nothing Then Throw New Exception($"保存版本独立设置 {Key} 时未提供目标版本")
                     WriteIni(Instance.PathVersion & "PCL\Setup.ini", Key, Value)
             End Select
-            '触发改变事件(必须在写入之后,以保证 VersionServerLogin 之类的在事件中读取到的是最新的值)
-            If Entry.OnChanged IsNot Nothing Then Entry.OnChanged.Invoke(Value)
+        End Sub
+    End Class
+
+    ''' <summary>
+    ''' 改变某个设置项的值。
+    ''' </summary>
+    Public Shared Sub [Set](Key As String, Value As Object, Optional Instance As McInstance = Nothing)
+        Dim Entry As Setting = Nothing
+        If Not Entries.TryGetValue(Key, Entry) Then Throw New KeyNotFoundException("未找到设置项:" & Key)
+        Try
+            '判断是否一致
+            If Entry.Value Is Nothing Then [Get](Key, Instance)
+            Value = CTypeDynamic(Value, Entry.Type)
+            If Entry.Value = Value Then Return
+            '设置新值
+            Entry.Value = Value
+            Entry.Save(Instance)
+            '触发改变事件(必须在保存之后,以保证 VersionServerLogin 之类的在事件中读取到的是最新的值)
+            If Entry.OnChanged IsNot Nothing Then Entry.OnChanged.Invoke(Entry.Value)
         Catch ex As Exception
             Log(ex, "设置设置项时出错(" & Key & ", " & Value & ")", LogLevel.Feedback)
         End Try
@@ -315,7 +322,8 @@
                     Catch ex As Exception
                         Log(ex, "解密设置失败:" & Key, LogLevel.Developer)
                         GotValue = Entry.DefaultValue
-                        [Set](Key, Entry.DefaultValue)
+                        Entry.Value = Entry.DefaultValue
+                        Entry.Save(Instance)
                     End Try
                 End If
             End If