diff --git a/.github/ISSUE_TEMPLATE/bug1.yml b/.github/ISSUE_TEMPLATE/bug1.yml index 68cd2ba..af235f1 100644 --- a/.github/ISSUE_TEMPLATE/bug1.yml +++ b/.github/ISSUE_TEMPLATE/bug1.yml @@ -12,7 +12,7 @@ body: required: false - label: "**我已尝试使用 HMCL 启动,HMCL 没有出现问题。** 如果 HMCL 也无法启动就不是 PCL 导致的问题,请 **不要** 提交反馈。[下载 HMCL](https://hmcl.huangyuhui.net/download)" required: true - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" required: true - type: textarea id: "yml-2" diff --git a/.github/ISSUE_TEMPLATE/bug2.yml b/.github/ISSUE_TEMPLATE/bug2.yml index de636f0..0490f61 100644 --- a/.github/ISSUE_TEMPLATE/bug2.yml +++ b/.github/ISSUE_TEMPLATE/bug2.yml @@ -12,7 +12,7 @@ body: required: false - label: "我知晓大多数此类问题都是网络环境不佳导致的,但我确实认为我的问题可能是 PCL 导致的,和网络环境无关。" required: true - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" required: true - type: textarea id: "yml-2" diff --git a/.github/ISSUE_TEMPLATE/bug9.yml b/.github/ISSUE_TEMPLATE/bug9.yml index be20097..73980a7 100644 --- a/.github/ISSUE_TEMPLATE/bug9.yml +++ b/.github/ISSUE_TEMPLATE/bug9.yml @@ -8,7 +8,7 @@ body: label: "检查项" description: "请逐个检查下列项目,并勾选确认。" options: - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一 Bug 未被提交过。" required: true - type: textarea id: "yml-2" diff --git a/.github/ISSUE_TEMPLATE/ch.yml b/.github/ISSUE_TEMPLATE/ch.yml index 0ddd693..fc3ee5d 100644 --- a/.github/ISSUE_TEMPLATE/ch.yml +++ b/.github/ISSUE_TEMPLATE/ch.yml @@ -8,9 +8,9 @@ body: label: "检查项" description: "请逐个检查下列项目,并勾选确认。" options: - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一建议未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一建议未被提交过。" required: true - - label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。" + - label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一建议未在投票列表中。" required: true - type: textarea id: "yml-2" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 082755e..3a6eed8 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,14 +1,14 @@ blank_issues_enabled: false contact_links: - name: 主页预设反馈 - url: https://github.com/Hex-Dragon/PCL2/discussions/categories/自定义主页 + url: https://github.com/Meloong-Git/PCL/discussions/categories/自定义主页 about: 提交与预设的主页(设置 → 个性化 → 主页预设)中的具体内容相关的反馈 - name: 帮助文档反馈 url: https://github.com/LTCatt/PCL2Help/issues about: 提交与 PCL 帮助文档(更多 → 帮助)中的具体内容相关的反馈 - name: 提问 - url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E6%8F%90%E9%97%AE + url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE about: 我想问一些 PCL 相关的问题…… - name: 讨论 - url: https://github.com/Hex-Dragon/PCL2/discussions/new?category=%E8%AE%A8%E8%AE%BA + url: https://github.com/Meloong-Git/PCL/discussions/new?category=%E8%AE%A8%E8%AE%BA about: 我想讨论一些 PCL 相关的事情…… diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index e27452b..f434a0a 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -8,9 +8,9 @@ body: label: "检查项" description: "请逐个检查下列项目,并勾选确认。" options: - - label: "我已在 [Issues 页面](https://github.com/Hex-Dragon/PCL2/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Hex-Dragon/PCL2/discussions/1930) 中搜索,确认了这一提案未被提交过。" + - label: "我已在 [Issues 页面](https://github.com/Meloong-Git/PCL/issues?q=is%3Aissue+) 和 [常见&难检反馈及问题列表](https://github.com/Meloong-Git/PCL/discussions/1930) 中搜索,确认了这一提案未被提交过。" required: true - - label: "我已查看 [功能投票页面](https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。" + - label: "我已查看 [功能投票页面](https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8/),确认了这一提案未在投票列表中。" required: true - label: "我知晓还没做的新功能真的太多了,忙不过来,所以新功能提案几乎不会被处理,也不建议再提交新功能提案 qwq……" required: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fbebe6a..b6de4af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,7 +38,6 @@ jobs: - name: Update Help run: | - $workpath = [System.Environment]::CurrentDirectory Set-Location .. git clone https://github.com/LTCatt/PCL2Help.git -b master --single-branch --depth 1 Set-Location PCL2Help @@ -52,8 +51,8 @@ jobs: - name: Replace run: | - (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public Const OAuthClientId As String = ""', 'Public Const OAuthClientId As String = "${{ secrets.CLIENT_ID }}"' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb" - (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public Const CurseForgeAPIKey As String = ""', 'Public Const CurseForgeAPIKey As String = "${{ secrets.CURSEFORGE_API_KEY }}"' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb" + (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "")', 'Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "${{ secrets.CLIENT_ID }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb" + (gc "Plain Craft Launcher 2\Modules\ModSecret.vb") -replace 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "")', 'Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "${{ secrets.CURSEFORGE_API_KEY }}")' | Out-File "Plain Craft Launcher 2\Modules\ModSecret.vb" (gc "Plain Craft Launcher 2\Modules\Base\ModBase.vb") -replace 'Public Const CommitHash As String = ""', 'Public Const CommitHash As String = "${{ github.sha }}"' | Out-File "Plain Craft Launcher 2\Modules\Base\ModBase.vb" - name: Build diff --git a/LICENCE b/LICENCE index ba3282f..c09c900 100644 --- a/LICENCE +++ b/LICENCE @@ -1,26 +1,40 @@ -本文件仅作为指引您合理使用本存储库内容的指南,不是法律文件或协议。 +一、声明 -一、承诺与声明。 +本文件仅作为指南,不是法律文件或协议。但开发者在此承诺,在您遵守本指南且善意、合理使用的情况下,不会追究您的责任。 +法律上,开发者仍保留所有权利(All Rights Reserved),若情况恶劣,仍可能将诉讼作为最终手段。 -  开发者承诺,在您遵守本指南且善意使用的情况下,不会追究您的责任,但最终裁量权仍由开发者保留。 -  法律上,开发者仍保留所有权利(All Rights Reserved),且可能随时修改本指南,本指南并不是永久性保证。 -二、少量使用情形。 -  如果您仅合理使用本存储库中的少量内容,或是参考实现思路,您必须: -  1、给出适当的署名,且提供指向本存储库的链接。GitHub 上的 Fork 可视为满足此条。 -  2、不得以任何方式暗示开发者为您或您的使用作担保。 +二、轻度使用 -三、大量使用情形。 +若您仅参考了本存储库中极小部分的内容,或您在制作与 PCL 的实质功能不存在任何重叠的软件,则为轻度使用。 +其常见情形例如: +· 使用或参考存储库中极小部分的内容,来开发启动器或其他软件。 +· 虽参考了较大量的内容,但制作的软件与 PCL 的实质功能不存在任何重叠。 -  如果您基于该存储库创作衍生作品,或合理使用了本存储库中的较大量内容,您必须: -  1、给出适当的署名,且提供指向本存储库的链接。GitHub 上的 Fork 可视为满足此条。 -  2、不得以任何方式暗示开发者为您或您的使用作担保。 -  3、给出适当的说明,以明确表示该衍生作品是非官方内容。 -  4、使用本指南文件作为协议,不得附加额外条款。 -  5、该衍生作品的名称必须以 “PCL” 或 “Plain Craft Launcher” 开头,并在其后添加后缀。例如 “PCL 整合包优化版”、“PCL PR Collection”。 -  6、该衍生作品不得包含 PCL 的赞助解锁功能,或与其相近的功能或内容。例如将配色修改为蓝色色系以外的颜色。 -  7、以善意目的进行合理使用,不要用 PCL 来干坏事、恰烂钱、整烂活。开发者可能会将撤销所有授权作为最终手段。 +若属于轻度使用,您需要: +1、以适当的形式给出署名。 +2、不得将您的内容与 PCL 混淆,或暗示您的内容与 PCL 有关。 +  例如:您的内容不得使用 Planting Craft Launcher、PCL 手机版 等会造成混淆或暗示这与 PCL 有关的名称。 + + + +三、重度使用 + +若不符合轻度使用的情形,即为重度使用。 +其常见情形例如: +· 基于本存储库进行修改。 +· 参考存储库中较大量的内容,以制作或重新实现 PCL 的部分实质功能(例如启动或下载 Java 版 Minecraft,下载或管理 Mod 等)。 + +若属于重度使用,您需要: +1、明确表示您的内容是第三方基于 PCL 独立进行二次创作的产物。 +2、您的内容的名称必须以 Plain Craft Launcher (PCL) 开头,并在其后添加足以表现其由第三方修改的后缀。 +  允许的示例:PCL 某服务器魔改版、PCL PR Collection、PCL (XX Bug Fix)、PCL XXX修改版。 +  禁止的示例:PCL 某服务器特供版(暗示由官方提供)、PCL 3、PCL 手机版、PCL++、PCL Pro(这像是官方的后续作品)。 +3、在关于页面的首位给出龙腾猫跃的署名及对应的赞助链接,公开源代码,且继续使用本文件作为指南(可参考 CC BY-SC 协议)。 +4、软件若有 Minecraft 启动功能,则应实现与 McLaunchPrecheck 函数一致的 Minecraft 正版购买弹窗与赞助弹窗。 +5、软件在 Windows 系统上运行时,不应有与 PCL 的赞助解锁功能类似的功能或表现。 +  例如:界面的主要色相必须为蓝色(即使没有修改功能,改为其他色相也和需要赞助解锁的主题功能类似)。 ———————————— @@ -29,5 +43,7 @@ 因为没有法律专业人士检查,所以这不敢作为正式的法律协议,如果有专业人士愿意帮忙改一改我也很乐意…… 这些规则主要是为了阻止恶意的使用和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧! -如果你只是想参考一下,或者用了一小部分代码,署个名就行啦。 -如果你整了个魔改版,或者用了一大堆代码,请仔细参考上面的第三条。 +如果你只是参考了一小段代码,署个名就行啦,不用担心。 +如果对具体细节有疑问,欢迎在 https://github.com/Meloong-Git/PCL/discussions/new?category=%E6%8F%90%E9%97%AE 发帖询问! + +多谢大家啦! diff --git a/Plain Craft Launcher 2/App.config b/Plain Craft Launcher 2/App.config index 8d134eb..0e1a473 100644 --- a/Plain Craft Launcher 2/App.config +++ b/Plain Craft Launcher 2/App.config @@ -30,6 +30,10 @@ + + + + diff --git a/Plain Craft Launcher 2/Application.xaml b/Plain Craft Launcher 2/Application.xaml index adbf7c0..3d5488f 100644 --- a/Plain Craft Launcher 2/Application.xaml +++ b/Plain Craft Launcher 2/Application.xaml @@ -1,6 +1,7 @@  - - - - - - @@ -66,7 +61,7 @@ #01eaf2fe - + - + diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb index beec27a..db86a37 100644 --- a/Plain Craft Launcher 2/Application.xaml.vb +++ b/Plain Craft Launcher 2/Application.xaml.vb @@ -19,6 +19,9 @@ Public Class Application '开始 Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup Try + '动态 DLL 调用(必须尽量在前面,否则模块加载 CacheCow 等 DLL 就可能导致崩溃) + AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolve + '开始 SecretOnApplicationStart() '检查参数调用 If e.Args.Length > 0 Then @@ -67,9 +70,9 @@ Public Class Application If Not CheckPermission(PathTemp) Then Throw New Exception("PCL 没有对 " & PathTemp & " 的访问权限") Catch ex As Exception If PathTemp = IO.Path.GetTempPath() & "PCL\" Then - MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用") + MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDetail(), "缓存文件夹不可用") Else - MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & "错误原因:" & GetExceptionDetail(ex), "缓存文件夹不可用") + MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDetail(), "缓存文件夹不可用") Setup.Set("SystemSystemCache", "") PathTemp = IO.Path.GetTempPath() & "PCL\" End If @@ -104,18 +107,27 @@ WaitRetry: ToolTipService.PlacementProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(Primitives.PlacementMode.Bottom)) ToolTipService.HorizontalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(8.0)) ToolTipService.VerticalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(4.0)) + '设置网络配置默认值 + ServicePointManager.Expect100Continue = False + ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12 + ServicePointManager.DefaultConnectionLimit = 10000 + ServicePointManager.UseNagleAlgorithm = False + ServicePointManager.EnableDnsRoundRobin = True + ServicePointManager.ReusePort = True '设置初始窗口 If Setup.Get("UiLauncherLogo") Then FrmStart = New SplashScreen("Images\icon.ico") FrmStart.Show(False, True) End If - '动态 DLL 调用 - AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolve '日志初始化 LogStart() '添加日志 Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})") +#If RELEASE Then + Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",正式版", "")}") +#Else Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}") +#End If Log($"[Start] 程序路径:{PathWithName}") Log($"[Start] 系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})") Log($"[Start] 管理员权限:{IsAdmin()}") @@ -131,10 +143,7 @@ WaitRetry: Setup.Load("SystemDebugAnim") Setup.Load("ToolDownloadThread") Setup.Load("ToolDownloadCert") - '网络配置初始化 - ServicePointManager.Expect100Continue = True - ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12 - ServicePointManager.DefaultConnectionLimit = 1024 + Setup.Load("ToolDownloadSpeed") '计时 Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms") ApplicationStartTick = GetTimeTick() @@ -149,7 +158,7 @@ WaitRetry: FilePath = PathWithName Catch End Try - MsgBox(GetExceptionDetail(ex, True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误") + MsgBox(ex.GetDetail(True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误") FormMain.EndProgramForce(ProcessReturnValues.Exception) End Try End Sub @@ -160,79 +169,55 @@ WaitRetry: End Sub '异常 - Private IsCritErrored As Boolean = False Private Sub Application_DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException On Error Resume Next - e.Handled = True - If IsProgramEnded Then Exit Sub - If IsCritErrored Then - '在汇报错误后继续引发错误,知道这次压不住了 - FormMain.EndProgramForce(ProcessReturnValues.Exception) - Exit Sub + '触发页面的 Dispatcher + If FrmMain?.PageLeft IsNot Nothing AndAlso TypeOf FrmMain.PageLeft Is IDispatcherUnhandledException Then + CType(FrmMain.PageLeft, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e) + If e.Handled Then Return End If - IsCritErrored = True - Dim ExceptionString As String = GetExceptionDetail(e.Exception, True) - If ExceptionString.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse - ExceptionString.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse - ExceptionString.Contains(".NET Framework") OrElse ' “自动错误判断” 的结果分析 - ExceptionString.Contains("未能加载文件或程序集") Then + If FrmMain?.PageRight IsNot Nothing AndAlso TypeOf FrmMain.PageRight Is IDispatcherUnhandledException Then + CType(FrmMain.PageRight, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e) + If e.Handled Then Return + End If + '正常处理 + e.Handled = True + If IsProgramEnded Then Return + FeedbackInfo() + Dim Detail As String = e.Exception.GetDetail(True) + If Detail.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse Detail.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse Detail.Contains("未能加载文件或程序集") OrElse + Detail.Contains(".NET Framework") Then ' “自动错误判断” 的结果分析 OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net462-offline-installer") - MsgBox("你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.6.2!", MsgBoxStyle.Information, "运行环境错误") - FormMain.EndProgramForce(ProcessReturnValues.Cancel) + Log(e.Exception, "你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.6.2!" & vbCrLf & "若无法安装,可在卸载高版本的 .NET Framework 后再试。", LogLevel.Critical, "运行环境错误") Else - FeedbackInfo() - Log(e.Exception, "程序出现未知错误", LogLevel.Assert, "锟斤拷烫烫烫") + Log(e.Exception, "程序出现未知错误", LogLevel.Critical, "锟斤拷烫烫烫") End If End Sub '动态 DLL 调用 - Private Shared AssemblyNAudio As Assembly - Private Shared AssemblyJson As Assembly - Private Shared AssemblyDialog As Assembly - Private Shared AssemblyImazenWebp As Assembly - Private Shared ReadOnly AssemblyNAudioLock As New Object - Private Shared ReadOnly AssemblyJsonLock As New Object - Private Shared ReadOnly AssemblyDialogLock As New Object - Private Shared ReadOnly AssemblyImazenWebpLock As New Object Private Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean - Public Shared Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly - If args.Name.StartsWithF("NAudio") Then - SyncLock AssemblyNAudioLock - If AssemblyNAudio Is Nothing Then - Log("[Start] 加载 DLL:NAudio") - AssemblyNAudio = Assembly.Load(GetResources("NAudio")) - End If - Return AssemblyNAudio - End SyncLock - ElseIf args.Name.StartsWithF("Newtonsoft.Json") Then - SyncLock AssemblyJsonLock - If AssemblyJson Is Nothing Then - Log("[Start] 加载 DLL:Json") - AssemblyJson = Assembly.Load(GetResources("Json")) - End If - Return AssemblyJson - End SyncLock - ElseIf args.Name.StartsWithF("Ookii.Dialogs.Wpf") Then - SyncLock AssemblyDialogLock - If AssemblyDialog Is Nothing Then - Log("[Start] 加载 DLL:Dialogs") - AssemblyDialog = Assembly.Load(GetResources("Dialogs")) - End If - Return AssemblyDialog - End SyncLock - ElseIf args.Name.StartsWithF("Imazen.WebP") Then - SyncLock AssemblyImazenWebpLock - If AssemblyImazenWebp Is Nothing Then - Log("[Start] 加载 DLL:Imazen.WebP") - AssemblyImazenWebp = Assembly.Load(GetResources("Imazen_WebP")) - SetDllDirectory(PathPure.TrimEnd("\")) + Public Shared Function AssemblyResolve(sender As Object, Args As ResolveEventArgs) As Assembly + '缓存 + Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "System.Net.Http.Formatting"} + Static Locks As New Dictionary(Of String, Object)(StringComparer.Ordinal) + Static LoadedAssembly As New Dictionary(Of String, Assembly)(StringComparer.Ordinal) + '查找对应的 DLL + Dim Prefix As String = Prefixes.FirstOrDefault(Function(p) Args.Name.StartsWithF(p)) + If Prefix Is Nothing Then Return Nothing + '加载 DLL + If Not Locks.ContainsKey(Prefix) Then Locks(Prefix) = New Object() + SyncLock Locks(Prefix) + If Not LoadedAssembly.ContainsKey(Prefix) Then + Log($"[Start] 加载 DLL:{Prefix}") + LoadedAssembly(Prefix) = Assembly.Load(GetResources(Prefix)) + 'WebP 特判 + If Prefix = "Imazen.WebP" Then + SetDllDirectory(PathPure.TrimEnd("\"c)) WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64")) End If - Return AssemblyImazenWebp - End SyncLock - Else - Return Nothing - End If + End If + Return LoadedAssembly(Prefix) + End SyncLock End Function '切换窗口 diff --git a/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb b/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb new file mode 100644 index 0000000..b145b27 --- /dev/null +++ b/Plain Craft Launcher 2/Controls/Behaviors/ClipboardInterceptor.vb @@ -0,0 +1,187 @@ +' Author: uye (owner of the MaaAssistantArknights team) +' Original Source: MaaAssistantArknights project - https://github.com/MaaAssistantArknights/MaaAssistantArknights +' License: Apache License 2.0 (this file only) +' +' This file is originally developed for MaaAssistantArknights and licensed under AGPL v3.0. +' As the original author and copyright holder, +' I hereby re-license this file under the Apache License 2.0 for use in the Plain Craft Launcher project. +' +' This file is contributed under the spirit of good faith and open cooperation. +' It does not implement any core launcher functions of PCL. +' +' Description: +' Provides a WPF clipboard handling fix to avoid OpenClipboard exceptions +' in TextBox, RichTextBox, and DataGrid under Windows focus issues or clipboard hooks. +' +' Date: 2025-07-03 + +Imports System.Windows +Imports System.Windows.Controls +Imports System.Windows.Input +Imports System.Windows.Documents +Imports System.Linq + +Namespace Controls.Behaviors + Public NotInheritable Class ClipboardInterceptor + Private Sub New() + End Sub + + Public Shared ReadOnly EnableSafeClipboardProperty As DependencyProperty = + DependencyProperty.RegisterAttached("EnableSafeClipboard", GetType(Boolean), GetType(ClipboardInterceptor), + New PropertyMetadata(False, AddressOf OnEnableSafeClipboardChanged)) + + Public Shared Sub SetEnableSafeClipboard(element As DependencyObject, value As Boolean) + element.SetValue(EnableSafeClipboardProperty, value) + End Sub + + Public Shared Function GetEnableSafeClipboard(element As DependencyObject) As Boolean + Return CBool(element.GetValue(EnableSafeClipboardProperty)) + End Function + + Private Shared Sub OnEnableSafeClipboardChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) + If TypeOf d Is TextBox AndAlso CBool(e.NewValue) Then + AddCommandBindingsToTextBox(DirectCast(d, TextBox)) + ElseIf TypeOf d Is RichTextBox AndAlso CBool(e.NewValue) Then + AddCommandBindingsToRichTextBox(DirectCast(d, RichTextBox)) + ElseIf TypeOf d Is DataGrid AndAlso CBool(e.NewValue) Then + AddCommandBindingsToDataGrid(DirectCast(d, DataGrid)) + End If + End Sub + + Private Shared Sub AddCommandBindingsToTextBox(tb As TextBox) + tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyTextBox)) + tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutTextBox)) + tb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteTextBox)) + End Sub + + Private Shared Sub AddCommandBindingsToRichTextBox(rtb As RichTextBox) + rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyRichTextBox)) + rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Cut, AddressOf OnCutRichTextBox)) + rtb.CommandBindings.Add(New CommandBinding(ApplicationCommands.Paste, AddressOf OnPasteRichTextBox)) + End Sub + + Private Shared Sub AddCommandBindingsToDataGrid(dg As DataGrid) + dg.CommandBindings.Add(New CommandBinding(ApplicationCommands.Copy, AddressOf OnCopyDataGrid)) + End Sub + + Private Shared Sub OnCopyTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim tb = TryCast(sender, TextBox) + If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(tb.SelectedText, True) + Catch + End Try + + e.Handled = True + End Sub + + Private Shared Sub OnCutTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim tb = TryCast(sender, TextBox) + If tb Is Nothing OrElse tb.SelectionLength <= 0 Then Return + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(tb.SelectedText, True) + Catch + End Try + + tb.SelectedText = String.Empty + e.Handled = True + End Sub + + Private Shared Sub OnPasteTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim tb = TryCast(sender, TextBox) + If tb Is Nothing Then Return + + If Forms.Clipboard.ContainsText() Then + Dim pasteText = Forms.Clipboard.GetText() + Dim start = tb.SelectionStart + + tb.SelectedText = pasteText + tb.CaretIndex = start + pasteText.Length + tb.SelectionLength = 0 + End If + + e.Handled = True + End Sub + + Private Shared Sub OnCopyRichTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim rtb = TryCast(sender, RichTextBox) + If rtb Is Nothing Then Return + + Dim textRange = New TextRange(rtb.Selection.Start, rtb.Selection.End) + If String.IsNullOrEmpty(textRange.Text) Then Return + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(textRange.Text, True) + Catch + End Try + + e.Handled = True + End Sub + + Private Shared Sub OnCutRichTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim rtb = TryCast(sender, RichTextBox) + If rtb Is Nothing Then Return + + Dim selection = New TextRange(rtb.Selection.Start, rtb.Selection.End) + If String.IsNullOrEmpty(selection.Text) Then Return + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(selection.Text, True) + Catch + End Try + + selection.Text = String.Empty + e.Handled = True + End Sub + + Private Shared Sub OnPasteRichTextBox(sender As Object, e As ExecutedRoutedEventArgs) + Dim rtb = TryCast(sender, RichTextBox) + If rtb Is Nothing Then Return + + If Not Forms.Clipboard.ContainsText() Then Return + + Dim pasteText = Forms.Clipboard.GetText() + Dim selection = rtb.Selection + + selection.Text = pasteText + + Dim caretPos = selection.End + rtb.CaretPosition = caretPos + rtb.Selection.Select(caretPos, caretPos) + + e.Handled = True + End Sub + + Private Shared Sub OnCopyDataGrid(sender As Object, e As ExecutedRoutedEventArgs) + Dim dg = TryCast(sender, DataGrid) + If dg Is Nothing OrElse dg.SelectedCells Is Nothing OrElse dg.SelectedCells.Count = 0 Then Return + + Dim sb = New System.Text.StringBuilder() + Dim rowGroups = dg.SelectedCells.GroupBy(Function(c) c.Item) + + For Each row In rowGroups + Dim rowText = String.Join(vbTab, row.Select(Function(cell) + Dim tb = TryCast(cell.Column.GetCellContent(cell.Item), TextBlock) + Return If(tb IsNot Nothing, tb.Text, "") + End Function)) + sb.AppendLine(rowText) + Next + + Dim sbStr = sb.ToString().TrimEnd(ControlChars.Cr, ControlChars.Lf) + + Try + Forms.Clipboard.Clear() + Forms.Clipboard.SetDataObject(sbStr, True) + Catch + End Try + + e.Handled = True + End Sub + End Class +End Namespace diff --git a/Plain Craft Launcher 2/Controls/Interfaces.vb b/Plain Craft Launcher 2/Controls/Interfaces.vb new file mode 100644 index 0000000..19a2f34 --- /dev/null +++ b/Plain Craft Launcher 2/Controls/Interfaces.vb @@ -0,0 +1,15 @@ +Imports System.Windows.Threading + +''' +''' 指示页面可以通过按下 F5 或以其他方式刷新。 +''' +Public Interface IRefreshable + Sub Refresh() +End Interface + +''' +''' 指示页面可以处理未捕获的异常。 +''' +Public Interface IDispatcherUnhandledException + Sub DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) +End Interface diff --git a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb index 1d5d240..2eeaa05 100644 --- a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb @@ -142,7 +142,7 @@ Public Class MyButton '实现自定义事件 Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp - If Not IsMouseDown Then Exit Sub + If Not IsMouseDown Then Return Log("[Control] 按下按钮:" & Text) RaiseEvent Click(sender, e) If Not String.IsNullOrEmpty(Tag) Then @@ -197,7 +197,7 @@ Public Class MyButton AniStart(AaColor(PanFore, BackgroundProperty, If(_ColorType = ColorState.Red, "ColorBrushRedBack", "ColorBrush7"), AnimationColorIn), "MyButton Background " & Uuid) End Sub Private Sub Button_MouseUp() Handles Me.MouseLeftButtonUp - If Not IsMouseDown Then Exit Sub + If Not IsMouseDown Then Return IsMouseDown = False AniStart({ AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 300, 10, New AniEaseOutFluent(AniEasePower.Middle)) @@ -205,7 +205,7 @@ Public Class MyButton End Sub Private Sub Button_MouseLeave() Handles Me.MouseLeave AniStart(AaColor(PanFore, BackgroundProperty, "ColorBrushHalfWhite", AnimationColorOut), "MyButton Background " & Uuid) - If Not IsMouseDown Then Exit Sub + If Not IsMouseDown Then Return IsMouseDown = False AniStart(AaScaleTransform(PanFore, 1 - CType(PanFore.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)), "MyButton Scale " & Uuid) End Sub diff --git a/Plain Craft Launcher 2/Controls/MyCard.vb b/Plain Craft Launcher 2/Controls/MyCard.vb index 1b1a5b5..0274be0 100644 --- a/Plain Craft Launcher 2/Controls/MyCard.vb +++ b/Plain Craft Launcher 2/Controls/MyCard.vb @@ -3,7 +3,7 @@ '控件 Inherits Grid Private ReadOnly MainGrid As Grid - Public ReadOnly Property MainChrome As SystemDropShadowChrome + Public ReadOnly Property MainChrome As MyDropShadow Private ReadOnly MainBorder As Border Public Property BorderChild As UIElement Get @@ -63,17 +63,18 @@ 'UI 建立 Public Sub New() - MainChrome = New SystemDropShadowChrome With {.Margin = New Thickness(-9.5, -9, 0.5, -0.5), .Opacity = 0.1, .CornerRadius = New CornerRadius(6)} - MainChrome.SetResourceReference(SystemDropShadowChrome.ColorProperty, "ColorObject1") + MainChrome = New MyDropShadow With { + .Margin = New Thickness(-3, -3, -3, -3 - GetWPFSize(1)), .ShadowRadius = 3, .Opacity = DropShadowIdleOpacity, .CornerRadius = New CornerRadius(5)} + MainChrome.SetResourceReference(MyDropShadow.ColorProperty, "ColorObject1") Children.Insert(0, MainChrome) - MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(205, 255, 255, 255)), .CornerRadius = New CornerRadius(6), .IsHitTestVisible = False} + MainBorder = New Border With {.Background = New SolidColorBrush(Color.FromArgb(245, 255, 255, 255)), .CornerRadius = New CornerRadius(5), .IsHitTestVisible = False} Children.Insert(1, MainBorder) MainGrid = New Grid Children.Add(MainGrid) End Sub Private IsLoad As Boolean = False Private Sub Init() Handles Me.Loaded - If IsLoad Then Exit Sub + If IsLoad Then Return IsLoad = True '初次加载限定 If MainTextBlock Is Nothing Then @@ -89,7 +90,7 @@ MainGrid.Children.Add(MainSwap) End If '改变默认的折叠 - If IsSwaped AndAlso SwapControl IsNot Nothing Then + If IsSwapped AndAlso SwapControl IsNot Nothing Then MainSwap.RenderTransform = New RotateTransform(If(SwapLogoRight, 270, 0)) '取消由于高度变化被迫触发的高度动画 Dim RawUseAnimation As Boolean = UseAnimation @@ -108,7 +109,7 @@ '这一部分的代码是好几年前留下的究极屎坑,当时还不知道该咋正确调用这种方法,就写了这么一坨屎 '但是现在……反正勉强能用……懒得改了就这样吧.jpg '别骂了别骂了.jpg - If IsNothing(Stack.Tag) Then Exit Sub + If IsNothing(Stack.Tag) Then Return '排序 Select Case Type Case 3 @@ -122,14 +123,6 @@ End Select '控件转换 Select Case Type - Case 5 - Dim LoadingPickaxe As New MyLoading With {.Text = "正在获取版本列表", .Margin = New Thickness(5)} - Dim Loader = New LoaderTask(Of String, List(Of DlForgeVersionEntry))("DlForgeVersion Main", AddressOf DlForgeVersionMain) - LoadingPickaxe.State = Loader - Loader.Start(Stack.Tag) - AddHandler LoadingPickaxe.StateChanged, AddressOf FrmDownloadForge.Forge_StateChanged - AddHandler LoadingPickaxe.Click, AddressOf FrmDownloadForge.Forge_Click - Stack.Children.Add(LoadingPickaxe) Case 6 ForgeDownloadListItemPreload(Stack, Stack.Tag, AddressOf ForgeSave_Click, True) Case 8 @@ -144,9 +137,6 @@ Stack.Children.Add(McDownloadListItem(Data, AddressOf McDownloadMenuSave, True)) Case 3 Stack.Children.Add(OptiFineDownloadListItem(Data, AddressOf OptiFineSave_Click, True)) - Case 4 - Stack.Children.Add(LiteLoaderDownloadListItem(Data, AddressOf FrmDownloadLiteLoader.DownloadStart, False)) - Case 5 Case 6 Stack.Children.Add(ForgeDownloadListItem(Data, AddressOf ForgeSave_Click, True)) Case 7 @@ -186,29 +176,29 @@ Stack.Tag = Nothing End Sub - '事件 + '动画 + Private Const DropShadowIdleOpacity As Double = 0.07 + Private Const DropShadowHoverOpacity As Double = 0.4 Public Property HasMouseAnimation As Boolean = True Private Sub MyCard_MouseEnter(sender As Object, e As MouseEventArgs) Handles Me.MouseEnter - If Not HasMouseAnimation Then Exit Sub + If Not HasMouseAnimation Then Return Dim AniList As New List(Of AniData) - If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 150)) - If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 150)) + If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush2", 90)) + If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush2", 90)) AniList.AddRange({ - AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject2", 180), - AaColor(MainBorder, Border.BackgroundProperty, New MyColor(230, 255, 255, 255) - MainBorder.Background, 180), - AaOpacity(MainChrome, 0.3 - MainChrome.Opacity, 180) + AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject4", 90), + AaOpacity(MainChrome, DropShadowHoverOpacity - MainChrome.Opacity, 90) }) AniStart(AniList, "MyCard Mouse " & Uuid) End Sub Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave - If Not HasMouseAnimation Then Exit Sub + If Not HasMouseAnimation Then Return Dim AniList As New List(Of AniData) - If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 250)) - If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 250)) + If Not IsNothing(MainTextBlock) Then AniList.Add(AaColor(MainTextBlock, TextBlock.ForegroundProperty, "ColorBrush1", 90)) + If Not IsNothing(MainSwap) Then AniList.Add(AaColor(MainSwap, Shapes.Path.FillProperty, "ColorBrush1", 90)) AniList.AddRange({ - AaColor(MainChrome, SystemDropShadowChrome.ColorProperty, "ColorObject1", 300), - AaColor(MainBorder, Border.BackgroundProperty, New MyColor(205, 255, 255, 255) - MainBorder.Background, 300), - AaOpacity(MainChrome, 0.1 - MainChrome.Opacity, 300) + AaColor(MainChrome, MyDropShadow.ColorProperty, "ColorObject1", 90), + AaOpacity(MainChrome, DropShadowIdleOpacity - MainChrome.Opacity, 90) }) AniStart(AniList, "MyCard Mouse " & Uuid) End Sub @@ -222,46 +212,64 @@ Private IsHeightAnimating As Boolean = False Private ActualUsedHeight As Double '回滚实际高度(例如 NaN) Private Sub MySizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged - If Not UseAnimation Then Exit Sub - Dim DeltaHeight As Double = If(IsSwaped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height + If Not UseAnimation Then Return + Dim DeltaHeight As Double = If(IsSwapped, SwapedHeight, e.NewSize.Height) - e.PreviousSize.Height '卡片的进入时动画已被页面通用切换动画替代 - If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Exit Sub + If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse ActualHeight = 0 Then Return StartHeightAnimation(DeltaHeight, e.PreviousSize.Height, False) End Sub - Private Sub StartHeightAnimation(DeltaHeight As Double, PreviousHeight As Double, IsLoadAnimation As Boolean) - If IsHeightAnimating OrElse FrmMain Is Nothing Then Exit Sub '避免 XAML 设计器出错 + Private Sub StartHeightAnimation(Delta As Double, PreviousHeight As Double, IsLoadAnimation As Boolean) + If IsHeightAnimating OrElse FrmMain Is Nothing Then Return '避免 XAML 设计器出错 Dim AnimList As New List(Of AniData) - If DeltaHeight > 10 OrElse (DeltaHeight < -10 AndAlso Not IsNothing(SwapControl)) Then '如果不是需要折叠的卡片,高度减小时的弹跳会吞掉按钮下边框 - '高度增加较大,使用弹起动画 - Dim Delta As Double = MathClamp(Math.Abs(DeltaHeight) * 0.05, 3, 10) * Math.Sign(DeltaHeight) - AnimList.AddRange({ - AaHeight(Me, DeltaHeight + Delta, 300, If(IsLoadAnimation, 30, 0), If(DeltaHeight > FrmMain.Height, New AniEaseInFluent(AniEasePower.ExtraStrong), New AniEaseOutFluent(AniEasePower.ExtraStrong))), - AaHeight(Me, -Delta, 150, 260, Ease:=New AniEaseOutFluent(AniEasePower.Strong)) - }) + Dim AbsDelta = Math.Abs(Delta) + + If AbsDelta <= 800 Then + '短距离,直接使用 150ms 的缓动动画 + AnimList.Add(AaHeight(Me, Delta, 150,, New AniEaseOutFluent(AniEasePower.ExtraStrong))) Else - '普通的改变就行啦 - AnimList.AddRange({ - AaHeight(Me, DeltaHeight, MathClamp(Math.Abs(DeltaHeight) * 4, 150, 250),, New AniEaseOutFluent) - }) + Dim EaseLength As Integer, EaseTime As Integer + Dim InitSpeed As Integer '到达缓动区前的初速度 + If Delta < 0 AndAlso AbsDelta - EaseLength > 5000 * 0.1 Then + '收回距离过长 (>0.1s),强制以 100ms 完成匀速段,然后让减速段更长 + EaseLength = 200 + EaseTime = 150 + InitSpeed = (AbsDelta - EaseLength) / 0.1 + ElseIf Delta > 0 AndAlso AbsDelta - EaseLength > 5000 * 0.6 Then + '展开距离过长 (>0.6s),以 5000 速度展示 300ms 匀速段,剩下的距离全部归入减速段 + InitSpeed = 5000 + EaseLength = AbsDelta - InitSpeed * 0.3 + EaseTime = 400 + Else + '中程,匀速地快速展开(或收回) + EaseLength = 150 + EaseTime = 200 + InitSpeed = 4000 + End If + '匀速段 + AnimList.Add(AaHeight(Me, (AbsDelta - EaseLength) * Math.Sign(Delta), + (AbsDelta - EaseLength) / InitSpeed * 1000)) + '减速段 + AnimList.Add(AaHeight(Me, EaseLength * Math.Sign(Delta), + EaseTime,, New AniEaseOutFluentWithInitial(InitSpeed, EaseTime / 1000, EaseLength), True)) End If + AnimList.Add(AaCode( Sub() IsHeightAnimating = False Height = ActualUsedHeight - If IsSwaped Then SwapControl.Visibility = Visibility.Collapsed + If IsSwapped AndAlso SwapControl IsNot Nothing Then SwapControl.Visibility = Visibility.Collapsed End Sub,, True)) AniStart(AnimList, "MyCard Height " & Uuid) - IsHeightAnimating = True - ActualUsedHeight = If(IsSwaped, SwapedHeight, Height) + ActualUsedHeight = If(IsSwapped, SwapedHeight, Height) Height = PreviousHeight End Sub ''' ''' 通知 MyCard,控件内容已改变,需要中断动画并更新高度。 ''' Public Sub TriggerForceResize() - Height = If(IsSwaped, SwapedHeight, Double.NaN) + Height = If(IsSwapped, SwapedHeight, Double.NaN) AniStop("MyCard Height " & Uuid) IsHeightAnimating = False End Sub @@ -281,26 +289,40 @@ ''' ''' 是否已被折叠。 ''' - Public Property IsSwaped As Boolean + Public Property IsSwapped As Boolean Get - Return _IsSwaped + Return _IsSwapped End Get Set(value As Boolean) - If _IsSwaped = value Then Exit Property - _IsSwaped = value - If SwapControl Is Nothing Then Exit Property + If _IsSwapped = value Then Return + _IsSwapped = value + If SwapControl Is Nothing Then Return '展开 - If Not IsSwaped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title) + If Not IsSwapped AndAlso TypeOf SwapControl Is StackPanel Then StackInstall(SwapControl, SwapType, Title) '若尚未加载,会在 Loaded 事件中触发无动画的折叠,不需要在这里进行 - If Not IsLoaded Then Exit Property + If Not IsLoaded Then Return '更新高度 SwapControl.Visibility = Visibility.Visible TriggerForceResize() '改变箭头 - AniStart(AaRotateTransform(MainSwap, If(_IsSwaped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 400,, New AniEaseOutBack(AniEasePower.Weak)), "MyCard Swap " & Uuid, True) + AniStart(AaRotateTransform(MainSwap, If(_IsSwapped, If(SwapLogoRight, 270, 0), 180) - CType(MainSwap.RenderTransform, RotateTransform).Angle, 250,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), "MyCard Swap " & Uuid, True) End Set End Property - Private _IsSwaped As Boolean = False + Private _IsSwapped As Boolean = False + ''' + ''' 是否已被折叠。(已过时,请使用 IsSwapped) + ''' + + Public Property IsSwaped As Boolean + Get + Return GetValue(IsSwapedProperty) + End Get + Set(value As Boolean) + SetValue(IsSwapedProperty, value) + End Set + End Property + Public Shared ReadOnly IsSwapedProperty As DependencyProperty = DependencyProperty.Register("IsSwaped", GetType(Boolean), GetType(MyCard), New PropertyMetadata(False, + Sub(sender, e) If sender IsNot Nothing AndAlso TypeOf sender Is MyCard Then CType(sender, MyCard).IsSwapped = CType(e.NewValue, Boolean))) Public Property SwapLogoRight As Boolean = False Private IsMouseDown As Boolean = False @@ -309,8 +331,8 @@ Public Const SwapedHeight As Integer = 40 Private Sub MyCard_MouseLeftButtonDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonDown Dim Pos As Double = Mouse.GetPosition(Me).Y - If Not IsSwaped AndAlso - (SwapControl Is Nothing OrElse Pos > If(IsSwaped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判 + If Not IsSwapped AndAlso + (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判 IsMouseDown = True End Sub Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp @@ -318,8 +340,8 @@ IsMouseDown = False Dim Pos As Double = Mouse.GetPosition(Me).Y - If Not IsSwaped AndAlso - (SwapControl Is Nothing OrElse Pos > If(IsSwaped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判 + If Not IsSwapped AndAlso + (SwapControl Is Nothing OrElse Pos > If(IsSwapped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Return '检测点击位置;或已经不在可视树上的误判 Dim ee = New RouteEventArgs(True) RaiseEvent PreviewSwap(Me, ee) @@ -328,8 +350,8 @@ Return End If - IsSwaped = Not IsSwaped - Log("[Control] " & If(IsSwaped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title)) + IsSwapped = Not IsSwapped + Log("[Control] " & If(IsSwapped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title)) RaiseEvent Swap(Me, ee) End Sub Private Sub MyCard_MouseLeave_Swap(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave @@ -350,7 +372,7 @@ Partial Public Module ModAnimation AaCode( Sub() If RemoveFromChildren Then - If Control.Parent Is Nothing Then Exit Sub + If Control.Parent Is Nothing Then Return CType(Control.Parent, Object).Children.Remove(Control) Else Control.Visibility = Visibility.Collapsed @@ -360,7 +382,7 @@ Partial Public Module ModAnimation }, "MyCard Dispose " & Control.Uuid) Else If RemoveFromChildren Then - If Control.Parent Is Nothing Then Exit Sub + If Control.Parent Is Nothing Then Return CType(Control.Parent, Object).Children.Remove(Control) Else Control.Visibility = Visibility.Collapsed diff --git a/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb b/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb index 4211286..a0e8c9c 100644 --- a/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb @@ -40,7 +40,7 @@ Public Class MyCheckBox ''' 是否由用户引发。 Public Sub SetChecked(value As Boolean, user As Boolean) Try - If value = Checked Then Exit Sub + If value = Checked Then Return 'Preview 事件 If value AndAlso user Then @@ -50,7 +50,7 @@ Public Class MyCheckBox MouseDowned = True Checkbox_MouseLeave() MouseDowned = False - Exit Sub + Return End If End If @@ -133,14 +133,14 @@ Public Class MyCheckBox Private MouseDowned As Boolean = False Private AllowMouseDown As Boolean = True Private Sub Checkbox_MouseUp() Handles Me.MouseLeftButtonUp - If Not MouseDowned Then Exit Sub + If Not MouseDowned Then Return Log("[Control] 按下复选框(" & (Not Checked).ToString & "):" & Text) MouseDowned = False SetChecked(Not Checked, True) AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid) End Sub Private Sub Checkbox_MouseDown() Handles Me.MouseLeftButtonDown - If Not AllowMouseDown Then Exit Sub + If Not AllowMouseDown Then Return MouseDowned = True Focus() AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushBg1", 100), "MyCheckBox Background " & Uuid) @@ -154,7 +154,7 @@ Public Class MyCheckBox End If End Sub Private Sub Checkbox_MouseLeave() Handles Me.MouseLeave - If Not MouseDowned Then Exit Sub + If Not MouseDowned Then Return MouseDowned = False AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid) If Checked Then @@ -203,7 +203,7 @@ Public Class MyCheckBox }, "MyCheckBox BorderColor " & Uuid) End Sub Private Sub Checkbox_MouseLeaveAnimation() Handles Me.MouseLeave - If Not IsEnabled Then Exit Sub 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误 + If Not IsEnabled Then Return 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误 AniStart({ AaColor(LabText, TextBlock.ForegroundProperty, If(Me.IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut) }, "MyCheckBox TextColor " & Uuid) diff --git a/Plain Craft Launcher 2/Controls/MyComboBox.vb b/Plain Craft Launcher 2/Controls/MyComboBox.vb index dbad5d1..874b48a 100644 --- a/Plain Craft Launcher 2/Controls/MyComboBox.vb +++ b/Plain Craft Launcher 2/Controls/MyComboBox.vb @@ -8,7 +8,7 @@ Public Property HintText As String = "" Public Overrides Sub OnApplyTemplate() MyBase.OnApplyTemplate() - If Not IsEditable Then Exit Sub + If Not IsEditable Then Return Try TextBox = Template.FindName("PART_EditableTextBox", Me) TextBox.AddHandler(LostFocusEvent, New RoutedEventHandler(AddressOf RefreshColor)) @@ -116,7 +116,7 @@ '修复 WPF Bug:下拉框文本修改后,依然误认为还选择着此前的选项,导致再次点击该选项时内容不变 Private IsTextChanging As Boolean = False Private Sub MyComboBox_TextChanged(sender As Object, e As TextChangedEventArgs) Handles Me.TextChanged - If IsTextChanging OrElse Not IsEditable Then Exit Sub + If IsTextChanging OrElse Not IsEditable Then Return If SelectedItem IsNot Nothing AndAlso Text <> SelectedItem.ToString Then Dim RawText As String = Text Dim RawSelectionStart As Integer = TextBox.SelectionStart diff --git a/Plain Craft Launcher 2/Controls/MyComboBoxItem.vb b/Plain Craft Launcher 2/Controls/MyComboBoxItem.vb index 3e77499..5e431d5 100644 --- a/Plain Craft Launcher 2/Controls/MyComboBoxItem.vb +++ b/Plain Craft Launcher 2/Controls/MyComboBoxItem.vb @@ -35,7 +35,7 @@ NewFontOpacity = 0.4 Time = AnimationTimeOut End If - If BackColorName = NewBackColorName AndAlso FontOpacity = NewFontOpacity Then Exit Sub + If BackColorName = NewBackColorName AndAlso FontOpacity = NewFontOpacity Then Return BackColorName = NewBackColorName FontOpacity = NewFontOpacity '触发颜色动画 diff --git a/Plain Craft Launcher 2/Modules/Reference/SystemDropShadowChrome.vb b/Plain Craft Launcher 2/Controls/MyDropShadow.vb similarity index 77% rename from Plain Craft Launcher 2/Modules/Reference/SystemDropShadowChrome.vb rename to Plain Craft Launcher 2/Controls/MyDropShadow.vb index 6e103cf..5f268d8 100644 --- a/Plain Craft Launcher 2/Modules/Reference/SystemDropShadowChrome.vb +++ b/Plain Craft Launcher 2/Controls/MyDropShadow.vb @@ -1,65 +1,89 @@ -Public NotInheritable Class SystemDropShadowChrome +Public Class MyDropShadow Inherits Decorator - '源码来源于:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1 - - Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(SystemDropShadowChrome), New FrameworkPropertyMetadata(Color.FromArgb(&H71, &H0, &H0, &H0), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes))) + ''' + ''' 阴影颜色。 + ''' Public Property Color As Color Get - Return CType(GetValue(ColorProperty), Color) + Return GetValue(ColorProperty) End Get Set(value As Color) SetValue(ColorProperty, value) End Set End Property + Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(MyDropShadow), + New FrameworkPropertyMetadata(Color.FromArgb(&H71, &H0, &H0, &H0), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes))) - Public Shared ReadOnly CornerRadiusProperty As DependencyProperty = DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(SystemDropShadowChrome), New FrameworkPropertyMetadata(New CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)), New ValidateValueCallback(AddressOf IsCornerRadiusValid)) + ''' + ''' 阴影模糊半径。 + ''' + Public Property ShadowRadius As Double + Get + Return GetValue(ShadowRadiusProperty) + End Get + Set(value As Double) + SetValue(ShadowRadiusProperty, value) + End Set + End Property + Public Shared ReadOnly ShadowRadiusProperty As DependencyProperty = DependencyProperty.Register("ShadowRadius", GetType(Double), GetType(MyDropShadow), + New FrameworkPropertyMetadata(CDbl(5), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes))) + + ''' + ''' 圆角大小。 + ''' Public Property CornerRadius As CornerRadius Get - Return CType(GetValue(CornerRadiusProperty), CornerRadius) + Return GetValue(CornerRadiusProperty) End Get Set(value As CornerRadius) SetValue(CornerRadiusProperty, value) End Set End Property Private Shared Function IsCornerRadiusValid(value As Object) As Boolean - Dim cr As CornerRadius = CType(value, CornerRadius) + Dim cr As CornerRadius = value Return Not (cr.TopLeft < 0.0 OrElse cr.TopRight < 0.0 OrElse cr.BottomLeft < 0.0 OrElse cr.BottomRight < 0.0 OrElse Double.IsNaN(cr.TopLeft) OrElse Double.IsNaN(cr.TopRight) OrElse Double.IsNaN(cr.BottomLeft) OrElse Double.IsNaN(cr.BottomRight) OrElse Double.IsInfinity(cr.TopLeft) OrElse Double.IsInfinity(cr.TopRight) OrElse Double.IsInfinity(cr.BottomLeft) OrElse Double.IsInfinity(cr.BottomRight)) End Function + Public Shared ReadOnly CornerRadiusProperty As DependencyProperty = DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(MyDropShadow), + New FrameworkPropertyMetadata(New CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)), New ValidateValueCallback(AddressOf IsCornerRadiusValid)) + + + ' ======================================= + ' 渲染 + ' ======================================= - Private Const ShadowDepth As Double = 5 Protected Overrides Sub OnRender(drawingContext As DrawingContext) Dim cornerRadius As CornerRadius = Me.CornerRadius - Dim shadowBounds As New Rect(New Point(ShadowDepth, ShadowDepth), New Size(RenderSize.Width, RenderSize.Height)) + Dim shadowBounds As New Rect(0, 0, RenderSize.Width, RenderSize.Height) Dim color As Color = Me.Color If shadowBounds.Width > 0 AndAlso shadowBounds.Height > 0 AndAlso color.A > 0 Then - Dim centerWidth As Double = shadowBounds.Right - shadowBounds.Left - 2 * ShadowDepth - Dim centerHeight As Double = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowDepth + Dim centerWidth As Double = shadowBounds.Right - shadowBounds.Left - 2 * ShadowRadius + Dim centerHeight As Double = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowRadius Dim maxRadius As Double = Math.Min(centerWidth * 0.5, centerHeight * 0.5) cornerRadius.TopLeft = Math.Min(cornerRadius.TopLeft, maxRadius) cornerRadius.TopRight = Math.Min(cornerRadius.TopRight, maxRadius) cornerRadius.BottomLeft = Math.Min(cornerRadius.BottomLeft, maxRadius) cornerRadius.BottomRight = Math.Min(cornerRadius.BottomRight, maxRadius) Dim brushes As Brush() = GetBrushes(color, cornerRadius) - Dim centerTop As Double = shadowBounds.Top + ShadowDepth - Dim centerLeft As Double = shadowBounds.Left + ShadowDepth - Dim centerRight As Double = shadowBounds.Right - ShadowDepth - Dim centerBottom As Double = shadowBounds.Bottom - ShadowDepth + Dim centerTop As Double = shadowBounds.Top + ShadowRadius + Dim centerLeft As Double = shadowBounds.Left + ShadowRadius + Dim centerRight As Double = shadowBounds.Right - ShadowRadius + Dim centerBottom As Double = shadowBounds.Bottom - ShadowRadius Dim guidelineSetX As Double() = New Double() {centerLeft, centerLeft + cornerRadius.TopLeft, centerRight - cornerRadius.TopRight, centerLeft + cornerRadius.BottomLeft, centerRight - cornerRadius.BottomRight, centerRight} Dim guidelineSetY As Double() = New Double() {centerTop, centerTop + cornerRadius.TopLeft, centerTop + cornerRadius.TopRight, centerBottom - cornerRadius.BottomLeft, centerBottom - cornerRadius.BottomRight, centerBottom} drawingContext.PushGuidelineSet(New GuidelineSet(guidelineSetX, guidelineSetY)) - cornerRadius.TopLeft += ShadowDepth - cornerRadius.TopRight += ShadowDepth - cornerRadius.BottomLeft += ShadowDepth - cornerRadius.BottomRight += ShadowDepth + cornerRadius.TopLeft += ShadowRadius + cornerRadius.TopRight += ShadowRadius + cornerRadius.BottomLeft += ShadowRadius + cornerRadius.BottomRight += ShadowRadius Dim topLeft As New Rect(shadowBounds.Left, shadowBounds.Top, cornerRadius.TopLeft, cornerRadius.TopLeft) drawingContext.DrawRectangle(brushes(Placement.TopLeft), Nothing, topLeft) Dim topWidth As Double = guidelineSetX(2) - guidelineSetX(1) If topWidth > 0 Then - Dim top As New Rect(guidelineSetX(1), shadowBounds.Top, topWidth, ShadowDepth) + Dim top As New Rect(guidelineSetX(1), shadowBounds.Top, topWidth, ShadowRadius) drawingContext.DrawRectangle(brushes(Placement.Top), Nothing, top) End If @@ -68,14 +92,14 @@ Dim leftHeight As Double = guidelineSetY(3) - guidelineSetY(1) If leftHeight > 0 Then - Dim left As New Rect(shadowBounds.Left, guidelineSetY(1), ShadowDepth, leftHeight) + Dim left As New Rect(shadowBounds.Left, guidelineSetY(1), ShadowRadius, leftHeight) drawingContext.DrawRectangle(brushes(Placement.Left), Nothing, left) End If Dim rightHeight As Double = guidelineSetY(4) - guidelineSetY(2) If rightHeight > 0 Then - Dim right As New Rect(guidelineSetX(5), guidelineSetY(2), ShadowDepth, rightHeight) + Dim right As New Rect(guidelineSetX(5), guidelineSetY(2), ShadowRadius, rightHeight) drawingContext.DrawRectangle(brushes(Placement.Right), Nothing, right) End If @@ -84,20 +108,20 @@ Dim bottomWidth As Double = guidelineSetX(4) - guidelineSetX(3) If bottomWidth > 0 Then - Dim bottom As New Rect(guidelineSetX(3), guidelineSetY(5), bottomWidth, ShadowDepth) + Dim bottom As New Rect(guidelineSetX(3), guidelineSetY(5), bottomWidth, ShadowRadius) drawingContext.DrawRectangle(brushes(Placement.Bottom), Nothing, bottom) End If Dim bottomRight As New Rect(guidelineSetX(4), guidelineSetY(4), cornerRadius.BottomRight, cornerRadius.BottomRight) drawingContext.DrawRectangle(brushes(Placement.BottomRight), Nothing, bottomRight) - If cornerRadius.TopLeft = ShadowDepth AndAlso cornerRadius.TopLeft = cornerRadius.TopRight AndAlso cornerRadius.TopLeft = cornerRadius.BottomLeft AndAlso cornerRadius.TopLeft = cornerRadius.BottomRight Then + If cornerRadius.TopLeft = ShadowRadius AndAlso cornerRadius.TopLeft = cornerRadius.TopRight AndAlso cornerRadius.TopLeft = cornerRadius.BottomLeft AndAlso cornerRadius.TopLeft = cornerRadius.BottomRight Then Dim center As New Rect(guidelineSetX(0), guidelineSetY(0), centerWidth, centerHeight) drawingContext.DrawRectangle(brushes(Placement.Center), Nothing, center) Else Dim figure As New PathFigure() - If cornerRadius.TopLeft > ShadowDepth Then + If cornerRadius.TopLeft > ShadowRadius Then figure.StartPoint = New Point(guidelineSetX(1), guidelineSetY(0)) figure.Segments.Add(New LineSegment(New Point(guidelineSetX(1), guidelineSetY(1)), True)) figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(1)), True)) @@ -105,7 +129,7 @@ figure.StartPoint = New Point(guidelineSetX(0), guidelineSetY(0)) End If - If cornerRadius.BottomLeft > ShadowDepth Then + If cornerRadius.BottomLeft > ShadowRadius Then figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(3)), True)) figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(3)), True)) figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(5)), True)) @@ -113,7 +137,7 @@ figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(5)), True)) End If - If cornerRadius.BottomRight > ShadowDepth Then + If cornerRadius.BottomRight > ShadowRadius Then figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(5)), True)) figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(4)), True)) figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(4)), True)) @@ -121,7 +145,7 @@ figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(5)), True)) End If - If cornerRadius.TopRight > ShadowDepth Then + If cornerRadius.TopRight > ShadowRadius Then figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(2)), True)) figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(2)), True)) figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(0)), True)) @@ -158,28 +182,27 @@ Private _brushes As Brush() Private Shared Sub ClearBrushes(o As DependencyObject, e As DependencyPropertyChangedEventArgs) - CType(o, SystemDropShadowChrome)._brushes = Nothing + CType(o, MyDropShadow)._brushes = Nothing End Sub - Private Shared Function CreateStops(c As Color, cornerRadius As Double) As GradientStopCollection - Dim gradientScale As Double = 1 / (cornerRadius + ShadowDepth) - Dim gsc As New GradientStopCollection From { - New GradientStop(c, (0.5 + cornerRadius) * gradientScale) - } + Private Function CreateStops(c As Color, cornerRadius As Double) As GradientStopCollection + Dim gradientScale As Double = 1 / (ShadowRadius + cornerRadius) + Dim gsc As New GradientStopCollection Dim stopColor As Color = c + gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.1 + cornerRadius) * gradientScale)) stopColor.A = CByte(0.74336 * c.A) - gsc.Add(New GradientStop(stopColor, (1.5 + cornerRadius) * gradientScale)) + gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.3 + cornerRadius) * gradientScale)) stopColor.A = CByte(0.38053 * c.A) - gsc.Add(New GradientStop(stopColor, (2.5 + cornerRadius) * gradientScale)) + gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.5 + cornerRadius) * gradientScale)) stopColor.A = CByte(0.12389 * c.A) - gsc.Add(New GradientStop(stopColor, (3.5 + cornerRadius) * gradientScale)) + gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.7 + cornerRadius) * gradientScale)) stopColor.A = CByte(0.02654 * c.A) - gsc.Add(New GradientStop(stopColor, (4.5 + cornerRadius) * gradientScale)) + gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.9 + cornerRadius) * gradientScale)) stopColor.A = 0 - gsc.Add(New GradientStop(stopColor, (5 + cornerRadius) * gradientScale)) + gsc.Add(New GradientStop(stopColor, (ShadowRadius + cornerRadius) * gradientScale)) gsc.Freeze() Return gsc End Function - Private Shared Function CreateBrushes(c As Color, cornerRadius As CornerRadius) As Brush() + Private Function CreateBrushes(c As Color, cornerRadius As CornerRadius) As Brush() Dim brushes As Brush() = New Brush(8) {} brushes(Placement.Center) = New SolidColorBrush(c) brushes(Placement.Center).Freeze() @@ -297,3 +320,5 @@ End Function End Class + +'参考自:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1 \ No newline at end of file diff --git a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb index c26cb79..0bb1b3a 100644 --- a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb @@ -11,7 +11,7 @@ Return _Progress End Get Set(value As Double) - If _Progress = value Then Exit Property + If _Progress = value Then Return _Progress = value If value < 0.0001 Then PanProgress.Visibility = Visibility.Collapsed @@ -30,7 +30,7 @@ Return _Logo End Get Set(value As String) - If value = _Logo Then Exit Property + If value = _Logo Then Return _Logo = value Path.Data = (New GeometryConverter).ConvertFromString(value) End Set @@ -51,7 +51,7 @@ Return _Show End Get Set(value As Boolean) - If _Show = value Then Exit Property + If _Show = value Then Return _Show = value RunInUi(Sub() If value Then @@ -121,7 +121,7 @@ Focus() End Sub Private Sub Button_RightMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseRightButtonDown - If Not CanRightClick Then Exit Sub + If Not CanRightClick Then Return If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then AniStart({ AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)), @@ -141,7 +141,7 @@ RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave End Sub Private Sub Button_RightMouseUp() Handles PanClick.MouseRightButtonUp - If Not CanRightClick Then Exit Sub + If Not CanRightClick Then Return If Not IsLeftMouseHeld Then AniStart({ AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack) diff --git a/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb index 67ea22f..9784cd0 100644 --- a/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb @@ -14,7 +14,7 @@ Public Class MyExtraTextButton Return _Logo End Get Set(value As String) - If value = _Logo Then Exit Property + If value = _Logo Then Return _Logo = value Path.Data = (New GeometryConverter).ConvertFromString(value) End Set @@ -55,7 +55,7 @@ Public Class MyExtraTextButton Return _Show End Get Set(value As Boolean) - If _Show = value Then Exit Property + If _Show = value Then Return _Show = value RunInUi( Sub() diff --git a/Plain Craft Launcher 2/Controls/MyHint.xaml b/Plain Craft Launcher 2/Controls/MyHint.xaml index 09b5694..6395c28 100644 --- a/Plain Craft Launcher 2/Controls/MyHint.xaml +++ b/Plain Craft Launcher 2/Controls/MyHint.xaml @@ -3,22 +3,14 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" FocusVisualStyle="{x:Null}" - UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Stretch" BorderThickness="1" BorderBrush="#99FF4444" CornerRadius="3"> - - - - - - + UseLayoutRounding="True" SnapsToDevicePixels="True" HorizontalAlignment="Stretch" BorderThickness="3,0,0,0" BorderBrush="#99FF4444" CornerRadius="2"> - - - - + + \ No newline at end of file diff --git a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb index 6e5bac3..dba944a 100644 --- a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb @@ -2,37 +2,73 @@ Public Class MyHint - Public Uuid As Integer = GetUuid() - Private _IsWarn As Boolean = True - Public Property IsWarn As Boolean + '边框 + Public Property HasBorder As Boolean Get - Return _IsWarn + Return BorderThickness.Top > 0 End Get Set(value As Boolean) - If _IsWarn = value Then Exit Property - _IsWarn = value - If _IsWarn Then - BorderBrush = New MyColor("#CCFF4444") - Gradient1.Color = New MyColor("#BBFFBBBB") - Gradient2.Color = New MyColor("#BBFF8888") - Path.Fill = New MyColor("#BF0000") - LabText.Foreground = New MyColor("#BF0000") - BtnClose.Foreground = New MyColor("#BF0000") - Path.Data = (New GeometryConverter).ConvertFromString("F1 M 58.5832,55.4172L 17.4169,55.4171C 15.5619,53.5621 15.5619,50.5546 17.4168,48.6996L 35.201,15.8402C 37.056,13.9852 40.0635,13.9852 41.9185,15.8402L 58.5832,48.6997C 60.4382,50.5546 60.4382,53.5622 58.5832,55.4172 Z M 34.0417,25.7292L 36.0208,41.9584L 39.9791,41.9583L 41.9583,25.7292L 34.0417,25.7292 Z M 38,44.3333C 36.2511,44.3333 34.8333,45.7511 34.8333,47.5C 34.8333,49.2489 36.2511,50.6667 38,50.6667C 39.7489,50.6667 41.1666,49.2489 41.1666,47.5C 41.1666,45.7511 39.7489,44.3333 38,44.3333 Z ") + If value Then + BorderThickness = New Thickness(3, GetWPFSize(1), GetWPFSize(1), GetWPFSize(1)) Else - BorderBrush = New MyColor("#CC4D76FF") - Gradient1.Color = New MyColor("#BBB0D0FF") - Gradient2.Color = New MyColor("#BB9EBAFF") - Path.Fill = New MyColor("#0062BF") - LabText.Foreground = New MyColor("#0062BF") - BtnClose.Foreground = New MyColor("#0062BF") - Path.Data = (New GeometryConverter).ConvertFromString("F1M38,19C48.4934,19 57,27.5066 57,38 57,48.4934 48.4934,57 38,57 27.5066,57 19,48.4934 19,38 19,27.5066 27.5066,19 38,19z M33.25,33.25L33.25,36.4167 36.4166,36.4167 36.4166,47.5 33.25,47.5 33.25,50.6667 44.3333,50.6667 44.3333,47.5 41.1666,47.5 41.1666,36.4167 41.1666,33.25 33.25,33.25z M38.7917,25.3333C37.48,25.3333 36.4167,26.3967 36.4167,27.7083 36.4167,29.02 37.48,30.0833 38.7917,30.0833 40.1033,30.0833 41.1667,29.02 41.1667,27.7083 41.1667,26.3967 40.1033,25.3333 38.7917,25.3333z") + BorderThickness = New Thickness(3, 0, 0, 0) End If End Set End Property + '配色 + Public Enum Themes + Blue = 0 + Red = 1 + Yellow = 2 + End Enum + Public Property Theme As Themes + Get + Return _ColorType + End Get + Set(value As Themes) + _ColorType = value + UpdateUI() + End Set + End Property + Private _ColorType As Themes = Themes.Red + Private Sub UpdateUI() Handles Me.Loaded + Select Case Theme + Case Themes.Blue + Background = New MyColor("#d9ecff") + BorderBrush = New MyColor("#1172D4") + LabText.Foreground = New MyColor("#0F64B8") + BtnClose.Foreground = New MyColor("#0F64B8") + Case Themes.Red + Background = New MyColor("#ffdddf") + BorderBrush = New MyColor("#d82929") + LabText.Foreground = New MyColor("#bf0b0b") + BtnClose.Foreground = New MyColor("#bf0b0b") + Case Themes.Yellow + Background = New MyColor("#ffebd7") + BorderBrush = New MyColor("#f57a00") + LabText.Foreground = New MyColor("#d86c00") + BtnClose.Foreground = New MyColor("#d86c00") + End Select + End Sub + + Public Property IsWarn As Boolean + Get + Return Theme = Themes.Red + End Get + Set(value As Boolean) + Theme = If(value, Themes.Red, Themes.Blue) + End Set + End Property + Public Shared ReadOnly IsWarnProperty As DependencyProperty = + DependencyProperty.Register("IsWarn", GetType(Boolean), GetType(MyHint), New PropertyMetadata(True, + Sub(d As MyHint, e As DependencyPropertyChangedEventArgs) + d.Theme = If(e.NewValue, Themes.Red, Themes.Blue) + End Sub)) + + '文本 Public ReadOnly Property Inlines As InlineCollection Get Return LabText.Inlines @@ -52,6 +88,7 @@ Public Class MyHint d.LabText.Text = e.NewValue End Sub)) + '关闭按钮 Public Property CanClose As Boolean Get Return BtnClose.Visibility = Visibility.Visible @@ -60,12 +97,9 @@ Public Class MyHint BtnClose.Visibility = If(value, Visibility.Visible, Visibility.Collapsed) End Set End Property - Public Property RelativeSetup As String = "" Private Sub MyHint_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded - If CanClose AndAlso Setup.Get(RelativeSetup) Then - Visibility = Visibility.Collapsed - End If + If CanClose AndAlso Setup.Get(RelativeSetup) Then Visibility = Visibility.Collapsed End Sub Private Sub BtnClose_Click(sender As Object, e As EventArgs) Handles BtnClose.Click Setup.Set(RelativeSetup, True) @@ -121,20 +155,21 @@ Public Class MyHint End Class Partial Public Module ModAnimation Public Sub AniDispose(Control As MyHint, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing) - If Not Control.IsHitTestVisible Then Exit Sub + If Not Control.IsHitTestVisible Then Return Control.IsHitTestVisible = False AniStart({ - AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent), - AaOpacity(Control, -1, 200,, New AniEaseOutFluent), - AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent), - AaCode(Sub() - If RemoveFromChildren Then - CType(Control.Parent, Object).Children.Remove(Control) - Else - Control.Visibility = Visibility.Collapsed - End If - If CallBack IsNot Nothing Then CallBack(Control) - End Sub,, True) - }, "MyCard Dispose " & Control.Uuid) + AaScaleTransform(Control, -0.08, 200,, New AniEaseInFluent), + AaOpacity(Control, -1, 200,, New AniEaseOutFluent), + AaHeight(Control, -Control.ActualHeight, 150, 100, New AniEaseOutFluent), + AaCode( + Sub() + If RemoveFromChildren Then + CType(Control.Parent, Object).Children.Remove(Control) + Else + Control.Visibility = Visibility.Collapsed + End If + If CallBack IsNot Nothing Then CallBack(Control) + End Sub,, True) + }, "MyCard Dispose " & Control.Uuid) End Sub End Module diff --git a/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb index ede71b5..d060e88 100644 --- a/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyIconButton.xaml.vb @@ -198,7 +198,7 @@ End Class Partial Public Module ModAnimation Public Sub AniDispose(Control As MyIconButton, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing) - If Not Control.IsHitTestVisible Then Exit Sub + If Not Control.IsHitTestVisible Then Return Control.IsHitTestVisible = False AniStart({ AaScaleTransform(Control, -1.5, 200,, New AniEaseInFluent), diff --git a/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb index 2d810cc..1fe5557 100644 --- a/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb @@ -72,7 +72,7 @@ Public Class MyIconTextButton Public Event Click(sender As Object, e As RouteEventArgs) Private IsMouseDown As Boolean = False Private Sub MyIconTextButton_MouseUp() Handles Me.MouseLeftButtonUp - If Not IsMouseDown Then Exit Sub + If Not IsMouseDown Then Return Log("[Control] 按下带图标按钮:" & Text) IsMouseDown = False RaiseEvent Click(Me, New RouteEventArgs(True)) diff --git a/Plain Craft Launcher 2/Controls/MyImage.vb b/Plain Craft Launcher 2/Controls/MyImage.vb index eaf7289..8f6de5b 100644 --- a/Plain Craft Launcher 2/Controls/MyImage.vb +++ b/Plain Craft Launcher 2/Controls/MyImage.vb @@ -25,7 +25,7 @@ ''' ''' 与 Image 的 Source 类似。 - ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。 + ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示;若 EnableCache 设为 True,图片还会保存为本地缓存。 ''' 支持 WebP 格式的图片。 ''' Public Shadows Property Source As String '覆写 Image 的 Source 属性 @@ -34,9 +34,9 @@ End Get Set(value As String) If value = "" Then value = Nothing - If _Source = value Then Exit Property + If _Source = value Then Return _Source = value - If Not IsInitialized Then Exit Property '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868) + If Not IsInitialized Then Return '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868) Load() End Set End Property @@ -46,7 +46,7 @@ Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString()))) ''' - ''' 当 Source 首次下载失败时,会从该备用地址加载图片。 + ''' 若 Source 是一个网络图片,该地址将作为第二下载源。 ''' Public Property FallbackSource As String Get @@ -82,7 +82,7 @@ End Get Set(value As String) If value = "" Then value = Nothing - If _ActualSource = value Then Exit Property + If _ActualSource = value Then Return _ActualSource = value Try Dim Bitmap As MyBitmap = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件 @@ -103,82 +103,43 @@ '空 If Source Is Nothing Then ActualSource = Nothing - Exit Sub + Return End If '本地图片 If Not Source.StartsWithF("http") Then ActualSource = Source - Exit Sub + Return End If '从缓存加载网络图片 - Dim Url As String = Source - Dim Retried As Boolean = False - Dim TempPath As String = GetTempPath(Url) - Dim TempFile As New FileInfo(TempPath) Dim EnableCache As Boolean = Me.EnableCache + Dim TempPath As String = GetTempPath(Source) & If(EnableCache, "", GetUuid()) '不启用缓存时加上随机字符串,避免冲突 + Dim TempFile As New FileInfo(TempPath) If EnableCache AndAlso TempFile.Exists Then ActualSource = TempPath - If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存 + If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Return '无需刷新缓存 End If RunInNewThread( Sub() - Dim TempDownloadingPath As String = Nothing Try -RetryStart: '下载 - ActualSource = LoadingSource '显示加载中图片 - TempDownloadingPath = TempPath & RandomInteger(0, 10000000) - Directory.CreateDirectory(GetPathFromFullPath(TempPath)) '重新实现下载,以避免携带 Header(#5072) - Using Client As New Net.WebClient - Client.DownloadFile(Url, TempDownloadingPath) - End Using - If Url <> Source AndAlso Url <> FallbackSource Then - '已经更换了地址 - File.Delete(TempDownloadingPath) - ElseIf EnableCache Then + ActualSource = LoadingSource '显示加载中的占位图片 + NetDownloadByLoader( + If(String.IsNullOrEmpty(FallbackSource), {Source}, {Source, FallbackSource}), + TempPath, SimulateBrowserHeaders:=True) + If EnableCache Then '保存缓存并显示 - If File.Exists(TempPath) Then File.Delete(TempPath) - FileSystem.Rename(TempDownloadingPath, TempPath) RunInUi(Sub() ActualSource = TempPath) Else '直接显示 - RunInUiWait(Sub() ActualSource = TempDownloadingPath) - File.Delete(TempDownloadingPath) + RunInUiWait(Sub() ActualSource = TempPath) + File.Delete(TempPath) End If Catch ex As Exception Try If TempPath IsNot Nothing Then File.Delete(TempPath) - If TempDownloadingPath IsNot Nothing Then File.Delete(TempDownloadingPath) Catch End Try - If Not Retried Then - '更换备用地址 - Log(ex, $"下载图片可重试地失败({Url})", LogLevel.Developer) - Retried = True - Url = If(FallbackSource, Source) - '空 - If Url Is Nothing Then - ActualSource = Nothing - Exit Sub - End If - '本地图片 - If Not Url.StartsWithF("http") Then - ActualSource = Url - Exit Sub - End If - '从缓存加载网络图片 - TempPath = GetTempPath(Url) - TempFile = New FileInfo(TempPath) - If EnableCache AndAlso TempFile.Exists() Then - ActualSource = TempPath - If (Date.Now - TempFile.CreationTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存 - End If - '下载 - If Source = Url Then Thread.Sleep(1000) '延迟 1s 重试 - GoTo RetryStart - Else - Log(ex, $"下载图片失败({Url})", LogLevel.Hint) - End If + Log(ex, $"下载图片失败", LogLevel.Hint) End Try End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal) End Sub diff --git a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb index d0729ed..48c5d8e 100644 --- a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb @@ -197,7 +197,7 @@ Public Class MyListItem Return _Info End Get Set(value As String) - If _Info = value Then Exit Property + If _Info = value Then Return value = value.Replace(vbCr, "").Replace(vbLf, "") _Info = value LabInfo.Text = value @@ -212,7 +212,7 @@ Public Class MyListItem Return _Logo End Get Set(value As String) - If _Logo = value Then Exit Property + If _Logo = value Then Return _Logo = value '删除旧 Logo If Not IsNothing(PathLogo) Then Children.Remove(PathLogo) @@ -297,7 +297,7 @@ Public Class MyListItem Return _Type End Get Set(value As CheckType) - If _Type = value Then Exit Property + If _Type = value Then Return _Type = value '切换左栏大小 ColumnCheck.Width = New GridLength(If(_Type = CheckType.None OrElse _Type = CheckType.Clickable, If(Height < 40, 4, 2), 6)) @@ -345,7 +345,7 @@ Public Class MyListItem Return _Checked End Get Set(value As Boolean) - SetChecked(value, False, True) + SetChecked(value, False, value <> _Checked) '仅在值发生变化时触发动画 (#4596) End Set End Property ''' @@ -367,31 +367,31 @@ Public Class MyListItem RaiseEvent Changed(Me, ChangedEventArgs) If ChangedEventArgs.Handled Then _Checked = RawValue - Exit Sub + Return End If End If _Checked = value Else - If value = _Checked Then Exit Sub + If value = _Checked Then Return _Checked = value If IsInitialized Then RaiseEvent Changed(Me, ChangedEventArgs) If ChangedEventArgs.Handled Then _Checked = RawValue - Exit Sub + Return End If End If End If If value Then Dim CheckEventArgs As New RouteEventArgs(user) RaiseEvent Check(Me, CheckEventArgs) - If CheckEventArgs.Handled Then Exit Sub + If CheckEventArgs.Handled Then Return End If '保证只有一个单选 ListItem 选中 If Type = CheckType.RadioBox Then - If IsNothing(Parent) Then Exit Sub + If IsNothing(Parent) Then Return Dim RadioboxList As New List(Of MyListItem) Dim CheckedCount As Integer = 0 '收集控件列表与选中个数 @@ -502,15 +502,15 @@ Public Class MyListItem '触发点击事件 Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp - If Not IsMouseDown Then Exit Sub + If Not IsMouseDown Then Return RaiseEvent Click(sender, e) - If e.Handled Then Exit Sub + If e.Handled Then Return '触发自定义事件 If Not String.IsNullOrEmpty(EventType) Then ModEvent.ProcessCustomEvents(EventType, EventData, _CustomEvents) e.Handled = True End If - If e.Handled Then Exit Sub + If e.Handled Then Return '实际的单击处理 Select Case Type Case CheckType.Clickable @@ -593,7 +593,7 @@ Public Class MyListItem Time = 180 End If End If - If StateLast = StateNew Then Exit Sub + If StateLast = StateNew Then Return StateLast = StateNew '触发颜色动画 If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画 diff --git a/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb b/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb index 77e0161..5a735ba 100644 --- a/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyLoading.xaml.vb @@ -35,7 +35,7 @@ Public Class MyLoading Return _ShowProgress End Get Set(value As Boolean) - If _ShowProgress = value Then Exit Property + If _ShowProgress = value Then Return _ShowProgress = value RefreshText() End Set @@ -79,11 +79,7 @@ Public Class MyLoading Do While Ex.InnerException IsNot Nothing Ex = Ex.InnerException Loop - LabText.Text = StrTrim(Ex.Message) - If {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", - "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) LabText.Text.Contains(s)) Then - LabText.Text = "网络环境不佳,请重试或尝试使用 VPN" - End If + LabText.Text = If(Ex.IsNetworkRelated(), "网络环境不佳,请稍后再试,或使用 VPN 改善网络环境", StrTrim(Ex.Message)) End If Else LabText.Text = TextError @@ -142,7 +138,7 @@ Public Class MyLoading Return _OuterState End Get Set(value As MyLoadingState) - If _OuterState = value Then Exit Property + If _OuterState = value Then Return Dim OldValue = _OuterState _OuterState = value '引发事件 @@ -159,7 +155,7 @@ Public Class MyLoading Return _InnerState End Get Set(value As MyLoadingState) - If _InnerState = value Then Exit Property + If _InnerState = value Then Return Dim OldValue = _InnerState _InnerState = value '引发事件 @@ -183,7 +179,7 @@ Public Class MyLoading Private IsLooping As Boolean = False Private Sub AniLoop() '这坨循环代码也是老屎坑了,救救.jpg - If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Exit Sub + If Not HasAnimation OrElse IsLooping OrElse Not InnerState = MyLoadingState.Run OrElse AniSpeed > 10 OrElse Not IsLoaded Then Return IsLooping = True ErrorAnimationWaiting = True AniStart({ @@ -271,7 +267,7 @@ Public Class MyLoadingStateSimulator Return _LoadingState End Get Set(value As MyLoadingState) - If _LoadingState = value Then Exit Property + If _LoadingState = value Then Return Dim OldState = _LoadingState _LoadingState = value RaiseEvent LoadingStateChanged(value, OldState) diff --git a/Plain Craft Launcher 2/Controls/MyMenuItem.vb b/Plain Craft Launcher 2/Controls/MyMenuItem.vb index 5f24795..cc70ee6 100644 --- a/Plain Craft Launcher 2/Controls/MyMenuItem.vb +++ b/Plain Craft Launcher 2/Controls/MyMenuItem.vb @@ -37,7 +37,7 @@ Time = AnimationTimeOut End If '重复性验证 - If ColorName = BackName Then Exit Sub + If ColorName = BackName Then Return ColorName = BackName '触发颜色动画 If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画 diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb index ca7bcac..065df7a 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb @@ -72,13 +72,14 @@ End Sub Public Sub Btn1_Click() Handles Btn1.Click - If MyConverter.IsExited OrElse Not TextArea.IsValidated Then Exit Sub + TextArea.Validate() '#5773 + If MyConverter.IsExited OrElse Not TextArea.IsValidated Then Return MyConverter.IsExited = True MyConverter.Result = TextArea.Text Close() End Sub Public Sub Btn2_Click() Handles Btn2.Click - If MyConverter.IsExited Then Exit Sub + If MyConverter.IsExited Then Return MyConverter.IsExited = True MyConverter.Result = Nothing Close() diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb index 9dc6f1e..59bb7fc 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml.vb @@ -77,13 +77,13 @@ End Sub Public Sub Btn1_Click() Handles Btn1.Click - If MyConverter.IsExited OrElse SelectedIndex = -1 Then Exit Sub + If MyConverter.IsExited OrElse SelectedIndex = -1 Then Return MyConverter.IsExited = True MyConverter.Result = SelectedIndex Close() End Sub Public Sub Btn2_Click() Handles Btn2.Click - If MyConverter.IsExited Then Exit Sub + If MyConverter.IsExited Then Return MyConverter.IsExited = True MyConverter.Result = Nothing Close() diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.vb b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.vb index c718b57..75c5bfb 100644 --- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml.vb @@ -70,7 +70,7 @@ End Sub Public Sub Btn1_Click() Handles Btn1.Click - If MyConverter.IsExited Then Exit Sub + If MyConverter.IsExited Then Return If MyConverter.Button1Action IsNot Nothing Then MyConverter.Button1Action() Else @@ -80,7 +80,7 @@ End If End Sub Public Sub Btn2_Click() Handles Btn2.Click - If MyConverter.IsExited Then Exit Sub + If MyConverter.IsExited Then Return If MyConverter.Button2Action IsNot Nothing Then MyConverter.Button2Action() Else @@ -90,7 +90,7 @@ End If End Sub Public Sub Btn3_Click() Handles Btn3.Click - If MyConverter.IsExited Then Exit Sub + If MyConverter.IsExited Then Return If MyConverter.Button3Action IsNot Nothing Then MyConverter.Button3Action() Else diff --git a/Plain Craft Launcher 2/Controls/MyPageLeft.vb b/Plain Craft Launcher 2/Controls/MyPageLeft.vb index 9c368a6..afc047f 100644 --- a/Plain Craft Launcher 2/Controls/MyPageLeft.vb +++ b/Plain Craft Launcher 2/Controls/MyPageLeft.vb @@ -3,15 +3,15 @@ Private Uuid As Integer = GetUuid() '执行逐个进入动画的控件 - Public Property AnimatedControl As FrameworkElement + Public Property AnimatedControl As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664) Get Return GetValue(AnimatedControlProperty) End Get - Set(value As FrameworkElement) + Set(value As String) SetValue(AnimatedControlProperty, value) End Set End Property - Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(FrameworkElement), GetType(MyPageLeft), New PropertyMetadata(Nothing)) + Public Shared ReadOnly AnimatedControlProperty As DependencyProperty = DependencyProperty.Register("AnimatedControl", GetType(String), GetType(MyPageLeft), New PropertyMetadata(Nothing)) Public Sub TriggerShowAnimation() If AnimatedControl Is Nothing Then @@ -39,16 +39,17 @@ Element.Opacity = 0 Element.RenderTransform = New TranslateTransform(-25, 0) If TypeOf Element Is MyListItem Then CType(Element, MyListItem).IsMouseOverAnimationEnabled = False - AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 200, Delay, New AniEaseOutFluent(AniEasePower.Weak))) + AniList.Add(AaOpacity(Element, If(TypeOf Element Is TextBlock, 0.6, 1), 100, Delay, New AniEaseOutFluent(AniEasePower.Weak))) AniList.Add(AaTranslateX(Element, 5, 200, Delay, New AniEaseOutFluent)) AniList.Add(AaTranslateX(Element, 20, 300, Delay, New AniEaseOutBack(AniEasePower.Weak))) If TypeOf Element Is MyListItem Then - AniList.Add(AaCode(Sub() - CType(Element, MyListItem).IsMouseOverAnimationEnabled = True - CType(Element, MyListItem).RefreshColor(Me, New EventArgs) - End Sub, Delay + 280)) + AniList.Add(AaCode( + Sub() + CType(Element, MyListItem).IsMouseOverAnimationEnabled = True + CType(Element, MyListItem).RefreshColor(Me, New EventArgs) + End Sub, Delay + 280)) End If - Delay += Math.Max(8, 20 - Id) * 2.5 + Delay += Math.Max(15 - Id, 7) * 2 Id += 1 End If Next @@ -63,8 +64,8 @@ RenderTransformOrigin = New Point(0.5, 0.5) End If AniStart({ - AaScaleTransform(Me, 0.95 - CType(RenderTransform, ScaleTransform).ScaleX, 130,, New AniEaseInFluent(AniEasePower.Weak)), - AaOpacity(Me, -Opacity, 100, 30) + AaScaleTransform(Me, 0.95 - CType(RenderTransform, ScaleTransform).ScaleX, 110,, New AniEaseInFluent(AniEasePower.Weak)), + AaOpacity(Me, -Opacity, 80, 30) }, "PageLeft PageChange " & Uuid) Else '逐个退出动画 @@ -72,8 +73,8 @@ Dim Id As Integer = 0 Dim Controls = GetAllAnimControls() For Each Element As FrameworkElement In Controls - AniList.Add(AaOpacity(Element, -Element.Opacity, 60, 80 / Controls.Count * Id)) - AniList.Add(AaTranslateX(Element, -6, 60, 80 / Controls.Count * Id)) + AniList.Add(AaOpacity(Element, -Element.Opacity, 50, 70 / Controls.Count * Id)) + AniList.Add(AaTranslateX(Element, -6, 50, 70 / Controls.Count * Id)) Id += 1 Next AniStart(AniList, "PageLeft PageChange " & Uuid) @@ -83,11 +84,11 @@ '遍历获取所有需要生成动画的控件 Private Function GetAllAnimControls(Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement) Dim AllControls As New List(Of FrameworkElement) - GetAllAnimControls(AnimatedControl, AllControls, IgnoreInvisibility) + GetAllAnimControls(FindName(AnimatedControl), AllControls, IgnoreInvisibility) Return AllControls End Function Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean) - If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub + If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return If TypeOf Element Is MyTextButton Then AllControls.Add(Element) ElseIf TypeOf Element Is MyListItem Then @@ -104,7 +105,3 @@ End Sub End Class - -Public Interface IRefreshable - Sub Refresh() -End Interface \ No newline at end of file diff --git a/Plain Craft Launcher 2/Controls/MyPageRight.vb b/Plain Craft Launcher 2/Controls/MyPageRight.vb index 4c427b5..151ad44 100644 --- a/Plain Craft Launcher 2/Controls/MyPageRight.vb +++ b/Plain Craft Launcher 2/Controls/MyPageRight.vb @@ -3,16 +3,16 @@ Public PageUuid As Integer = GetUuid() '“返回顶部” 按钮检测的滚动区域 - Public Property PanScroll As MyScrollViewer + Public Property PanScroll As String '需要在 Loaded 之后才能获取到控件,所以不能用 Binding 直接绑定(#6664) Get Return GetValue(PanScrollProperty) End Get - Set(value As MyScrollViewer) + Set(value As String) SetValue(PanScrollProperty, value) End Set End Property Private Shared ReadOnly PanScrollProperty = - DependencyProperty.Register("PanScroll", GetType(MyScrollViewer), GetType(MyPageRight), New PropertyMetadata(Nothing)) + DependencyProperty.Register("PanScroll", GetType(String), GetType(MyPageRight), New PropertyMetadata(Nothing)) '当前状态 Public Enum PageStates @@ -33,7 +33,7 @@ Return _PageState End Get Set(value As PageStates) - If _PageState = value Then Exit Property + If _PageState = value Then Return _PageState = value If ModeDebug Then Log("[UI] 页面状态切换为 " & GetStringFromEnum(value)) End Set @@ -106,7 +106,7 @@ End Sub '重试 Public Sub PageLoaderRestart(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = True) '由外部调用的重试 - If Not PageLoaderAutoRun Then Exit Sub + If Not PageLoaderAutoRun Then Return If PageLoader.GetType.Name.StartsWithF("LoaderTask") Then PageLoader.Start(CType(PageLoader, Object).StartGetInput(Input, PageLoaderInputInvoke), IsForceRestart:=IsForceRestart) Else @@ -187,7 +187,7 @@ ''' 需要立即切换至 Empty。 ''' Public Sub PageOnForceExit() - If PageState = PageStates.Empty Then Exit Sub + If PageState = PageStates.Empty Then Return If ModeDebug Then Log("[UI] 已触发 PageOnForceExit") PageState = PageStates.Empty AniStop("PageRight PageChange " & PageUuid) @@ -302,7 +302,7 @@ Private Sub PageLoaderState(sender As Object, NewState As LoadState, OldState As LoadState) Select Case NewState Case LoadState.Failed, LoadState.Loading - If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Exit Sub + If OldState = LoadState.Failed OrElse OldState = LoadState.Loading Then Return If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Start/Refresh)") '(重新)开始运行 '需要从部分状态切换到 ReloadExit @@ -314,7 +314,7 @@ PageState = PageStates.ContentExit End Select Case LoadState.Finished, LoadState.Aborted, LoadState.Waiting - If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Exit Sub + If Not (OldState = LoadState.Failed OrElse OldState = LoadState.Loading) Then Return If ModeDebug Then Log("[UI] 已触发 PageLoaderState (Stop/Abort)") '运行结束 '需要从 LoaderWait 切换到 ContentEnter,或从 LoaderStay 切换到 LoaderExit @@ -358,10 +358,10 @@ Else Control.Opacity = 0 Control.RenderTransform = New TranslateTransform(0, -16) - AniList.Add(AaOpacity(Control, 1, 150, Delay, New AniEaseOutFluent(AniEasePower.Weak))) + AniList.Add(AaOpacity(Control, 1, 100, Delay, New AniEaseOutFluent(AniEasePower.Weak))) AniList.Add(AaTranslateY(Control, 5, 250, Delay, New AniEaseOutFluent)) AniList.Add(AaTranslateY(Control, 11, 350, Delay, New AniEaseOutBack)) - Delay += 40 + Delay += 25 End If Next Next @@ -387,9 +387,9 @@ CType(Control, MyExtraTextButton).Show = False Else Control.IsHitTestVisible = False - AniList.Add(AaOpacity(Control, -1, 90, Delay)) - AniList.Add(AaTranslateY(Control, -6, 90, Delay)) - Delay += 20 + AniList.Add(AaOpacity(Control, -1, 70, Delay)) + AniList.Add(AaTranslateY(Control, -6, 70, Delay)) + Delay += 15 End If Next Next @@ -423,7 +423,7 @@ Return AllControls.Except(DisabledPageAnimControls) End Function Private Sub _GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean) - If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Exit Sub + If Not IgnoreInvisibility AndAlso Element.Visibility = Visibility.Collapsed Then Return If TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is MyExtraTextButton OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then AllControls.Add(Element) ElseIf TypeOf Element Is ContentControl Then diff --git a/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb b/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb index 816d7f9..5dd839f 100644 --- a/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb @@ -41,7 +41,7 @@ Public Class MyRadioBox RaiseEvent PreviewCheck(Me, e) If e.Handled Then Radiobox_MouseLeave() - Exit Sub + Return End If End If @@ -54,7 +54,7 @@ Public Class MyRadioBox End If '保证只有一个单选框选中 - If Parent Is Nothing Then Exit Sub + If Parent Is Nothing Then Return Dim RadioboxList As New List(Of MyRadioBox) Dim CheckedCount As Integer = 0 For Each Control In CType(Parent, Object).Children '收集控件列表与选中个数 @@ -172,7 +172,7 @@ Public Class MyRadioBox Private MouseDowned As Boolean = False Private AllowMouseDown As Boolean = True Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp - If Not MouseDowned Then Exit Sub + If Not MouseDowned Then Return Log("[Control] 按下单选框:" & Text) SetChecked(True, True) MouseDowned = False @@ -187,7 +187,7 @@ Public Class MyRadioBox End If End Sub Private Sub Radiobox_MouseLeave() Handles Me.MouseLeave - If Not MouseDowned Then Exit Sub + If Not MouseDowned Then Return MouseDowned = False AniStart(AaColor(ShapeBorder, Ellipse.FillProperty, "ColorBrushHalfWhite", 100), "MyRadioBox Background " & Uuid) If Not Checked Then @@ -224,7 +224,7 @@ Public Class MyRadioBox AniStart(AaColor(LabText, TextBlock.ForegroundProperty, "ColorBrush3", AnimationTimeOfMouseIn), "MyRadioBox TextColor " & Uuid) End Sub Private Sub Radiobox_MouseLeaveAnimation() Handles Me.MouseLeave - If Not Me.IsEnabled Then Exit Sub 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误 + If Not Me.IsEnabled Then Return 'MouseLeave 比 IsEnabledChanged 后执行,所以如果自定义事件修改了 IsEnabled,将导致显示错误 If IsLoaded AndAlso AniControlEnabled = 0 Then AniStart(AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsEnabled, If(Checked, "ColorBrush2", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox BorderColor " & Uuid) AniStart(AaColor(LabText, TextBlock.ForegroundProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"), AnimationTimeOfMouseOut), "MyRadioBox TextColor " & Uuid) diff --git a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb index 6478a78..4be05b8 100644 --- a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb @@ -62,7 +62,7 @@ Public Class MyRadioButton '保证只有一个单选框选中 - If IsNothing(Parent) Then Exit Sub + If IsNothing(Parent) Then Return Dim RadioboxList As New List(Of MyRadioButton) Dim CheckedCount As Integer = 0 '收集控件列表与选中个数 @@ -101,7 +101,7 @@ Public Class MyRadioButton '更改动画 - If Not IsChanged Then Exit Sub + If Not IsChanged Then Return RefreshColor(Nothing, anime) '触发事件 @@ -147,17 +147,17 @@ Public Class MyRadioButton Public Event PreviewClick(sender As Object, e As RouteEventArgs) Private IsMouseDown As Boolean = False Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp - If Checked Then Exit Sub - If Not IsMouseDown Then Exit Sub + If Checked Then Return + If Not IsMouseDown Then Return Log("[Control] 按下单选按钮:" & Text) IsMouseDown = False Dim e As New RouteEventArgs(True) RaiseEvent PreviewClick(Me, e) - If e.Handled Then Exit Sub + If e.Handled Then Return SetChecked(True, True, True) End Sub Private Sub Radiobox_MouseDown() Handles Me.MouseLeftButtonDown - If Checked Then Exit Sub + If Checked Then Return IsMouseDown = True RefreshColor() End Sub diff --git a/Plain Craft Launcher 2/Controls/MyResizer.vb b/Plain Craft Launcher 2/Controls/MyResizer.vb index e0c21ed..b9bd5f6 100644 --- a/Plain Craft Launcher 2/Controls/MyResizer.vb +++ b/Plain Craft Launcher 2/Controls/MyResizer.vb @@ -301,7 +301,7 @@ Public Class MyResizer End Sub Private Sub updateMouseDown() - Dim flag As Boolean = Mouse.LeftButton = MouseButtonState.Released + Dim flag = (GetAsyncKeyState(&H1) And &H8000) = 0 '调用原生API判断鼠标是否抬起,如果使用WPF的API的话鼠标不在窗口上时不会更新状态 (#5655) If flag Then resizeRight = False resizeLeft = False @@ -311,5 +311,5 @@ Public Class MyResizer End Sub Private Declare Function GetCursorPos Lib "user32.dll" ( ByRef lpPoint As PointAPI) As Boolean - + Private Declare Function GetAsyncKeyState Lib "user32.dll" (vKey As Integer) As Short End Class diff --git a/Plain Craft Launcher 2/Controls/MyScrollViewer.vb b/Plain Craft Launcher 2/Controls/MyScrollViewer.vb index 42da890..e91151c 100644 --- a/Plain Craft Launcher 2/Controls/MyScrollViewer.vb +++ b/Plain Craft Launcher 2/Controls/MyScrollViewer.vb @@ -6,16 +6,15 @@ Private RealOffset As Double Private Sub MyScrollViewer_PreviewMouseWheel(sender As Object, e As MouseWheelEventArgs) Handles Me.PreviewMouseWheel - If e.Delta = 0 OrElse ActualHeight = 0 OrElse ScrollableHeight = 0 Then Exit Sub + If e.Delta = 0 OrElse ActualHeight = 0 OrElse ScrollableHeight = 0 Then Return Dim SourceType = e.Source.GetType If Content.TemplatedParent Is Nothing AndAlso ( (GetType(ComboBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, ComboBox).IsDropDownOpen) OrElse (GetType(TextBox).IsAssignableFrom(SourceType) AndAlso CType(e.Source, TextBox).AcceptsReturn) OrElse GetType(ComboBoxItem).IsAssignableFrom(SourceType) OrElse - TypeOf e.Source Is CheckBox - ) Then + TypeOf e.Source Is CheckBox) Then '如果当前是在对有滚动条的下拉框或文本框执行,则不接管操作 - Exit Sub + Return End If e.Handled = True PerformVerticalOffsetDelta(-e.Delta) diff --git a/Plain Craft Launcher 2/Controls/MySlider.xaml.vb b/Plain Craft Launcher 2/Controls/MySlider.xaml.vb index 0a6ff76..0a3b9ca 100644 --- a/Plain Craft Launcher 2/Controls/MySlider.xaml.vb +++ b/Plain Craft Launcher 2/Controls/MySlider.xaml.vb @@ -14,7 +14,7 @@ Return _MaxValue End Get Set(value As Integer) - If value = _MaxValue Then Exit Property + If value = _MaxValue Then Return _MaxValue = value RefreshWidth(Nothing, Nothing) End Set @@ -29,7 +29,7 @@ Try newValue = MathClamp(newValue, 0, MaxValue) - If _Value = newValue Then Exit Property + If _Value = newValue Then Return '触发 Preview 事件,修改新值 Dim OldValue = _Value @@ -40,12 +40,12 @@ If e.Handled Then _Value = OldValue DragStop() - Exit Property + Return End If End If If IsLoaded AndAlso AniControlEnabled = 0 Then - If ActualWidth < ShapeDot.Width Then Exit Property + If ActualWidth < ShapeDot.Width Then Return Dim NewWidth As Double = _Value / MaxValue * (ActualWidth - ShapeDot.Width) Dim DeltaProcess As Double = Math.Abs(LineFore.Width / (ActualWidth - ShapeDot.Width) - _Value / MaxValue) Dim Time As Double = (1 - Math.Pow(1 - DeltaProcess, 3)) * 300 + If(ChangeByKey, 100, 0) @@ -103,7 +103,7 @@ Popup.IsOpen = False End Sub Public Sub RefreshPopup() - If GetHintText Is Nothing Then Exit Sub + If GetHintText Is Nothing Then Return Popup.IsOpen = True TextHint.Text = GetHintText.DynamicInvoke(Value) Dim typeface As New Typeface(TextHint.FontFamily, TextHint.FontStyle, TextHint.FontWeight, TextHint.FontStretch) @@ -165,7 +165,7 @@ End Sub Private Sub MySlider_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown '拒绝一边拖动一边用按键改变 - If ReferenceEquals(Me, DragControl) Then Exit Sub + If ReferenceEquals(Me, DragControl) Then Return '改变值 If e.Key = Key.Left Then ChangeByKey = True @@ -178,7 +178,7 @@ ChangeByKey = False e.Handled = True Else - Exit Sub + Return End If '更新 Popup If GetHintText IsNot Nothing Then diff --git a/Plain Craft Launcher 2/Controls/MyTextBox.vb b/Plain Craft Launcher 2/Controls/MyTextBox.vb index ae3f0ae..e847a58 100644 --- a/Plain Craft Launcher 2/Controls/MyTextBox.vb +++ b/Plain Craft Launcher 2/Controls/MyTextBox.vb @@ -36,7 +36,7 @@ End Property Public Overrides Sub OnApplyTemplate() MyBase.OnApplyTemplate() - If HintText = "" OrElse labHint.Text <> "" Then Exit Sub + If HintText = "" OrElse labHint.Text <> "" Then Return labHint.Text = If(Text = "", HintText, "") End Sub @@ -217,11 +217,11 @@ IsTextChanged = IsLoaded '进行输入验证 Validate() - If Not IsValidated Then Exit Sub + If Not IsValidated Then Return '改变文本 RaiseEvent ValidatedTextChanged(sender, e) Catch ex As Exception - Log(ex, "进行输入验证时出错", LogLevel.Assert) + Log(ex, "进行输入验证时出错", LogLevel.Critical) End Try End Sub @@ -230,7 +230,7 @@ Private Sub RefreshColor() Handles Me.IsEnabledChanged, Me.MouseEnter, Me.MouseLeave, Me.GotFocus, Me.LostFocus Try '不对 ComboBox 从属进行动画 - If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Exit Sub + If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Return '判断当前颜色 Dim ForeColorName As String, BackColorName As String Dim AnimationTime As Integer @@ -264,7 +264,7 @@ If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画 '有动画 AniStart({ - AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime), + AaColor(Me, BorderBrushProperty, ForeColorName, AnimationTime), AaColor(Me, BackgroundProperty, BackColorName, AnimationTime) }, "MyTextBox Color " & Uuid) Else @@ -280,7 +280,7 @@ End Sub Private Sub RefreshTextColor() Handles Me.IsEnabledChanged Dim NewColor As MyColor = If(IsEnabled, ColorGray1, ColorGray4) - If CType(Foreground, SolidColorBrush).Color.R = NewColor.R Then Exit Sub + If CType(Foreground, SolidColorBrush).Color.R = NewColor.R Then Return If IsLoaded AndAlso AniControlEnabled = 0 AndAlso Not Text = "" Then '有动画 AniStart({AaColor(Me, ForegroundProperty, If(IsEnabled, "ColorBrushGray1", "ColorBrushGray4"), 200)}, "MyTextBox TextColor " & Uuid) diff --git a/Plain Craft Launcher 2/Controls/MyTextButton.vb b/Plain Craft Launcher 2/Controls/MyTextButton.vb index 67d9d45..bea7021 100644 --- a/Plain Craft Launcher 2/Controls/MyTextButton.vb +++ b/Plain Craft Launcher 2/Controls/MyTextButton.vb @@ -57,7 +57,7 @@ Private Const AnimationTimeIn As Integer = 100 Private Const AnimationTimeOut As Integer = 200 Private ColorName As String - Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp + Private Sub RefreshColor() Handles Me.MouseEnter, Me.MouseLeave, Me.IsEnabledChanged, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp '判断当前颜色 Dim ForeName As String Dim Time As Integer @@ -72,7 +72,7 @@ Time = AnimationTimeOut End If '重复性验证 - If ColorName = ForeName Then Exit Sub + If ColorName = ForeName Then Return ColorName = ForeName '触发颜色动画 If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画 diff --git a/Plain Craft Launcher 2/FormMain.xaml b/Plain Craft Launcher 2/FormMain.xaml index 89def5a..0582efb 100644 --- a/Plain Craft Launcher 2/FormMain.xaml +++ b/Plain Craft Launcher 2/FormMain.xaml @@ -99,9 +99,7 @@ - - - + @@ -115,19 +113,19 @@ - + - - + - - @@ -145,7 +143,7 @@ - + diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb index 60fe898..7799e8f 100644 --- a/Plain Craft Launcher 2/FormMain.xaml.vb +++ b/Plain Craft Launcher 2/FormMain.xaml.vb @@ -1,4 +1,5 @@ Imports System.ComponentModel +Imports System.Windows.Interop Public Class FormMain @@ -10,7 +11,44 @@ Public Class FormMain Dim FeatureList As New List(Of KeyValuePair(Of Integer, String)) '统计更新日志条目 #If BETA Then - If LastVersion < 354 Then 'Release 2.9.3 + If LastVersion < 369 Then 'Release 2.10.8 + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "新增:允许在版本设置中设置禁止更新 Mod,以防整合包玩家误操作")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:若整合包需要 PCL 不兼容的加载器,允许选择跳过它的安装")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "删除:由于已不再需要,删除手动安装包下载功能")) + FeatureCount += 21 + BugCount += 32 + End If + If LastVersion < 367 Then 'Release 2.10.6 + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:启用 MCIM 社区资源镜像源,以缓解社区资源难以下载的问题")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:正版登录出错时无法给出正确的错误信息")) + FeatureCount += 9 + BugCount += 9 + End If + If LastVersion < 365 Then 'Release 2.10.5 + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源时,会单独记忆每种资源上次下载到的文件夹,以防混淆")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:网络底层框架与下载稳定性")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法启动一部分 LabyMod 和 GTNH 客户端")) + FeatureCount += 22 + BugCount += 26 + End If + If LastVersion < 361 Then 'Release 2.10.3 + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法安装部分使用老版本 PCL 导出的整合包")) + End If + If LastVersion < 359 Then 'Release 2.10.2 + If LastVersion >= 357 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源包、光影包时能自动跳转到对应的文件夹")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:调整界面样式与动画,让整体视觉更干净,操作体验更顺滑")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法从 CurseForge 下载 Mod 等资源,或是安装整合包")) + FeatureCount += 28 + BugCount += 28 + End If + If LastVersion < 357 Then 'Release 2.10.0 + FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:下载资源包、光影包、数据包")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:允许设置文件下载源")) + FeatureCount += 9 + BugCount += 26 + End If + If LastVersion < 355 Then 'Release 2.9.3 FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:Minecraft 会优先使用独立显卡运行")) FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:简化下载新版本第二步的 UI")) FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:使用新的版本隔离策略")) @@ -66,7 +104,60 @@ Public Class FormMain '3:BUG+ IMP* FEAT- '2:BUG* IMP- '1:BUG- - If LastVersion < 353 Then 'Snapshot 2.9.3 + If LastVersion < 370 Then 'Snapshot 2.10.8 + If LastVersion >= 368 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化")) + FeatureCount += 3 + BugCount += 4 + End If + If LastVersion < 368 Then 'Snapshot 2.10.7 + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "新增:允许在版本设置中设置禁止更新 Mod,以防整合包玩家误操作")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:网络与下载稳定性优化")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:若整合包需要 PCL 不兼容的加载器,允许选择跳过它的安装")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "删除:由于已不再需要,删除手动安装包下载功能")) + FeatureCount += 19 + BugCount += 28 + End If + If LastVersion < 366 Then 'Snapshot 2.10.6 + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:启用 MCIM 社区资源镜像源,以缓解社区资源难以下载的问题")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:正版登录出错时无法给出正确的错误信息")) + FeatureCount += 9 + BugCount += 9 + End If + If LastVersion < 364 Then 'Snapshot 2.10.5 + If LastVersion >= 363 Then + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法添加新的正版账号")) + BugCount += 1 + End If + End If + If LastVersion < 363 Then 'Snapshot 2.10.4 + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源时,会单独记忆每种资源上次下载到的文件夹,以防混淆")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:网络底层框架与下载稳定性")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法启动一部分 LabyMod 和 GTNH 客户端")) + FeatureCount += 22 + BugCount += 26 + End If + If LastVersion < 362 Then 'Snapshot 2.10.3 + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法安装部分使用老版本 PCL 导出的整合包")) + End If + If LastVersion < 360 Then 'Snapshot 2.10.2 + FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法从 CurseForge 下载 Mod 等资源,或是安装整合包")) + If LastVersion >= 358 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法加载正版皮肤的头像")) + FeatureCount += 3 + BugCount += 5 + End If + If LastVersion < 358 Then 'Snapshot 2.10.1 + If LastVersion >= 356 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源包、光影包时能自动跳转到对应的文件夹")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:调整界面样式与动画,让整体视觉更干净,操作体验更顺滑")) + FeatureCount += 25 + BugCount += 23 + End If + If LastVersion < 356 Then 'Snapshot 2.10.0 + FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:下载资源包、光影包、数据包")) + FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:允许设置文件下载源")) + FeatureCount += 9 + BugCount += 26 + End If + If LastVersion < 354 Then 'Snapshot 2.9.3 If LastVersion = 352 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:低版本 MC 没有声音")) FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:若不安装 Mod 加载器,则无法安装 OptiFine 1.14+")) @@ -145,7 +236,7 @@ Public Class FormMain '整理更新日志文本 Dim ContentList As New List(Of String) Dim SortedFeatures = FeatureList.OrderByDescending(Function(f) f.Key).ToList - If Not SortedFeatures.Any() AndAlso FeatureCount = 0 AndAlso BugCount = 0 Then ContentList.Add("龙猫忘记写更新日志啦!可以去提醒他一下……") + If Not SortedFeatures.Any() AndAlso FeatureCount = 0 AndAlso BugCount = 0 Then ContentList.Add("没有更新日志……") For i = 0 To Math.Min(9, SortedFeatures.Count - 1) '最多取 10 项 ContentList.Add(SortedFeatures(i).Value) Next @@ -188,7 +279,7 @@ Public Class FormMain If Not Setup.IsUnset("LaunchArgumentIndie") Then Log("[Start] 从老 PCL 迁移版本隔离") Setup.Set("LaunchArgumentIndieV2", Setup.Get("LaunchArgumentIndie")) - ElseIf Not Setup.IsUnset("WindowHeight") Then + ElseIf Not Setup.IsUnset("LaunchVersionSelect") Then Log("[Start] 从老 PCL 升级,但此前未调整版本隔离,使用老的版本隔离默认值") Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndie")) Else @@ -199,16 +290,24 @@ Public Class FormMain '刷新主题 ThemeCheckAll(False) Setup.Load("UiLauncherTheme") + '注册拖拽事件(不能直接加 Handles,否则没用;#6340) + [AddHandler](DragDrop.DragEnterEvent, New DragEventHandler(AddressOf HandleDrag), handledEventsToo:=True) + [AddHandler](DragDrop.DragOverEvent, New DragEventHandler(AddressOf HandleDrag), handledEventsToo:=True) '加载 UI InitializeComponent() Opacity = 0 - ''开启管理员权限下的文件拖拽,但下列代码也没用(#2531) - 'If IsAdmin() Then - ' Log("[Start] PCL 正以管理员权限运行") - ' ChangeWindowMessageFilter(&H233, 1) - ' ChangeWindowMessageFilter(&H4A, 1) - ' ChangeWindowMessageFilter(&H49, 1) - 'End If + '开启管理员权限下的文件拖拽 + If IsAdmin() Then + Static Helper As New DragHelper + AddHandler SourceInitialized, + Sub() + Dim WpfHelper As New WindowInteropHelper(Me) + Helper.HwndIntPtrSource = HwndSource.FromHwnd(WpfHelper.Handle) + Helper.AddHook() + End Sub + AddHandler Closing, Sub() Helper.RemoveDragHook() + AddHandler Helper.DragDrop, Sub() FileDrag(Helper.DropFilePaths) + End If '切换到首页 If Not IsNothing(FrmLaunchLeft.Parent) Then FrmLaunchLeft.SetValue(ContentPresenter.ContentProperty, Nothing) If Not IsNothing(FrmLaunchRight.Parent) Then FrmLaunchRight.SetValue(ContentPresenter.ContentProperty, Nothing) @@ -255,7 +354,7 @@ Public Class FormMain Resizer.addResizerUp(ResizerT) 'PLC 彩蛋 If RandomInteger(1, 1000) = 233 Then - ShapeTitleLogo.Data = New GeometryConverter().ConvertFromString("M26,29 v-25 h5 a7,7 180 0 1 0,14 h-5 M80,6.5 a10,11.5 180 1 0 0,18 M47,2.5 v24.5 h12 M98,2 v27 M107,2 v27") + ShapeTitleLogo.Data = New GeometryConverter().ConvertFromString("M26,29 v-25 h6 a7,7 180 0 1 0,14 h-6 M83,6.5 a10,11.5 180 1 0 0,18 M48,2.5 v24.5 h13.5") End If '加载窗口 ThemeRefreshMain() @@ -311,8 +410,8 @@ Public Class FormMain '启动加载器池 Try JavaListInit() '延后到同意协议后再执行,避免在初次启动时进行进程操作 - Thread.Sleep(200) - DlClientListMojangLoader.Start(1) + Thread.Sleep(100) + DlClientListMojangLoader.Start(1) 'PCL 会同时根据这里的加载结果决定是否使用官方源进行下载 RunCountSub() ServerLoader.Start(1) RunInNewThread(AddressOf TryClearTaskTemp, "TryClearTaskTemp", ThreadPriority.BelowNormal) @@ -325,7 +424,9 @@ Public Class FormMain Catch ex As Exception Log(ex, "清理自动更新文件失败") End Try - End Sub, "Start Loader", ThreadPriority.Lowest) + '上报 + Telemetry("启动") + End Sub, "初始化", ThreadPriority.Lowest) Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms") End Sub @@ -421,8 +522,8 @@ Public Class FormMain Log("[Start] 已从老版本迁移 Mod 命名设置") End If '输出更新日志 - If LastVersionCode <= 0 Then Exit Sub - If LowerVersionCode >= VersionCode Then Exit Sub + If LastVersionCode <= 0 Then Return + If LowerVersionCode >= VersionCode Then Return ShowUpdateLog(LowerVersionCode) End Sub Private Sub DowngradeSub(LastVersionCode As Integer) @@ -456,7 +557,7 @@ Public Class FormMain Next End Sub, "强行停止下载任务") Else - Exit Sub + Return End If End If '关闭 @@ -496,26 +597,20 @@ Public Class FormMain On Error Resume Next IsProgramEnded = True AniControlEnabled += 1 - PageLinkIoi.IoiStop(False) - PageLinkHiper.HiperStop(False) If IsUpdateWaitingRestart Then UpdateRestart(False) If ReturnCode = ProcessReturnValues.Exception Then If Not IsLogShown Then FeedbackInfo() - Log("请在 https://github.com/Hex-Dragon/PCL2/issues 提交错误报告,以便于作者解决此问题!") + Log("请在 https://github.com/Meloong-Git/PCL/issues 提交错误报告,以便于作者解决此问题!") IsLogShown = True - ShellOnly(Path & "PCL\Log1.txt") + StartProcess(Path & "PCL\Log1.txt") End If Thread.Sleep(500) '防止 PCL 在记事本打开前就被掐掉 End If - Log("[System] 程序已退出,返回值:" & GetStringFromEnum(CType(ReturnCode, [Enum]))) + Log("[System] 程序已退出,返回值:" & GetStringFromEnum(ReturnCode)) LogFlush() - If ReturnCode = ProcessReturnValues.Success Then - Process.GetCurrentProcess.Kill() - Else - Environment.Exit(ReturnCode) - Process.GetCurrentProcess.Kill() - End If + If ReturnCode <> ProcessReturnValues.Success Then Environment.Exit(ReturnCode) + Process.GetCurrentProcess.Kill() End Sub Private Sub BtnTitleClose_Click(sender As Object, e As RoutedEventArgs) Handles BtnTitleClose.Click EndProgram(True) @@ -556,12 +651,13 @@ Public Class FormMain '按键事件 Private Sub FormMain_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown - If e.IsRepeat Then Exit Sub - '调用弹窗:回车选择第一个,Esc 选择最后一个 - If PanMsg.Children.Count > 0 Then + If e.IsRepeat Then Return + '修复按下 Alt 后误认为弹出系统菜单导致的冻结 + If e.SystemKey = Key.LeftAlt OrElse e.SystemKey = Key.RightAlt Then e.Handled = True + '在有弹窗时:回车选择第一个,Esc 选择最后一个 + If PanMsg.Children.Any Then If e.Key = Key.Enter Then CType(PanMsg.Children(0), Object).Btn1_Click() - Exit Sub ElseIf e.Key = Key.Escape Then Dim Msg As Object = PanMsg.Children(0) If TypeOf Msg IsNot MyMsgInput AndAlso TypeOf Msg IsNot MyMsgSelect AndAlso Msg.Btn3.Visibility = Visibility.Visible Then @@ -571,16 +667,21 @@ Public Class FormMain Else Msg.Btn1_Click() End If - Exit Sub End If + Return End If + + '========================== + ' 在没有弹窗时:继续检查…… + '========================== + '按 ESC 返回上一级 If e.Key = Key.Escape Then TriggerPageBack() '更改隐藏版本可见性 If e.Key = Key.F11 AndAlso PageCurrent = FormMain.PageType.VersionSelect Then FrmSelectRight.ShowHidden = Not FrmSelectRight.ShowHidden LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") - Exit Sub + Return End If '更改功能隐藏可见性 If e.Key = Key.F12 Then @@ -591,13 +692,13 @@ Public Class FormMain Hint("功能隐藏设置已重新开启!", HintType.Finish) End If PageSetupUI.HiddenRefresh() - Exit Sub + Return End If '按 F5 刷新页面 If e.Key = Key.F5 Then If TypeOf PageLeft Is IRefreshable Then CType(PageLeft, IRefreshable).Refresh() If TypeOf PageRight Is IRefreshable Then CType(PageRight, IRefreshable).Refresh() - Exit Sub + Return End If '调用启动游戏 If e.Key = Key.Enter AndAlso PageCurrent = FormMain.PageType.Launch Then @@ -607,12 +708,10 @@ Public Class FormMain FrmLaunchLeft.LaunchButtonClick() End If End If - '修复按下 Alt 后误认为弹出系统菜单导致的冻结 - If e.SystemKey = Key.LeftAlt OrElse e.SystemKey = Key.RightAlt Then e.Handled = True End Sub Private Sub FormMain_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseDown '鼠标侧键返回上一级 - If FrmMain.PanMsg.Children.Count > 0 OrElse WaitingMyMsgBox.Any Then Exit Sub '弹窗中(#5513) + If FrmMain.PanMsg.Children.Count > 0 OrElse WaitingMyMsgBox.Any Then Return '弹窗中(#5513) If e.ChangedButton = MouseButton.XButton1 OrElse e.ChangedButton = MouseButton.XButton2 Then TriggerPageBack() End Sub Private Sub TriggerPageBack() @@ -639,15 +738,41 @@ Public Class FormMain End Sub '文件拖放 - Private Sub FrmMain_PreviewDragOver(sender As Object, e As DragEventArgs) Handles Me.PreviewDragOver - If e.Data.GetFormats.Contains("FileDrop") Then - e.Effects = DragDropEffects.Link - Else - e.Effects = DragDropEffects.None - End If - End Sub - Private Sub FrmMain_Drop(sender As Object, e As DragEventArgs) Handles Me.PreviewDrop + Private Sub HandleDrag(sender As Object, e As DragEventArgs) Try + If e.Handled AndAlso (e.Effects <> DragDropEffects.None) Then Return + e.Handled = True + '缓存 + Static PrevData As IDataObject, PrevEffects As DragDropEffects + If e.Data Is PrevData Then + e.Effects = PrevEffects + Return + End If + '确定拖放效果 + e.Effects = DragDropEffects.None + If e.Data.GetDataPresent(DataFormats.Text) Then + Dim Str As String = e.Data.GetData(DataFormats.Text) + If Str.StartsWithF("authlib-injector:yggdrasil-server:") Then + e.Effects = DragDropEffects.Copy + ElseIf Str.StartsWithF("file:///") Then + e.Effects = DragDropEffects.Copy + End If + ElseIf e.Data.GetDataPresent(DataFormats.FileDrop) Then + Dim Files As String() = e.Data.GetData(DataFormats.FileDrop) + If Files IsNot Nothing AndAlso Files.Length > 0 Then + e.Effects = DragDropEffects.Link + End If + End If + PrevData = e.Data + PrevEffects = e.Effects + Log("[System] 设置拖放类型:" & GetStringFromEnum(e.Effects)) + Catch ex As Exception + Log(ex, "处理拖放时出错", LogLevel.Feedback) + End Try + End Sub + Private Sub FrmMain_Drop(sender As Object, e As DragEventArgs) Handles Me.Drop + Try + ShowWindowToTop() If e.Data.GetDataPresent(DataFormats.Text) Then '获取文本 Try @@ -656,22 +781,23 @@ Public Class FormMain If Str.StartsWithF("authlib-injector:yggdrasil-server:") Then 'Authlib 拖拽 e.Handled = True + e.Effects = DragDropEffects.Copy Dim AuthlibServer As String = Net.WebUtility.UrlDecode(Str.Substring("authlib-injector:yggdrasil-server:".Length)) Log("[System] Authlib 拖拽:" & AuthlibServer) If Not String.IsNullOrEmpty(New ValidateHttp().Validate(AuthlibServer)) Then Hint($"输入的 Authlib 验证服务器不符合网址格式({AuthlibServer})!", HintType.Critical) - Exit Sub + Return End If Dim TargetVersion = If(PageCurrent = PageType.VersionSetup, PageVersionLeft.Version, McVersionCurrent) If TargetVersion Is Nothing Then Hint("请先下载游戏,再设置第三方登录!", HintType.Critical) - Exit Sub + Return End If If AuthlibServer = "https://littleskin.cn/api/yggdrasil" Then 'LittleSkin If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启 LittleSkin 登录?" & vbCrLf & "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then - Exit Sub + Return End If Setup.Set("VersionServerLogin", 4, Version:=TargetVersion) Setup.Set("VersionServerAuthServer", "https://littleskin.cn/api/yggdrasil", Version:=TargetVersion) @@ -682,7 +808,7 @@ Public Class FormMain If MyMsgBox($"是否要在版本 {TargetVersion.Name} 中开启第三方登录?" & vbCrLf & $"登录服务器:{AuthlibServer}" & vbCrLf & vbCrLf & "你可以在 版本设置 → 设置 → 服务器选项 中修改登录方式。", "第三方登录开启确认", "确定", "取消") = 2 Then - Exit Sub + Return End If Setup.Set("VersionServerLogin", 4, Version:=TargetVersion) Setup.Set("VersionServerAuthServer", AuthlibServer, Version:=TargetVersion) @@ -700,20 +826,22 @@ Public Class FormMain '文件拖拽(例如从浏览器下载窗口拖入) Dim FilePath = Net.WebUtility.UrlDecode(Str).Substring("file:///".Length).Replace("/", "\") e.Handled = True + e.Effects = DragDropEffects.Copy FileDrag(New List(Of String) From {FilePath}) End If Catch ex As Exception Log(ex, "无法接取文本拖拽事件", LogLevel.Developer) - Exit Sub + Return End Try ElseIf e.Data.GetDataPresent(DataFormats.FileDrop) Then '获取文件并检查 Dim FilePathRaw = e.Data.GetData(DataFormats.FileDrop) If FilePathRaw Is Nothing Then '#2690 Hint("请将文件解压后再拖入!", HintType.Critical) - Exit Sub + Return End If e.Handled = True + e.Effects = DragDropEffects.Link FileDrag(CType(FilePathRaw, IEnumerable(Of String))) End If Catch ex As Exception @@ -728,10 +856,10 @@ Public Class FormMain '基础检查 If Directory.Exists(FilePathList.First) AndAlso Not File.Exists(FilePathList.First) Then Hint("请拖入一个文件,而非文件夹!", HintType.Critical) - Exit Sub + Return ElseIf Not File.Exists(FilePathList.First) Then Hint("拖入的文件不存在:" & FilePathList.First, HintType.Critical) - Exit Sub + Return End If '多文件拖拽 If FilePathList.Count > 1 Then @@ -739,17 +867,17 @@ Public Class FormMain For Each File In FilePathList If Not {"jar", "litemod", "disabled", "old"}.Contains(File.AfterLast(".").ToLower) Then Hint("一次请只拖入一个文件!", HintType.Critical) - Exit Sub + Return End If Next End If - '自定义主页 + '主页 Dim Extension As String = FilePath.AfterLast(".").ToLower If Extension = "xaml" Then - Log("[System] 文件后缀为 XAML,作为自定义主页加载") + Log("[System] 文件后缀为 XAML,作为主页加载") If File.Exists(Path & "PCL\Custom.xaml") Then - If MyMsgBox("已存在一个自定义主页文件,是否要将它覆盖?", "覆盖确认", "覆盖", "取消") = 2 Then - Exit Sub + If MyMsgBox("已存在一个主页文件,是否要将它覆盖?", "覆盖确认", "覆盖", "取消") = 2 Then + Return End If End If CopyFile(FilePath, Path & "PCL\Custom.xaml") @@ -759,18 +887,18 @@ Public Class FormMain FrmLaunchRight.ForceRefresh() Hint("已加载主页自定义文件!", HintType.Finish) End Sub) - Exit Sub + Return End If '安装 Mod - If PageVersionMod.InstallMods(FilePathList) Then Exit Sub + If PageVersionMod.InstallMods(FilePathList) Then Return '安装整合包 If {"zip", "rar", "mrpack"}.Any(Function(t) t = Extension) Then '部分压缩包是 zip 格式但后缀为 rar,总之试一试 Log("[System] 文件为压缩包,尝试作为整合包安装") Try ModpackInstall(FilePath) - Exit Sub + Return Catch ex As CancelledException - Exit Sub '用户主动取消 + Return '用户主动取消 Catch ex As Exception '安装失败,继续往后尝试 End Try @@ -778,17 +906,17 @@ Public Class FormMain 'RAR 处理 If Extension = "rar" Then Hint("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试!") - Exit Sub + Return End If '错误报告分析 Try Log("[System] 尝试进行错误报告分析") - Dim Analyzer As New CrashAnalyzer(GetUuid()) + Dim Analyzer As New CrashAnalyzer Analyzer.Import(FilePath) - If Analyzer.Prepare() = 0 Then Exit Try + If Not Analyzer.Prepare() Then Exit Try Analyzer.Analyze() Analyzer.Output(True, New List(Of String)) - Exit Sub + Return Catch ex As Exception Log(ex, "自主错误报告分析失败", LogLevel.Feedback) End Try @@ -828,7 +956,7 @@ Public Class FormMain Return _Hidden End Get Set(value As Boolean) - If _Hidden = value Then Exit Property + If _Hidden = value Then Return _Hidden = value If value Then '隐藏 @@ -918,15 +1046,12 @@ Public Class FormMain ''' Public Enum PageSubType [Default] = 0 - DownloadInstall = 1 - DownloadClient = 4 - DownloadOptiFine = 5 - DownloadForge = 6 - DownloadNeoForge = 7 - DownloadFabric = 8 - DownloadLiteLoader = 9 - DownloadMod = 11 - DownloadPack = 12 + DownloadInstall = 0 + DownloadMod = 2 + DownloadPack = 3 + DownloadDataPack = 4 + DownloadResourcePack = 5 + DownloadShader = 6 SetupLaunch = 0 SetupUI = 1 SetupSystem = 2 @@ -957,18 +1082,9 @@ Public Class FormMain Case PageType.VersionSetup Return "版本设置 - " & If(PageVersionLeft.Version Is Nothing, "未知版本", PageVersionLeft.Version.Name) Case PageType.CompDetail - Dim Project As CompProject = Stack.Additional(0) - Select Case Project.Type - Case CompType.Mod - Return "Mod 下载 - " & Project.TranslatedName - Case CompType.ModPack - Return "整合包下载 - " & Project.TranslatedName - Case Else 'CompType.ResourcePack - Return "资源包下载 - " & Project.TranslatedName - End Select + Return "资源下载 - " & CType(Stack.Additional(0), CompProject).TranslatedName Case PageType.HelpDetail - Dim Entry As HelpEntry = Stack.Additional(0) - Return Entry.Title + Return CType(Stack.Additional(0), HelpEntry).Title Case Else Return "" End Select @@ -1097,7 +1213,7 @@ Public Class FormMain ''' 通过点击导航栏改变页面。 ''' Private Sub BtnTitleSelect_Click(sender As MyRadioButton, raiseByMouse As Boolean) Handles BtnTitleSelect0.Check, BtnTitleSelect1.Check, BtnTitleSelect2.Check, BtnTitleSelect3.Check, BtnTitleSelect4.Check - If IsChangingPage Then Exit Sub + If IsChangingPage Then Return PageChangeActual(Val(sender.Tag)) End Sub ''' @@ -1116,7 +1232,7 @@ Public Class FormMain ''' 切换现有页面的实际方法。 ''' Private Sub PageChangeActual(Stack As PageStackData, Optional SubType As PageSubType = -1) - If PageCurrent = Stack AndAlso (PageCurrentSub = SubType OrElse SubType = -1) Then Exit Sub + If PageCurrent = Stack AndAlso (PageCurrentSub = SubType OrElse SubType = -1) Then Return AniControlEnabled += 1 Try @@ -1235,14 +1351,14 @@ Public Class FormMain PanMainLeft.Background = Nothing AniControlEnabled -= 1 RunInUi(Sub() PanMainLeft_Resize(PanMainLeft.ActualWidth), True) - End Sub, 130), + End Sub, 110), AaCode( Sub() '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理 PageLeft.Opacity = 1 PageLeft.TriggerShowAnimation() End Sub, 30, True) - }, "FrmMain PageChangeLeft") + }, "FrmMain PageChangeLeft") AniStart({ AaCode( Sub() @@ -1254,14 +1370,14 @@ Public Class FormMain PanMainRight.Background = Nothing AniControlEnabled -= 1 RunInUi(Sub() BtnExtraBack.ShowRefresh(), True) - End Sub, 130), + End Sub, 110), AaCode( Sub() '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理 PageRight.Opacity = 1 PageRight.PageOnEnter() End Sub, 30, True) - }, "FrmMain PageChangeRight") + }, "FrmMain PageChangeRight") End Sub ''' ''' 退出子界面。 @@ -1287,7 +1403,7 @@ Public Class FormMain '左边栏改变 Private Sub PanMainLeft_SizeChanged(sender As Object, e As SizeChangedEventArgs) Handles PanMainLeft.SizeChanged - If Not e.WidthChanged Then Exit Sub + If Not e.WidthChanged Then Return PanMainLeft_Resize(e.NewSize.Width) End Sub Private Sub PanMainLeft_Resize(NewWidth As Double) @@ -1297,16 +1413,16 @@ Public Class FormMain If NewWidth > 0 Then '宽度足够,显示 AniStart({ - AaWidth(RectLeftBackground, NewWidth - RectLeftBackground.Width, 400,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), - AaOpacity(RectLeftShadow, 1 - RectLeftShadow.Opacity, 200), - AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 250) + AaWidth(RectLeftBackground, NewWidth - RectLeftBackground.Width, 180,, New AniEaseOutFluent(AniEasePower.ExtraStrong)), + AaOpacity(RectLeftShadow, 1 - RectLeftShadow.Opacity, 180), + AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 150) }, "FrmMain LeftChange", True) Else '宽度不足,隐藏 AniStart({ - AaWidth(RectLeftBackground, -RectLeftBackground.Width, 200,, New AniEaseOutFluent), - AaOpacity(RectLeftShadow, -RectLeftShadow.Opacity, 200), - AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 170) + AaWidth(RectLeftBackground, -RectLeftBackground.Width, 180,, New AniEaseOutFluent), + AaOpacity(RectLeftShadow, -RectLeftShadow.Opacity, 180), + AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 150) }, "FrmMain LeftChange", True) End If Else @@ -1322,14 +1438,14 @@ Public Class FormMain '在时钟中调用,使得即使鼠标在窗口外松开,也可以释放控件 Public Sub DragTick() - If DragControl Is Nothing Then Exit Sub + If DragControl Is Nothing Then Return If Not Mouse.LeftButton = MouseButtonState.Pressed Then DragStop() End If End Sub '在鼠标移动时调用,以改变 Slider 位置 Public Sub DragDoing() Handles PanBack.MouseMove - If DragControl Is Nothing Then Exit Sub + If DragControl Is Nothing Then Return If Mouse.LeftButton = MouseButtonState.Pressed Then DragControl.DragDoing() Else @@ -1339,7 +1455,7 @@ Public Class FormMain Public Sub DragStop() '存在其他线程调用的可能性,因此需要确保在 UI 线程运行 RunInUi(Sub() - If DragControl Is Nothing Then Exit Sub + If DragControl Is Nothing Then Return Dim Control = DragControl DragControl = Nothing Control.DragStop() '控件会在该事件中判断 DragControl,所以得放在后面 @@ -1413,7 +1529,8 @@ Public Class FormMain End Function Private Function BtnExtraBack_GetRealChild() As MyScrollViewer If PanMainRight.Child Is Nothing OrElse TypeOf PanMainRight.Child IsNot MyPageRight Then Return Nothing - Return CType(PanMainRight.Child, MyPageRight).PanScroll + Dim Page As MyPageRight = PanMainRight.Child + Return Page.FindName(Page.PanScroll) End Function #End Region diff --git a/Plain Craft Launcher 2/Modules/Base/ModAnimation.vb b/Plain Craft Launcher 2/Modules/Base/ModAnimation.vb index 528f81e..06bf1a6 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModAnimation.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModAnimation.vb @@ -624,6 +624,26 @@ Public Module ModAnimation Return Ease.GetValue(t) End Function End Class + ''' + ''' 以特定速度开始的平滑结束。 + ''' + Public Class AniEaseOutFluentWithInitial + Inherits AniEase + Private ReadOnly alpha As Double '(初速度 / 平均速度) – 1 + ''' 初速度,px/s + ''' 总时长,s + ''' 总路程,px + Public Sub New(InitialPixelPerSecond As Double, TotalSecond As Double, TotalDistance As Double) + Dim v0_norm As Double = InitialPixelPerSecond * TotalSecond / TotalDistance '归一化初速度 + alpha = v0_norm - 1.0 + If alpha < 0 Then alpha = 0 '初速度小于平均速度时,退化为线性 + End Sub + Public Overrides Function GetValue(percent As Double) As Double + Dim p As Double = MathClamp(percent, 0, 1) + If alpha = 0 Then Return p '退化到线性 + Return (alpha + 1) * p / (1 + alpha * p) + End Function + End Class 'Back / 回弹 ''' @@ -807,7 +827,7 @@ Sleeper: Thread.Sleep(1) Loop Catch ex As Exception - Log(ex, "动画帧执行失败", LogLevel.Assert) + Log(ex, "动画帧执行失败", LogLevel.Critical) End Try End Sub, "Animation", ThreadPriority.AboveNormal) End Sub diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index bdfa0c9..8d026e1 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -1,6 +1,6 @@ Imports System.Globalization Imports System.IO.Compression -Imports System.Reflection +Imports System.Net.Sockets Imports System.Runtime.CompilerServices Imports System.Security.Cryptography Imports System.Security.Principal @@ -13,13 +13,13 @@ Public Module ModBase #Region "声明" '下列版本信息由更新器自动修改 - Public Const VersionBaseName As String = "2.9.3" '不含分支前缀的显示用版本名 - Public Const VersionStandardCode As String = "2.9.3." & VersionBranchCode '标准格式的四段式版本号 + Public Const VersionBaseName As String = "2.10.8" '不含分支前缀的显示用版本名 + Public Const VersionStandardCode As String = "2.10.8." & VersionBranchCode '标准格式的四段式版本号 Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换 #If BETA Then - Public Const VersionCode As Integer = 355 'Release + Public Const VersionCode As Integer = 367 'Release #Else - Public Const VersionCode As Integer = 354 'Snapshot + Public Const VersionCode As Integer = 370 'Snapshot #End If '自动生成的版本信息 Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName @@ -745,7 +745,6 @@ Public Module ModBase Dim IsRight As Boolean = FilePath.EndsWithF("\") FilePath = Left(FilePath, Len(FilePath) - 1) GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"})) & If(IsRight, "\", "/") - If GetPathFromFullPath = "" Then Throw New Exception("不包含路径:" & FilePath) Else '是文件路径 GetPathFromFullPath = Left(FilePath, FilePath.LastIndexOfAny({"\", "/"}) + 1) @@ -766,17 +765,6 @@ Public Module ModBase Return FilePath End Function ''' - ''' 从文件路径或者 Url 获取不包含路径与扩展名的文件名。不包含文件名将会抛出异常。 - ''' - Public Function GetFileNameWithoutExtentionFromPath(FilePath As String) As String - Dim Name As String = GetFileNameFromPath(FilePath) - If Name.Contains(".") Then - Return Name.Substring(0, Name.LastIndexOfF(".")) - Else - Return Name - End If - End Function - ''' ''' 从文件夹路径获取文件夹名。 ''' Public Function GetFolderNameFromPath(FolderPath As String) As String @@ -786,13 +774,16 @@ Public Module ModBase End Function '读取、写入、复制文件 + ''' + ''' 复制文件。会自动创建文件夹、会覆盖已有的文件。 + ''' Public Sub CopyFile(FromPath As String, ToPath As String) Try '还原文件路径 - If Not FromPath.Contains(":\") Then FromPath = Path & FromPath - If Not ToPath.Contains(":\") Then ToPath = Path & ToPath + FromPath = If(FromPath.Contains(":\"), FromPath, Path & FromPath) + ToPath = If(ToPath.Contains(":\"), ToPath, Path & ToPath) '如果复制同一个文件则跳过 - If FromPath = ToPath Then Exit Sub + If FromPath = ToPath Then Return '确保目录存在 Directory.CreateDirectory(GetPathFromFullPath(ToPath)) '复制文件 @@ -804,7 +795,6 @@ Public Module ModBase ''' ''' 读取文件,如果失败则返回空数组。 ''' - ''' 文件完整或相对路径。 Public Function ReadFileBytes(FilePath As String, Optional Encoding As Encoding = Nothing) As Byte() Try '还原文件路径 @@ -860,7 +850,7 @@ Public Module ModBase ''' 是否将文件内容追加到当前文件,而不是覆盖它。 Public Sub WriteFile(FilePath As String, Text As String, Optional Append As Boolean = False, Optional Encoding As Encoding = Nothing) '处理相对路径 - If Not FilePath.Contains(":\") Then FilePath = Path & FilePath + FilePath = If(FilePath.Contains(":\"), FilePath, Path & FilePath) '确保目录存在 Directory.CreateDirectory(GetPathFromFullPath(FilePath)) '写入文件 @@ -885,7 +875,7 @@ Public Module ModBase ''' 是否将文件内容追加到当前文件,而不是覆盖它。 Public Sub WriteFile(FilePath As String, Content As Byte(), Optional Append As Boolean = False) '处理相对路径 - If Not FilePath.Contains(":\") Then FilePath = Path & FilePath + FilePath = If(FilePath.Contains(":\"), FilePath, Path & FilePath) '确保目录存在 Directory.CreateDirectory(GetPathFromFullPath(FilePath)) '写入文件 @@ -1282,7 +1272,7 @@ Re: Return Nothing Catch ex As Exception Log(ex, "检查文件出错") - Return GetExceptionSummary(ex) + Return ex.GetBrief() End Try End Function End Class @@ -1308,16 +1298,13 @@ Re: Else '以 zip 方式解压 Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030"))) - Dim TotalCount As Integer = Archive.Entries.Count + Dim TotalCount As Long = Archive.Entries.Count For Each Entry As ZipArchiveEntry In Archive.Entries - If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / TotalCount) + If ProgressIncrementHandler IsNot Nothing AndAlso TotalCount > 0 Then ProgressIncrementHandler(1 / TotalCount) Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName) - If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then - Continue For '不创建空文件夹 - Else - Directory.CreateDirectory(GetPathFromFullPath(DestinationPath)) - Entry.ExtractToFile(DestinationPath, True) - End If + If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then Continue For '不创建空文件夹 + Directory.CreateDirectory(GetPathFromFullPath(DestinationPath)) + Entry.ExtractToFile(DestinationPath, True) Next End Using End If @@ -1328,11 +1315,20 @@ Re: ''' Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer If Not Directory.Exists(Path) Then Return 0 + If Not Path.EndsWithF("\") Then Path &= "\" + If Path.EndsWith(":\") OrElse + Path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) & "\" OrElse + Path = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) & "\" OrElse + Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) & "\" Then '#7030 + Throw New UnauthorizedAccessException("这是重要的系统文件夹,无法删除:" & Path) + End If + '实际删除 Dim DeletedCount As Integer = 0 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 @@ -1363,8 +1359,9 @@ RetryFile: RetryDir: Try Directory.Delete(Path, True) + Catch ex As DirectoryNotFoundException Catch ex As Exception - If Not RetriedDir Then + If Not RetriedDir AndAlso Not RunInUi() Then RetriedDir = True Log(ex, $"删除文件夹失败,将在 0.3s 后重试({Path})") Thread.Sleep(300) @@ -1386,17 +1383,17 @@ RetryDir: ToPath = ToPath.Replace("/", "\") If Not ToPath.EndsWithF("\") Then ToPath &= "\" Dim AllFiles = EnumerateFiles(FromPath).ToList - Dim FileCount As Integer = AllFiles.Count + Dim FileCount As Long = AllFiles.Count For Each File In AllFiles CopyFile(File.FullName, File.FullName.Replace(FromPath, ToPath)) - If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / FileCount) + If ProgressIncrementHandler IsNot Nothing AndAlso FileCount > 0 Then ProgressIncrementHandler(1 / FileCount) Next End Sub ''' ''' 遍历文件夹中的所有文件。 ''' Public Function EnumerateFiles(Directory As String) As IEnumerable(Of FileInfo) - Dim Info As New DirectoryInfo(ShortenPath(Directory)) + Dim Info As New DirectoryInfo(Directory) If Not Info.Exists Then Return New List(Of FileInfo) Return Info.EnumerateFiles("*", SearchOption.AllDirectories) End Function @@ -1404,13 +1401,15 @@ RetryDir: ''' ''' 若路径长度大于指定值,则将长路径转换为短路径。 ''' - Public Function ShortenPath(LongPath As String, Optional ShortenThreshold As Integer = 247) As String - If LongPath.Length <= ShortenThreshold Then Return LongPath + Public Function ShortenPath(LongPath As String) As String + If LongPath.Length <= 200 OrElse LongPath.StartsWithF("http", True) Then Return LongPath + If Not Directory.Exists(LongPath) AndAlso Not File.Exists(LongPath) Then Return LongPath Dim ShortPath As New StringBuilder(260) GetShortPathName(LongPath, ShortPath, 260) - Return ShortPath.ToString + ShortenPath = ShortPath.ToString + If String.IsNullOrEmpty(ShortenPath) Then Return LongPath End Function - Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (ByVal lpszLongPath As String, ByVal lpszShortPath As StringBuilder, ByVal cchBuffer As Integer) As Integer + Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (lpszLongPath As String, lpszShortPath As StringBuilder, cchBuffer As Integer) As Integer #End Region @@ -1419,10 +1418,10 @@ RetryDir: Public vbRQ As Char = Convert.ToChar(8221) ''' - ''' 提取 Exception 的具体描述与堆栈。 + ''' 提取 Exception 与 Inner Exception 的详细描述与堆栈信息。返回内容总是多于一行。 ''' ''' 是否必须显示所有堆栈。通常用于判定堆栈信息。 - Public Function GetExceptionDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String + Public Function GetDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String If Ex Is Nothing Then Return "无可用错误信息!" '获取最底层的异常 @@ -1448,32 +1447,18 @@ RetryDir: IsInner = True Loop - '常见错误(记得同时修改下面的) - Dim CommonReason As String = Nothing - If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then - CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。" - ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then - CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。" - ElseIf TypeOf InnerEx Is OutOfMemoryException Then - CommonReason = "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。" - ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then - CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。" - ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", - "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) DescList.Any(Function(l) l.Contains(s))) Then - CommonReason = "你的网络环境不佳,导致难以连接到服务器。请检查网络,多重试几次,或尝试使用 VPN。" - End If - '构造输出信息 - If CommonReason Is Nothing Then + Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList) + If UsualReason Is Nothing Then Return DescList.Join(vbCrLf) Else - Return CommonReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf) + Return UsualReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf) End If End Function ''' - ''' 提取 Exception 描述,汇总到一行。 + ''' 提取 Exception 与 Inner Exception 的描述,汇总到一行。 ''' - Public Function GetExceptionSummary(Ex As Exception) As String + Public Function GetBrief(Ex As Exception) As String If Ex Is Nothing Then Return "无可用错误信息!" '获取最底层的异常 @@ -1491,29 +1476,32 @@ RetryDir: DescList = DescList.Distinct.ToList Dim Desc As String = Join(DescList, vbCrLf & "→ ") - '常见错误(记得同时修改上面的) - Dim CommonReason As String = Nothing - If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then - CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。" - ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then - CommonReason = "PCL 的权限不足。请尝试右键 PCL,选择以管理员身份运行。" - ElseIf TypeOf InnerEx Is OutOfMemoryException Then - CommonReason = "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。" - ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then - CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。" - ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝", - "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) Desc.Contains(s)) Then - CommonReason = "你的网络环境不佳,导致难以连接到服务器。请检查网络,多重试几次,或尝试使用 VPN。" - End If - '构造输出信息 - If CommonReason IsNot Nothing Then - Return CommonReason & "详细错误:" & DescList.First + Dim UsualReason As String = AnalyzeUsualReason(InnerEx, Ex, DescList) + If UsualReason IsNot Nothing Then + Return UsualReason & "详细错误:" & DescList.First Else DescList.Reverse() '让最深层错误在最左边 Return Join(DescList, " ← ") End If End Function + Private Function AnalyzeUsualReason(InnerEx As Exception, OuterEx As Exception, DescList As List(Of String)) As String + If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is BadImageFormatException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then + Return "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。若无法安装,请先卸载较新版本的 .NET Framework,然后再尝试安装。" + ElseIf TypeOf InnerEx Is UnauthorizedAccessException Then + Return "PCL 的权限不足。请尝试右键 PCL 选择以管理员身份运行。" + ElseIf TypeOf InnerEx Is OutOfMemoryException Then + Return "你的电脑运行内存不足,导致 PCL 无法继续运行。请在关闭一部分不需要的程序后再试。" + ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then + Return "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。" + ElseIf TypeOf InnerEx Is SocketException AndAlso DescList.Any(Function(l) l.Contains("WSAStartup")) Then + Return "请尝试卸载中国移动云盘,然后再试。" + ElseIf OuterEx.IsNetworkRelated() Then + Return "你的网络环境不佳,请稍后再试,或使用 VPN 改善网络环境。" + Else + Return Nothing + End If + End Function ''' ''' 返回一个枚举对应的字符串。 @@ -1550,12 +1538,12 @@ RetryDir: ''' ''' 获取 JSON 对象。 ''' - Public Function GetJson(Data As String) + Public Function GetJson(Data As String) As JToken Try Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local}) Catch ex As Exception Dim Length As Integer = If(Data, "").Length - Throw New Exception("格式化 JSON 失败:" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data)) + Throw New Exception("格式化 JSON 失败:" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data), ex) End Try End Function @@ -1982,8 +1970,8 @@ RetryDir: #Region "系统" ''' - ''' 线程安全的,可以直接使用 For Each 的 List。 - ''' 在使用 For Each 循环时,列表的结果可能并非最新,但不会抛出异常。 + ''' 线程安全的 List。 + ''' 通过在 For Each 循环中使用一个浅表副本规避多线程操作或移除自身导致的异常。 ''' Public Class SafeList(Of T) Inherits SynchronizedCollection(Of T) @@ -2015,8 +2003,8 @@ RetryDir: End Class ''' - ''' 线程安全的,可以直接使用 For Each 的字典。 - ''' 在使用 For Each 循环时,字典的结果可能并非最新,但不会抛出异常。 + ''' 线程安全的字典。 + ''' 通过在 For Each 循环中使用一个浅表副本规避多线程操作或移除自身导致的异常。 ''' Public Class SafeDictionary(Of TKey, TValue) Implements IDictionary(Of TKey, TValue) @@ -2028,8 +2016,8 @@ RetryDir: '构造函数 Public Sub New() End Sub - Public Sub New(data As IEnumerable(Of KeyValuePair(Of TKey, TValue))) - For Each DataItem In data + Public Sub New(Data As IEnumerable(Of KeyValuePair(Of TKey, TValue))) + For Each DataItem In Data _Dictionary.Add(DataItem.Key, DataItem.Value) Next End Sub @@ -2130,6 +2118,19 @@ RetryDir: End Function End Class + ''' + ''' 将二维数组转换为字典。 + ''' + Public Function ToDictionary(Of T)(arr As T(,)) As Dictionary(Of T, T) + Dim result As New Dictionary(Of T, T) + If arr.Length = 0 Then Return result + If arr.GetLength(1) <> 2 Then Throw New ArgumentException("数组必须为两列,第一列为 Key,第二列为 Value。") + For i As Integer = 0 To arr.GetLength(0) - 1 + result(arr(i, 0)) = arr(i, 1) + Next + Return result + End Function + ''' ''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。 ''' @@ -2172,16 +2173,16 @@ RetryDir: ''' 返回程序的返回代码,如果运行失败将抛出异常。 ''' Public Function RunAsAdmin(Argument As String) As Integer - Dim NewProcess = Process.Start(New ProcessStartInfo(PathWithName) With {.Verb = "runas", .Arguments = Argument}) + Dim NewProcess = StartProcess(New ProcessStartInfo(PathWithName) With {.Verb = "runas", .Arguments = Argument}) NewProcess.WaitForExit() Return NewProcess.ExitCode End Function ''' - ''' 判断当前系统语言是否为 zh-CN。 + ''' 判断当前系统语言是否为中文。 ''' Public Function IsSystemLanguageChinese() As Boolean - Return CultureInfo.CurrentCulture.Name = "zh-CN" OrElse CultureInfo.CurrentUICulture.Name = "zh-CN" + Return CultureInfo.CurrentCulture.Name.StartsWithF("zh-") OrElse CultureInfo.CurrentUICulture.Name.StartsWithF("zh-") End Function Private Uuid As Integer = 1 @@ -2224,6 +2225,9 @@ NextElement: Next i Return ResultArray End Function + Public Function Any(Arr As UIElementCollection) As Boolean + Return Arr?.Count > 0 + End Function ''' ''' 获取格式类似于“11:08:52.037”的当前时间的字符串。 @@ -2328,42 +2332,21 @@ NextElement: End Class ''' - ''' 前台运行文件。 + ''' 启动进程并返回进程实例,若失败则抛出异常。 + ''' 会自动进行长路径处理。 ''' - ''' 文件名。可以为“notepad”等缩写。 - ''' 运行参数。 - Public Sub ShellOnly(FileName As String, Optional Arguments As String = "") - FileName = ShortenPath(FileName) - Using Program As New Process - Program.StartInfo.Arguments = Arguments - Program.StartInfo.FileName = FileName - Log("[System] 执行外部命令:" & FileName & " " & Arguments) - Program.Start() - End Using - End Sub + Public Function StartProcess(FileName As String, Optional Arguments As String = "") As Process + Return StartProcess(New ProcessStartInfo With {.FileName = FileName, .Arguments = Arguments}) + End Function ''' - ''' 前台运行文件并返回返回值。 + ''' 启动进程并返回进程实例,若失败则抛出异常。 + ''' 会自动进行长路径处理。 ''' - ''' 文件名。可以为“notepad”等缩写。 - ''' 运行参数。 - ''' 等待该程序结束的最长时间(毫秒)。超时会返回 Result.Timeout。 - Public Function ShellAndGetExitCode(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000) As ProcessReturnValues - Try - Using Program As New Process - Program.StartInfo.Arguments = Arguments - Program.StartInfo.FileName = FileName - Log("[System] 执行外部命令并等待返回码:" & FileName & " " & Arguments) - Program.Start() - If Program.WaitForExit(Timeout) Then - Return Program.ExitCode - Else - Return ProcessReturnValues.Timeout - End If - End Using - Catch ex As Exception - Log(ex, "执行命令失败:" & FileName, LogLevel.Msgbox) - Return ProcessReturnValues.Fail - End Try + Public Function StartProcess(StartInfo As ProcessStartInfo) As Process + StartInfo.FileName = ShortenPath(StartInfo.FileName) + If Not String.IsNullOrEmpty(StartInfo.WorkingDirectory) Then StartInfo.WorkingDirectory = ShortenPath(StartInfo.WorkingDirectory) + Log("[System] 启动进程:" & StartInfo.FileName & " " & StartInfo.Arguments) + Return Process.Start(StartInfo) End Function ''' ''' 静默运行文件并返回输出流字符串。执行失败会抛出异常。 @@ -2371,10 +2354,11 @@ NextElement: ''' 文件名。可以为“notepad”等缩写。 ''' 运行参数。 ''' 等待该程序结束的最长时间(毫秒)。超时会抛出错误。 - Public Function ShellAndGetOutput(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000, Optional WorkingDirectory As String = Nothing) As String + Public Function StartProcessAndGetOutput(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000, + Optional WorkingDirectory As String = Nothing) As String Dim Info = New ProcessStartInfo With { .Arguments = Arguments, - .FileName = FileName, + .FileName = ShortenPath(FileName), .UseShellExecute = False, .CreateNoWindow = True, .RedirectStandardError = True, @@ -2389,7 +2373,7 @@ NextElement: End If End If Log("[System] 执行外部命令并等待返回结果:" & FileName & " " & Arguments) - Using Program As New Process() With {.StartInfo = Info} + Using Program As New Process With {.StartInfo = Info} Program.Start() Dim Result As String = Program.StandardOutput.ReadToEnd & Program.StandardError.ReadToEnd Program.WaitForExit(Timeout) @@ -2457,7 +2441,7 @@ NextElement: ''' Public Sub RunInThread(Action As Action) If RunInUi() Then - RunInNewThread(Action, "Runtime Invoke " & GetUuid() & "#") + RunInNewThread(Action, "Invoke " & GetUuid()) Else Action() End If @@ -2491,17 +2475,14 @@ NextElement: ''' ''' 尝试从字典中获取某项,如果该项不存在,则返回默认值。 ''' - Public Function GetOrDefault(Of TKey, TValue)(Dict As Dictionary(Of TKey, TValue), Key As TKey, Optional DefaultValue As TValue = Nothing) As TValue - If Dict.ContainsKey(Key) Then - Return Dict(Key) - Else - Return DefaultValue - End If + Public Function GetOrDefault(Of TKey, TValue)(Dict As IDictionary(Of TKey, TValue), Key As TKey, Optional DefaultValue As TValue = Nothing) As TValue + Dim Result As TValue + Return If(Dict.TryGetValue(Key, Result), Result, DefaultValue) End Function ''' ''' 将某项添加到以列表作为值的字典中。 ''' - Public Sub AddToList(Of TKey, TValue)(Dict As Dictionary(Of TKey, List(Of TValue)), Key As TKey, Value As TValue) + Public Sub AddToList(Of TKey, TValue)(Dict As IDictionary(Of TKey, List(Of TValue)), Key As TKey, Value As TValue) If Dict.ContainsKey(Key) Then Dict(Key).Add(Value) Else @@ -2549,7 +2530,7 @@ NextElement: Throw New Exception(Url & " 不是一个有效的网址,它必须以 http 开头!") End If Log("[System] 正在打开网页:" & Url) - Process.Start(Url) + StartProcess(Url) Catch ex As Exception Log(ex, "无法打开网页(" & Url & ")") ClipboardSet(Url, False) @@ -2563,12 +2544,12 @@ NextElement: ''' Public Sub OpenExplorer(Location As String) Try - Location = ShortenPath(Location.Replace("/", "\").Trim(" "c, """"c)) + Location = Location.Replace("/", "\").Trim(" "c, """"c) Log("[System] 正在打开资源管理器:" & Location) If Location.EndsWith("\") Then - ShellOnly(Location) + StartProcess(Location) Else - ShellOnly("explorer", $"/select,""{Location}""") + StartProcess("explorer", $"/select,""{Location}""") End If Catch ex As Exception Log(ex, "打开资源管理器失败,请尝试关闭安全软件(如 360 安全卫士)", LogLevel.Msgbox) @@ -2620,10 +2601,6 @@ Retry: ''' 相对增减控件的左边距。 ''' Public Sub DeltaLeft(control As FrameworkElement, newValue As Double) - '安全性检查 - DebugAssert(Not Double.IsNaN(newValue)) - DebugAssert(Not Double.IsInfinity(newValue)) - If TypeOf control Is Window Then '窗口改变 CType(control, Window).Left += newValue @@ -2635,8 +2612,6 @@ Retry: Case HorizontalAlignment.Right control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right - newValue, control.Margin.Bottom) 'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom) - Case Else - DebugAssert(False) End Select End If End Sub @@ -2644,17 +2619,12 @@ Retry: ''' 设置控件的左边距。(仅针对置左控件) ''' Public Sub SetLeft(control As FrameworkElement, newValue As Double) - DebugAssert(control.HorizontalAlignment = HorizontalAlignment.Left) control.Margin = New Thickness(newValue, control.Margin.Top, control.Margin.Right, control.Margin.Bottom) End Sub ''' ''' 相对增减控件的上边距。 ''' Public Sub DeltaTop(control As FrameworkElement, newValue As Double) - '安全性检查 - DebugAssert(Not Double.IsNaN(newValue)) - DebugAssert(Not Double.IsInfinity(newValue)) - If TypeOf control Is Window Then '窗口改变 CType(control, Window).Top += newValue @@ -2666,12 +2636,9 @@ Retry: Case VerticalAlignment.Bottom control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, control.Margin.Right, control.Margin.Bottom - newValue) 'control.Margin = New Thickness(control.Margin.Left, control.Margin.Top, CType(control.Parent, Object).ActualWidth - control.ActualWidth - newValue, control.Margin.Bottom) - Case Else - DebugAssert(False) End Select End If - - 'If Double.IsNaN(newValue) OrElse Double.IsInfinity(newValue) Then Exit Sub '安全性检查 + 'If Double.IsNaN(newValue) OrElse Double.IsInfinity(newValue) Then Return '安全性检查 'Select Case control.VerticalAlignment ' Case VerticalAlignment.Top, VerticalAlignment.Stretch, VerticalAlignment.Center ' control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom) @@ -2684,7 +2651,6 @@ Retry: ''' 设置控件的顶边距。(仅针对置上控件) ''' Public Sub SetTop(control As FrameworkElement, newValue As Double) - DebugAssert(control.VerticalAlignment = VerticalAlignment.Top) control.Margin = New Thickness(control.Margin.Left, newValue, control.Margin.Right, control.Margin.Bottom) End Sub @@ -2755,11 +2721,11 @@ Retry: Using Reader As New XamlXmlReader(Stream) While Reader.Read() For Each BlackListType In {GetType(WebBrowser), GetType(Frame), GetType(MediaElement), GetType(ObjectDataProvider), GetType(XamlReader), GetType(Window), GetType(XmlDataProvider)} - If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 类型。") - If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 值。") + If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 类型。") + If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 值。") Next For Each BlackListMember In {"Code", "FactoryMethod", "Static"} - If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListMember} 成员。") + If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListMember} 成员。") Next End While End Using @@ -2844,9 +2810,10 @@ Retry: ''' Feedback = 5 ''' - ''' 弹窗,结束程序。 + ''' 弹出 Windows 原生弹窗,要求反馈。在无法保证 WPF 窗口能正常运行时使用此级别。 + ''' 在第二次触发后会直接结束程序。 ''' - Assert = 6 + Critical = 6 End Enum Private LogList As New StringBuilder Private LogWritter As StreamWriter @@ -2889,7 +2856,7 @@ Retry: Private ReadOnly LogFlushLock As New Object '防止外部调用 LogFlush 时同时输出多次日志 Public Sub LogFlush() On Error Resume Next - If LogWritter Is Nothing Then Exit Sub + If LogWritter Is Nothing Then Return Dim Log As String = Nothing SyncLock LogFlushLock If LogList.Length > 0 Then @@ -2905,6 +2872,7 @@ Retry: End Sub Private ReadOnly LogListLock As New Object '防止日志乱码,只在调试模式下启用 + Private IsCriticalErrorTriggered As Boolean = False ''' ''' 输出 Log。 ''' @@ -2915,7 +2883,7 @@ Retry: '处理错误会导致再次调用 Log() 导致无限循环 '输出日志 - Dim AppendText As String = "[" & GetTimeNow() & "] " & Text & vbCrLf '减轻同步锁占用 + Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Text}{vbCrLf}" '减轻同步锁占用 If ModeDebug Then SyncLock LogListLock LogList.Append(AppendText) @@ -2926,7 +2894,7 @@ Retry: #If DEBUG Then Console.Write(AppendText) #End If - If IsProgramEnded OrElse Level = LogLevel.Normal Then Exit Sub + If IsProgramEnded OrElse Level = LogLevel.Normal Then Return '去除前缀 Text = Text.RegexReplace("\[[^\]]+?\] ", "") @@ -2953,22 +2921,26 @@ Retry: Else MyMsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True) End If - Case LogLevel.Assert - Dim Time As Long = GetTimeTick() + Case LogLevel.Critical + If IsCriticalErrorTriggered Then + FormMain.EndProgramForce(ProcessReturnValues.Exception) + Return + End If + IsCriticalErrorTriggered = True If CanFeedback(False) Then If MsgBox(Text & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True) Else MsgBox(Text & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title) End If - If GetTimeTick() - Time < 1500 Then - '弹窗无法保留 - Log("[System] PCL 已崩溃:" & vbCrLf & Text) - FormMain.EndProgramForce(ProcessReturnValues.Exception) - Else - FormMain.EndProgramForce(ProcessReturnValues.Fail) - End If End Select + '上报 + If Level >= LogLevel.Hint Then + Telemetry("PCL 错误", + "Level", GetStringFromEnum(Level), + "Title", FilterUserName(FilterAccessToken(Title, "*"), "*"), + "Exception", FilterUserName(FilterAccessToken(Text, "*"), "*")) + End If End Sub ''' ''' 输出错误信息。 @@ -2976,13 +2948,13 @@ Retry: ''' 错误描述。会在处理时在末尾加入冒号。 Public Sub Log(Ex As Exception, Desc As String, Optional Level As LogLevel = LogLevel.Debug, Optional Title As String = "出现错误") On Error Resume Next - If TypeOf Ex Is ThreadInterruptedException Then Exit Sub + If TypeOf Ex Is ThreadInterruptedException Then Return '获取错误信息 - Dim ExFull As String = Desc & ":" & GetExceptionDetail(Ex) + Dim ExFull As String = $"{Desc}:{Ex.GetDetail()}" '输出日志 - Dim AppendText As String = "[" & GetTimeNow() & "] " & Desc & ":" & GetExceptionDetail(Ex, True) & vbCrLf '减轻同步锁占用 + Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{Ex.GetDetail(True)}{vbCrLf}" '减轻同步锁占用 If ModeDebug Then SyncLock LogListLock LogList.Append(AppendText) @@ -2993,26 +2965,26 @@ Retry: #If DEBUG Then Console.Write(AppendText) #End If - If IsProgramEnded Then Exit Sub + If IsProgramEnded Then Return '输出提示 Select Case Level Case LogLevel.Normal #If DEBUG Then Case LogLevel.Developer - Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex) + Dim ExLine As String = Desc & ":" & Ex.GetBrief() Hint("[开发者模式] " & ExLine, HintType.Info, False) Case LogLevel.Debug - Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex) + Dim ExLine As String = Desc & ":" & Ex.GetBrief() Hint("[调试模式] " & ExLine, HintType.Info, False) #Else Case LogLevel.Developer Case LogLevel.Debug - Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex) + Dim ExLine As String = Desc & ":" & Ex.GetBrief() If ModeDebug Then Hint("[调试模式] " & ExLine, HintType.Info, False) #End If Case LogLevel.Hint - Dim ExLine As String = Desc & ":" & GetExceptionSummary(Ex) + Dim ExLine As String = Desc & ":" & Ex.GetBrief() Hint(ExLine, HintType.Critical, False) Case LogLevel.Msgbox MyMsgBox(ExFull, Title, IsWarn:=True) @@ -3022,22 +2994,26 @@ Retry: Else MyMsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", Title, IsWarn:=True) End If - Case LogLevel.Assert - Dim Time As Long = GetTimeTick() + Case LogLevel.Critical + If IsCriticalErrorTriggered Then + FormMain.EndProgramForce(ProcessReturnValues.Exception) + Return + End If + IsCriticalErrorTriggered = True If CanFeedback(False) Then If MsgBox(ExFull & vbCrLf & vbCrLf & "是否反馈此问题?如果不反馈,这个问题可能永远无法得到解决!", MsgBoxStyle.Critical + MsgBoxStyle.YesNo, Title) = MsgBoxResult.Yes Then Feedback(False, True) Else MsgBox(ExFull & vbCrLf & vbCrLf & "将 PCL 更新至最新版或许可以解决这个问题……", MsgBoxStyle.Critical, Title) End If - If GetTimeTick() - Time < 1500 Then - '弹窗无法保留 - Log("[System] PCL 已崩溃:" & vbCrLf & ExFull) - FormMain.EndProgramForce(ProcessReturnValues.Exception) - Else - FormMain.EndProgramForce(ProcessReturnValues.Fail) - End If End Select + '上报 + If Level >= LogLevel.Hint Then + Telemetry("PCL 异常", + "Level", GetStringFromEnum(Level), + "Title", FilterUserName(FilterAccessToken(Title, "*"), "*"), + "Exception", FilterUserName(FilterAccessToken(ExFull, "*"), "*")) + End If End Sub '反馈 @@ -3047,7 +3023,7 @@ Retry: If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then OpenExplorer(Path & "PCL\Log1.txt") End If - OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/") + OpenWebsite("https://github.com/Meloong-Git/PCL/issues/") End Sub Public Function CanFeedback(ShowHint As Boolean) As Boolean If False.Equals(PageSetupSystem.IsLauncherNewest) Then @@ -3074,9 +3050,23 @@ Retry: "文件位置:" & Path) End Sub - '断言 - Public Sub DebugAssert(Exp As Boolean) - If Not Exp Then Throw New Exception("断言命中") + '遥测 + Public Sub Telemetry([Event] As String, ParamArray Datas As String()) + If Not Setup.Get("SystemSystemTelemetry") Then Return '用户关闭了遥测 + If Not ClsBaseUrl.StartsWithF("http") Then Return '开源版没有设置遥测地址 + RunInNewThread( + Sub() + Try + Log($"[System] 匿名数据上报:{[Event]}") + Dim Url As String = $"{ClsBaseUrl}&Event={WebUtility.UrlEncode([Event])}" + For i = 0 To Datas.Length - 1 Step 2 + Url &= "&" & WebUtility.UrlEncode(Datas(i)) & "=" & WebUtility.UrlEncode(Datas(i + 1).Replace(vbCrLf, vbLf)) + Next + NetRequestByClient(Url, MakeLog:=False) + Catch ex As Exception + Log(ex, "匿名数据上报失败") + End Try + End Sub, "Telemetry", ThreadPriority.Lowest) End Sub '获取当前的堆栈信息 diff --git a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb index a408f14..88a5d09 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb @@ -85,7 +85,7 @@ Return _State End Get Set(value As LoadState) - If _State = value Then Exit Property + If _State = value Then Return Dim OldState = _State If value = LoadState.Finished AndAlso Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000)) _State = value @@ -112,7 +112,7 @@ Return _LoadingState End Get Set(value As MyLoading.MyLoadingState) - If _LoadingState = value Then Exit Property + If _LoadingState = value Then Return Dim OldState = _LoadingState _LoadingState = value RaiseEvent LoadingStateChanged(value, OldState) @@ -154,7 +154,7 @@ End Select End Get Set(value As Double) - If _Progress = value Then Exit Property + If _Progress = value Then Return Dim OldValue = _Progress _Progress = value RaiseEvent ProgressChanged(value, OldValue) @@ -184,7 +184,7 @@ Thread.Sleep(10) Loop If State = LoadState.Finished Then - Exit Sub + Return ElseIf State = LoadState.Aborted Then Throw New ThreadInterruptedException("加载器执行已中断。") ElseIf IsNothing([Error]) Then @@ -207,7 +207,7 @@ If Timeout < 0 Then Throw New TimeoutException(TimeoutMessage) Loop If State = LoadState.Finished Then - Exit Sub + Return ElseIf State = LoadState.Aborted Then Throw New ThreadInterruptedException("加载器执行已中断。") ElseIf IsNothing([Error]) Then @@ -290,11 +290,12 @@ '检验输入以确定情况 If IsForceRestart Then Return True '强制要求重启 If ((Input Is Nothing) <> (Me.Input Is Nothing)) OrElse (Input IsNot Nothing AndAlso Not Input.Equals(Me.Input)) Then Return True '输入不同 - If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束 - (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '没有超时 - Return False '则不重试 + If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束… + (IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse + LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '…且没有超时… + Return False '…则不重试 Else - Return True '需要开始 + Return True '否则需要重启 End If End Function Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False) @@ -308,48 +309,49 @@ Progress = -1 End SyncLock Else - Exit Sub + Return End If LastRunningThread = New Thread( - Sub() - Try - IsForceRestarting = IsForceRestart - If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动") - LoadDelegate(Me) - If IsAborted Then - Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})", LogLevel.Developer) - Else - RaisePreviewFinish() - State = LoadState.Finished - LastFinishedTime = GetTimeTick() '未中断,本次输出有效 - End If - Catch ex As CancelledException - If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发取消中断,已完成 {Math.Round(Progress * 100)}%") - If Not IsAborted Then State = LoadState.Aborted - Catch ex As ThreadInterruptedException - If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发线程中断,已完成 {Math.Round(Progress * 100)}%") - '如果线程是因为判断到 IsAborted 而提前中止,则代表已有新线程被重启,此时不应当改为 Aborted - '如果线程是在没有 IsAborted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Aborted - If Not IsAborted Then State = LoadState.Aborted - Catch ex As Exception - If IsAborted Then Exit Sub - Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 出错,已完成 {Math.Round(Progress * 100)}%", LogLevel.Developer) - [Error] = ex - State = LoadState.Failed - End Try - End Sub) With {.Name = Name, .Priority = ThreadPriority} + Sub() + Try + IsForceRestarting = IsForceRestart + If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动") + LoadDelegate(Me) + If IsAborted Then + Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})", LogLevel.Developer) + Return + End If + If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已完成") + RaisePreviewFinish() + State = LoadState.Finished + LastFinishedTime = GetTimeTick() '未中断,本次输出有效 + Catch ex As CancelledException + If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发取消中断,已完成 {Math.Round(Progress * 100)}%") + If Not IsAborted Then State = LoadState.Aborted + Catch ex As ThreadInterruptedException + If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发线程中断,已完成 {Math.Round(Progress * 100)}%") + '如果线程是因为判断到 IsAborted 而提前中止,则代表已有新线程被重启,此时不应当改为 Aborted + '如果线程是在没有 IsAborted 时手动引发了 ThreadInterruptedException,则代表没有重启线程,这通常代表用户手动取消,应当改为 Aborted + If Not IsAborted Then State = LoadState.Aborted + Catch ex As Exception + If IsAborted Then Return + Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 出错,已完成 {Math.Round(Progress * 100)}%", LogLevel.Developer) + [Error] = ex + State = LoadState.Failed + End Try + End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority} LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted End Sub Public Overrides Sub Abort() - If State <> LoadState.Loading Then Exit Sub + If State <> LoadState.Loading Then Return SyncLock LockState State = LoadState.Aborted End SyncLock TriggerThreadAbort() End Sub Private Sub TriggerThreadAbort() - If LastRunningThread Is Nothing Then Exit Sub + If LastRunningThread Is Nothing Then Return If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({LastRunningThread.ManagedThreadId}) 已中断") If LastRunningThread.IsAlive Then LastRunningThread.Interrupt() LastRunningThread = Nothing @@ -419,7 +421,7 @@ '改变状态 SyncLock LockState If State = LoadState.Loading Then - Exit Sub + Return Else State = LoadState.Loading End If @@ -439,7 +441,7 @@ If State = LoadState.Loading OrElse State = LoadState.Waiting Then State = LoadState.Aborted Else - Exit Sub + Return End If End SyncLock RunInThread( @@ -470,7 +472,7 @@ Case Else '完蛋,出错了 SyncLock LockState - If State >= LoadState.Finished Then Exit Sub + If State >= LoadState.Finished Then Return [Error] = New Exception(Loader.Name & "失败", Loader.Error) State = Loader.State End SyncLock @@ -478,14 +480,14 @@ Loader.Abort() Next FrmMain.BtnExtraDownload.ShowRefresh() - Exit Sub + Return End Select End Sub ''' ''' 触发一次更新,以启动新加载器或完成。 ''' Private Sub Update() - If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Aborted Then Exit Sub + If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Aborted Then Return Dim IsFinished As Boolean = True Dim Blocked As Boolean = False Dim Input As Object = Me.Input @@ -584,16 +586,11 @@ Restart: Try Dim NewState As Shell.TaskbarItemProgressState Dim NewProgress As Double = LoaderTaskbarProgressGet() - '检查任务是否完成,若完成则移除 - '外显任务是否已经全部完成 - Dim IsAllDownloadTaskCompleted As Boolean = True - For Each Loader In LoaderTaskbar.ToList() - If Loader.State = LoadState.Loading Then IsAllDownloadTaskCompleted = False - Next - '若单个任务已中止或全部任务已完成,则刷新并移除 - For Each Task In LoaderTaskbar.ToList() - If IsAllDownloadTaskCompleted OrElse Task.State = LoadState.Aborted OrElse Task.State = LoadState.Waiting Then - If FrmSpeedLeft IsNot Nothing Then FrmSpeedLeft.TaskRefresh(Task) + '若单个任务已中止,或全部任务已完成,则刷新并移除 + For Each Task In LoaderTaskbar + If LoaderTaskbar.All(Function(l) l.State <> LoadState.Loading) OrElse + (Task.State = LoadState.Waiting OrElse Task.State = LoadState.Aborted) Then + FrmSpeedLeft?.TaskRefresh(Task) LoaderTaskbar.Remove(Task) Log($"[Taskbar] {Task.Name} 已移出任务列表") End If @@ -626,7 +623,7 @@ Restart: Public Function LoaderTaskbarProgressGet() As Double Try If Not LoaderTaskbar.Any Then Return 1 - Return MathClamp(LoaderTaskbar.Select(Function(e) e.Progress).Average(), 0, 1) + Return MathClamp(LoaderTaskbar.Select(Function(l) l.Progress).Average(), 0, 1) Catch ex As Exception Log(ex, "获取任务栏进度出错", LogLevel.Feedback) Return 0.5 diff --git a/Plain Craft Launcher 2/Modules/Base/ModNet.vb b/Plain Craft Launcher 2/Modules/Base/ModNet.vb index ba0e019..806da0b 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModNet.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModNet.vb @@ -1,102 +1,55 @@ -Public Module ModNet - Public Const NetDownloadEnd As String = ".PCLDownloading" +Imports System.Net.Http.Headers +Imports System.Net.Sockets +Imports System.Runtime.CompilerServices +Imports System.Threading.Tasks + +Public Module ModNet + +#Region "网络请求" ''' - ''' 测试 Ping。失败则返回 -1。 + ''' 发送一次网络请求并获取返回内容。 ''' - Public Function Ping(Ip As String, Optional Timeout As Integer = 10000, Optional MakeLog As Boolean = True) As Integer - Dim PingResult As NetworkInformation.PingReply + Public Function NetRequestByClient(Url As String, Optional Method As HttpMethod = Nothing, + Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*", + Optional Timeout As Integer = 25000, Optional Headers As String(,) = Nothing, Optional RequireJson As Boolean = False, + Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders As Boolean = False, Optional MakeLog As Boolean = True) As String + If Method Is Nothing Then Method = HttpMethod.Get + If MakeLog Then Log("[Net] 发起网络请求(" & Method.Method & "," & Url & "),最大超时 " & Timeout) Try - PingResult = (New NetworkInformation.Ping).Send(Ip) - Catch ex As Exception - If MakeLog Then Log("[Net] Ping " & Ip & " 失败:" & ex.Message) - Return -1 - End Try - If PingResult.Status = NetworkInformation.IPStatus.Success Then - If MakeLog Then Log("[Net] Ping " & Ip & " 结束:" & PingResult.RoundtripTime & "ms") - Return PingResult.RoundtripTime - Else - If MakeLog Then Log("[Net] Ping " & Ip & " 失败") - Return -1 - End If - End Function - - ''' - ''' 以 WebClient 获取网页源代码。会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。 - ''' - ''' 网页的 Url。 - ''' 网页的编码,通常为 UTF-8。 - Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Optional Accept As String = "application/json, text/javascript, */*; q=0.01", Optional UseBrowserUserAgent As Boolean = False) As String - Dim RetryCount As Integer = 0 - Dim RetryException As Exception = Nothing - Dim StartTime As Long = GetTimeTick() - Try -Retry: - Select Case RetryCount - Case 0 '正常尝试 - Return NetGetCodeByClient(Url, Encoding, 10000, Accept, UseBrowserUserAgent) - Case 1 '慢速重试 - Thread.Sleep(500) - Return NetGetCodeByClient(Url, Encoding, 30000, Accept, UseBrowserUserAgent) - Case Else '快速重试 - If GetTimeTick() - StartTime > 5500 Then - '若前两次加载耗费 5 秒以上,才进行重试 - Thread.Sleep(500) - Return NetGetCodeByClient(Url, Encoding, 4000, Accept, UseBrowserUserAgent) - Else - Throw RetryException - End If - End Select - Catch ex As Exception - Select Case RetryCount - Case 0 - RetryException = ex - RetryCount += 1 - GoTo Retry - Case 1 - RetryCount += 1 - GoTo Retry - Case Else - Throw - End Select - End Try - End Function - Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Timeout As Integer, Accept As String, Optional UseBrowserUserAgent As Boolean = False) As String - Url = SecretCdnSign(Url) - Log("[Net] 获取客户端网络结果:" & Url & ",最大超时 " & Timeout) - Dim Request As CookieWebClient - Dim res As HttpWebResponse = Nothing - Dim HttpStream As Stream = Nothing - Try - Request = New CookieWebClient With { - .Encoding = Encoding, - .Timeout = Timeout - } - Request.Headers("Accept") = Accept - Request.Headers("Accept-Language") = "en-US,en;q=0.5" - Request.Headers("X-Requested-With") = "XMLHttpRequest" - SecretHeadersSign(Url, Request, UseBrowserUserAgent) - Return Request.DownloadString(Url) - Catch ex As Exception - If ex.GetType.Equals(GetType(WebException)) AndAlso CType(ex, WebException).Status = WebExceptionStatus.Timeout Then - Throw New TimeoutException("连接服务器超时(" & Url & ")", ex) - Else - Throw New WebException("获取结果失败," & ex.Message & "(" & Url & ")", ex) + Dim HeaderDictionary = If(Headers, {}).ToDictionary + If ContentType IsNot Nothing Then HeaderDictionary("Content-Type") = ContentType + If Accept IsNot Nothing Then HeaderDictionary("Accept") = Accept + Dim Result = If(Encoding, Encoding.UTF8).GetString(SendRequest(Url, Method, Content, HeaderDictionary, Timeout:=Timeout, Encoding:=Encoding, SimulateBrowserHeaders:=SimulateBrowserHeaders)) + '检查结果是否为完整 JSON(在 #6683 中可能返回空,在被 GFW 截断时可能不完整) + '在此处检查并报错,可以让 NetRequestByClientRetry 等方法进行重试 + If RequireJson Then + Result = Result.Trim((vbCrLf & " ").ToCharArray) 'Authlib-Injector 的结果会在末尾附带一个空行 + If Not (Result.StartsWithF("{") AndAlso Result.EndsWithF("}")) AndAlso + Not (Result.StartsWithF("[") AndAlso Result.EndsWithF("]")) Then + Throw New FormatException("返回结果并非 JSON 格式:" & + If(Result.Length > 2000, Result.Substring(0, 1000) & "..." & Result.Substring(Result.Length - 1000), Result)) + End If End If - Finally - If Not IsNothing(HttpStream) Then HttpStream.Dispose() - If Not IsNothing(res) Then res.Dispose() + Return Result + Catch ex As FormatException + Throw + Catch ex As ThreadInterruptedException + Throw + Catch ex As Exception + If MakeLog Then Log(ex, "网络请求失败", LogLevel.Developer) + Throw End Try End Function - ''' - ''' 以 WebRequest 获取网页源代码或 Json。会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。 + ''' 发送网络请求并获取返回内容。 + ''' 会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。 ''' - ''' 网页的 Url。 - ''' 网页的编码,通常为 UTF-8。 ''' 如果第一次尝试失败,换用的备用 URL。 - Public Function NetGetCodeByRequestRetry(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", - Optional IsJson As Boolean = False, Optional BackupUrl As String = Nothing, Optional UseBrowserUserAgent As Boolean = False) + Public Function NetRequestByClientRetry(Url As String, Optional Method As HttpMethod = Nothing, Optional BackupUrl As String = Nothing, + Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*", + Optional Headers As String(,) = Nothing, Optional RequireJson As Boolean = False, + Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders As Boolean = False) As String Dim RetryCount As Integer = 0 Dim RetryException As Exception = Nothing Dim StartTime As Long = GetTimeTick() @@ -104,15 +57,15 @@ Retry: Retry: Select Case RetryCount Case 0 '正常尝试 - Return NetGetCodeByRequestOnce(Url, Encode, 10000, IsJson, Accept, UseBrowserUserAgent) + Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 10000, Headers, RequireJson, Encoding, SimulateBrowserHeaders) Case 1 '慢速重试 Thread.Sleep(500) - Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 30000, IsJson, Accept, UseBrowserUserAgent) + Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 30000, Headers, RequireJson, Encoding, SimulateBrowserHeaders) Case Else '快速重试 If GetTimeTick() - StartTime > 5500 Then '若前两次加载耗费 5 秒以上,才进行重试 Thread.Sleep(500) - Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 4000, IsJson, Accept, UseBrowserUserAgent) + Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 4000, Headers, RequireJson, Encoding, SimulateBrowserHeaders) Else Throw RetryException End If @@ -120,6 +73,11 @@ Retry: Catch ex As ThreadInterruptedException Throw Catch ex As Exception + If TypeOf ex Is ResponsedWebException Then + If CType(ex, ResponsedWebException).StatusCode = HttpStatusCode.Forbidden Then Throw + If CType(ex, ResponsedWebException).StatusCode = 429 Then Thread.Sleep(10000) 'Too Many Requests + End If + '重试 Select Case RetryCount Case 0 RetryException = ex @@ -134,20 +92,22 @@ Retry: End Try End Function ''' - ''' 以 WebRequest 获取网页源代码或 Json。会逐渐生成 4 个尝试线程,并在 60s 后超时。 + ''' 生成数个线程,同时发送网络请求并获取返回内容。 + ''' 每个线程开始之前会略有延迟。 ''' - ''' 网页的 Url。 - ''' 网页的编码,通常为 UTF-8。 - Public Function NetGetCodeByRequestMultiple(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", Optional IsJson As Boolean = False) + Public Function NetRequestByClientMultiple(Url As String, Optional Method As HttpMethod = Nothing, Optional ThreadCount As Integer = 3, + Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*", + Optional Headers As String(,) = Nothing, Optional RequireJson As Boolean = False, Optional Timeout As Integer = 25000, + Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders As Boolean = False) As String Dim Threads As New List(Of Thread) Dim RequestResult = Nothing Dim RequestEx As Exception = Nothing Dim FailCount As Integer = 0 - For i = 1 To 4 + For i = 1 To ThreadCount Dim th As New Thread( Sub() Try - RequestResult = NetGetCodeByRequestOnce(Url, Encode, 30000, IsJson, Accept) + RequestResult = NetRequestByClient(Url, Method, Content, ContentType, Accept, Timeout, Headers, RequireJson, Encoding, SimulateBrowserHeaders) Catch ex As Exception FailCount += 1 RequestEx = ex @@ -168,7 +128,7 @@ RequestFinished: Catch End Try Return RequestResult - ElseIf FailCount = 4 Then + ElseIf FailCount = ThreadCount Then Try For Each th In Threads If th.IsAlive Then th.Interrupt() @@ -179,124 +139,71 @@ RequestFinished: End If Thread.Sleep(20) Loop - Throw New Exception("未知错误") - End Function - Public Function NetGetCodeByRequestOnce(Url As String, Optional Encode As Encoding = Nothing, Optional Timeout As Integer = 30000, Optional IsJson As Boolean = False, Optional Accept As String = "", Optional UseBrowserUserAgent As Boolean = False) - If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求") - Url = SecretCdnSign(Url) - Log($"[Net] 获取网络结果:{Url},超时 {Timeout}ms{If(IsJson, ",要求 json", "")}") - Dim Request As HttpWebRequest = WebRequest.Create(Url) - Dim Result As New List(Of Byte) - Try - If Url.StartsWithF("https", True) Then Request.ProtocolVersion = HttpVersion.Version11 - Request.Timeout = Timeout - Request.Accept = Accept - SecretHeadersSign(Url, Request, UseBrowserUserAgent) - Using res As HttpWebResponse = Request.GetResponse() - Using HttpStream As Stream = res.GetResponseStream() - HttpStream.ReadTimeout = Timeout - Dim HttpData As Byte() = New Byte(16384) {} - Using Reader As New StreamReader(HttpStream, If(Encode, Encoding.UTF8)) - Dim ResultString As String = Reader.ReadToEnd - Return If(IsJson, GetJson(ResultString), ResultString) - End Using - End Using - End Using - Catch ex As ThreadInterruptedException - Throw - Catch ex As Exception - If TypeOf ex Is WebException AndAlso CType(ex, WebException).Status = WebExceptionStatus.Timeout Then - Throw New TimeoutException($"获取结果失败({CType(ex, WebException).Status},{ex.Message},{Url})", ex) - Else - Throw New WebException($"获取结果失败({If(TypeOf ex Is WebException, CType(ex, WebException).Status & ",", "")}{ex.Message},{Url})", ex) - End If - Finally - Request.Abort() - End Try + Return Nothing End Function ''' - ''' 以多线程下载网页文件的方式获取网页源代码。 + ''' 以多线程下载网页文件的方式获取内容。 + ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。 ''' - ''' 网页的 Url。 - Public Function NetGetCodeByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String - Dim Temp As String = RequestTaskTempFolder() & "download.txt" - Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)}) - Try - NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(" & Url & ")") - NetGetCodeByLoader = ReadFile(Temp) - File.Delete(Temp) - Finally - NewTask.Abort() - End Try + Public Function NetRequestByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional SimulateBrowserHeaders As Boolean = False) As String + Return NetRequestByLoader({Url}, Timeout, IsJson, SimulateBrowserHeaders) End Function ''' - ''' 以多线程下载网页文件的方式获取网页源代码。 + ''' 以多线程下载网页文件的方式获取内容。 + ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。 ''' - ''' 网页的 Url 列表。 - Public Function NetGetCodeByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String + Public Function NetRequestByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional SimulateBrowserHeaders As Boolean = False) As String Dim Temp As String = RequestTaskTempFolder() & "download.txt" - Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)}) + Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, Temp, New FileChecker With {.IsJson = IsJson}, SimulateBrowserHeaders)}) Try NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(第一下载源:" & Urls.First & ")") - NetGetCodeByLoader = ReadFile(Temp) + NetRequestByLoader = ReadFile(Temp) File.Delete(Temp) Finally NewTask.Abort() End Try End Function +#End Region + +#Region "文件下载" + ''' - ''' 使用 WebClient 从网络中下载文件。这不能下载 CDN 中的文件。 + ''' 通过网络请求直接下载小文件,若文件已存在将被覆盖。 + ''' 不建议用于下载大文件。 ''' - ''' 网络 Url。 - ''' 下载的本地地址。 - Public Sub NetDownloadByClient(Url As String, LocalFile As String, Optional UseBrowserUserAgent As Boolean = False) - Log("[Net] 直接下载文件:" & Url) - '初始化 + Public Sub NetDownloadByClient(Url As String, LocalPath As String, Optional SimulateBrowserHeaders As Boolean = False) + Log($"[Net] 通过网络请求直接下载小文件:{Url} → {LocalPath}") + '初始化文件路径 Try - '建立目录 - Directory.CreateDirectory(GetPathFromFullPath(LocalFile)) - '尝试删除原文件 - File.Delete(LocalFile) + Directory.CreateDirectory(GetPathFromFullPath(LocalPath)) + File.Delete(LocalPath) Catch ex As Exception - Throw New WebException($"预处理下载文件路径失败({LocalFile})", ex) + Throw New Exception($"预处理下载文件路径失败({LocalPath})", ex) End Try '下载 - Using Client As New WebClient - Try - SecretHeadersSign(Url, Client, UseBrowserUserAgent) - Client.DownloadFile(Url, LocalFile) - Catch ex As Exception - File.Delete(LocalFile) - Throw New WebException($"直接下载文件失败({Url})", ex) - End Try - End Using - End Sub - - ''' - ''' 简单的多线程下载文件。可以下载 CDN 中的文件。 - ''' - ''' 文件的 Url。 - ''' 下载的本地地址。 - Public Sub NetDownloadByLoader(Url As String, LocalFile As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False) - Dim NewTask As New LoaderDownload("文件下载 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, LocalFile, Check, UseBrowserUserAgent)}) Try - NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress) + File.WriteAllBytes(LocalPath, SendRequest(Url, HttpMethod.Get, SimulateBrowserHeaders:=SimulateBrowserHeaders)) Catch ex As Exception - Throw New WebException($"多线程直接下载文件失败({Url})", ex) - Finally - NewTask.Abort() + File.Delete(LocalPath) + Throw New WebException($"通过网络请求直接下载小文件失败({LocalPath})", ex) End Try End Sub ''' - ''' 简单的多线程下载文件。可以下载 CDN 中的文件。 + ''' 通过多线程下载引擎下载文件。 + ''' + Public Sub NetDownloadByLoader(Url As String, LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False) + NetDownloadByLoader({Url}, LocalPath, LoaderToSyncProgress, Check, SimulateBrowserHeaders) + End Sub + ''' + ''' 通过多线程下载引擎下载文件。 ''' ''' 文件的 Url 列表。 - ''' 下载的本地地址。 - Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalFile As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False) - Dim NewTask As New LoaderDownload("文件下载 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, LocalFile, Check, UseBrowserUserAgent)}) + ''' 下载的本地地址。 + Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False) + Dim NewTask As New LoaderDownload($"文件下载 {GetUuid()}#", New List(Of NetFile) From {New NetFile(Urls, LocalPath, Check, SimulateBrowserHeaders)}) Try NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress) Catch ex As Exception @@ -306,184 +213,183 @@ RequestFinished: End Try End Sub - ''' - ''' 发送一个网络请求并获取返回内容,会重试三次并在最长 45s 后超时。 - ''' - ''' 请求的服务器地址。 - ''' 请求方式(POST 或 GET)。 - ''' 请求的内容。 - ''' 请求的套接字类型。 - ''' 当返回 40x 时不重试。 - Public Function NetRequestRetry(Url As String, Method As String, Data As Object, ContentType As String, Optional DontRetryOnRefused As Boolean = True, Optional Headers As Dictionary(Of String, String) = Nothing) As String - Dim RetryCount As Integer = 0 - Dim RetryException As Exception = Nothing - Dim StartTime As Long = GetTimeTick() - Try -Retry: - Select Case RetryCount - Case 0 '正常尝试 - Return NetRequestOnce(Url, Method, Data, ContentType, 15000, Headers) - Case 1 '慢速重试 - Thread.Sleep(500) - Return NetRequestOnce(Url, Method, Data, ContentType, 25000, Headers) - Case Else '快速重试 - If GetTimeTick() - StartTime > 5500 Then - '若前两次加载耗费 5 秒以上,才进行重试 - Thread.Sleep(500) - Return NetRequestOnce(Url, Method, Data, ContentType, 4000, Headers) - Else - Throw RetryException - End If - End Select - Catch ex As ThreadInterruptedException - Throw - Catch ex As Exception - If ex.InnerException IsNot Nothing AndAlso ex.InnerException.Message.Contains("(40") AndAlso DontRetryOnRefused Then Throw - Select Case RetryCount - Case 0 - If ModeDebug Then Log(ex, "[Net] 网络请求第一次失败(" & Url & ")") - RetryException = ex - RetryCount += 1 - GoTo Retry - Case 1 - If ModeDebug Then Log(ex, "[Net] 网络请求第二次失败(" & Url & ")") - RetryCount += 1 - GoTo Retry - Case Else - Throw - End Select - End Try - End Function - ''' - ''' 同时发送多个网络请求并要求返回内容。 - ''' - Public Function NetRequestMultiple(Url As String, Method As String, Data As Object, ContentType As String, Optional RequestCount As Integer = 4, Optional Headers As Dictionary(Of String, String) = Nothing, Optional MakeLog As Boolean = True) - Dim Threads As New List(Of Thread) - Dim RequestResult = Nothing - Dim RequestEx As Exception = Nothing - Dim FailCount As Integer = 0 - For i = 1 To RequestCount - Dim th As New Thread( - Sub() - Try - RequestResult = NetRequestOnce(Url, Method, Data, ContentType, 30000, Headers, MakeLog) - Catch ex As Exception - FailCount += 1 - RequestEx = ex - End Try - End Sub) - th.Start() - Threads.Add(th) - Thread.Sleep(i * 250) - If RequestResult IsNot Nothing Then GoTo RequestFinished - Next - Do While True - If RequestResult IsNot Nothing Then -RequestFinished: - For Each th In Threads - If th.IsAlive Then th.Interrupt() - Next - Return RequestResult - ElseIf FailCount = RequestCount Then - For Each th In Threads - If th.IsAlive Then th.Interrupt() - Next - Throw RequestEx - End If - Thread.Sleep(20) - Loop - Throw New Exception("未知错误") - End Function - ''' - ''' 发送一次网络请求并获取返回内容。 - ''' - Public Function NetRequestOnce(Url As String, Method As String, Data As Object, ContentType As String, Optional Timeout As Integer = 25000, Optional Headers As Dictionary(Of String, String) = Nothing, Optional MakeLog As Boolean = True, Optional UseBrowserUserAgent As Boolean = False) As String +#End Region + +#Region "基础请求引擎" + + '基础请求函数 + Private Function SendRequest(Url As String, Method As HttpMethod, + Optional Content As Object = Nothing, Optional Headers As Dictionary(Of String, String) = Nothing, + Optional SimulateBrowserHeaders As Boolean = False, Optional Timeout As Integer = 25000, Optional Encoding As Encoding = Nothing) As Byte() If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求") - Url = SecretCdnSign(Url) - If MakeLog Then Log("[Net] 发起网络请求(" & Method & "," & Url & "),最大超时 " & Timeout) - Dim DataStream As Stream = Nothing - Dim Resp As WebResponse = Nothing - Dim Req As HttpWebRequest + Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing, + Response As HttpResponseMessage = Nothing, HostIp As String = Nothing Try - Req = WebRequest.Create(Url) - Req.Method = Method - Dim SendData As Byte() - If TypeOf Data Is Byte() Then - SendData = Data - Else - SendData = New UTF8Encoding(False).GetBytes(Data.ToString) - End If - If Headers IsNot Nothing Then - For Each Pair In Headers - Req.Headers.Add(Pair.Key, Pair.Value) - Next - End If - Req.ContentType = ContentType - Req.Timeout = Timeout - SecretHeadersSign(Url, Req, UseBrowserUserAgent) - If Url.StartsWithF("https", True) Then Req.ProtocolVersion = HttpVersion.Version11 - If Method = "POST" OrElse Method = "PUT" Then - Req.ContentLength = SendData.Length - DataStream = Req.GetRequestStream() - DataStream.WriteTimeout = Timeout - DataStream.ReadTimeout = Timeout - DataStream.Write(SendData, 0, SendData.Length) - DataStream.Close() - End If - Resp = Req.GetResponse() - DataStream = Resp.GetResponseStream() - DataStream.WriteTimeout = Timeout - DataStream.ReadTimeout = Timeout - Using Reader As New StreamReader(DataStream) - Return Reader.ReadToEnd() - End Using - Catch ex As ThreadInterruptedException - Throw - Catch ex As WebException - If ex.Status = WebExceptionStatus.Timeout Then - ex = New WebException($"连接服务器超时,请检查你的网络环境是否良好({ex.Message},{Url})", ex) - Else - '获取请求失败的返回 - Dim Res As String = "" - Try - If ex.Response Is Nothing Then Exit Try - DataStream = ex.Response.GetResponseStream() - DataStream.WriteTimeout = Timeout - DataStream.ReadTimeout = Timeout - Using Reader As New StreamReader(DataStream) - Res = Reader.ReadToEnd() - End Using - Catch - End Try - If Res = "" Then - ex = New WebException($"网络请求失败({ex.Status},{ex.Message},{Url})", ex) - Else - ex = New ResponsedWebException($"服务器返回错误({ex.Status},{ex.Message},{Url}){vbCrLf}{Res}", Res, ex) + Url = SecretCdnSign(Url) + Request = New HttpRequestMessage(Method, Url) + '写入 Content + If Content IsNot Nothing AndAlso Request.Method <> HttpMethod.Get AndAlso Request.Method <> HttpMethod.Head Then + If TypeOf Content Is HttpContent Then + Request.Content = DirectCast(Content, HttpContent) + ElseIf TypeOf Content Is Byte() Then + Request.Content = New ByteArrayContent(DirectCast(Content, Byte())) + ElseIf Content IsNot Nothing Then + Request.Content = New ByteArrayContent(If(Encoding, Encoding.UTF8).GetBytes(Content.ToString())) End If End If - If MakeLog Then Log(ex, "NetRequestOnce 失败", LogLevel.Developer) - Throw ex + '写入 Headers + For Each Header In If(Headers, New Dictionary(Of String, String)) + If Header.Key.ToLower = "content-type" Then + Request.Content?.Headers.TryAddWithoutValidation(Header.Key, Header.Value) + Else + Request.Headers.Add(Header.Key, Header.Value) + End If + Next + SecretHeadersSign(Url, Request, SimulateBrowserHeaders) + 'DNS 解析 + CancelToken = New CancellationTokenSource(Timeout) + HostIp = DNSLookup(Request, CancelToken) + If HostIp IsNot Nothing AndAlso Not IPReliability.ContainsKey(HostIp) Then + IPReliability(HostIp) = -0.01 '预先降低一点,这样快速的重复请求会使用不同的 IP 以提高成功率 + End If + '发送请求 + SyncLock RequestClientLock + '延迟初始化,以避免在程序启动前加载 CacheCow 导致 DLL 加载失败 + If RequestClient Is Nothing Then + RequestClient = CacheCow.Client.ClientExtensions.CreateClient(New CacheCow.Client.FileCacheStore.FileStore(PathTemp & "Cache/Http/"), New HttpClientHandler With { + .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None, + .UseCookies = False '不设为 False 就不能从 Header 手动传入 Cookies + }) + End If + End SyncLock + Response = RequestClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult() + Dim ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult() + Dim ResponseBytes As Byte() + Using Stream As New MemoryStream + ResponseStream.CopyToAsync(Stream, 81920, CancelToken.Token).GetAwaiter().GetResult() + ResponseBytes = Stream.ToArray() + End Using + '输出 + If Response.IsSuccessStatusCode Then + RecordIPReliability(HostIp, 0.5) + Return ResponseBytes + Else + RecordIPReliability(HostIp, -0.7) + Dim ResponseMessage = If(Encoding, Encoding.UTF8).GetString(ResponseBytes) + Throw New ResponsedWebException( + $"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url},{HostIp}" & + If(String.IsNullOrEmpty(ResponseMessage), "", vbCrLf & ResponseMessage), Response.StatusCode, ResponseMessage) + End If + Catch ex As ThreadInterruptedException + Throw + Catch ex As ResponsedWebException + Throw Catch ex As Exception - ex = New WebException("网络请求失败(" & Url & ")", ex) - If MakeLog Then Log(ex, "NetRequestOnce 失败", LogLevel.Developer) - Throw ex + RecordIPReliability(HostIp, -1) + If TypeOf ex Is OperationCanceledException Then 'CancellationToken 超时 + Throw New WebException($"连接服务器超时,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url},IP:{HostIp})", WebExceptionStatus.Timeout) + ElseIf ex.IsNetworkRelated Then + Throw New WebException($"网络请求失败,请稍后再试,或使用 VPN 改善网络环境({Method}, {Url},IP:{HostIp})", WebExceptionStatus.Timeout) + Else + Throw New Exception($"网络请求出现意外异常({Method}, {Url},{HostIp})", ex) + End If Finally - If DataStream IsNot Nothing Then DataStream.Dispose() - If Resp IsNot Nothing Then Resp.Dispose() + Request?.Dispose() + CancelToken?.Dispose() + Response?.Dispose() End Try End Function + Private RequestClient As HttpClient = Nothing + Private RequestClientLock As New Object + + ''' + ''' 进行 DNS 解析。它仅在选择的 IP 与系统默认的不一致时才对 URL 中的 Host 进行替换。 + ''' 返回请求时应使用的 IP;若为 IPv6,则加上了方括号。 + ''' 若解析失败,则返回 Nothing。 + ''' + Private Function DNSLookup(Request As HttpRequestMessage, CancelToken As CancellationTokenSource) As String + Dim GetIPReliability = Function(Key) IPReliability.GetOrDefault(Key.ToString, 0) + '初步 DNS 解析 + Dim Host = Request.RequestUri.Host + Dim DnsTask As Task(Of IPAddress()) = Nothing + Try + DnsTask = Dns.GetHostAddressesAsync(Host) + DnsTask.Wait(CancelToken.Token) + Catch ex As Exception + Log(ex, $"DNS 解析失败({Host})") + Return Nothing + End Try + Dim Candidates As IPAddress() = DnsTask.Result.ToArray + If Not Candidates.Any Then + Log($"[Net] DNS 解析无结果({Host})") + Return Nothing + End If + '若同时存在 IPv4 和 IPv6 地址,仅选择其中一类(因为 GFW 可能只屏蔽了 IPv4 或 IPv6) + Dim IPv4Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetwork).ToArray + Dim IPv6Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetworkV6).ToArray + If IPv4Targets.Any AndAlso IPv6Targets.Any Then + Dim IPv4Reliability = IPv4Targets.Average(Function(i) GetIPReliability(i)) + Dim IPv6Reliability = IPv6Targets.Average(Function(i) GetIPReliability(i)) + If Host = "api.modrinth.com" Then IPv6Reliability -= 0.1 '让 Modrinth 优先使用 IPv4 地址(#6887) + Candidates = If(IPv4Reliability >= IPv6Reliability, IPv4Targets, IPv6Targets) + End If + '选择可靠度最高的 IP + Dim MaxReliability As Double = Candidates.Max(Function(i) GetIPReliability(i)) + Dim Target As IPAddress = Candidates.First(Function(i) GetIPReliability(i) = MaxReliability) + Dim TargetIp As String = If(Target.AddressFamily = AddressFamily.InterNetworkV6, $"[{Target}]", Target.ToString) + If Target IsNot Candidates.First Then + '优选结果与系统默认 IP 不一致,替换域名并设置 Host 头 + If ModeDebug Then Log($"[Net] DNS 解析结果:{Host} → {Target},所有可能的 IP 与可靠度:{DnsTask.Result.Select(Function(i) $"{i} → {GetIPReliability(i):0.000}").Join(";")}") + Request.Headers.Host = Host + Dim Builder As New UriBuilder(Request.RequestUri) + Builder.Host = TargetIp + Request.RequestUri = Builder.Uri + End If + Return TargetIp + End Function + ''' + ''' 记录每个 IP 地址的请求可靠度。 + ''' 通常取值 -1 ~ +0.5,越高越好。未尝试过的 IP 应视为 0。 + ''' + Private IPReliability As New SafeDictionary(Of String, Double) + ''' + ''' 根据请求结果,记录 IP 地址的可靠度。 + ''' + Private Sub RecordIPReliability(IP As String, Result As Double) + If IP Is Nothing Then Return + If Not IPReliability.ContainsKey(IP) Then IPReliability(IP) = 0 + IPReliability(IP) = (IPReliability(IP) + Result) / 2 + End Sub + + ''' + ''' 当 HTTP 状态码不指示成功时引发的异常。 + ''' 附带额外属性,可用于获取远程服务器给予的回复以及 HTTP 状态码。 + ''' Public Class ResponsedWebException Inherits WebException ''' + ''' HTTP 状态码。 + ''' + Public StatusCode As HttpStatusCode + ''' ''' 远程服务器给予的回复。 ''' Public Overloads Property Response As String - Public Sub New(Message As String, Response As String, InnerException As Exception) - MyBase.New(Message, InnerException) + Public Sub New(Message As String, StatusCode As HttpStatusCode, Response As String) + MyBase.New(Message) + Me.StatusCode = StatusCode Me.Response = Response End Sub End Class +#End Region + +#Region "多线程下载引擎" + + Private ThreadClient As New HttpClient(New HttpClientHandler With { + .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None + }) + ''' ''' 最大线程数。 ''' @@ -516,7 +422,10 @@ RequestFinished: Public Url As String Public FailCount As Integer Public Ex As Exception - Public Thread As NetThread + ''' + ''' 若该下载源正在进行强制单线程下载,标记这个唯一的线程。 + ''' + Public SingleThread As NetThread Public IsFailed As Boolean Public Overrides Function ToString() As String Return Url @@ -585,7 +494,7 @@ RequestFinished: ''' 下载线程。 ''' Public Class NetThread - Implements IEnumerable(Of NetThread) + Implements IEnumerable(Of NetThread), IEquatable(Of NetThread) ''' ''' 对应的下载任务。 @@ -644,7 +553,7 @@ RequestFinished: SyncLock Task.LockChain If NextThread Is Nothing Then If Task.IsUnknownSize Then - Return 5 * 1024 * 1024 * 1024L '5G + Return 1000 * 1000 * 1000 * 1000L '约 1T Else Return Task.FileSize - 1 End If @@ -718,6 +627,19 @@ RequestFinished: ''' Public Source As NetSource + '允许进行 UUID 比较 + Public Overloads Function Equals(other As NetThread) As Boolean Implements IEquatable(Of NetThread).Equals + Return other IsNot Nothing AndAlso Uuid = other.Uuid + End Function + Public Overrides Function Equals(obj As Object) As Boolean + Return Equals(TryCast(obj, NetThread)) + End Function + Public Shared Operator =(left As NetThread, right As NetThread) As Boolean + Return EqualityComparer(Of NetThread).Default.Equals(left, right) + End Operator + Public Shared Operator <>(left As NetThread, right As NetThread) As Boolean + Return Not left = right + End Operator End Class ''' ''' 下载单个文件。 @@ -743,12 +665,17 @@ RequestFinished: ''' Public SourcesOnce As New SafeList(Of NetSource) ''' + ''' 仅当合并失败或首次下载失败时,会将所有下载源重新标记为不允许断点续传的下载源,逐个重新尝试下载。 + ''' 这一策略可以兼容多个下载源中的一部分返回错误的文件的情况,以及部分在多线程下载时会抽风的源。 + ''' + Private Retried As Boolean = False + ''' ''' 获取从某个源开始,第一个可用的源。 ''' Private Function GetSource(Optional Id As Integer = 0) As NetSource If Id >= Sources.Count OrElse Id < 0 Then Id = 0 SyncLock LockSource - If Not IsSourceFailed(False) Then + If HasAvailableSource(False) Then '存在多线程可用源 Dim CurrentSource As NetSource = Sources(Id) While CurrentSource.IsFailed @@ -759,7 +686,7 @@ RequestFinished: Return CurrentSource ElseIf SourcesOnce.Any Then '仅存在单线程可用源 - Return SourcesOnce(0) + Return SourcesOnce.First Else '没有可用源 Return Nothing @@ -767,16 +694,14 @@ RequestFinished: End SyncLock End Function ''' - ''' 是否已经没有可用源了。 + ''' 是否存在可用源。 ''' - Public Function IsSourceFailed(Optional AllowOnceSource As Boolean = True) As Boolean - If AllowOnceSource AndAlso SourcesOnce.Any Then Return False + Public Function HasAvailableSource(Optional AllowOnceSource As Boolean = True) As Boolean SyncLock LockSource - For Each Source As NetSource In Sources - If Not Source.IsFailed Then Return False - Next + If Sources.Any(Function(s) Not s.IsFailed) Then Return True '存在多线程可用源 + If AllowOnceSource AndAlso SourcesOnce.Any Then Return True '存在单线程可用源 End SyncLock - Return True + Return False End Function ''' @@ -820,9 +745,9 @@ RequestFinished: End Get End Property ''' - ''' 为不需要分割的小文件进行临时存储。 + ''' 为不需要分割的小文件在内存中提供临时存储。 ''' - Private SmailFileCache As Queue(Of Byte) + Private Cache As MemoryStream ''' ''' 文件的已下载大小。 @@ -834,9 +759,9 @@ RequestFinished: ''' Public Check As FileChecker ''' - ''' 下载时是否添加浏览器 UA。 + ''' 是否模拟浏览器的 UserAgent 和 Referer。 ''' - Public UseBrowserUserAgent As Boolean + Public SimulateBrowserHeaders As Boolean ''' ''' 上次记速时的时间。 @@ -934,7 +859,7 @@ RequestFinished: ''' 新建一个需要下载的文件。 ''' ''' 包含文件名的本地地址。 - Public Sub New(Urls As IEnumerable(Of String), LocalPath As String, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False) + Public Sub New(Urls As IEnumerable(Of String), LocalPath As String, Optional Check As FileChecker = Nothing, Optional SimulateBrowserHeaders As Boolean = False) Dim Sources As New List(Of NetSource) Dim Count As Integer = 0 Urls = Urls.Distinct.ToArray @@ -945,7 +870,7 @@ RequestFinished: Me.Sources = Sources Me.LocalPath = LocalPath Me.Check = Check - Me.UseBrowserUserAgent = UseBrowserUserAgent + Me.SimulateBrowserHeaders = SimulateBrowserHeaders Me.LocalName = GetFileNameFromPath(LocalPath) End Sub @@ -956,35 +881,29 @@ RequestFinished: Public Function TryBeginThread() As NetThread Try - '条件检测 - If NetTaskThreadCount >= NetTaskThreadLimit OrElse IsSourceFailed() OrElse - (IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing - If State >= NetState.Merge OrElse State = NetState.WaitForCheck Then Return Nothing - SyncLock LockState - If State < NetState.Connect Then State = NetState.Connect - End SyncLock - '初始化参数 Dim StartPosition As Long, StartSource As NetSource = Nothing Dim Th As Thread, ThreadInfo As NetThread - SyncLock LockChain + '条件检测 + SyncLock LockSource + If NetTaskThreadCount >= NetTaskThreadLimit OrElse Not HasAvailableSource() OrElse + (IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing + If State >= NetState.Merge OrElse State = NetState.WaitForCheck Then Return Nothing + SyncLock LockState + If State < NetState.Connect Then State = NetState.Connect + End SyncLock + '初始化参数 '获取线程起点与下载源 '不分割 - If IsNoSplit Then GoTo Capture - '单线程 - If IsSourceFailed(False) Then - '确认没有其他线程正使用此点 - If SourcesOnce(0).Thread IsNot Nothing AndAlso SourcesOnce(0).Thread.State <> NetState.Error Then Return Nothing - '占用此点 -Capture: - SmailFileCache = Nothing - Threads = Nothing - NetManager.DownloadDone -= DownloadDone - SyncLock LockDone - DownloadDone = 0 - End SyncLock - SpeedLastDone = 0 + If IsNoSplit Then GoTo StartSingleThreadDownload + '只剩下单线程可用点 + If Not HasAvailableSource(False) Then + If SourcesOnce.First.SingleThread IsNot Nothing AndAlso SourcesOnce.First.SingleThread.State <> NetState.Error Then Return Nothing +StartSingleThreadDownload: + Reset() State = NetState.Get End If + End SyncLock + SyncLock LockChain '首个开始点 If Threads Is Nothing Then StartPosition = 0 @@ -1001,9 +920,11 @@ Capture: End If Next '是否禁用多线程,以及规定碎片大小 - Dim TargetUrl As String = GetSource().Url + Dim Source As NetSource = GetSource() + If Source Is Nothing Then Return Nothing + Dim TargetUrl As String = Source.Url If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("bmclapi") OrElse TargetUrl.Contains("github.com") OrElse - TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("modrinth") Then Return Nothing + TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("modrinth") OrElse TargetUrl.Contains("momot.rs") Then Return Nothing '寻找最大碎片 'FUTURE: 下载引擎重做,计算下载源平均链接时间和线程下载速度,按最高时间节省来开启多线程 Dim FilePieceMax As NetThread = Threads @@ -1020,7 +941,7 @@ StartThread: '构建线程 Dim ThreadUuid As Integer = GetUuid() If Not Tasks.Any() Then Return Nothing '由于中断,已没有可用任务 - Th = New Thread(AddressOf Thread) With {.Name = $"NetTask {Tasks(0).Uuid}/{Uuid} Download {ThreadUuid}#", .Priority = ThreadPriority.BelowNormal} + Th = New Thread(AddressOf Thread) With {.Name = $"下载 {Tasks(0).Uuid}/{Uuid}/{ThreadUuid}#", .Priority = ThreadPriority.BelowNormal} ThreadInfo = New NetThread With {.Uuid = ThreadUuid, .DownloadStart = StartPosition, .Thread = Th, .Source = StartSource, .Task = Me, .State = NetState.WaitForDownload} '链表处理 If ThreadInfo.IsFirstThread OrElse Threads Is Nothing Then @@ -1040,332 +961,317 @@ StartThread: NetTaskThreadCount += 1 End SyncLock SyncLock LockSource - If IsSourceFailed(False) Then SourcesOnce(0).Thread = ThreadInfo + If Not HasAvailableSource(False) Then SourcesOnce.First.SingleThread = ThreadInfo End SyncLock Th.Start(ThreadInfo) Return ThreadInfo Catch ex As Exception - Log(ex, "尝试开始下载线程失败(" & If(LocalName, "Nothing") & ")", LogLevel.Hint) + Fail(New Exception($"尝试开始下载线程失败({LocalName})", ex)) Return Nothing End Try End Function ''' ''' 每个下载线程执行的代码。 ''' - Private Sub Thread(Info As NetThread) - If ModeDebug OrElse Info.DownloadStart = 0 Then Log("[Download] " & LocalName & " " & Info.Uuid & "#:开始,起始点 " & Info.DownloadStart & "," & Info.Source.Url) - Dim HttpRequest As HttpWebRequest - Dim ResultStream As Stream = Nothing + Private Sub Thread(Th As NetThread) + If ModeDebug OrElse Th.DownloadStart = 0 Then Log($"[Download] {LocalName}:开始,起始点 {Th.DownloadStart},{Th.Source.Url}") + Dim ResultStream As Stream = Nothing, HttpRequest As HttpRequestMessage = Nothing, + Response As HttpResponseMessage = Nothing, ResponseStream As Stream = Nothing, + CancelToken As CancellationTokenSource = Nothing, HostIp As String = Nothing '部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟 '延迟过长会导致某些特别慢的链接迟迟不被掐死 - Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 6000) * (1 + Info.Source.FailCount), 30000) - Info.State = NetState.Connect + Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 6000) * (1 + Th.Source.FailCount), 30000) + Th.State = NetState.Connect Try Dim HttpDataCount As Integer = 0 - If SourcesOnce.Contains(Info.Source) AndAlso Not Info.Equals(Info.Source.Thread) Then GoTo SourceBreak - '请求头 - HttpRequest = WebRequest.Create(Info.Source.Url) - If Info.Source.Url.StartsWithF("https", True) Then HttpRequest.ProtocolVersion = HttpVersion.Version11 - 'HttpRequest.Proxy = Nothing 'new WebProxy(Ip, Port) - HttpRequest.Timeout = Timeout - HttpRequest.AddRange(Info.DownloadStart) - SecretHeadersSign(Info.Source.Url, HttpRequest, UseBrowserUserAgent) + If SourcesOnce.Contains(Th.Source) AndAlso Th <> Th.Source.SingleThread Then GoTo SourceBreak + HttpRequest = New HttpRequestMessage(HttpMethod.Get, Th.Source.Url) + SecretHeadersSign(Th.Source.Url, HttpRequest, SimulateBrowserHeaders) + CancelToken = New CancellationTokenSource(Timeout) + HostIp = DNSLookup(HttpRequest, CancelToken) 'DNS 预解析 + If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New RangeHeaderValue(Th.DownloadStart, Nothing) Dim ContentLength As Long = 0 - Using HttpResponse As HttpWebResponse = HttpRequest.GetResponse() - If State = NetState.Error Then GoTo SourceBreak '快速中断 - If ModeDebug AndAlso HttpResponse.ResponseUri.OriginalString <> Info.Source.Url Then - Log($"[Download] {LocalName} {Info.Uuid}#:重定向至 {HttpResponse.ResponseUri.OriginalString}") - End If - ''从响应头获取文件名 - 'If Info.IsFirstThread Then - ' Dim FileName As String = GetFileNameFromResponse(HttpResponse) - ' If ModeDebug Then Log($"[Download] {LocalName} {Info.Uuid}#:远程文件名:{If(FileName, "未提供")}") - ' If FileName IsNot Nothing AndAlso LocalName = "待定" Then - ' LocalName = FileName - ' Log($"[Download] {LocalName} {Info.Uuid}#:从响应头获取到文件名") - ' End If - 'End If - '文件大小校验 - ContentLength = HttpResponse.ContentLength - If ContentLength = -1 Then - If FileSize > 1 Then - If Info.DownloadStart = 0 Then - Log($"[Download] {LocalName} {Info.Uuid}#:文件大小未知,但已从其他下载源获取,不作处理") - Else - Log($"[Download] {LocalName} {Info.Uuid}#:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持") - GoTo NotSupportRange - End If + Response = ThreadClient.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult() + If Not Response.IsSuccessStatusCode Then Throw New Exception($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Th.Source.Url}") '状态码检查 + If State = NetState.Error Then GoTo SourceBreak '快速中断 + If ModeDebug AndAlso Response.RequestMessage.RequestUri.ToString <> Th.Source.Url Then Log($"[Download] {LocalName}:重定向至 {Response.RequestMessage.RequestUri}") + '文件大小校验 + ContentLength = Response.Content.Headers.ContentLength.GetValueOrDefault(-1) + If ContentLength = -1 Then + If FileSize > 1 Then + If Th.DownloadStart = 0 Then + Log($"[Download] {LocalName}:文件大小未知,但已从其他下载源获取,不作处理") Else - FileSize = -1 : IsUnknownSize = True - Log($"[Download] {LocalName} {Info.Uuid}#:文件大小未知") + Log($"[Download] {LocalName}:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持") + GoTo NotSupportRange End If - ElseIf ContentLength < 0 Then - Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。") - ElseIf Info.IsFirstThread Then - If Check IsNot Nothing Then - If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then - Throw New Exception($"文件大小不足,获取结果为 {ContentLength},要求至少为 {Check.MinSize}。") - End If - If ContentLength <> Check.ActualSize AndAlso Check.ActualSize > 0 Then - Throw New Exception($"文件大小不一致,获取结果为 {ContentLength},要求必须为 {Check.ActualSize}。") - End If - End If - FileSize = ContentLength : IsUnknownSize = False - Log($"[Download] {LocalName} {Info.Uuid}#:文件大小 {ContentLength}({GetString(ContentLength)})") - '若文件大小大于 50 M,进行剩余磁盘空间校验 - If ContentLength > 50 * 1024 * 1024 Then - For Each Drive As DriveInfo In DriveInfo.GetDrives - Dim DriveName As String = Drive.Name.First.ToString - Dim RequiredSpace = If(PathTemp.StartsWithF(DriveName), ContentLength * 1.1, 0) + - If(LocalPath.StartsWithF(DriveName), ContentLength + 5 * 1024 * 1024, 0) - If Drive.TotalFreeSpace < RequiredSpace Then - Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" & - If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", "")) - End If - Next - End If - ElseIf FileSize < 0 Then - Throw New Exception("非首线程运行时,尚未获取文件大小") - ElseIf Info.DownloadStart > 0 AndAlso ContentLength = FileSize Then -NotSupportRange: - SyncLock LockSource - If SourcesOnce.Contains(Info.Source) Then - GoTo SourceBreak - Else - SourcesOnce.Add(Info.Source) - End If - End SyncLock - Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Info.DownloadStart},预期 ContentLength 为 {FileSize - Info.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") - ElseIf Not FileSize - Info.DownloadStart = ContentLength Then - Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Info.DownloadStart},预期 ContentLength 为 {FileSize - Info.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") - End If - 'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}") - Info.State = NetState.Get - SyncLock LockState - If State < NetState.Get Then State = NetState.Get - End SyncLock - '创建缓存文件 - If IsNoSplit Then - Info.Temp = Nothing - SmailFileCache = New Queue(Of Byte) Else - Info.Temp = $"{PathTemp}Download\{Uuid}_{Info.Uuid}_{RandomInteger(0, 999999)}.tmp" - ResultStream = New FileStream(Info.Temp, FileMode.Create, FileAccess.Write, FileShare.Read) + FileSize = -1 : IsUnknownSize = True + Log($"[Download] {LocalName}:文件大小未知") End If - '开始下载 - Using HttpStream = HttpResponse.GetResponseStream() - HttpStream.ReadTimeout = Timeout - If Setup.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000)) - Dim HttpData As Byte() = New Byte(16384) {} - HttpDataCount = HttpStream.Read(HttpData, 0, 16384) - While (IsUnknownSize OrElse Info.DownloadUndone > 0) AndAlso '判断是否下载完成 - HttpDataCount > 0 AndAlso Not IsProgramEnded AndAlso State < NetState.Merge AndAlso (Not Info.Source.IsFailed OrElse Info.Equals(Info.Source.Thread)) - '限速 - While NetTaskSpeedLimitHigh > 0 AndAlso NetTaskSpeedLimitLeft <= 0 - Threading.Thread.Sleep(16) - End While - Dim RealDataCount As Integer = If(IsUnknownSize, HttpDataCount, Math.Min(HttpDataCount, Info.DownloadUndone)) - SyncLock NetTaskSpeedLimitLeftLock - If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount - End SyncLock - Dim DeltaTime = GetTimeTick() - Info.LastReceiveTime - If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值 - If RealDataCount > 0 Then - '有数据 - If Info.DownloadDone = 0 Then - '第一次接受到数据 - Info.State = NetState.Download - SyncLock LockState - If State < NetState.Download Then State = NetState.Download - End SyncLock - SyncLock LockCount - ConnectCount += 1 - ConnectTime += GetTimeTick() - Info.InitTime - End SyncLock - End If - SyncLock LockCount - Info.Source.FailCount = 0 - For Each Task In Tasks - Task.FailCount = 0 - Next - End SyncLock - NetManager.DownloadDone += RealDataCount - SyncLock LockDone - DownloadDone += RealDataCount - End SyncLock - Info.DownloadDone += RealDataCount - If IsNoSplit Then - If HttpData.Count = RealDataCount Then - 'SmailFileCache.AddRange(HttpData) - For Each B In HttpData - SmailFileCache.Enqueue(B) - Next - Else - 'SmailFileCache.AddRange(HttpData.ToList.GetRange(0, RealDataCount)) - For i = 0 To RealDataCount - 1 - SmailFileCache.Enqueue(HttpData(i)) - Next - End If - Else - ResultStream.Write(HttpData, 0, RealDataCount) - End If - '检查速度是否过慢 - If DeltaTime > 1500 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1.5s,且速度小于 1.5K/s - Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。") - End If - Info.LastReceiveTime = GetTimeTick() - '已完成 - If Info.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While - ElseIf Info.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then - '无数据,且已超时 - Throw New TimeoutException("操作超时,无数据。") + ElseIf ContentLength < 0 Then + Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。") + ElseIf Th.IsFirstThread Then + If Check IsNot Nothing Then + If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then + Throw New Exception($"文件大小不足,获取结果为 {ContentLength},要求至少为 {Check.MinSize}。") + End If + If ContentLength <> Check.ActualSize AndAlso Check.ActualSize > 0 Then + Throw New Exception($"文件大小不一致,获取结果为 {ContentLength},要求必须为 {Check.ActualSize}。") + End If + End If + FileSize = ContentLength : IsUnknownSize = False + Log($"[Download] {LocalName}:文件大小 {ContentLength}({GetString(ContentLength)})") + '若文件大小大于 50 M,进行剩余磁盘空间校验 + If ContentLength > 50 * 1024 * 1024 Then + For Each Drive As DriveInfo In DriveInfo.GetDrives + Dim DriveName As String = Drive.Name.First.ToString + Dim RequiredSpace = If(PathTemp.StartsWithF(DriveName), ContentLength * 1.1, 0) + + If(LocalPath.StartsWithF(DriveName), ContentLength + 5 * 1024 * 1024, 0) + If Drive.TotalFreeSpace < RequiredSpace Then + Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" & + If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", "")) End If - HttpDataCount = HttpStream.Read(HttpData, 0, 16384) - End While - End Using - End Using + Next + End If + ElseIf FileSize < 0 Then + Throw New Exception("非首线程运行时,尚未获取文件大小") + ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then +NotSupportRange: + SyncLock LockSource + If SourcesOnce.Contains(Th.Source) Then + GoTo SourceBreak + Else + SourcesOnce.Add(Th.Source) + End If + End SyncLock + Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") + ElseIf Not FileSize - Th.DownloadStart = ContentLength Then + Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}") + End If + 'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}") + Th.State = NetState.Get + SyncLock LockState + If State < NetState.Get Then State = NetState.Get + End SyncLock + '创建缓存文件 + If IsNoSplit Then + Th.Temp = Nothing + Cache = New MemoryStream + ResultStream = Cache + Else + Th.Temp = $"{PathTemp}Download\{Uuid}_{Th.Uuid}_{RandomInteger(0, 999999)}.tmp" + ResultStream = New FileStream(Th.Temp, FileMode.Create, FileAccess.Write, FileShare.Read) + End If + '开始下载 + ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult() + If Setup.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000)) + Dim ResponseBytes As Byte() = New Byte(16384) {} + HttpDataCount = ResponseStream.ReadAsync(ResponseBytes, 0, 16384, CancelToken.Token).GetAwaiter().GetResult() + While (IsUnknownSize OrElse Th.DownloadUndone > 0) AndAlso '判断是否下载完成 + HttpDataCount > 0 AndAlso Not IsProgramEnded AndAlso State < NetState.Merge AndAlso (Not Th.Source.IsFailed OrElse Th.Source.SingleThread = Th) + '限速 + While NetTaskSpeedLimitHigh > 0 AndAlso NetTaskSpeedLimitLeft <= 0 + Threading.Thread.Sleep(16) + End While + Dim RealDataCount As Integer = If(IsUnknownSize, HttpDataCount, Math.Min(HttpDataCount, Th.DownloadUndone)) + SyncLock NetTaskSpeedLimitLeftLock + If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount + End SyncLock + Dim DeltaTime = GetTimeTick() - Th.LastReceiveTime + If DeltaTime > 1000000 Then DeltaTime = 1 '避免时间刻反转导致出现极大值 + If RealDataCount > 0 Then + '有数据 + If Th.DownloadDone = 0 Then + '第一次接受到数据 + Th.State = NetState.Download + SyncLock LockState + If State < NetState.Download Then State = NetState.Download + End SyncLock + SyncLock LockCount + ConnectCount += 1 + ConnectTime += GetTimeTick() - Th.InitTime + End SyncLock + End If + SyncLock LockCount + Th.Source.FailCount = 0 + For Each Task In Tasks + Task.FailCount = 0 + Next + End SyncLock + NetManager.DownloadDone += RealDataCount + SyncLock LockDone + DownloadDone += RealDataCount + End SyncLock + Th.DownloadDone += RealDataCount + ResultStream.Write(ResponseBytes, 0, RealDataCount) + '已完成 + If Th.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While + '检查速度是否过慢 + If DeltaTime > 5000 AndAlso DeltaTime > RealDataCount AndAlso '数据包间隔大于 5s,且速度小于 1K/s + Th.Source.SingleThread Is Nothing Then '且并非单线程下载 + Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。") + End If + Th.LastReceiveTime = GetTimeTick() + ElseIf Th.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then + '无数据,且已超时 + Throw New TimeoutException("操作超时,无数据。") + End If + HttpDataCount = ResponseStream.Read(ResponseBytes, 0, 16384) + End While SourceBreak: - If State = NetState.Error OrElse Info.Source.IsFailed OrElse (Info.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then + If State = NetState.Error OrElse (Th.Source.IsFailed AndAlso Th.Source.SingleThread <> Th) OrElse (Th.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then '被外部中断 - Info.State = NetState.Error - Log($"[Download] {LocalName} {Info.Uuid}#:中断") - ElseIf HttpDataCount = 0 AndAlso Info.DownloadUndone > 0 AndAlso Not IsUnknownSize Then + Th.State = NetState.Error + Log($"[Download] {LocalName}:中断") + ElseIf HttpDataCount = 0 AndAlso Th.DownloadUndone > 0 AndAlso Not IsUnknownSize Then '服务器无返回数据 - Throw New Exception($"返回的 ContentLength 过多:ContentLength 为 {ContentLength},但获取到的总数据量仅为 {Info.DownloadDone}(全文件总数据量 {DownloadDone})") + Throw New Exception($"返回的 ContentLength 过多:ContentLength 为 {ContentLength},但获取到的总数据量仅为 {Th.DownloadDone}(全文件总数据量 {DownloadDone})") Else '本线程完成 - Info.State = NetState.Finish - If ModeDebug Then Log($"[Download] {LocalName} {Info.Uuid}#:完成,已下载 {Info.DownloadDone}") + Th.State = NetState.Finish + If ModeDebug Then Log($"[Download] {LocalName}:完成,已下载 {Th.DownloadDone}({Th.DownloadStart}~{Th.DownloadEnd})") + RecordIPReliability(HostIp, 0.5) End If Catch ex As Exception - '状态变更 - SyncLock LockCount - Info.Source.FailCount += 1 - For Each Task In Tasks - Task.FailCount += 1 - Next - End SyncLock - Dim IsTimeoutString As String = GetExceptionSummary(ex).ToLower.Replace(" ", "") - Dim IsTimeout As Boolean = IsTimeoutString.Contains("由于连接方在一段时间后没有正确答复或连接的主机没有反应") OrElse - IsTimeoutString.Contains("超时") OrElse IsTimeoutString.Contains("timeout") OrElse IsTimeoutString.Contains("timedout") - Log("[Download] " & LocalName & " " & Info.Uuid & If(IsTimeout, "#:超时(" & (Timeout * 0.001) & "s)", "#:出错," & GetExceptionDetail(ex))) - Info.State = NetState.Error - ''使用该下载源的线程是否没有速度 - ''下载超时也会导致没有速度,容易误判下载失败,所以已弃用此方法 - 'Dim IsNoSpeed As Boolean = True - 'SyncLock LockChain - ' If Threads IsNot Nothing Then - ' For Each Thread As NetThread In Threads - ' If Thread.Source.Id = Info.Source.Id AndAlso Thread.Speed > 0 Then - ' IsNoSpeed = False - ' Exit For - ' End If - ' Next - ' End If - 'End SyncLock - Info.Source.Ex = ex - '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传) - If ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse - ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse - (ex.Message.Contains("(403)") AndAlso Not Info.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403,所以不应因此禁用下载源 - (Info.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse - Info.Source.FailCount > NetTaskThreadLimit + 2 Then - Dim IsThisFail As Boolean = False - SyncLock LockSource - If Info.Source.Thread IsNot Nothing AndAlso Info.Source.Thread.Equals(Info) Then - '单线程下,本线程出错 - SourcesOnce.RemoveAt(0) - GoTo Wrong - ElseIf Not Info.Source.IsFailed Then - '多线程下,本线程出错 -Wrong: - Info.Source.IsFailed = True - IsThisFail = True - End If - End SyncLock - '本线程引发下载源被禁用 - If IsThisFail Then - Log($"[Download] {LocalName} {Info.Uuid}#:下载源被禁用({Info.Source.Id}):{Info.Source.Url}") - Log(ex, "下载源 " & Info.Source.Id & " 已被禁用", If(ex.Message.Contains("不支持分段下载") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(416)"), LogLevel.Developer, LogLevel.Debug)) - If IsSourceFailed() Then - '没有可用源 - Log("[Download] 文件 " & LocalName & " 已无可用下载源") - Dim ExampleEx As Exception = Nothing - SyncLock LockSource - For Each Source As NetSource In Sources - Log("[Download] 已禁用的下载源:" & Source.Url) - If Source.Ex IsNot Nothing Then - ExampleEx = Source.Ex - Log(Source.Ex, "下载源禁用原因", LogLevel.Developer) - End If - Next - End SyncLock - Fail(ExampleEx) - ElseIf ex.Message.Contains("空间不足") Then - '没有空间 - Fail(ex) - End If - End If - End If - '首线程错误 - If FileSize = -2 Then - SyncLock LockChain - Threads = Nothing - End SyncLock - End If + Log($"[Download] {LocalName}:出错,{If(TypeOf ex Is OperationCanceledException, $"已超时({Timeout}ms)", ex.GetDetail())},IP:{HostIp}") + RecordIPReliability(HostIp, -0.7) + SourceFail(Th, ex, False) Finally - If ResultStream IsNot Nothing Then ResultStream.Dispose() + '释放资源 + HttpRequest?.Dispose() + Response?.Dispose() + ResponseStream?.Dispose() + CancelToken?.Dispose() + If Not IsNoSplit Then ResultStream?.Dispose() + '改变计数 SyncLock NetTaskThreadCountLock NetTaskThreadCount -= 1 End SyncLock - '可能在没有下载完的时候开始合并文件了,这造成了大多数合并失败 - If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso State < NetState.Merge Then Merge() + '合并 + If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso + State < NetState.Merge AndAlso Th.State <> NetState.Error Then Merge(Th) End Try End Sub - ''' - ''' 从 HTTP 响应头中获取文件名。 - ''' 如果没有,返回 Nothing。 - ''' - Private Function GetFileNameFromResponse(response As HttpWebResponse) As String - Dim header As String = response.Headers("Content-Disposition") - If String.IsNullOrEmpty(header) Then Return Nothing - 'attachment; filename="filename.ext" - If Not header.Contains("filename=") Then Return Nothing - Return header.AfterLast("filename=").Trim(""""c, " "c).BeforeFirst(";") - End Function + Private Sub SourceFail(Th As NetThread, ex As Exception, IsMergeFailure As Boolean) + '状态变更 + SyncLock LockCount + Th.Source.FailCount += 1 + For Each Task In Tasks + Task.FailCount += 1 + Next + End SyncLock + Th.State = NetState.Error + Th.Source.Ex = ex + '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传) + If IsMergeFailure OrElse + ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse + ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse + ((ex.Message.Contains("(403)") OrElse ex.Message.Contains("(429)")) AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403/429,所以不应因此禁用下载源 + (Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then + '当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理 + Dim IsThisFail As Boolean = False + SyncLock LockSource + If Not Th.Source.IsFailed OrElse Th.Source.SingleThread = Th Then + IsThisFail = True + Th.Source.IsFailed = True + End If + End SyncLock + '……后续处理 + If IsThisFail Then + Log($"[Download] {LocalName}:下载源被禁用({Th.Source.Id}):{Th.Source.Url}") + Log(ex, $"{If(SourcesOnce.FirstOrDefault?.SingleThread Is Nothing, "", "单线程")}下载源 {Th.Source.Id} 已被禁用", + If(ex.Message.Contains("不支持分段下载") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(416)"), LogLevel.Developer, LogLevel.Debug)) + SyncLock LockSource + SourcesOnce.Remove(Th.Source) + End SyncLock + If ex.Message.Contains("空间不足") Then + '硬盘空间不足:强制失败 + Fail(ex) + ElseIf HasAvailableSource() AndAlso Not IsMergeFailure Then + '当前源失败,但还有下载源:正常地继续执行 + ElseIf Not Retried AndAlso Sources.Count > 1 Then + '合并失败或首次下载失败,未重试:将所有下载源重新标记为不允许断点续传的下载源,逐个重新尝试下载 + Hint($"{LocalName}:文件下载失败,正在自动重试……", If(ModeDebug, HintType.Critical, HintType.Info)) '在调试模式下给出红色提示 + Retried = True + SyncLock LockSource + SourcesOnce.Clear() + For Each Source In Sources + SourcesOnce.Add(Source) + Source.IsFailed = True + Next + End SyncLock + Reset() + SyncLock LockState + State = NetState.WaitForDownload + End SyncLock + ElseIf HasAvailableSource() AndAlso IsMergeFailure Then + '合并失败且单个源失败:继续下一个源 + Reset() + SyncLock LockState + State = NetState.WaitForDownload + End SyncLock + Else + '失败 + Log($"[Download] {LocalName}:已无可用下载源,下载失败") + Dim ExampleEx As Exception = Nothing + SyncLock LockSource + For Each Source As NetSource In Sources + Log("[Download] 已禁用的下载源:" & Source.Url) + If Source.Ex IsNot Nothing Then + ExampleEx = Source.Ex + Log(Source.Ex, "下载源禁用原因", LogLevel.Developer) + End If + Next + End SyncLock + Fail(ExampleEx) + End If + End If + End If + '清理当前已下载的内容 + If FileSize = -2 Then Reset() + End Sub + Private Sub Reset() + FileSize = -2 + Cache?.Dispose() : Cache = Nothing + SyncLock LockChain + Threads = Nothing + End SyncLock + NetManager.DownloadDone -= DownloadDone + SyncLock LockDone + DownloadDone = 0 + End SyncLock + SpeedLastDone = 0 + End Sub + + '最终收束事件 - '下载文件的最终收束事件 ''' ''' 下载完成。合并文件。 ''' - Private Sub Merge() + Private Sub Merge(Th As NetThread) '状态判断 SyncLock LockState If State < NetState.Merge Then State = NetState.Merge Else - Exit Sub + Return End If End SyncLock Dim RetryCount As Integer = 0 - Dim MergeFile As Stream = Nothing, AddWriter As BinaryWriter = Nothing Try Retry: + '创建文件夹 + If File.Exists(LocalPath) Then File.Delete(LocalPath) + Directory.CreateDirectory(GetPathFromFullPath(LocalPath)) SyncLock LockChain - '创建文件夹 - If File.Exists(LocalPath) Then File.Delete(LocalPath) - Dim Info As New FileInfo(LocalPath) - Info.Directory.Create() '合并文件 If IsNoSplit Then '仅有一个线程,从缓存中输出 - If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从缓存输出文件,长度:" & SmailFileCache.Count) - MergeFile = New FileStream(LocalPath, FileMode.Create) - AddWriter = New BinaryWriter(MergeFile) - AddWriter.Write(SmailFileCache.ToArray) - AddWriter.Dispose() : AddWriter = Nothing - MergeFile.Dispose() : MergeFile = Nothing + If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从内存输出文件") + Cache.Position = 0 + WriteFile(LocalPath, Cache) ElseIf Threads.DownloadDone = DownloadDone AndAlso Threads.Temp IsNot Nothing Then '仅有一个文件,直接复制 If ModeDebug Then Log($"[Download] {LocalName}:下载结束,仅有一个文件,无需合并") @@ -1373,40 +1279,37 @@ Retry: Else '有多个线程,合并 If ModeDebug Then Log($"[Download] {LocalName}:下载结束,开始合并文件") - MergeFile = New FileStream(LocalPath, FileMode.Create) - AddWriter = New BinaryWriter(MergeFile) - For Each Thread As NetThread In Threads - If Thread.DownloadDone = 0 OrElse Thread.Temp Is Nothing Then Continue For - Using fs As New FileStream(Thread.Temp, FileMode.Open, FileAccess.Read, FileShare.Read) - Using TempReader As New BinaryReader(fs) - AddWriter.Write(TempReader.ReadBytes(Thread.DownloadDone)) - End Using + Using MergeFile As New FileStream(LocalPath, FileMode.Create) + Using AddWriter As New BinaryWriter(MergeFile) + For Each Thread As NetThread In Threads + If Thread.DownloadDone = 0 OrElse Thread.Temp Is Nothing Then Continue For + Using fs As New FileStream(Thread.Temp, FileMode.Open, FileAccess.Read, FileShare.Read) + Using TempReader As New BinaryReader(fs) + AddWriter.Write(TempReader.ReadBytes(Thread.DownloadDone)) + End Using + End Using + Next End Using - Next - AddWriter.Dispose() : AddWriter = Nothing - MergeFile.Dispose() : MergeFile = Nothing - End If - '写入大小要求 - If Not IsUnknownSize AndAlso Check IsNot Nothing Then - If Check.ActualSize = -1 Then - Check.ActualSize = FileSize - ElseIf Check.ActualSize <> FileSize Then - Throw New Exception($"文件大小不一致:任务要求为 {Check.ActualSize} B,网络获取结果为 {FileSize}B") - End If + End Using End If '检查文件 Dim CheckResult As String = Check?.Check(LocalPath) + If CheckResult Is Nothing AndAlso Not IsUnknownSize AndAlso Check IsNot Nothing Then + If Check.ActualSize = -1 Then + CheckResult = (New FileChecker With {.ActualSize = FileSize}).Check(LocalPath) '不修改原始的 Checker,以免污染原始实例 + ElseIf Check.ActualSize <> FileSize Then + CheckResult = $"文件大小不一致:任务校验要求为 {Check.ActualSize},请求结果为 {FileSize}" + End If + End If If CheckResult IsNot Nothing Then Log($"[Download] {LocalName} 文件校验失败,下载线程细节:") - For Each Th As NetThread In Threads - Log($"[Download] {Th.Uuid}#,状态 {GetStringFromEnum(Th.State)},范围 {Th.DownloadStart}~{Th.DownloadStart + Th.DownloadDone},完成 {Th.DownloadDone},剩余 {Th.DownloadUndone}") + For Each T As NetThread In Threads + Log($"[Download] - {T.Uuid}#,状态 {GetStringFromEnum(T.State)},范围 {T.DownloadStart}~{T.DownloadEnd},已下载 {T.DownloadDone},未下载 {T.DownloadUndone}") Next Throw New Exception(CheckResult) End If '后处理 - If IsNoSplit Then - SmailFileCache = Nothing - Else + If Not IsNoSplit Then For Each Thread As NetThread In Threads If Thread.Temp IsNot Nothing Then File.Delete(Thread.Temp) Next @@ -1414,20 +1317,18 @@ Retry: Finish() End SyncLock Catch ex As Exception - Log(ex, "合并文件出错(" & LocalName & ")") - If MergeFile IsNot Nothing Then - MergeFile.Dispose() : MergeFile = Nothing - End If - If AddWriter IsNot Nothing Then - AddWriter.Dispose() : AddWriter = Nothing - End If - '重试 - If RetryCount <= 3 Then - Threading.Thread.Sleep(RandomInteger(500, 1000)) - RetryCount += 1 + RetryCount += 1 + Log(ex, $"合并文件出错,第 {RetryCount} 次尝试({LocalName})") + '重新尝试合并 + If RetryCount < 3 Then + Threading.Thread.Sleep(500 * RetryCount) GoTo Retry End If - Fail(ex) + '失败,禁用当前下载源并重启下载 + If File.Exists(LocalPath) Then File.Delete(LocalPath) + SourceFail(Th, ex, True) + Finally + Cache?.Dispose() End Try End Sub ''' @@ -1435,12 +1336,12 @@ Retry: ''' Private Sub Fail(Optional RaiseEx As Exception = Nothing) SyncLock LockState - If State >= NetState.Finish Then Exit Sub + If State >= NetState.Finish Then Return If RaiseEx IsNot Nothing Then Ex.Add(RaiseEx) '凉凉 State = NetState.Error End SyncLock - InterruptAndDelete() + AbortInternal() For Each Task In Tasks Task.OnFileFail(Me) Next @@ -1448,24 +1349,24 @@ Retry: ''' ''' 下载中断。 ''' - Public Sub Abort(CausedByTask As LoaderDownload) + Public Sub Abort(AbortedTask As LoaderDownload) '从特定任务中移除,如果它还属于其他任务,则继续下载 - Tasks.Remove(CausedByTask) - If Tasks.Any Then Exit Sub + Tasks.Remove(AbortedTask) + If Tasks.Any Then Return '确认中断 SyncLock LockState - If State >= NetState.Finish Then Exit Sub + If State >= NetState.Finish Then Return State = NetState.Error End SyncLock - InterruptAndDelete() + AbortInternal() End Sub - Private Sub InterruptAndDelete() + Private Sub AbortInternal() On Error Resume Next - If File.Exists(LocalPath) Then File.Delete(LocalPath) + Reset() SyncLock NetManager.LockRemain NetManager.FileRemain -= 1 - Log($"[Download] {LocalName}:状态 {State},剩余文件 {NetManager.FileRemain}") End SyncLock + Log($"[Download] {LocalName}:已终止,当前状态 {State}") End Sub '状态改变接口 @@ -1474,13 +1375,13 @@ Retry: ''' Public Sub Finish(Optional PrintLog As Boolean = True) SyncLock LockState - If State >= NetState.Finish Then Exit Sub + If State >= NetState.Finish Then Return State = NetState.Finish End SyncLock SyncLock NetManager.LockRemain NetManager.FileRemain -= 1 - If PrintLog Then Log("[Download] " & LocalName & ":已完成,剩余文件 " & NetManager.FileRemain) End SyncLock + If PrintLog Then Log($"[Download] {LocalName}:已完成") For Each Task In Tasks Task.OnFileFinish(Me) Next @@ -1601,19 +1502,19 @@ NextElement: '输入检测 If Not Files.Any() Then OnFinish() - Exit Sub + Return End If For Each File As NetFile In Files If File Is Nothing Then Throw New ArgumentException("存在空文件请求!") For Each Source As NetSource In File.Sources If Not (Source.Url.StartsWithF("https://", True) OrElse Source.Url.StartsWithF("http://", True)) Then - Source.Ex = New ArgumentException("输入的下载链接不正确!") + Source.Ex = New ArgumentException("输入的下载链接不正确:" & Source.Url) Source.IsFailed = True End If Next - If File.IsSourceFailed() Then Throw New ArgumentException("输入的下载链接不正确!") - If Not File.LocalPath.ToLower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确!") - If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径!") + If Not File.HasAvailableSource() Then Throw New ArgumentException("输入的下载链接不正确!") + If Not File.LocalPath.ToLower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确:" & File.LocalPath) + If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径:" & File.LocalPath) '文件夹检测 Dim DirPath As String = New FileInfo(File.LocalPath).Directory.FullName If Not Directory.Exists(DirPath) Then Directory.CreateDirectory(DirPath) @@ -1641,30 +1542,31 @@ NextElement: If FilesInThread.Count = FilesPerThread Then Dim FilesToRun As New List(Of NetFile) FilesToRun.AddRange(FilesInThread) - RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid) + RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "下载 文件复制 " & Uuid) FilesInThread.Clear() End If Next If FilesInThread.Any Then Dim FilesToRun As New List(Of NetFile) FilesToRun.AddRange(FilesInThread) - RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid) + RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "下载 文件复制 " & Uuid) FilesInThread.Clear() End If Catch ex As Exception OnFail(New List(Of Exception) From {ex}) End Try - End Sub, "NetTask " & Uuid & " Main") + End Sub, "L/下载 " & Uuid) End Sub Private Sub StartCopy(Files As List(Of NetFile), FolderList As List(Of String)) Try - If ModeDebug Then Log("[Download] 检查线程分配文件数:" & Files.Count & ",线程名:" & Thread.CurrentThread.Name) + If ModeDebug Then Log($"[Download] 检查线程分配文件数:{Files.Count},线程名:{Thread.CurrentThread.Name}") '试图从已存在的 Minecraft 文件夹中寻找目标文件 Dim ExistFiles As New List(Of KeyValuePair(Of NetFile, String)) '{NetFile, Target As String} For Each File As NetFile In Files Dim ExistFilePath As String = Nothing '判断是否有已存在的文件 - If File.Check IsNot Nothing AndAlso McFolderList IsNot Nothing AndAlso PathMcFolder IsNot Nothing AndAlso File.Check.CanUseExistsFile AndAlso File.LocalPath.StartsWithF(PathMcFolder) Then + If File.Check IsNot Nothing AndAlso McFolderList IsNot Nothing AndAlso PathMcFolder IsNot Nothing AndAlso + File.Check.CanUseExistsFile AndAlso File.LocalPath.StartsWithF(PathMcFolder) Then Dim Relative = File.LocalPath.Replace(PathMcFolder, "") For Each Folder In FolderList Dim Target = Folder & Relative @@ -1690,7 +1592,7 @@ NextElement: For Each FileToken In ExistFiles Dim File As NetFile = FileToken.Key SyncLock LockState - If File.State > NetState.WaitForCopy Then Exit Sub + If File.State > NetState.WaitForCopy Then Return End SyncLock Dim LocalPath As String = FileToken.Value Dim RetryCount As Integer = 0 @@ -1701,7 +1603,7 @@ Retry: File.Finish(False) Catch ex As Exception RetryCount += 1 - Log(ex, String.Format("复制已存在的文件失败,重试第 {2} 次({0} -> {1})", LocalPath, File.LocalPath, RetryCount)) + Log(ex, $"复制已存在的文件失败,重试第 {RetryCount} 次({LocalPath} -> {File.LocalPath})") If RetryCount < 3 Then Thread.Sleep(200) GoTo Retry @@ -1719,14 +1621,14 @@ Retry: '要求全部文件完成 SyncLock FileRemainLock FileRemain -= 1 - If FileRemain > 0 Then Exit Sub + If FileRemain > 0 Then Return End SyncLock OnFinish() End Sub Public Sub OnFinish() RaisePreviewFinish() SyncLock LockState - If State > LoadState.Loading Then Exit Sub + If State > LoadState.Loading Then Return State = LoadState.Finished End SyncLock End Sub @@ -1739,36 +1641,41 @@ Retry: End Sub Public Sub OnFail(ExList As List(Of Exception)) SyncLock LockState - If State > LoadState.Loading Then Exit Sub + If State > LoadState.Loading Then Return If ExList Is Nothing OrElse Not ExList.Any() Then ExList = New List(Of Exception) From {New Exception("未知错误!")} - '寻找第一个不是 404 的下载源 - Dim UsefulExs = ExList.Where(Function(e) Not e.Message.Contains("(404)")).ToList - [Error] = If(UsefulExs.Any, UsefulExs(0), ExList(0)) + '寻找有效的错误信息 + Dim UsefulExs = ExList.Where(Function(e) TypeOf e IsNot OperationCanceledException AndAlso TypeOf e IsNot ThreadInterruptedException).ToList + [Error] = If(UsefulExs.FirstOrDefault, ExList.FirstOrDefault) '获取实际失败的文件 For Each File In Files - If File.State = NetState.Error Then - [Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf & Join( - File.Sources.Select(Function(s) If(s.Ex Is Nothing, s.Url, s.Ex.Message & "(" & s.Url & ")")), vbCrLf), [Error]) - Exit For - End If + If File.State <> NetState.Error Then Continue For + Dim Detail As String = Join(File.Sources.Select(Function(s) $"{If(s.Ex Is Nothing, "无错误信息。", s.Ex.GetBrief())}({s.Url})"), vbCrLf) + [Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf & + "各下载源的错误如下:" & vbCrLf & Detail, + [Error]) + '上报 + Telemetry("文件下载失败", + "FileName", File.LocalName, + "Exception", Detail) + Exit For Next '在设置 Error 对象后再更改为失败,避免 WaitForExit 无法捕获错误 State = LoadState.Failed End SyncLock '中断所有文件 For Each TaskFile In Files - If TaskFile.State < NetState.Merge Then TaskFile.State = NetState.Error + TaskFile.Abort(Me) Next '在退出同步锁后再进行日志输出 Dim ErrOutput As New List(Of String) For Each Ex As Exception In ExList - ErrOutput.Add(GetExceptionDetail(Ex)) + ErrOutput.Add(Ex.GetDetail()) Next Log("[Download] " & Join(ErrOutput.Distinct.ToArray, vbCrLf)) End Sub Public Overrides Sub Abort() SyncLock LockState - If State >= LoadState.Finished Then Exit Sub + If State >= LoadState.Finished Then Return State = LoadState.Aborted End SyncLock Log("[Download] " & Name & " 已取消!") @@ -1885,7 +1792,7 @@ Retry: ''' 启动监控线程,用于新增下载线程。 ''' Private Sub StartManager() - If IsManagerStarted Then Exit Sub + If IsManagerStarted Then Return IsManagerStarted = True Dim ThreadStarter = Sub(Id As Integer) '0 或 1 @@ -1912,7 +1819,7 @@ Retry: For Each File As NetFile In WaitingFiles If NetTaskThreadCount >= NetTaskThreadLimit Then Continue While '最大线程数检查 Dim NewThread = File.TryBeginThread() - If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) + If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(40) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) Next '为进行中的文件追加线程 If Speed >= NetTaskSpeedLimitLow Then Continue While '下载速度足够,无需新增 @@ -1932,11 +1839,11 @@ Retry: '新增线程 If PreparingCount > DownloadingCount Then Continue For '准备中的线程已多于下载中的线程,不再新增 Dim NewThread = File.TryBeginThread() - If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) + If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(40) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次) Next End While Catch ex As Exception - Log(ex, $"下载管理启动线程 {Id} 出错", LogLevel.Assert) + Log(ex, $"下载管理启动线程 {Id} 出错", LogLevel.Critical) End Try End Sub RunInNewThread(Sub() ThreadStarter(0), "NetManager ThreadStarter 0") @@ -1960,13 +1867,13 @@ Retry: Loop End While Catch ex As Exception - Log(ex, "下载管理刷新线程出错", LogLevel.Assert) + Log(ex, "下载管理刷新线程出错", LogLevel.Critical) End Try End Sub, "NetManager StatRefresher") End Sub Private IsManagerStarted As Boolean = False - 'Public FileRemainList As New List(Of String) + Private DownloadCacheLock As New Object Private IsDownloadCacheCleared As Boolean = False ''' ''' 开始一个下载任务。 @@ -1974,15 +1881,19 @@ Retry: Public Sub Start(Task As LoaderDownload) StartManager() '清理缓存 - If Not IsDownloadCacheCleared Then - Try - DeleteDirectory(PathTemp & "Download") - Catch ex As Exception - Log(ex, "清理下载缓存失败") - End Try + SyncLock DownloadCacheLock '防止同时开启多个下载任务时重复清理 + If Not IsDownloadCacheCleared Then + Try + Log("[Net] 开始清理下载缓存") + DeleteDirectory(PathTemp & "Download") + Log("[Net] 下载缓存已清理") + Catch ex As Exception + Log(ex, "清理下载缓存失败") + End Try + Directory.CreateDirectory(PathTemp & "Download") + End If IsDownloadCacheCleared = True - End If - Directory.CreateDirectory(PathTemp & "Download") + End SyncLock '文件处理 SyncLock LockFiles '添加每个文件 @@ -1997,9 +1908,8 @@ Retry: Files(File.LocalPath) = File SyncLock LockRemain FileRemain += 1 - If ModeDebug Then Log("[Download] " & File.LocalName & ":已替换列表,剩余文件 " & FileRemain) - 'FileRemainList.Add(File.LocalPath) End SyncLock + If ModeDebug Then Log($"[Download] {File.LocalName}:重新加入下载列表") Else '该文件正在下载中 '将当前文件替换成下载中的文件,即两个任务指向同一个文件 @@ -2012,9 +1922,8 @@ Retry: Files.Add(File.LocalPath, File) SyncLock LockRemain FileRemain += 1 - If ModeDebug Then Log("[Download] " & File.LocalName & ":已加入列表,剩余文件 " & FileRemain) - 'FileRemainList.Add(File.LocalPath) End SyncLock + If ModeDebug Then Log($"[Download] {File.LocalName}:已加入下载列表") End If Task.Files(i) = File '回设 Next @@ -2037,4 +1946,38 @@ Retry: Return False End Function +#End Region + + ''' + ''' 测试 Ping。失败则返回 -1。 + ''' + Public Function Ping(Ip As String, Optional Timeout As Integer = 10000, Optional MakeLog As Boolean = True) As Integer + Dim PingResult As NetworkInformation.PingReply + Try + PingResult = (New NetworkInformation.Ping).Send(Ip) + Catch ex As Exception + If MakeLog Then Log("[Net] Ping " & Ip & " 失败:" & ex.Message) + Return -1 + End Try + If PingResult.Status = NetworkInformation.IPStatus.Success Then + If MakeLog Then Log("[Net] Ping " & Ip & " 结束:" & PingResult.RoundtripTime & "ms") + Return PingResult.RoundtripTime + Else + If MakeLog Then Log("[Net] Ping " & Ip & " 失败") + Return -1 + End If + End Function + + ''' + ''' 判断某个 Exception 是否为网络问题所导致。 + ''' + Public Function IsNetworkRelated(Ex As Exception) As Boolean + Dim Detail = Ex.GetDetail() + If Detail.Contains("(403)") Then Return False + Return { + "(408)", "超时", "timeout", "网络请求失败", "连接尝试失败", "远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", + "由于目标计算机积极拒绝", "基础连接已经关闭" + }.Any(Function(k) Detail.ContainsF(k, True)) + End Function + End Module diff --git a/Plain Craft Launcher 2/Modules/Base/ModValidate.vb b/Plain Craft Launcher 2/Modules/Base/ModValidate.vb index e873030..54e9859 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModValidate.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModValidate.vb @@ -9,6 +9,7 @@ Public Module ModValidate Dim Result As String = "" For Each ValidateRule As Validate In ValidateRules Result = ValidateRule.Validate(Text) + If Result Is Nothing Then Return "" If Result <> "" Then Return Result Next Return Result @@ -21,7 +22,7 @@ End Module ''' Public MustInherit Class Validate ''' - ''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因。 + ''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因;若需要中断检查并直接通过,返回 Nothing。 ''' Public MustOverride Function Validate(Str As String) As String End Class @@ -135,9 +136,9 @@ Public Class ValidateLength Me.Max = Max End Sub Public Overrides Function Validate(Str As String) As String - If Len(Str) <> Max AndAlso Max = Min Then Return "长度必须为 " & Max & " 个字符!" - If Len(Str) > Max Then Return "长度最长为 " & Max & " 个字符!" - If Len(Str) < Min Then Return "长度至少需 " & Min & " 个字符!" + If Len(Str) <> Max AndAlso Max = Min Then Return $"长度必须为 {Max} 个字符!" + If Len(Str) > Max Then Return $"长度最长为 {Max} 个字符!" + If Len(Str) < Min Then Return $"长度至少需 {Min} 个字符!" Return "" End Function End Class @@ -147,7 +148,7 @@ End Class ''' Public Class ValidateExcept Inherits Validate - Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String) + Public Property Excepts As New ObjectModel.Collection(Of String) Public Property ErrorMessage As String Public Sub New() ErrorMessage = "输入内容不能包含 %" @@ -156,12 +157,15 @@ Public Class ValidateExcept Me.Excepts = Excepts Me.ErrorMessage = ErrorMessage End Sub - Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能包含 %") - Me.Excepts = New ObjectModel.Collection(Of String) - Me.ErrorMessage = ErrorMessage - For Each Data As String In Excepts - Me.Excepts.Add(Data) - Next + Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能包含 %") + Me.New( + New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)), + ErrorMessage) + End Sub + Public Sub New(Excepts As IEnumerable(Of Char), Optional ErrorMessage As String = "输入内容不能包含 %") + Me.New( + New ObjectModel.Collection(Of String)(Excepts.Select(Function(c) c.ToString).ToList), + ErrorMessage) End Sub Public Overrides Function Validate(Str As String) As String For Each Ch As String In Excepts @@ -180,7 +184,7 @@ End Class ''' Public Class ValidateExceptSame Inherits Validate - Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String) + Public Property Excepts As New ObjectModel.Collection(Of String) Public Property ErrorMessage As String Public Property IgnoreCase As Boolean = False Public Sub New() @@ -190,13 +194,15 @@ Public Class ValidateExceptSame Me.ErrorMessage = ErrorMessage Me.IgnoreCase = IgnoreCase End Sub - Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False) - Me.Excepts = New ObjectModel.Collection(Of String) - For Each Data As String In Excepts - Me.Excepts.Add(Data) - Next - Me.ErrorMessage = ErrorMessage - Me.IgnoreCase = IgnoreCase + Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False) + Me.New( + New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)), + ErrorMessage, IgnoreCase) + End Sub + Public Sub New(Except As String, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False) + Me.New( + New ObjectModel.Collection(Of String) From {Except}, + ErrorMessage, IgnoreCase) End Sub Public Overrides Function Validate(Str As String) As String If Str Is Nothing Then Return ErrorMessage.Replace("%", "null") @@ -234,21 +240,21 @@ Public Class ValidateFolderName Try '检查是否为空 Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str) - If Not LengthCheck = "" Then Return LengthCheck + If LengthCheck <> "" Then Return LengthCheck '检查空格 If Str.StartsWithF(" ") Then Return "文件夹名不能以空格开头!" If Str.EndsWithF(" ") Then Return "文件夹名不能以空格结尾!" '检查长度 LengthCheck = New ValidateLength(1, 100).Validate(Str) - If Not LengthCheck = "" Then Return LengthCheck + If LengthCheck <> "" Then Return LengthCheck '检查尾部小数点 If Str.EndsWithF(".") Then Return "文件夹名不能以小数点结尾!" '检查特殊字符 Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件夹名不可包含 % 字符!").Validate(Str) - If Not CharactCheck = "" Then Return CharactCheck + If CharactCheck <> "" Then Return CharactCheck '检查特殊字符串 Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!", True).Validate(Str) - If Not InvalidStrCheck = "" Then Return InvalidStrCheck + If InvalidStrCheck <> "" Then Return InvalidStrCheck '检查 NTFS 8.3 文件名(#4505) If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!" '检查文件夹重名 @@ -295,15 +301,15 @@ Public Class ValidateFileName If Str.EndsWithF(" ") Then Return "文件名不能以空格结尾!" '检查长度 LengthCheck = New ValidateLength(1, 253).Validate(Str & If(ParentFolder, "")) - If Not LengthCheck = "" Then Return LengthCheck + If LengthCheck <> "" Then Return LengthCheck '检查尾部小数点 If Str.EndsWithF(".") Then Return "文件名不能以小数点结尾!" '检查特殊字符 Dim CharactCheck As String = New ValidateExcept(IO.Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件名不可包含 % 字符!").Validate(Str) - If Not CharactCheck = "" Then Return CharactCheck + If CharactCheck <> "" Then Return CharactCheck '检查特殊字符串 Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件名不可为 %!", True).Validate(Str) - If Not InvalidStrCheck = "" Then Return InvalidStrCheck + If InvalidStrCheck <> "" Then Return InvalidStrCheck '检查 NTFS 8.3 文件名(#4505) If RegexCheck(Str, ".{2,}~\d") Then Return "文件名不能包含这一特殊格式!" '检查文件重名 @@ -342,10 +348,10 @@ Public Class ValidateFolderPath If Not Str.TrimEnd("\").EndsWithF(":") Then Str = Str.TrimEnd("\") '检查是否为空 Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str) - If Not LengthCheck = "" Then Return LengthCheck + If LengthCheck <> "" Then Return LengthCheck '检查长度 LengthCheck = New ValidateLength(1, 254).Validate(Str) - If Not LengthCheck = "" Then Return LengthCheck + If LengthCheck <> "" Then Return LengthCheck '检查开头 If Str.StartsWithF("\\Mac\") Then GoTo Fin For Each Drive As DriveInfo In My.Computer.FileSystem.Drives diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb index 59ad53e..d7f0dc8 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb @@ -2,6 +2,10 @@ Public Enum CompType ''' + ''' 允许任意种类,或种类未知。 + ''' + Any = -1 + ''' ''' Mod。 ''' [Mod] = 0 @@ -13,6 +17,19 @@ ''' 资源包。 ''' ResourcePack = 2 + ''' + ''' 光影包。 + ''' + Shader = 3 + ''' + ''' CurseForge:数据包。 + ''' Modrinth:数据包,或数据包与 Mod 的混合。 + ''' + DataPack = 4 + ''' + ''' 服务端插件。 + ''' + Plugin = 5 End Enum Public Enum CompModLoaderType 'https://docs.curseforge.com/?http#tocS_ModLoaderType @@ -61,8 +78,8 @@ If SplitedLine.Count >= 2 Then Entry.ChineseName = SplitedLine(1) If Entry.ChineseName.Contains("*") Then '处理 * - Entry.ChineseName = Entry.ChineseName.Replace("*", " (" & - String.Join(" ", If(Entry.CurseForgeSlug, Entry.ModrinthSlug).Split("-").Select(Function(w) w.Substring(0, 1).ToUpper & w.Substring(1, w.Length - 1))) & ")") + Entry.ChineseName = Entry.ChineseName.Replace("*", + $" ({If(Entry.CurseForgeSlug, Entry.ModrinthSlug).Replace("-", " ").Capitalize})") End If End If _CompDatabase.Add(Entry) @@ -111,6 +128,7 @@ Public ReadOnly FromCurseForge As Boolean ''' ''' 工程的种类。 + ''' 由于 Modrinth 混合使用 Mod 和数据包,结果不一定准确。 ''' Public ReadOnly Type As CompType ''' @@ -178,7 +196,9 @@ Get If Not LoadedDatabase Then LoadedDatabase = True - If Type = CompType.Mod Then _DatabaseEntry = CompDatabase.FirstOrDefault(Function(c) If(FromCurseForge, c.CurseForgeSlug, c.ModrinthSlug) = Slug) + If Type = CompType.Mod OrElse Type = CompType.DataPack Then + _DatabaseEntry = CompDatabase.FirstOrDefault(Function(c) If(FromCurseForge, c.CurseForgeSlug, c.ModrinthSlug) = Slug) + End If End If Return _DatabaseEntry End Get @@ -254,6 +274,20 @@ LogoUrl = Data("logo")("thumbnailUrl") End If End If + 'Type + If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then + Type = CompType.Mod + ElseIf Website.Contains("/modpacks/") Then + Type = CompType.ModPack + ElseIf Website.Contains("/resourcepacks/") Then + Type = CompType.ResourcePack + ElseIf Website.Contains("/texture-packs/") Then + Type = CompType.ResourcePack + ElseIf Website.Contains("/shaders/") Then + Type = CompType.Shader + Else + Type = CompType.DataPack + End If 'FileIndexes / GameVersions / ModLoaders ModLoaders = New List(Of CompModLoaderType) Dim Files As New List(Of KeyValuePair(Of Integer, List(Of String))) 'FileId, GameVersions @@ -274,20 +308,12 @@ Select(Function(v) CInt(Val(v.Split(".")(1).BeforeFirst("-")))).Where(Function(v) v > 0). Distinct.OrderByDescending(Function(v) v).ToList ModLoaders = ModLoaders.Distinct.OrderBy(Of Integer)(Function(t) t).ToList - 'Type - If Website.Contains("/mc-mods/") OrElse Website.Contains("/mod/") Then - Type = CompType.Mod - ElseIf Website.Contains("/modpacks/") Then - Type = CompType.ModPack - Else - Type = CompType.ResourcePack - End If 'Tags Tags = New List(Of String) - For Each Category In If(Data("categories"), New JArray). '镜像源 API 可能丢失此字段:https://github.com/Hex-Dragon/PCL2/issues/4267#issuecomment-2254590831 + For Each Category In If(Data("categories"), New JArray). '镜像源 API 可能丢失此字段 (4267#issuecomment-2254590831) Select(Of Integer)(Function(t) t("id")).Distinct.OrderByDescending(Function(c) c) Select Case Category - 'Mod + 'Mod Case 406 : Tags.Add("世界元素") Case 407 : Tags.Add("生物群系") Case 410 : Tags.Add("维度") @@ -311,7 +337,7 @@ Case 435 : Tags.Add("服务器") Case 5191 : Tags.Add("改良") Case 421 : Tags.Add("支持库") - '整合包 + '整合包 Case 4484 : Tags.Add("多人") Case 4479 : Tags.Add("硬核") Case 4483 : Tags.Add("战斗") @@ -321,17 +347,43 @@ Case 4475 : Tags.Add("冒险") Case 4476 : Tags.Add("探索") Case 4477 : Tags.Add("小游戏") - Case 4471 : Tags.Add("科幻") + Case 4474 : Tags.Add("科幻") Case 4736 : Tags.Add("空岛") Case 5128 : Tags.Add("原版改良") Case 4487 : Tags.Add("FTB") Case 4480 : Tags.Add("基于地图") Case 4481 : Tags.Add("轻量") Case 4482 : Tags.Add("大型") - 'FUTURE: Res + '资源包 + Case 403 : Tags.Add("原版风") + Case 400 : Tags.Add("写实风") + Case 401 : Tags.Add("现代风") + Case 402 : Tags.Add("中世纪") + Case 399 : Tags.Add("蒸汽朋克") + Case 5244 : Tags.Add("含字体") + Case 404 : Tags.Add("动态效果") + Case 4465 : Tags.Add("兼容 Mod") + Case 393 : Tags.Add("16x") + Case 394 : Tags.Add("32x") + Case 395 : Tags.Add("64x") + Case 396 : Tags.Add("128x") + Case 397 : Tags.Add("256x") + Case 398 : Tags.Add("超高清") + Case 5193 : Tags.Add("数据包") '有这个 Tag 的项会从资源包请求中被移除 + '光影包 + Case 6553 : Tags.Add("写实风") + Case 6554 : Tags.Add("幻想风") + Case 6555 : Tags.Add("原版风") + '数据包 + Case 6948 : Tags.Add("冒险") + Case 6949 : Tags.Add("幻想") + Case 6950 : Tags.Add("支持库") + Case 6952 : Tags.Add("魔法") + Case 6946 : Tags.Add("Mod 相关") + Case 6951 : Tags.Add("科技") + Case 6953 : Tags.Add("实用") End Select Next - If Not Tags.Any() Then Tags.Add("杂项") #End Region Else #Region "Modrinth" @@ -353,9 +405,10 @@ Distinct.OrderByDescending(Function(v) v).ToList 'Type Select Case Data("project_type").ToString - Case "mod" : Type = CompType.Mod Case "modpack" : Type = CompType.ModPack Case "resourcepack" : Type = CompType.ResourcePack + Case "shader" : Type = CompType.Shader + Case Else : Type = CompType.Mod 'Modrinth 将数据包标为 Mod End Select 'Tags & ModLoaders Tags = New List(Of String) @@ -377,41 +430,78 @@ Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric) Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt) Case "neoforge" : ModLoaders.Add(CompModLoaderType.NeoForge) - 'Mod - Case "worldgen" : Tags.Add("世界元素") + Case "datapack" : Type = CompType.DataPack '若包含数据包版本,则优先标为 DataPack + '共用 Case "technology" : Tags.Add("科技") + Case "magic" : Tags.Add("魔法") + Case "adventure" : Tags.Add("冒险") + Case "utility" : Tags.Add("实用") + Case "optimization" : Tags.Add("性能优化") + Case "vanilla-like" : Tags.Add("原版风") + Case "realistic" : Tags.Add("写实风") + 'Mod/数据包 + Case "worldgen" : Tags.Add("世界元素") Case "food" : Tags.Add("食物/烹饪") Case "game-mechanics" : Tags.Add("游戏机制") Case "transportation" : Tags.Add("运输") Case "storage" : Tags.Add("仓储") - Case "magic" : Tags.Add("魔法") - Case "adventure" : Tags.Add("冒险") - Case "decoration" : Tags.Add("装饰") - Case "mobs" : Tags.Add("生物") - Case "equipment" : Tags.Add("装备") - Case "optimization" : 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 "social" : Tags.Add("服务器") - Case "utility" : Tags.Add("改良") Case "library" : Tags.Add("支持库") '整合包 Case "multiplayer" : Tags.Add("多人") - Case "optimization" : Tags.Add("性能优化") Case "challenging" : Tags.Add("硬核") Case "combat" : Tags.Add("战斗") Case "quests" : Tags.Add("任务") - Case "technology" : Tags.Add("科技") - Case "magic" : Tags.Add("魔法") - Case "adventure" : Tags.Add("冒险") - Case "kitchen-sink" : Tags.Add("水槽包/大杂烩") + Case "kitchen-sink" : Tags.Add("水槽包") Case "lightweight" : Tags.Add("轻量") - 'FUTURE: Res + '资源包 + Case "simplistic" : Tags.Add("简洁") + Case "combat" : Tags.Add("战斗") + Case "tweaks" : Tags.Add("改良") + + Case "8x-" : Tags.Add("极简") + Case "16x" : Tags.Add("16x") + Case "32x" : Tags.Add("32x") + Case "48x" : Tags.Add("48x") + Case "64x" : Tags.Add("64x") + Case "128x" : Tags.Add("128x") + Case "256x" : Tags.Add("256x") + Case "512x+" : Tags.Add("超高清") + + Case "audio" : Tags.Add("含声音") + Case "fonts" : Tags.Add("含字体") + Case "models" : Tags.Add("含模型") + Case "gui" : Tags.Add("含 UI") + Case "locale" : Tags.Add("含语言") + Case "core-shaders" : Tags.Add("核心着色器") + Case "modded" : Tags.Add("兼容 Mod") + '光影包 + Case "fantasy" : Tags.Add("幻想风") + Case "semi-realistic" : Tags.Add("半写实风") + Case "cartoon" : Tags.Add("卡通风") + '暂时不添加性能负荷 Tag + 'Case "potato" : Tags.Add("极低") + 'Case "low" : Tags.Add("低") + 'Case "medium" : Tags.Add("中") + 'Case "high" : Tags.Add("高") + Case "colored-lighting" : Tags.Add("彩色光照") + Case "path-tracing" : Tags.Add("路径追踪") + Case "pbr" : Tags.Add("PBR") + Case "reflections" : Tags.Add("反射") + + Case "iris" : Tags.Add("Iris") + Case "optifine" : Tags.Add("OptiFine") + Case "vanilla" : Tags.Add("原版可用") End Select Next - If Not Tags.Any() Then Tags.Add("杂项") - Tags.Sort() - ModLoaders.Sort() #End Region End If + If Not Tags.Any() Then Tags.Add("其他") + Tags.Sort() + ModLoaders.Sort() End If '保存缓存 CompProjectCache(Id) = Me @@ -534,7 +624,7 @@ NewItem.ColumnVersion1.Width = New GridLength(0) NewItem.ColumnVersion2.MaxWidth = 0 NewItem.ColumnVersion3.Width = New GridLength(0) - ElseIf ShowMcVersionDesc AndAlso ShowMcVersionDesc Then + ElseIf ShowMcVersionDesc AndAlso ShowLoaderDesc Then '全部显示 NewItem.LabVersion.Text = If(ModLoaderDescriptionPart = "", "", ModLoaderDescriptionPart & " ") & GameVersionDescription ElseIf ShowMcVersionDesc Then @@ -664,8 +754,8 @@ NoSubtitle: If FromCurseForge = Project.FromCurseForge Then Return False 'Mod 加载器一致 If ModLoaders.Count <> Project.ModLoaders.Count OrElse ModLoaders.Except(Project.ModLoaders).Any() Then Return False - 'MC 版本一致 - If GameVersions.Count <> Project.GameVersions.Count OrElse GameVersions.Except(Project.GameVersions).Any() Then Return False + '若不为光影,则要求 MC 版本一致 + If Type <> CompType.Shader AndAlso (GameVersions.Count <> Project.GameVersions.Count OrElse GameVersions.Except(Project.GameVersions).Any()) Then Return False '最近更新时间差距在一周以内 If LastUpdate IsNot Nothing AndAlso Project.LastUpdate IsNot Nothing AndAlso Math.Abs((LastUpdate - Project.LastUpdate).Value.TotalDays) > 7 Then Return False @@ -775,8 +865,12 @@ NoSubtitle: Address += "&classId=6" Case CompType.ModPack Address += "&classId=4471" + Case CompType.DataPack + Address += "&classId=6945" + Case CompType.Shader + Address += "&classId=6552" Case CompType.ResourcePack - 'FUTURE: Res + Address += "&classId=12" End Select Address += "&categoryId=" & If(Tag = "", "0", Tag.BeforeFirst("/")) If ModLoader <> CompModLoaderType.Any Then Address += "&modLoaderType=" & CType(ModLoader, Integer) @@ -856,38 +950,39 @@ NoSubtitle: ''' 根据搜索请求获取一系列的工程列表。需要基于加载器运行。 ''' Public Sub CompProjectsGet(Task As LoaderTask(Of CompProjectRequest, Integer)) - Dim Storage = Task.Input.Storage '避免多线程问题 + Dim Request As CompProjectRequest = Task.Input + Dim Storage = Request.Storage '避免多线程问题 - If Task.Input.Storage.Results.Count >= Task.Input.TargetResultCount Then - Log($"[Comp] 已有 {Task.Input.Storage.Results.Count} 个结果,多于所需的 {Task.Input.TargetResultCount} 个结果,结束处理") - Exit Sub - ElseIf Not Task.Input.CanContinue Then - If Not Task.Input.Storage.Results.Any() Then + If Request.Storage.Results.Count >= Request.TargetResultCount Then + Log($"[Comp] 已有 {Request.Storage.Results.Count} 个结果,多于所需的 {Request.TargetResultCount} 个结果,结束处理") + Return + ElseIf Not Request.CanContinue Then + If Not Request.Storage.Results.Any() Then Throw New Exception("没有符合条件的结果") Else - Log($"[Comp] 已有 {Task.Input.Storage.Results.Count} 个结果,少于所需的 {Task.Input.TargetResultCount} 个结果,但无法继续获取,结束处理") - Exit Sub + Log($"[Comp] 已有 {Request.Storage.Results.Count} 个结果,少于所需的 {Request.TargetResultCount} 个结果,但无法继续获取,结束处理") + Return End If End If #Region "拒绝 1.13- Quilt(这个版本根本没有 Quilt)" - If Task.Input.ModLoader = CompModLoaderType.Quilt AndAlso VersionSortInteger(If(Task.Input.GameVersion, "1.15"), "1.14") = -1 Then - Throw New Exception("Quilt 不支持 Minecraft " & Task.Input.GameVersion) + If Request.ModLoader = CompModLoaderType.Quilt AndAlso VersionSortInteger(If(Request.GameVersion, "1.15"), "1.14") = -1 Then + Throw New Exception("Quilt 不支持 Minecraft " & Request.GameVersion) End If #End Region #Region "处理搜索文本,赋值回 Task.Input.SearchText" - Dim RawFilter As String = If(Task.Input.SearchText, "").Trim - Task.Input.SearchText = RawFilter + Dim RawFilter As String = If(Request.SearchText, "").Trim + Request.SearchText = RawFilter RawFilter = RawFilter.ToLower Log("[Comp] 工程列表搜索原始文本:" & RawFilter) '中文请求关键字处理 Dim IsChineseSearch As Boolean = RegexCheck(RawFilter, "[\u4e00-\u9fbb]") AndAlso Not String.IsNullOrEmpty(RawFilter) - If IsChineseSearch AndAlso Task.Input.Type = CompType.Mod Then + If IsChineseSearch AndAlso (Request.Type = CompType.Mod OrElse Request.Type = CompType.DataPack) Then '构造搜索请求 Dim SearchEntries As New List(Of SearchEntry(Of CompDatabaseEntry)) For Each Entry In CompDatabase @@ -900,7 +995,7 @@ NoSubtitle: }) Next '获取搜索结果 - Dim SearchResults = Search(SearchEntries, Task.Input.SearchText, 3) + Dim SearchResults = Search(SearchEntries, Request.SearchText, 3) If Not SearchResults.Any() Then Throw New Exception("无搜索结果,请尝试搜索英文名称") Dim SearchResult As String = "" For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个 @@ -918,14 +1013,14 @@ NoSubtitle: If SearchResult.Split(" ").Count > 3 AndAlso {"ftb"}.Contains(Word.ToLower) Then Continue For RealFilter += Word.TrimStart("{[(").TrimEnd("}])") & " " Next - Task.Input.SearchText = RealFilter + Request.SearchText = RealFilter Log("[Comp] 中文搜索最终关键词:" & RealFilter, LogLevel.Developer) End If '驼峰英文请求关键字处理 - Dim SpacedKeywords = Task.Input.SearchText.RegexReplace("([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)", "$& ") - Dim ConnectedKeywords = Task.Input.SearchText.Replace(" ", "") - Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Task.Input.SearchText, ConnectedKeywords & " " & RawFilter)).ToLower + Dim SpacedKeywords = Request.SearchText.RegexReplace("([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)", "$& ") + Dim ConnectedKeywords = Request.SearchText.Replace(" ", "") + Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Request.SearchText, ConnectedKeywords & " " & RawFilter)).ToLower '最终处理关键字:分割、去重 Dim RightKeywords As New List(Of String) @@ -939,15 +1034,15 @@ NoSubtitle: RightKeywords.Add(Keyword) Next If RawFilter.Length > 0 AndAlso Not RightKeywords.Any() Then - Task.Input.SearchText = RawFilter '全都被过滤掉了 + Request.SearchText = RawFilter '全都被过滤掉了 Else - Task.Input.SearchText = Join(RightKeywords.Distinct.ToList, " ").ToLower + Request.SearchText = Join(RightKeywords.Distinct.ToList, " ").ToLower End If '例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod) - If RawFilter.Replace(" ", "").ContainsF("optiforge", True) Then Task.Input.SearchText = "optiforge" - If RawFilter.Replace(" ", "").ContainsF("optifabric", True) Then Task.Input.SearchText = "optifabric" - Log("[Comp] 工程列表搜索最终文本:" & Task.Input.SearchText, LogLevel.Debug) + If RawFilter.Replace(" ", "").ContainsF("optiforge", True) Then Request.SearchText = "optiforge" + If RawFilter.Replace(" ", "").ContainsF("optifabric", True) Then Request.SearchText = "optifabric" + Log("[Comp] 工程列表搜索最终文本:" & Request.SearchText, LogLevel.Debug) Task.Progress = 0.1 #End Region @@ -963,10 +1058,17 @@ Retry: Dim ModrinthThread As Thread = Nothing Dim ResultsLock As New Object + '在 1.14-,部分老 Mod 没有设置支持的加载器,因此添加 Forge 筛选就会出现遗漏 + '所以,在发起请求时不筛选加载器,然后在返回的结果中自行筛除不是 Forge 的 Mod + Dim IsOldForgeRequest = Request.ModLoader = CompModLoaderType.Forge AndAlso Request.GameVersion?.Contains(".") AndAlso Val(Request.GameVersion.Split(".")(1)) < 14 + If IsOldForgeRequest Then Request.ModLoader = CompModLoaderType.Any + Dim CurseForgeUrl As String = Request.GetCurseForgeAddress() + Dim ModrinthUrl As String = Request.GetModrinthAddress() + If IsOldForgeRequest Then Request.ModLoader = CompModLoaderType.Forge '设置回去 + Try '启动 CurseForge 线程 - Dim CurseForgeUrl As String = Task.Input.GetCurseForgeAddress() Dim CurseForgeFailed As Boolean = False If CurseForgeUrl IsNot Nothing Then CurseForgeThread = RunInNewThread( @@ -974,11 +1076,13 @@ Retry: Try '获取工程列表 Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl) - Dim RequestResult As JObject = DlModRequest(CurseForgeUrl, IsJson:=True) + Dim RequestResult As JObject = DlModRequest(CurseForgeUrl) Task.Progress += 0.2 Dim ProjectList As New List(Of CompProject) For Each JsonEntry As JObject In RequestResult("data") - ProjectList.Add(New CompProject(JsonEntry)) + Dim Project As New CompProject(JsonEntry) + If Request.Type = CompType.ResourcePack AndAlso Project.Tags.Contains("数据包") Then Continue For 'CurseForge 将一些数据包分类成了资源包 + ProjectList.Add(Project) Next '更新结果 SyncLock ResultsLock @@ -997,14 +1101,13 @@ Retry: End If '启动 Modrinth 线程 - Dim ModrinthUrl As String = Task.Input.GetModrinthAddress() Dim ModrinthFailed As Boolean = False If ModrinthUrl IsNot Nothing Then ModrinthThread = RunInNewThread( Sub() Try Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl) - Dim RequestResult As JObject = DlModRequest(ModrinthUrl, IsJson:=True) + Dim RequestResult As JObject = DlModRequest(ModrinthUrl) Task.Progress += 0.2 Dim ProjectList As New List(Of CompProject) For Each JsonEntry As JObject In RequestResult("hits") @@ -1013,7 +1116,6 @@ Retry: '更新结果 SyncLock ResultsLock For Each Project In ProjectList - If Task.Input.Type = CompType.Mod AndAlso Not Project.ModLoaders.Any() Then Continue For '过滤插件(#2458) RawResults.Add(Project) Next End SyncLock @@ -1031,9 +1133,14 @@ Retry: '等待线程结束 If CurseForgeThread IsNot Nothing Then CurseForgeThread.Join() - If Task.IsAborted Then Exit Sub '会自动触发 Finally + If Task.IsAborted Then Return '会自动触发 Finally If ModrinthThread IsNot Nothing Then ModrinthThread.Join() - If Task.IsAborted Then Exit Sub + If Task.IsAborted Then Return + + '筛除不是 Forge 的 Mod + If IsOldForgeRequest Then + RawResults = RawResults.Where(Function(p) Not p.ModLoaders.Any() OrElse p.ModLoaders.Contains(CompModLoaderType.Forge)).ToList + End If '确保存在结果 Storage.ErrorMessage = Nothing @@ -1041,11 +1148,11 @@ Retry: If [Error] IsNot Nothing Then Throw [Error] Else - If IsChineseSearch AndAlso Task.Input.Type <> CompType.Mod Then - Throw New Exception($"{If(Task.Input.Type = CompType.ModPack, "整合包", "资源包")}搜索仅支持英文") - ElseIf Task.Input.Source = CompSourceType.CurseForge AndAlso Task.Input.Tag.StartsWithF("/") Then + If IsChineseSearch AndAlso Not (Request.Type = CompType.Mod OrElse Request.Type = CompType.DataPack) Then + Throw New Exception("没有搜索结果,请尝试使用英文搜索") + ElseIf Request.Source = CompSourceType.CurseForge AndAlso Request.Tag.StartsWithF("/") Then Throw New Exception("CurseForge 不兼容所选的类型") - ElseIf Task.Input.Source = CompSourceType.Modrinth AndAlso Task.Input.Tag.EndsWithF("/") Then + ElseIf Request.Source = CompSourceType.Modrinth AndAlso Request.Tag.EndsWithF("/") Then Throw New Exception("Modrinth 不兼容所选的类型") Else Throw New Exception("没有搜索结果") @@ -1054,15 +1161,15 @@ Retry: ElseIf [Error] IsNot Nothing Then '有结果但是有错误 If CurseForgeFailed Then - Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,结果可能不全。{vbCrLf}请尝试使用 VPN 或加速器以改善网络。" + Storage.ErrorMessage = $"无法连接到 CurseForge,所以目前仅显示了来自 Modrinth 的内容,搜索结果可能不全。{vbCrLf}请稍后再试,或使用 VPN 改善网络环境。" Else - Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,结果可能不全。{vbCrLf}请尝试使用 VPN 或加速器以改善网络。" + Storage.ErrorMessage = $"无法连接到 Modrinth,所以目前仅显示了来自 CurseForge 的内容,搜索结果可能不全。{vbCrLf}请稍后再试,或使用 VPN 改善网络环境。" End If End If Finally - If CurseForgeThread IsNot Nothing Then CurseForgeThread.Interrupt() - If ModrinthThread IsNot Nothing Then ModrinthThread.Interrupt() + CurseForgeThread?.Interrupt() + ModrinthThread?.Interrupt() End Try #End Region @@ -1085,9 +1192,9 @@ Retry: #Region "检查结果数量,如果不足且可继续,会继续加载下一页" - If RealResults.Count + Storage.Results.Count < Task.Input.TargetResultCount Then - Log($"[Comp] 总结果数需求最少 {Task.Input.TargetResultCount} 个,仅获得了 {RealResults.Count + Storage.Results.Count} 个") - If Task.Input.CanContinue AndAlso [Error] Is Nothing Then '如果有下载源失败则不再重试,这时候重试可能导致无限循环 + If RealResults.Count + Storage.Results.Count < Request.TargetResultCount Then + Log($"[Comp] 总结果数需求最少 {Request.TargetResultCount} 个,仅获得了 {RealResults.Count + Storage.Results.Count} 个") + If Request.CanContinue AndAlso [Error] Is Nothing Then '如果有下载源失败则不再重试,这时候重试可能导致无限循环 Log("[Comp] 将继续尝试加载下一页") GoTo Retry Else @@ -1100,10 +1207,23 @@ Retry: #Region "将结果排序并添加" Dim Scores As New Dictionary(Of CompProject, Double) '排序分 - If String.IsNullOrEmpty(Task.Input.SearchText) Then + Dim GetDownloadCountMult = + Function(Project As CompProject) As Double + Select Case Request.Type + Case CompType.Mod, CompType.ModPack + Return If(Project.FromCurseForge, 1, 7) + Case CompType.DataPack + Return If(Project.FromCurseForge, 10, 1) + Case CompType.ResourcePack, CompType.Shader + Return If(Project.FromCurseForge, 1, 5) + Case Else + Return 1 + End Select + End Function + If String.IsNullOrEmpty(Request.SearchText) Then '如果没有搜索文本,按下载量将结果排序 For Each Result As CompProject In RealResults - Scores.Add(Result, Result.DownloadCount * If(Result.FromCurseForge, 1, 5)) + Scores.Add(Result, Result.DownloadCount * GetDownloadCountMult(Result)) Next Else '如果有搜索文本,按关联度将结果排序 @@ -1111,7 +1231,7 @@ Retry: Dim Entry As New List(Of SearchEntry(Of CompProject)) For Each Result As CompProject In RealResults Scores.Add(Result, If(Result.WikiId > 0, 0.2, 0) + - Math.Log10(Math.Max(Result.DownloadCount, 1) * If(Result.FromCurseForge, 1, 5)) / 9) + Math.Log10(Math.Max(Result.DownloadCount, 1) * GetDownloadCountMult(Result)) / 9) Entry.Add(New SearchEntry(Of CompProject) With {.Item = Result, .SearchSource = New List(Of KeyValuePair(Of String, Double)) From { New KeyValuePair(Of String, Double)(If(IsChineseSearch, Result.TranslatedName, Result.RawName), 1), New KeyValuePair(Of String, Double)(Result.Description, 0.05)}}) @@ -1194,7 +1314,7 @@ Retry: Case CompFileStatus.Beta Return If(ModeDebug, "Beta 版", "测试版") Case Else - Return If(ModeDebug, "Alpha 版", "测试版") + Return If(ModeDebug, "Alpha 版", "早期测试版") End Select End Get End Property @@ -1234,7 +1354,7 @@ Retry: ''' ''' 目标本地文件夹,或完整的文件路径。会自动判断类型。 Public Function ToNetFile(LocalAddress As String) As NetFile - Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), UseBrowserUserAgent:=True) + Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), SimulateBrowserHeaders:=True) End Function '实例化 @@ -1242,8 +1362,8 @@ Retry: ''' ''' 从文件 Json 中初始化实例。若出错会抛出异常。 ''' - Public Sub New(Data As JObject, Type As CompType) - Me.Type = Type + Public Sub New(Data As JObject, DefaultType As CompType) + Type = DefaultType If Data.ContainsKey("FromCurseForge") Then #Region "CompJson" FromCurseForge = Data("FromCurseForge").ToObject(Of Boolean) @@ -1275,13 +1395,12 @@ Retry: If Hash Is Nothing Then Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value") 'DownloadAddress Dim Url = Data("downloadUrl").ToString - If Url = "" Then Url = $"https://media.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}" - Url = Url.Replace(FileName, Net.WebUtility.UrlEncode(FileName)) '对文件名进行编码 - DownloadUrls = HandleCurseForgeDownloadUrls(Url) '对脑残 CurseForge 的下载地址进行多种修正 - DownloadUrls.AddRange(DownloadUrls.Select(Function(u) DlSourceModGet(u)).ToList) '添加镜像源,这个写法是为了让镜像源排在后面 + If Url = "" Then Url = $"https://edge.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}" + DownloadUrls = HandleCurseForgeDownloadUrls(Url.Replace(FileName, WebUtility.UrlEncode(FileName))) '对脑残 CurseForge 的下载地址进行多种修正 + DownloadUrls.Add(DlSourceModGet(Url)) '添加镜像源;注意 MCIM 源不支持 URL 编码后的文件名,必须传入 URL 编码前的文件名 DownloadUrls = DownloadUrls.Distinct.ToList '最终去重 'Dependencies - If Type = CompType.Mod Then + If Data.ContainsKey("dependencies") Then RawDependencies = Data("dependencies"). Where(Function(d) d("relationType").ToObject(Of Integer) = 3 AndAlso '种类为依赖 d("modId").ToObject(Of Integer) <> 306612 AndAlso d("modId").ToObject(Of Integer) <> 634179). '排除 Fabric API 和 Quilt API @@ -1292,7 +1411,7 @@ Retry: GameVersions = RawVersions.Where(Function(v) v.StartsWithF("1.")).Select(Function(v) v.Replace("-snapshot", " 预览版")).ToList If GameVersions.Count > 1 Then GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList - If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} + If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本 ElseIf GameVersions.Count = 1 Then GameVersions = GameVersions.ToList Else @@ -1319,8 +1438,27 @@ Retry: DownloadUrls = New List(Of String) From {File("url"), DlSourceModGet(File("url"))}.Distinct.ToList '同时添加了镜像源 Hash = File("hashes")("sha1") End If + 'ModLoaders + '结果可能混杂着 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 'Dependencies - If Type = CompType.Mod Then + If Data.ContainsKey("dependencies") Then RawDependencies = Data("dependencies"). Where(Function(d) d("dependency_type") = "required" AndAlso '种类为依赖 d("project_id") <> "P7dR8mSH" AndAlso d("project_id") <> "qvIfYCYJ" AndAlso '排除 Fabric API 和 Quilt API @@ -1333,7 +1471,7 @@ Retry: Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).ToList If GameVersions.Count > 1 Then GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList - If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} + If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本 ElseIf GameVersions.Count = 1 Then '无需处理 ElseIf RawVersions.Any(Function(v) RegexCheck(v, "[0-9]{2}w[0-9]{2}[a-z]{1}")) Then @@ -1341,13 +1479,6 @@ Retry: Else GameVersions = New List(Of String) From {"未知版本"} End If - 'ModLoaders - Dim RawLoaders As List(Of String) = Data("loaders").Select(Function(v) v.ToString).ToList - ModLoaders = New List(Of CompModLoaderType) - If RawLoaders.Contains("forge") Then ModLoaders.Add(CompModLoaderType.Forge) - If RawLoaders.Contains("neoforge") Then ModLoaders.Add(CompModLoaderType.NeoForge) - If RawLoaders.Contains("fabric") Then ModLoaders.Add(CompModLoaderType.Fabric) - If RawLoaders.Contains("quilt") Then ModLoaders.Add(CompModLoaderType.Quilt) #End Region End If End If @@ -1358,9 +1489,10 @@ Retry: ''' Public Shared Function HandleCurseForgeDownloadUrls(Url As String) As List(Of String) Return { - Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge", "://media"), + Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."), + Url.Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."), Url.Replace("-service.overwolf.wtf", ".forgecdn.net"), - Url.Replace("://edge", "://media"), + Url.Replace("://media.", "://edge."), Url }.Distinct.ToList End Function @@ -1395,12 +1527,8 @@ Retry: Dim Title As String = If(BadDisplayName, FileName, DisplayName) Dim Info As New List(Of String) If Title <> FileName.BeforeLast(".") Then Info.Add(FileName.BeforeLast(".")) - Select Case Type - Case CompType.Mod - If Dependencies.Any Then Info.Add(Dependencies.Count & " 个前置 Mod") - Case CompType.ModPack - If GameVersions.All(Function(v) v.Contains("w")) Then Info.Add($"游戏版本 {Join(GameVersions, "、")}") - End Select + If Dependencies.Any Then Info.Add(Dependencies.Count & " 项前置") + If GameVersions.All(Function(v) Not IsVersionNameLikeRelease(v)) Then Info.Add($"游戏版本 {Join(GameVersions, "、")}") If DownloadCount > 0 Then 'CurseForge 的下载次数经常错误地返回 0 Info.Add("下载 " & If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次", DownloadCount & " 次")) End If @@ -1461,50 +1589,63 @@ Retry: If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存 TargetProject = CompProjectCache(ProjectId) ElseIf FromCurseForge Then 'CurseForge - TargetProject = New CompProject(DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True)("data")) + TargetProject = New CompProject(DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId)("data")) Else 'Modrinth - TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId, IsJson:=True)) + TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId)) End If '获取工程对象的文件列表 - If Not CompFilesCache.ContainsKey(ProjectId) Then '有缓存也不能直接返回,这时候前置 Mod 可能没获取(#5173) + If Not CompFilesCache.ContainsKey(ProjectId) Then '有缓存也不能直接返回,这时候前置可能没获取(#5173) Log("[Comp] 开始获取文件列表:" & ProjectId) - Dim ResultJsonArray As JArray + Dim ResultJsonArray As JArray = Nothing If FromCurseForge Then 'CurseForge - 'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522) - ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=10000", IsJson:=True)("data") - '之前只请求一部分文件的方法备份如下: - 'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件 - ' ResultJsonArray = GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json"))("data") - 'Else '否则使用全部文件 - ' ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999", IsJson:=True)("data") - 'End If + For RetryCount As Integer = 0 To 2 + Dim ResultJson As JObject = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=" & + (10000 + RetryCount)) '每次重试多请求一个文件,以避免触发 CDN 缓存 + 'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522) + '之前只请求一部分文件的方法备份如下: + 'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件 + ' ResultJsonArray = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json")("data") + 'Else '否则使用全部文件 + ' ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999")("data") + 'End If + If ResultJson("pagination")("resultCount").ToObject(Of Integer) = ResultJson("pagination")("totalCount").ToObject(Of Integer) Then + ResultJsonArray = ResultJson("data") + Exit For + ElseIf RetryCount < 2 Then + Log($"[Comp] CurseForge 返回的文件列表存在缺失,即将进行第 {RetryCount + 1} 次重试", LogLevel.Debug) '#6224 + Log($"[Comp] 返回的原始内容如下:{vbCrLf}{ResultJson}") + Else + Log($"[Comp] CurseForge 返回的文件列表存在缺失,返回的原始内容如下:{vbCrLf}{ResultJson}") + Throw New Exception("CurseForge 返回的文件列表存在缺失") + End If + Next Else 'Modrinth - ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version", IsJson:=True) + ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version") End If CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)). - Where(Function(a) a.Available).ToList.Distinct(Function(a, b) a.Id = b.Id) 'CurseForge 可能会重复返回相同项(#1330) + Where(Function(a) a.Available).ToList. + Distinct(Function(a, b) a.Id = b.Id) 'CurseForge 可能会重复返回相同项(#1330) End If - '获取前置 Mod 列表 - If TargetProject.Type <> CompType.Mod Then Return CompFilesCache(ProjectId) + '获取前置列表 Dim Deps As List(Of String) = CompFilesCache(ProjectId).SelectMany(Function(f) f.RawDependencies).Distinct().ToList Dim UndoneDeps = Deps.Where(Function(f) Not CompProjectCache.ContainsKey(f)).ToList - '获取前置 Mod 工程信息 + '获取前置工程信息 If UndoneDeps.Any Then - Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置 Mod:{Join(UndoneDeps, ",")}") + Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置:{Join(UndoneDeps, ",")}") Dim Projects As JArray If TargetProject.FromCurseForge Then - Projects = GetJson(DlModRequest("https://api.curseforge.com/v1/mods", - "POST", "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json"))("data") + Projects = DlModRequest("https://api.curseforge.com/v1/mods", + HttpMethod.Post, "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json")("data") Else - Projects = DlModRequest($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]", IsJson:=True) + Projects = DlModRequest($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]") End If For Each Project In Projects - Dim NewProject As New CompProject(Project) '在 New 的时候就添加了缓存 + Dim Unused As New CompProject(Project) '在 New 的时候会添加缓存以便之后读取 Next End If - '更新前置 Mod 信息 + '更新前置信息 If Deps.Any Then For Each DepProject In Deps.Where(Function(id) CompProjectCache.ContainsKey(id)).Select(Function(id) CompProjectCache(id)) For Each File In CompFilesCache(ProjectId) @@ -1518,28 +1659,28 @@ Retry: End Function ''' - ''' 预载包含大量 CompFile 的卡片,添加必要的元素和前置 Mod 列表。 + ''' 预载包含大量 CompFile 的卡片,添加必要的元素和前置列表。 ''' Public Sub CompFilesCardPreload(Stack As StackPanel, Files As List(Of CompFile)) '获取卡片对应的前置 ID '如果为整合包就不会有 Dependencies 信息,所以不用管 Dim Deps As List(Of String) = Files.SelectMany(Function(f) f.Dependencies).Distinct.ToList() Deps.Sort() - If Not Deps.Any() Then Exit Sub + If Not Deps.Any() Then Return Deps = Deps.Where( Function(dep) - If Not CompProjectCache.ContainsKey(dep) Then Log($"[Comp] 未找到 ID {dep} 的前置 Mod 信息", LogLevel.Debug) + If Not CompProjectCache.ContainsKey(dep) Then Log($"[Comp] 未找到 ID {dep} 的前置信息", LogLevel.Debug) Return CompProjectCache.ContainsKey(dep) End Function).ToList '添加开头间隔 - Stack.Children.Add(New TextBlock With {.Text = "前置 Mod", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)}) - '添加前置 Mod 列表 + Stack.Children.Add(New TextBlock With {.Text = "前置资源", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 2, 0, 5)}) + '添加前置列表 For Each Dep In Deps Dim Item = CompProjectCache(Dep).ToCompItem(False, False) Stack.Children.Add(Item) Next '添加结尾间隔 - Stack.Children.Add(New TextBlock With {.Text = "可选版本", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)}) + Stack.Children.Add(New TextBlock With {.Text = "版本列表", .FontSize = 14, .HorizontalAlignment = HorizontalAlignment.Left, .Margin = New Thickness(6, 12, 0, 5)}) End Sub #End Region diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb index 267f92a..bc11865 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb @@ -2,7 +2,9 @@ '构造函数 Private TempFolder As String - Public Sub New(UUID As Integer) + Private TargetVersion As McVersion + Public Sub New(Optional TargetVersion As McVersion = Nothing) + Me.TargetVersion = TargetVersion '构建文件结构 TempFolder = RequestTaskTempFolder() Directory.CreateDirectory(TempFolder & "Temp\") @@ -71,11 +73,7 @@ '将可能可用的日志文件导出 For Each FilePath In RightLogs Try - If FilePath.Contains("crash-") Then - AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(FilePath, ReadFile(FilePath).Split(vbCrLf.ToCharArray))) - Else - AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(FilePath, ReadFile(FilePath, Encoding.UTF8).Split(vbCrLf.ToCharArray))) - End If + AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(FilePath, ReadFile(FilePath).Split(vbCrLf.ToCharArray))) Catch ex As Exception Log(ex, "读取可能的崩溃日志文件失败(" & FilePath & ")") End Try @@ -117,11 +115,7 @@ Extracted: If Not TargetFile.Exists OrElse TargetFile.Length = 0 Then Continue For Dim Ext As String = TargetFile.Extension.ToLower If Ext = ".log" OrElse Ext = ".txt" Then - If TargetFile.Name.StartsWithF("crash-") Then - AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName).Split(vbCrLf.ToCharArray))) - Else - AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName, Encoding.UTF8).Split(vbCrLf.ToCharArray))) - End If + AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName).Split(vbCrLf.ToCharArray))) Else File.Delete(TargetFile.FullName) End If @@ -137,20 +131,21 @@ Extracted: Private Enum AnalyzeFileType HsErr MinecraftLog - ExtraLog + ExtraLogFile + ExtraReportFile CrashReport End Enum Private DirectFile As KeyValuePair(Of String, String())? = Nothing '在弹窗中选择直接打开的文件 ''' ''' 从 AnalyzeRawFiles 中提取实际有用的文本片段存储到 AnalyzeFiles,并整理可用于生成报告的文件。 - ''' 返回找到的用于分析的项目数。 + ''' 返回是否有足够信息可用于分析。 ''' - Public Function Prepare() As Integer + Public Function Prepare() As Boolean Log("[Crash] 步骤 2:准备日志文本") '对日志文件进行分类 DirectFile = Nothing - Dim TotalFiles As New List(Of KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String()))) + Dim AllFiles As New List(Of KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String()))) For Each LogFile In AnalyzeRawFiles Dim MatchName As String = GetFileNameFromPath(LogFile.Key).ToLower Dim TargetType As AnalyzeFileType @@ -170,27 +165,35 @@ Extracted: TargetType = AnalyzeFileType.MinecraftLog If DirectFile Is Nothing Then DirectFile = LogFile Else - TargetType = AnalyzeFileType.ExtraLog + TargetType = AnalyzeFileType.ExtraLogFile End If - ElseIf MatchName.EndsWithF(".log", True) OrElse MatchName.EndsWithF(".txt", True) Then - TargetType = AnalyzeFileType.ExtraLog + ElseIf MatchName.EndsWithF(".log", True) Then + TargetType = AnalyzeFileType.ExtraLogFile + ElseIf MatchName.EndsWithF(".txt", True) Then + TargetType = AnalyzeFileType.ExtraReportFile Else Log("[Crash] " & MatchName & " 分类为 Ignore") Continue For End If - If Not LogFile.Value.Any() Then - Log("[Crash] " & MatchName & " 由于内容为空跳过") - Else - TotalFiles.Add(New KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String()))(TargetType, LogFile)) + If LogFile.Value.Any Then + AllFiles.Add(New KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String()))(TargetType, LogFile)) Log("[Crash] " & MatchName & " 分类为 " & GetStringFromEnum(TargetType)) + Else + Log("[Crash] " & MatchName & " 由于内容为空跳过") End If Next + '若只有额外日志,则将它们视作 Minecraft 日志 + If AllFiles.Any() AndAlso AllFiles.All(Function(p) p.Key = AnalyzeFileType.ExtraLogFile) Then + Log("[Crash] 由于仅发现了额外日志,将它们视作 Minecraft 日志进行分析") + AllFiles = AllFiles.Select(Function(p) New KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String()))(AnalyzeFileType.MinecraftLog, p.Value)).ToList + End If + '将分类后的文件分别写入 - For Each SelectType In {AnalyzeFileType.MinecraftLog, AnalyzeFileType.HsErr, AnalyzeFileType.ExtraLog, AnalyzeFileType.CrashReport} + For Each SelectType In {AnalyzeFileType.MinecraftLog, AnalyzeFileType.HsErr, AnalyzeFileType.ExtraLogFile, AnalyzeFileType.CrashReport} '获取该种类的所有文件 {文件路径 -> 文件内容行} Dim SelectedFiles As New List(Of KeyValuePair(Of String, String())) - For Each File In TotalFiles + For Each File In AllFiles If SelectType = File.Key Then SelectedFiles.Add(File.Value) Next If Not SelectedFiles.Any() Then Continue For @@ -232,54 +235,59 @@ Extracted: Next '选择一份最佳的来自启动器的游戏日志 For Each FileName As String In {"rawoutput.log", "启动器日志.txt", "log1.txt", "游戏崩溃前的输出.txt", "PCL2 启动器日志.txt", "PCL 启动器日志.txt"} - If FileNameDict.ContainsKey(FileName) Then - Dim CurrentLog = FileNameDict(FileName) - '截取 “以下为游戏输出的最后一段内容” 后的内容 - Dim HasLauncherMark As Boolean = False - For Each Line In CurrentLog.Value - If HasLauncherMark Then - LogMc += Line & vbLf - ElseIf Line.Contains("以下为游戏输出的最后一段内容") Then - HasLauncherMark = True - Log("[Crash] 找到 PCL 输出的游戏实时日志头") - End If - Next - '导入后 500 行 - If Not HasLauncherMark Then LogMc += GetHeadTailLines(CurrentLog.Value, 0, 500) - LogMc = LogMc.TrimEnd(vbCrLf.ToCharArray) - Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为启动器日志") - Exit For - End If + If Not FileNameDict.ContainsKey(FileName) Then Continue For + Dim CurrentLog = FileNameDict(FileName) + '截取 “以下为游戏输出的最后一段内容” 后的内容 + Dim HasLauncherMark As Boolean = False + For Each Line In CurrentLog.Value + If HasLauncherMark Then + LogMc += Line & vbLf + ElseIf Line.Contains("以下为游戏输出的最后一段内容") Then + HasLauncherMark = True + Log("[Crash] 找到 PCL 输出的游戏实时日志头") + End If + Next + '导入后 500 行 + If Not HasLauncherMark Then LogMc += GetHeadTailLines(CurrentLog.Value, 0, 500) + LogMc = LogMc.TrimEnd(vbCrLf.ToCharArray) + Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为启动器日志") + Exit For Next '选择一份最佳的 Minecraft Log For Each FileName As String In {"latest.log", "latest log.txt", "debug.log", "debug log.txt"} - If FileNameDict.ContainsKey(FileName) Then - Dim CurrentLog = FileNameDict(FileName) - LogMc += GetHeadTailLines(CurrentLog.Value, 250, 500) - Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft 日志") - Exit For - End If + If Not FileNameDict.ContainsKey(FileName) Then Continue For + Dim CurrentLog = FileNameDict(FileName) + LogMc += GetHeadTailLines(CurrentLog.Value, 1500, 500) + Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft 日志") + Exit For Next '查找 Debug Log For Each FileName As String In {"debug.log", "debug log.txt"} - If FileNameDict.ContainsKey(FileName) Then - Dim CurrentLog = FileNameDict(FileName) - LogMcDebug += GetHeadTailLines(CurrentLog.Value, 1000, 0) - Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft Debug 日志") - Exit For - End If + If Not FileNameDict.ContainsKey(FileName) Then Continue For + Dim CurrentLog = FileNameDict(FileName) + LogMcDebug += GetHeadTailLines(CurrentLog.Value, 1000, 0) + Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft Debug 日志") + Exit For Next - '检查错误 + '兜底 If LogMc = "" Then - LogMc = Nothing - Throw New Exception("无法找到匹配的 Minecraft Log") + If LogMcDebug <> "" Then '如果没有找到 Minecraft 日志,则使用 Debug 日志作为兜底 + LogMc = LogMcDebug + ElseIf FileNameDict.Any() Then '如果都没有找到,则使用第一个文件 + Dim CurrentLog = FileNameDict.First.Value + LogMc += GetHeadTailLines(CurrentLog.Value, 1500, 500) + Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为兜底日志") + Else + LogMc = Nothing + Throw New Exception("无法找到匹配的 Minecraft Log") + End If End If If LogMcDebug = "" Then LogMcDebug = Nothing - Case AnalyzeFileType.ExtraLog + Case AnalyzeFileType.ExtraLogFile, AnalyzeFileType.ExtraReportFile '全部丢过去 For Each SelectedFile In SelectedFiles OutputFiles.Add(SelectedFile.Key) - Log("[Crash] 输出报告:" & SelectedFile.Key & ",作为额外日志") + Log("[Crash] 输出报告:" & SelectedFile.Key & ",不用作分析") Next End Select Catch ex As Exception @@ -287,15 +295,13 @@ Extracted: End Try Next - '获取种类数 - Dim ResultCount As Integer = If(LogMc Is Nothing, 0, 1) + If(LogHs Is Nothing, 0, 1) + If(LogCrash Is Nothing, 0, 1) - If ResultCount = 0 Then - Log("[Crash] 步骤 2:准备日志文本完成,没有任何可供分析的日志") - Else + '结束 + Prepare = LogMc IsNot Nothing OrElse LogHs IsNot Nothing OrElse LogCrash IsNot Nothing + If Prepare Then Log(("[Crash] 步骤 2:准备日志文本完成,找到" & If(LogMc Is Nothing, "", "游戏日志、") & If(LogMcDebug Is Nothing, "", "游戏 Debug 日志、") & If(LogHs Is Nothing, "", "虚拟机日志、") & If(LogCrash Is Nothing, "", "崩溃日志、")).TrimEnd("、") & "用作分析") + Else + Log("[Crash] 步骤 2:准备日志文本完成,没有任何可供分析的日志") End If - Return ResultCount - End Function ''' ''' 输出字符串的前后某些行,并统一行尾为 vbLf (正则 \n)、删除空行和重复行。 @@ -335,6 +341,7 @@ Extracted: ''' 导致崩溃的原因枚举。 ''' Private Enum CrashReason + Java虚拟机参数有误 Mod文件被解压 MixinBootstrap缺失 内存不足 @@ -386,7 +393,7 @@ Extracted: ''' Public Sub Analyze(Optional Version As McVersion = Nothing) Log("[Crash] 步骤 3:分析崩溃原因") - LogAll = (If(LogMc, "") & If(LogMcDebug, "") & If(LogHs, "") & If(LogCrash, "")) + LogAll = If(LogMc, If(LogMcDebug, "")) & If(LogHs, "") & If(LogCrash, "") '1. 精准日志匹配,中/高优先级 AnalyzeCrit1() @@ -472,16 +479,18 @@ Done: '空白分析 If LogMc Is Nothing AndAlso LogHs Is Nothing AndAlso LogCrash Is Nothing Then AppendReason(CrashReason.没有可用的分析文件) - Exit Sub + Return End If '崩溃报告分析,高优先级 If LogCrash IsNot Nothing Then If LogCrash.Contains("Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass") Then AppendReason(CrashReason.Java版本过高) + If LogCrash.Contains("Failed loading config file ") Then AppendReason(CrashReason.Mod配置文件导致游戏崩溃, {TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Failed loading config file .+ for modid )[^\n]+"), "").TrimEnd(vbCrLf)).First, If(RegexSeek(LogCrash, "(?<=Failed loading config file ).+(?= of type)"), "").TrimEnd(vbCrLf)}) '#7071 End If '游戏日志分析 If LogMc IsNot Nothing Then + If LogMc.Contains("Unrecognized option:") Then AppendReason(CrashReason.Java虚拟机参数有误) If LogMc.Contains("Found multiple arguments for option fml.forgeVersion, but you asked for only one") Then AppendReason(CrashReason.版本Json中存在多个Forge) If LogMc.Contains("The driver does not appear to support OpenGL") Then AppendReason(CrashReason.显卡不支持OpenGL) If LogMc.Contains("java.lang.ClassCastException: java.base/jdk") Then AppendReason(CrashReason.使用JDK) @@ -504,13 +513,13 @@ Done: If LogMc.Contains("Couldn't set pixel format") Then AppendReason(CrashReason.显卡驱动不支持导致无法设置像素格式) If LogMc.Contains("java.lang.OutOfMemoryError") OrElse LogMc.Contains("an out of memory error") Then AppendReason(CrashReason.内存不足) If LogMc.Contains("java.lang.RuntimeException: Shaders Mod detected. Please remove it, OptiFine has built-in support for shaders.") Then AppendReason(CrashReason.ShadersMod与OptiFine同时安装) - If LogMc.Contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier") Then AppendReason(CrashReason.低版本Forge与高版本Java不兼容) + If LogMc.Contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier") OrElse LogMc.Contains("java.lang.NoSuchMethodError: 'void sun.security.util.ManifestEntryVerifier") Then AppendReason(CrashReason.低版本Forge与高版本Java不兼容) If LogMc.Contains("1282: Invalid operation") Then AppendReason(CrashReason.光影或资源包导致OpenGL1282错误) If LogMc.Contains("signer information does not match signer information of other classes in the same package") Then AppendReason(CrashReason.文件或内容校验失败, If(RegexSeek(LogMc, "(?<=class "")[^']+(?=""'s signer information)"), "").TrimEnd(vbCrLf)) If LogMc.Contains("Maybe try a lower resolution resourcepack?") Then AppendReason(CrashReason.材质过大或显卡配置不足) If LogMc.Contains("java.lang.NoSuchMethodError: net.minecraft.world.server.ChunkManager$ProxyTicketManager.shouldForceTicks(J)Z") AndAlso LogMc.Contains("OptiFine") Then AppendReason(CrashReason.OptiFine导致无法加载世界) If LogMc.Contains("Unsupported class file major version") Then AppendReason(CrashReason.Java版本不兼容) - If LogMc.Contains("com.electronwill.nightconfig.core.io.ParsingException: Not enough data available") Then AppendReason(CrashReason.NightConfig的Bug) + If LogMc.Contains("com.electronwill.nightconfig.core.io.ParsingException: Not enough data available") AndAlso Not CrashReasons.ContainsKey(CrashReason.Mod配置文件导致游戏崩溃) Then AppendReason(CrashReason.NightConfig的Bug) If LogMc.Contains("Cannot find launch target fmlclient, unable to launch") Then AppendReason(CrashReason.Forge安装不完整) If LogMc.Contains("Invalid paths argument, contained no existing paths") AndAlso LogMc.Contains("libraries\net\minecraftforge\fmlcore") Then AppendReason(CrashReason.Forge安装不完整) If LogMc.Contains("Invalid module name: '' is not a Java identifier") Then AppendReason(CrashReason.Mod名称包含特殊字符) @@ -533,8 +542,8 @@ Done: If LogMc.Contains("Found a duplicate mod") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "Found a duplicate mod[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase)) If LogMc.Contains("Found duplicate mods") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=Mod ID: ')\w+?(?=' from mod files:)").Distinct.ToList) If LogMc.Contains("ModResolutionException: Duplicate") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "ModResolutionException: Duplicate[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase)) - If LogMc.Contains("Incompatible mods found!") Then '#5006 - AppendReason(CrashReason.Mod互不兼容, If(RegexSeek(LogMc, "(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )"), "")) + If LogMc.Contains("Incompatible mods found!") Then '#5006, #5115 + AppendReason(CrashReason.Mod互不兼容, If(RegexSeek(LogMc, "(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )"), "").BeforeFirst("更多信息:").Replace("Some of your mods are incompatible with the game or each other!", "").Trim((vbCrLf & " ").ToCharArray)) End If If LogMc.Contains("Missing or unsupported mandatory dependencies:") Then AppendReason(CrashReason.Mod缺少前置或MC版本错误, @@ -614,9 +623,9 @@ Done: Dim IsMixin As Boolean = MixinAnalyze(LogMc) '常规信息 If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf)) - If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf)) - If LogMc.Contains("A potential solution has been determined, this may resolve your problem:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined, this may resolve your problem:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf)) - If LogMc.Contains("确定了一种可能的解决方法,这样做可能会解决你的问题:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=确定了一种可能的解决方法,这样做可能会解决你的问题:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf)) + If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)(\s+ - [^\n]+\n)+"), ""), "(?<=\s+)[^\n]+"), vbLf)) + If LogMc.Contains("A potential solution has been determined, this may resolve your problem:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined, this may resolve your problem:\n)(\s+ - [^\n]+\n)+"), ""), "(?<=\s+)[^\n]+"), vbLf)) + If LogMc.Contains("确定了一种可能的解决方法,这样做可能会解决你的问题:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=确定了一种可能的解决方法,这样做可能会解决你的问题:\n)(\s+ - [^\n]+\n)+"), ""), "(?<=\s+)[^\n]+"), vbLf)) If Not IsMixin AndAlso LogMc.Contains("due to errors, provided by ") Then '在 #3104 的情况下,这一句导致 OptiFabric 的 Mixin 失败错判为 Fabric Loader 加载失败 AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=due to errors, provided by ')[^']+"), "").TrimEnd((vbCrLf & " ").ToCharArray))) End If @@ -650,7 +659,7 @@ Done: End If 'Mod 解析错误(常见于 Fabric 前置校验失败) If LogMc.Contains("Mod resolution failed") Then AppendReason(CrashReason.Mod加载器报错) - 'Mixin 失败可以导致大量 Mod 实例创建失败 + 'Mixin 失败可以导致大量 Mod 实例创建失败,因此需要放到低优先级中 If LogMc.Contains("Failed to create mod instance.") Then AppendReason(CrashReason.Mod初始化失败, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModID: )[^,]+"), If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModId )[^\n]+(?= for )"), "")).TrimEnd(vbCrLf))) '注意:Fabric 的 Warnings were found! 不一定是崩溃原因,它可能是单纯的警报 End If @@ -704,11 +713,12 @@ NextStack: If Word.Length <= 2 OrElse Word.StartsWithF("func_") Then Continue For If {"com", "org", "net", "asm", "fml", "mod", "jar", "sun", "lib", "map", "gui", "dev", "nio", "api", "dsi", "top", "mcp", "core", "init", "mods", "main", "file", "game", "load", "read", "done", "util", "tile", "item", "base", "oshi", "impl", "data", "pool", "task", - "forge", "setup", "block", "model", "mixin", "event", "unimi", "netty", "world", + "forge", "setup", "block", "model", "mixin", "event", "unimi", "netty", "world", "lwjgl", "gitlab", "common", "server", "config", "mixins", "compat", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe", "render", "packet", "events", "preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content", "systems", "modules", "service", "fastutil", "optifine", "internal", "platform", "override", "fabricmc", "neoforge", - "injection", "listeners", "scheduler", "minecraft", "transformer", "transformers", "neoforged", "universal", "multipart", "minecraftforge", "blockentity", "spongepowered", "electronwill" + "injection", "listeners", "scheduler", "minecraft", "universal", "multipart", "neoforged", "microsoft", + "transformer", "transformers", "minecraftforge", "blockentity", "spongepowered", "electronwill", "concurrent" }.Contains(Word.ToLower) Then Continue For PossibleWords.Add(Word.Trim) Next @@ -851,20 +861,21 @@ NextStack: ''' ''' 弹出崩溃弹窗,并指导导出崩溃报告。 ''' - Public Sub Output(IsHandAnalyze As Boolean, Optional ExtraFiles As List(Of String) = Nothing) + Public Sub Output(IsManualAnalyze As Boolean, Optional ExtraFiles As List(Of String) = Nothing) '弹窗提示 FrmMain.ShowWindowToTop() - Select Case MyMsgBox(GetAnalyzeResult(IsHandAnalyze), If(IsHandAnalyze, "错误报告分析结果", "Minecraft 出现错误"), - "确定", If(IsHandAnalyze OrElse DirectFile Is Nothing, "", "查看日志"), If(IsHandAnalyze, "", "导出错误报告"), - Button2Action:=If(IsHandAnalyze OrElse DirectFile Is Nothing, Nothing, + Dim Detail As String = GetAnalyzeResult(IsManualAnalyze) + Select Case MyMsgBox(Detail, If(IsManualAnalyze, "错误报告分析结果", "Minecraft 出现错误"), + "确定", If(IsManualAnalyze OrElse DirectFile Is Nothing, "", "查看日志"), If(IsManualAnalyze, "", "导出错误报告"), + Button2Action:=If(IsManualAnalyze OrElse DirectFile Is Nothing, Nothing, Sub() '弹窗选择:查看日志 If File.Exists(DirectFile.Value.Key) Then - ShellOnly(DirectFile.Value.Key) + StartProcess(DirectFile.Value.Key) Else Dim FilePath As String = PathTemp & "Crash.txt" WriteFile(FilePath, Join(DirectFile.Value.Value, vbCrLf)) - ShellOnly(FilePath) + StartProcess(FilePath) End If End Sub)) Case 3 @@ -873,7 +884,7 @@ NextStack: Try '获取文件路径 RunInUiWait(Sub() FileAddress = SelectSaveFile("选择保存位置", "错误报告-" & Date.Now.ToString("G").Replace("/", "-").Replace(":", ".").Replace(" ", "_") & ".zip", "Minecraft 错误报告(*.zip)|*.zip")) - If String.IsNullOrEmpty(FileAddress) Then Exit Sub + If String.IsNullOrEmpty(FileAddress) Then Return Directory.CreateDirectory(GetPathFromFullPath(FileAddress)) If File.Exists(FileAddress) Then File.Delete(FileAddress) '输出诊断信息 @@ -896,9 +907,10 @@ NextStack: End Select If File.Exists(OutputFile) Then If FileEncoding Is Nothing Then FileEncoding = GetEncoding(ReadFileBytes(OutputFile)) - WriteFile(TempFolder & "Report\" & FileName, - SecretFilter(ReadFile(OutputFile, FileEncoding), If(FileName = "启动脚本.bat", "F", "*")), - Encoding:=FileEncoding) + Dim FileContent As String = ReadFile(OutputFile, FileEncoding) + FileContent = FilterAccessToken(FileContent, If(FileName = "启动脚本.bat", "F", "*")) + FileContent = FilterUserName(FileContent, "*") + WriteFile(TempFolder & "Report\" & FileName, FileContent, Encoding:=FileEncoding) Log($"[Crash] 导出文件:{FileName},编码:{FileEncoding.HeaderName}") End If Next @@ -908,10 +920,15 @@ NextStack: Hint("错误报告已导出!", HintType.Finish) Catch ex As Exception Log(ex, "导出错误报告失败", LogLevel.Feedback) - Exit Sub + Return End Try OpenExplorer(FileAddress) End Select + '上报 + If IsManualAnalyze Then Return + Telemetry("Minecraft 崩溃", + "Version", If(TargetVersion Is Nothing, "null", TargetVersion.Version.ToString), + "Detail", FilterUserName(Detail, "*")) End Sub ''' ''' 获取崩溃分析的结果描述。 @@ -932,12 +949,14 @@ NextStack: For Each Reason In CrashReasons Dim Additional As List(Of String) = Reason.Value Select Case Reason.Key + Case CrashReason.Java虚拟机参数有误 + Results.Add("由于 Java 参数有误,导致游戏无法继续运行。\n请检查高级选项中的 Java 虚拟机参数和游戏参数设置是否有误。") Case CrashReason.Mod文件被解压 Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。") Case CrashReason.内存不足 Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是因为电脑内存不足、游戏分配的内存不足,或是配置要求过高。\n\n你可以尝试在 更多 → 百宝箱 中选择 内存优化,然后再启动游戏。\n如果还是不行,请在启动设置中增加为游戏分配的内存,并删除配置要求较高的材质、Mod、光影。\n如果依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h") Case CrashReason.使用OpenJ9 - Results.Add("游戏因为使用 Open J9 而崩溃了。\n请在启动设置的 Java 选择一项中改用非 OpenJ9 的 Java,然后再启动游戏。") + Results.Add("游戏因为使用 OpenJ9 而崩溃了。\n请在启动设置的 Java 选择一项中改用非 OpenJ9 的 Java,然后再启动游戏。") Case CrashReason.使用JDK Results.Add("游戏似乎因为使用 JDK,或 Java 版本过高而崩溃了。\n请在启动设置的 Java 选择一项中改用 JRE 8(Java 8),然后再启动游戏。\n如果你没有安装 JRE 8,你可以从网络中下载、安装一个。") Case CrashReason.Java版本过高 @@ -1012,14 +1031,22 @@ NextStack: End If Case CrashReason.特定实体导致崩溃 If Additional.Count = 1 Then - Results.Add("游戏似乎因为实体 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并生成一个该实体,然后观察游戏的运行情况:\n - 若正常运行,则是该实体导致出错,你或许需要使用一些方式删除此实体。\n - 若仍然出错,问题就可能来自其他原因……\h") + If Additional.First.Contains("minecraft:player") Then + If Additional.First.Contains(" ") Then + Results.Add($"游戏因为位于 {Additional.First.AfterFirst(" ")} 的玩家实体导致了崩溃。\h") + Else + Results.Add($"游戏因为玩家实体导致了崩溃。\h") + End If + Else + Results.Add($"游戏因为实体 {Additional.First} 导致了崩溃。\h") + End If Else - Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n你可以创建一个新世界,并生成各种实体,观察游戏的运行情况:\n - 若正常运行,则是某些实体导致出错,你或许需要删除该世界。\n - 若仍然出错,问题就可能来自其他原因……\h") + Results.Add($"游戏因为下列实体导致了崩溃:{Additional.Join("、")}\h") End If Case CrashReason.OptiFine与Forge不兼容 Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。") Case CrashReason.ShadersMod与OptiFine同时安装 - Results.Add("无需同时安装 Optifine 和 Shaders Mod,OptiFine 已经集成了 Shaders Mod 的功能。\n在删除 Shaders Mod 后,游戏即可正常运行。") + Results.Add("无需同时安装 OptiFine 和 Shaders Mod,OptiFine 已经集成了 Shaders Mod 的功能。\n在删除 Shaders Mod 后,游戏即可正常运行。") Case CrashReason.低版本Forge与高版本Java不兼容 Results.Add("由于低版本 Forge 与当前 Java 不兼容,导致了游戏崩溃。\n\n请尝试以下解决方案:\n - 更新 Forge 到 36.2.26 或更高版本\n - 换用版本低于 1.8.0.320 的 Java") Case CrashReason.版本Json中存在多个Forge @@ -1096,7 +1123,7 @@ NextStack: If(Not Results.Any(Function(r) r.EndsWithF("\h")) OrElse IsHandAnalyze, "", vbCrLf & "如果要寻求帮助,请把错误报告文件发给对方,而不是发送这个窗口的照片或者截图。" & If(If(PageSetupSystem.IsLauncherNewest(), True), "", - vbCrLf & vbCrLf & "此外,你正在使用老版本 PCL,更新 PCL 或许也能解决这个问题。" & vbCrLf & "你可以点击 设置 → 启动器 → 检查更新 来更新 PCL。")) + vbCrLf & vbCrLf & "此外,你正在使用老版本 PCL,更新 PCL 或许也能解决这个问题。" & vbCrLf & "你可以点击 [设置 → 其他 → 启动器 → 检查更新] 更新 PCL。")) End Function End Class diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb index b7bbc6d..c2f766b 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb @@ -197,10 +197,17 @@ Public DlClientListMojangLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Mojang", AddressOf DlClientListMojangMain) Private IsNewClientVersionHinted As Boolean = False Private Sub DlClientListMojangMain(Loader As LoaderTask(Of String, DlClientListResult)) - Dim Json As JObject = NetGetCodeByRequestRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", IsJson:=True) + Dim StartTime As Long = GetTimeTick() + Dim Json As JObject = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", RequireJson:=True)) Try Dim Versions As JArray = Json("versions") If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")") + '确定官方源是否可用 + If Not DlPreferMojang Then + Dim DeltaTime = GetTimeTick() - StartTime + DlPreferMojang = DeltaTime < 4000 + Log($"[Download] Mojang 官方源加载耗时:{DeltaTime}ms,{If(DlPreferMojang, "可优先使用官方源", "不优先使用官方源")}") + End If '添加 PCL 特供项 If File.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(GetJson(ReadFile(PathTemp & "Cache\download.json"))) '返回 @@ -228,7 +235,7 @@ ''' Public DlClientListBmclapiLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain) Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of String, DlClientListResult)) - Dim Json As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json", IsJson:=True) + Dim Json As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json", RequireJson:=True)) Try Dim Versions As JArray = Json("versions") If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")") @@ -237,7 +244,7 @@ '检查是否有要求的版本(#5195) If Not String.IsNullOrEmpty(Loader.Input) Then Dim Id = Loader.Input - If Not DlClientListLoader.Output.Value("versions").Any(Function(v) v("id") = Id) Then + If DlClientListLoader.Output.Value IsNot Nothing AndAlso Not DlClientListLoader.Output.Value("versions").Any(Function(v) v("id") = Id) Then Throw New Exception("BMCLAPI 源未包含目标版本 " & Id) End If End If @@ -370,7 +377,7 @@ ''' Public DlOptiFineListOfficialLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Official", AddressOf DlOptiFineListOfficialMain) Private Sub DlOptiFineListOfficialMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult)) - Dim Result As String = NetGetCodeByClient("https://optifine.net/downloads", Encoding.Default) + Dim Result As String = NetRequestByClientRetry("https://optifine.net/downloads", Encoding:=Encoding.Default) If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") Try '获取所有版本信息 @@ -406,7 +413,7 @@ ''' Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain) Private Sub DlOptiFineListBmclapiMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult)) - Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/optifine/versionList", IsJson:=True) + Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/optifine/versionList", RequireJson:=True)) Try Dim Versions As New List(Of DlOptiFineListEntry) For Each Token As JObject In Json @@ -476,7 +483,7 @@ ''' Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain) Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult)) - Dim Result As String = NetGetCodeByRequestRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html", UseBrowserUserAgent:=True) + Dim Result As String = NetRequestByClientRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding:=Encoding.Default, Accept:="text/html", SimulateBrowserHeaders:=True) If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") '获取所有版本信息 Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)") @@ -490,7 +497,7 @@ ''' Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain) Private Sub DlForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlForgeListResult)) - Dim Result As String = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding.Default) + Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding:=Encoding.Default) If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")") '获取所有版本信息 Dim Names As List(Of String) = RegexSearch(Result, "[0-9.]+(_pre[0-9]?)?") @@ -587,7 +594,7 @@ VersionName = Version Me.Version = New Version(Version) Me.Inherit = Inherit - FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch) + FileVersion = Version & If(Branch Is Nothing, "", "-" & Branch) End Sub End Class @@ -622,11 +629,11 @@ Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry))) Dim Result As String Try - Result = NetGetCodeByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & + Result = NetRequestByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" & Loader.Input.Replace("-", "_") & '兼容 Forge 1.7.10-pre4,#4057 - ".html", UseBrowserUserAgent:=True) + ".html", SimulateBrowserHeaders:=True) Catch ex As Exception - If GetExceptionSummary(ex).Contains("(404)") Then + If ex.GetBrief().Contains("(404)") Then Throw New Exception("不可用") Else Throw @@ -693,9 +700,8 @@ ''' Forge 版本列表,BMCLAPI。 ''' Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry))) - Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" & - Loader.Input.Replace("-", "_"), '兼容 Forge 1.7.10-pre4,#4057 - IsJson:=True) + Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" & + Loader.Input.Replace("-", "_"), RequireJson:=True)) '兼容 Forge 1.7.10-pre4,#4057 Dim Versions As New List(Of DlForgeVersionEntry) Try Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input) @@ -829,8 +835,8 @@ Public DlNeoForgeListOfficialLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Official", AddressOf DlNeoForgeListOfficialMain) Private Sub DlNeoForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult)) '获取版本列表 JSON - Dim ResultLatest As String = NetGetCodeByLoader("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True) - Dim ResultLegacy As String = NetGetCodeByLoader("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True) + Dim ResultLatest As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", RequireJson:=True) + Dim ResultLegacy As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", RequireJson:=True) If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")") '解析 Try @@ -847,8 +853,8 @@ Public DlNeoForgeListBmclapiLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Bmclapi", AddressOf DlNeoForgeListBmclapiMain) Public Sub DlNeoForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult)) '获取版本列表 JSON - Dim ResultLatest As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True) - Dim ResultLegacy As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True) + Dim ResultLatest As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", RequireJson:=True) + Dim ResultLegacy As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", RequireJson:=True) If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")") '解析 Try @@ -953,7 +959,7 @@ ''' Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain) Private Sub DlLiteLoaderListOfficialMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult)) - Dim Result As JObject = NetGetCodeByRequestRetry("https://dl.liteloader.com/versions/versions.json", IsJson:=True) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://dl.liteloader.com/versions/versions.json", RequireJson:=True)) Try Dim Json As JObject = Result("versions") Dim Versions As New List(Of DlLiteLoaderListEntry) @@ -981,7 +987,7 @@ ''' Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain) Private Sub DlLiteLoaderListBmclapiMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult)) - Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json", IsJson:=True) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json", RequireJson:=True)) Try Dim Json As JObject = Result("versions") Dim Versions As New List(Of DlLiteLoaderListEntry) @@ -1052,7 +1058,7 @@ ''' Public DlFabricListOfficialLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Official", AddressOf DlFabricListOfficialMain) Private Sub DlFabricListOfficialMain(Loader As LoaderTask(Of Integer, DlFabricListResult)) - Dim Result As JObject = NetGetCodeByRequestRetry("https://meta.fabricmc.net/v2/versions", IsJson:=True) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://meta.fabricmc.net/v2/versions", RequireJson:=True)) Try Dim Output = New DlFabricListResult With {.IsOfficial = True, .SourceName = "Fabric 官方源", .Value = Result} If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项") @@ -1067,7 +1073,7 @@ ''' Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain) Private Sub DlFabricListBmclapiMain(Loader As LoaderTask(Of Integer, DlFabricListResult)) - Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions", IsJson:=True) + Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions", RequireJson:=True)) Try Dim Output = New DlFabricListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Result} If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项") @@ -1094,72 +1100,37 @@ #Region "DlMod | Mod 镜像源请求" ''' - ''' 对可能涉及 Mod 镜像源的请求进行处理,返回字符串或 JObject。 - ''' 调用 NetGetCodeByRequest,会进行重试。 - ''' - Public Function DlModRequest(Url As String, Optional IsJson As Boolean = False) As Object - Dim Urls As New List(Of KeyValuePair(Of String, Integer)) - Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - Urls.Add(New KeyValuePair(Of String, Integer)(Url, 20)) - 'Dim McimUrl As String = DlSourceModGet(Url) - 'If McimUrl <> Url Then - ' Select Case Setup.Get("ToolDownloadMod") - ' Case 0 - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Case 1 - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' Case Else - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' End Select - 'End If - Dim Exs As String = "" - For Each Source In Urls - Try - Return NetGetCodeByRequestOnce(Source.Key, Encode:=Encoding.UTF8, Timeout:=Source.Value * 1000, IsJson:=IsJson, UseBrowserUserAgent:=True) - Catch ex As Exception - Exs += ex.Message + vbCrLf - End Try - Next - Throw New Exception(Exs) - End Function - - ''' - ''' 对可能涉及 Mod 镜像源的请求进行处理。 + ''' 对可能涉及 Mod 镜像源的请求进行处理,返回 JToken。 ''' 调用 NetRequest,会进行重试。 ''' - Public Function DlModRequest(Url As String, Method As String, Data As String, ContentType As String) As String + Public Function DlModRequest(Url As String, Optional Method As HttpMethod = Nothing, + Optional Content As String = Nothing, Optional ContentType As String = Nothing) As JToken Dim Urls As New List(Of KeyValuePair(Of String, Integer)) - Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - Urls.Add(New KeyValuePair(Of String, Integer)(Url, 20)) - 'Dim McimUrl As String = DlSourceModGet(Url) - 'If McimUrl <> Url Then - ' Select Case Setup.Get("ToolDownloadMod") - ' Case 0 - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Case 1 - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' Case Else - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15)) - ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) - ' End Select - 'End If + Dim McimUrl As String = DlSourceModGet(Url) + If McimUrl <> Url Then + Select Case Setup.Get("ToolDownloadMod") + Case 0 + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30)) + Case 1 + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10)) '至少 10s,要不然有时候远端服务器来不及完成 + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30)) + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30)) + Case Else + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30)) + Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30)) + End Select + Else + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10)) + Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30)) + End If Dim Exs As String = "" For Each Source In Urls Try - Return NetRequestOnce(Source.Key, Method, Data, ContentType, Timeout:=Source.Value * 1000) + Return GetJson(NetRequestByClient(Source.Key, Method, Content, ContentType, Timeout:=Source.Value * 1000, Encoding:=Encoding.UTF8, RequireJson:=True)) Catch ex As Exception Exs += ex.Message + vbCrLf End Try @@ -1171,18 +1142,54 @@ #Region "DlSource | 镜像下载源" - Public Function DlSourceResourceGet(Original As String) As String() - Original = Original.Replace("http://resources.download.minecraft.net", "https://resources.download.minecraft.net") - Return { - Original. - Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/assets"). - Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/assets"). - Replace("https://resources.download.minecraft.net", "https://bmclapi2.bangbang93.com/assets"), - Original - } + Private DlPreferMojang As Boolean = False + ''' + ''' 下载文件(而非获取版本列表)的时候,是否优先使用官方源。 + ''' + Public ReadOnly Property DlSourcePreferMojang As Boolean + Get + Return Setup.Get("ToolDownloadSource") = 2 OrElse (Setup.Get("ToolDownloadSource") = 1 AndAlso DlPreferMojang) + End Get + End Property + ''' + ''' 下载文件(而非获取版本列表)的时候,根据是否优先使用官方源决定使用 Url 的顺序。 + ''' + Public Function DlSourceOrder(OfficialUrls As IEnumerable(Of String), MirrorUrls As IEnumerable(Of String)) As IEnumerable(Of String) + Return If(DlSourcePreferMojang, OfficialUrls.Union(MirrorUrls), MirrorUrls.Union(OfficialUrls)) + End Function + ''' + ''' 获取版本列表(而非下载文件)的时候,是否优先使用官方源。 + ''' + Public ReadOnly Property DlVersionListPreferMojang As Boolean + Get + Return Setup.Get("ToolDownloadVersion") = 2 OrElse (Setup.Get("ToolDownloadVersion") = 1 AndAlso DlPreferMojang) + End Get + End Property + ''' + ''' 获取版本列表(而非下载文件)的时候,根据是否优先使用官方源决定使用 Url 的顺序。 + ''' + Public Function DlVersionListOrder(OfficialUrls As IEnumerable(Of String), MirrorUrls As IEnumerable(Of String)) As IEnumerable(Of String) + Return If(DlVersionListPreferMojang, OfficialUrls.Union(MirrorUrls), MirrorUrls.Union(OfficialUrls)) End Function - Public Function DlSourceLibraryGet(Original As String) As String() + + ''' + ''' 下载 Assets 文件。 + ''' + Public Function DlSourceAssetsGet(Original As String) As IEnumerable(Of String) + Original = Original.Replace("http://resources.download.minecraft.net", "https://resources.download.minecraft.net") + Return DlSourceOrder( + {Original}, + {Original. + Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/assets"). + Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/assets"). + Replace("https://resources.download.minecraft.net", "https://bmclapi2.bangbang93.com/assets") + }) + End Function + ''' + ''' 下载 Libraries 文件。 + ''' + Public Function DlSourceLibraryGet(Original As String) As IEnumerable(Of String) If {"minecraftforge", "fabricmc", "neoforged"}.Any(Function(k) Original.Contains(k)) Then '不添加原版源 Return { Original. @@ -1195,44 +1202,48 @@ Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries") } Else - Return { - Original. + Return DlSourceOrder( + {Original}, + {Original. Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/maven"). Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/maven"). Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/maven"), - Original. + Original. Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com/libraries"). Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com/libraries"). - Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries"), - Original - } + Replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries") + }) End If End Function - - Public Function DlSourceModGet(Original As String) As String - Return Original - 'Return Original. - ' Replace("api.modrinth.com", "mod.mcimirror.top/modrinth"). - ' Replace("staging-api.modrinth.com", "mod.mcimirror.top/modrinth"). - ' Replace("cdn.modrinth.com", "mod.mcimirror.top"). - ' Replace("api.curseforge.com", "mod.mcimirror.top/curseforge"). - ' Replace("edge.forgecdn.net", "mod.mcimirror.top"). - ' Replace("mediafilez.forgecdn.net", "mod.mcimirror.top"). - ' Replace("media.forgecdn.net", "mod.mcimirror.top") - End Function - - Public Function DlSourceLauncherOrMetaGet(Original As String) As String() + ''' + ''' 下载 Launcher 或 Meta 文件。 + ''' 不应使用它来获取版本列表(因为它只使用文件下载源设置来决定源顺序)。 + ''' + Public Function DlSourceLauncherOrMetaGet(Original As String) As IEnumerable(Of String) If Original Is Nothing Then Throw New Exception("无对应的 json 下载地址") - Return { - Original. + Return DlSourceOrder( + {Original}, + {Original. Replace("https://piston-data.mojang.com", "https://bmclapi2.bangbang93.com"). Replace("https://piston-meta.mojang.com", "https://bmclapi2.bangbang93.com"). Replace("https://launcher.mojang.com", "https://bmclapi2.bangbang93.com"). - Replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com"), - Original - } + Replace("https://launchermeta.mojang.com", "https://bmclapi2.bangbang93.com") + }) End Function + 'Mod 下载源 + Public Function DlSourceModGet(Original As String) As String + Return Original. + Replace("api.modrinth.com", "mod.mcimirror.top/modrinth"). + Replace("staging-api.modrinth.com", "mod.mcimirror.top/modrinth"). + Replace("cdn.modrinth.com", "mod.mcimirror.top"). + Replace("api.curseforge.com", "mod.mcimirror.top/curseforge"). + Replace("edge.forgecdn.net", "mod.mcimirror.top"). + Replace("mediafilez.forgecdn.net", "mod.mcimirror.top"). + Replace("media.forgecdn.net", "mod.mcimirror.top") + End Function + + 'Loader 自动切换 Private Sub DlSourceLoader(Of InputType, OutputType)(MainLoader As LoaderTask(Of InputType, OutputType), LoaderList As List(Of KeyValuePair(Of LoaderTask(Of InputType, OutputType), Integer)), Optional IsForceRestart As Boolean = False) @@ -1251,7 +1262,7 @@ '检查加载器成功 MainLoader.Output = SubLoader.Key.Output DlSourceLoaderAbort(LoaderList) - Exit Sub + Return ElseIf BeforeLoadersAllFailed Then '此前的加载器全部失败,直接启动后续加载器 If WaitCycle < SubLoader.Value * 100 Then WaitCycle = SubLoader.Value * 100 @@ -1293,7 +1304,7 @@ '检查父加载器中断 If MainLoader.IsAborted Then DlSourceLoaderAbort(LoaderList) - Exit Sub + Return End If Loop End Sub diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb index 05d2c98..d84592b 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb @@ -10,7 +10,7 @@ '路径 ''' - ''' Java.exe 文件的完整路径。 + ''' java.exe 文件的完整路径。 ''' Public ReadOnly Property PathJava As String Get @@ -18,15 +18,7 @@ End Get End Property ''' - ''' Javaw.exe 文件的完整路径。 - ''' - Public ReadOnly Property PathJavaw As String - Get - Return PathFolder & "javaw.exe" - End Get - End Property - ''' - ''' Javaw.exe 文件所在文件夹的路径,以 \ 结尾。 + ''' java.exe 文件所在文件夹的路径,以 \ 结尾。 ''' Public PathFolder As String ''' @@ -98,22 +90,19 @@ ''' 检查并获取 Java 详细信息。在 Java 存在异常时抛出错误。 ''' Public Sub Check() - If IsChecked Then Exit Sub + If IsChecked Then Return Dim Output As String = Nothing Try '确定文件存在 - If Not File.Exists(PathJavaw) Then - Throw New FileNotFoundException("未找到 javaw.exe 文件", PathJavaw) - End If - If Not File.Exists(PathFolder & "java.exe") Then - Throw New FileNotFoundException("未找到 java.exe 文件", PathFolder & "java.exe") - End If + If Not File.Exists(PathJava) Then Throw New FileNotFoundException("未找到 java.exe 文件", PathJava) + If File.Exists(PathFolder & "pdf-bookmark") Then Throw New Exception("不兼容 PDF Bookmark 的 Java") '#5326 IsJre = Not File.Exists(PathFolder & "javac.exe") '运行 -version - Output = ShellAndGetOutput(PathFolder & "java.exe", "-version", 15000).ToLower + Output = StartProcessAndGetOutput(PathJava, "-version", 15000).ToLower If Output = "" Then Throw New ApplicationException("尝试运行该 Java 失败") - If ModeDebug Then Log("[Java] Java 检查输出:" & PathFolder & "java.exe" & vbCrLf & Output) + If ModeDebug Then Log("[Java] Java 检查输出:" & PathJava & vbCrLf & Output) If Output.Contains("/lib/ext exists") Then Throw New ApplicationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试") + If Output.Contains("a fatal error") Then Throw New ApplicationException("无法运行该 Java,该 Java 或系统存在问题") '获取详细信息 Dim VersionString = If(RegexSeek(Output, "(?<=version "")[^""]+"), If(RegexSeek(Output, "(?<=openjdk )[0-9]+"), "")).Replace("_", ".").Split("-").First If VersionString.Split(".").Count > 4 Then VersionString = VersionString.Replace(".0.", ".") '#3493,VersionString = "21.0.2.0.2" @@ -132,17 +121,17 @@ End If Is64Bit = Output.Contains("64-bit") If Version.Minor <= 4 OrElse Version.Minor >= 100 Then Throw New ApplicationException("分析详细信息失败,获取的版本为 " & Version.ToString) - '基于 #3649,在 64 位系统上禁用 32 位 Java + '#3649:在 64 位系统上禁用 32 位 Java If Not Is64Bit AndAlso Not Is32BitSystem Then Throw New Exception("该 Java 为 32 位版本,请安装 64 位的 Java") - '基于 #2249 发现 JRE 17 似乎也导致了 Forge 安装失败,干脆禁用更多版本的 JRE + '#2249:JRE 17 似乎会导致 Forge 安装失败,干脆禁用更多版本的 JRE If IsJre AndAlso VersionCode >= 16 Then Throw New Exception("由于高版本 JRE 对游戏的兼容性很差,因此不再允许使用。你可以使用对应版本的 JDK,而非 JRE!") Catch ex As ApplicationException Throw ex Catch ex As ThreadInterruptedException Throw ex Catch ex As Exception - Log("[Java] 检查失败的 Java 输出:" & PathFolder & "java.exe" & vbCrLf & If(Output, "无程序输出")) - Throw New Exception("检查 Java 失败(" & If(PathJavaw, "Nothing") & ")", ex) + Log("[Java] 检查失败的 Java 输出:" & If(PathJava, "Nothing") & vbCrLf & If(Output, "无程序输出")) + Throw New Exception("检查 Java 失败(" & If(PathJava, "Nothing") & ")", ex) End Try IsChecked = True End Sub @@ -372,6 +361,7 @@ ExitUserJavaCheck: For Each Java In AllowedJavaList '如果在官启文件夹启动,会将官启自带 Java 错误视作 MC 文件夹指定 Java,导致了 #2054 的第二例 If Java.PathFolder.Contains(".minecraft\cache\java") Then Continue For + If Java.PathFolder.Contains("PCL\MyDownload\") Then Continue For '#5780 If TargetJavaList.Contains(Java) Then '直接使用指定的 Java AllowedJavaList = New List(Of JavaEntry) From {Java} @@ -455,10 +445,12 @@ NoUserJava: ''' 将 Java 按照适用性排序。 ''' Public Function JavaSorter(Left As JavaEntry, Right As JavaEntry) As Boolean + Dim PathInfo As New DirectoryInfo(Path) + Dim PathMcInfo As New DirectoryInfo(PathMcFolder) '1. 尽量在当前文件夹或当前 Minecraft 文件夹 Dim ProgramPathParent As String, MinecraftPathParent As String = "" - ProgramPathParent = If(New DirectoryInfo(Path).Parent, New DirectoryInfo(Path)).FullName - If PathMcFolder <> "" Then MinecraftPathParent = If(New DirectoryInfo(PathMcFolder).Parent, New DirectoryInfo(PathMcFolder)).FullName + ProgramPathParent = If(PathInfo.Parent, PathInfo).FullName + If PathMcFolder <> "" Then MinecraftPathParent = If(PathMcInfo.Parent, PathMcInfo).FullName If Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Not Right.PathFolder.StartsWithF(ProgramPathParent) Then Return True If Not Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Right.PathFolder.StartsWithF(ProgramPathParent) Then Return False If PathMcFolder <> "" Then @@ -525,6 +517,8 @@ NoUserJava: If Disk.DriveType = DriveType.Network Then Continue For '跳过网络驱动器(#3705) JavaSearchFolder(Disk.Name, JavaPreList, False) Next + '查找 .jdks 文件夹中的 Java + JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\.jdks\", JavaPreList, False, True) '查找 APPDATA 文件夹中的 Java JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\", JavaPreList, False) JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) & "\", JavaPreList, False) @@ -555,10 +549,9 @@ NoUserJava: '若不全为特殊引用,则清除特殊引用的地址 Dim JavaWithoutInherit As New Dictionary(Of String, Boolean) For Each Pair In JavaPreList - If Pair.Key.Contains("java8path_target_") OrElse Pair.Key.Contains("javapath_target_") OrElse Pair.Key.Contains("javatmp") Then - Log("[Java] 位于 " & Pair.Key & " 的 Java 包含特殊引用") + If Pair.Key.Contains("java8path_target_") OrElse Pair.Key.Contains("javapath_target_") OrElse Pair.Key.Contains("javatmp") OrElse Pair.Key.ContainsF("system32") Then + Log("[Java] 位于 " & Pair.Key & " 的 Java 位于特殊路径,不应优先使用") Else - Log("[Java] 位于 " & Pair.Key & " 的 Java 不含特殊引用") JavaWithoutInherit.Add(Pair.Key, Pair.Value) End If Next @@ -653,7 +646,7 @@ Wait: Private Sub JavaSearchFolder(OriginalPath As String, ByRef Results As Dictionary(Of String, Boolean), Source As Boolean, Optional IsFullSearch As Boolean = False) Try Log("[Java] 开始" & If(IsFullSearch, "完全", "部分") & "遍历查找:" & OriginalPath) - JavaSearchFolder(New DirectoryInfo(ShortenPath(OriginalPath)), Results, Source, IsFullSearch) + JavaSearchFolder(New DirectoryInfo(OriginalPath), Results, Source, IsFullSearch) Catch ex As UnauthorizedAccessException Log("[Java] 遍历查找 Java 时遭遇无权限的文件夹:" & OriginalPath) Catch ex As Exception @@ -668,20 +661,20 @@ Wait: Private Sub JavaSearchFolder(OriginalPath As DirectoryInfo, ByRef Results As Dictionary(Of String, Boolean), Source As Boolean, Optional IsFullSearch As Boolean = False) Try '确认目录存在 - If Not OriginalPath.Exists Then Exit Sub + If Not OriginalPath.Exists Then Return Dim Path As String = OriginalPath.FullName.Replace("\\", "\") If Not Path.EndsWithF("\") Then Path += "\" '若该目录有 Java,则加入结果 If File.Exists(Path & "javaw.exe") Then Results(Path) = Source '查找其下的所有文件夹 - '不应使用网易的 Java:https://github.com/Hex-Dragon/PCL2/issues/1279#issuecomment-2761489121 - Dim Keywords = {"java", "jdk", "env", "环境", "run", "软件", "jre", "mc", "dragon", + '不应使用网易的 Java:https://github.com/Meloong-Git/PCL/issues/1279#issuecomment-2761489121 + Dim Keywords = {"java", "jdk", "env", "环境", "run", "软件", "jre", "mc", "dragon", "bin", "soft", "cache", "temp", "corretto", "roaming", "users", "craft", "program", "世界", "net", "游戏", "oracle", "game", "file", "data", "jvm", "服务", "server", "客户", "client", "整合", "应用", "运行", "前置", "mojang", "官启", "新建文件夹", "eclipse", "microsoft", "hotspot", "runtime", "x86", "x64", "forge", "原版", "optifine", "官方", "启动", "hmcl", "mod", "高清", "download", "launch", "程序", "path", "version", "baka", "pcl", "zulu", "local", "packages", - "4297127d64ec6", "1.", "启动"} + "4297127d64ec6", "1.", "启动", "jbr"} For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接 Dim SearchEntry = GetFolderNameFromPath(FolderInfo.Name).ToLower '用于搜索的字符串 @@ -712,8 +705,8 @@ Wait: Return False Else Return MyMsgBox($"PCL 未找到 {VersionDescription},是否需要 PCL 自动下载?" & vbCrLf & - $"如果你已经安装了 {VersionDescription},请在 设置 → 启动选项 → 游戏 Java 中手动导入。", - "未找到 Java", "自动下载", "取消") = 1 + $"如果你已经安装了 {VersionDescription},可以在 设置 → 启动选项 → 游戏 Java 中手动导入。", + "自动下载 Java?", "自动下载", "取消") = 1 End If End Function @@ -742,10 +735,10 @@ Wait: Private LastJavaBaseDir As String = Nothing '用于在下载中断或失败时删除未完成下载的 Java 文件夹,防止残留只下了一半但 -version 能跑的 Java Private Sub JavaFileList(Loader As LoaderTask(Of Integer, List(Of NetFile))) Log("[Java] 开始获取 Java 下载信息") - Dim IndexFileStr As String = NetGetCodeByLoader( - {"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json", - "https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"}, - IsJson:=True) + Dim IndexFileStr As String = NetRequestByLoader(DlVersionListOrder( + {"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"}, + {"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"} + ), IsJson:=True) '获取下载地址 Dim MainEntry As JObject = CType(GetJson(IndexFileStr), JObject)($"windows-x{If(Is32BitSystem, "86", "64")}") Dim Entries = MainEntry.Children.Reverse. '选择最靠后的一项(最新) @@ -754,7 +747,7 @@ Wait: Dim Address As String = TargetEntry.Value("manifest")("url") Log($"[Java] 准备下载 Java {TargetEntry.Value("version")("name")}({TargetEntry.Key}):{Address}") '获取文件列表 - Dim ListFileStr As String = NetGetCodeByLoader({Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com"), Address}, IsJson:=True) + Dim ListFileStr As String = NetRequestByLoader(DlSourceOrder({Address}, {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com")}), IsJson:=True) LastJavaBaseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\runtime\" & TargetEntry.Key & "\" Dim Results As New List(Of NetFile) For Each File As JProperty In CType(GetJson(ListFileStr), JObject)("files") @@ -767,7 +760,7 @@ Wait: End If If Checker.Check(LastJavaBaseDir & File.Name) Is Nothing Then Continue For '跳过已存在的文件 Dim Url As String = Info("url") - Results.Add(New NetFile({Url.Replace("piston-data.mojang.com", "bmclapi2.bangbang93.com"), Url}, LastJavaBaseDir & File.Name, Checker)) + Results.Add(New NetFile(DlSourceOrder({Url}, {Url.Replace("piston-data.mojang.com", "bmclapi2.bangbang93.com")}), LastJavaBaseDir & File.Name, Checker)) Next Loader.Output = Results Log($"[Java] 需要下载 {Results.Count} 个文件,目标文件夹:{LastJavaBaseDir}") diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb index e9988bd..ccaddd1 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb @@ -65,8 +65,8 @@ Public Module ModLaunch ''' 记录启动日志。 ''' Public Sub McLaunchLog(Text As String) - Text = SecretFilter(Text, "*") - RunInUi(Sub() FrmLaunchRight.LabLog.Text += vbCrLf & "[" & GetTimeNow() & "] " & Text) + Text = FilterUserName(FilterAccessToken(Text, "*"), "*") + RunInUi(Sub() FrmLaunchRight.LabLog.Text += $"{vbCrLf}[{GetTimeNow()}] {Text}") Log("[Launch] " & Text) End Sub @@ -101,6 +101,8 @@ Public Module ModLaunch If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Critical) Throw End Try + Dim IsSavingBatch As Boolean = CurrentLaunchOptions?.SaveBatch IsNot Nothing + '正式加载 Try '构造主加载器 @@ -145,9 +147,15 @@ Public Module ModLaunch Select Case LaunchLoader.State Case LoadState.Finished Hint(McVersionCurrent.Name & " 启动成功!", HintType.Finish) + '上报 + If Not IsSavingBatch Then + Telemetry("Minecraft 启动成功", + "Version", McVersionCurrent.Version.ToString, + "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType))) + End If Case LoadState.Aborted If AbortHint Is Nothing Then - Hint(If(CurrentLaunchOptions?.SaveBatch Is Nothing, "已取消启动!", "已取消导出启动脚本!"), HintType.Info) + Hint(If(IsSavingBatch, "已取消导出启动脚本!", "已取消启动!"), HintType.Info) Else Hint(AbortHint, HintType.Finish) End If @@ -162,7 +170,7 @@ NextInner: If CurrentEx.Message.StartsWithF("$") Then '若有以 $ 开头的错误信息,则以此为准显示提示 '若错误信息为 $$,则不提示 - If Not CurrentEx.Message = "$$" Then MyMsgBox(CurrentEx.Message.TrimStart("$"), If(CurrentLaunchOptions?.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败")) + If Not CurrentEx.Message = "$$" Then MyMsgBox(CurrentEx.Message.TrimStart("$"), If(IsSavingBatch, "导出启动脚本失败", "启动失败")) Throw ElseIf CurrentEx.InnerException IsNot Nothing Then '检查下一级错误 @@ -170,10 +178,17 @@ NextInner: GoTo NextInner Else '没有特殊处理过的错误信息 - McLaunchLog("错误:" & GetExceptionDetail(ex)) + McLaunchLog("错误:" & ex.GetDetail()) Log(ex, - If(CurrentLaunchOptions?.SaveBatch Is Nothing, "Minecraft 启动失败", "导出启动脚本失败"), LogLevel.Msgbox, - If(CurrentLaunchOptions?.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败")) + If(IsSavingBatch, "导出启动脚本失败", "Minecraft 启动失败"), LogLevel.Msgbox, + If(IsSavingBatch, "导出启动脚本失败", "启动失败")) + '上报 + If Not IsSavingBatch Then + Telemetry("Minecraft 启动失败", + "Version", McVersionCurrent.Version.ToString, + "LoginType", GetStringFromEnum(CType(Setup.Get("LoginType"), McLoginType)), + "Exception", FilterUserName(FilterAccessToken(ex.GetDetail(), "*"), "*")) + End If Throw End If End Try @@ -578,11 +593,7 @@ SkipLogin: Private Sub McLoginServerStart(Data As LoaderTask(Of McLoginServer, McLoginResult)) Dim Input As McLoginServer = Data.Input Dim NeedRefresh As Boolean = False, WasRefreshed As Boolean = False - Dim LogUsername As String = Input.UserName - If LogUsername.Contains("@") AndAlso Setup.Get("UiLauncherEmail") Then - LogUsername = AccountFilter(LogUsername) - End If - McLaunchLog("登录方式:" & Input.Description & "(" & LogUsername & ")") + McLaunchLog("登录方式:" & Input.Description) Data.Progress = 0.05 '尝试登录 If (Not Data.Input.ForceReselectProfile) AndAlso @@ -598,12 +609,8 @@ SkipLogin: McLoginRequestValidate(Data) GoTo LoginFinish Catch ex As Exception - Dim AllMessage = GetExceptionDetail(ex) - McLaunchLog("验证登录失败:" & AllMessage) - If (AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout")) AndAlso Not AllMessage.Contains("403") Then - McLaunchLog("已触发超时登录失败") - Throw New Exception("$登录失败:连接登录服务器超时。" & vbCrLf & "请检查你的网络状况是否良好,或尝试使用 VPN!") - End If + Log(ex, "验证登录失败") + If IsNetworkRelated(ex) Then Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后再试,或使用加速器、VPN 改善网络环境。") End Try Data.Progress = 0.25 '尝试刷新登录 @@ -613,7 +620,7 @@ Refresh: McLoginRequestRefresh(Data, NeedRefresh) GoTo LoginFinish Catch ex As Exception - McLaunchLog("刷新登录失败:" & GetExceptionDetail(ex)) + McLaunchLog("刷新登录失败:" & ex.GetDetail()) If WasRefreshed Then Throw New Exception("二轮刷新登录失败", ex) End Try Data.Progress = If(NeedRefresh, 0.85, 0.45) @@ -623,7 +630,7 @@ Refresh: If Data.IsAborted Then Throw New ThreadInterruptedException NeedRefresh = McLoginRequestLogin(Data) Catch ex As Exception - McLaunchLog("登录失败:" & GetExceptionDetail(ex)) + McLaunchLog("登录失败:" & ex.GetDetail()) Throw End Try If NeedRefresh Then @@ -688,11 +695,10 @@ LoginFinish: '发送登录请求 Dim RequestData As New JObject( New JProperty("accessToken", AccessToken), New JProperty("clientToken", ClientToken), New JProperty("requestUser", True)) - NetRequestRetry( - Url:=Data.Input.BaseUrl & "/validate", - Method:="POST", - Data:=RequestData.ToString(0), - Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}}, + NetRequestByClientRetry( + Data.Input.BaseUrl & "/validate", HttpMethod.Post, + Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None), + Headers:={{"Accept-Language", "zh-CN"}}, ContentType:="application/json; charset=utf-8") '没有返回值的 '将登录结果输出 Data.Output.AccessToken = AccessToken @@ -705,10 +711,9 @@ LoginFinish: End Sub Private Sub McLoginRequestRefresh(ByRef Data As LoaderTask(Of McLoginServer, McLoginResult), RequestUser As Boolean) McLaunchLog("刷新登录开始(Refresh, " & Data.Input.Token & ")") - Dim LoginJson As JObject = GetJson(NetRequestRetry( - Url:=Data.Input.BaseUrl & "/refresh", - Method:="POST", - Data:=New JObject( + Dim LoginJson As JObject = GetJson(NetRequestByClientRetry( + Data.Input.BaseUrl & "/refresh", HttpMethod.Post, + Content:=New JObject( New JProperty("selectedProfile", New JObject( New JProperty("name", Setup.Get($"Cache{Data.Input.Token}Name")), New JProperty("id", Setup.Get($"Cache{Data.Input.Token}Uuid")) @@ -716,8 +721,8 @@ LoginFinish: New JProperty("accessToken", Setup.Get($"Cache{Data.Input.Token}Access")), New JProperty("requestUser", True) ).ToString(Newtonsoft.Json.Formatting.None), - Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}}, - ContentType:="application/json; charset=utf-8")) + Headers:={{"Accept-Language", "zh-CN"}}, + ContentType:="application/json; charset=utf-8", RequireJson:=True)) '将登录结果输出 If LoginJson("selectedProfile") Is Nothing Then Throw New Exception("选择的角色 " & Setup.Get("Cache" & Data.Input.Token & "Name") & " 无效!") Data.Output.AccessToken = LoginJson("accessToken").ToString @@ -743,12 +748,11 @@ LoginFinish: New JProperty("username", Data.Input.UserName), New JProperty("password", Data.Input.Password), New JProperty("requestUser", True)) - Dim LoginJson As JObject = GetJson(NetRequestRetry( - Url:=Data.Input.BaseUrl & "/authenticate", - Method:="POST", - Data:=RequestData.ToString(0), - Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}}, - ContentType:="application/json; charset=utf-8")) + Dim LoginJson As JObject = GetJson(NetRequestByClientRetry( + Data.Input.BaseUrl & "/authenticate", HttpMethod.Post, + Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None), + Headers:={{"Accept-Language", "zh-CN"}}, + ContentType:="application/json; charset=utf-8", RequireJson:=True)) '检查登录结果 If LoginJson("availableProfiles").Count = 0 Then If Data.Input.ForceReselectProfile Then Hint("你还没有创建角色,无法更换!", HintType.Critical) @@ -806,13 +810,14 @@ LoginFinish: McLaunchLog("登录成功(Login, " & Data.Input.Token & ")") Return NeedRefresh Catch ex As Exception - Dim AllMessage As String = GetExceptionSummary(ex) + Dim AllMessage As String = ex.GetBrief() Log(ex, "登录失败原始错误信息", LogLevel.Normal) '读取服务器返回的错误 If TypeOf ex Is ResponsedWebException Then Dim ErrorMessage As String = Nothing Try - ErrorMessage = GetJson(DirectCast(ex, ResponsedWebException).Response)("errorMessage") + Dim Response = DirectCast(ex, ResponsedWebException).Response + If ErrorMessage.ContainsF("errorMessage", True) Then ErrorMessage = GetJson(Response)("errorMessage") Catch End Try If Not String.IsNullOrWhiteSpace(ErrorMessage) Then @@ -830,7 +835,7 @@ LoginFinish: End If End If '通用关键字检测 - If AllMessage.Contains("403") Then + If AllMessage.Contains("(403)") Then Select Case Data.Input.Type Case McLoginType.Auth Throw New Exception("$登录失败,以下为可能的原因:" & vbCrLf & @@ -844,8 +849,8 @@ LoginFinish: If(Data.Input.UserName.Contains("@"), "", " - 登录账号应为邮箱或统一通行证账号,而非游戏角色 ID。" & vbCrLf) & " - 只注册了账号,但没有加入对应服务器。") End Select - ElseIf AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout") OrElse AllMessage.Contains("网络请求失败") Then - Throw New Exception("$登录失败:连接登录服务器超时。" & vbCrLf & "请检查你的网络状况是否良好,或尝试使用 VPN!") + ElseIf ex.IsNetworkRelated() Then + Throw New Exception("$登录失败:你的网络环境不佳,导致难以连接到海外服务器。" & vbCrLf & "请稍后再试,或使用加速器、VPN 改善网络环境。") ElseIf ex.Message.StartsWithF("$") Then Throw Else @@ -862,8 +867,9 @@ LoginFinish: '初始请求 Retry: McLaunchLog("开始微软登录步骤 1/6(原始登录)") - Dim PrepareJson As JObject = GetJson(NetRequestRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", "POST", - $"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access", "application/x-www-form-urlencoded")) + Dim PrepareJson As JObject = GetJson(NetRequestByClientRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", HttpMethod.Post, + Content:=$"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access", + ContentType:="application/x-www-form-urlencoded", RequireJson:=True)) McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString) '弹窗 @@ -892,13 +898,23 @@ Retry: Dim Result As String Try - Result = NetRequestMultiple("https://login.live.com/oauth20_token.srf", "POST", - $"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access", - "application/x-www-form-urlencoded", 2) - Catch ex As Exception - If ex.Message.ContainsF("must sign in again", True) OrElse ex.Message.ContainsF("password expired", True) OrElse - (ex.Message.Contains("refresh_token") AndAlso ex.Message.Contains("is not valid")) Then '#269 + Result = NetRequestByClientMultiple("https://login.live.com/oauth20_token.srf", HttpMethod.Post, + Content:=$"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access", + ContentType:="application/x-www-form-urlencoded", + Headers:={{"Accept-Language", "en-US,en;q=0.5"}, {"X-Requested-With", "XMLHttpRequest"}}, + ThreadCount:=2) + Catch ex As ResponsedWebException + '修改错误列表时,同时检查 MyMsgLogin.xaml.vb 中的对应代码 + Dim Response = ex.Response + If Response.ContainsF("must sign in again", True) OrElse Response.ContainsF("password expired", True) OrElse + (Response.Contains("refresh_token") AndAlso Response.Contains("is not valid")) Then '#269 Return {"Relogin", ""} + ElseIf Response.Contains("Account security interrupt") Then + MyMsgBox("该账号由于安全问题无法登陆,请前往微软账户页获取更多信息。", "登录失败", "我知道了", IsWarn:=True) + Throw New Exception("$$") + ElseIf Response.Contains("service abuse") Then + MyMsgBox("非常抱歉,该账号已被微软封禁,无法登录。", "登录失败", "我知道了", IsWarn:=True) + Throw New Exception("$$") Else Throw End If @@ -922,8 +938,8 @@ Retry: ""RelyingParty"": ""http://auth.xboxlive.com"", ""TokenType"": ""JWT"" }" - Dim Result As String = NetRequestMultiple("https://user.auth.xboxlive.com/user/authenticate", "POST", Request, "application/json", 3) - + Dim Result As String = NetRequestByClientMultiple("https://user.auth.xboxlive.com/user/authenticate", HttpMethod.Post, + Content:=Request, ContentType:="application/json", RequireJson:=True) Dim ResultJson As JObject = GetJson(Result) Dim XBLToken As String = ResultJson("Token").ToString Return XBLToken @@ -944,21 +960,22 @@ Retry: }" Dim Result As String Try - Result = NetRequestMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", "POST", Request, "application/json", 3) - Catch ex As Net.WebException + Result = NetRequestByClientMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", HttpMethod.Post, + Content:=Request, ContentType:="application/json", RequireJson:=True) + Catch ex As ResponsedWebException '参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js - If ex.Message.Contains("2148916227") Then + If ex.Response.Contains("2148916227") Then MyMsgBox("该账号似乎已被微软封禁,无法登录。", "登录失败", "我知道了", IsWarn:=True) Throw New Exception("$$") - ElseIf ex.Message.Contains("2148916233") Then + ElseIf ex.Response.Contains("2148916233") Then If MyMsgBox("你尚未注册 Xbox 账户,请在注册后再登录。", "登录提示", "注册", "取消") = 1 Then OpenWebsite("https://signup.live.com/signup") End If Throw New Exception("$$") - ElseIf ex.Message.Contains("2148916235") Then - MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请尝试使用加速器或 VPN。", "登录失败", "我知道了") + ElseIf ex.Response.Contains("2148916235") Then + MyMsgBox($"你的网络所在的国家或地区无法登录微软账号。{vbCrLf}请使用加速器或 VPN,然后再试。", "登录失败", "我知道了") Throw New Exception("$$") - ElseIf ex.Message.Contains("2148916238") Then + ElseIf ex.Response.Contains("2148916238") Then If MyMsgBox("该账号年龄不足,你需要先修改出生日期,然后才能登录。" & vbCrLf & "该账号目前填写的年龄是否在 13 岁以上?", "登录提示", "13 岁以上", "12 岁以下", "我不知道") = 1 Then OpenWebsite("https://account.live.com/editprof.aspx") @@ -976,9 +993,10 @@ Retry: End Try Dim ResultJson As JObject = GetJson(Result) - Dim XSTSToken As String = ResultJson("Token").ToString - Dim UHS As String = ResultJson("DisplayClaims")("xui")(0)("uhs").ToString - Return {XSTSToken, UHS} + Return { + ResultJson("Token"), 'XSTSToken + ResultJson("DisplayClaims")("xui")(0)("uhs") 'UHS + } End Function '微软登录步骤 4:从 {XSTSToken, UHS} 获取 Minecraft AccessToken Private Function MsLoginStep4(Tokens As String()) As String @@ -987,29 +1005,29 @@ Retry: Dim Request As String = New JObject(New JProperty("identityToken", $"XBL3.0 x={Tokens(1)};{Tokens(0)}")).ToString(0) Dim Result As String Try - Result = NetRequestRetry("https://api.minecraftservices.com/authentication/login_with_xbox", "POST", Request, "application/json") + Result = NetRequestByClientRetry("https://api.minecraftservices.com/authentication/login_with_xbox", HttpMethod.Post, + Content:=Request, ContentType:="application/json", RequireJson:=True) Catch ex As Net.WebException - Dim Message As String = GetExceptionSummary(ex) + Dim Message As String = ex.GetBrief() If Message.Contains("(429)") Then - Log(ex, "微软登录第 5 步汇报 429") + Log(ex, "微软登录第 4 步汇报 429") Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!") ElseIf Message.Contains("(403)") Then - Log(ex, "微软登录第 5 步汇报 403") + Log(ex, "微软登录第 4 步汇报 403") Throw New Exception("$当前 IP 的登录尝试异常。" & vbCrLf & "如果你使用了 VPN 或加速器,请把它们关掉或更换节点后再试!") Else Throw End If End Try - Dim ResultJson As JObject = GetJson(Result) - Dim AccessToken As String = ResultJson("access_token").ToString - Return AccessToken + Return GetJson(Result)("access_token").ToString End Function '微软登录步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP Private Sub MsLoginStep5(AccessToken As String) McLaunchLog("开始微软登录步骤 5/6") - Dim Result As String = NetRequestMultiple("https://api.minecraftservices.com/entitlements/mcstore", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}}) + Dim Result As String = NetRequestByClientMultiple("https://api.minecraftservices.com/entitlements/mcstore", + ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}, RequireJson:=True) Try Dim ResultJson As JObject = GetJson(Result) If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any) Then @@ -1020,7 +1038,7 @@ Retry: Throw New Exception("$$") End If Catch ex As Exception - Log(ex, "微软登录第 6 步异常:" & Result) + Log(ex, "微软登录第 5 步异常:" & Result) Throw End Try End Sub @@ -1030,14 +1048,15 @@ Retry: Dim Result As String Try - Result = NetRequestMultiple("https://api.minecraftservices.com/minecraft/profile", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}}) - Catch ex As Net.WebException - Dim Message As String = GetExceptionSummary(ex) + Result = NetRequestByClientMultiple("https://api.minecraftservices.com/minecraft/profile", + ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}}, RequireJson:=True) + Catch ex As WebException + Dim Message As String = ex.GetBrief() If Message.Contains("(429)") Then - Log(ex, "微软登录第 7 步汇报 429") + Log(ex, "微软登录第 6 步汇报 429") Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!") ElseIf Message.Contains("(404)") Then - Log(ex, "微软登录第 7 步汇报 404") + Log(ex, "微软登录第 6 步汇报 404") RunInNewThread( Sub() Select Case MyMsgBox("请先创建 Minecraft 玩家档案,然后再重新登录。", "登录失败", "创建档案", "取消") @@ -1084,8 +1103,7 @@ Retry: Uuid = McLoginMojangUuid(SkinName, False) End If Catch ex As Exception - Log(ex, "离线启动时使用的正版皮肤获取失败") - MyMsgBox("由于设置的离线启动时使用的正版皮肤获取失败,游戏将以无皮肤的方式启动。" & vbCrLf & "请检查你的网络是否通畅,或尝试使用 VPN!" & vbCrLf & vbCrLf & "详细的错误信息:" & ex.Message, "皮肤获取失败") + Log(ex, "皮肤信息获取失败,游戏将以无皮肤的方式启动", LogLevel.Hint) End Try Case 4 '自定义 @@ -1104,7 +1122,7 @@ Retry: If Len(Uuid) = 32 Then Return Uuid '从官网获取 Try - Dim GotJson As JObject = NetGetCodeByRequestRetry("https://api.mojang.com/users/profiles/minecraft/" & Name, IsJson:=True) + Dim GotJson As JObject = GetJson(NetRequestByClientRetry("https://api.mojang.com/users/profiles/minecraft/" & Name, RequireJson:=True)) If GotJson Is Nothing Then Throw New FileNotFoundException("正版玩家档案不存在(" & Name & ")") Uuid = If(GotJson("id"), "") Catch ex As Exception @@ -1134,23 +1152,22 @@ Retry: Dim MinVer As New Version(0, 0, 0, 0), MaxVer As New Version(999, 999, 999, 999) 'MC 大版本检测 - If (McVersionCurrent.ReleaseTime >= New Date(2024, 4, 2) AndAlso McVersionCurrent.Version.McCodeMain = 99) OrElse - (McVersionCurrent.Version.McCodeMain > 20 AndAlso McVersionCurrent.Version.McCodeMain <> 99) OrElse - (McVersionCurrent.Version.McCodeMain = 20 AndAlso McVersionCurrent.Version.McCodeSub >= 5) Then + If (Not McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.ReleaseTime >= New Date(2024, 4, 2)) OrElse + (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 20, 5)) Then '1.20.5+(24w14a+):至少 Java 21 MinVer = New Version(1, 21, 0, 0) - ElseIf (McVersionCurrent.ReleaseTime >= New Date(2021, 11, 16) AndAlso McVersionCurrent.Version.McCodeMain = 99) OrElse - (McVersionCurrent.Version.McCodeMain >= 18 AndAlso McVersionCurrent.Version.McCodeMain <> 99) Then + ElseIf (Not McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.ReleaseTime >= New Date(2021, 11, 16)) OrElse + (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 18)) Then '1.18 pre2+:至少 Java 17 MinVer = New Version(1, 17, 0, 0) - ElseIf (McVersionCurrent.ReleaseTime >= New Date(2021, 5, 11) AndAlso McVersionCurrent.Version.McCodeMain = 99) OrElse - (McVersionCurrent.Version.McCodeMain >= 17 AndAlso McVersionCurrent.Version.McCodeMain <> 99) Then + ElseIf (Not McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.ReleaseTime >= New Date(2021, 5, 11)) OrElse + (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 17)) Then '1.17+ (21w19a+):至少 Java 16 MinVer = New Version(1, 16, 0, 0) ElseIf McVersionCurrent.ReleaseTime.Year >= 2017 Then 'Minecraft 1.12 与 1.11 的分界线正好是 2017 年,太棒了 '1.12+:至少 Java 8 MinVer = New Version(1, 8, 0, 0) - ElseIf McVersionCurrent.ReleaseTime <= New Date(2013, 5, 1) AndAlso McVersionCurrent.ReleaseTime.Year >= 2001 Then '避免某些版本的 1960 癌 + ElseIf McVersionCurrent.ReleaseTime <= New Date(2013, 5, 1) AndAlso McVersionCurrent.ReleaseTime.Year >= 2001 Then '避免某些版本写个 1960 年 '1.5.2-:最高 Java 12 MaxVer = New Version(1, 12, 999, 999) End If @@ -1161,11 +1178,11 @@ Retry: End If 'OptiFine 检测 - If McVersionCurrent.Version.HasOptiFine Then - If McVersionCurrent.Version.McCodeMain <= 7 AndAlso McVersionCurrent.Version.McCodeMain > 0 Then + If McVersionCurrent.Version.HasOptiFine AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本 + If McVersionCurrent.Version.McVersion < New Version(1, 7) Then '<1.7:至多 Java 8 MaxVer = New Version(1, 8, 999, 999) - ElseIf McVersionCurrent.Version.McCodeMain >= 8 AndAlso McVersionCurrent.Version.McCodeMain <= 11 Then + ElseIf McVersionCurrent.Version.McVersion >= New Version(1, 8) AndAlso McVersionCurrent.Version.McVersion < New Version(1, 12) Then '1.8 - 1.11:必须恰好 Java 8 MinVer = New Version(1, 8, 0, 0) : MaxVer = New Version(1, 8, 999, 999) ElseIf McVersionCurrent.Version.McCodeMain = 12 Then @@ -1176,14 +1193,14 @@ Retry: 'Forge 检测 If McVersionCurrent.Version.HasForge Then - If McVersionCurrent.Version.McName = "1.7.2" Then - '1.7.2:必须 Java 7 + If McVersionCurrent.Version.McVersion >= New Version(1, 6, 1) AndAlso McVersionCurrent.Version.McVersion <= New Version(1, 7, 2) Then + '1.6.1 - 1.7.2:必须 Java 7 MinVer = If(New Version(1, 7, 0, 0) > MinVer, New Version(1, 7, 0, 0), MinVer) MaxVer = If(New Version(1, 7, 999, 999) < MaxVer, New Version(1, 7, 999, 999), MaxVer) - ElseIf McVersionCurrent.Version.McCodeMain <= 12 AndAlso McVersionCurrent.Version.McCodeMain > 0 Then + ElseIf McVersionCurrent.Version.McCodeMain <= 12 OrElse Not McVersionCurrent.Version.IsStandardVersion Then '非标准版本 '<=1.12:Java 8 MaxVer = New Version(1, 8, 999, 999) - ElseIf McVersionCurrent.Version.McCodeMain <= 14 AndAlso McVersionCurrent.Version.McCodeMain >= 13 Then + ElseIf McVersionCurrent.Version.McCodeMain <= 14 Then '1.13 - 1.14:Java 8 - 10 MinVer = If(New Version(1, 8, 0, 0) > MinVer, New Version(1, 8, 0, 0), MinVer) MaxVer = If(New Version(1, 10, 999, 999) < MaxVer, New Version(1, 10, 999, 999), MaxVer) @@ -1201,16 +1218,22 @@ Retry: End If 'Fabric 检测 - If McVersionCurrent.Version.HasFabric Then - If McVersionCurrent.Version.McCodeMain >= 15 AndAlso McVersionCurrent.Version.McCodeMain <= 16 AndAlso McVersionCurrent.Version.McCodeMain <> -1 Then + If McVersionCurrent.Version.HasFabric AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本 + If McVersionCurrent.Version.McCodeMain >= 15 AndAlso McVersionCurrent.Version.McCodeMain <= 16 Then '1.15 - 1.16:Java 8+ MinVer = If(New Version(1, 8, 0, 0) > MinVer, New Version(1, 8, 0, 0), MinVer) - ElseIf McVersionCurrent.Version.McCodeMain >= 18 AndAlso McVersionCurrent.Version.McCodeMain < 99 Then + ElseIf McVersionCurrent.Version.McCodeMain >= 18 Then '1.18+:Java 17+ MinVer = If(New Version(1, 17, 0, 0) > MinVer, New Version(1, 17, 0, 0), MinVer) End If End If + 'LiteLoader 检测 + If McVersionCurrent.Version.HasLiteLoader AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本 + '最高 Java 8 + MaxVer = If(New Version(1, 8, 999, 999) < MaxVer, New Version(1, 8, 999, 999), MaxVer) + End If + '统一通行证检测 If Setup.Get("LoginType") = McLoginType.Nide Then '至少 Java 8u101 @@ -1222,28 +1245,32 @@ Retry: '选择 Java McLaunchLog("Java 版本需求:最低 " & MinVer.ToString & ",最高 " & MaxVer.ToString) McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent) - If Task.IsAborted Then Exit Sub + If Task.IsAborted Then Return If McLaunchJavaSelected IsNot Nothing Then McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString) - Exit Sub + Return End If '无合适的 Java - If Task.IsAborted Then Exit Sub '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java + If Task.IsAborted Then Return '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java McLaunchLog("无合适的 Java,需要确认是否自动下载") Dim JavaCode As String - If MinVer >= New Version(1, 22, 0, 0) Then '潜在的向后兼容 + If MinVer >= New Version(1, 22) Then '潜在的向后兼容 JavaCode = MinVer.Minor If Not JavaDownloadConfirm("Java " & JavaCode) Then Throw New Exception("$$") - ElseIf MinVer >= New Version(1, 21, 0, 0) Then + ElseIf MinVer >= New Version(1, 21) Then JavaCode = 21 If Not JavaDownloadConfirm("Java 21") Then Throw New Exception("$$") - ElseIf MinVer >= New Version(1, 9, 0, 0) Then + ElseIf MinVer >= New Version(1, 9) Then JavaCode = 17 If Not JavaDownloadConfirm("Java 17") Then Throw New Exception("$$") - ElseIf MaxVer < New Version(1, 8, 0, 0) Then + ElseIf MaxVer < New Version(1, 8) Then JavaCode = 7 - If Not JavaDownloadConfirm("Java 7", True) Then Throw New Exception("$$") + If McVersionCurrent.Version.HasForge Then + MyMsgBox("你需要先安装 LegacyJavaFixer Mod,或自行安装 Java 7,然后才能启动该版本。", "未找到 Java") + Else + If Not JavaDownloadConfirm("Java 7", True) Then Throw New Exception("$$") + End If ElseIf MinVer > New Version(1, 8, 0, 140) AndAlso MaxVer < New Version(1, 8, 0, 321) Then JavaCode = "8u141" If Not JavaDownloadConfirm("Java 8.0.141 ~ 8.0.320", True) Then Throw New Exception("$$") @@ -1273,7 +1300,7 @@ Retry: '检查下载结果 If JavaSearchLoader.State <> LoadState.Loading Then JavaSearchLoader.State = LoadState.Waiting '2872# McLaunchJavaSelected = JavaSelect("$$", MinVer, MaxVer, McVersionCurrent) - If Task.IsAborted Then Exit Sub + If Task.IsAborted Then Return If McLaunchJavaSelected IsNot Nothing Then McLaunchLog("选择的 Java:" & McLaunchJavaSelected.ToString) Else @@ -1350,22 +1377,14 @@ Retry: Arguments += " " & McLaunchArgumentsGameNew(McVersionCurrent) McLaunchLog("新版 Game 参数获取成功") End If - '编码参数(#5818、#5892) + '编码参数(#4700、#5892、#5909) + If McLaunchJavaSelected.VersionCode >= 18 Then 'Dfile.encoding 需要放在 Dstdout.encoding 后面(#6934) + If Not Arguments.Contains("-Dfile.encoding=") Then Arguments = "-Dfile.encoding=COMPAT " & Arguments + End If If McLaunchJavaSelected.VersionCode > 8 Then - If Not Arguments.Contains("-Dfile.encoding=") Then Arguments += " -Dfile.encoding=UTF-8" - If Not Arguments.Contains("-Dstdout.encoding=") Then Arguments += " -Dstdout.encoding=UTF-8" - If Not Arguments.Contains("-Dstderr.encoding=") Then Arguments += " -Dstderr.encoding=UTF-8" + If Not Arguments.Contains("-Dstdout.encoding=") Then Arguments = "-Dstdout.encoding=UTF-8 " & Arguments + If Not Arguments.Contains("-Dstderr.encoding=") Then Arguments = "-Dstderr.encoding=UTF-8 " & Arguments End If - '替换参数 - Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader) - If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then - '若自定义信息为空,则去掉该部分 - Arguments = Arguments.Replace(" --versionType ${version_type}", "") - ReplaceArguments("${version_type}") = """""" - End If - For Each entry As KeyValuePair(Of String, String) In ReplaceArguments - Arguments = Arguments.Replace(entry.Key, If(entry.Value.Contains(" ") OrElse entry.Value.Contains(":\"), """" & entry.Value & """", entry.Value)) - Next 'MJSB Arguments = Arguments.Replace(" -Dos.name=Windows 10", " -Dos.name=""Windows 10""") '全屏 @@ -1374,31 +1393,47 @@ Retry: For Each Arg In CurrentLaunchOptions.ExtraArgs Arguments += " " & Arg.Trim Next + '自定义 + Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent) + Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame) + '进行参数替换 + Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader) + If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then + '若自定义信息为空,则去掉该部分 + Arguments = Arguments.Replace(" --versionType ${version_type}", "") + ReplaceArguments("${version_type}") = """""" + End If + Dim FinalArguments As String = "" + For Each Argument In Arguments.Split(" ") + For Each Entry As KeyValuePair(Of String, String) In ReplaceArguments + Argument = Argument.Replace(Entry.Key, Entry.Value) + Next + If (Argument.Contains(" ") OrElse Argument.Contains(":\")) AndAlso Not Argument.EndsWithF("""") Then Argument = $"""{Argument}""" + FinalArguments += Argument & " " + Next + FinalArguments = FinalArguments.TrimEnd '进服 Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), CurrentLaunchOptions.ServerIp) If Server.Length > 0 Then If McVersionCurrent.ReleaseTime > New Date(2023, 4, 4) Then 'QuickPlay - Arguments += $" --quickPlayMultiplayer ""{Server}""" + FinalArguments += $" --quickPlayMultiplayer ""{Server}""" Else '老版本 If Server.Contains(":") Then '包含端口号 - Arguments += " --server " & Server.Split(":")(0) & " --port " & Server.Split(":")(1) + FinalArguments += $" --server {Server.Split(":")(0)} --port {Server.Split(":")(1)}" Else '不包含端口号 - Arguments += " --server " & Server & " --port 25565" + FinalArguments += $" --server {Server} --port 25565" End If If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Critical) End If End If - '自定义 - Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent) - Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame) '输出 McLaunchLog("Minecraft 启动参数:") - McLaunchLog(Arguments) - McLaunchArgument = Arguments + McLaunchLog(FinalArguments) + McLaunchArgument = FinalArguments End Sub 'Jvm 部分(第一段) @@ -1424,11 +1459,12 @@ Retry: End If 'Authlib-Injector If McLoginLoader.Output.Type = "Auth" Then + If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#) Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy, "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录 Setup.Get("VersionServerAuthServer", McVersionCurrent)) Try - Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8) + Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8) DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server & " -Dauthlibinjector.side=client" & " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response))) @@ -1451,6 +1487,9 @@ Retry: DataList.Add(Version.JsonObject("mainClass")) End If + '处理 DataList 的换行符 + DataList = DataList.Select(Function(d) d.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ").Trim).ToList + Return Join(DataList, " ") End Function Private Function McLaunchArgumentsJvmNew(Version As McVersion) As String @@ -1493,11 +1532,10 @@ NextVersion: End If 'Authlib-Injector If McLoginLoader.Output.Type = "Auth" Then - Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy, - "http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录 - Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent)) + If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#) + Dim Server As String = Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent) Try - Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8) + Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8) DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server & " -Dauthlibinjector.side=client" & " -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response))) @@ -1517,7 +1555,7 @@ NextVersion: '如果不合并,会导致 Forge 1.17 启动无效,它有两个 --add-exports,进一步导致其中一个在后面被去重 Dim DeDuplicateDataList As New List(Of String) For i = 0 To DataList.Count - 1 - Dim CurrentEntry As String = DataList(i) + Dim CurrentEntry As String = DataList(i).Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ").Trim If DataList(i).StartsWithF("-") Then Do While i < DataList.Count - 1 If DataList(i + 1).StartsWithF("-") Then @@ -1531,10 +1569,8 @@ NextVersion: DeDuplicateDataList.Add(CurrentEntry.Trim.Replace("McEmu= ", "McEmu=")) Next - '#3511 的清理 - DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") - '去重 + DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") '#3511 的清理 Dim Result As String = Join(DeDuplicateDataList.Distinct.ToList, " ") '添加 MainClass @@ -1671,20 +1707,21 @@ NextVersion: GameArguments.Add("${access_token}", McLoginLoader.Output.AccessToken) GameArguments.Add("${auth_session}", McLoginLoader.Output.AccessToken) GameArguments.Add("${user_type}", "msa") '#1221 + GameArguments.Add("${primary_jar}", Version.Path & Version.Name & ".jar") '#6942 '窗口尺寸参数 Dim GameSize As Size Select Case Setup.Get("LaunchArgumentWindowType") - Case 2 + Case 2 '与启动器尺寸一致 Dim Result As Size RunInUiWait(Sub() Result = New Size(GetPixelSize(FrmMain.PanForm.ActualWidth), GetPixelSize(FrmMain.PanForm.ActualHeight))) GameSize = Result - Case 3 - GameSize = New Size(Math.Max(100, Setup.Get("LaunchArgumentWindowWidth") - 2), Math.Max(100, Setup.Get("LaunchArgumentWindowHeight") - 2)) + GameSize.Height -= 29.5 * DPI / 96 '标题栏高度 + Case 3 '自定义 + GameSize = New Size(Math.Max(100, Setup.Get("LaunchArgumentWindowWidth")), Math.Max(100, Setup.Get("LaunchArgumentWindowHeight"))) Case Else - GameSize = New Size(875 - 2, 540 - 2) + GameSize = New Size(854, 480) End Select - GameSize.Height -= 29.5 * DPI / 96 '标题栏高度 If McVersionCurrent.Version.McCodeMain <= 12 AndAlso McLaunchJavaSelected.VersionCode <= 8 AndAlso McLaunchJavaSelected.Version.Revision >= 200 AndAlso McLaunchJavaSelected.Version.Revision <= 321 AndAlso Not McVersionCurrent.Version.HasOptiFine AndAlso Not McVersionCurrent.Version.HasForge Then @@ -1713,7 +1750,7 @@ NextVersion: CpStrings.Add(Library.LocalPath) End If Next - If OptiFineCp IsNot Nothing Then CpStrings.Insert(CpStrings.Count - 2, OptiFineCp) + If OptiFineCp IsNot Nothing Then CpStrings.Insert(CpStrings.Count - 2, OptiFineCp) 'OptiFine 的总是需要放到倒数第二位 GameArguments.Add("${classpath}", Join(CpStrings.Select(Function(c) ShortenPath(c)), ";")) Return GameArguments @@ -1759,7 +1796,7 @@ NextVersion: File.Delete(FilePath) Catch ex As UnauthorizedAccessException McLaunchLog("删除原 dll 访问被拒绝,这通常代表有一个 MC 正在运行,跳过解压:" & FilePath) - McLaunchLog("实际的错误信息:" & GetExceptionSummary(ex)) + McLaunchLog("实际的错误信息:" & ex.GetDetail()) Exit For End Try End If @@ -1779,8 +1816,8 @@ NextVersion: File.Delete(FileName) Catch ex As UnauthorizedAccessException McLaunchLog("删除多余文件访问被拒绝,跳过删除步骤") - McLaunchLog("实际的错误信息:" & GetExceptionSummary(ex)) - Exit Sub + McLaunchLog("实际的错误信息:" & ex.GetDetail()) + Return End Try Next @@ -1790,9 +1827,9 @@ NextVersion: ''' Private Function GetNativesFolder() As String Dim Result As String = McVersionCurrent.Path & McVersionCurrent.Name & "-natives" - If IsGBKEncoding OrElse Result.IsASCII() Then Return Result + If IsGBKEncoding OrElse Result.IsASCII() Then Return ShortenPath(Result) Result = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\bin\natives" - If Result.IsASCII() Then Return Result + If Result.IsASCII() Then Return ShortenPath(Result) Return OsDrive & "ProgramData\PCL\natives" End Function @@ -1805,7 +1842,7 @@ NextVersion: '要求 Java 使用高性能显卡 If Setup.Get("LaunchAdvanceGraphicCard") Then Try - SetGPUPreference(McLaunchJavaSelected.PathJavaw) + SetGPUPreference(McLaunchJavaSelected.PathJava) SetGPUPreference(PathWithName) Catch ex As Exception If IsAdmin() Then @@ -1813,7 +1850,7 @@ NextVersion: Else Log(ex, "直接调整显卡设置失败,将以管理员权限重启 PCL 再次尝试") Try - If RunAsAdmin($"--gpu ""{McLaunchJavaSelected.PathJavaw}""") = ProcessReturnValues.TaskDone Then + If RunAsAdmin($"--gpu ""{McLaunchJavaSelected.PathJava}""") = ProcessReturnValues.TaskDone Then McLaunchLog("以管理员权限重启 PCL 并调整显卡设置成功") Else Throw New Exception("调整过程中出现异常") @@ -2099,21 +2136,20 @@ IgnoreCustomSkin: "@echo off" & vbCrLf & $"title 启动 - {McVersionCurrent.Name}" & vbCrLf & "echo 游戏正在启动,请稍候。" & vbCrLf & - $"set APPDATA=""{ShortenPath(McVersionCurrent.PathIndie)}""" & vbCrLf & $"cd /D ""{ShortenPath(McVersionCurrent.PathIndie)}""" & vbCrLf & CustomCommandGlobal & vbCrLf & CustomCommandVersion & vbCrLf & $"""{McLaunchJavaSelected.PathJava}"" {McLaunchArgument}" & vbCrLf & "echo 游戏已退出。" & vbCrLf & "pause" - WriteFile(If(CurrentLaunchOptions.SaveBatch, Path & "PCL\LatestLaunch.bat"), SecretFilter(CmdString, "F"), - Encoding:=If(Encoding.Default.Equals(Encoding.UTF8), Encoding.UTF8, Encoding.GetEncoding("GB18030"))) + WriteFile(If(CurrentLaunchOptions.SaveBatch, Path & "PCL\LatestLaunch.bat"), FilterAccessToken(CmdString, "F"), + Encoding:=If(McLaunchJavaSelected.VersionCode > 8, Encoding.UTF8, Encoding.Default)) If CurrentLaunchOptions.SaveBatch IsNot Nothing Then McLaunchLog("导出启动脚本完成,强制结束启动过程") AbortHint = "导出启动脚本成功!" OpenExplorer(CurrentLaunchOptions.SaveBatch) Loader.Parent.Abort() - Exit Sub '导出脚本完成 + Return '导出脚本完成 End If Catch ex As Exception Log(ex, "输出启动脚本失败") @@ -2123,14 +2159,15 @@ IgnoreCustomSkin: '执行自定义命令 If CustomCommandGlobal <> "" Then McLaunchLog("正在执行全局自定义命令:" & CustomCommandGlobal) - Dim CustomProcess As New Process + Dim CustomProcess As Process = Nothing Try - CustomProcess.StartInfo.FileName = "cmd.exe" - CustomProcess.StartInfo.Arguments = "/c """ & CustomCommandGlobal & """" - CustomProcess.StartInfo.WorkingDirectory = ShortenPath(PathMcFolder) - CustomProcess.StartInfo.UseShellExecute = False - CustomProcess.StartInfo.CreateNoWindow = True - CustomProcess.Start() + CustomProcess = StartProcess(New ProcessStartInfo With { + .FileName = "cmd.exe", + .Arguments = $"/c ""{CustomCommandGlobal}""", + .WorkingDirectory = PathMcFolder, + .UseShellExecute = False, + .CreateNoWindow = True + }) If Setup.Get("LaunchAdvanceRunWait") Then Do Until CustomProcess.HasExited OrElse Loader.IsAborted Thread.Sleep(10) @@ -2139,7 +2176,7 @@ IgnoreCustomSkin: Catch ex As Exception Log(ex, "执行全局自定义命令失败", LogLevel.Hint) Finally - If Not CustomProcess.HasExited AndAlso Loader.IsAborted Then + If CustomProcess IsNot Nothing AndAlso Not CustomProcess.HasExited AndAlso Loader.IsAborted Then McLaunchLog("由于取消启动,已强制结束自定义命令 CMD 进程") '#1183 CustomProcess.Kill() End If @@ -2147,14 +2184,15 @@ IgnoreCustomSkin: End If If CustomCommandVersion <> "" Then McLaunchLog("正在执行版本自定义命令:" & CustomCommandVersion) - Dim CustomProcess As New Process + Dim CustomProcess As Process = Nothing Try - CustomProcess.StartInfo.FileName = "cmd.exe" - CustomProcess.StartInfo.Arguments = "/c """ & CustomCommandVersion & """" - CustomProcess.StartInfo.WorkingDirectory = ShortenPath(PathMcFolder) - CustomProcess.StartInfo.UseShellExecute = False - CustomProcess.StartInfo.CreateNoWindow = True - CustomProcess.Start() + CustomProcess = StartProcess(New ProcessStartInfo With { + .FileName = "cmd.exe", + .Arguments = $"/c ""{CustomCommandVersion}""", + .WorkingDirectory = PathMcFolder, + .UseShellExecute = False, + .CreateNoWindow = True + }) If Setup.Get("VersionAdvanceRunWait", Version:=McVersionCurrent) Then Do Until CustomProcess.HasExited OrElse Loader.IsAborted Thread.Sleep(10) @@ -2163,7 +2201,7 @@ IgnoreCustomSkin: Catch ex As Exception Log(ex, "执行版本自定义命令失败", LogLevel.Hint) Finally - If Not CustomProcess.HasExited AndAlso Loader.IsAborted Then + If CustomProcess IsNot Nothing AndAlso Not CustomProcess.HasExited AndAlso Loader.IsAborted Then McLaunchLog("由于取消启动,已强制结束自定义命令 CMD 进程") '#1183 CustomProcess.Kill() End If @@ -2174,8 +2212,7 @@ IgnoreCustomSkin: Private Sub McLaunchRun(Loader As LoaderTask(Of Integer, Process)) '启动信息 - Dim GameProcess = New Process() - Dim StartInfo As New ProcessStartInfo(McLaunchJavaSelected.PathJavaw) + Dim StartInfo As New ProcessStartInfo(McLaunchJavaSelected.PathJava) '使用 javaw.exe 会导致 #6263 '设置环境变量 Dim Paths As New List(Of String)(StartInfo.EnvironmentVariables("Path").Split(";")) @@ -2184,23 +2221,20 @@ IgnoreCustomSkin: StartInfo.EnvironmentVariables("appdata") = ShortenPath(PathMcFolder) '设置其他参数 - StartInfo.StandardErrorEncoding = If(McLaunchJavaSelected.VersionCode > 8, Encoding.UTF8, Nothing) - StartInfo.StandardOutputEncoding = If(McLaunchJavaSelected.VersionCode > 8, Encoding.UTF8, Nothing) - StartInfo.WorkingDirectory = ShortenPath(McVersionCurrent.PathIndie) + StartInfo.WorkingDirectory = McVersionCurrent.PathIndie StartInfo.UseShellExecute = False StartInfo.RedirectStandardOutput = True StartInfo.RedirectStandardError = True - StartInfo.CreateNoWindow = False + StartInfo.CreateNoWindow = True StartInfo.Arguments = McLaunchArgument - GameProcess.StartInfo = StartInfo '开始进程 - GameProcess.Start() - McLaunchLog("已启动游戏进程:" & McLaunchJavaSelected.PathJavaw) + Dim GameProcess = StartProcess(StartInfo) + McLaunchLog("已启动游戏进程:" & McLaunchJavaSelected.PathJava) If Loader.IsAborted Then McLaunchLog("由于取消启动,已强制结束游戏进程") '#1631 GameProcess.Kill() - Exit Sub + Return End If Loader.Output = GameProcess McLaunchProcess = GameProcess diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb index 6dc7c0e..6a3a527 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb @@ -86,7 +86,7 @@ Public Module ModMinecraft Next If Not Renamed Then CacheMcFolderList.Add(New McFolder With {.Name = Name, .Path = Path, .Type = McFolderType.Custom}) Catch ex As Exception - MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & GetExceptionSummary(ex), "Minecraft 文件夹失效", IsWarn:=True) + MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & ex.GetBrief(), "Minecraft 文件夹失效", IsWarn:=True) Log(ex, $"无法访问 Minecraft 文件夹 {Path}") End Try Next @@ -127,7 +127,7 @@ Public Module ModMinecraft ''' Public Sub McFolderLauncherProfilesJsonCreate(Folder As String) Try - If File.Exists(Folder & "launcher_profiles.json") Then Exit Sub + If File.Exists(Folder & "launcher_profiles.json") Then Return Dim ResultJson As String = "{ ""profiles"": { @@ -165,12 +165,12 @@ Public Module ModMinecraft Return _McVersionCurrent End Get Set(value As McVersion) - If ReferenceEquals(_McVersionLast, value) Then Exit Property + If ReferenceEquals(_McVersionLast, value) Then Return _McVersionCurrent = value '由于有可能是 Nothing,导致无法初始化,才得这样弄一圈 _McVersionLast = value - If value Is Nothing Then Exit Property - '重置缓存的 Mod 文件夹 - PageDownloadCompDetail.CachedFolder = Nothing + If value Is Nothing Then Return + '重置缓存的下载文件夹 + PageDownloadCompDetail.CachedFolder.Clear() '统一通行证重判 If AniControlEnabled = 0 AndAlso Setup.Get("VersionServerNide", Version:=value) <> Setup.Get("CacheNideServer") AndAlso @@ -207,46 +207,44 @@ Public Module ModMinecraft ''' Public ReadOnly Property PathIndie As String Get - InitPathIndie(Modable) + If Setup.IsUnset("VersionArgumentIndieV2", Version:=Me) Then + If Not IsLoaded Then Load() + '决定该版本是否应该被隔离 + Dim ShouldBeIndie = + Function() As Boolean + '从老的版本独立设置中迁移:-1 未决定,0 使用全局设置,1 手动开启,2 手动关闭 + If Not Setup.IsUnset("VersionArgumentIndie", Version:=Me) AndAlso Setup.Get("VersionArgumentIndie", Version:=Me) > 0 Then + Log($"[Minecraft] 版本隔离初始化({Name}):从老的版本独立设置中迁移") + Return Setup.Get("VersionArgumentIndie", Version:=Me) = 1 + End If + '若版本文件夹下包含 mods 或 saves 文件夹,则自动开启版本隔离 + Dim ModFolder As New DirectoryInfo(Path & "mods\") + Dim SaveFolder As New DirectoryInfo(Path & "saves\") + If (ModFolder.Exists AndAlso ModFolder.EnumerateFiles.Any) OrElse (SaveFolder.Exists AndAlso SaveFolder.EnumerateDirectories.Any) Then + Log($"[Minecraft] 版本隔离初始化({Name}):版本文件夹下存在 mods 或 saves 文件夹,自动开启") + Return True + End If + '根据全局的默认设置决定是否隔离 + Dim IsRelease As Boolean = State <> McVersionState.Fool AndAlso State <> McVersionState.Old AndAlso State <> McVersionState.Snapshot + Log($"[Minecraft] 版本隔离初始化({Name}):从全局默认设置中({Setup.Get("LaunchArgumentIndieV2")})判断,State {GetStringFromEnum(State)},IsRelease {IsRelease},Modable {Modable}") + Select Case Setup.Get("LaunchArgumentIndieV2") + Case 0 '关闭 + Return False + Case 1 '仅隔离可安装 Mod 的版本 + Return Modable + Case 2 '仅隔离非正式版 + Return Not IsRelease + Case 3 '隔离非正式版与可安装 Mod 的版本 + Return Not IsRelease OrElse Modable + Case Else '隔离所有版本 + Return True + End Select + End Function + Setup.Set("VersionArgumentIndieV2", ShouldBeIndie(), Version:=Me) + End If Return If(Setup.Get("VersionArgumentIndieV2", Version:=Me), Path, PathMcFolder) End Get End Property - ''' - ''' 决定该版本是否应该被隔离。 - ''' - Public Sub InitPathIndie(Modable As Boolean) - If Not Setup.IsUnset("VersionArgumentIndieV2", Version:=Me) Then Return - Dim ShouldBeIndie = - Function() As Boolean - '从老的版本独立设置中迁移:-1 未决定,0 使用全局设置,1 手动开启,2 手动关闭 - If Not Setup.IsUnset("VersionArgumentIndie", Version:=Me) AndAlso Setup.Get("VersionArgumentIndie", Version:=Me) > 0 Then - Log($"[Minecraft] 版本隔离初始化({Name}):从老的版本独立设置中迁移") - Return Setup.Get("VersionArgumentIndie", Version:=Me) = 1 - End If - '若版本文件夹下包含 mods 或 saves 文件夹,则自动开启版本隔离 - Dim ModFolder As New DirectoryInfo(Path & "mods\") - Dim SaveFolder As New DirectoryInfo(Path & "saves\") - If (ModFolder.Exists AndAlso ModFolder.EnumerateFiles.Any) OrElse (SaveFolder.Exists AndAlso SaveFolder.EnumerateFiles.Any) Then - Log($"[Minecraft] 版本隔离初始化({Name}):版本文件夹下存在 mods 或 saves 文件夹,自动开启") - Return True - End If - '根据全局的默认设置决定是否隔离 - Log($"[Minecraft] 版本隔离初始化({Name}):从全局默认设置中({Setup.Get("LaunchArgumentIndieV2")})判断") - Select Case Setup.Get("LaunchArgumentIndieV2") - Case 0 '关闭 - Return False - Case 1 '仅隔离可安装 Mod 的版本 - Return Modable - Case 2 '仅隔离非正式版 - Return State = McVersionState.Fool OrElse State = McVersionState.Old OrElse State = McVersionState.Snapshot - Case 3 '隔离非正式版与可安装 Mod 的版本 - Return Modable OrElse State = McVersionState.Fool OrElse State = McVersionState.Old OrElse State = McVersionState.Snapshot - Case Else '隔离所有版本 - Return True - End Select - End Function - Setup.Set("VersionArgumentIndieV2", ShouldBeIndie(), Version:=Me) - End Sub ''' ''' 该版本的版本文件夹名称。 @@ -318,14 +316,6 @@ Public Module ModMinecraft _Version.McName = "pending" GoTo VersionSearchFinish End If - '从 JumpLoader 信息中获取版本号 - If HasJumpLoader Then - Try - _Version.McName = JsonObject("jumploader")("jars")("minecraft")(0)("gameVersion") - GoTo VersionSearchFinish - Catch - End Try - End If '从 PCL 下载的版本信息中获取版本号 If JsonObject("clientVersion") IsNot Nothing Then _Version.McName = JsonObject("clientVersion") @@ -382,7 +372,6 @@ Public Module ModMinecraft _Version.McName = Regex GoTo VersionSearchFinish End If - 'FUTURE: [Quilt 支持] 从 Quilt 版本中获取版本号 '从 jar 项中获取版本号 If JsonObject("jar") IsNot Nothing Then _Version.McName = JsonObject("jar").ToString @@ -432,7 +421,7 @@ VersionSearchFinish: _Version.McCodeMain = If(SplitResult.Length <= 2, Val(SplitResult), "0") SplitResult = If(SplitVersion.Count >= 3, SplitVersion(2), "0") _Version.McCodeSub = If(SplitResult.Length <= 2, Val(SplitResult), "0") - ElseIf _Version.McName.Contains("w") OrElse _Version.McName = "pending" Then + ElseIf (Not IsVersionNameLikeRelease(_Version.McName)) OrElse _Version.McName = "pending" Then _Version.McCodeMain = 99 _Version.McCodeSub = 99 End If @@ -451,6 +440,33 @@ VersionSearchFinish: ''' Public ReleaseTime As New Date(1970, 1, 1, 15, 0, 0) + ''' + ''' 获取该版本的 JSON 路径。 + ''' + Public Function GetJsonPath() As String + Dim JsonPath As String = Path & Name & ".json" + If Not File.Exists(JsonPath) Then + '尝试寻找 JSON 文件 + JsonPath = Nothing + For Each JsonCandidatePath In Directory.GetFiles(Path, "*.json") + Try + Dim JsonCandidate As JObject = GetJson(ReadFile(JsonCandidatePath)) + If Not JsonCandidate.ContainsKey("mainClass") Then Continue For + If Not JsonCandidate.ContainsKey("type") Then Continue For + If Not JsonCandidate.ContainsKey("id") Then Continue For + Catch + End Try + JsonPath = JsonCandidatePath + Exit For + Next + If JsonPath Is Nothing Then + Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json") + Else + Log("[Minecraft] 未找到同名版本 JSON,自动换用 " & JsonPath, LogLevel.Debug) + End If + End If + Return JsonPath + End Function ''' ''' 该版本的 Json 文本。 ''' @@ -463,17 +479,7 @@ VersionSearchFinish: Return TrimedJson.StartsWithF("{") AndAlso TrimedJson.EndsWithF("}") End Function If _JsonText Is Nothing Then - Dim JsonPath As String = Path & Name & ".json" - If Not File.Exists(JsonPath) Then - '如果文件夹下只有一个 JSON 文件,则将其作为版本 JSON - Dim JsonFiles As String() = Directory.GetFiles(Path, "*.json") - If JsonFiles.Count = 1 Then - JsonPath = JsonFiles(0) - Log("[Minecraft] 未找到同名版本 JSON,自动换用 " & JsonPath, LogLevel.Debug) - Else - Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json") - End If - End If + Dim JsonPath As String = GetJsonPath() _JsonText = ReadFile(JsonPath) '如果 ReadFile 失败会返回空字符串;这可能是由于文件被临时占用,故延时后重试 If Not FastJsonCheck(_JsonText) Then @@ -551,9 +557,15 @@ Recheck: '继续循环 If Inherit.InheritVersion = InheritVersion Then Throw New Exception("版本依赖项出现嵌套:" & InheritVersion) InheritVersion = Inherit.InheritVersion - '合并 + '合并 Libraries 项:子版本放在前面,父版本放在后面(5978#:如果多个 jar 包中含有相同的类,Java 8 和之前的版本按照 -cp 指定的顺序选择第一个) + Dim CurrentLib As JArray = _JsonObject("libraries").DeepClone() + For Each LibToken In Inherit.JsonObject("libraries") + CurrentLib.Add(LibToken) + Next + '合并其他项:子版本优先于父版本 Inherit.JsonObject.Merge(_JsonObject) _JsonObject = Inherit.JsonObject + _JsonObject("libraries") = CurrentLib GoTo Recheck End If Catch ex As Exception @@ -562,25 +574,6 @@ Recheck: Catch ex As Exception Throw New Exception("初始化版本 JSON 时失败(" & If(Name, "null") & ")", ex) End Try - Try - '处理 JumpLoader - If Text.Contains("minecraftforge") AndAlso File.Exists(PathIndie & "config\jumploader.json") Then - For Each ModFile In Directory.EnumerateFiles(PathIndie & "mods") - Dim FileName As String = GetFileNameFromPath(ModFile) - If FileName.EndsWithF(".jar", True) AndAlso FileName.ContainsF("jumploader", True) Then - Log("[Minecraft] 发现 JumpLoader 分支项:" & FileName) - HasJumpLoader = True - Exit For - End If - Next - End If - If HasJumpLoader Then - _JsonObject.Remove("jumploader") - _JsonObject.Add("jumploader", GetJson(ReadFile(PathIndie & "config\jumploader.json"))) - End If - Catch ex As Exception - Log(ex, "处理 JumpLoader 失败") - End Try End If Return _JsonObject End Get @@ -601,10 +594,6 @@ Recheck: ''' Json 是否为 HMCL 格式。 ''' Public Property IsHmclFormatJson As Boolean = False - ''' - ''' 是否包含 JumpLoader。 - ''' - Public Property HasJumpLoader As Boolean = False ''' ''' 版本 jar 中的 version.json 文件对象。 @@ -614,20 +603,18 @@ Recheck: Get If Not JsonVersionInited Then JsonVersionInited = True - If File.Exists(Path & Name & ".jar") Then - Try - Using JarArchive As New ZipArchive(New FileStream(Path & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - Dim VersionJson As ZipArchiveEntry = JarArchive.GetEntry("version.json") - If VersionJson IsNot Nothing Then - Using VersionJsonStream As New StreamReader(VersionJson.Open) - _JsonVersion = GetJson(VersionJsonStream.ReadToEnd) - End Using - End If + Try + If Not File.Exists($"{Path}{Name}.jar") Then Exit Try + Using JarArchive As New ZipArchive(New FileStream(Path & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + Dim VersionJson As ZipArchiveEntry = JarArchive.GetEntry("version.json") + If VersionJson Is Nothing Then Exit Try + Using VersionJsonStream As New StreamReader(VersionJson.Open) + _JsonVersion = GetJson(VersionJsonStream.ReadToEnd) End Using - Catch ex As Exception - Log(ex, "从版本 jar 中读取 version.json 失败") - End Try - End If + End Using + Catch ex As Exception + Log(ex, $"从版本 jar 中读取 version.json 失败({Path}{Name}.jar)") + End Try End If Return _JsonVersion End Get @@ -708,7 +695,7 @@ Recheck: Catch ex As Exception Log(ex, "依赖版本检查出错(" & Name & ")") State = McVersionState.Error - Info = "未知错误:" & GetExceptionSummary(ex) + Info = "未知错误:" & ex.GetBrief() Return False End Try @@ -748,7 +735,6 @@ Recheck: Version.HasLiteLoader = True End If 'Fabric、Forge - 'FUTURE: [Quilt 支持] 确认这里的玩意儿对不对 If RealJson.Contains("net.fabricmc:fabric-loader") OrElse RealJson.Contains("org.quiltmc:quilt-loader") Then State = McVersionState.Fabric Version.HasFabric = True @@ -822,7 +808,7 @@ ExitDataLoad: WriteIni(Path & "PCL\Setup.ini", "VersionOriginalSub", Version.McCodeSub) End If Catch ex As Exception - Info = "未知错误:" & GetExceptionSummary(ex) + Info = "未知错误:" & ex.GetBrief() Logo = PathImage & "Blocks/RedstoneBlock.png" State = McVersionState.Error Log(ex, "加载版本失败(" & Name & ")", LogLevel.Feedback) @@ -859,7 +845,6 @@ ExitDataLoad: Info = "发生了未知错误,请向作者反馈此问题" End Select If Not State = McVersionState.Error Then - If HasJumpLoader Then Info += ", JumpLoader" If Setup.Get("VersionServerLogin", Version:=Me) = 3 Then Info += ", 统一通行证验证" If Setup.Get("VersionServerLogin", Version:=Me) = 4 Then Info += ", Authlib 验证" End If @@ -919,6 +904,24 @@ ExitDataLoad: ''' 原版次版本号,如 2(For 1.12.2),快照则固定为 99。不可用则为 -1。 ''' Public McCodeSub As Integer = -1 + ''' + ''' 是否为非快照版,且读取到了一个有效的原版版本号。 + ''' + Public ReadOnly Property IsStandardVersion As Boolean + Get + Return McCodeMain > -1 AndAlso McCodeMain < 99 AndAlso McCodeSub > -1 AndAlso McCodeSub < 99 + End Get + End Property + ''' + ''' 标准的原版版本号。 + ''' 若为快照版或没有有效版本号,则返回 0。 + ''' + Public ReadOnly Property McVersion As Version + Get + If Not IsStandardVersion Then Return New Version(0, 0, 0) + Return New Version(1, McCodeMain, McCodeSub) + End Get + End Property 'OptiFine @@ -986,7 +989,7 @@ ExitDataLoad: If ToString = "" Then Return "原版 " & McName Else - Return McName & ToString & If(ModeDebug, " (" & SortCode & "#)", "") + Return McName & ToString End If End Function @@ -1152,7 +1155,7 @@ Reload: '改变当前选择的版本 OnLoaded: - If Loader.IsAborted Then Exit Sub + If Loader.IsAborted Then Return If McVersionList.Any(Function(v) v.Key <> McVersionCardType.Error) Then '尝试读取已储存的选择 Dim SavedSelection As String = ReadIni(Path & "PCL.ini", "Version") @@ -1164,7 +1167,7 @@ OnLoaded: McVersionCurrent = Version Setup.Set("LaunchVersionSelect", McVersionCurrent.Name) Log("[Minecraft] 选择该文件夹储存的 Minecraft 版本:" & McVersionCurrent.Path) - Exit Sub + Return End If Next Next @@ -1183,6 +1186,7 @@ OnLoaded: If Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(200, 3000)) Catch ex As ThreadInterruptedException Catch ex As Exception + If Loader.IsAborted Then Return '#5617 WriteIni(Path & "PCL.ini", "VersionCache", "") '要求下次重新加载 Log(ex, "加载 .minecraft 版本列表失败", LogLevel.Feedback) End Try @@ -1556,7 +1560,7 @@ OnLoaded: ''' 要求玩家选择一个皮肤文件,并进行相关校验。 ''' Public Function McSkinSelect() As McSkinInfo - Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.webp)|*.png;*.jpg;*.webp", "选择皮肤文件") + Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.jpeg;*.webp)|*.png;*.jpg;*.jpeg;*.webp", "选择皮肤文件") '验证有效性 If FileName = "" Then Return New McSkinInfo With {.IsVaild = False} @@ -1589,11 +1593,11 @@ OnLoaded: ''' ''' 获取 Uuid 对应的皮肤文件地址,失败将抛出异常。 ''' - Public Function McSkinGetAddress(Uuid As String, Type As String) As String - If Uuid = "" Then Throw New Exception("Uuid 为空。") - If Uuid.StartsWithF("00000") Then Throw New Exception("离线 Uuid 无正版皮肤文件。") + Public Function McSkinGetAddress(UUID As String, Type As String) As String + If UUID = "" Then Throw New Exception("UUID 为空。") + If UUID.StartsWithF("00000") AndAlso Type <> "Auth" Then Throw New Exception("离线 UUID 无正版皮肤文件:" & UUID) '尝试读取缓存 - Dim CacheSkinAddress As String = ReadIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid) + Dim CacheSkinAddress As String = ReadIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", UUID) If Not CacheSkinAddress = "" Then Return CacheSkinAddress '获取皮肤地址 Dim Url As String @@ -1607,7 +1611,7 @@ OnLoaded: Case Else Throw New ArgumentException("皮肤地址种类无效:" & If(Type, "null")) End Select - Dim SkinString = NetGetCodeByRequestRetry(Url & Uuid) + Dim SkinString = NetRequestByClientRetry(Url & UUID, RequireJson:=True) If SkinString = "" Then Throw New Exception("皮肤返回值为空,可能是未设置自定义皮肤的用户") '处理皮肤地址 Dim SkinValue As String @@ -1628,11 +1632,12 @@ OnLoaded: If SkinJson("textures") Is Nothing OrElse SkinJson("textures")("skin") Is Nothing OrElse SkinJson("textures")("skin")("url") Is Nothing Then Throw New Exception("用户未设置自定义皮肤") Else - SkinValue = SkinJson("textures")("skin")("url").ToString + Dim SkinUrl As String = SkinJson("textures")("skin")("url").ToString + SkinValue = If(SkinUrl.Contains("minecraft.net/"), SkinUrl.Replace("http://", "https://"), SkinUrl) End If '保存缓存 - WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid, SkinValue) - Log("[Skin] UUID " & Uuid & " 对应的皮肤文件为 " & SkinValue) + WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", UUID, SkinValue) + Log("[Skin] UUID " & UUID & " 对应的皮肤文件为 " & SkinValue) Return SkinValue End Function @@ -1645,9 +1650,9 @@ OnLoaded: Dim FileAddress As String = PathTemp & "Cache\Skin\" & GetHash(Address) & ".png" SyncLock McSkinDownloadLock If Not File.Exists(FileAddress) Then - NetDownloadByClient(Address, FileAddress & NetDownloadEnd) + NetDownloadByClient(Address, FileAddress & ".PCLDownloading") File.Delete(FileAddress) - FileSystem.Rename(FileAddress & NetDownloadEnd, FileAddress) + FileSystem.Rename(FileAddress & ".PCLDownloading", FileAddress) Log("[Minecraft] 皮肤下载成功:" & FileAddress) End If Return FileAddress @@ -1728,10 +1733,6 @@ OnLoaded: ''' 原 Json 中的 Name 项。 ''' Public OriginalName As String - ''' - ''' 是否为 JumpLoader 项。 - ''' - Public IsJumpLoader As Boolean = False Public Overrides Function ToString() As String Return If(IsNatives, "[Native] ", "") & GetString(Size) & " | " & LocalPath @@ -1794,7 +1795,7 @@ OnLoaded: '获取当前支持库列表 Log("[Minecraft] 获取支持库列表:" & Version.Name) - McLibListGet = McLibListGetWithJson(Version.JsonObject, JumpLoaderFolder:=Version.PathIndie & ".jumploader\") + Dim Result = McLibListGetWithJson(Version.JsonObject) '需要添加原版 Jar If IncludeVersionJar Then @@ -1834,28 +1835,20 @@ OnLoaded: ClientSHA1 = Nothing End If '把所需的原版 Jar 添加进去 - McLibListGet.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1}) + Result.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1}) End If + Return Result End Function ''' ''' 获取 Minecraft 某一版本忽视继承的支持库列表,即结果中没有继承项。 ''' - Public Function McLibListGetWithJson(JsonObject As JObject, Optional KeepSameNameDifferentVersionResult As Boolean = False, Optional CustomMcFolder As String = Nothing, Optional JumpLoaderFolder As String = Nothing) As List(Of McLibToken) + Public Function McLibListGetWithJson(JsonObject As JObject, Optional KeepSameNameDifferentVersionResult As Boolean = False, Optional CustomMcFolder As String = Nothing) As List(Of McLibToken) CustomMcFolder = If(CustomMcFolder, PathMcFolder) - Dim BasicArray As New List(Of McLibToken) - - '添加基础 Json 项 - Dim AllLibs As JArray = JsonObject("libraries") - '添加 JumpLoader Json 项 - If JsonObject("jumploader") IsNot Nothing AndAlso JsonObject("jumploader")("jars") IsNot Nothing AndAlso JsonObject("jumploader")("jars")("maven") IsNot Nothing Then - For Each JumpLoaderToken In JsonObject("jumploader")("jars")("maven") - AllLibs.Add(JumpLoaderToken) - Next - End If '转换为 LibToken - For Each Library As JObject In AllLibs.Children + Dim BasicArray As New List(Of McLibToken) + For Each Library As JObject In CType(JsonObject("libraries"), JArray).Children '清理 null 项(BakaXL 会把没有的项序列化为 null,但会被 Newtonsoft 转换为 JValue,导致 Is Nothing = false;这导致了 #409) For i = Library.Properties.Count - 1 To 0 Step -1 @@ -1865,14 +1858,6 @@ OnLoaded: '检查是否需要(Rules) If Not McJsonRuleCheck(Library("rules")) Then Continue For - '检查 JumpLoader - Dim IsJumpLoader As Boolean = False - If Library("mavenPath") IsNot Nothing Then - IsJumpLoader = True - If Library("name") Is Nothing Then Library.Add("name", Library("mavenPath")) '这里的修改会导致原 Json 内容改变 - If Library("repoUrl") IsNot Nothing AndAlso Library("url") Is Nothing Then Library.Add("url", Library("repoUrl")) - End If - '获取根节点下的 url Dim RootUrl As String = Library("url") If RootUrl IsNot Nothing Then @@ -1882,15 +1867,10 @@ OnLoaded: '根据是否本地化处理(Natives) If Library("natives") Is Nothing Then '没有 Natives Dim LocalPath As String - If IsJumpLoader Then - LocalPath = McLibGet(Library("name"), CustomMcFolder:=If(JumpLoaderFolder, CustomMcFolder)) - Else - LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder) - End If + LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder) Try If Library("downloads") IsNot Nothing AndAlso Library("downloads")("artifact") IsNot Nothing Then BasicArray.Add(New McLibToken With { - .IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = If(RootUrl, Library("downloads")("artifact")("url")), .LocalPath = If(Library("downloads")("artifact")("path") Is Nothing, McLibGet(Library("name"), @@ -1899,17 +1879,16 @@ OnLoaded: .IsNatives = False, .SHA1 = Library("downloads")("artifact")("sha1")?.ToString}) Else - BasicArray.Add(New McLibToken With {.IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = RootUrl, .LocalPath = LocalPath, .Size = 0, .IsNatives = False, .SHA1 = Nothing}) + BasicArray.Add(New McLibToken With {.OriginalName = Library("name"), .Url = RootUrl, .LocalPath = LocalPath, .Size = 0, .IsNatives = False, .SHA1 = Nothing}) End If Catch ex As Exception Log(ex, "处理实际支持库列表失败(无 Natives," & If(Library("name"), "Nothing").ToString & ")") - BasicArray.Add(New McLibToken With {.IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = RootUrl, .LocalPath = LocalPath, .Size = 0, .IsNatives = False, .SHA1 = Nothing}) + BasicArray.Add(New McLibToken With {.OriginalName = Library("name"), .Url = RootUrl, .LocalPath = LocalPath, .Size = 0, .IsNatives = False, .SHA1 = Nothing}) End Try ElseIf Library("natives")("windows") IsNot Nothing Then '有 Windows Natives Try If Library("downloads") IsNot Nothing AndAlso Library("downloads")("classifiers") IsNot Nothing AndAlso Library("downloads")("classifiers")("natives-windows") IsNot Nothing Then BasicArray.Add(New McLibToken With { - .IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = If(RootUrl, Library("downloads")("classifiers")("natives-windows")("url")), .LocalPath = If(Library("downloads")("classifiers")("natives-windows")("path") Is Nothing, @@ -1919,11 +1898,11 @@ OnLoaded: .IsNatives = True, .SHA1 = Library("downloads")("classifiers")("natives-windows")("sha1").ToString}) Else - BasicArray.Add(New McLibToken With {.IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = RootUrl, .LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder).Replace(".jar", "-" & Library("natives")("windows").ToString & ".jar").Replace("${arch}", If(Environment.Is64BitOperatingSystem, "64", "32")), .Size = 0, .IsNatives = True, .SHA1 = Nothing}) + BasicArray.Add(New McLibToken With {.OriginalName = Library("name"), .Url = RootUrl, .LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder).Replace(".jar", "-" & Library("natives")("windows").ToString & ".jar").Replace("${arch}", If(Environment.Is64BitOperatingSystem, "64", "32")), .Size = 0, .IsNatives = True, .SHA1 = Nothing}) End If Catch ex As Exception Log(ex, "处理实际支持库列表失败(有 Natives," & If(Library("name"), "Nothing").ToString & ")") - BasicArray.Add(New McLibToken With {.IsJumpLoader = IsJumpLoader, .OriginalName = Library("name"), .Url = RootUrl, .LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder).Replace(".jar", "-" & Library("natives")("windows").ToString & ".jar").Replace("${arch}", If(Environment.Is64BitOperatingSystem, "64", "32")), .Size = 0, .IsNatives = True, .SHA1 = Nothing}) + BasicArray.Add(New McLibToken With {.OriginalName = Library("name"), .Url = RootUrl, .LocalPath = McLibGet(Library("name"), CustomMcFolder:=CustomMcFolder).Replace(".jar", "-" & Library("natives")("windows").ToString & ".jar").Replace("${arch}", If(Environment.Is64BitOperatingSystem, "64", "32")), .Size = 0, .IsNatives = True, .SHA1 = Nothing}) End Try End If @@ -1940,7 +1919,7 @@ OnLoaded: Return GetFolderNameFromPath(GetPathFromFullPath(Token.LocalPath)) End Function For i = 0 To BasicArray.Count - 1 - Dim Key As String = BasicArray(i).Name & BasicArray(i).IsNatives.ToString & BasicArray(i).IsJumpLoader.ToString + Dim Key As String = BasicArray(i).Name & BasicArray(i).IsNatives.ToString If ResultArray.ContainsKey(Key) Then Dim BasicArrayVersion As String = GetVersion(BasicArray(i)) Dim ResultArrayVersion As String = GetVersion(ResultArray(Key)) @@ -1964,7 +1943,7 @@ OnLoaded: ''' 获取版本缺失的支持库文件所对应的 NetTaskFile。 ''' Public Function McLibFix(Version As McVersion) As List(Of NetFile) - If Not Version.IsLoaded Then Version.Load() '确保例如 JumpLoader 等项被合并入 Json + If Not Version.IsLoaded Then Version.Load() Dim Result As New List(Of NetFile) '更新此方法时需要同步更新 Forge 新版自动安装方法! @@ -1978,7 +1957,7 @@ OnLoaded: End Try 'Library 文件 - Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False), JumpLoaderFolder:=Version.PathIndie & ".jumploader\")) + Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False))) '统一通行证文件 If Setup.Get("VersionServerLogin", Version:=Version) = 3 Then @@ -1988,8 +1967,7 @@ OnLoaded: Try Log("[Minecraft] 开始获取统一通行证下载信息") '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/ - DownloadInfo = GetJson(NetGetCodeByLoader({ - "https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version)}, IsJson:=True)) + DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version), RequireJson:=True)) Catch ex As Exception Log(ex, "获取统一通行证下载信息失败") End Try @@ -2006,17 +1984,20 @@ OnLoaded: 'Authlib-Injector 文件 If Setup.Get("VersionServerLogin", Version:=Version) = 4 Then - Dim TargetFile = PathPure & "\authlib-injector.jar" + Dim TargetFile = PathPure & "authlib-injector.jar" Dim DownloadInfo As JObject = Nothing '获取下载信息 Try Log("[Minecraft] 开始获取 Authlib-Injector 下载信息") - DownloadInfo = GetJson(NetGetCodeByLoader({ + DownloadInfo = GetJson(NetRequestByClientRetry( "https://authlib-injector.yushi.moe/artifact/latest.json", - "https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json" - }, IsJson:=True)) + BackupUrl:="https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json", RequireJson:=True)) Catch ex As Exception - Log(ex, "获取 Authlib-Injector 下载信息失败") + If File.Exists(TargetFile) Then + Log(ex, "获取 Authlib-Injector 下载信息失败") + Else + Throw New Exception("获取 Authlib-Injector 下载信息失败", ex) + End If End Try '校验文件 If DownloadInfo IsNot Nothing Then @@ -2053,30 +2034,39 @@ OnLoaded: ''' ''' 将 McLibToken 列表转换为 NetFile。无需下载的文件会被自动过滤。 ''' - Public Function McLibFixFromLibToken(Libs As List(Of McLibToken), Optional CustomMcFolder As String = Nothing, Optional JumpLoaderFolder As String = Nothing) As List(Of NetFile) + Public Function McLibFixFromLibToken(Libs As List(Of McLibToken), Optional CustomMcFolder As String = Nothing) As List(Of NetFile) CustomMcFolder = If(CustomMcFolder, PathMcFolder) Dim Result As New List(Of NetFile) '获取 For Each Token As McLibToken In Libs '检查文件 - Dim Checker As New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1) + Dim Checker As FileChecker + If Token.Name.ContainsF("labymod") Then + Checker = New FileChecker '不检查 LabyMod 的文件,它们提供的文件校验信息是错的(#3225) + Else + Checker = New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1) + End If If Checker.Check(Token.LocalPath) Is Nothing Then Continue For '文件不符合,添加下载 Dim Urls As New List(Of String) - If Token.Url Is Nothing AndAlso Token.Name = "net.minecraftforge:forge:universal" Then - '特判修复 Forge 部分 universal 文件缺失 URL(#5455) - Token.Url = "https://maven.minecraftforge.net" & Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries", "").Replace("\", "/") + If Token.Url Is Nothing AndAlso Token.Name = "net.minecraftforge:forge:universal" Then '特判修复 Forge 部分 universal 文件缺失 URL(#5455) + Token.Url = "https://maven.minecraftforge.net" & Token.LocalPath.Replace(CustomMcFolder & "libraries", "").Replace("\", "/") End If If Token.Url IsNot Nothing Then '获取 URL 的真实地址 Urls.Add(Token.Url) If Token.Url.Contains("launcher.mojang.com/v1/objects") OrElse Token.Url.Contains("client.txt") OrElse Token.Url.Contains(".tsrg") Then - Urls.AddRange(DlSourceLauncherOrMetaGet(Token.Url).ToList()) 'Mappings(#4425) + Urls.AddRange(DlSourceLauncherOrMetaGet(Token.Url)) 'Mappings(#4425) End If If Token.Url.Contains("maven") Then - Urls.Insert(0, Token.Url.Replace(Mid(Token.Url, 1, Token.Url.IndexOfF("maven")), "https://bmclapi2.bangbang93.com/"). - Replace("maven.fabricmc.net", "maven").Replace("maven.minecraftforge.net", "maven").Replace("maven.neoforged.net/releases", "maven")) + Dim BmclapiUrl As String = + Token.Url.Replace(Mid(Token.Url, 1, Token.Url.IndexOfF("maven")), "https://bmclapi2.bangbang93.com/").Replace("maven.fabricmc.net", "maven").Replace("maven.minecraftforge.net", "maven").Replace("maven.neoforged.net/releases", "maven") + If DlSourcePreferMojang Then + Urls.Add(BmclapiUrl) '官方源优先 + Else + Urls.Insert(0, BmclapiUrl) '镜像源优先 + End If End If End If If Token.LocalPath.Contains("transformer-discovery-service") Then @@ -2086,13 +2076,13 @@ OnLoaded: Continue For ElseIf Token.LocalPath.Contains("optifine\OptiFine") Then 'OptiFine 主 Jar - Dim OptiFineBase As String = Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries\optifine\OptiFine\", "").Split("_")(0) & "/" & GetFileNameFromPath(Token.LocalPath).Replace("-", "_") + Dim OptiFineBase As String = Token.LocalPath.Replace(CustomMcFolder & "libraries\optifine\OptiFine\", "").Split("_")(0) & "/" & GetFileNameFromPath(Token.LocalPath).Replace("-", "_") OptiFineBase = "/maven/com/optifine/" & OptiFineBase If OptiFineBase.Contains("_pre") Then OptiFineBase = OptiFineBase.Replace("com/optifine/", "com/optifine/preview_") Urls.Add("https://bmclapi2.bangbang93.com" & OptiFineBase) ElseIf Urls.Count <= 2 Then '普通文件 - Urls.AddRange(DlSourceLibraryGet("https://libraries.minecraft.net" & Token.LocalPath.Replace(If(Token.IsJumpLoader, JumpLoaderFolder, CustomMcFolder) & "libraries", "").Replace("\", "/"))) + Urls.AddRange(DlSourceLibraryGet("https://libraries.minecraft.net" & Token.LocalPath.Replace(CustomMcFolder & "libraries", "").Replace("\", "/"))) End If Result.Add(New NetFile(Urls.Distinct, Token.LocalPath, Checker)) Next @@ -2231,16 +2221,16 @@ OnLoaded: Dim LocalPath As String If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToObject(Of Boolean) Then 'Remap - LocalPath = Version.PathIndie & "resources\" & File.Name.Replace("/", "\") + LocalPath = $"{Version.PathIndie}resources\{File.Name}" ElseIf Json("virtual") IsNot Nothing AndAlso Json("virtual").ToObject(Of Boolean) Then 'Virtual - LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\") + LocalPath = $"{PathMcFolder}assets\virtual\legacy\{File.Name}" Else '正常 - LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString + LocalPath = $"{PathMcFolder}assets\objects\{Left(File.Value("hash").ToString, 2)}\{File.Value("hash")}" End If Result.Add(New McAssetsToken With { - .LocalPath = LocalPath, + .LocalPath = LocalPath.Replace("/", "\"), .SourcePath = File.Name, .Hash = File.Value("hash").ToString, .Size = File.Value("size").ToString @@ -2275,7 +2265,7 @@ OnLoaded: If File.Exists AndAlso (Token.Size = 0 OrElse Token.Size = File.Length) AndAlso (Not CheckHash OrElse Token.Hash Is Nothing OrElse Token.Hash = GetFileSHA1(Token.LocalPath)) Then Continue For '文件不存在,添加下载 - Result.Add(New NetFile(DlSourceResourceGet("https://resources.download.minecraft.net/" & Left(Token.Hash, 2) & "/" & Token.Hash), Token.LocalPath, New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.Hash))) + Result.Add(New NetFile(DlSourceAssetsGet($"https://resources.download.minecraft.net/{Left(Token.Hash, 2)}/{Token.Hash}"), Token.LocalPath, New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.Hash))) Next Catch ex As Exception Log(ex, "获取版本缺失的资源文件下载列表失败") @@ -2302,7 +2292,7 @@ OnLoaded: End If Next '进行提示 - If Version Is Nothing Then Exit Sub + If Version Is Nothing Then Return Dim Time As Date = Version("releaseTime") Dim MsgBoxText As String = $"新版本:{VersionName}{vbCrLf}" & If((Date.Now - Time).TotalDays > 1, "更新时间:" & Time.ToString, "更新于:" & GetTimeSpanString(Time - Date.Now, False)) @@ -2400,26 +2390,45 @@ NextEntry: End Class ''' - ''' 为邮箱地址或手机号账号进行部分打码。 + ''' 判断版本名是否类似正式版。 ''' - Public Function AccountFilter(Account As String) As String - If Account.Contains("@") Then - '是邮箱 - Dim Splits = Account.Split("@") - 'If Splits(0).Count >= 6 Then - ' '前半部分至少 6 位,屏蔽后 4 位 - ' Return Mid(Splits(0), 1, Splits(0).Count - 4) & "****" & "@" & Splits(1) - 'Else - '前半部分不到 6 位,返回全 * - Return "".PadLeft(Splits(0).Count, "*") & "@" & Splits(1) - 'End If - ElseIf Account.Count >= 6 Then - '至少 6 位,屏蔽后 4 位 - Return Mid(Account, 1, Account.Count - 4) & "****" - Else - '不到 6 位,返回全 * - Return "".PadLeft(Account.Count, "*") + Public Function IsVersionNameLikeRelease(VerName As String) As Boolean + Return VerName.StartsWithF("1.") AndAlso Not (VerName.Contains("w") OrElse VerName.Contains("pre") OrElse VerName.Contains("rc") OrElse VerName.Contains("-")) + End Function + + ''' + ''' 打码字符串中的 AccessToken。 + ''' + Public Function FilterAccessToken(Raw As String, FilterChar As Char) As String + '打码 "accessToken " 后的内容 + If Raw.Contains("accessToken ") Then + For Each Token In RegexSearch(Raw, "(?<=accessToken ([^ ]{5}))[^ ]+(?=[^ ]{5})") + Raw = Raw.Replace(Token, New String(FilterChar, Token.Count)) + Next End If + '打码当前登录的结果 + Dim AccessToken As String = McLoginLoader.Output.AccessToken + If AccessToken IsNot Nothing AndAlso AccessToken.Length >= 10 AndAlso Raw.ContainsF(AccessToken, True) AndAlso + McLoginLoader.Output.Uuid <> McLoginLoader.Output.AccessToken Then 'UUID 和 AccessToken 一样则不打码 + Raw = Raw.Replace(AccessToken, Left(AccessToken, 5) & New String(FilterChar, AccessToken.Length - 10) & Right(AccessToken, 5)) + End If + Return Raw + End Function + ''' + ''' 打码字符串中的 Windows 用户名。 + ''' + Public Function FilterUserName(Raw As String, FilterChar As Char) As String + If Raw.Contains(":\Users\") Then + For Each Token In RegexSearch(Raw, "(?<=:\\Users\\)[^\\]+") + Raw = Raw.Replace("\Users\" & Token, "\Users\" & New String(FilterChar, Token.Count)) + Next + End If + If Raw.Contains(":/Users/") Then + For Each Token In RegexSearch(Raw, "(?<=:/Users/)[^/]+") + Raw = Raw.Replace("/Users/" & Token, "/Users/" & New String(FilterChar, Token.Count)) + Next + End If + Return Raw End Function End Module diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb index f20e352..764bafb 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb @@ -1,7 +1,7 @@ Imports System.IO.Compression Public Module ModMod - Private Const LocalModCacheVersion As Integer = 7 + Private Const LocalModCacheVersion As Integer = 8 Public Class McMod @@ -73,7 +73,7 @@ Public Module ModMod Get If _Name Is Nothing Then Load() If _Name Is Nothing Then _Name = _ModId - If _Name Is Nothing Then _Name = GetFileNameWithoutExtentionFromPath(Path) + If _Name Is Nothing Then _Name = IO.Path.GetFileNameWithoutExtension(Path) Return _Name End Get Set(value As String) @@ -188,9 +188,9 @@ Public Module ModMod Private _Dependencies As New Dictionary(Of String, String) Private Sub AddDependency(ModID As String, Optional VersionRequirement As String = Nothing) '确保信息正确 - If ModID Is Nothing OrElse ModID.Count < 2 Then Exit Sub + If ModID Is Nothing OrElse ModID.Count < 2 Then Return ModID = ModID.ToLower - If ModID = "name" OrElse Val(ModID).ToString = ModID Then Exit Sub '跳过 name 与纯数字 id + If ModID = "name" OrElse Val(ModID).ToString = ModID Then Return '跳过 name 与纯数字 id If VersionRequirement Is Nothing OrElse ((Not VersionRequirement.Contains(".")) AndAlso (Not VersionRequirement.Contains("-"))) OrElse VersionRequirement.Contains("$") Then VersionRequirement = Nothing Else @@ -279,7 +279,7 @@ Public Module ModMod ''' 进行文件可用性检查与 .class 以外的信息获取。 ''' Public Sub Load(Optional ForceReload As Boolean = False) - If IsLoaded AndAlso Not ForceReload Then Exit Sub + If IsLoaded AndAlso Not ForceReload Then Return '初始化 Init() Dim Jar As ZipArchive = Nothing @@ -688,7 +688,7 @@ Finished: ''' Public ReadOnly Property CanUpdate As Boolean Get - Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso ChangelogUrls.Any() + Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso Not Setup.Get("VersionAdvanceDisableModUpdate", Version:=PageVersionLeft.Version) AndAlso ChangelogUrls.Any() End Get End Property @@ -819,7 +819,7 @@ Finished: Try RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.Text = "正在更新 Mod") Do Until Not PageVersionMod.UpdatingVersions.Contains(Loader.Input) - If Loader.IsAborted Then Exit Sub + If Loader.IsAborted Then Return Thread.Sleep(100) Loop Finally @@ -837,7 +837,7 @@ Finished: '仅当 Forge 1.13- 且文件夹名与版本号相同时,才加载该子文件夹下的 Mod If Not (PageVersionLeft.Version IsNot Nothing AndAlso PageVersionLeft.Version.Version.HasForge AndAlso PageVersionLeft.Version.Version.McCodeMain < 13 AndAlso - File.Directory.Name = "1." & PageVersionLeft.Version.Version.McCodeMain & "." & PageVersionLeft.Version.Version.McCodeSub) Then + File.Directory.Name = $"1.{PageVersionLeft.Version.Version.McCodeMain}.{PageVersionLeft.Version.Version.McCodeSub}") Then Continue For End If End If @@ -872,7 +872,7 @@ Finished: Dim ModUpdateList As New List(Of McMod) For Each ModFile As FileInfo In ModFileList Loader.Progress += 0.94 / ModFileList.Count - If Loader.IsAborted Then Exit Sub + If Loader.IsAborted Then Return '加载 McMod 对象 Dim ModEntry As New McMod(ModFile.FullName) ModEntry.Load() @@ -912,7 +912,7 @@ Finished: End Function) '回设 - If Loader.IsAborted Then Exit Sub + If Loader.IsAborted Then Return Loader.Output = ModList '开始联网加载 @@ -939,18 +939,18 @@ Finished: '开始网络获取 Log($"[Mod] 目标加载器:{ModLoaders.Join("/")},版本:{McVersion}") Dim EndedThreadCount As Integer = 0, IsFailed As Boolean = False - Dim MainThread As Thread = Thread.CurrentThread + Dim CurrentTaskThread As Thread = Thread.CurrentThread '从 Modrinth 获取信息 RunInNewThread( Sub() Try '步骤 1:获取 Hash 与对应的工程 ID Dim ModrinthHashes = Mods.Select(Function(m) m.ModrinthHash).ToList() - Dim ModrinthVersion = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files", "POST", - $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject) + Dim ModrinthVersion As JObject = DlModRequest("https://api.modrinth.com/v2/version_files", HttpMethod.Post, + $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json") Log($"[Mod] 从 Modrinth 获取到 {ModrinthVersion.Count} 个本地 Mod 的对应信息") '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系 - If ModrinthVersion.Count = 0 Then Exit Sub + If ModrinthVersion.Count = 0 Then Return Dim ModrinthMapping As New Dictionary(Of String, List(Of McMod)) For Each Entry In Mods If Not ModrinthVersion.ContainsKey(Entry.ModrinthHash) Then Continue For @@ -963,13 +963,12 @@ Finished: Dim File As New CompFile(ModrinthVersion(Entry.ModrinthHash), CompType.Mod) If Entry.CompFile Is Nothing OrElse Entry.CompFile.ReleaseDate < File.ReleaseDate Then Entry.CompFile = File Next - If Loader.IsAbortedWithThread(MainThread) Then Exit Sub + If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return Log($"[Mod] 需要从 Modrinth 获取 {ModrinthMapping.Count} 个本地 Mod 的工程信息") '步骤 3:获取工程信息 - If Not ModrinthMapping.Any() Then Exit Sub - Dim ModrinthProject = CType(GetJson(DlModRequest( - $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]", - "GET", "", "application/json")), JArray) + If Not ModrinthMapping.Any() Then Return + Dim ModrinthProject As JArray = DlModRequest( + $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]") For Each ProjectJson In ModrinthProject Dim Project As New CompProject(ProjectJson) For Each Entry In ModrinthMapping(Project.Id) @@ -978,9 +977,9 @@ Finished: Next Log($"[Mod] 已从 Modrinth 获取本地 Mod 信息,继续获取更新信息") '步骤 4:获取更新信息 - Dim ModrinthUpdate = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files/update", "POST", + Dim ModrinthUpdate As JObject = DlModRequest("https://api.modrinth.com/v2/version_files/update", HttpMethod.Post, $"{{""hashes"": [""{ModrinthMapping.SelectMany(Function(l) l.Value.Select(Function(m) m.ModrinthHash)).Join(""",""")}""], ""algorithm"": ""sha1"", - ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json")), JObject) + ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json") For Each Entry In Mods If Not ModrinthUpdate.ContainsKey(Entry.ModrinthHash) OrElse Entry.CompFile Is Nothing Then Continue For Dim UpdateFile As New CompFile(ModrinthUpdate(Entry.ModrinthHash), CompType.Mod) @@ -1013,13 +1012,13 @@ Finished: Dim CurseForgeHashes As New List(Of UInteger) For Each Entry In Mods CurseForgeHashes.Add(Entry.CurseForgeHash) - If Loader.IsAbortedWithThread(MainThread) Then Exit Sub + If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return Next - Dim CurseForgeRaw = CType(CType(GetJson(DlModRequest("https://api.curseforge.com/v1/fingerprints/432", "POST", - $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer) + Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432", HttpMethod.Post, + $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")("data")("exactMatches") Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息") '步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系 - If Not CurseForgeRaw.Any() Then Exit Sub + If Not CurseForgeRaw.Any() Then Return Dim CurseForgeMapping As New Dictionary(Of Integer, List(Of McMod)) For Each Project In CurseForgeRaw Dim ProjectId = Project("id").ToString @@ -1034,12 +1033,12 @@ Finished: If Entry.CompFile Is Nothing OrElse Entry.CompFile.ReleaseDate < File.ReleaseDate Then Entry.CompFile = File Next Next - If Loader.IsAbortedWithThread(MainThread) Then Exit Sub + If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return Log($"[Mod] 需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息") '步骤 3:获取工程信息 - If Not CurseForgeMapping.Any() Then Exit Sub - Dim CurseForgeProject = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods", "POST", - $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("data") + If Not CurseForgeMapping.Any() Then Return + Dim CurseForgeProject = DlModRequest("https://api.curseforge.com/v1/mods", HttpMethod.Post, + $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")("data") Dim UpdateFileIds As New Dictionary(Of Integer, List(Of McMod)) 'FileId -> 本地 Mod 文件列表 Dim FileIdToProjectSlug As New Dictionary(Of Integer, String) For Each ProjectJson In CurseForgeProject @@ -1078,9 +1077,9 @@ Finished: Next Log($"[Mod] 已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息") '步骤 4:获取更新文件信息 - If Not UpdateFileIds.Any() Then Exit Sub - Dim CurseForgeFiles = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", - $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")), JObject)("data") + If Not UpdateFileIds.Any() Then Return + Dim CurseForgeFiles = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, + $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")("data") Dim UpdateFiles As New Dictionary(Of McMod, CompFile) For Each FileJson In CurseForgeFiles Dim File As New CompFile(FileJson, CompType.Mod) @@ -1114,20 +1113,25 @@ Finished: End Sub, "Mod List Detail Loader CurseForge") '等待线程结束 Do Until EndedThreadCount = 2 - If Loader.IsAborted Then Exit Sub Thread.Sleep(10) + If Loader.IsAborted Then Return Loop '保存缓存 Mods = Mods.Where(Function(m) m.Comp IsNot Nothing).ToList() Log($"[Mod] 联网获取本地 Mod 信息完成,为 {Mods.Count} 个 Mod 更新缓存") - If Not Mods.Any() Then Exit Sub + If Not Mods.Any() Then Return For Each Entry In Mods Entry.CompLoaded = Not IsFailed Cache(Entry.ModrinthHash & McVersion & ModLoaders.Join("")) = Entry.ToJson() Next WriteFile(PathTemp & "Cache\LocalMod.json", Cache.ToString(If(ModeDebug, Newtonsoft.Json.Formatting.Indented, Newtonsoft.Json.Formatting.None))) '刷新边栏 - RunInUi(Sub() FrmVersionMod?.RefreshBars()) + If Loader.IsAborted Then Return + If FrmVersionMod?.Filter = PageVersionMod.FilterType.CanUpdate Then + RunInUi(Sub() FrmVersionMod?.RefreshUI()) '同步 “可更新” 列表 (#4677) + Else + RunInUi(Sub() FrmVersionMod?.RefreshBars()) + End If End Sub Public Function GetTargetModLoaders() As List(Of CompModLoaderType) Dim ModLoaders As New List(Of CompModLoaderType) @@ -1209,7 +1213,7 @@ Finished: If ReqVersionHead = ReqVersionTail Then VersionRequire = "应为 " & ReqVersionHead ElseIf ReqVersionHead.Contains(".") AndAlso ReqVersionTail.Contains(".") Then - VersionRequire = "应为 " & ReqVersionHead & " 至 " & ReqVersionTail + VersionRequire = $"应为 {ReqVersionHead} 至 {ReqVersionTail}" ElseIf ReqVersionHead.Contains(".") Then If ReqVersionHeadCanEqual Then VersionRequire = "最低应为 " & ReqVersionHead @@ -1227,7 +1231,7 @@ Finished: End If '检查前置 Mod 是否存在,并获取其版本 If Not CurrentDependencies.ContainsKey(ReqId) Then - Result.Add("缺少前置 Mod:" & ReqId & If(VersionRequire = "", "", ",其版本" & VersionRequire) & "。" & vbCrLf & " - " & ModEntity.FileName) + Result.Add($"缺少前置 Mod:{ReqId}{If(VersionRequire = "", "", ",其版本" & VersionRequire)}。{vbCrLf} - {ModEntity.FileName}") Continue For End If Dim CurrentVersion As String = If(CurrentDependencies(ReqId)(0), "0.0") @@ -1236,7 +1240,7 @@ Finished: If ReqVersionHead.Contains(".") Then If VersionSortInteger(ReqVersionHead, CurrentVersion) > If(ReqVersionHeadCanEqual, 0, -1) Then Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过低,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf & - " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), "")) + " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", $"{vbCrLf} - 前置:{CurrentDependencies(ReqId)(1)}", "")) Continue For End If End If @@ -1244,26 +1248,26 @@ Finished: If ReqVersionTail.Contains(".") Then If VersionSortInteger(CurrentVersion, ReqVersionTail) > If(ReqVersionTailCanEqual, 0, -1) Then Result.Add(ReqId.Substring(0, 1).ToUpper & ReqId.Substring(1) & " 版本过高,其版本" & VersionRequire & ",而当前版本为 " & CurrentVersion & "。" & vbCrLf & - " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", vbCrLf & " - 前置:" & CurrentDependencies(ReqId)(1), "")) + " - " & ModEntity.FileName & If(ReqId <> "minecraft" AndAlso ReqId <> "forge", $"{vbCrLf} - 前置:{CurrentDependencies(ReqId)(1)}", "")) Continue For End If End If Else If Not CurrentDependencies.ContainsKey(Dependency.Key) Then - Result.Add("缺少前置 Mod:" & Dependency.Key & "。" & vbCrLf & " - " & ModEntity.FileName) + Result.Add($"缺少前置 Mod:{Dependency.Key}。{vbCrLf} - {ModEntity.FileName}") Continue For End If End If Next Catch ex As Exception - Result.Add("检查 Mod 时出错:" & GetExceptionSummary(ex) & vbCrLf & " - " & ModEntity.FileName) + Result.Add($"检查 Mod 时出错:{ex.GetBrief()}{vbCrLf} - {ModEntity.FileName}") Log(ex, "检查 Mod 时出错") End Try Next If Not Result.Any() Then Log("[Minecraft] Mod 检查未发现异常") Else - Log("[Minecraft] Mod 检查异常结果:" & vbCrLf & Join(Result, vbCrLf)) + Log($"[Minecraft] Mod 检查异常结果:{vbCrLf}{Join(Result, vbCrLf)}") End If Return Result End Function diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb index 1d8dc13..a2d7783 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb @@ -8,7 +8,7 @@ Public Module ModModpack ''' Public Sub ModpackInstall() Dim File As String = SelectFile("整合包文件(*.rar;*.zip;*.mrpack)|*.rar;*.zip;*.mrpack", "选择整合包压缩文件") '选择整合包文件 - If String.IsNullOrEmpty(File) Then Exit Sub + If String.IsNullOrEmpty(File) Then Return RunInThread( Sub() Try @@ -72,7 +72,7 @@ Public Module ModModpack If FullNames(1) = "modpack.zip" OrElse FullNames(1) = "modpack.mrpack" Then PackType = 9 : Exit Try '带启动器的压缩包 Next Catch ex As Exception - If GetExceptionDetail(ex, True).Contains("Error.WinIOError") Then + If ex.GetDetail(True).Contains("Error.WinIOError") Then Throw New Exception("打开整合包文件失败", ex) ElseIf File.EndsWithF(".rar", True) Then Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex) @@ -109,30 +109,71 @@ Public Module ModModpack End Try End Function - Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase, LoaderProgressDelta As Double) + Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase, ProgressIncrement As Double) '解压文件 Dim RetryCount As Integer = 1 Dim Encode = Encoding.GetEncoding("GB18030") + Dim InitialProgress = Loader.Progress Try Retry: - '完全不知道为啥会出现文件正在被另一进程使用的问题,总之多试试 + Loader.Progress = InitialProgress DeleteDirectory(InstallTemp) - ExtractFile(FileAddress, InstallTemp, Encode, ProgressIncrementHandler:=Sub(Delta) Loader.Progress += Delta * LoaderProgressDelta) + ExtractFile(FileAddress, InstallTemp, Encode, ProgressIncrementHandler:=Sub(Delta) Loader.Progress += Delta * ProgressIncrement) Catch ex As Exception Log(ex, "第 " & RetryCount & " 次解压尝试失败") If TypeOf ex Is ArgumentException Then Encode = Encoding.UTF8 Log("[ModPack] 已切换压缩包解压编码为 UTF8") End If + '完全不知道为啥会出现文件正在被另一进程使用的问题,总之加个重试 If RetryCount < 5 Then Thread.Sleep(RetryCount * 2000) - If Loader IsNot Nothing AndAlso Loader.LoadingState <> MyLoading.MyLoadingState.Run Then Exit Sub + If Loader IsNot Nothing AndAlso Loader.LoadingState <> MyLoading.MyLoadingState.Run Then Return RetryCount += 1 GoTo Retry Else Throw New Exception("解压整合包文件失败", ex) End If End Try + Loader.Progress = InitialProgress + ProgressIncrement + End Sub + ''' + ''' 从整合包的 override 目录复制文件,同时设置 PCL 的配置文件与版本隔离。 + ''' 对路径末尾是否为 \ 没有要求。 + ''' + Private Sub CopyOverrideDirectory(OverridesFolder As String, VersionFolder As String, Loader As LoaderBase, ProgressIncrement As Double) + If Not OverridesFolder.EndsWithF("\") Then OverridesFolder += "\" + If Not VersionFolder.EndsWithF("\") Then VersionFolder += "\" + '复制文件 + If Directory.Exists(OverridesFolder) Then + Log($"[ModPack] 处理整合包覆写文件夹:{OverridesFolder} → {VersionFolder}") + CopyDirectory(OverridesFolder, VersionFolder, Sub(Delta) Loader.Progress += Delta * ProgressIncrement) + Else + Log($"[ModPack] 整合包中没有覆写文件夹:{OverridesFolder}") + Loader.Progress += ProgressIncrement + End If + '设置 ini + Dim OverridesIni As String = $"{OverridesFolder}PCL\Setup.ini" + Dim VersionIni As String = $"{VersionFolder}PCL\Setup.ini" + If File.Exists(OverridesIni) Then + WriteIni(OverridesIni, "VersionArgumentIndie", 1) '开启版本隔离 + WriteIni(OverridesIni, "VersionArgumentIndieV2", True) + WriteIni(OverridesIni, "IsStar", False) + CopyFile(OverridesIni, VersionIni) '覆写已有的 ini + Else + WriteIni(VersionIni, "VersionArgumentIndie", 1) '开启版本隔离 + WriteIni(VersionIni, "VersionArgumentIndieV2", True) + End If + IniClearCache(VersionIni) '重置缓存,避免被安装过程中写入的 ini 覆盖 + End Sub + ''' + ''' 弹窗提示整合包中存在不兼容的加载器。 + ''' 如果取消,则抛出 CancelledException。 + ''' + ''' + Private Sub NotifyIncompatibleLoader(LoaderName As String) + If MyMsgBox($"整合包中存在不兼容的加载器:{LoaderName}{vbCrLf}如果你知道如何手动安装它,可以先选择跳过,然后在整合包下载结束后手动安装。", + "不兼容的加载器", "取消", $"不安装 {LoaderName} 并继续") = 1 Then Throw New CancelledException End Sub #Region "不同类型整合包的安装方法" @@ -179,10 +220,11 @@ Retry: Log("[ModPack] 整合包 Fabric 版本:" & Id) FabricVersion = Id.Replace("fabric-", "") Else - Log("[ModPack] 未知 Mod 加载器:" & Id) + 'ElseIf Id.StartsWithF("quilt-") Then + NotifyIncompatibleLoader(Id) End If Next - '解压与配置文件 + '解压 Dim InstallTemp As String = RequestTaskTempFolder() Dim InstallLoaders As New List(Of LoaderBase) Dim OverrideHome As String = If(Json("overrides"), "") @@ -190,21 +232,10 @@ Retry: InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6) - Task.Progress = 0.6 - '复制结果 - Log("[ModPack] 整合包 override 目标:" & OverrideHome) - Dim OverrideRoot = InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome) '#5613 - If Directory.Exists(OverrideRoot) Then - Dim CopyTarget = $"{PathMcFolder}versions\{VersionName}" - CopyDirectory(OverrideRoot, CopyTarget, Sub(Delta) Task.Progress += Delta * 0.35) - Log($"[ModPack] 整合包 override 复制:{OverrideRoot} -> {CopyTarget}") - Else - Log($"[ModPack] 整合包中未找到 override 文件夹:{OverrideRoot}") - End If - Task.Progress = 0.95 - '开启版本隔离 - WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndie", 1) - WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndieV2", True) + CopyOverrideDirectory( + InstallTemp & ArchiveBaseFolder & If(OverrideHome = "." OrElse OverrideHome = "./", "", OverrideHome), '#5613 + $"{PathMcFolder}versions\{VersionName}", + Task, 0.4) End Sub) With { .ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s End If @@ -224,7 +255,7 @@ Retry: '获取 Mod 下载信息 ModDownloadLoaders.Add(New LoaderTask(Of Integer, JArray)("获取 Mod 下载信息", Sub(Task As LoaderTask(Of Integer, JArray)) - Task.Output = GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(ModList, ",") & "]}", "application/json"))("data") + Task.Output = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(ModList, ",") & "]}", "application/json")("data") '如果文件已被删除,则 API 会跳过那一项 If ModList.Count > Task.Output.Count Then Throw New Exception("整合包中的部分 Mod 版本已被 Mod 作者删除,所以没法继续安装了,请向整合包作者反馈该问题") End Sub) With {.ProgressWeight = ModList.Count / 10}) '每 10 Mod 需要 1s @@ -238,29 +269,28 @@ Retry: If FileList.ContainsKey(Id) Then Continue For '可选 Mod 提示 If ModOptionalList.Contains(Id) Then - If MyMsgBox("是否要下载整合包中的可选文件 " & ModJson("displayName").ToString & "?", - "下载可选文件", "是", "否") = 2 Then + If MyMsgBox("是否要下载整合包中的可选文件 " & ModJson("displayName").ToString & "?", "下载可选文件", "是", "否") = 2 Then Continue For End If End If - '建立 CompFile - Dim File As New CompFile(ModJson, CompType.Mod) - If Not File.Available Then Continue For '根据 modules 和文件名后缀判断资源类型 - Dim TargetFolder As String + Dim TargetFolder As String, Type As CompType If ModJson("modules").Any Then 'modules 可能返回 null(#1006) Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList If ModuleNames.Contains("META-INF") OrElse ModuleNames.Contains("mcmod.info") OrElse - File.FileName.EndsWithF(".jar", True) Then - TargetFolder = "mods" + ModJson?("FileName")?.ToString?.EndsWithF(".jar", True) Then + TargetFolder = "mods" : Type = CompType.Mod ElseIf ModuleNames.Contains("pack.mcmeta") Then - TargetFolder = "resourcepacks" + TargetFolder = "resourcepacks" : Type = CompType.ResourcePack Else - TargetFolder = "shaderpacks" + TargetFolder = "shaderpacks" : Type = CompType.Shader End If Else - TargetFolder = "mods" + TargetFolder = "mods" : Type = CompType.Mod End If + '建立 CompFile + Dim File As New CompFile(ModJson, Type) + If Not File.Available Then Continue For '实际的添加 FileList.Add(Id, File.ToNetFile($"{PathMcFolder}versions\{VersionName}\{TargetFolder}\")) Task.Progress += 1 / (1 + ModList.Count) @@ -283,16 +313,11 @@ Retry: .NeoForgeVersion = NeoForgeVersion, .FabricVersion = FabricVersion } - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True) - '构造 Libraries 加载器 - Dim LoadersLib As New List(Of LoaderBase) - LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False}) - LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False}) + Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) '构造总加载器 Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}) - Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}) Loaders.Add(New LoaderTask(Of String, String)("最终整理文件", Sub(Task As LoaderTask(Of String, String)) '设置图标 @@ -310,7 +335,7 @@ Retry: File.Delete(Target) End If Next - If File.Exists(FileAddress) AndAlso GetFileNameWithoutExtentionFromPath(FileAddress) = "modpack" Then + If File.Exists(FileAddress) AndAlso IO.Path.GetFileNameWithoutExtension(FileAddress) = "modpack" Then Log("[ModPack] 删除安装整合包文件:" & FileAddress) File.Delete(FileAddress) End If @@ -361,12 +386,9 @@ Retry: Case "fabric-loader" 'eg. 0.14.14 FabricVersion = Entry.Value.ToString Log("[ModPack] 整合包 Fabric 版本:" & FabricVersion) - Case "quilt-loader" 'eg. 1.0.0 - Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical) - Throw New CancelledException Case Else - Hint($"无法安装整合包,其中出现了未知的 Mod 加载器 {Entry.Value}!", HintType.Critical) - Throw New CancelledException + 'Case "quilt-loader" 'eg. 1.0.0 + NotifyIncompatibleLoader(Entry.Name) End Select Next '获取版本名 @@ -377,29 +399,20 @@ Retry: If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException End If - '解压和配置文件 + '解压 Dim InstallTemp As String = RequestTaskTempFolder() Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) - ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6) - Task.Progress = 0.6 - '复制 overrides 文件夹和 client-overrides 文件夹 - If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then - CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName, - Sub(Delta) Task.Progress += Delta * 0.25) - Else - Log("[ModPack] 整合包中未找到 override 目录,已跳过") - End If - Task.Progress = 0.85 - If Directory.Exists(InstallTemp & ArchiveBaseFolder & "client-overrides") Then - CopyDirectory(InstallTemp & ArchiveBaseFolder & "client-overrides", PathMcFolder & "versions\" & VersionName, - Sub(Delta) Task.Progress += Delta * 0.1) - End If - Task.Progress = 0.95 - '开启版本隔离 - WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndie", 1) - WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndieV2", True) + ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.5) + CopyOverrideDirectory( + InstallTemp & ArchiveBaseFolder & "overrides", + PathMcFolder & "versions\" & VersionName, + Task, 0.4) + CopyOverrideDirectory( + InstallTemp & ArchiveBaseFolder & "client-overrides", + PathMcFolder & "versions\" & VersionName, + Task, 0.1) End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s '获取下载文件列表 Dim FileList As New List(Of NetFile) @@ -417,10 +430,15 @@ Retry: End Select End If '添加下载文件 - Dim Urls = File("downloads").Select(Function(t) t.ToString.Replace("://edge.forgecdn", "://media.forgecdn")).ToList + Dim Urls = File("downloads").SelectMany(Function(t) CompFile.HandleCurseForgeDownloadUrls(t.ToString)).ToList Urls.AddRange(Urls.Select(Function(u) DlSourceModGet(u)).ToList) Urls = Urls.Distinct.ToList() - FileList.Add(New NetFile(Urls, PathMcFolder & "versions\" & VersionName & "\" & File("path").ToString, + Dim TargetPath As String = $"{PathMcFolder}versions\{VersionName}\{File("path")}" + If Not IO.Path.GetFullPath(TargetPath).StartsWithF($"{PathMcFolder}versions\{VersionName}\", True) Then + MyMsgBox($"整合包的文件路径超出了版本文件夹,请向整合包作者反馈此问题!" & vbCrLf & "错误的文件:" & TargetPath, "文件路径校验失败", IsWarn:=True) + Throw New CancelledException + End If + FileList.Add(New NetFile(Urls, TargetPath, New FileChecker(ActualSize:=File("fileSize").ToObject(Of Long), Hash:=File("hashes")("sha1").ToString), True)) Next If FileList.Any Then @@ -436,20 +454,15 @@ Retry: .NeoForgeVersion = NeoForgeVersion, .FabricVersion = FabricVersion } - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True) - '构造 Libraries 加载器 - Dim LoadersLib As New List(Of LoaderBase) - LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False}) - LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False}) + Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) '构造总加载器 Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}) - Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}) Loaders.Add(New LoaderTask(Of String, String)("最终整理文件", Sub(Task As LoaderTask(Of String, String)) '设置图标 - Dim VersionFolder As String = PathMcFolder & "versions\" & VersionName & "\" + Dim VersionFolder As String = $"{PathMcFolder}versions\{VersionName}\" If Logo IsNot Nothing AndAlso File.Exists(Logo) Then File.Copy(Logo, VersionFolder & "PCL\Logo.png", True) WriteIni(VersionFolder & "PCL\Setup.ini", "Logo", "PCL\Logo.png") @@ -463,7 +476,7 @@ Retry: File.Delete(Target) End If Next - If File.Exists(FileAddress) AndAlso GetFileNameWithoutExtentionFromPath(FileAddress) = "modpack" Then + If File.Exists(FileAddress) AndAlso IO.Path.GetFileNameWithoutExtension(FileAddress) = "modpack" Then Log("[ModPack] 删除安装整合包文件:" & FileAddress) File.Delete(FileAddress) End If @@ -500,67 +513,38 @@ Retry: If Validate.Validate(VersionName) <> "" Then VersionName = "" If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException - '解压与配置文件 + '解压 Dim InstallTemp As String = RequestTaskTempFolder() Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6) - Task.Progress = 0.6 - '复制结果 - If Directory.Exists(InstallTemp & ArchiveBaseFolder & "minecraft") Then - CopyDirectory(InstallTemp & ArchiveBaseFolder & "minecraft", PathMcFolder & "versions\" & VersionName, Sub(Delta) Task.Progress += Delta * 0.35) - Else - Log("[ModPack] 整合包中未找到 minecraft override 目录,已跳过") - End If - Task.Progress = 0.95 - '开启版本隔离 - WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndie", 1) - WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndieV2", True) + CopyOverrideDirectory( + InstallTemp & ArchiveBaseFolder & "minecraft", + PathMcFolder & "versions\" & VersionName, + Task, 0.4) End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s - '构造加载器 + '构造游戏本体安装加载器 If Json("gameVersion") Is Nothing Then Throw New Exception("该 HMCL 整合包未提供游戏版本信息,无法安装!") Dim Request As New McInstallRequest With { .TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\", .MinecraftName = Json("gameVersion").ToString } - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True) - '构造 Libraries 加载器(为了使得 Mods 下载结束后再构造,这样才会下载 JumpLoader 文件) - Dim LoadersLib As New List(Of LoaderBase) - LoadersLib.Add(New LoaderTask(Of String, String)("重命名版本 Json(副加载器)", - Sub() - Dim RealFileName As String = PathMcFolder & "versions\" & VersionName & "\" & VersionName & ".json" - Dim OldFileName As String = PathMcFolder & "versions\" & VersionName & "\pack.json" - If File.Exists(OldFileName) Then - '修改 id - Dim FileJson = GetJson(ReadFile(OldFileName)) - FileJson("id") = VersionName - '替换文件 - File.Delete(OldFileName) - WriteFile(RealFileName, FileJson.ToString) - Log("[ModPack] 已重命名版本 Json:" & RealFileName) - End If - End Sub) With {.ProgressWeight = 0.1, .Show = False}) - LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False}) - LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False}) + Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) '构造总加载器 Dim Loaders As New List(Of LoaderBase) From { - New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .Block = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}, - New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}, - New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8} + New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}, + New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)} } - '重复任务检查 Dim LoaderName As String = "HMCL 整合包安装:" & VersionName & " " If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then Hint("该整合包正在安装中!", HintType.Critical) Throw New CancelledException End If - '启动 Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf McInstallState} - 'If Archive IsNot Nothing Then Archive.Dispose() '解除占用,以免在加载器中触发 “正由另一进程使用,因此该进程无法访问此文件” Loader.Start(Request.TargetVersionFolder) LoaderTaskbarAdd(Loader) FrmMain.BtnExtraDownload.ShowRefresh() @@ -584,24 +568,17 @@ Retry: If Validate.Validate(VersionName) <> "" Then VersionName = "" If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException - '解压、配置设置文件 + '解压 Dim InstallTemp As String = RequestTaskTempFolder() Dim SetupFile As String = $"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini" Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) - ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6) - Task.Progress = 0.6 - '复制结果 - If Directory.Exists(InstallTemp & ArchiveBaseFolder & ".minecraft") Then - CopyDirectory(InstallTemp & ArchiveBaseFolder & ".minecraft", PathMcFolder & "versions\" & VersionName, Sub(Delta) Task.Progress += Delta * 0.35) - Else - Log("[ModPack] 整合包中未找到 override .minecraft 目录,已跳过") - End If - Task.Progress = 0.95 - '开启版本隔离 - WriteIni(SetupFile, "VersionArgumentIndie", 1) - WriteIni(SetupFile, "VersionArgumentIndieV2", True) + ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.55) + CopyOverrideDirectory( + InstallTemp & ArchiveBaseFolder & ".minecraft", + PathMcFolder & "versions\" & VersionName, + Task, 0.4) '读取 MMC 设置文件(#2655) Try Dim MMCSetupFile As String = InstallTemp & ArchiveBaseFolder & "instance.cfg" @@ -663,7 +640,8 @@ Retry: If PackJson("components") Is Nothing Then Throw New Exception("该 MMC 整合包未提供游戏版本信息,无法安装!") Dim Request As New McInstallRequest With {.TargetVersionName = VersionName, .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\"} For Each Component In PackJson("components") - Select Case If(Component("uid"), "").ToString + If Not Component.Contains("uid") Then Continue For + Select Case Component("uid").ToString Case "org.lwjgl" Log("[ModPack] 已跳过 LWJGL 项") Case "net.minecraft" @@ -674,22 +652,17 @@ Retry: Request.NeoForgeVersion = Component("version") Case "net.fabricmc.fabric-loader" Request.FabricVersion = Component("version") - Case "org.quiltmc.quilt-loader" 'eg. 1.0.0 - Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical) - Throw New CancelledException + Case Else + 'Case "org.quiltmc.quilt-loader" 'eg. 1.0.0 + NotifyIncompatibleLoader(Component("uid")) End Select Next '构造加载器 - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True) - '构造 Libraries 加载器 - Dim LoadersLib As New List(Of LoaderBase) - LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False}) - LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False}) + Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) '构造总加载器 Dim Loaders As New List(Of LoaderBase) - Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .Block = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}) - Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) - Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}) + Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) + Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}) '重复任务检查 Dim LoaderName As String = "MMC 整合包安装:" & VersionName & " " @@ -726,24 +699,23 @@ Retry: If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate}) If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException End If - '解压与配置文件 + '解压 Dim InstallTemp As String = RequestTaskTempFolder() + Dim SetupFile As String = $"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini" Dim InstallLoaders As New List(Of LoaderBase) InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件", Sub(Task As LoaderTask(Of String, Integer)) ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6) - Task.Progress = 0.6 - '复制结果 - If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then - CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName, - Sub(Delta) Task.Progress += 0.35 * Delta) - Else - Log("[ModPack] 整合包中未找到 overrides 目录,已跳过") + CopyOverrideDirectory( + InstallTemp & ArchiveBaseFolder & "overrides", + PathMcFolder & "versions\" & VersionName, + Task, 0.4) + 'JVM 参数 + If Json("launchInfo") IsNot Nothing Then + Dim LaunchInfo As JObject = Json("launchInfo") + If LaunchInfo.ContainsKey("javaArgument") Then WriteIni(SetupFile, "VersionAdvanceJvm", String.Join(" ", LaunchInfo("javaArgument"))) + If LaunchInfo.ContainsKey("launchArgument") Then WriteIni(SetupFile, "VersionAdvanceGame", String.Join(" ", LaunchInfo("launchArgument"))) End If - Task.Progress = 0.95 - '开启版本隔离 - WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndie", 1) - WriteIni($"{PathMcFolder}versions\{VersionName}\PCL\Setup.ini", "VersionArgumentIndieV2", True) End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s '构造加载器 If Json("addons") Is Nothing Then Throw New Exception("该 MCBBS 整合包未提供游戏版本附加信息,无法安装!") @@ -765,16 +737,11 @@ Retry: .NeoForgeVersion = If(Addons.ContainsKey("neoforge"), Addons("neoforge"), Nothing), .FabricVersion = If(Addons.ContainsKey("fabric"), Addons("fabric"), Nothing) } - Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True) - '构造 Libraries 加载器 - Dim LoadersLib As New List(Of LoaderBase) - LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionName))) With {.ProgressWeight = 1, .Show = False}) - LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False}) + Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request) '构造总加载器 Dim Loaders As New List(Of LoaderBase) - Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .Block = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}) - Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) - Loaders.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}) + Loaders.Add(New LoaderCombo(Of String)("整合包安装", InstallLoaders) With {.Show = False, .Block = False, .ProgressWeight = InstallLoaders.Sum(Function(l) l.ProgressWeight)}) + Loaders.Add(New LoaderCombo(Of String)("游戏安装", MergeLoaders) With {.Show = False, .ProgressWeight = MergeLoaders.Sum(Function(l) l.ProgressWeight)}) '重复任务检查 Dim LoaderName As String = "MCBBS 整合包安装:" & VersionName & " " @@ -828,7 +795,7 @@ Retry: Log("[Modpack] 找到压缩包中附带的启动器:" & Launcher) If MyMsgBox($"整合包里似乎自带了启动器,是否换用它继续安装?{vbCrLf}即将打开:{Launcher}", "换用整合包启动器?", "换用", "不换用") = 1 Then OpenExplorer(TargetFolder) - ShellOnly(Launcher, "--wait") '要求等待已有的 PCL 退出 + StartProcess(Launcher, "--wait") '要求等待已有的 PCL 退出 Log("[Modpack] 为换用整合包中的启动器启动,强制结束程序") FrmMain.EndProgram(False) Return diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb index ce900ea..c4f08a4 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb +++ b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb @@ -15,7 +15,7 @@ TriggerLauncherShutdown = False End If Next - If IsWatcherRunning = IsRunning Then Exit Sub + If IsWatcherRunning = IsRunning Then Return IsWatcherRunning = IsRunning If IsWatcherRunning Then MinecraftStart() @@ -81,7 +81,7 @@ WatcherStateChanged() '初始化进程与日志读取 - Me.GameProcess = Loader.Input + GameProcess = Loader.Input GameProcess.BeginOutputReadLine() GameProcess.BeginErrorReadLine() AddHandler GameProcess.OutputDataReceived, AddressOf LogReceived @@ -118,7 +118,7 @@ Return _State End Get Set(value As MinecraftState) - If _State = value Then Exit Property + If _State = value Then Return _State = value WatcherStateChanged() End Set @@ -144,7 +144,7 @@ '输出文本 Dim Copyed As New List(Of String) SyncLock WaitingLogLock - If Not WaitingLog.Any() Then Exit Sub + If Not WaitingLog.Any() Then Return Copyed = WaitingLog WaitingLog = New List(Of String)(1000) End SyncLock @@ -180,8 +180,9 @@ Public LatestLog As New Queue(Of String) Private Sub GameLog(Text As String) '预处理 - If Text Is Nothing Then Exit Sub + If Text Is Nothing Then Return Text = Text.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, vbCrLf) + 'If Text.Contains("�����") Then Hint("检测到错误的日志编码:" & Text) '加入预存储 LatestLog.Enqueue(Text) If LatestLog.Count >= 501 Then LatestLog.Dequeue() @@ -256,8 +257,8 @@ Private WindowHandle As IntPtr Private Sub TimerWindow() Try - If GameProcess.HasExited Then Exit Sub - If IsWindowFinished Then Exit Sub + If GameProcess.HasExited Then Return + If IsWindowFinished Then Return '获取全部窗口,检查是否有新增的 Dim MinecraftWindow As KeyValuePair(Of IntPtr, String)? = Nothing Try @@ -267,10 +268,10 @@ Log(ex, "由于反作弊或安全软件拦截,PCL 无法操作游戏窗口", LogLevel.Hint) IsWindowFinished = True End Try - If MinecraftWindow Is Nothing Then Exit Sub + If MinecraftWindow Is Nothing Then Return Dim MinecraftWindowName = MinecraftWindow.Value.Value, MinecraftWindowHandle = MinecraftWindow.Value.Key '已找到窗口 - If Not MinecraftWindowName.StartsWithF("FML") Then + If Not MinecraftWindowName.StartsWithF("FML") AndAlso Not MinecraftWindowName.StartsWithF("Quilt Loader") Then '已找到 Minecraft 窗口 WindowHandle = MinecraftWindowHandle WatcherLog($"Minecraft 窗口已加载:{MinecraftWindowName}({MinecraftWindowHandle.ToInt64})") @@ -291,8 +292,8 @@ End Sub, "MinecraftWindowMaximize") End If ElseIf Not IsWindowAppeared Then - '已找到 FML 窗口 - WatcherLog("FML 窗口已加载:" & MinecraftWindowName & "(" & MinecraftWindowHandle.ToInt64 & ")") + '已找到 Mod Loader 窗口 + WatcherLog("Mod Loader 窗口已加载:" & MinecraftWindowName & "(" & MinecraftWindowHandle.ToInt64 & ")") End If IsWindowAppeared = True Catch ex As Exception @@ -307,27 +308,27 @@ TryGetMinecraftWindow = Nothing EnumWindows( Sub(hwnd As IntPtr, lParam As Integer) - If TryGetMinecraftWindow IsNot Nothing Then Exit Sub + If TryGetMinecraftWindow IsNot Nothing Then Return '检查类名 Dim str As New StringBuilder(512) GetClassName(hwnd, str, str.Capacity) Dim ClassName As String = str.ToString - If Not (ClassName = "GLFW30" OrElse ClassName = "LWJGL" OrElse ClassName = "SunAwtFrame") Then Exit Sub + If Not (ClassName = "GLFW30" OrElse ClassName = "LWJGL" OrElse ClassName = "SunAwtFrame") Then Return '获取窗口标题名 str = New StringBuilder(512) GetWindowText(hwnd, str, str.Capacity) Dim WindowText As String = str.ToString '有的 Mod 可以修改窗口标题,所以不能检测是否为 Minecraft 打头,这并不准确 '部分版本会搞个 GLFW message window 出来所以得反选 - If Not (WindowText.StartsWithF("FML") OrElse (WindowText <> "PopupMessageWindow") AndAlso Not WindowText.StartsWithF("GLFW")) Then Exit Sub + If Not (WindowText.StartsWithF("FML") OrElse (WindowText <> "PopupMessageWindow") AndAlso Not WindowText.StartsWithF("GLFW")) Then Return '获取窗口关联的进程 Dim ProcessId As Integer GetWindowThreadProcessId(hwnd, ProcessId) Try - If Process.GetProcessById(ProcessId).StartTime < GameProcess.StartTime Then Exit Sub '需要是此后启动的进程 + If Process.GetProcessById(ProcessId).StartTime < GameProcess.StartTime Then Return '需要是此后启动的进程 Catch ex As Exception Log(ex, "枚举 Minecraft 窗口进程失败") - Exit Sub + Return End Try '返回 TryGetMinecraftWindow = New KeyValuePair(Of IntPtr, String)(hwnd, WindowText) @@ -344,18 +345,18 @@ '崩溃处理 Private Sub Crashed() - If State = MinecraftState.Crashed OrElse State = MinecraftState.Ended Then Exit Sub + If State = MinecraftState.Crashed OrElse State = MinecraftState.Ended Then Return State = MinecraftState.Crashed '崩溃分析 WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析") - Hint("检测到 Minecraft 出现错误,错误分析已开始……") + Hint("检测到 Minecraft 出现错误,错误分析已开始……", HintType.Critical) FeedbackInfo() RunInNewThread( Sub() Try Thread.Sleep(2000) WatcherLog("崩溃分析开始") - Dim Analyzer As New CrashAnalyzer(PID) + Dim Analyzer As New CrashAnalyzer(Version) Analyzer.Collect(Version.PathIndie, LatestLog.ToList) Analyzer.Prepare() Analyzer.Analyze(Version) diff --git a/Plain Craft Launcher 2/Modules/ModEvent.vb b/Plain Craft Launcher 2/Modules/ModEvent.vb index 491bfc1..615fcf7 100644 --- a/Plain Craft Launcher 2/Modules/ModEvent.vb +++ b/Plain Craft Launcher 2/Modules/ModEvent.vb @@ -1,4 +1,4 @@ -Imports System.Collections.ObjectModel +Imports System.Collections.ObjectModel Public Module ModEvent #Region "事件执行方法" diff --git a/Plain Craft Launcher 2/Modules/ModMain.vb b/Plain Craft Launcher 2/Modules/ModMain.vb index 5eda3e1..68aaebb 100644 --- a/Plain Craft Launcher 2/Modules/ModMain.vb +++ b/Plain Craft Launcher 2/Modules/ModMain.vb @@ -34,12 +34,12 @@ Public Module ModMain ''' ''' 等待弹出的提示列表。以 {String, HintType, Log As Boolean} 形式存储为数组。 ''' - Private HintWaiting As List(Of HintMessage) = If(HintWaiting, New List(Of HintMessage)) + Private HintWaiting As SafeList(Of HintMessage) = If(HintWaiting, New SafeList(Of HintMessage)) ''' ''' 在窗口左下角弹出提示文本。 ''' Public Sub Hint(Text As String, Optional Type As HintType = HintType.Info, Optional Log As Boolean = True) - If HintWaiting Is Nothing Then HintWaiting = New List(Of HintMessage) + If HintWaiting Is Nothing Then HintWaiting = New SafeList(Of HintMessage) HintWaiting.Add(New HintMessage With {.Text = If(Text, ""), .Type = Type, .Log = Log}) End Sub @@ -47,7 +47,7 @@ Public Module ModMain Try 'Tag 存储了:{ 是否可以重用, Uuid } - If Not HintWaiting.Any() Then Exit Sub + If Not HintWaiting.Any() Then Return Do While HintWaiting.Any ''清除空提示 'If IsNothing(HintWaiting(0)) OrElse IsNothing(HintWaiting(0)(0)) Then @@ -343,7 +343,7 @@ EndHint: Public WaitingMyMsgBox As List(Of MyMsgBoxConverter) = If(WaitingMyMsgBox, New List(Of MyMsgBoxConverter)) Public Sub MyMsgBoxTick() Try - If FrmMain Is Nothing OrElse FrmMain.PanMsg Is Nothing OrElse FrmMain.WindowState = WindowState.Minimized Then Exit Sub + If FrmMain Is Nothing OrElse FrmMain.PanMsg Is Nothing OrElse FrmMain.WindowState = WindowState.Minimized Then Return If FrmMain.PanMsg.Children.Count > 0 Then '弹窗中 FrmMain.PanMsg.Visibility = Visibility.Visible @@ -397,14 +397,11 @@ EndHint: '下载页面声明 Public FrmDownloadLeft As PageDownloadLeft Public FrmDownloadInstall As PageDownloadInstall - Public FrmDownloadClient As PageDownloadClient - Public FrmDownloadOptiFine As PageDownloadOptiFine - Public FrmDownloadLiteLoader As PageDownloadLiteLoader - Public FrmDownloadForge As PageDownloadForge - Public FrmDownloadNeoForge As PageDownloadNeoForge - Public FrmDownloadFabric As PageDownloadFabric Public FrmDownloadMod As PageDownloadMod Public FrmDownloadPack As PageDownloadPack + Public FrmDownloadDataPack As PageDownloadDataPack + Public FrmDownloadShader As PageDownloadShader + Public FrmDownloadResourcePack As PageDownloadResourcePack '设置页面声明 Public FrmSetupLeft As PageSetupLeft @@ -634,7 +631,7 @@ NextFile: Catch ex As Exception Log(ex, "检查帮助文件夹失败", LogLevel.Msgbox) End Try - If Loader.IsAborted Then Exit Sub + If Loader.IsAborted Then Return '将文件实例化 Dim Dict As New List(Of HelpEntry) @@ -650,7 +647,7 @@ NextFile: '回设 If Not Dict.Any() Then Throw New Exception("未找到可用的帮助;若不需要帮助页面,可以在 设置 → 个性化 → 功能隐藏 中将其隐藏") - If Loader.IsAborted Then Exit Sub + If Loader.IsAborted Then Return Loader.Output = Dict Catch ex As Exception @@ -693,8 +690,8 @@ NextFile: Private AprilDistance As Integer = 0 Private Sub TimerFool() Try - If FrmLaunchLeft Is Nothing OrElse FrmLaunchLeft.AprilPosTrans Is Nothing OrElse FrmMain.lastMouseArg Is Nothing Then Exit Sub - If IsAprilGiveup OrElse FrmMain.PageCurrent <> FormMain.PageType.Launch OrElse AniControlEnabled <> 0 OrElse Not FrmLaunchLeft.BtnLaunch.IsLoaded Then Exit Sub + If FrmLaunchLeft Is Nothing OrElse FrmLaunchLeft.AprilPosTrans Is Nothing OrElse FrmMain.lastMouseArg Is Nothing Then Return + If IsAprilGiveup OrElse FrmMain.PageCurrent <> FormMain.PageType.Launch OrElse AniControlEnabled <> 0 OrElse Not FrmLaunchLeft.BtnLaunch.IsLoaded Then Return '计算是否空闲 Dim MousePos = FrmMain.lastMouseArg.GetPosition(FrmMain) @@ -763,7 +760,7 @@ NextFile: '移动 AprilSpeed = AprilSpeed * 0.8 + Acc Dim SpeedValue = Math.Min(60, AprilSpeed.Length) - If SpeedValue < 0.01 Then Exit Sub + If SpeedValue < 0.01 Then Return AprilSpeed.Normalize() AprilSpeed *= SpeedValue AprilDistance += SpeedValue @@ -910,7 +907,7 @@ NextFile: If ThemeDontClick = 2 Then ThemeRefresh() #End Region Catch ex As Exception - Log(ex, "短程主时钟执行异常", LogLevel.Assert) + Log(ex, "短程主时钟执行异常", LogLevel.Critical) End Try Timer4Count += 1 If Timer4Count = 4 Then @@ -930,15 +927,16 @@ NextFile: #Region "每 7.5s 执行一次的代码" If FrmMain.BtnExtraApril_ShowCheck AndAlso AprilDistance <> 0 Then FrmMain.BtnExtraApril.Ribble() '以未知原因窗口被丢到一边去的修复(Top、Left = -25600),还有 #745 - RunInUi(Sub() - If Not FrmMain.Hidden Then - If FrmMain.Top < -9000 Then FrmMain.Top = 100 - If FrmMain.Left < -9000 Then FrmMain.Left = 100 '窗口拉至最大时 Left = -18.8 - End If - End Sub) + RunInUi( + Sub() + If Not FrmMain.Hidden Then + If FrmMain.Top < -9000 Then FrmMain.Top = 100 + If FrmMain.Left < -9000 Then FrmMain.Left = 100 '窗口拉至最大时 Left = -18.8 + End If + End Sub) #End Region Catch ex As Exception - Log(ex, "长程主时钟执行异常", LogLevel.Assert) + Log(ex, "长程主时钟执行异常", LogLevel.Critical) End Try End If End Sub @@ -954,7 +952,7 @@ NextFile: Log(ex, "程序主时钟出错", LogLevel.Feedback) End Try End Sub, "Timer Main") - If Not IsAprilEnabled Then Exit Sub + If Not IsAprilEnabled Then Return RunInNewThread( Sub() Try diff --git a/Plain Craft Launcher 2/Modules/ModMusic.vb b/Plain Craft Launcher 2/Modules/ModMusic.vb index 060091a..0476e89 100644 --- a/Plain Craft Launcher 2/Modules/ModMusic.vb +++ b/Plain Craft Launcher 2/Modules/ModMusic.vb @@ -79,7 +79,7 @@ If MusicState = MusicStates.Pause Then FrmMain.BtnExtraMusic.Logo = Logo.IconPlay FrmMain.BtnExtraMusic.LogoScale = 0.8 - ToolTipText = "已暂停:" & GetFileNameWithoutExtentionFromPath(MusicCurrent) + ToolTipText = "已暂停:" & IO.Path.GetFileNameWithoutExtension(MusicCurrent) If MusicAllList.Count > 1 Then ToolTipText += vbCrLf & "左键恢复播放,右键播放下一曲。" Else @@ -88,7 +88,7 @@ Else FrmMain.BtnExtraMusic.Logo = Logo.IconMusic FrmMain.BtnExtraMusic.LogoScale = 1 - ToolTipText = "正在播放:" & GetFileNameWithoutExtentionFromPath(MusicCurrent) + ToolTipText = "正在播放:" & IO.Path.GetFileNameWithoutExtension(MusicCurrent) If MusicAllList.Count > 1 Then ToolTipText += vbCrLf & "左键暂停,右键播放下一曲。" Else @@ -247,7 +247,7 @@ Log("[Music] 已恢复播放") Try MusicNAudio?.Play() - Catch 'https://github.com/Hex-Dragon/PCL2/pull/5415#issuecomment-2751135223 + Catch 'https://github.com/Meloong-Git/PCL/pull/5415#issuecomment-2751135223 MusicNAudio?.Stop() MusicNAudio?.Play() End Try diff --git a/Plain Craft Launcher 2/Modules/ModSecret.vb b/Plain Craft Launcher 2/Modules/ModSecret.vb index 3c38897..672ea2a 100644 --- a/Plain Craft Launcher 2/Modules/ModSecret.vb +++ b/Plain Craft Launcher 2/Modules/ModSecret.vb @@ -9,9 +9,11 @@ Friend Module ModSecret '在开源版的注册表与常规版的注册表隔离,以防数据冲突 Public Const RegFolder As String = "PCLDebug" '用于微软登录的 ClientId - Public Const OAuthClientId As String = "" + Public OAuthClientId As String = If(Environment.GetEnvironmentVariable("PCL_MS_CLIENT_ID"), "") 'CurseForge API Key - Public Const CurseForgeAPIKey As String = "" + Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "") + '用于匿名数据收集的腾讯云日志服务上报 URL,形如 https://{region}.cls.tencentcs.com/track?topic_id={topic_id} + Public Const ClsBaseUrl As String = "" Friend Sub SecretOnApplicationStart() '提升 UI 线程优先级 @@ -67,61 +69,29 @@ Friend Module ModSecret If Not DataList.Any(Function(d) d.Contains("-Dlog4j2.formatMsgNoLookups=true")) Then DataList.Add("-Dlog4j2.formatMsgNoLookups=true") End Sub - ''' - ''' 打码字符串中的 AccessToken。 - ''' - Friend Function SecretFilter(Raw As String, FilterChar As Char) As String - '打码 "accessToken " 后的内容 - If Raw.Contains("accessToken ") Then - For Each Token In RegexSearch(Raw, "(?<=accessToken ([^ ]{5}))[^ ]+(?=[^ ]{5})") - Raw = Raw.Replace(Token, New String(FilterChar, Token.Count)) - Next - End If - '打码当前登录的结果 - Dim AccessToken As String = McLoginLoader.Output.AccessToken - If AccessToken Is Nothing OrElse AccessToken.Length < 10 OrElse Not Raw.ContainsF(AccessToken, True) OrElse - McLoginLoader.Output.Uuid = McLoginLoader.Output.AccessToken Then 'UUID 和 AccessToken 一样则不打码 - Return Raw - Else - Return Raw.Replace(AccessToken, Left(AccessToken, 5) & New String(FilterChar, AccessToken.Length - 10) & Right(AccessToken, 5)) - End If - End Function - #End Region #Region "网络鉴权" - Friend Function SecretCdnSign(UrlWithMark As String) + Friend Function SecretCdnSign(UrlWithMark As String) As String If Not UrlWithMark.EndsWithF("{CDN}") Then Return UrlWithMark Return UrlWithMark.Replace("{CDN}", "").Replace(" ", "%20") End Function ''' ''' 设置 Headers 的 UA、Referer。 ''' - Friend Sub SecretHeadersSign(Url As String, ByRef Client As WebClient, Optional UseBrowserUserAgent As Boolean = False) - If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then - Client.Headers("User-Agent") = "LogStatistic" '#4951 - ElseIf UseBrowserUserAgent Then - Client.Headers("User-Agent") = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36" - Else - Client.Headers("User-Agent") = "PCL2/" & VersionStandardCode + Friend Sub SecretHeadersSign(Url As String, ByRef Req As HttpRequestMessage, Optional SimulateBrowserHeaders As Boolean = False) + If Not Req.Headers.UserAgent.Any Then + If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then + Req.Headers.Add("User-Agent", "LogStatistic") '#4951 + ElseIf SimulateBrowserHeaders Then + Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode} Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36") + Else + Req.Headers.Add("User-Agent", $"PCL2/{VersionStandardCode}") + End If End If - Client.Headers("Referer") = "http://" & VersionCode & ".open.pcl2.server/" - If Url.Contains("api.curseforge.com") Then Client.Headers("x-api-key") = CurseForgeAPIKey - End Sub - ''' - ''' 设置 Headers 的 UA、Referer。 - ''' - Friend Sub SecretHeadersSign(Url As String, ByRef Request As HttpWebRequest, Optional UseBrowserUserAgent As Boolean = False) - If Url.Contains("baidupcs.com") OrElse Url.Contains("baidu.com") Then - Request.UserAgent = "LogStatistic" '#4951 - ElseIf UseBrowserUserAgent Then - Request.UserAgent = "PCL2/" & VersionStandardCode & " Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36" - Else - Request.UserAgent = "PCL2/" & VersionStandardCode - End If - Request.Referer = "http://" & VersionCode & ".open.pcl2.server/" - If Url.Contains("api.curseforge.com") Then Request.Headers("x-api-key") = CurseForgeAPIKey + If Not SimulateBrowserHeaders Then Req.Headers.Add("Referer", $"http://{VersionCode}.open.pcl2.server/") + If Url.Contains("api.curseforge.com") Then Req.Headers.Add("x-api-key", CurseForgeAPIKey) End Sub #End Region @@ -198,45 +168,70 @@ Friend Module ModSecret Public ThemeDontClick As Integer = 0 Public Sub ThemeRefresh(Optional NewTheme As Integer = -1) - Hint("该版本中不包含主题功能……") + Try + If ThemeNow = NewTheme AndAlso NewTheme >= 0 Then Return + If NewTheme >= 0 Then ThemeNow = NewTheme + + Color1 = New MyColor().FromHSL2(ColorHue, ColorSat * 0.2, 25 + ColorLightAdjust * 0.3) + Color2 = New MyColor().FromHSL2(ColorHue, ColorSat, 45 + ColorLightAdjust) + Color3 = New MyColor().FromHSL2(ColorHue, ColorSat, 55 + ColorLightAdjust) + Color4 = New MyColor().FromHSL2(ColorHue, ColorSat, 65 + ColorLightAdjust) + Color5 = New MyColor().FromHSL2(ColorHue, ColorSat, 80 + ColorLightAdjust * 0.4) + Color6 = New MyColor().FromHSL2(ColorHue, ColorSat, 91 + ColorLightAdjust * 0.1) + Color7 = New MyColor().FromHSL2(ColorHue, ColorSat, 95) + Color8 = New MyColor().FromHSL2(ColorHue, ColorSat, 97) + ColorBg0 = Color4 * 0.4 + Color5 * 0.4 + ColorGray4 * 0.2 + ColorBg1 = New MyColor(190, Color7) + + ColorSemiTransparent = New MyColor(1, Color8) + Application.Current.Resources("ColorBrush1") = New SolidColorBrush(Color1) + Application.Current.Resources("ColorBrush2") = New SolidColorBrush(Color2) + Application.Current.Resources("ColorBrush3") = New SolidColorBrush(Color3) + Application.Current.Resources("ColorBrush4") = New SolidColorBrush(Color4) + Application.Current.Resources("ColorBrush5") = New SolidColorBrush(Color5) + Application.Current.Resources("ColorBrush6") = New SolidColorBrush(Color6) + Application.Current.Resources("ColorBrush7") = New SolidColorBrush(Color7) + Application.Current.Resources("ColorBrush8") = New SolidColorBrush(Color8) + Application.Current.Resources("ColorBrushBg0") = New SolidColorBrush(ColorBg0) + Application.Current.Resources("ColorBrushBg1") = New SolidColorBrush(ColorBg1) + Application.Current.Resources("ColorObject1") = CType(Color1, Color) + Application.Current.Resources("ColorObject2") = CType(Color2, Color) + Application.Current.Resources("ColorObject3") = CType(Color3, Color) + Application.Current.Resources("ColorObject4") = CType(Color4, Color) + Application.Current.Resources("ColorObject5") = CType(Color5, Color) + Application.Current.Resources("ColorObject6") = CType(Color6, Color) + Application.Current.Resources("ColorObject7") = CType(Color7, Color) + Application.Current.Resources("ColorObject8") = CType(Color8, Color) + Application.Current.Resources("ColorObjectBg0") = CType(ColorBg0, Color) + Application.Current.Resources("ColorObjectBg1") = CType(ColorBg1, Color) + ThemeRefreshMain() + Catch ex As Exception + Log(ex, "刷新主题颜色失败", LogLevel.Hint) + End Try End Sub Public Sub ThemeRefreshMain() RunInUi( Sub() - If Not FrmMain.IsLoaded Then Exit Sub + If Not FrmMain.IsLoaded Then Return '顶部条背景 Dim Brush = New LinearGradientBrush With {.EndPoint = New Point(1, 0), .StartPoint = New Point(0, 0)} - If ThemeNow = 5 Then - Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 25)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 15)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 25)}) - FrmMain.PanTitle.Background = Brush - FrmMain.PanTitle.Background.Freeze() - ElseIf Not (ThemeNow = 12 OrElse ThemeDontClick = 2) Then - If TypeOf ColorHueTopbarDelta Is Integer Then - Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue - ColorHueTopbarDelta, ColorSat, 48 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 54 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta, ColorSat, 48 + ColorLightAdjust)}) - Else - Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(0), ColorSat, 48 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(1), ColorSat, 54 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(2), ColorSat, 48 + ColorLightAdjust)}) - End If - FrmMain.PanTitle.Background = Brush - FrmMain.PanTitle.Background.Freeze() + If TypeOf ColorHueTopbarDelta Is Integer Then + Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue - ColorHueTopbarDelta, ColorSat, 48 + ColorLightAdjust)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue, ColorSat, 54 + ColorLightAdjust)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta, ColorSat, 48 + ColorLightAdjust)}) Else - Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue - 21, ColorSat, 53 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 0.33, .Color = New MyColor().FromHSL2(ColorHue - 7, ColorSat, 47 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 0.67, .Color = New MyColor().FromHSL2(ColorHue + 7, ColorSat, 47 + ColorLightAdjust)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + 21, ColorSat, 53 + ColorLightAdjust)}) - FrmMain.PanTitle.Background = Brush + Brush.GradientStops.Add(New GradientStop With {.Offset = 0, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(0), ColorSat, 48 + ColorLightAdjust)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 0.5, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(1), ColorSat, 54 + ColorLightAdjust)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 1, .Color = New MyColor().FromHSL2(ColorHue + ColorHueTopbarDelta(2), ColorSat, 48 + ColorLightAdjust)}) End If + FrmMain.PanTitle.Background = Brush + FrmMain.PanTitle.Background.Freeze() '主页面背景 If Setup.Get("UiBackgroundColorful") Then Brush = New LinearGradientBrush With {.EndPoint = New Point(0.1, 1), .StartPoint = New Point(0.9, 0)} - Brush.GradientStops.Add(New GradientStop With {.Offset = -0.1, .Color = New MyColor().FromHSL2(ColorHue - 20, Math.Min(60, ColorSat) * 0.5, 80)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 0.4, .Color = New MyColor().FromHSL2(ColorHue, ColorSat * 0.9, 90)}) - Brush.GradientStops.Add(New GradientStop With {.Offset = 1.1, .Color = New MyColor().FromHSL2(ColorHue + 20, Math.Min(60, ColorSat) * 0.5, 80)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = -0.1, .Color = New MyColor().FromHSL2(ColorHue - 15, ColorSat * 0.8, 91)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 0.4, .Color = New MyColor().FromHSL2(ColorHue, ColorSat * 0.8, 91)}) + Brush.GradientStops.Add(New GradientStop With {.Offset = 1.1, .Color = New MyColor().FromHSL2(ColorHue + 15, ColorSat * 0.8, 91)}) FrmMain.PanForm.Background = Brush Else FrmMain.PanForm.Background = New MyColor(245, 245, 245) diff --git a/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb b/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb deleted file mode 100644 index 1f5ea45..0000000 --- a/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb +++ /dev/null @@ -1,59 +0,0 @@ -Imports System.Net - -Public Class CookieWebClient - Inherits WebClient - - Public Sub New(container As CookieContainer, Headers As Dictionary(Of String, String)) - Me.New(container) - For Each keyVal In Headers - Me.Headers(keyVal.Key) = keyVal.Value - Next - End Sub - - Public Sub New() - Me.New(New CookieContainer()) - End Sub - - Public Sub New(container As CookieContainer) - Me.container = container - End Sub - - Private Shadows ReadOnly container As New CookieContainer() - - ''' - ''' 以毫秒为单位的超时。 - ''' - Public Timeout As Integer = 600000 - - Protected Overrides Function GetWebRequest(address As Uri) As WebRequest - Dim r As WebRequest = MyBase.GetWebRequest(address) - Dim request = TryCast(r, HttpWebRequest) - - If request IsNot Nothing Then - request.CookieContainer = container - request.Timeout = Timeout - End If - - Return r - End Function - - Protected Overrides Function GetWebResponse(request As WebRequest, result As IAsyncResult) As WebResponse - Dim response As WebResponse = MyBase.GetWebResponse(request, result) - ReadCookies(response) - Return response - End Function - - Protected Overrides Function GetWebResponse(request As WebRequest) As WebResponse - Dim response As WebResponse = MyBase.GetWebResponse(request) - ReadCookies(response) - Return response - End Function - - Private Sub ReadCookies(r As WebResponse) - Dim response = TryCast(r, HttpWebResponse) - If response IsNot Nothing Then - Dim cookies As CookieCollection = response.Cookies - container.Add(cookies) - End If - End Sub -End Class diff --git a/Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb b/Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb new file mode 100644 index 0000000..e2d79e7 --- /dev/null +++ b/Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb @@ -0,0 +1,151 @@ +'来源:https://blog.csdn.net/simpleman2000/article/details/140952294 + +Imports System.ComponentModel +Imports System.Runtime.InteropServices +Imports System.Windows.Interop + +Public Class DragHelper + + Public Event DragDrop As EventHandler + + Public Property DropFilePaths As String() + Get + Return _DropFilePathsBackingField + End Get + Private Set(value As String()) + _DropFilePathsBackingField = value + End Set + End Property + Private _DropFilePathsBackingField As String() + + Public Property DropPoint As POINT + Get + Return _DropPointBackingField + End Get + Private Set(value As POINT) + _DropPointBackingField = value + End Set + End Property + Private _DropPointBackingField As POINT + + Public Property HwndIntPtrSource As HwndSource + + Public Sub AddHook() + RemoveDragHook() + HwndIntPtrSource.AddHook(AddressOf WndProc) + Dim handle As IntPtr = HwndIntPtrSource.Handle + If IsUserAnAdmin() Then RevokeDragDrop(handle) + DragAcceptFiles(handle, True) + ChangeMessageFilter(handle) + End Sub + + Public Sub RemoveDragHook() + HwndIntPtrSource.RemoveHook(AddressOf WndProc) + DragAcceptFiles(HwndIntPtrSource.Handle, False) + End Sub + + Private Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr + Dim filePaths As String() = Nothing + Dim point As New POINT() + + If TryGetDropInfo(msg, wParam, filePaths, point) Then + DropPoint = point + DropFilePaths = filePaths + RaiseEvent DragDrop(Me, EventArgs.Empty) + handled = True + End If + Return IntPtr.Zero + End Function + + + Private Shared Function ChangeWindowMessageFilterEx(hWnd As IntPtr, msg As UInteger, action As UInteger, ByRef pChangeFilterStruct As CHANGEFILTERSTRUCT) As Boolean + End Function + + + Private Shared Function ChangeWindowMessageFilter(msg As UInteger, flags As UInteger) As Boolean + End Function + + + Private Shared Sub DragAcceptFiles(hWnd As IntPtr, fAccept As Boolean) + End Sub + + + Private Shared Function DragQueryFile(hWnd As IntPtr, iFile As UInteger, lpszFile As StringBuilder, cch As Integer) As UInteger + End Function + + + Private Shared Function DragQueryPoint(hDrop As IntPtr, ByRef lppt As POINT) As Boolean + End Function + + + Private Shared Sub DragFinish(hDrop As IntPtr) + End Sub + + + Private Shared Function RevokeDragDrop(hWnd As IntPtr) As Integer + End Function + + + Private Shared Function IsUserAnAdmin() As Boolean + End Function + + + Public Structure POINT + Public X As Integer + Public Y As Integer + End Structure + + + Private Structure CHANGEFILTERSTRUCT + Public cbSize As UInteger + Public ExtStatus As UInteger + End Structure + + Private Const WM_COPYGLOBALDATA As UInteger = &H49 + Private Const WM_COPYDATA As UInteger = &H4A + Private Const WM_DROPFILES As UInteger = &H233 + Private Const MSGFLT_ALLOW As UInteger = 1 + Private Const MSGFLT_ADD As UInteger = 1 + Private Const MAX_PATH As Integer = 260 + + Private Shared Sub ChangeMessageFilter(handle As IntPtr) + Dim ver As Version = Environment.OSVersion.Version + Dim isVistaOrHigher As Boolean = ver >= New Version(6, 0) + Dim isNt61OrHiger As Boolean = ver >= New Version(6, 1) + + If isVistaOrHigher Then + Dim status As New CHANGEFILTERSTRUCT With {.cbSize = 8} + For Each msg As UInteger In New UInteger() {WM_DROPFILES, WM_COPYGLOBALDATA, WM_COPYDATA} + Dim [error] As Boolean = False + If isNt61OrHiger Then + [error] = Not ChangeWindowMessageFilterEx(handle, msg, MSGFLT_ALLOW, status) + Else + [error] = Not ChangeWindowMessageFilter(msg, MSGFLT_ADD) + End If + + If [error] Then Throw New Win32Exception(Marshal.GetLastWin32Error()) + Next + End If + End Sub + + Private Shared Function TryGetDropInfo(msg As Integer, wParam As IntPtr, ByRef dropFilePaths As String(), ByRef dropPoint As POINT) As Boolean + dropFilePaths = Nothing + dropPoint = New POINT() + + If msg <> WM_DROPFILES Then Return False + + Dim fileCount As UInteger = DragQueryFile(wParam, UInteger.MaxValue, Nothing, 0) + ReDim dropFilePaths(CInt(fileCount) - 1) + + For i As UInteger = 0 To CInt(fileCount) - 1 + Dim sb As New StringBuilder(MAX_PATH) + Dim result As UInteger = DragQueryFile(wParam, i, sb, sb.Capacity) + If result > 0 Then dropFilePaths(i) = sb.ToString() + Next + + DragQueryPoint(wParam, dropPoint) + DragFinish(wParam) + Return True + End Function + +End Class diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb index 17bee37..ac15996 100644 --- a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb +++ b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb @@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices ' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号 ' 方法是按如下所示使用“*” - - + + diff --git a/Plain Craft Launcher 2/My Project/Resources.Designer.vb b/Plain Craft Launcher 2/My Project/Resources.Designer.vb index 81e998d..dfc568a 100644 --- a/Plain Craft Launcher 2/My Project/Resources.Designer.vb +++ b/Plain Craft Launcher 2/My Project/Resources.Designer.vb @@ -63,9 +63,9 @@ Namespace My.Resources ''' ''' 查找 System.Byte[] 类型的本地化资源。 ''' - Friend ReadOnly Property Custom() As Byte() + Friend ReadOnly Property CacheCow_Client() As Byte() Get - Dim obj As Object = ResourceManager.GetObject("Custom", resourceCulture) + Dim obj As Object = ResourceManager.GetObject("CacheCow.Client", resourceCulture) Return CType(obj,Byte()) End Get End Property @@ -73,9 +73,29 @@ Namespace My.Resources ''' ''' 查找 System.Byte[] 类型的本地化资源。 ''' - Friend ReadOnly Property Dialogs() As Byte() + Friend ReadOnly Property CacheCow_Client_FileStore() As Byte() Get - Dim obj As Object = ResourceManager.GetObject("Dialogs", resourceCulture) + Dim obj As Object = ResourceManager.GetObject("CacheCow.Client.FileStore", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property CacheCow_Common() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("CacheCow.Common", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property Custom() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Custom", resourceCulture) Return CType(obj,Byte()) End Get End Property @@ -105,7 +125,7 @@ Namespace My.Resources ''' Friend ReadOnly Property Imazen_WebP() As Byte() Get - Dim obj As Object = ResourceManager.GetObject("Imazen_WebP", resourceCulture) + Dim obj As Object = ResourceManager.GetObject("Imazen.WebP", resourceCulture) Return CType(obj,Byte()) End Get End Property @@ -120,16 +140,6 @@ Namespace My.Resources End Get End Property - ''' - ''' 查找 System.Byte[] 类型的本地化资源。 - ''' - Friend ReadOnly Property Json() As Byte() - Get - Dim obj As Object = ResourceManager.GetObject("Json", resourceCulture) - Return CType(obj,Byte()) - End Get - End Property - ''' ''' 查找 System.Byte[] 类型的本地化资源。 ''' @@ -160,6 +170,36 @@ Namespace My.Resources End Get End Property + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property Newtonsoft_Json() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Newtonsoft.Json", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property Ookii_Dialogs_Wpf() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("Ookii.Dialogs.Wpf", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + + ''' + ''' 查找 System.Byte[] 类型的本地化资源。 + ''' + Friend ReadOnly Property System_Net_Http_Formatting() As Byte() + Get + Dim obj As Object = ResourceManager.GetObject("System.Net.Http.Formatting", resourceCulture) + Return CType(obj,Byte()) + End Get + End Property + ''' ''' 查找 System.Byte[] 类型的本地化资源。 ''' diff --git a/Plain Craft Launcher 2/My Project/Resources.resx b/Plain Craft Launcher 2/My Project/Resources.resx index 8f1da65..8db2019 100644 --- a/Plain Craft Launcher 2/My Project/Resources.resx +++ b/Plain Craft Launcher 2/My Project/Resources.resx @@ -118,27 +118,30 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\CacheCow.Client.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\CacheCow.Client.FileStore.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\CacheCow.Common.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\resources\custom.xaml;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\resources\ookii.dialogs.wpf.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - ..\Resources\forge-installer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ..\Resources\Help.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + ..\Resources\Imazen.WebP.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ..\Resources\java-wrapper.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - ..\Resources\Newtonsoft.Json.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - ..\Resources\libwebp64.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 @@ -148,6 +151,15 @@ ..\Resources\NAudio.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + ..\Resources\Newtonsoft.Json.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\resources\ookii.dialogs.wpf.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ..\Resources\System.Net.Http.Formatting.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + ..\Resources\transformer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml similarity index 100% rename from Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml rename to Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb similarity index 80% rename from Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb rename to Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb index 34e7ab2..47b17e9 100644 --- a/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/MyCompItem.xaml.vb @@ -19,7 +19,7 @@ Return LabTitle.Text End Get Set(value As String) - If LabTitle.Text = value Then Exit Property + If LabTitle.Text = value Then Return LabTitle.Text = value End Set End Property @@ -30,7 +30,7 @@ Return If(LabTitleRaw?.Text, "") End Get Set(value As String) - If LabTitleRaw.Text = value Then Exit Property + If LabTitleRaw.Text = value Then Return LabTitleRaw.Text = value LabTitleRaw.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible) End Set @@ -42,7 +42,7 @@ Return LabInfo.Text End Get Set(value As String) - If LabInfo.Text = value Then Exit Property + If LabInfo.Text = value Then Return LabInfo.Text = value End Set End Property @@ -88,41 +88,52 @@ Private Sub Button_MouseUp(sender As Object, e As MouseButtonEventArgs) Handles Me.PreviewMouseLeftButtonUp If IsMouseDown Then RaiseEvent Click(sender, e) - If e.Handled Then Exit Sub + If e.Handled Then Return Log("[Control] 按下资源工程列表项:" & LabTitle.Text) End If End Sub - Private Sub ProjectClick(sender As MyCompItem, e As EventArgs) Handles Me.Click + Private Sub MyCompItem_Click(sender As MyCompItem, e As EventArgs) Handles Me.Click '记录当前展开的卡片标题(#2712) Dim Titles As New List(Of String) If FrmMain.PageCurrent.Page = FormMain.PageType.CompDetail Then For Each Card As MyCard In FrmDownloadCompDetail.PanResults.Children - If Card.Title <> "" AndAlso Not Card.IsSwaped Then Titles.Add(Card.Title) + If Card.Title <> "" AndAlso Not Card.IsSwapped Then Titles.Add(Card.Title) Next Log("[Comp] 记录当前已展开的卡片:" & String.Join("、", Titles)) FrmMain.PageCurrent.Additional(1) = Titles End If '打开详情页 - Dim TargetVersion As String - Dim TargetLoader As CompModLoaderType - If FrmMain.PageCurrent.Page = FormMain.PageType.CompDetail Then + Dim TargetType As CompType + Dim TargetVersion 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 + TargetLoader = FrmDownloadMod.Content.Loader.Input.ModLoader + Case FormMain.PageSubType.DownloadPack + TargetType = CompType.ModPack + TargetVersion = FrmDownloadPack.Content.Loader.Input.GameVersion + Case FormMain.PageSubType.DownloadDataPack + TargetType = CompType.DataPack + TargetVersion = FrmDownloadDataPack.Content.Loader.Input.GameVersion + Case FormMain.PageSubType.DownloadResourcePack + TargetType = CompType.ResourcePack + TargetVersion = FrmDownloadResourcePack.Content.Loader.Input.GameVersion + Case FormMain.PageSubType.DownloadShader + TargetType = CompType.Shader + TargetVersion = FrmDownloadShader.Content.Loader.Input.GameVersion + End Select + Else + '从详情页进入(查看前置) + TargetType = CompType.Any '允许任意类别 TargetVersion = FrmMain.PageCurrent.Additional(2) TargetLoader = FrmMain.PageCurrent.Additional(3) - Else - Select Case CType(sender.Tag, CompProject).Type - Case CompType.Mod - TargetVersion = If(PageDownloadMod.Loader.Input.GameVersion, "") - TargetLoader = PageDownloadMod.Loader.Input.ModLoader - Case CompType.ModPack - TargetVersion = If(PageDownloadPack.Loader.Input.GameVersion, "") - Case Else 'CompType.ResourcePack - 'FUTURE: Res - TargetVersion = "" 'If(PageDownloadResource.Loader.Input.GameVersion, "") - End Select End If - If CType(sender.Tag, CompProject).Type <> CompType.Mod Then TargetLoader = CompModLoaderType.Any FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail, - .Additional = {sender.Tag, New List(Of String), TargetVersion, TargetLoader}}) + .Additional = {sender.Tag, New List(Of String), TargetVersion, TargetLoader, TargetType}}) End Sub '鼠标点击判定 @@ -174,7 +185,7 @@ ''' Public Property CanInteraction As Boolean = True Public Sub RefreshColor(sender As Object, e As EventArgs) Handles Me.MouseEnter, Me.MouseLeave, Me.MouseLeftButtonDown, Me.MouseLeftButtonUp - If Not CanInteraction Then Exit Sub + If Not CanInteraction Then Return '判断当前颜色 Dim StateNew As String, Time As Integer If IsMouseOver Then @@ -189,7 +200,7 @@ StateNew = "Idle" Time = 180 End If - If StateLast = StateNew Then Exit Sub + If StateLast = StateNew Then Return StateLast = StateNew '触发颜色动画 If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画 diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml new file mode 100644 index 0000000..4de9f55 --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb similarity index 57% rename from Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb rename to Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb index 113ff25..f3d278c 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb @@ -1,17 +1,83 @@ -Public Class PageDownloadMod +Imports System.Windows.Markup + + +Public Class PageComp + +#Region "属性" - Public Const PageSize = 40 ''' - ''' 在切换到该页面时自动设置的目标版本。 + ''' 用于 XAML 快速设置的 Tag 下拉框列表。 + ''' + Public ReadOnly Property SearchTags As ItemCollection + Get + Return ComboSearchTag.Items + End Get + End Property + + ''' + ''' 英文前后不含空格的可读资源类型名,例如 "Mod"、"整合包"。 + ''' + Public Property TypeName As String + Get + Return _TypeName + End Get + Set(Value As String) + If _TypeName = Value Then Return + _TypeName = Value + Loader.Name = $"社区资源获取:{Value}" + End Set + End Property + Private _TypeName As String = "" + + ''' + ''' 英文前后含一个空格的可读资源类型名,例如 " Mod "、"整合包"。 + ''' + Public Property TypeNameSpaced As String + Get + Return _TypeNameSpaced + End Get + Set(Value As String) + If _TypeNameSpaced = Value Then Return + _TypeNameSpaced = Value + PanAlways.Title = $"搜索{Value}" + Load.Text = $"正在获取{Value}列表" + End Set + End Property + Private _TypeNameSpaced As String = "" + + ''' + ''' 该页面对应的资源类型。 + ''' + Public Property PageType As CompType + Get + Return _Type + End Get + Set(Value As CompType) + If _Type = Value Then Return + _Type = Value + BtnSearchInstallModPack.Visibility = If(Value = CompType.ModPack, Visibility.Visible, Visibility.Collapsed) + End Set + End Property + Private _Type As CompType = -1 + +#End Region + +#Region "加载" + + ''' + ''' 在切换到页面时,应自动将筛选项设置为与该目标 MC 版本和加载器相同。 ''' Public Shared TargetVersion As McVersion = Nothing + ''' + ''' 在切换到该页面时自动设置的搜索框内的内容。 + ''' + Public Shared TargetName As String = Nothing + + '在点击 MyCompItem 时会获取 Loader 的输入,以使资源详情页面可以应用相同的筛选项 + Public Loader As New LoaderTask(Of CompProjectRequest, Integer)("社区资源获取:XXX", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000} - '加载器信息 - Public Shared Loader As New LoaderTask(Of CompProjectRequest, Integer)("CompProject Mod", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000} - Public Shared Storage As New CompProjectStorage - Public Shared Page As Integer = 0 Private IsLoaderInited As Boolean = False - Private Sub PageDownloadMod_Inited(sender As Object, e As EventArgs) Handles Me.Loaded + Private Sub PageCompControls_Inited(sender As Object, e As EventArgs) Handles Me.Loaded '不知道从 Initialized 改成 Loaded 会不会有问题,但用 Initialized 会导致初始的筛选器修改被覆盖回默认值 If TargetVersion IsNot Nothing Then '设置目标 @@ -32,45 +98,53 @@ ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge") End If TargetVersion = Nothing + If TargetName IsNot Nothing Then + TextSearchName.Text = TargetName + TargetName = Nothing + End If '如果已经完成请求,则重新开始 If IsLoaderInited Then StartNewSearch() - PanScroll.ScrollToHome() + ScrollToHome() End If '加载器初始化 If IsLoaderInited Then Return IsLoaderInited = True - PageLoaderInit(Load, PanLoad, PanContent, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput) + CType(Parent, MyPageRight).PageLoaderInit(Load, PanLoad, PanContent, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput) If McVersionHighest = -1 Then McVersionHighest = Math.Max(McVersionHighest, Integer.Parse(CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString.Split(".")(1))) End Sub - Private Shared Function LoaderInput() As CompProjectRequest - Dim Request As New CompProjectRequest(CompType.Mod, Storage, (Page + 1) * PageSize) - If FrmDownloadMod IsNot Nothing Then - Dim ModLoader As CompModLoaderType = Val(FrmDownloadMod.ComboSearchLoader.SelectedItem.Tag) - Dim GameVersion As String = If(FrmDownloadMod.TextSearchVersion.Text = "全部 (也可自行输入)", Nothing, - If(FrmDownloadMod.TextSearchVersion.Text.Contains(".") OrElse FrmDownloadMod.TextSearchVersion.Text.Contains("w"), FrmDownloadMod.TextSearchVersion.Text, Nothing)) - If GameVersion IsNot Nothing AndAlso GameVersion.Contains(".") AndAlso Val(GameVersion.Split(".")(1)) < 14 AndAlso '1.14- - ModLoader = CompModLoaderType.Forge Then '选择了 Forge - ModLoader = CompModLoaderType.Any '此时,视作没有筛选 Mod Loader(因为部分老 Mod 没有设置自己支持的加载器) - End If - With Request - .SearchText = FrmDownloadMod.TextSearchName.Text - .GameVersion = GameVersion - .Tag = FrmDownloadMod.ComboSearchTag.SelectedItem.Tag - .ModLoader = ModLoader - .Source = CType(Val(FrmDownloadMod.ComboSearchSource.SelectedItem.Tag), CompSourceType) - End With - End If + Private Function LoaderInput() As CompProjectRequest + Dim Request As New CompProjectRequest(PageType, Storage, (Page + 1) * PageSize) + Dim GameVersion As String = If(TextSearchVersion.Text = "全部 (也可自行输入)", Nothing, + If(TextSearchVersion.Text.Contains(".") OrElse TextSearchVersion.Text.Contains("w"), TextSearchVersion.Text, Nothing)) + With Request + .SearchText = TextSearchName.Text + .GameVersion = GameVersion + .Tag = ComboSearchTag.SelectedItem.Tag + .ModLoader = If(PageType = CompType.Mod, Val(ComboSearchLoader.SelectedItem.Tag), CompModLoaderType.Any) + .Source = CType(Val(ComboSearchSource.SelectedItem.Tag), CompSourceType) + End With Return Request End Function +#End Region + + Public Storage As New CompProjectStorage + ''' + ''' 每页展示的结果数量。 + ''' + Public Const PageSize = 40 + Public Page As Integer = 0 + '结果 UI 化 Private Sub Load_OnFinish() Try - Log($"[Comp] 开始可视化 Mod 列表,已储藏 {Storage.Results.Count} 个结果,当前在第 {Page + 1} 页") + Log($"[Comp] 开始可视化{TypeNameSpaced}列表,已储藏 {Storage.Results.Count} 个结果,当前在第 {Page + 1} 页") '列表项 PanProjects.Children.Clear() For i = Math.Min(Page * PageSize, Storage.Results.Count - 1) To Math.Min((Page + 1) * PageSize - 1, Storage.Results.Count - 1) - PanProjects.Children.Add(Storage.Results(i).ToCompItem(Loader.Input.GameVersion Is Nothing, Loader.Input.ModLoader = CompModLoaderType.Any)) + PanProjects.Children.Add(Storage.Results(i).ToCompItem( + ShowMcVersionDesc:=Loader.Input.GameVersion Is Nothing, + ShowLoaderDesc:=Loader.Input.ModLoader = CompModLoaderType.Any AndAlso (PageType = CompType.Mod OrElse PageType = CompType.ModPack))) Next '页码 CardPages.Visibility = If(Storage.Results.Count > 40 OrElse @@ -94,9 +168,9 @@ HintError.Text = Storage.ErrorMessage End If '强制返回顶部 - PanBack.ScrollToTop() + ScrollToTop() Catch ex As Exception - Log(ex, "可视化 Mod 列表出错", LogLevel.Feedback) + Log(ex, $"可视化{TypeNameSpaced}列表出错", LogLevel.Feedback) End Try End Sub @@ -107,14 +181,13 @@ Dim ErrorMessage As String = "" If Loader.Error IsNot Nothing Then ErrorMessage = Loader.Error.Message If ErrorMessage.Contains("不是有效的 json 文件") Then - Log("[Download] 下载的 Mod 列表 json 文件损坏,已自动重试", LogLevel.Debug) - PageLoaderRestart() + Log($"[Download] 下载的{TypeNameSpaced}列表 json 文件损坏,已自动重试", LogLevel.Debug) + CType(Parent, MyPageRight).PageLoaderRestart() End If End Select End Sub '切换页码 - Private Sub BtnPageFirst_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageFirst.Click ChangePage(0) End Sub @@ -128,7 +201,7 @@ CardPages.IsEnabled = False Page = NewPage FrmMain.BackToTop() - Log($"[Download] Mod 切换到第 {Page + 1} 页") + Log($"[Download] {TypeName}:切换到第 {Page + 1} 页") RunInThread( Sub() Thread.Sleep(100) '等待向上滚的动画结束 @@ -166,7 +239,7 @@ If Not TextSearchVersion.IsDropDownOpen Then UpdateSearchLoaderVisibility() End Sub Private Sub UpdateSearchLoaderVisibility() Handles TextSearchVersion.DropDownClosed - If TextSearchVersion.Text.Contains(".") OrElse TextSearchVersion.Text.Contains("w") Then + If PageType = CompType.Mod AndAlso (TextSearchVersion.Text.Contains(".") OrElse TextSearchVersion.Text.Contains("w")) Then ComboSearchLoader.Visibility = Visibility.Visible Grid.SetColumnSpan(TextSearchVersion, 1) Else @@ -178,4 +251,9 @@ #End Region + '安装已有整合包按钮 + Private Sub BtnSearchInstallModPack_Click(sender As Object, e As EventArgs) Handles BtnSearchInstallModPack.Click + ModpackInstall() + End Sub + End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml similarity index 98% rename from Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml rename to Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml index 3a52922..eb4a37c 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageDownloadCompDetail" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb similarity index 70% rename from Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb rename to Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb index c8557a8..9d2dae5 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb @@ -4,23 +4,31 @@ #Region "加载器" Private CompFileLoader As New LoaderTask(Of Integer, List(Of CompFile))( - "Comp File", Sub(Task As LoaderTask(Of Integer, List(Of CompFile))) Task.Output = CompFilesGet(Project.Id, Project.FromCurseForge)) + "Comp File", + Sub(Task) + LoadTargetFromAdditional() + Dim Result = CompFilesGet(Project.Id, Project.FromCurseForge) + If Task.IsAborted Then Return + Task.Output = Result + End Sub) '初始化加载器信息 Private Sub PageDownloadCompDetail_Inited(sender As Object, e As EventArgs) Handles Me.Initialized - Project = FrmMain.PageCurrent.Additional(0) - TargetVersion = FrmMain.PageCurrent.Additional(2) - TargetLoader = FrmMain.PageCurrent.Additional(3) + LoadTargetFromAdditional() PageLoaderInit(Load, PanLoad, PanMain, CardIntro, CompFileLoader, AddressOf Load_OnFinish) End Sub - Private Sub PageDownloadCompDetail_Loaded(sender As Object, e As EventArgs) Handles Me.Loaded - 'Initialized 只会执行一次 + Public Sub LoadTargetFromAdditional() Handles Me.Loaded Project = FrmMain.PageCurrent.Additional(0) TargetVersion = FrmMain.PageCurrent.Additional(2) TargetLoader = FrmMain.PageCurrent.Additional(3) + PageType = FrmMain.PageCurrent.Additional(4) End Sub Private Project As CompProject Private TargetVersion As String, TargetLoader As CompModLoaderType + ''' + ''' 当前页面应展示的内容类别。可能为 Any。 + ''' + Private PageType As CompType '自动重试 Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged Select Case CompFileLoader.State @@ -62,18 +70,33 @@ Private VersionFilter As String Private IsMajorVersionFilter As Boolean '是否按大版本号筛选(1.21 / 1.20 / 1.19 / ...)而非小版本号(1.21.1 / 1.21 / 1.20.4 / ...) + '筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表) + Private Function GetResults() As List(Of CompFile) + Dim Results As List(Of CompFile) = CompFileLoader.Output + Select Case PageType + 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 + End Select + Return Results + End Function Private Sub Load_OnFinish() + Dim Results = GetResults() + '初始化筛选器 Dim VersionFilters As List(Of String) '按小版本号筛选? IsMajorVersionFilter = False - VersionFilters = CompFileLoader.Output.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)). + VersionFilters = Results.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)). Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList '按大版本号筛选? If VersionFilters.Count >= 9 Then IsMajorVersionFilter = True - VersionFilters = CompFileLoader.Output.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)). + VersionFilters = Results.SelectMany(Function(v) v.GameVersions).Select(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True)). Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList End If @@ -100,7 +123,7 @@ '自动选择 Dim ToCheck As MyRadioButton = Nothing If TargetVersion <> "" Then - Dim TargetFile = CompFileLoader.Output.FirstOrDefault(Function(v) v.GameVersions.Contains(TargetVersion)) + Dim TargetFile = Results.FirstOrDefault(Function(v) v.GameVersions.Contains(TargetVersion)) If TargetFile IsNot Nothing Then Dim TargetGroup = GetGroupedVersionName(TargetVersion, IsMajorVersionFilter, True) For Each Button As MyRadioButton In PanFilter.Children @@ -118,24 +141,26 @@ UpdateFilterResult() End Sub Private Sub UpdateFilterResult() + Dim Results = GetResults() + Dim TargetCardName As String = If(TargetVersion <> "" OrElse TargetLoader <> CompModLoaderType.Any, $"所选版本:{If(TargetLoader <> CompModLoaderType.Any, TargetLoader.ToString & " ", "")}{TargetVersion}", "") '归类到卡片下 Dim Dict As New SortedDictionary(Of String, List(Of CompFile))(New CardSorter(TargetCardName)) Dict.Add("其他版本", New List(Of CompFile)) Dim SupportedLoaders As New List(Of Integer)([Enum].GetValues(GetType(CompModLoaderType))) - For Each Version As CompFile In CompFileLoader.Output + For Each Version As CompFile In Results For Each GameVersion In Version.GameVersions '检查是否符合版本筛选器 If VersionFilter IsNot Nothing AndAlso GetGroupedVersionName(GameVersion, IsMajorVersionFilter, True) <> VersionFilter Then Continue For '决定添加到哪个卡片 - Dim Ver As String = GetGroupedVersionName(GameVersion, False, False) + Dim VerName As String = GetGroupedVersionName(GameVersion, False, False) '遍历加入的加载器列表 Dim Loaders As New List(Of String) - If Project.ModLoaders.Count > 1 AndAlso '至少有两个加载器 - Project.Type = CompType.Mod AndAlso '是 Mod - Ver.StartsWith("1.") Then '不是 “快照版本” 之类的 + If Project.ModLoaders.Count > 1 AndAlso '工程至少有两个加载器 + Version.Type = CompType.Mod AndAlso '是 Mod + VerName.StartsWith("1.") Then '不是 “快照版本” 之类的 For Each Loader In Version.ModLoaders If Loader = CompModLoaderType.Quilt AndAlso Setup.Get("ToolDownloadIgnoreQuilt") Then Continue For If SupportedLoaders.Contains(Loader) Then Loaders.Add(Loader.ToString & " ") @@ -144,16 +169,16 @@ If Not Loaders.Any() Then Loaders.Add("") '保底加一个空的,确保它在一张卡片里 '实际添加 For Each Loader In Loaders - Dim TargetCard As String = Loader & Ver + Dim TargetCard As String = Loader & VerName If Not Dict.ContainsKey(TargetCard) Then Dict.Add(TargetCard, New List(Of CompFile)) If Not Dict(TargetCard).Contains(Version) Then Dict(TargetCard).Add(Version) Next Next Next '添加筛选的版本的卡片 - If TargetCardName <> "" Then + If TargetCardName <> "" AndAlso (VersionFilter Is Nothing OrElse GetGroupedVersionName(TargetVersion, IsMajorVersionFilter, True).StartsWithF(VersionFilter)) Then Dict.Add(TargetCardName, New List(Of CompFile)) - For Each Version As CompFile In CompFileLoader.Output + For Each Version As CompFile In Results If Version.GameVersions.Contains(TargetVersion) AndAlso (TargetLoader = CompModLoaderType.Any OrElse Version.ModLoaders.Contains(TargetLoader)) Then '检查是否符合版本筛选器 @@ -169,7 +194,7 @@ For Each Pair As KeyValuePair(Of String, List(Of CompFile)) In Dict If Not Pair.Value.Any() Then Continue For '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key, .Margin = New Thickness(0, 0, 0, 15), .SwapType = If(Project.Type = CompType.ModPack, 9, 8)} 'FUTURE: Res + 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 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 @@ -178,18 +203,18 @@ 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(Project.Type = CompType.ModPack, 9, 8), Pair.Key) 'FUTURE: Res + MyCard.StackInstall(NewStack, If(PageType = CompType.ModPack, 9, 8), Pair.Key) '9 是安装,8 是另存为 Else - NewCard.IsSwaped = True + NewCard.IsSwapped = True End If '增加提示 If Pair.Key = "其他版本" Then - NewStack.Children.Add(New MyHint With {.Text = "由于版本信息更新缓慢,可能无法识别刚更新的 MC 版本,只需等待几天即可自动恢复正常。", .IsWarn = False, .Margin = New Thickness(0, 0, 0, 7)}) + NewStack.Children.Add(New MyHint With {.Text = "由于版本信息更新缓慢,可能无法识别刚更新的 MC 版本。几天后即可正常识别。", .Theme = MyHint.Themes.Yellow, .Margin = New Thickness(5, 0, 0, 8)}) End If Next '如果只有一张卡片,展开第一张卡片 If PanResults.Children.Count = 1 Then - CType(PanResults.Children(0), MyCard).IsSwaped = False + CType(PanResults.Children(0), MyCard).IsSwapped = False End If Catch ex As Exception Log(ex, "可视化工程下载列表出错", LogLevel.Feedback) @@ -237,7 +262,7 @@ AniControlEnabled -= 1 End Sub - '整合包下载(安装) + '整合包安装 Public Sub Install_Click(sender As MyListItem, e As EventArgs) Try @@ -250,7 +275,7 @@ Dim Validate As New ValidateFolderName(PathMcFolder & "versions") If Validate.Validate(PackName) <> "" Then PackName = "" Dim VersionName As String = MyMsgBoxInput("输入版本名称", "", PackName, New ObjectModel.Collection(Of Validate) From {Validate}) - If String.IsNullOrEmpty(VersionName) Then Exit Sub + If String.IsNullOrEmpty(VersionName) Then Return '构造步骤加载器 Dim Loaders As New List(Of LoaderBase) @@ -265,11 +290,11 @@ Sub(MyLoader) Select Case MyLoader.State Case LoadState.Failed - Hint(MyLoader.Name & "失败:" & GetExceptionSummary(MyLoader.Error), HintType.Critical) + Hint(MyLoader.Name & "失败:" & MyLoader.Error.GetBrief(), HintType.Critical) Case LoadState.Aborted Hint(MyLoader.Name & "已取消!", HintType.Info) Case LoadState.Loading - Exit Sub '不重新加载版本列表 + Return '不重新加载版本列表 End Select McInstallFailedClearFolder(MyLoader) End Sub} @@ -282,77 +307,89 @@ Log(ex, "下载资源整合包失败", LogLevel.Feedback) End Try End Sub - 'Mod、资源包下载;整合包另存为 - Public Shared CachedFolder As String = Nothing '仅在本次缓存的下载文件夹 + '资源下载;整合包另存为 + Public Shared CachedFolder As New Dictionary(Of CompType, String) '仅在本次缓存的下载文件夹 Public Sub Save_Click(sender As Object, e As EventArgs) Dim File As CompFile = If(TypeOf sender Is MyListItem, sender, sender.Parent).Tag RunInNewThread( Sub() Try - Dim Desc As String = If(Project.Type = CompType.ModPack, "整合包", If(Project.Type = CompType.Mod, "Mod ", "资源包")) + Dim Desc As String = Nothing + Select Case File.Type + Case CompType.ModPack : Desc = "整合包" + Case CompType.Mod : Desc = "Mod " + Case CompType.ResourcePack : Desc = "资源包" + Case CompType.Shader : Desc = "光影包" + Case CompType.DataPack : Desc = "数据包" + End Select '确认默认保存位置 Dim DefaultFolder As String = Nothing - If Project.Type = CompType.Mod Then - '获取 Mod 所需的加载器种类 - Dim AllowForge As Boolean? = Nothing, AllowFabric As Boolean? = Nothing - If File.ModLoaders.Any Then '从文件中获取 - AllowForge = File.ModLoaders.Contains(CompModLoaderType.Forge) OrElse File.ModLoaders.Contains(CompModLoaderType.NeoForge) - AllowFabric = File.ModLoaders.Contains(CompModLoaderType.Fabric) - ElseIf Project.ModLoaders.Any Then '从工程中获取 - AllowForge = Project.ModLoaders.Contains(CompModLoaderType.Forge) OrElse File.ModLoaders.Contains(CompModLoaderType.NeoForge) - AllowFabric = Project.ModLoaders.Contains(CompModLoaderType.Fabric) + If File.Type <> CompType.ModPack Then + Dim SubFolder As String = Nothing + Select Case Project.Type + Case CompType.Mod : SubFolder = "mods\" + Case CompType.ResourcePack : SubFolder = "resourcepacks\" + Case CompType.Shader : SubFolder = "shaderpacks\" + Case CompType.DataPack : SubFolder = "" '导航到版本根目录 + End Select + Dim IsVersionSuitable As Func(Of McVersion, Boolean) = Nothing + '获取资源所需的加载器 + Dim AllowedLoaders As New List(Of CompModLoaderType) + If File.ModLoaders.Any Then + AllowedLoaders = File.ModLoaders + ElseIf Project.ModLoaders.Any Then + AllowedLoaders = Project.ModLoaders End If - If AllowForge IsNot Nothing AndAlso Not AllowForge AndAlso - AllowFabric IsNot Nothing AndAlso Not AllowFabric Then - AllowForge = Nothing : AllowFabric = Nothing - End If - Log("[Comp] 允许 Forge:" & If(AllowForge, "未知") & ",允许 Fabric:" & If(AllowFabric, "未知")) - '判断某个版本是否符合要求 - Dim IsVersionSuitable As Func(Of McVersion, Boolean) = + Log($"[Comp] {Desc}要求的加载器种类:" & If(AllowedLoaders.Any(), AllowedLoaders.Join(" / "), "无要求")) + '判断某个版本是否符合资源要求 + IsVersionSuitable = Function(Version) If Not Version.IsLoaded Then Version.Load() - If Not Version.Modable Then Return False - If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso - Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Version.Version.McName) Then Return False - If AllowForge Is Nothing OrElse AllowFabric Is Nothing Then Return True - If AllowForge AndAlso (Version.Version.HasForge OrElse Version.Version.HasNeoForge) Then Return True - If AllowFabric AndAlso Version.Version.HasFabric Then Return True + '只对 Mod 和数据包进行版本检测 + If Project.Type = CompType.Mod OrElse Project.Type = CompType.DataPack Then + If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso + Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Version.Version.McName) Then Return False + End If + '加载器 + If Not AllowedLoaders.Any() Then Return True '无要求 + If AllowedLoaders.Contains(CompModLoaderType.Forge) AndAlso Version.Version.HasForge Then Return True + If AllowedLoaders.Contains(CompModLoaderType.Fabric) AndAlso Version.Version.HasFabric Then Return True + If AllowedLoaders.Contains(CompModLoaderType.NeoForge) AndAlso Version.Version.HasNeoForge Then Return True + If AllowedLoaders.Contains(CompModLoaderType.LiteLoader) AndAlso Version.Version.HasLiteLoader Then Return True Return False End Function - '获取 Mod 默认下载位置 - If CachedFolder IsNot Nothing Then - DefaultFolder = CachedFolder - Log("[Comp] 使用上次下载时的文件夹作为默认下载位置") + '获取常规资源默认下载位置 + If CachedFolder.ContainsKey(Project.Type) Then + DefaultFolder = CachedFolder(Project.Type) + Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}") ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then - DefaultFolder = McVersionCurrent.PathIndie & "mods\" + DefaultFolder = $"{McVersionCurrent.PathIndie}{SubFolder}" Directory.CreateDirectory(DefaultFolder) - Log("[Comp] 使用当前版本的 mods 文件夹作为默认下载位置(" & McVersionCurrent.Name & ")") + Log($"[Comp] 使用当前版本作为默认下载位置:{DefaultFolder}") Else + '查找所有可能的版本 Dim NeedLoad As Boolean = McVersionListLoader.State <> LoadState.Finished If NeedLoad Then Hint("正在查找适合的游戏版本……") LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\", WaitForExit:=True) End If - Dim SuitableVersions As New List(Of McVersion) - For Each Version As McVersion In McVersionList.Values.SelectMany(Function(l) l) - If IsVersionSuitable(Version) Then SuitableVersions.Add(Version) - Next - If Not SuitableVersions.Any() Then + Dim SuitableVersions = McVersionList.Values.SelectMany(Function(l) l).Where(Function(v) IsVersionSuitable(v)). + Select(Function(v) New DirectoryInfo($"{v.PathIndie}{SubFolder}")) + If SuitableVersions.Any Then + Dim SelectedVersion = SuitableVersions. + OrderByDescending(Function(Dir) If(Dir.Exists, Dir.LastWriteTimeUtc, Date.MinValue)). '先按文件夹更改时间降序 + ThenByDescending(Function(Dir) If(Dir.Exists, Dir.GetFiles().Length, -1)). '再按文件夹中的文件数量降序 + First() + DefaultFolder = SelectedVersion.FullName + Directory.CreateDirectory(DefaultFolder) + Log($"[Comp] 使用适合的游戏版本作为默认下载位置:{DefaultFolder}") + Else DefaultFolder = PathMcFolder If NeedLoad Then - Hint("当前 MC 文件夹中没有找到适合这个 Mod 的版本!") + Hint("当前 MC 文件夹中没有找到适合此资源的版本!") Else Log("[Comp] 由于当前版本不兼容,使用当前的 MC 文件夹作为默认下载位置") End If - Else '选择 Mod 数量最多的版本 - Dim SelectedVersion = SuitableVersions.OrderBy( - Function(v) - Dim Info As New DirectoryInfo(v.PathIndie & "mods\") - Return If(Info.Exists, Info.GetFiles().Length, -1) - End Function).LastOrDefault() - DefaultFolder = SelectedVersion.PathIndie & "mods\" - Directory.CreateDirectory(DefaultFolder) - Log("[Comp] 使用适合的游戏版本作为默认下载位置(" & SelectedVersion.Name & ")") End If End If End If @@ -382,13 +419,13 @@ Dim Target As String Target = SelectSaveFile("选择保存位置", FileName, Desc & "文件|" & - If(Project.Type = CompType.Mod, + If(File.Type = CompType.Mod, If(File.FileName.EndsWith(".litemod"), "*.litemod", "*.jar"), If(File.FileName.EndsWith(".mrpack"), "*.mrpack", "*.zip")), DefaultFolder) - If Not Target.Contains("\") Then Exit Sub + If Not Target.Contains("\") Then Return '构造步骤加载器 - Dim LoaderName As String = Desc & "下载:" & GetFileNameWithoutExtentionFromPath(Target) & " " - If Target <> DefaultFolder AndAlso Project.Type = CompType.Mod Then CachedFolder = GetPathFromFullPath(Target) + Dim LoaderName As String = Desc & "下载:" & IO.Path.GetFileNameWithoutExtension(Target) & " " + If Target <> DefaultFolder Then CachedFolder(Project.Type) = GetPathFromFullPath(Target) Dim Loaders As New List(Of LoaderBase) Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True}) '启动 diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadDataPack.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadDataPack.xaml new file mode 100644 index 0000000..c07469c --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadDataPack.xaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadDataPack.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadDataPack.xaml.vb new file mode 100644 index 0000000..4c5ac83 --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadDataPack.xaml.vb @@ -0,0 +1,2 @@ +Public Class PageDownloadDataPack +End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml new file mode 100644 index 0000000..d000fd8 --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml.vb new file mode 100644 index 0000000..1f8560e --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadMod.xaml.vb @@ -0,0 +1,2 @@ +Public Class PageDownloadMod +End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml new file mode 100644 index 0000000..0954583 --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml.vb new file mode 100644 index 0000000..5c06f4e --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadPack.xaml.vb @@ -0,0 +1,2 @@ +Public Class PageDownloadPack +End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadResourcePack.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadResourcePack.xaml new file mode 100644 index 0000000..ec7ead0 --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadResourcePack.xaml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadResourcePack.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadResourcePack.xaml.vb new file mode 100644 index 0000000..765d13f --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadResourcePack.xaml.vb @@ -0,0 +1,2 @@ +Public Class PageDownloadResourcePack +End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadShader.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadShader.xaml new file mode 100644 index 0000000..9d0ca3b --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadShader.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadShader.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadShader.xaml.vb new file mode 100644 index 0000000..ce5c1ec --- /dev/null +++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadShader.xaml.vb @@ -0,0 +1,2 @@ +Public Class PageDownloadShader +End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb index a748106..a19a93c 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb @@ -217,14 +217,14 @@ Public Module ModDownloadLib Dim Id = Version.Title Dim JsonUrl = Version.Tag("url").ToString Dim VersionFolder As String = SelectFolder() - If Not VersionFolder.Contains("\") Then Exit Sub + If Not VersionFolder.Contains("\") Then Return VersionFolder = VersionFolder & Id & "\" '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"Minecraft {Id} 服务端下载" Then Continue For Hint("该服务端正在下载中!", HintType.Critical) - Exit Sub + Return Next Dim Loaders As New List(Of LoaderBase) @@ -244,7 +244,7 @@ Public Module ModDownloadLib Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Critical) Thread.Sleep(2000) '等玩家把上一个提示看完 Task.Abort() - Exit Sub + Return End If Dim JarUrl As String = McVersion.JsonObject("downloads")("server")("url") Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(McVersion.JsonObject("downloads")("server")("size"), -1), Hash:=McVersion.JsonObject("downloads")("server")("sha1")) @@ -294,14 +294,14 @@ pause" Dim Id = Version.Title Dim JsonUrl = Version.Tag("url").ToString Dim VersionFolder As String = SelectFolder() - If Not VersionFolder.Contains("\") Then Exit Sub + If Not VersionFolder.Contains("\") Then Return VersionFolder = VersionFolder & Id & "\" '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For Hint("该版本正在下载中!", HintType.Critical) - Exit Sub + Return Next Dim Loaders As New List(Of LoaderBase) @@ -383,7 +383,7 @@ pause" WikiName = "Java版Pre-classic_" & Id Else Log("[Error] 未知的版本格式:" & Id & "。", LogLevel.Feedback) - Exit Sub + Return End If OpenWebsite("https://zh.minecraft.wiki/w/" & WikiName.Replace("_experimental-snapshot-", "-exp")) End Sub @@ -396,13 +396,13 @@ pause" Try Dim Id As String = DownloadInfo.NameVersion Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.NameFile, "OptiFine Jar (*.jar)|*.jar") - If Not Target.Contains("\") Then Exit Sub + If Not Target.Contains("\") Then Return '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"OptiFine {DownloadInfo.NameDisplay} 下载" Then Continue For Hint("该版本正在下载中!", HintType.Critical) - Exit Sub + Return Next Dim Loader As New LoaderCombo(Of DlOptiFineListEntry)("OptiFine " & DownloadInfo.NameDisplay & " 下载", McDownloadOptiFineSaveLoader(DownloadInfo, Target)) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly} @@ -434,7 +434,7 @@ pause" End Try '检查下载结果 Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 0)) - If Task.IsAborted Then Exit Sub + If Task.IsAborted Then Return If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。") End If End SyncLock @@ -449,7 +449,7 @@ pause" '开始启动 SyncLock InstallSyncLock Dim Info = New ProcessStartInfo With { - .FileName = Java.PathJavaw, + .FileName = Java.PathJava, .Arguments = Arguments, .UseShellExecute = False, .CreateNoWindow = True, @@ -570,7 +570,8 @@ pause" '官方源 Dim PageData As String Try - PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True) + PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, + Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", SimulateBrowserHeaders:=True) Task.Progress = 0.8 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0)) Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last) @@ -584,7 +585,7 @@ pause" Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("等待原版下载", Sub(Task As LoaderTask(Of List(Of NetFile), Boolean)) '等待原版文件下载完成 - If ClientDownloadLoader Is Nothing Then Exit Sub + If ClientDownloadLoader Is Nothing Then Return Dim TargetLoaders As List(Of LoaderBase) = ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName). Where(Function(l) l.State <> LoadState.Finished).ToList @@ -593,9 +594,9 @@ pause" TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList Thread.Sleep(50) Loop - If Task.IsAborted Then Exit Sub + If Task.IsAborted Then Return '拷贝原版文件 - If Not IsCustomFolder Then Exit Sub + If Not IsCustomFolder Then Return SyncLock VanillaSyncLock Dim ClientName As String = GetFolderNameFromPath(ClientFolder) Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit) @@ -707,7 +708,7 @@ Retry: '下载支持库 If FixLibrary Then Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析 OptiFine 支持库文件", - Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) + Sub(Task) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) Loaders.Add(New LoaderDownload("下载 OptiFine 支持库文件", New List(Of NetFile)) With {.ProgressWeight = 4}) End If @@ -733,7 +734,8 @@ Retry: '官方源 Dim PageData As String Try - PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True) + PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, + Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", SimulateBrowserHeaders:=True) Task.Progress = 0.8 Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0)) Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last) @@ -831,7 +833,7 @@ Retry: For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For Hint("该版本正在下载中!", HintType.Critical) - Exit Sub + Return Next '已有版本检查 @@ -840,7 +842,7 @@ Retry: File.Delete(VersionFolder & VersionName & ".jar") File.Delete(VersionFolder & VersionName & ".json") Else - Exit Sub + Return End If End If @@ -859,13 +861,13 @@ Retry: Try Dim Id As String = DownloadInfo.Inherit Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.FileName.Replace("-SNAPSHOT", ""), "LiteLoader 安装器 (*.jar)|*.jar") - If Not Target.Contains("\") Then Exit Sub + If Not Target.Contains("\") Then Return '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For Hint("该版本正在下载中!", HintType.Critical) - Exit Sub + Return Next '构造步骤加载器 @@ -1041,13 +1043,13 @@ Retry: Dim Target As String = SelectSaveFile("选择保存位置", $"{Info.LoaderName}-{Info.Inherit}-{Info.VersionName}.{Info.FileExtension}", $"{Info.LoaderName} 安装器 (*.{Info.FileExtension})|*.{Info.FileExtension}") Dim DisplayName As String = $"{Info.LoaderName} {Info.Inherit} - {Info.VersionName}" - If Not Target.Contains("\") Then Exit Sub + If Not Target.Contains("\") Then Return '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"{DisplayName} 下载" Then Continue For Hint("该版本正在下载中!", HintType.Critical) - Exit Sub + Return Next '获取下载地址 @@ -1103,7 +1105,7 @@ Retry: End Try '检查下载结果 Java = JavaSelect("已取消安装。", New Version(1, 8, 0, 60)) - If Task.IsAborted Then Exit Sub + If Task.IsAborted Then Return If Java Is Nothing Then Throw New Exception("由于未找到 Java,已取消安装。") End If End SyncLock @@ -1118,7 +1120,7 @@ Retry: '开始启动 SyncLock InstallSyncLock Dim Info = New ProcessStartInfo With { - .FileName = Java.PathJavaw, + .FileName = Java.PathJava, .Arguments = Arguments, .UseShellExecute = False, .CreateNoWindow = True, @@ -1188,8 +1190,8 @@ Retry: outputWaitHandle.WaitOne(10000) errorWaitHandle.WaitOne(10000) process.Dispose() - '检查是否安装成功:最后两行中是否有 true(true 可能在倒数第二行,见 #832) - If LastResults.Last = "true" OrElse (LastResults.Count >= 2 AndAlso LastResults(LastResults.Count - 2) = "true") Then Exit Sub + '检查是否安装成功:最后 5 行中是否有 true(true 可能在倒数数行,见 #832) + If LastResults.Reverse().Take(5).Any(Function(l) l = "true") Then Return Log(Join(LastResults, vbCrLf)) Dim LastLines As String = "" For i As Integer = Math.Max(0, LastResults.Count - 5) To LastResults.Count - 1 '最后 5 行 @@ -1237,7 +1239,7 @@ Retry: Task.Progress = 0.91 Case Else If ModeDebug Then Log("[Installer] " & Content) - Exit Sub + Return End Select Log("[Installer] " & Content) End Sub @@ -1282,7 +1284,7 @@ Retry: For Each ForgeVersion In ForgeLoader.Output If VersionSortInteger(ForgeVersion.Version.ToString, LoaderVersion) = 0 Then Info = ForgeVersion - Exit Sub + Return End If Next Throw New Exception($"未能找到 {LoaderName} " & Inherit & "-" & LoaderVersion & " 的详细信息!") @@ -1340,13 +1342,13 @@ Retry: If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then '下载原版 Json 文件 Task.Progress = 0.4 - Dim RawJson As JObject = GetJson(NetGetCodeByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True)) + Dim RawJson As JObject = GetJson(NetRequestByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True)) '[net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg] Dim OriginalName As String = Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).BeforeFirst("@") Dim Address = McLibGet(OriginalName).Replace(".jar", "-mappings." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1)) Dim ClientMappings As JToken = RawJson("downloads")("client_mappings") Libs.Add(New McLibToken With { - .IsJumpLoader = False, .IsNatives = False, .LocalPath = Address, .OriginalName = OriginalName, + .IsNatives = False, .LocalPath = Address, .OriginalName = OriginalName, .Url = ClientMappings("url"), .Size = ClientMappings("size"), .SHA1 = ClientMappings("sha1")}) Log($"[Download] 需要下载 Mappings:{ClientMappings("url")} (SHA1: {ClientMappings("sha1")})") End If @@ -1385,7 +1387,7 @@ Retry: #End Region #Region "原版文件" '等待原版文件下载完成 - If ClientDownloadLoader Is Nothing Then Exit Sub + If ClientDownloadLoader Is Nothing Then Return Dim TargetLoaders As List(Of LoaderBase) = ClientDownloadLoader.GetLoaderList.Where(Function(l) l.Name = McDownloadClientLibName OrElse l.Name = McDownloadClientJsonName). Where(Function(l) l.State <> LoadState.Finished).ToList() @@ -1394,9 +1396,9 @@ Retry: TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList Thread.Sleep(50) Loop - If Task.IsAborted Then Exit Sub + If Task.IsAborted Then Return '拷贝原版文件 - If Not IsCustomFolder Then Exit Sub + If Not IsCustomFolder Then Return SyncLock VanillaSyncLock Dim ClientName As String = GetFolderNameFromPath(ClientFolder) Directory.CreateDirectory(McFolder & "versions\" & Inherit) @@ -1639,32 +1641,33 @@ Retry: ''' 尝试刷新 Forge 推荐版本缓存。 ''' Public Sub McDownloadForgeRecommendedRefresh() - If IsForgeRecommendedRefreshed Then Exit Sub + If IsForgeRecommendedRefreshed Then Return IsForgeRecommendedRefreshed = True - RunInNewThread(Sub() - Try - Log("[Download] 刷新 Forge 推荐版本缓存开始") - Dim Result As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/forge/promos") - If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")") - Dim ResultJson As JContainer = GetJson(Result) - '获取所有推荐版本列表 - Dim RecommendedList As New List(Of String) - For Each Version As JObject In ResultJson - If Version("name") Is Nothing OrElse Version("build") Is Nothing Then Continue For - Dim Name As String = Version("name") - If Not Name.EndsWithF("-recommended") Then Continue For - '内容为:"1.15.2":"31.2.0" - RecommendedList.Add("""" & Name.Replace("-recommended", """:""" & Version("build")("version").ToString & """")) - Next - If RecommendedList.Count < 5 Then Throw New Exception("获取的推荐版本数过少(" & Result & ")") - '保存 - Dim CacheJson As String = "{" & Join(RecommendedList, ",") & "}" - WriteFile(PathTemp & "Cache\ForgeRecommendedList.json", CacheJson) - Log("[Download] 刷新 Forge 推荐版本缓存成功") - Catch ex As Exception - Log(ex, "刷新 Forge 推荐版本缓存失败") - End Try - End Sub, "ForgeRecommendedRefresh") + RunInNewThread( + Sub() + Try + Log("[Download] 刷新 Forge 推荐版本缓存开始") + Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/promos", RequireJson:=True) + If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")") + Dim ResultJson As JContainer = GetJson(Result) + '获取所有推荐版本列表 + Dim RecommendedList As New List(Of String) + For Each Version As JObject In ResultJson + If Version("name") Is Nothing OrElse Version("build") Is Nothing Then Continue For + Dim Name As String = Version("name") + If Not Name.EndsWithF("-recommended") Then Continue For + '内容为:"1.15.2":"31.2.0" + RecommendedList.Add("""" & Name.Replace("-recommended", """:""" & Version("build")("version").ToString & """")) + Next + If RecommendedList.Count < 5 Then Throw New Exception("获取的推荐版本数过少(" & Result & ")") + '保存 + Dim CacheJson As String = "{" & Join(RecommendedList, ",") & "}" + WriteFile(PathTemp & "Cache\ForgeRecommendedList.json", CacheJson) + Log("[Download] 刷新 Forge 推荐版本缓存成功") + Catch ex As Exception + Log(ex, "刷新 Forge 推荐版本缓存失败") + End Try + End Sub, "ForgeRecommendedRefresh") End Sub Private IsForgeRecommendedRefreshed As Boolean = False @@ -1795,13 +1798,13 @@ Retry: Dim FileName As String = GetFileNameFromPath(Url) Dim Version As String = GetFileNameFromPath(DownloadInfo("version").ToString) Dim Target As String = SelectSaveFile("选择保存位置", FileName, "Fabric 安装器 (*.jar)|*.jar") - If Not Target.Contains("\") Then Exit Sub + If Not Target.Contains("\") Then Return '重复任务检查 For Each OngoingLoader In LoaderTaskbar.ToList() If OngoingLoader.Name <> $"Fabric {Version} 安装器下载" Then Continue For Hint("该版本正在下载中!", HintType.Critical) - Exit Sub + Return Next '构造步骤加载器 @@ -1988,7 +1991,7 @@ Retry: Case LoadState.Finished Hint(Loader.Name & "成功!", HintType.Finish) Case LoadState.Failed - Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical) + Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical) Case LoadState.Aborted Hint(Loader.Name & "已取消!", HintType.Info) End Select @@ -2002,11 +2005,11 @@ Retry: WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") '清空缓存(合并安装会先生成文件夹,这会在刷新时误判为可以使用缓存) Hint(Loader.Name & "成功!", HintType.Finish) Case LoadState.Failed - Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical) + Hint(Loader.Name & "失败:" & Loader.Error.GetBrief(), HintType.Critical) Case LoadState.Aborted Hint(Loader.Name & "已取消!", HintType.Info) Case LoadState.Loading - Exit Sub '不重新加载版本列表 + Return '不重新加载版本列表 End Select McInstallFailedClearFolder(Loader) LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") @@ -2055,7 +2058,7 @@ Retry: ''' 获取合并安装加载器列表,并进行前期的缓存清理与 Java 检查工作。 ''' ''' - Public Function McInstallLoader(Request As McInstallRequest, Optional DontFixLibraries As Boolean = False) As List(Of LoaderBase) + Public Function McInstallLoader(Request As McInstallRequest) As List(Of LoaderBase) '获取缓存目录(安装 Mod 加载器的文件夹不能包含空格) Dim TempMcFolder As String = RequestTaskTempFolder(Request.OptiFineEntry IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing) @@ -2086,17 +2089,13 @@ Retry: Dim LiteLoaderFolder As String = Nothing If Request.LiteLoaderEntry IsNot Nothing Then LiteLoaderFolder = TempMcFolder & "versions\" & Request.MinecraftName & "-LiteLoader" - '决定版本隔离情况(#5970) - Dim Modable As Boolean = Request.FabricVersion IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing - Dim Version As New McVersion(VersionFolder) - Version.InitPathIndie(Modable) - Dim ModsFolder As String = If(Modable, VersionFolder, PathMcFolder) & "mods\" - '判断 OptiFine 是否作为 Mod 进行下载 + Dim Modable As Boolean = Request.FabricVersion IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing + Dim ModsTempFolder As String = TempMcFolder & "mods\" Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso Modable '选择了 OptiFine 与任意 Mod 加载器 If OptiFineAsMod Then Log("[Download] OptiFine 将作为 Mod 进行下载") - OptiFineFolder = ModsFolder + OptiFineFolder = ModsTempFolder End If '记录日志 @@ -2118,57 +2117,71 @@ Retry: LoaderList.Add(New LoaderTask(Of Integer, Integer)("添加忽略标识", Sub() WriteFile(VersionFolder & ".pclignore", "用于临时地在 PCL 的版本列表中屏蔽此版本。")) With {.Show = False, .Block = False}) 'Fabric API If Request.FabricApi IsNot Nothing Then - LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(ModsFolder)}) With {.ProgressWeight = 3, .Block = False}) + LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(ModsTempFolder)}) With {.ProgressWeight = 3, .Block = False}) End If 'OptiFabric If Request.OptiFabric IsNot Nothing Then - LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(ModsFolder)}) With {.ProgressWeight = 3, .Block = False}) + LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(ModsTempFolder)}) With {.ProgressWeight = 3, .Block = False}) End If '原版 - Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.TargetVersionName)) With {.Show = False, .ProgressWeight = 39, .Block = Request.ForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing} + Dim ClientLoader = New LoaderCombo(Of String)("下载原版 " & Request.MinecraftName, McDownloadClientLoader(Request.MinecraftName, Request.MinecraftJson, Request.TargetVersionName)) With {.Show = False, .ProgressWeight = 39, + .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.OptiFineEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing} LoaderList.Add(ClientLoader) 'OptiFine If Request.OptiFineEntry IsNot Nothing Then If OptiFineAsMod Then - LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineSaveLoader(Request.OptiFineEntry, OptiFineFolder & Request.OptiFineEntry.NameFile)) With {.Show = False, .ProgressWeight = 16, .Block = Request.ForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) + LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineSaveLoader(Request.OptiFineEntry, OptiFineFolder & Request.OptiFineEntry.NameFile)) With {.Show = False, .ProgressWeight = 16, + .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) Else - LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder, False)) With {.Show = False, .ProgressWeight = 24, .Block = Request.ForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) + LoaderList.Add(New LoaderCombo(Of String)("下载 OptiFine " & Request.OptiFineEntry.NameDisplay, McDownloadOptiFineLoader(Request.OptiFineEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder, False)) With {.Show = False, .ProgressWeight = 24, + .Block = Request.ForgeVersion Is Nothing AndAlso Request.NeoForgeVersion Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) End If End If 'Forge If Request.ForgeVersion IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgelikeLoader(False, Request.ForgeVersion, "forge-" & Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25, .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing AndAlso Request.NeoForgeEntry Is Nothing}) + LoaderList.Add(New LoaderCombo(Of String)("下载 Forge " & Request.ForgeVersion, McDownloadForgelikeLoader(False, Request.ForgeVersion, "forge-" & Request.ForgeVersion, Request.MinecraftName, Request.ForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25, + .Block = Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing AndAlso Request.NeoForgeEntry Is Nothing}) End If 'NeoForge If Request.NeoForgeVersion IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 NeoForge " & Request.NeoForgeVersion, McDownloadForgelikeLoader(True, Request.NeoForgeVersion, "neoforge-" & Request.NeoForgeVersion, Request.MinecraftName, Request.NeoForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25, .Block = Request.ForgeEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) + LoaderList.Add(New LoaderCombo(Of String)("下载 NeoForge " & Request.NeoForgeVersion, McDownloadForgelikeLoader(True, Request.NeoForgeVersion, "neoforge-" & Request.NeoForgeVersion, Request.MinecraftName, Request.NeoForgeEntry, TempMcFolder, ClientLoader, Request.TargetVersionFolder)) With {.Show = False, .ProgressWeight = 25, + .Block = Request.ForgeEntry Is Nothing AndAlso Request.FabricVersion Is Nothing AndAlso Request.LiteLoaderEntry Is Nothing}) End If 'LiteLoader If Request.LiteLoaderEntry IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 LiteLoader " & Request.MinecraftName, McDownloadLiteLoaderLoader(Request.LiteLoaderEntry, TempMcFolder, ClientLoader, False)) With {.Show = False, .ProgressWeight = 1, .Block = Request.FabricVersion Is Nothing}) + LoaderList.Add(New LoaderCombo(Of String)("下载 LiteLoader " & Request.MinecraftName, McDownloadLiteLoaderLoader(Request.LiteLoaderEntry, TempMcFolder, ClientLoader, False)) With {.Show = False, .ProgressWeight = 1, + .Block = Request.FabricVersion Is Nothing}) End If 'Fabric If Request.FabricVersion IsNot Nothing Then - LoaderList.Add(New LoaderCombo(Of String)("下载 Fabric " & Request.FabricVersion, McDownloadFabricLoader(Request.FabricVersion, Request.MinecraftName, TempMcFolder, False)) With {.Show = False, .ProgressWeight = 2, .Block = True}) + LoaderList.Add(New LoaderCombo(Of String)("下载 Fabric " & Request.FabricVersion, McDownloadFabricLoader(Request.FabricVersion, Request.MinecraftName, TempMcFolder, False)) With {.Show = False, .ProgressWeight = 2, + .Block = True}) End If '合并安装 LoaderList.Add(New LoaderTask(Of String, String)("安装游戏", - Sub(Task As LoaderTask(Of String, String)) - InstallMerge(VersionFolder, VersionFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder) - Task.Progress = 0.3 - If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries") - If Directory.Exists(TempMcFolder & "mods") Then CopyDirectory(TempMcFolder & "mods", ModsFolder) - '新建 mods 文件夹 - If Request.ForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing Then - Directory.CreateDirectory(ModsFolder) - Log("[Download] 自动创建 mods 文件夹:" & ModsFolder) - End If - End Sub) With {.ProgressWeight = 2, .Block = True}) + Sub(Task As LoaderTask(Of String, String)) + '合并 JSON + MergeJson(VersionFolder, VersionFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder) + Task.Progress = 0.2 + '迁移文件 + If Directory.Exists(TempMcFolder & "libraries") Then CopyDirectory(TempMcFolder & "libraries", PathMcFolder & "libraries") + Task.Progress = 0.8 + '创建 Mod 和资源包文件夹 + Dim ModsFolder = New McVersion(VersionFolder).PathIndie & "mods\" '版本隔离信息在此时被决定 + If Directory.Exists(ModsTempFolder) Then + CopyDirectory(ModsTempFolder, ModsFolder) + ElseIf Modable Then + Directory.CreateDirectory(ModsFolder) + Log("[Download] 自动创建 Mod 文件夹:" & ModsFolder) + End If + Dim ResourcepacksFolder = New McVersion(VersionFolder).PathIndie & "resourcepacks\" + Directory.CreateDirectory(ResourcepacksFolder) + Log("[Download] 自动创建资源包文件夹:" & ResourcepacksFolder) + End Sub) With {.ProgressWeight = 2, .Block = True}) '补全文件 - If Not DontFixLibraries AndAlso - (Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.BeforeFirst(".") >= 20) OrElse Request.NeoForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing) Then + If Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.BeforeFirst(".") >= 20) OrElse Request.NeoForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing Then Dim LoadersLib As New List(Of LoaderBase) - LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) + LoadersLib.Add(New LoaderTask(Of String, List(Of NetFile))("分析游戏支持库文件(副加载器)", Sub(Task) Task.Output = McLibFix(New McVersion(VersionFolder))) With {.ProgressWeight = 1, .Show = False}) LoadersLib.Add(New LoaderDownload("下载游戏支持库文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 7, .Show = False}) LoaderList.Add(New LoaderCombo(Of String)("下载游戏支持库文件", LoadersLib) With {.ProgressWeight = 8}) End If @@ -2179,9 +2192,9 @@ Retry: End Function ''' - ''' 将多个版本 Json 进行合并,如果目标已存在则直接覆盖。失败会抛出异常。 + ''' 将多个版本 JSON 进行合并,如果目标已存在则直接覆盖。失败会抛出异常。 ''' - Private Sub InstallMerge(OutputFolder As String, MinecraftFolder As String, Optional OptiFineFolder As String = Nothing, Optional OptiFineAsMod As Boolean = False, Optional ForgeFolder As String = Nothing, Optional ForgeVersion As String = Nothing, Optional NeoForgeFolder As String = Nothing, Optional NeoForgeVersion As String = Nothing, Optional FabricFolder As String = Nothing, Optional LiteLoaderFolder As String = Nothing) + Private Sub MergeJson(OutputFolder As String, MinecraftFolder As String, Optional OptiFineFolder As String = Nothing, Optional OptiFineAsMod As Boolean = False, Optional ForgeFolder As String = Nothing, Optional ForgeVersion As String = Nothing, Optional NeoForgeFolder As String = Nothing, Optional NeoForgeVersion As String = Nothing, Optional FabricFolder As String = Nothing, Optional LiteLoaderFolder As String = Nothing) Log("[Download] 开始进行版本合并,输出:" & OutputFolder & ",Minecraft:" & MinecraftFolder & If(OptiFineFolder IsNot Nothing, ",OptiFine:" & OptiFineFolder, "") & If(ForgeFolder IsNot Nothing, ",Forge:" & ForgeFolder, "") & diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml deleted file mode 100644 index e45317e..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb deleted file mode 100644 index b3de19c..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb +++ /dev/null @@ -1,111 +0,0 @@ -Public Class PageDownloadClient - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanBack, Nothing, DlClientListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '归类 - Dim Dict As New Dictionary(Of String, List(Of JObject)) From { - {"正式版", New List(Of JObject)}, {"预览版", New List(Of JObject)}, {"远古版", New List(Of JObject)}, {"愚人节版", New List(Of JObject)} - } - Dim Versions As JArray = DlClientListLoader.Output.Value("versions") - For Each Version As JObject In Versions - '确定分类 - Dim Type As String = Version("type") - Select Case Type - Case "release" - Type = "正式版" - Case "snapshot" - Type = "预览版" - 'Mojang 误分类 - If Version("id").ToString.StartsWith("1.") AndAlso - Not Version("id").ToString.ToLower.Contains("combat") AndAlso - Not Version("id").ToString.ToLower.Contains("rc") AndAlso - Not Version("id").ToString.ToLower.Contains("experimental") AndAlso - Not Version("id").ToString.ToLower.Contains("pre") Then - Type = "正式版" - Version("type") = "release" - End If - '愚人节版本 - Select Case Version("id").ToString.ToLower - Case "20w14infinite", "20w14∞" - Type = "愚人节版" - Version("id") = "20w14∞" - Version("type") = "special" - Version.Add("lore", GetMcFoolName(Version("id"))) - Case "3d shareware v1.34", "1.rv-pre1", "15w14a", "2.0", "22w13oneblockatatime", "23w13a_or_b", "24w14potato" - Type = "愚人节版" - Version("type") = "special" - Version.Add("lore", GetMcFoolName(Version("id"))) - Case Else '4/1 自动视作愚人节版 - Dim ReleaseDate = Version("releaseTime").Value(Of Date).ToUniversalTime().AddHours(2) - If ReleaseDate.Month = 4 AndAlso ReleaseDate.Day = 1 Then - Type = "愚人节版" - Version("type") = "special" - End If - End Select - Case "special" - '已被处理的愚人节版 - Type = "愚人节版" - Case Else - Type = "远古版" - End Select - '加入辞典 - Dict(Type).Add(Version) - Next - '排序 - For i = 0 To Dict.Keys.Count - 1 - Dict(Dict.Keys(i)) = Dict.Values(i).OrderByDescending(Function(v) v("releaseTime").Value(Of Date)).ToList - Next - '清空当前 - PanMain.Children.Clear() - '添加最新版本 - Dim CardInfo As New MyCard With {.Title = "最新版本", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 2} - Dim TopestVersions As New List(Of JObject) - Dim Release As JObject = Dict("正式版")(0).DeepClone() - Release("lore") = "最新正式版,发布于 " & Release("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm") - TopestVersions.Add(Release) - If Dict("正式版")(0)("releaseTime").Value(Of Date) < Dict("预览版")(0)("releaseTime").Value(Of Date) Then - Dim Snapshot As JObject = Dict("预览版")(0).DeepClone() - Snapshot("lore") = "最新预览版,发布于 " & Snapshot("releaseTime").Value(Of Date).ToString("yyyy'/'MM'/'dd HH':'mm") - TopestVersions.Add(Snapshot) - End If - Dim PanInfo As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = TopestVersions} - MyCard.StackInstall(PanInfo, 2) - CardInfo.Children.Add(PanInfo) - PanMain.Children.Add(CardInfo) - '添加其他版本 - For Each Pair As KeyValuePair(Of String, List(Of JObject)) In Dict - If Not Pair.Value.Any() Then Continue For - '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 2} - 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 - NewCard.IsSwaped = True - PanMain.Children.Add(NewCard) - Next - Catch ex As Exception - Log(ex, "可视化 MC 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - Public Sub DownloadStart(sender As MyListItem, e As Object) - McDownloadClient(NetPreDownloadBehaviour.HintWhileExists, sender.Title, sender.Tag("url").ToString) - End Sub - - ''介绍栏 - 'Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - ' OpenWebsite("https://www.minecraft.net/zh-hans") - 'End Sub - 'Private Sub BtnInstall_Click(sender As Object, e As EventArgs) Handles BtnInstall.Click - ' FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadInstall) - 'End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml deleted file mode 100644 index 0c19a78..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb deleted file mode 100644 index fa65b79..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadFabric.xaml.vb +++ /dev/null @@ -1,32 +0,0 @@ -Public Class PageDownloadFabric - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, CardVersions, CardTip, DlFabricListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - Dim Versions As JArray = DlFabricListLoader.Output.Value("installer") - PanVersions.Children.Clear() - For Each Version In Versions - PanVersions.Children.Add(FabricDownloadListItem(Version, AddressOf Fabric_Selected)) - Next - CardVersions.Title = "版本列表 (" & Versions.Count & ")" - Catch ex As Exception - Log(ex, "可视化 Fabric 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - Private Sub Fabric_Selected(sender As MyListItem, e As EventArgs) - McDownloadFabricLoaderSave(sender.Tag) - End Sub - - Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - OpenWebsite("https://www.fabricmc.net") - End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml deleted file mode 100644 index a4379c4..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb deleted file mode 100644 index 38ba113..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb +++ /dev/null @@ -1,61 +0,0 @@ -Public Class PageDownloadForge - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlForgeListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '清空当前 - PanMain.Children.Clear() - '转化为 UI - For Each Version As String In DlForgeListLoader.Output.Value.Sort(AddressOf VersionSortBoolean) - '增加卡片 - Dim NewCard As New MyCard With {.Title = Version.Replace("_p", " P"), .Margin = New Thickness(0, 0, 0, 15), .SwapType = 5} - Dim NewStack As New StackPanel With {.Margin = New Thickness(20, MyCard.SwapedHeight, 18, 0), .VerticalAlignment = VerticalAlignment.Top, .RenderTransform = New TranslateTransform(0, 0), .Tag = Version} - NewCard.Children.Add(NewStack) - NewCard.SwapControl = NewStack - NewCard.IsSwaped = True - PanMain.Children.Add(NewCard) - Next - ''非官方源警示 - 'If Setup.Get("ToolDownloadOutOfDate") AndAlso Not DlForgeListLoader.Output.IsOfficial Then - ' Dim CardWarn As New MyCard With {.Title = "过期提示", .Margin = New Thickness(0, 0, 0, 15)} - ' CardWarn.Children.Add(New TextBlock With { - ' .Margin = New Thickness(25, MyCard.SwapedHeight, 15, 15), .VerticalAlignment = VerticalAlignment.Top, .HorizontalAlignment = HorizontalAlignment.Left, .TextTrimming = TextTrimming.None, .TextWrapping = TextWrapping.Wrap, - ' .Text = "获取官方源失败,正在使用 " & DlForgeListLoader.Output.SourceName & " 镜像源,版本列表可能并非最新。" & vbCrLf & "官方源错误原因:" & If(DlForgeListLoader.Output.OfficialError, New Exception("连接服务器超时")).Message}) - ' PanMain.Children.Insert(0, CardWarn) - 'End If - Catch ex As Exception - Log(ex, "可视化 Forge 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - 'Forge 版本列表加载 - Public Sub Forge_Click(sender As MyLoading, e As MouseButtonEventArgs) - If sender.State.LoadingState = MyLoading.MyLoadingState.Error Then - CType(sender.State, LoaderTask(Of String, List(Of DlForgeVersionEntry))).Start(IsForceRestart:=True) - End If - End Sub - Public Sub Forge_StateChanged(sender As MyLoading, newState As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) - If newState <> MyLoading.MyLoadingState.Stop Then Exit Sub - - Dim Card As MyCard = CType(sender.Parent, FrameworkElement).Parent - Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = sender.State - '载入列表 - Card.SwapControl.Children.Clear() - Card.SwapControl.Tag = Loader.Output - Card.SwapType = 6 - Card.StackInstall() - End Sub - - '介绍栏 - Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - OpenWebsite("https://files.minecraftforge.net") - End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml index 124a1d7..74a753d 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageDownloadInstall" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> @@ -23,11 +23,11 @@ - - - - - + + + + + @@ -45,7 +45,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -63,7 +63,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -81,7 +81,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -99,7 +99,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -119,7 +119,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + @@ -137,7 +137,7 @@ Data="F1 M2,0 L0,2 8,10 0,18 2,20 10,12 18,20 20,18 12,10 20,2 18,0 10,8 2,0Z" /> - + diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb index 4346a46..51b25e1 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb @@ -19,7 +19,7 @@ SelectReload() '非重复加载部分 - If IsLoad Then Exit Sub + If IsLoad Then Return IsLoad = True McDownloadForgeRecommendedRefresh() @@ -38,7 +38,7 @@ Public IsInSelectPage As Boolean = False Private IsFirstLoaded As Boolean = False Private Sub EnterSelectPage() - If IsInSelectPage Then Exit Sub + If IsInSelectPage Then Return IsInSelectPage = True PanInner.Margin = New Thickness(25, 10, 25, 40) @@ -54,18 +54,13 @@ DisabledPageAnimControls.Remove(BtnStart) BtnStart.Show = True - CardOptiFine.IsSwaped = True - CardLiteLoader.IsSwaped = True - CardForge.IsSwaped = True - CardNeoForge.IsSwaped = True - CardFabric.IsSwaped = True - CardFabricApi.IsSwaped = True - CardOptiFabric.IsSwaped = True - - If Not Setup.Get("HintInstallBack") Then - Setup.Set("HintInstallBack", True) - Hint("点击 Minecraft 项即可返回游戏主版本选择页面!") - End If + CardOptiFine.IsSwapped = True + CardLiteLoader.IsSwapped = True + CardForge.IsSwapped = True + CardNeoForge.IsSwapped = True + CardFabric.IsSwapped = True + CardFabricApi.IsSwapped = True + CardOptiFabric.IsSwapped = True '如果在选择页面按了刷新键,选择页的东西可能会由于动画被隐藏,但不会由于加载结束而再次显示,因此这里需要手动恢复 For Each Control In GetAllAnimControls(PanSelect) @@ -87,8 +82,8 @@ DlOptiFabricLoader.Start() AniStart({ - AaOpacity(PanMinecraft, -PanMinecraft.Opacity, 90, 10), - AaTranslateX(PanMinecraft, -50 - CType(PanMinecraft.RenderTransform, TranslateTransform).X, 100, 10), + AaOpacity(PanMinecraft, -PanMinecraft.Opacity, 70, 10), + AaTranslateX(PanMinecraft, -50 - CType(PanMinecraft.RenderTransform, TranslateTransform).X, 90, 10), AaCode( Sub() PanBack.ScrollToHome() @@ -101,15 +96,15 @@ FabricApi_Loaded() OptiFabric_Loaded() SelectReload() + PanMinecraft.Visibility = Visibility.Collapsed End Sub, After:=True), - AaOpacity(PanSelect, 1 - PanSelect.Opacity, 90, 100), - AaTranslateX(PanSelect, -CType(PanSelect.RenderTransform, TranslateTransform).X, 200, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)), + AaOpacity(PanSelect, 1 - PanSelect.Opacity, 70, 100), + AaTranslateX(PanSelect, -CType(PanSelect.RenderTransform, TranslateTransform).X, 160, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)), AaCode( Sub() - PanMinecraft.Visibility = Visibility.Collapsed PanBack.IsHitTestVisible = True '初始化 Binding - If IsFirstLoaded Then Exit Sub + If IsFirstLoaded Then Return IsFirstLoaded = True BtnOptiFineClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardOptiFine.MainTextBlock, .Mode = BindingMode.OneWay}) BtnLiteLoaderClearInner.SetBinding(Shapes.Path.FillProperty, New Binding("Foreground") With {.Source = CardLiteLoader.MainTextBlock, .Mode = BindingMode.OneWay}) @@ -122,7 +117,7 @@ }, "FrmDownloadInstall SelectPageSwitch", True) End Sub Public Sub ExitSelectPage() Handles BtnBack.Click - If Not IsInSelectPage Then Exit Sub + If Not IsInSelectPage Then Return IsInSelectPage = False PanInner.Margin = New Thickness(25, 10, 25, 25) @@ -137,11 +132,11 @@ PanBack.ScrollToHome() AniStart({ - AaOpacity(PanSelect, -PanSelect.Opacity, 90, 10), - AaTranslateX(PanSelect, 50 - CType(PanSelect.RenderTransform, TranslateTransform).X, 100, 10), + AaOpacity(PanSelect, -PanSelect.Opacity, 70, 10), + AaTranslateX(PanSelect, 50 - CType(PanSelect.RenderTransform, TranslateTransform).X, 90, 10), AaCode(Sub() PanBack.ScrollToHome(), After:=True), - AaOpacity(PanMinecraft, 1 - PanMinecraft.Opacity, 90, 100), - AaTranslateX(PanMinecraft, -CType(PanMinecraft.RenderTransform, TranslateTransform).X, 200, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)), + AaOpacity(PanMinecraft, 1 - PanMinecraft.Opacity, 70, 100), + AaTranslateX(PanMinecraft, -CType(PanMinecraft.RenderTransform, TranslateTransform).X, 160, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)), AaCode( Sub() PanSelect.Visibility = Visibility.Collapsed @@ -168,19 +163,19 @@ 'OptiFine Private SelectedOptiFine As DlOptiFineListEntry = Nothing Private Sub SetOptiFineInfoShow(IsShow As String) - If PanOptiFineInfo.Tag = IsShow Then Exit Sub + If PanOptiFineInfo.Tag = IsShow Then Return PanOptiFineInfo.Tag = IsShow If IsShow = "True" Then '显示信息栏 AniStart({ - AaTranslateY(PanOptiFineInfo, -CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack), - AaOpacity(PanOptiFineInfo, 1 - PanOptiFineInfo.Opacity, 100, 90) + AaTranslateY(PanOptiFineInfo, -CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), + AaOpacity(PanOptiFineInfo, 1 - PanOptiFineInfo.Opacity, 80, 90) }, "SetOptiFineInfoShow") Else '隐藏信息栏 AniStart({ - AaTranslateY(PanOptiFineInfo, 6 - CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 200), - AaOpacity(PanOptiFineInfo, -PanOptiFineInfo.Opacity, 100) + AaTranslateY(PanOptiFineInfo, 6 - CType(PanOptiFineInfo.RenderTransform, TranslateTransform).Y, 120), + AaOpacity(PanOptiFineInfo, -PanOptiFineInfo.Opacity, 50) }, "SetOptiFineInfoShow") End If End Sub @@ -188,19 +183,19 @@ 'LiteLoader Private SelectedLiteLoader As DlLiteLoaderListEntry = Nothing Private Sub SetLiteLoaderInfoShow(IsShow As String) - If PanLiteLoaderInfo.Tag = IsShow Then Exit Sub + If PanLiteLoaderInfo.Tag = IsShow Then Return PanLiteLoaderInfo.Tag = IsShow If IsShow = "True" Then '显示信息栏 AniStart({ - AaTranslateY(PanLiteLoaderInfo, -CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack), - AaOpacity(PanLiteLoaderInfo, 1 - PanLiteLoaderInfo.Opacity, 100, 90) + AaTranslateY(PanLiteLoaderInfo, -CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), + AaOpacity(PanLiteLoaderInfo, 1 - PanLiteLoaderInfo.Opacity, 80, 90) }, "SetLiteLoaderInfoShow") Else '隐藏信息栏 AniStart({ - AaTranslateY(PanLiteLoaderInfo, 6 - CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 200), - AaOpacity(PanLiteLoaderInfo, -PanLiteLoaderInfo.Opacity, 100) + AaTranslateY(PanLiteLoaderInfo, 6 - CType(PanLiteLoaderInfo.RenderTransform, TranslateTransform).Y, 120), + AaOpacity(PanLiteLoaderInfo, -PanLiteLoaderInfo.Opacity, 50) }, "SetLiteLoaderInfoShow") End If End Sub @@ -208,19 +203,19 @@ 'Forge Private SelectedForge As DlForgeVersionEntry = Nothing Private Sub SetForgeInfoShow(IsShow As String) - If PanForgeInfo.Tag = IsShow Then Exit Sub + If PanForgeInfo.Tag = IsShow Then Return PanForgeInfo.Tag = IsShow If IsShow = "True" Then '显示信息栏 AniStart({ - AaTranslateY(PanForgeInfo, -CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack), - AaOpacity(PanForgeInfo, 1 - PanForgeInfo.Opacity, 100, 90) + AaTranslateY(PanForgeInfo, -CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), + AaOpacity(PanForgeInfo, 1 - PanForgeInfo.Opacity, 80, 90) }, "SetForgeInfoShow") Else '隐藏信息栏 AniStart({ - AaTranslateY(PanForgeInfo, 6 - CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 200), - AaOpacity(PanForgeInfo, -PanForgeInfo.Opacity, 100) + AaTranslateY(PanForgeInfo, 6 - CType(PanForgeInfo.RenderTransform, TranslateTransform).Y, 120), + AaOpacity(PanForgeInfo, -PanForgeInfo.Opacity, 50) }, "SetForgeInfoShow") End If End Sub @@ -228,19 +223,19 @@ 'NeoForge Private SelectedNeoForge As DlNeoForgeListEntry = Nothing Private Sub SetNeoForgeInfoShow(IsShow As String) - If PanNeoForgeInfo.Tag = IsShow Then Exit Sub + If PanNeoForgeInfo.Tag = IsShow Then Return PanNeoForgeInfo.Tag = IsShow If IsShow = "True" Then '显示信息栏 AniStart({ - AaTranslateY(PanNeoForgeInfo, -CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack), - AaOpacity(PanNeoForgeInfo, 1 - PanNeoForgeInfo.Opacity, 100, 90) + AaTranslateY(PanNeoForgeInfo, -CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), + AaOpacity(PanNeoForgeInfo, 1 - PanNeoForgeInfo.Opacity, 80, 90) }, "SetNeoForgeInfoShow") Else '隐藏信息栏 AniStart({ - AaTranslateY(PanNeoForgeInfo, 6 - CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 200), - AaOpacity(PanNeoForgeInfo, -PanNeoForgeInfo.Opacity, 100) + AaTranslateY(PanNeoForgeInfo, 6 - CType(PanNeoForgeInfo.RenderTransform, TranslateTransform).Y, 120), + AaOpacity(PanNeoForgeInfo, -PanNeoForgeInfo.Opacity, 50) }, "SetNeoForgeInfoShow") End If End Sub @@ -248,19 +243,19 @@ 'Fabric Private SelectedFabric As String = Nothing Private Sub SetFabricInfoShow(IsShow As String) - If PanFabricInfo.Tag = IsShow Then Exit Sub + If PanFabricInfo.Tag = IsShow Then Return PanFabricInfo.Tag = IsShow If IsShow = "True" Then '显示信息栏 AniStart({ - AaTranslateY(PanFabricInfo, -CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack), - AaOpacity(PanFabricInfo, 1 - PanFabricInfo.Opacity, 100, 90) + AaTranslateY(PanFabricInfo, -CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), + AaOpacity(PanFabricInfo, 1 - PanFabricInfo.Opacity, 80, 90) }, "SetFabricInfoShow") Else '隐藏信息栏 AniStart({ - AaTranslateY(PanFabricInfo, 6 - CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 200), - AaOpacity(PanFabricInfo, -PanFabricInfo.Opacity, 100) + AaTranslateY(PanFabricInfo, 6 - CType(PanFabricInfo.RenderTransform, TranslateTransform).Y, 120), + AaOpacity(PanFabricInfo, -PanFabricInfo.Opacity, 50) }, "SetFabricInfoShow") End If End Sub @@ -268,19 +263,19 @@ 'FabricApi Private SelectedFabricApi As CompFile = Nothing Private Sub SetFabricApiInfoShow(IsShow As String) - If PanFabricApiInfo.Tag = IsShow Then Exit Sub + If PanFabricApiInfo.Tag = IsShow Then Return PanFabricApiInfo.Tag = IsShow If IsShow = "True" Then '显示信息栏 AniStart({ - AaTranslateY(PanFabricApiInfo, -CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack), - AaOpacity(PanFabricApiInfo, 1 - PanFabricApiInfo.Opacity, 100, 90) + AaTranslateY(PanFabricApiInfo, -CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), + AaOpacity(PanFabricApiInfo, 1 - PanFabricApiInfo.Opacity, 80, 90) }, "SetFabricApiInfoShow") Else '隐藏信息栏 AniStart({ - AaTranslateY(PanFabricApiInfo, 6 - CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 200), - AaOpacity(PanFabricApiInfo, -PanFabricApiInfo.Opacity, 100) + AaTranslateY(PanFabricApiInfo, 6 - CType(PanFabricApiInfo.RenderTransform, TranslateTransform).Y, 120), + AaOpacity(PanFabricApiInfo, -PanFabricApiInfo.Opacity, 50) }, "SetFabricApiInfoShow") End If End Sub @@ -288,19 +283,19 @@ 'OptiFabric Private SelectedOptiFabric As CompFile = Nothing Private Sub SetOptiFabricInfoShow(IsShow As String) - If PanOptiFabricInfo.Tag = IsShow Then Exit Sub + If PanOptiFabricInfo.Tag = IsShow Then Return PanOptiFabricInfo.Tag = IsShow If IsShow = "True" Then '显示信息栏 AniStart({ - AaTranslateY(PanOptiFabricInfo, -CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 270, 100, Ease:=New AniEaseOutBack), - AaOpacity(PanOptiFabricInfo, 1 - PanOptiFabricInfo.Opacity, 100, 90) + AaTranslateY(PanOptiFabricInfo, -CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 200, 100, Ease:=New AniEaseOutFluent), + AaOpacity(PanOptiFabricInfo, 1 - PanOptiFabricInfo.Opacity, 80, 90) }, "SetOptiFabricInfoShow") Else '隐藏信息栏 AniStart({ - AaTranslateY(PanOptiFabricInfo, 6 - CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 200), - AaOpacity(PanOptiFabricInfo, -PanOptiFabricInfo.Opacity, 100) + AaTranslateY(PanOptiFabricInfo, 6 - CType(PanOptiFabricInfo.RenderTransform, TranslateTransform).Y, 120), + AaOpacity(PanOptiFabricInfo, -PanOptiFabricInfo.Opacity, 50) }, "SetOptiFabricInfoShow") End If End Sub @@ -310,7 +305,7 @@ ''' 重载已选择的项目的显示。 ''' Private Sub SelectReload() Handles CardOptiFine.Swap, LoadOptiFine.StateChanged, CardForge.Swap, LoadForge.StateChanged, CardNeoForge.Swap, LoadNeoForge.StateChanged, CardFabric.Swap, LoadFabric.StateChanged, CardFabricApi.Swap, LoadFabricApi.StateChanged, CardOptiFabric.Swap, LoadOptiFabric.StateChanged, CardLiteLoader.Swap, LoadLiteLoader.StateChanged - If SelectedMinecraftId Is Nothing OrElse IsReloading Then Exit Sub + If SelectedMinecraftId Is Nothing OrElse IsReloading Then Return IsReloading = True '主预览 SelectNameUpdate() @@ -318,8 +313,8 @@ 'OptiFine Dim OptiFineError As String = LoadOptiFineGetError() CardOptiFine.MainSwap.Visibility = If(OptiFineError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If OptiFineError IsNot Nothing Then CardOptiFine.IsSwaped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 - SetOptiFineInfoShow(CardOptiFine.IsSwaped) + If OptiFineError IsNot Nothing Then CardOptiFine.IsSwapped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 + SetOptiFineInfoShow(CardOptiFine.IsSwapped) If SelectedOptiFine Is Nothing Then BtnOptiFineClear.Visibility = Visibility.Collapsed ImgOptiFine.Visibility = Visibility.Collapsed @@ -338,8 +333,8 @@ CardLiteLoader.Visibility = Visibility.Visible Dim LiteLoaderError As String = LoadLiteLoaderGetError() CardLiteLoader.MainSwap.Visibility = If(LiteLoaderError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If LiteLoaderError IsNot Nothing Then CardLiteLoader.IsSwaped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 - SetLiteLoaderInfoShow(CardLiteLoader.IsSwaped) + If LiteLoaderError IsNot Nothing Then CardLiteLoader.IsSwapped = True '例如在同时展开卡片时选择了不兼容项则强制折叠 + SetLiteLoaderInfoShow(CardLiteLoader.IsSwapped) If SelectedLiteLoader Is Nothing Then BtnLiteLoaderClear.Visibility = Visibility.Collapsed ImgLiteLoader.Visibility = Visibility.Collapsed @@ -355,8 +350,8 @@ 'Forge Dim ForgeError As String = LoadForgeGetError() CardForge.MainSwap.Visibility = If(ForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If ForgeError IsNot Nothing Then CardForge.IsSwaped = True - SetForgeInfoShow(CardForge.IsSwaped) + If ForgeError IsNot Nothing Then CardForge.IsSwapped = True + SetForgeInfoShow(CardForge.IsSwapped) If SelectedForge Is Nothing Then BtnForgeClear.Visibility = Visibility.Collapsed ImgForge.Visibility = Visibility.Collapsed @@ -375,8 +370,8 @@ CardNeoForge.Visibility = Visibility.Visible Dim NeoForgeError As String = LoadNeoForgeGetError() CardNeoForge.MainSwap.Visibility = If(NeoForgeError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If NeoForgeError IsNot Nothing Then CardNeoForge.IsSwaped = True - SetNeoForgeInfoShow(CardNeoForge.IsSwaped) + If NeoForgeError IsNot Nothing Then CardNeoForge.IsSwapped = True + SetNeoForgeInfoShow(CardNeoForge.IsSwapped) If SelectedNeoForge Is Nothing Then BtnNeoForgeClear.Visibility = Visibility.Collapsed ImgNeoForge.Visibility = Visibility.Collapsed @@ -396,8 +391,8 @@ CardFabric.Visibility = Visibility.Visible Dim FabricError As String = LoadFabricGetError() CardFabric.MainSwap.Visibility = If(FabricError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If FabricError IsNot Nothing Then CardFabric.IsSwaped = True - SetFabricInfoShow(CardFabric.IsSwaped) + If FabricError IsNot Nothing Then CardFabric.IsSwapped = True + SetFabricInfoShow(CardFabric.IsSwapped) If SelectedFabric Is Nothing Then BtnFabricClear.Visibility = Visibility.Collapsed ImgFabric.Visibility = Visibility.Collapsed @@ -417,8 +412,8 @@ CardFabricApi.Visibility = Visibility.Visible Dim FabricApiError As String = LoadFabricApiGetError() CardFabricApi.MainSwap.Visibility = If(FabricApiError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If FabricApiError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardFabricApi.IsSwaped = True - SetFabricApiInfoShow(CardFabricApi.IsSwaped) + If FabricApiError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardFabricApi.IsSwapped = True + SetFabricApiInfoShow(CardFabricApi.IsSwapped) If SelectedFabricApi Is Nothing Then BtnFabricApiClear.Visibility = Visibility.Collapsed ImgFabricApi.Visibility = Visibility.Collapsed @@ -438,8 +433,8 @@ CardOptiFabric.Visibility = Visibility.Visible Dim OptiFabricError As String = LoadOptiFabricGetError() CardOptiFabric.MainSwap.Visibility = If(OptiFabricError Is Nothing, Visibility.Visible, Visibility.Collapsed) - If OptiFabricError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardOptiFabric.IsSwaped = True - SetOptiFabricInfoShow(CardOptiFabric.IsSwaped) + If OptiFabricError IsNot Nothing OrElse SelectedFabric Is Nothing Then CardOptiFabric.IsSwapped = True + SetOptiFabricInfoShow(CardOptiFabric.IsSwapped) If SelectedOptiFabric Is Nothing Then BtnOptiFabricClear.Visibility = Visibility.Collapsed ImgOptiFabric.Visibility = Visibility.Collapsed @@ -540,13 +535,13 @@ Private IsSelectNameEdited As Boolean = False Private IsSelectNameChanging As Boolean = False Private Sub SelectNameUpdate() - If IsSelectNameEdited OrElse IsSelectNameChanging Then Exit Sub + If IsSelectNameEdited OrElse IsSelectNameChanging Then Return IsSelectNameChanging = True TextSelectName.Text = GetSelectName() IsSelectNameChanging = False End Sub Private Sub TextSelectName_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TextSelectName.TextChanged - If IsSelectNameChanging Then Exit Sub + If IsSelectNameChanging Then Return IsSelectNameEdited = True SelectReload() End Sub @@ -590,7 +585,7 @@ Version("id") = "20w14∞" Version("type") = "special" Version.Add("lore", GetMcFoolName(Version("id"))) - Case "3d shareware v1.34", "1.rv-pre1", "15w14a", "2.0", "22w13oneblockatatime", "23w13a_or_b", "24w14potato" + Case "3d shareware v1.34", "1.rv-pre1", "15w14a", "2.0", "22w13oneblockatatime", "23w13a_or_b", "24w14potato", "25w14craftmine" Type = "愚人节版" Version("type") = "special" Version.Add("lore", GetMcFoolName(Version("id"))) @@ -639,7 +634,7 @@ 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 - NewCard.IsSwaped = True + NewCard.IsSwapped = True PanMinecraft.Children.Add(NewCard) Next '自动选择版本 @@ -676,6 +671,11 @@ VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then Return "与 Forge 不兼容" End If + '检查 Fabric 1.20.5+:全部不兼容 + If SelectedFabric IsNot Nothing AndAlso + VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then + Return "与 Fabric 不兼容" + End If '检查 Forge 版本 Dim HasAny As Boolean = False Dim HasRequiredVersion As Boolean = False @@ -717,7 +717,7 @@ ''' Private Sub OptiFine_Loaded() Handles LoadOptiFine.StateChanged Try - If DlOptiFineListLoader.State <> LoadState.Finished Then Exit Sub + If DlOptiFineListLoader.State <> LoadState.Finished Then Return '获取版本列表 Dim Versions As New List(Of DlOptiFineListEntry) @@ -725,7 +725,7 @@ If SelectedForge IsNot Nothing AndAlso Not IsOptiFineSuitForForge(Version, SelectedForge) Then Continue For If Version.NameDisplay.StartsWith(SelectedMinecraftId & " ") Then Versions.Add(Version) Next - If Not Versions.Any() Then Exit Sub + If Not Versions.Any() Then Return '排序 Versions = Versions.Sort( Function(Left As DlOptiFineListEntry, Right As DlOptiFineListEntry) As Boolean @@ -750,14 +750,14 @@ OptiFabric_Loaded() Forge_Loaded() NeoForge_Loaded() - CardOptiFine.IsSwaped = True + CardOptiFine.IsSwapped = True SelectReload() End Sub Private Sub OptiFine_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFineClear.MouseLeftButtonUp SelectedOptiFine = Nothing SelectedOptiFabric = Nothing AutoSelectedOptiFabric = False - CardOptiFine.IsSwaped = True + CardOptiFine.IsSwapped = True e.Handled = True Forge_Loaded() NeoForge_Loaded() @@ -791,13 +791,13 @@ ''' Private Sub LiteLoader_Loaded() Handles LoadLiteLoader.StateChanged Try - If DlLiteLoaderListLoader.State <> LoadState.Finished Then Exit Sub + If DlLiteLoaderListLoader.State <> LoadState.Finished Then Return '获取版本列表 Dim Versions As New List(Of DlLiteLoaderListEntry) For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value If Version.Inherit = SelectedMinecraftId Then Versions.Add(Version) Next - If Not Versions.Any() Then Exit Sub + If Not Versions.Any() Then Return '可视化 PanLiteLoader.Children.Clear() For Each Version In Versions @@ -811,12 +811,12 @@ '选择与清除 Private Sub LiteLoader_Selected(sender As MyListItem, e As EventArgs) SelectedLiteLoader = sender.Tag - CardLiteLoader.IsSwaped = True + CardLiteLoader.IsSwapped = True SelectReload() End Sub Private Sub LiteLoader_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnLiteLoaderClear.MouseLeftButtonUp SelectedLiteLoader = Nothing - CardLiteLoader.IsSwaped = True + CardLiteLoader.IsSwapped = True e.Handled = True SelectReload() End Sub @@ -871,13 +871,13 @@ ''' Private Sub Forge_Loaded() Handles LoadForge.StateChanged Try - If Not LoadForge.State.IsLoader Then Exit Sub + If Not LoadForge.State.IsLoader Then Return Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State - If SelectedMinecraftId <> Loader.Input Then Exit Sub - If Loader.State <> LoadState.Finished Then Exit Sub + If SelectedMinecraftId <> Loader.Input Then Return + If Loader.State <> LoadState.Finished Then Return '获取要显示的版本 Dim Versions = Loader.Output.ToList '复制数组,以免 Output 在实例化后变空 - If Not Loader.Output.Any() Then Exit Sub + If Not Loader.Output.Any() Then Return PanForge.Children.Clear() Versions = Versions.Where( Function(v) @@ -897,14 +897,14 @@ '选择与清除 Private Sub Forge_Selected(sender As MyListItem, e As EventArgs) SelectedForge = sender.Tag - CardForge.IsSwaped = True + CardForge.IsSwapped = True If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, SelectedForge) Then SelectedOptiFine = Nothing OptiFine_Loaded() SelectReload() End Sub Private Sub Forge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnForgeClear.MouseLeftButtonUp SelectedForge = Nothing - CardForge.IsSwaped = True + CardForge.IsSwapped = True e.Handled = True OptiFine_Loaded() SelectReload() @@ -942,9 +942,9 @@ Private Sub NeoForge_Loaded() Handles LoadNeoForge.StateChanged Try '获取版本列表 - If DlNeoForgeListLoader.State <> LoadState.Finished Then Exit Sub + If DlNeoForgeListLoader.State <> LoadState.Finished Then Return Dim Versions = DlNeoForgeListLoader.Output.Value.Where(Function(v) v.Inherit = SelectedMinecraftId).ToList - If Not Versions.Any() Then Exit Sub + If Not Versions.Any() Then Return '可视化 PanNeoForge.Children.Clear() NeoForgeDownloadListItemPreload(PanNeoForge, Versions, AddressOf NeoForge_Selected, False) @@ -959,13 +959,13 @@ '选择与清除 Private Sub NeoForge_Selected(sender As MyListItem, e As EventArgs) SelectedNeoForge = sender.Tag - CardNeoForge.IsSwaped = True + CardNeoForge.IsSwapped = True OptiFine_Loaded() SelectReload() End Sub Private Sub NeoForge_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnNeoForgeClear.MouseLeftButtonUp SelectedNeoForge = Nothing - CardNeoForge.IsSwaped = True + CardNeoForge.IsSwapped = True e.Handled = True OptiFine_Loaded() SelectReload() @@ -981,6 +981,11 @@ Private Function LoadFabricGetError() As String If LoadFabric Is Nothing OrElse LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……" If LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabric.State, Object).Error.Message + '检查 OptiFine 1.20.5+:没有 OptiFabric 故全部不兼容 + If SelectedOptiFine IsNot Nothing AndAlso + VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then + Return "与 OptiFine 不兼容" + End If For Each Version As JObject In DlFabricListLoader.Output.Value("game") If Version("version").ToString = SelectedMinecraftId.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") Then If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容" @@ -1001,10 +1006,10 @@ ''' Private Sub Fabric_Loaded() Handles LoadFabric.StateChanged Try - If DlFabricListLoader.State <> LoadState.Finished Then Exit Sub + If DlFabricListLoader.State <> LoadState.Finished Then Return '获取版本列表 Dim Versions As JArray = DlFabricListLoader.Output.Value("loader") - If Not Versions.Any() Then Exit Sub + If Not Versions.Any() Then Return '可视化 PanFabric.Children.Clear() PanFabric.Tag = Versions @@ -1020,7 +1025,7 @@ SelectedFabric = sender.Tag("version").ToString FabricApi_Loaded() OptiFabric_Loaded() - CardFabric.IsSwaped = True + CardFabric.IsSwapped = True SelectReload() End Sub Private Sub Fabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricClear.MouseLeftButtonUp @@ -1029,7 +1034,7 @@ AutoSelectedFabricApi = False SelectedOptiFabric = Nothing AutoSelectedOptiFabric = False - CardFabric.IsSwaped = True + CardFabric.IsSwapped = True e.Handled = True SelectReload() End Sub @@ -1106,8 +1111,8 @@ ''' Private Sub FabricApi_Loaded() Handles LoadFabricApi.StateChanged Try - If DlFabricApiLoader.State <> LoadState.Finished Then Exit Sub - If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing Then Exit Sub + If DlFabricApiLoader.State <> LoadState.Finished Then Return + If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing Then Return '获取版本列表 Dim Versions As New List(Of CompFile) For Each Version In DlFabricApiLoader.Output @@ -1119,7 +1124,7 @@ Versions.Add(Version) End If Next - If Not Versions.Any() Then Exit Sub + If Not Versions.Any() Then Return Versions = Versions.OrderByDescending(Function(v) v.ReleaseDate).ToList '可视化 PanFabricApi.Children.Clear() @@ -1141,12 +1146,12 @@ '选择与清除 Private Sub FabricApi_Selected(sender As MyListItem, e As EventArgs) SelectedFabricApi = sender.Tag - CardFabricApi.IsSwaped = True + CardFabricApi.IsSwapped = True SelectReload() End Sub Private Sub FabricApi_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricApiClear.MouseLeftButtonUp SelectedFabricApi = Nothing - CardFabricApi.IsSwaped = True + CardFabricApi.IsSwapped = True e.Handled = True SelectReload() End Sub @@ -1202,14 +1207,14 @@ ''' Private Sub OptiFabric_Loaded() Handles LoadOptiFabric.StateChanged Try - If DlOptiFabricLoader.State <> LoadState.Finished Then Exit Sub - If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing OrElse SelectedOptiFine Is Nothing Then Exit Sub + If DlOptiFabricLoader.State <> LoadState.Finished Then Return + If SelectedMinecraftId Is Nothing OrElse SelectedFabric Is Nothing OrElse SelectedOptiFine Is Nothing Then Return '获取版本列表 Dim Versions As New List(Of CompFile) For Each Version In DlOptiFabricLoader.Output If IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Versions.Add(Version) Next - If Not Versions.Any() Then Exit Sub + If Not Versions.Any() Then Return '排序 Versions = Versions.OrderByDescending(Function(v) v.ReleaseDate).ToList '可视化 @@ -1233,12 +1238,12 @@ '选择与清除 Private Sub OptiFabric_Selected(sender As MyListItem, e As EventArgs) SelectedOptiFabric = sender.Tag - CardOptiFabric.IsSwaped = True + CardOptiFabric.IsSwapped = True SelectReload() End Sub Private Sub OptiFabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFabricClear.MouseLeftButtonUp SelectedOptiFabric = Nothing - CardOptiFabric.IsSwaped = True + CardOptiFabric.IsSwapped = True e.Handled = True SelectReload() End Sub @@ -1254,10 +1259,10 @@ '确认版本隔离 If (SelectedForge IsNot Nothing OrElse SelectedNeoForge IsNot Nothing OrElse SelectedFabric IsNot Nothing) AndAlso (Setup.Get("LaunchArgumentIndieV2") = 0 OrElse Setup.Get("LaunchArgumentIndieV2") = 2) Then - If MyMsgBox("你尚未开启版本隔离,这会导致多个版本共用同一个 Mod 文件夹。" & vbCrLf & - "因此游戏可能会因为读取到与当前版本不符的 Mod 而崩溃。" & vbCrLf & - "推荐在开始下载前,在 设置 → 启动选项 → 版本隔离 中开启版本隔离!", "版本隔离提示", "取消下载", "继续") = 1 Then - Exit Sub + If MyMsgBox("你尚未开启版本隔离,多个 MC 版本会共用同一个 Mod 文件夹。" & vbCrLf & + "因此,游戏可能会因为读取到与当前版本不符的 Mod 而崩溃。" & vbCrLf & + "推荐先在 设置 → 启动选项 → 默认版本隔离 中开启版本隔离!", "版本隔离提示", "取消下载", "继续") = 1 Then + Return End If End If '提交安装申请 @@ -1275,7 +1280,7 @@ .OptiFabric = SelectedOptiFabric, .LiteLoaderEntry = SelectedLiteLoader } - If Not McInstall(Request) Then Exit Sub + If Not McInstall(Request) Then Return '返回,这样在再次进入安装页面时这个版本就会显示文件夹已重复 ExitSelectPage() End Sub diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml index 86c9ef2..2aa4592 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml @@ -1,91 +1,59 @@  + xmlns:local="clr-namespace:PCL" AnimatedControl="PanItem"> - - + - + - - - - + + + + + + + + + + + + + + + + - - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb index f2eb250..3ddece3 100644 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb @@ -11,7 +11,7 @@ ''' ''' 勾选事件改变页面。 ''' - Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemInstall.Check, ItemClient.Check, ItemOptiFine.Check, ItemForge.Check, ItemNeoForge.Check, ItemLiteLoader.Check, ItemMod.Check, ItemFabric.Check, ItemPack.Check + Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemInstall.Check, ItemMod.Check, ItemPack.Check, ItemResourcePack.Check, ItemShader.Check, ItemDataPack.Check '尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0 '若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False) If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag)) @@ -23,30 +23,21 @@ Case FormMain.PageSubType.DownloadInstall If FrmDownloadInstall Is Nothing Then FrmDownloadInstall = New PageDownloadInstall Return FrmDownloadInstall - Case FormMain.PageSubType.DownloadClient - If FrmDownloadClient Is Nothing Then FrmDownloadClient = New PageDownloadClient - Return FrmDownloadClient - Case FormMain.PageSubType.DownloadOptiFine - If FrmDownloadOptiFine Is Nothing Then FrmDownloadOptiFine = New PageDownloadOptiFine - Return FrmDownloadOptiFine - Case FormMain.PageSubType.DownloadForge - If FrmDownloadForge Is Nothing Then FrmDownloadForge = New PageDownloadForge - Return FrmDownloadForge - Case FormMain.PageSubType.DownloadNeoForge - If FrmDownloadNeoForge Is Nothing Then FrmDownloadNeoForge = New PageDownloadNeoForge - Return FrmDownloadNeoForge - Case FormMain.PageSubType.DownloadLiteLoader - If FrmDownloadLiteLoader Is Nothing Then FrmDownloadLiteLoader = New PageDownloadLiteLoader - Return FrmDownloadLiteLoader - Case FormMain.PageSubType.DownloadFabric - If FrmDownloadFabric Is Nothing Then FrmDownloadFabric = New PageDownloadFabric - Return FrmDownloadFabric Case FormMain.PageSubType.DownloadMod If FrmDownloadMod Is Nothing Then FrmDownloadMod = New PageDownloadMod Return FrmDownloadMod Case FormMain.PageSubType.DownloadPack If FrmDownloadPack Is Nothing Then FrmDownloadPack = New PageDownloadPack Return FrmDownloadPack + Case FormMain.PageSubType.DownloadResourcePack + If FrmDownloadResourcePack Is Nothing Then FrmDownloadResourcePack = New PageDownloadResourcePack + Return FrmDownloadResourcePack + Case FormMain.PageSubType.DownloadShader + If FrmDownloadShader Is Nothing Then FrmDownloadShader = New PageDownloadShader + Return FrmDownloadShader + Case FormMain.PageSubType.DownloadDataPack + If FrmDownloadDataPack Is Nothing Then FrmDownloadDataPack = New PageDownloadDataPack + Return FrmDownloadDataPack Case Else Throw New Exception("未知的下载子页面种类:" & ID) End Select @@ -56,7 +47,7 @@ ''' 切换现有页面。 ''' Public Sub PageChange(ID As FormMain.PageSubType) - If PageID = ID Then Exit Sub + If PageID = ID Then Return AniControlEnabled += 1 Try PageChangeRun(PageGet(ID)) @@ -110,96 +101,58 @@ DlOptiFabricLoader.Start(IsForceRestart:=True) ItemInstall.Checked = True Case FormMain.PageSubType.DownloadMod - PageDownloadMod.Storage = New CompProjectStorage - PageDownloadMod.Page = 0 CompProjectCache.Clear() CompFilesCache.Clear() - If FrmDownloadMod IsNot Nothing Then FrmDownloadMod.PageLoaderRestart() + If FrmDownloadMod IsNot Nothing Then + FrmDownloadMod.Content.Storage = New CompProjectStorage + FrmDownloadMod.Content.Page = 0 + FrmDownloadMod.PageLoaderRestart() + End If ItemMod.Checked = True Case FormMain.PageSubType.DownloadPack - PageDownloadPack.Storage = New CompProjectStorage - PageDownloadPack.Page = 0 CompProjectCache.Clear() CompFilesCache.Clear() - If FrmDownloadPack IsNot Nothing Then FrmDownloadPack.PageLoaderRestart() + If FrmDownloadPack IsNot Nothing Then + FrmDownloadPack.Content.Storage = New CompProjectStorage + FrmDownloadPack.Content.Page = 0 + FrmDownloadPack.PageLoaderRestart() + End If ItemPack.Checked = True - Case FormMain.PageSubType.DownloadClient - DlClientListLoader.Start(IsForceRestart:=True) - ItemClient.Checked = True - Case FormMain.PageSubType.DownloadOptiFine - DlOptiFineListLoader.Start(IsForceRestart:=True) - ItemOptiFine.Checked = True - Case FormMain.PageSubType.DownloadForge - DlForgeListLoader.Start(IsForceRestart:=True) - ItemForge.Checked = True - Case FormMain.PageSubType.DownloadNeoForge - DlNeoForgeListLoader.Start(IsForceRestart:=True) - ItemNeoForge.Checked = True - Case FormMain.PageSubType.DownloadLiteLoader - DlLiteLoaderListLoader.Start(IsForceRestart:=True) - ItemLiteLoader.Checked = True - Case FormMain.PageSubType.DownloadFabric - DlFabricListLoader.Start(IsForceRestart:=True) - ItemFabric.Checked = True + Case FormMain.PageSubType.DownloadResourcePack + CompProjectCache.Clear() + CompFilesCache.Clear() + If FrmDownloadResourcePack IsNot Nothing Then + FrmDownloadResourcePack.Content.Storage = New CompProjectStorage + FrmDownloadResourcePack.Content.Page = 0 + FrmDownloadResourcePack.PageLoaderRestart() + End If + ItemResourcePack.Checked = True + Case FormMain.PageSubType.DownloadShader + CompProjectCache.Clear() + CompFilesCache.Clear() + If FrmDownloadShader IsNot Nothing Then + FrmDownloadShader.Content.Storage = New CompProjectStorage + FrmDownloadShader.Content.Page = 0 + FrmDownloadShader.PageLoaderRestart() + End If + ItemShader.Checked = True + Case FormMain.PageSubType.DownloadDataPack + CompProjectCache.Clear() + CompFilesCache.Clear() + If FrmDownloadDataPack IsNot Nothing Then + FrmDownloadDataPack.Content.Storage = New CompProjectStorage + FrmDownloadDataPack.Content.Page = 0 + FrmDownloadDataPack.PageLoaderRestart() + End If + ItemDataPack.Checked = True End Select Hint("正在刷新……", Log:=False) End Sub '点击返回 Private Sub ItemInstall_Click(sender As Object, e As MouseButtonEventArgs) Handles ItemInstall.Click - If Not ItemInstall.Checked Then Exit Sub + If Not ItemInstall.Checked Then Return FrmDownloadInstall.ExitSelectPage() End Sub - '展开手动安装 - Private Sub ItemHand_Click(sender As Object, e As RouteEventArgs) Handles ItemHand.Changed - If ItemHand.Checked = False Then Exit Sub - e.Handled = True - AniControlEnabled += 1 - If Not Setup.Get("HintHandInstall") Then - Setup.Set("HintHandInstall", True) - If MyMsgBox("手动安装包功能提供了 OptiFine、Forge 等组件的 .jar 安装文件下载,但无法自动安装。" & vbCrLf & - "在自动安装页面先选择 MC 版本,然后就可以选择 OptiFine、Forge 等组件,让 PCL 自动进行安装了。", "自动安装提示", "返回自动安装", "继续下载手动安装包") = 1 Then - FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.Download}, FormMain.PageSubType.DownloadInstall) - AniControlEnabled -= 1 - Exit Sub - End If - End If - ItemHand.Visibility = Visibility.Collapsed - LabGame.Visibility = Visibility.Collapsed - LabHand.Visibility = Visibility.Visible - ItemClient.Visibility = Visibility.Visible - ItemOptiFine.Visibility = Visibility.Visible - ItemFabric.Visibility = Visibility.Visible - ItemForge.Visibility = Visibility.Visible - ItemNeoForge.Visibility = Visibility.Visible - ItemLiteLoader.Visibility = Visibility.Visible - RunInThread( - Sub() - Thread.Sleep(20) - RunInUiWait(Sub() ItemClient.SetChecked(True, True, True)) - AniControlEnabled -= 1 - End Sub) - End Sub - '折叠手动安装 - Private Sub LabHand_Click(sender As Object, e As MouseButtonEventArgs) Handles LabHand.MouseLeftButtonUp - e.Handled = True - AniControlEnabled += 1 - ItemHand.Visibility = Visibility.Visible - LabGame.Visibility = Visibility.Visible - LabHand.Visibility = Visibility.Collapsed - ItemClient.Visibility = Visibility.Collapsed - ItemOptiFine.Visibility = Visibility.Collapsed - ItemNeoForge.Visibility = Visibility.Collapsed - ItemFabric.Visibility = Visibility.Collapsed - ItemForge.Visibility = Visibility.Collapsed - ItemLiteLoader.Visibility = Visibility.Collapsed - RunInThread( - Sub() - Thread.Sleep(20) - RunInUiWait(Sub() ItemInstall.SetChecked(True, True, True)) - AniControlEnabled -= 1 - End Sub) - End Sub - End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml deleted file mode 100644 index 69c4db0..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb deleted file mode 100644 index ac18c47..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLiteLoader.xaml.vb +++ /dev/null @@ -1,52 +0,0 @@ -Public Class PageDownloadLiteLoader - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlLiteLoaderListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '归类 - Dim Dict As New Dictionary(Of String, List(Of DlLiteLoaderListEntry)) - For VersionCode As Integer = 30 To 0 Step -1 - Dict.Add("1." & VersionCode, New List(Of DlLiteLoaderListEntry)) - Next - Dict.Add("未知版本", New List(Of DlLiteLoaderListEntry)) - For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value - Dim MainVersion As String = "1." & Version.Inherit.Split(".")(1) - If Dict.ContainsKey(MainVersion) Then - Dict(MainVersion).Add(Version) - Else - Dict("未知版本").Add(Version) - End If - Next - '清空当前 - PanMain.Children.Clear() - '转化为 UI - For Each Pair As KeyValuePair(Of String, List(Of DlLiteLoaderListEntry)) In Dict - If Not Pair.Value.Any() Then Continue For - '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 10} - 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 - NewCard.IsSwaped = True - PanMain.Children.Add(NewCard) - Next - Catch ex As Exception - Log(ex, "可视化 LiteLoader 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - Public Sub DownloadStart(sender As MyListItem, e As Object) - McDownloadLiteLoader(sender.Tag) - End Sub - Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - OpenWebsite("https://www.liteloader.com") - End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml deleted file mode 100644 index bcfb637..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml deleted file mode 100644 index 886c343..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb deleted file mode 100644 index 064dda5..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadNeoForge.xaml.vb +++ /dev/null @@ -1,34 +0,0 @@ -Public Class PageDownloadNeoForge - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlNeoForgeListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '归类 - Dim Dict = DlNeoForgeListLoader.Output.Value.GroupBy(Function(d) d.Inherit).OrderByDescending(Function(g) g.Key). - ToDictionary(Function(g) g.Key, Function(g) g.ToList()) - '清空当前 - PanMain.Children.Clear() - '转化为 UI - For Each Pair As KeyValuePair(Of String, List(Of DlNeoForgeListEntry)) In Dict - If Not Pair.Value.Any() Then Continue For - '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 13} - 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 - NewCard.IsSwaped = True - PanMain.Children.Add(NewCard) - Next - Catch ex As Exception - Log(ex, "可视化 NeoForge 版本列表出错", LogLevel.Feedback) - End Try - End Sub - -End Class \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml deleted file mode 100644 index 1a08b51..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb deleted file mode 100644 index f843529..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb +++ /dev/null @@ -1,53 +0,0 @@ -Public Class PageDownloadOptiFine - - Private Sub LoaderInit() Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanMain, CardTip, DlOptiFineListLoader, AddressOf Load_OnFinish) - End Sub - Private Sub Init() Handles Me.Loaded - PanBack.ScrollToHome() - End Sub - - Private Sub Load_OnFinish() - '结果数据化 - Try - '归类 - Dim Dict As New Dictionary(Of String, List(Of DlOptiFineListEntry)) - Dict.Add("快照版本", New List(Of DlOptiFineListEntry)) - For VersionCode As Integer = 50 To 0 Step -1 - Dict.Add("1." & VersionCode, New List(Of DlOptiFineListEntry)) - Next - For Each Version As DlOptiFineListEntry In DlOptiFineListLoader.Output.Value - If Version.Inherit.StartsWith("1.") Then - Dim MainVersion As String = "1." & Version.NameDisplay.Split(".")(1).Split(" ")(0) - If Dict.ContainsKey(MainVersion) Then - Dict(MainVersion).Add(Version) - Else - Dict("快照版本").Add(Version) - End If - Else - Dict("快照版本").Add(Version) - End If - Next - '清空当前 - PanMain.Children.Clear() - '转化为 UI - For Each Pair As KeyValuePair(Of String, List(Of DlOptiFineListEntry)) In Dict - If Not Pair.Value.Any() Then Continue For - '增加卡片 - Dim NewCard As New MyCard With {.Title = Pair.Key & " (" & Pair.Value.Count & ")", .Margin = New Thickness(0, 0, 0, 15), .SwapType = 3} - 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 - NewCard.IsSwaped = True - PanMain.Children.Add(NewCard) - Next - Catch ex As Exception - Log(ex, "可视化 OptiFine 版本列表出错", LogLevel.Feedback) - End Try - End Sub - - Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click - OpenWebsite("https://www.optifine.net/") - End Sub - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml deleted file mode 100644 index 5ad426d..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb deleted file mode 100644 index 6ec8a31..0000000 --- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml.vb +++ /dev/null @@ -1,128 +0,0 @@ -Public Class PageDownloadPack - - Public Const PageSize = 40 - - '加载器信息 - Public Shared Loader As New LoaderTask(Of CompProjectRequest, Integer)("CompProject ModPack", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000} - Public Shared Storage As New CompProjectStorage - Public Shared Page As Integer = 0 - Private Sub PageDownloadPack_Inited(sender As Object, e As EventArgs) Handles Me.Initialized - PageLoaderInit(Load, PanLoad, PanContent, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput) - If McVersionHighest = -1 Then McVersionHighest = Math.Max(McVersionHighest, Integer.Parse(CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString.Split(".")(1))) - End Sub - Private Shared Function LoaderInput() As CompProjectRequest - Dim Request As New CompProjectRequest(CompType.ModPack, Storage, (Page + 1) * PageSize) - If FrmDownloadPack IsNot Nothing Then - With Request - .SearchText = FrmDownloadPack.TextSearchName.Text - .GameVersion = If(FrmDownloadPack.TextSearchVersion.Text = "全部 (也可自行输入)", Nothing, - If(FrmDownloadPack.TextSearchVersion.Text.Contains(".") OrElse FrmDownloadPack.TextSearchVersion.Text.Contains("w"), FrmDownloadPack.TextSearchVersion.Text, Nothing)) - .Tag = FrmDownloadPack.ComboSearchTag.SelectedItem.Tag - .Source = CType(Val(FrmDownloadPack.ComboSearchSource.SelectedItem.Tag), CompSourceType) - End With - End If - Return Request - End Function - - '结果 UI 化 - Private Sub Load_OnFinish() - Try - Log($"[Comp] 开始可视化整合包列表,已储藏 {Storage.Results.Count} 个结果,当前在第 {Page + 1} 页") - '列表项 - PanProjects.Children.Clear() - For i = Math.Min(Page * PageSize, Storage.Results.Count - 1) To Math.Min((Page + 1) * PageSize - 1, Storage.Results.Count - 1) - PanProjects.Children.Add(Storage.Results(i).ToCompItem(Loader.Input.GameVersion Is Nothing, True)) - Next - '页码 - CardPages.Visibility = If(Storage.Results.Count > 40 OrElse - Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal, - Visibility.Visible, Visibility.Collapsed) - LabPage.Text = Page + 1 - BtnPageFirst.IsEnabled = Page > 1 - BtnPageFirst.Opacity = If(Page > 1, 1, 0.2) - BtnPageLeft.IsEnabled = Page > 0 - BtnPageLeft.Opacity = If(Page > 0, 1, 0.2) - Dim IsRightEnabled As Boolean = '由于 WPF 的未知 bug,读取到的 IsEnabled 可能是错误的值(#3319) - Storage.Results.Count > PageSize * (Page + 1) OrElse - Storage.CurseForgeOffset < Storage.CurseForgeTotal OrElse Storage.ModrinthOffset < Storage.ModrinthTotal - BtnPageRight.IsEnabled = IsRightEnabled - BtnPageRight.Opacity = If(IsRightEnabled, 1, 0.2) - '错误信息 - If Storage.ErrorMessage Is Nothing Then - HintError.Visibility = Visibility.Collapsed - Else - HintError.Visibility = Visibility.Visible - HintError.Text = Storage.ErrorMessage - End If - '强制返回顶部 - PanBack.ScrollToTop() - Catch ex As Exception - Log(ex, "可视化整合包列表出错", LogLevel.Feedback) - End Try - End Sub - - '自动重试 - Private Sub Load_State(sender As Object, state As MyLoading.MyLoadingState, oldState As MyLoading.MyLoadingState) Handles Load.StateChanged - Select Case Loader.State - Case LoadState.Failed - Dim ErrorMessage As String = "" - If Loader.Error IsNot Nothing Then ErrorMessage = Loader.Error.Message - If ErrorMessage.Contains("不是有效的 json 文件") Then - Log("[Download] 下载的整合包列表 json 文件损坏,已自动重试", LogLevel.Debug) - PageLoaderRestart() - End If - End Select - End Sub - - '切换页码 - - Private Sub BtnPageFirst_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageFirst.Click - ChangePage(0) - End Sub - Private Sub BtnPageLeft_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageLeft.Click - ChangePage(Page - 1) - End Sub - Private Sub BtnPageRight_Click(sender As Object, e As RoutedEventArgs) Handles BtnPageRight.Click - ChangePage(Page + 1) - End Sub - Private Sub ChangePage(NewPage As Integer) - CardPages.IsEnabled = False - Page = NewPage - FrmMain.BackToTop() - Log($"[Download] 整合包切换到第 {Page + 1} 页") - RunInThread(Sub() - Thread.Sleep(100) '等待向上滚的动画结束 - RunInUi(Sub() CardPages.IsEnabled = True) - Loader.Start() - End Sub) - End Sub - - Private Sub BtnSearchInstall_Click(sender As Object, e As EventArgs) Handles BtnSearchInstall.Click - ModpackInstall() - End Sub - -#Region "搜索" - - '搜索按钮 - Private Sub StartNewSearch() Handles BtnSearchRun.Click - Page = 0 - If Loader.ShouldStart(LoaderInput()) Then Storage = New CompProjectStorage '避免连续搜索两次使得 CompProjectStorage 引用丢失(#1311) - Loader.Start() - End Sub - Private Sub EnterTrigger(sender As Object, e As KeyEventArgs) Handles TextSearchName.KeyDown, TextSearchVersion.KeyDown - If e.Key = Key.Enter Then StartNewSearch() - End Sub - - '重置按钮 - Private Sub BtnSearchReset_Click(sender As Object, e As EventArgs) Handles BtnSearchReset.Click - TextSearchName.Text = "" - TextSearchVersion.Text = "全部 (也可自行输入)" - TextSearchVersion.SelectedIndex = 0 - ComboSearchSource.SelectedIndex = 0 - ComboSearchTag.SelectedIndex = 0 - Loader.LastFinishedTime = 0 '要求强制重新开始 - End Sub - -#End Region - -End Class diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb index 174f4be..dd48c06 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb @@ -87,7 +87,7 @@ LabTitle.Text = "登录 Minecraft" LabCaption.Text = $"登录网页将自动开启,请在网页中输入 {UserCode}(已自动复制)。" & vbCrLf & vbCrLf & - $"如果网络环境不佳,网页可能一直加载不出来,届时请使用 VPN 并重试。" & vbCrLf & + $"如果网络环境不佳,网页可能一直加载不出来,届时请使用加速器或 VPN 改善网络环境。" & vbCrLf & $"你也可以用其他设备打开 {Website} 并输入上述代码。" Btn1.EventData = Website Btn2.EventData = UserCode @@ -97,7 +97,7 @@ Private Sub WorkThread() Thread.Sleep(3000) - If MyConverter.IsExited Then Exit Sub + If MyConverter.IsExited Then Return OpenWebsite(Website) ClipboardSet(UserCode) Thread.Sleep((Data("interval").ToObject(Of Integer) - 1) * 1000) @@ -105,13 +105,16 @@ Dim UnknownFailureCount As Integer = 0 Do While Not MyConverter.IsExited Try - Dim Result = NetRequestOnce( - "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", "POST", - "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" & - "client_id=" & OAuthClientId & "&" & - "device_code=" & DeviceCode & "&" & - "scope=XboxLive.signin%20offline_access", - "application/x-www-form-urlencoded", 5000 + UnknownFailureCount * 5000, MakeLog:=False) + Dim Result = NetRequestByClient( + "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", HttpMethod.Post, + Content:= + "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" & + "client_id=" & OAuthClientId & "&" & + "device_code=" & DeviceCode & "&" & + "scope=XboxLive.signin%20offline_access", + ContentType:="application/x-www-form-urlencoded", + Timeout:=5000 + UnknownFailureCount * 5000, + MakeLog:=False, RequireJson:=True) '获取结果 Dim ResultJson As JObject = GetJson(Result) McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒") @@ -119,18 +122,30 @@ Finished({ResultJson("access_token").ToString, ResultJson("refresh_token").ToString}) Return Catch ex As Exception - If ex.Message.Contains("authorization_declined") Then - Finished(New Exception("$你拒绝了 PCL 申请的权限……")) - Return - ElseIf ex.Message.Contains("expired_token") Then - Finished(New Exception("$登录用时太长啦,重新试试吧!")) - Return - ElseIf ex.Message.Contains("AADSTS70000") Then '可能不能判 “invalid_grant”,见 #269 - Finished(New RestartException) - Return - ElseIf ex.Message.Contains("authorization_pending") Then - Thread.Sleep(2000) - ElseIf UnknownFailureCount <= 2 Then + '修改错误列表时,同时修改 ModLaunch.MsLoginStep1Refresh 中的对应代码 + If TypeOf ex Is ResponsedWebException Then + Dim Response = CType(ex, ResponsedWebException).Response + If Response.Contains("authorization_declined") Then + Finished(New Exception("$你拒绝了 PCL 申请的权限……")) + Return + ElseIf Response.Contains("expired_token") Then + Finished(New Exception("$登录用时太长啦,重新试试吧!")) + Return + ElseIf Response.Contains("Account security interrupt") Then + Finished(New Exception("$该账号由于安全问题无法登陆,请前往微软账户页获取更多信息。")) + Return + ElseIf Response.Contains("service abuse") Then + Finished(New Exception("$非常抱歉,该账号已被微软封禁,无法登录。")) + Return + ElseIf Response.Contains("AADSTS70000") Then '可能不能判 “invalid_grant”,见 #269 + Finished(New RestartException) + Return + ElseIf Response.Contains("authorization_pending") Then + Thread.Sleep(2000) + Continue Do + End If + End If + If UnknownFailureCount <= 2 Then UnknownFailureCount += 1 Log(ex, $"登录轮询第 {UnknownFailureCount} 次失败") Thread.Sleep(2000) diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb index 448df0b..352c69b 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb @@ -49,7 +49,7 @@ If Not Loader.State = LoadState.Finished Then Hint("皮肤正在获取中,请稍候!", HintType.Critical) If Not Loader.State = LoadState.Loading Then Loader.Start() - Exit Sub + Return End If Try Dim FileAddress As String = SelectSaveFile("选取保存皮肤的位置", GetFileNameFromPath(Address), "皮肤图片文件(*.png)|*.png") @@ -87,7 +87,7 @@ Catch ex As Exception '#2272 Log(ex, $"皮肤文件已损坏:{Address}", LogLevel.Hint) File.Delete(Address) - Exit Sub + Return End Try ImgBack.Tag = Address '大小检查 @@ -209,11 +209,11 @@ '检查条件,获取新披风 If IsChanging Then Hint("正在更改披风中,请稍候!") - Exit Sub + Return End If If McLoginMsLoader.State = LoadState.Failed Then Hint("登录失败,无法更改披风!", HintType.Critical) - Exit Sub + Return End If Hint("正在获取披风列表,请稍候……") IsChanging = True @@ -226,7 +226,7 @@ Retry: If McLoginMsLoader.State <> LoadState.Finished Then McLoginMsLoader.WaitForExit(PageLoginMsSkin.GetLoginData()) If McLoginMsLoader.State <> LoadState.Finished Then Hint("登录失败,无法更改披风!", HintType.Critical) - Exit Sub + Return End If Dim AccessToken As String = McLoginMsLoader.Output.AccessToken Dim Uuid As String = McLoginMsLoader.Output.Uuid @@ -243,7 +243,8 @@ Retry: {"Minecon2013", "Minecon 2013 参与者披风"}, {"Minecon2015", "Minecon 2015 参与者披风"}, {"Minecon2016", "Minecon 2016 参与者披风"}, {"Cherry Blossom", "樱花披风"}, {"15th Anniversary", "15 周年纪念披风"}, {"Purple Heart", "紫色心形披风"}, {"Follower's", "追随者披风"}, {"MCC 15th Year", "MCC 15 周年披风"}, {"Minecraft Experience", "村民救援披风"}, - {"Mojang Office", "Mojang 办公室披风"}, {"Home", "家园披风"}, {"Menace", "入侵披风"} + {"Mojang Office", "Mojang 办公室披风"}, {"Home", "家园披风"}, {"Menace", "入侵披风"}, {"Yearn", "渴望披风"}, + {"Common", "普通披风"}, {"Pan", "薄煎饼披风"}, {"Founder's", "创始人披风"} } Dim SelectionControl As New List(Of IMyRadio) From {New MyRadioBox With {.Text = "无披风"}} For Each Cape In SkinData("capes") @@ -256,15 +257,16 @@ Retry: Log(ex, "获取玩家皮肤列表失败", LogLevel.Feedback) End Try End Sub) - If SelId Is Nothing Then Exit Sub + If SelId Is Nothing Then Return '发送请求 - Dim Result As String = NetRequestRetry("https://api.minecraftservices.com/minecraft/profile/capes/active", - If(SelId = 0, "DELETE", "PUT"), - If(SelId = 0, "", New JObject(New JProperty("capeId", SkinData("capes")(SelId - 1)("id"))).ToString(0)), - "application/json", Headers:=New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}}) + Dim Result As String = NetRequestByClientRetry("https://api.minecraftservices.com/minecraft/profile/capes/active", + If(SelId = 0, HttpMethod.Delete, HttpMethod.Put), + Content:=If(SelId = 0, "", New JObject(New JProperty("capeId", SkinData("capes")(SelId - 1)("id"))).ToString(0)), + ContentType:="application/json", + Headers:={{"Authorization", "Bearer " & AccessToken}}) If Result.Contains("""errorMessage""") Then - Hint("更改披风失败:" & GetJson(Result)("errorMessage"), HintType.Critical) - Exit Sub + Hint("更改披风失败:" & GetJson(Result)("errorMessage").ToString, HintType.Critical) + Return Else Hint("更改披风成功!", HintType.Finish) End If diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb index e74b323..64f43b6 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb @@ -9,7 +9,7 @@ AprilPosTrans.X = 0 AprilPosTrans.Y = 0 - If IsLoad Then Exit Sub + If IsLoad Then Return IsLoad = True AniControlEnabled += 1 @@ -243,19 +243,21 @@ If Not IsNothing(PageNew) AndAlso Not IsNothing(PageNew.Parent) Then PageNew.SetValue(ContentPresenter.ContentProperty, Nothing) If Anim Then '动画 - Dispatcher.Invoke(Sub() - '执行动画 - AniStart({ - AaOpacity(PanLogin, -PanLogin.Opacity, 100,, New AniEaseOutFluent), - AaCode(Sub() - AniControlEnabled += 1 - PanLogin.Children.Clear() - PanLogin.Children.Add(PageNew) - AniControlEnabled -= 1 - End Sub, 100), - AaOpacity(PanLogin, 1, 100, 120, New AniEaseInFluent) - }, "FrmLogin PageChange") - End Sub, Threading.DispatcherPriority.Render) + Dispatcher.Invoke( + Sub() + '执行动画 + AniStart({ + AaOpacity(PanLogin, -PanLogin.Opacity, 100,, New AniEaseOutFluent), + AaCode( + Sub() + AniControlEnabled += 1 + PanLogin.Children.Clear() + PanLogin.Children.Add(PageNew) + AniControlEnabled -= 1 + End Sub, 100), + AaOpacity(PanLogin, 1, 100, 120, New AniEaseInFluent) + }, "FrmLogin PageChange") + End Sub, Threading.DispatcherPriority.Render) Else '无动画 AniControlEnabled += 1 @@ -353,7 +355,7 @@ UnknownType: GoTo UnknownType End Select '刷新页面 - If PageCurrent = Type Then Exit Sub + If PageCurrent = Type Then Return PageChange(Type, Anim).Reload(KeepInput) Dim Control As MyRadioButton = FindName("RadioLoginType" & Setup.Get("LoginType")) If Control IsNot Nothing Then Control.Checked = True @@ -390,14 +392,14 @@ UnknownType: Result = McSkinDownload(Result) If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName) Data.Output = Result + Catch ex As ThreadInterruptedException + Data.Output = "" + Return Catch ex As Exception - If ex.GetType.Name = "ThreadInterruptedException" Then - Data.Output = "" - Exit Sub - ElseIf GetExceptionSummary(ex).Contains("429") Then + If ex.GetBrief.Contains("(429)") Then Data.Output = PathImage & "Skins/" & McSkinSex(McLoginLegacyUuid(UserName)) & ".png" Log("[Minecraft] 获取正版皮肤失败(" & UserName & "):获取皮肤太过频繁,请 5 分钟后再试!", LogLevel.Hint) - ElseIf GetExceptionSummary(ex).Contains("未设置自定义皮肤") Then + ElseIf ex.GetBrief.Contains("未设置自定义皮肤") Then Data.Output = PathImage & "Skins/" & McSkinSex(McLoginLegacyUuid(UserName)) & ".png" Log("[Minecraft] 用户未设置自定义皮肤,跳过皮肤加载") Else @@ -460,11 +462,11 @@ UseDefault: If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & ID) Data.Output = Result End If + Catch ex As ThreadInterruptedException + Data.Output = "" + Return Catch ex As Exception - If ex.GetType.Name = "ThreadInterruptedException" Then - Data.Output = "" - Exit Sub - ElseIf GetExceptionSummary(ex).Contains("429") Then + If ex.GetBrief.Contains("(429)") Then Data.Output = PathImage & "Skins/" & McSkinSex(McLoginLegacyUuid(ID)) & ".png" Log("获取离线登录使用的正版皮肤失败(" & ID & "):获取皮肤太过频繁,请 5 分钟后再试!") Else @@ -512,14 +514,14 @@ UseDefault: Result = McSkinDownload(Result) If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName) Data.Output = Result + Catch ex As ThreadInterruptedException + Data.Output = "" + Return Catch ex As Exception - If ex.GetType.Name = "ThreadInterruptedException" Then - Data.Output = "" - Exit Sub - ElseIf GetExceptionSummary(ex).Contains("429") Then + If ex.GetBrief.Contains("(429)") Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 获取统一通行证皮肤失败(" & UserName & "):获取皮肤太过频繁,请 5 分钟后再试!", LogLevel.Hint) - ElseIf GetExceptionSummary(ex).Contains("未设置自定义皮肤") Then + ElseIf ex.GetBrief.Contains("未设置自定义皮肤") Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 用户未设置自定义皮肤,跳过皮肤加载") Else @@ -548,26 +550,26 @@ Finish: RunInUi(Sub() If FrmLoginAuthSkin IsNot Nothing AndAlso FrmLoginAuthSkin.Skin IsNot Nothing Then FrmLoginAuthSkin.Skin.Clear()) '获取 Url Dim UserName As String = Data.Input(0) - Dim Uuid As String = Data.Input(1) + Dim UUID As String = Data.Input(1) If UserName = "" Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 获取 Authlib-Injector 皮肤失败,ID 为空") GoTo Finish End If Try - Dim Result As String = McSkinGetAddress(Uuid, "Auth") + Dim Result As String = McSkinGetAddress(UUID, "Auth") If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName) Result = McSkinDownload(Result) If Data.IsAborted Then Throw New ThreadInterruptedException("当前任务已取消:" & UserName) Data.Output = Result + Catch ex As ThreadInterruptedException + Data.Output = "" + Return Catch ex As Exception - If ex.GetType.Name = "ThreadInterruptedException" Then - Data.Output = "" - Exit Sub - ElseIf GetExceptionSummary(ex).Contains("429") Then + If ex.GetBrief.Contains("(429)") Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 获取 Authlib-Injector 皮肤失败(" & UserName & "):获取皮肤太过频繁,请 5 分钟后再试!", LogLevel.Hint) - ElseIf GetExceptionSummary(ex).Contains("未设置自定义皮肤") Then + ElseIf ex.GetBrief.Contains("未设置自定义皮肤") Then Data.Output = PathImage & "Skins/Steve.png" Log("[Minecraft] 用户未设置自定义皮肤,跳过皮肤加载") Else @@ -592,13 +594,13 @@ Finish: '版本选择按钮 Private Sub BtnVersion_Click(sender As Object, e As EventArgs) Handles BtnVersion.Click - If McLaunchLoader.State = LoadState.Loading Then Exit Sub + If McLaunchLoader.State = LoadState.Loading Then Return FrmMain.PageChange(FormMain.PageType.VersionSelect) End Sub '启动按钮 Public Sub LaunchButtonClick() Handles BtnLaunch.Click If McLaunchLoader.State = LoadState.Loading OrElse Not BtnLaunch.IsEnabled OrElse - (FrmMain.PageRight IsNot Nothing AndAlso FrmMain.PageRight.PageState <> MyPageRight.PageStates.ContentStay AndAlso FrmMain.PageRight.PageState <> MyPageRight.PageStates.ContentEnter) Then Exit Sub + (FrmMain.PageRight IsNot Nothing AndAlso FrmMain.PageRight.PageState <> MyPageRight.PageStates.ContentStay AndAlso FrmMain.PageRight.PageState <> MyPageRight.PageStates.ContentEnter) Then Return '愚人节处理 If IsAprilEnabled AndAlso Not IsAprilGiveup Then ThemeUnlock(12, False, "隐藏主题 滑稽彩 已解锁!") @@ -619,7 +621,7 @@ Finish: Private BtnLaunchState As Integer = 0 Private BtnLaunchVersion As McVersion = Nothing Public Sub RefreshButtonsUI() Handles BtnLaunch.Loaded - If Not BtnLaunch.IsLoaded Then Exit Sub + If Not BtnLaunch.IsLoaded Then Return '获取当前状态 Dim CurrentState As Integer If (Not IsLoadFinished) OrElse McVersionListLoader.State = LoadState.Loading OrElse McFolderListLoader.State = LoadState.Loading Then @@ -695,7 +697,7 @@ ExitRefresh: End Sub '版本设置按钮 Private Sub BtnMore_Click(sender As Object, e As EventArgs) Handles BtnMore.Click - If McLaunchLoader.State = LoadState.Loading Then Exit Sub + If McLaunchLoader.State = LoadState.Loading Then Return McVersionCurrent.Load() PageVersionLeft.Version = McVersionCurrent FrmMain.PageChange(FormMain.PageType.VersionSetup, 0) @@ -705,7 +707,7 @@ ExitRefresh: ''' Public Sub LaunchingRefresh() Try - If McLaunchLoaderReal.State = LoadState.Aborted Then Exit Sub + If McLaunchLoaderReal.State = LoadState.Aborted Then Return '阶段状态获取 Dim IsLaunched As Boolean = False '是否已经启动游戏,只是在等待窗口 Try @@ -719,7 +721,7 @@ ExitRefresh: LabLaunchingStage.Text = "已完成" Catch ex As Exception Log(ex, "获取是否启动完成失败,可能是由于启动状态改变导致集合已修改") - Exit Sub + Return End Try If AniIsRun("Launch State Page") Then IsLaunched = False '等待页面切换动画完成 '计算应显示的进度 @@ -789,7 +791,7 @@ ExitRefresh: Private ActualUsedWidth As Double Private Sub PanLaunchingInfo_SizeChangedW(sender As Object, e As SizeChangedEventArgs) Handles PanLaunchingInfo.SizeChanged Dim DeltaWidth As Double = e.NewSize.Width - e.PreviousSize.Width - If e.PreviousSize.Width = 0 OrElse IsWidthAnimating OrElse Math.Abs(DeltaWidth) < 1 OrElse PanLaunchingInfo.ActualWidth = 0 Then Exit Sub + If e.PreviousSize.Width = 0 OrElse IsWidthAnimating OrElse Math.Abs(DeltaWidth) < 1 OrElse PanLaunchingInfo.ActualWidth = 0 Then Return AniStart({ AaWidth(PanLaunchingInfo, DeltaWidth, 180,, New AniEaseOutFluent), AaCode(Sub() @@ -805,7 +807,7 @@ ExitRefresh: Private ActualUsedHeight As Double Private Sub PanLaunchingInfo_SizeChangedH(sender As Object, e As SizeChangedEventArgs) Handles PanLaunchingInfo.SizeChanged Dim DeltaHeight As Double = e.NewSize.Height - e.PreviousSize.Height - If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse PanLaunchingInfo.ActualHeight = 0 Then Exit Sub + If e.PreviousSize.Height = 0 OrElse IsHeightAnimating OrElse Math.Abs(DeltaHeight) < 1 OrElse PanLaunchingInfo.ActualHeight = 0 Then Return AniStart({ AaHeight(PanLaunchingInfo, DeltaHeight, 180,, New AniEaseOutFluent), AaCode(Sub() diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml index 1cc6a70..db61276 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:PCL"> + xmlns:local="clr-namespace:PCL" PanScroll="PanBack"> diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb index c5dfecc..65b5086 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb @@ -1,9 +1,10 @@ -Public Class PageLaunchRight - Implements IRefreshable +Imports System.Windows.Threading + +Public Class PageLaunchRight + Implements IRefreshable, IDispatcherUnhandledException Private Sub Init() Handles Me.Loaded PanBack.ScrollToHome() - PanScroll = PanBack '不知道为啥不能在 XAML 设置 PanLog.Visibility = If(ModeDebug, Visibility.Visible, Visibility.Collapsed) '快照版提示 #If BETA Then @@ -22,52 +23,35 @@ End Sub #End If -#Region "自定义主页" +#Region "主页" ''' - ''' 刷新自定义主页。 + ''' 刷新主页。 ''' Private Sub Refresh() Handles Me.Loaded - RunInNewThread( + RunInThread( Sub() Try SyncLock RefreshLock RefreshReal() End SyncLock Catch ex As Exception - Log(ex, "加载 PCL 主页自定义信息失败", LogLevel.Msgbox) + Log(ex, "加载 PCL 主页自定义信息失败", If(ModeDebug, LogLevel.Msgbox, LogLevel.Hint)) End Try - End Sub, $"刷新自定义主页 #{GetUuid()}") + End Sub) End Sub Private Sub RefreshReal() - Dim Content As String = "" - Dim Url As String + Dim Content As String = Nothing, Url As String = Nothing Select Case Setup.Get("UiCustomType") Case 1 '加载本地文件 Log("[Page] 主页自定义数据来源:本地文件") Content = ReadFile(Path & "PCL\Custom.xaml") 'ReadFile 会进行存在检测 Case 2 + '联网下载 Url = Setup.Get("UiCustomNet") -Download: - '加载联网文件 - If String.IsNullOrWhiteSpace(Url) Then Exit Select - If Url = Setup.Get("CacheSavedPageUrl") AndAlso File.Exists(PathTemp & "Cache\Custom.xaml") Then - '缓存可用 - Log("[Page] 主页自定义数据来源:联网缓存文件") - Content = ReadFile(PathTemp & "Cache\Custom.xaml") - '后台更新缓存 - OnlineLoader.Start(Url) - Else - '缓存不可用 - Log("[Page] 主页自定义数据来源:联网全新下载") - Hint("正在加载主页……") - RunInUiWait(Sub() LoadContent("")) '在加载结束前清空页面 - Setup.Set("CacheSavedPageVersion", "") - OnlineLoader.Start(Url) '下载完成后将会再次触发更新 - Exit Sub - End If Case 3 + '预设 Select Case Setup.Get("UiCustomPreset") Case 0 Log("[Page] 主页预设:你知道吗") @@ -89,52 +73,64 @@ Download: " Case 2 Log("[Page] 主页预设:Minecraft 新闻") - Url = "http://pcl.mcnews.thestack.top" - GoTo Download + Url = "https://mcnews.meloong.com" Case 3 Log("[Page] 主页预设:简单主页") - Url = "https://raw.gitcode.com/mfn233/PCL-Mainpage/raw/main/Custom.xaml" - GoTo Download + Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/MFn233/Custom.xaml" Case 4 Log("[Page] 主页预设:每日整合包推荐") Url = "https://pclsub.sodamc.com/" - GoTo Download Case 5 Log("[Page] 主页预设:Minecraft 皮肤推荐") Url = "https://forgepixel.com/pcl_sub_file" - GoTo Download Case 6 Log("[Page] 主页预设:OpenBMCLAPI 仪表盘 Lite") Url = "https://pcl-bmcl.milu.ink/" - GoTo Download Case 7 Log("[Page] 主页预设:主页市场") Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/JingHai-Lingyun/Custom.xaml" - GoTo Download Case 8 Log("[Page] 主页预设:更新日志") Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Joker2184/UpdateHomepage.xaml" - GoTo Download Case 9 Log("[Page] 主页预设:PCL 新功能说明书") Url = "https://raw.gitcode.com/WForst-Breeze/WhatsNewPCL/raw/main/Custom.xaml" - GoTo Download Case 10 Log("[Page] 主页预设:OpenMCIM Dashboard") Url = "https://files.mcimirror.top/PCL" - GoTo Download Case 11 Log("[Page] 主页预设:杂志主页") Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Ext1nguisher/Custom.xaml" - GoTo Download + Case 12 + Log("[Page] 主页预设:PCL GitHub 仪表盘") + Url = "https://ddf.pcl-community.org/Custom.xaml" End Select End Select + '联网下载 + If Not String.IsNullOrWhiteSpace(Url) Then + If Url = Setup.Get("CacheSavedPageUrl") AndAlso File.Exists(PathTemp & "Cache\Custom.xaml") Then + '缓存可用 + Log("[Page] 主页自定义数据来源:联网缓存文件") + Content = ReadFile(PathTemp & "Cache\Custom.xaml") + '后台更新缓存 + OnlineLoader.Start(Url) + Else + '缓存不可用 + Log("[Page] 主页自定义数据来源:联网全新下载") + Hint("正在加载主页……") + RunInUiWait(Sub() LoadContent(Nothing)) '在加载结束前清空页面 + Setup.Set("CacheSavedPageVersion", "") + OnlineLoader.Start(Url) '下载完成后将会再次触发更新 + Return + End If + End If + '同步到 UI RunInUi(Sub() LoadContent(Content)) End Sub Private RefreshLock As New Object - '联网获取自定义主页文件 - Private OnlineLoader As New LoaderTask(Of String, Integer)("自定义主页获取", AddressOf OnlineLoaderSub) With {.ReloadTimeout = 10 * 60 * 1000} + '联网获取主页文件 + Private OnlineLoader As New LoaderTask(Of String, Integer)("下载主页", AddressOf OnlineLoaderSub) With {.ReloadTimeout = 10 * 60 * 1000} Private Sub OnlineLoaderSub(Task As LoaderTask(Of String, Integer)) Dim Address As String = Task.Input '#3721 中连续触发两次导致内容变化 Try @@ -146,50 +142,44 @@ Download: VersionAddress = Address.BeforeFirst("?") If Not VersionAddress.EndsWith("/") Then VersionAddress += "/" VersionAddress += "version" - If Address.Contains("?") Then VersionAddress += Address.AfterLast("?") + If Address.Contains("?") Then VersionAddress += "?" & Address.AfterFirst("?") End If '校验版本 Dim Version As String = "" Dim NeedDownload As Boolean = True Try - Version = NetGetCodeByRequestOnce(VersionAddress, Timeout:=10000) - If Version.Length > 1000 Then Throw New Exception($"获取的自定义主页版本过长({Version.Length} 字符)") + Version = NetRequestByClientRetry(VersionAddress) + If Version.Length > 1000 Then Throw New Exception($"获取的主页版本过长({Version.Length} 字符)") Dim CurrentVersion As String = Setup.Get("CacheSavedPageVersion") If Version <> "" AndAlso CurrentVersion <> "" AndAlso Version = CurrentVersion Then - Log($"[Page] 当前缓存的自定义主页已为最新,当前版本:{Version},检查源:{VersionAddress}") + Log($"[Page] 当前缓存的主页已为最新,当前版本:{Version},检查源:{VersionAddress}") NeedDownload = False Else - Log($"[Page] 需要下载联网自定义主页,当前版本:{Version},检查源:{VersionAddress}") + Log($"[Page] 需要下载联网主页,当前版本:{Version},检查源:{VersionAddress}") End If Catch exx As Exception - Log(exx, $"联网获取自定义主页版本失败", LogLevel.Developer) - Log($"[Page] 无法检查联网自定义主页版本,将直接下载,检查源:{VersionAddress}") + Log(exx, $"联网获取主页版本失败", LogLevel.Developer) + Log($"[Page] 无法检查联网主页版本,将直接下载,检查源:{VersionAddress}") End Try '实际下载 - If NeedDownload Then - Dim FileContent As String = NetGetCodeByRequestRetry(Address) - Log($"[Page] 已联网下载自定义主页,内容长度:{FileContent.Length},来源:{Address}") - Setup.Set("CacheSavedPageUrl", Address) - Setup.Set("CacheSavedPageVersion", Version) - WriteFile(PathTemp & "Cache\Custom.xaml", FileContent) - End If - '要求刷新 - Refresh() + If Not NeedDownload Then Return + Dim FileContent As String = NetRequestByClientRetry(Address) + Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}") + Setup.Set("CacheSavedPageUrl", Address) + Setup.Set("CacheSavedPageVersion", Version) + WriteFile(PathTemp & "Cache\Custom.xaml", FileContent) + Refresh() '要求刷新 Catch ex As Exception - If Setup.Get("CacheSavedPageVersion") = "" Then - Log(ex, $"联网下载自定义主页失败({Address})", LogLevel.Msgbox) - Else - Log(ex, $"联网下载自定义主页失败({Address})") - End If + Log(ex, $"下载主页失败({Address})", If(ModeDebug, LogLevel.Msgbox, LogLevel.Hint)) End Try End Sub ''' - ''' 立即强制刷新自定义主页。 + ''' 立即强制刷新主页。 ''' 必须在 UI 线程调用。 ''' Public Sub ForceRefresh() Implements IRefreshable.Refresh - Log("[Page] 要求强制刷新自定义主页") + Log("[Page] 要求强制刷新主页") ClearCache() '实际的刷新 If FrmMain.PageCurrent.Page = FormMain.PageType.Launch Then @@ -201,55 +191,74 @@ Download: End Sub ''' - ''' 清空自定义主页缓存信息。 + ''' 清空主页缓存信息。 ''' Private Sub ClearCache() LoadedContentHash = -1 OnlineLoader.Input = "" Setup.Set("CacheSavedPageUrl", "") Setup.Set("CacheSavedPageVersion", "") - Log("[Page] 已清空自定义主页缓存") + Log("[Page] 已清空主页缓存") End Sub ''' - ''' 从文本内容中加载自定义主页。 + ''' 从文本内容中加载主页。 ''' 必须在 UI 线程调用。 ''' Private Sub LoadContent(Content As String) - SyncLock LoadContentLock - '如果加载目标内容一致则不加载 - Dim Hash = Content.GetHashCode() - If Hash = LoadedContentHash Then Exit Sub - LoadedContentHash = Hash - '实际加载内容 - PanCustom.Children.Clear() - If String.IsNullOrWhiteSpace(Content) Then - Log($"[Page] 实例化:清空自定义主页 UI,来源为空") - Return - End If - Try + Try + SyncLock LoadContentLock + '如果加载目标内容一致则不加载 + Dim Hash = If(Content, "").GetHashCode() + If Hash = LoadedContentHash Then Return + LoadedContentHash = Hash + '实际加载内容 + PanCustom.Children.Clear() + If String.IsNullOrWhiteSpace(Content) Then + Log($"[Page] 实例化:清空主页 UI,来源为空") + Return + End If + Dim LoadStartTime As Date = Date.Now + '修改时应同时修改 PageOtherHelpDetail.Init Content = HelpArgumentReplace(Content) - If Content.Contains("xmlns") Then Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间 + Do While Content.Contains("xmlns") + Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间 + Loop Content = "" & Content & "" - Log($"[Page] 实例化:加载自定义主页 UI 开始,最终内容长度:{Content.Count}") + Log($"[Page] 实例化:加载主页 UI 开始,最终内容长度:{Content.Count}") PanCustom.Children.Add(GetObjectFromXML(Content)) - Catch ex As UnauthorizedAccessException - Log(ex, "加载失败的自定义主页内容:" & vbCrLf & Content) - If MyMsgBox(ex.Message, "加载自定义主页失败", "重试", "取消") = 1 Then - GoTo Refresh '防止 SyncLock 死锁 - End If - Catch ex As Exception - Log(ex, "加载失败的自定义主页内容:" & vbCrLf & Content) - If MyMsgBox($"自定义主页内容编写有误,请根据下列错误信息进行检查:{vbCrLf}{GetExceptionSummary(ex)}", "加载自定义主页失败", "重试", "取消") = 1 Then - GoTo Refresh '防止 SyncLock 死锁 - End If - End Try - Log($"[Page] 实例化:加载自定义主页 UI 完成") - End SyncLock - Return -Refresh: - ForceRefresh() + '加载计时 + Dim LoadCostTime = (Date.Now - LoadStartTime).Milliseconds + Log($"[Page] 实例化:加载主页 UI 完成,耗时 {LoadCostTime}ms") + If LoadCostTime > 3000 Then Hint($"主页加载过于缓慢(花费了 {Math.Round(LoadCostTime / 1000, 1)} 秒),请向主页作者反馈此问题,或暂时停止使用该主页") + End SyncLock + Catch ex As Exception + Log(ex, "加载失败的主页内容:" & vbCrLf & Content) + OnLoadContentFailed(ex) + End Try End Sub + ''' + ''' 加载主页失败时调用。 + ''' + Private Sub OnLoadContentFailed(ex As Exception) + If ModeDebug OrElse Setup.Get("UiCustomType") = 1 Then + Log(ex, "加载主页失败") + If MyMsgBox(If(TypeOf ex Is UnauthorizedAccessException, ex.Message, $"主页内容编写有误,请根据下列错误信息进行检查:{vbCrLf}{ex.GetBrief}"), + "加载主页失败", "重试", "取消") = 1 Then ForceRefresh() + Else + Log(ex, "加载主页失败", LogLevel.Hint) + End If + End Sub + ''' + ''' 捕获主页在 Measure 和 Arrange 阶段抛出的异常。 + ''' + Private Sub DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Implements IDispatcherUnhandledException.DispatcherUnhandledException + If TypeOf e.Exception IsNot Markup.XamlParseException Then Return + e.Handled = True + LoadContent(Nothing) + OnLoadContentFailed(e.Exception) + End Sub + Private LoadedContentHash As Integer = -1 Private LoadContentLock As New Object diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb index fec81e6..56970a4 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb @@ -49,11 +49,10 @@ Log("[Launch] 要求更换角色,但登录加载器繁忙", LogLevel.Debug) If CType(McLoginLoader.Input, McLoginServer).ForceReselectProfile Then Hint("正在尝试更换,请稍候!") - Exit Sub Else Hint("正在登录中,请稍后再更换角色!", HintType.Critical) - Exit Sub End If + Return End If Hint("正在尝试更换,请稍候!") Setup.Set("CacheAuthUuid", "") '清空选择缓存 diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml index e531ac5..2ca71d0 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml @@ -5,8 +5,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:PCL" mc:Ignorable="d" d:DesignWidth="297.6"> - - + + diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb index dc1c043..33cafbb 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb @@ -45,7 +45,7 @@ Public Class PageLoginLegacy If LoginData.UserName.Trim = "" Then Return "玩家名不能为空!" If LoginData.UserName.Contains("""") Then Return "玩家名不能包含英文引号!" If McVersionCurrent IsNot Nothing AndAlso - ((McVersionCurrent.Version.McCodeMain = 20 AndAlso McVersionCurrent.Version.McCodeSub >= 3) OrElse McVersionCurrent.Version.McCodeMain > 20) AndAlso + (McVersionCurrent.Version.IsStandardVersion AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 20, 3)) AndAlso LoginData.UserName.Trim.Length > 16 Then Return "自 1.20.3 起,玩家名至多只能包含 16 个字符!" End If @@ -64,6 +64,7 @@ Public Class PageLoginLegacy Private Sub ComboLegacy_TextChanged(sender As Object, e As TextChangedEventArgs) Handles ComboName.TextChanged If Setup.Get("LaunchSkinType") = 0 Then PageLaunchLeft.SkinLegacy.Start(IsForceRestart:=True) HintChinese.Visibility = If(RegexCheck(ComboName.Text, "^[0-9A-Za-z_]*$"), Visibility.Collapsed, Visibility.Visible) + RunInUi(Sub() PanMain.InvalidateMeasure(), True) '由于 WPF 的 Bug,它不一定会自动更新 HintChinese 的大小(#6627) End Sub Private Sub Skin_Click() Handles Skin.Click If (Setup.Get("UiHiddenPageSetup") OrElse Setup.Get("UiHiddenSetupLaunch")) AndAlso Not PageSetupUI.HiddenForceShow Then diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb index 49c627e..ae823a7 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb @@ -25,7 +25,7 @@ ComboAccounts.SelectedIndex = If(KeepInput, Math.Max(0, IndexBefore), 0) End Sub Private Sub ComboAccounts_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboAccounts.SelectionChanged - If AniControlEnabled <> 0 OrElse ComboAccounts.SelectedItem Is Nothing OrElse ComboAccounts.ContentPresenter Is Nothing Then Exit Sub + If AniControlEnabled <> 0 OrElse ComboAccounts.SelectedItem Is Nothing OrElse ComboAccounts.ContentPresenter Is Nothing Then Return If TypeOf ComboAccounts.SelectedItem Is MyListItem Then ComboAccounts.ContentPresenter.Content = CType(ComboAccounts.SelectedItem, MyListItem).Title ElseIf TypeOf ComboAccounts.SelectedItem Is MyComboBoxItem Then diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml index f8acc1a..f6c72da 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml @@ -23,10 +23,11 @@ - - + + + diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb index 29e865e..4b808f9 100644 --- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb @@ -33,7 +33,7 @@ AniStart(AaOpacity(PanButtons, 1 - PanButtons.Opacity, 120), "PageLoginMsSkin Button") End Sub Public Sub HidePanel() Handles PanData.MouseLeave - If BtnEdit.ContextMenu.IsOpen OrElse BtnSkin.ContextMenu.IsOpen OrElse PanData.IsMouseOver Then Exit Sub + If BtnEdit.ContextMenu.IsOpen OrElse BtnSkin.ContextMenu.IsOpen OrElse PanData.IsMouseOver Then Return AniStart(AaOpacity(PanButtons, -PanButtons.Opacity, 120), "PageLoginMsSkin Button") End Sub @@ -74,41 +74,40 @@ '检查条件,获取新皮肤 If IsChanging Then Hint("正在更改皮肤中,请稍候!") - Exit Sub + Return End If If McLoginLoader.State = LoadState.Failed Then Hint("登录失败,无法更改皮肤!", HintType.Critical) - Exit Sub + Return End If Dim SkinInfo As McSkinInfo = McSkinSelect() - If Not SkinInfo.IsVaild Then Exit Sub + If Not SkinInfo.IsVaild Then Return Hint("正在更改皮肤……") IsChanging = True '开始实际获取 RunInNewThread( - Async Sub() + Sub() Try Retry: - If McLoginMsLoader.State = LoadState.Loading Then McLoginMsLoader.WaitForExit() '等待登录结束 + 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 = Setup.Get("CacheMsV2Access") Dim Uuid As String = Setup.Get("CacheMsV2Uuid") - - Dim Client As New Net.Http.HttpClient With {.Timeout = New TimeSpan(0, 0, 30)} - Client.DefaultRequestHeaders.Authorization = New Net.Http.Headers.AuthenticationHeaderValue("Bearer", AccessToken) - Client.DefaultRequestHeaders.Accept.Add(New Net.Http.Headers.MediaTypeWithQualityHeaderValue("*/*")) - Client.DefaultRequestHeaders.UserAgent.Add(New Net.Http.Headers.ProductInfoHeaderValue("MojangSharp", "0.1")) - Dim Contents As 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)} - } - Dim Result As String = Await (Await Client.PostAsync(New Uri("https://api.minecraftservices.com/minecraft/profile/skins"), Contents)).Content.ReadAsStringAsync + 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"), HintType.Critical) - Exit Sub + Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Critical) + Return End If '获取新皮肤地址 Log("[Skin] 皮肤修改返回值:" & vbCrLf & Result) @@ -117,13 +116,13 @@ Retry: For Each Skin As JObject In ResultJson("skins") If Skin("state").ToString = "ACTIVE" Then MySkin.ReloadCache(Skin("url")) - Exit Sub + Return End If Next Throw New Exception("未知错误(" & Result & ")") Catch ex As Exception - If ex.GetType.Equals(GetType(Tasks.TaskCanceledException)) Then - Hint("更改皮肤失败:与 Mojang 皮肤服务器的连接超时,请检查你的网络是否通畅!", HintType.Critical) + If TypeOf ex Is OperationCanceledException Then + Hint("更改皮肤失败:连接皮肤服务器超时,请稍后再试,或使用 VPN 改善网络环境", HintType.Critical) Else Log(ex, "更改皮肤失败", LogLevel.Hint) End If diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml index 7ac9b47..a64acc2 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkFeedback.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageLinkFeedback" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml index 2c597b6..9cf794e 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml @@ -103,7 +103,6 @@ - diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb index e0639ae..b4f7a3b 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb @@ -18,7 +18,7 @@ Private IsLoad As Boolean = False Private Sub OnLoaded() Handles Me.Loaded FormMain.EndProgramForce(ProcessReturnValues.Aborted) - If IsLoad Then Exit Sub + If IsLoad Then Return IsLoad = True '启动监视线程 If Not IsWatcherStarted Then RunInNewThread(AddressOf WatcherThread, "Hiper Watcher") @@ -149,7 +149,7 @@ '每 1 秒执行的 Timer Private Sub WatcherTimer1() - If HiperState <> LoadState.Finished Then Exit Sub + If HiperState <> LoadState.Finished Then Return RunInUi(Sub() '索引码剩余时间 Dim Span As TimeSpan = HiperCertTime - Date.Now @@ -178,7 +178,7 @@ HiperExit(True) ShowWindowToTop(Handle) Beep() - Exit Sub + Return End If '网络质量 Dim QualityScore As Integer = If(IpIsInChina, 0, -2) @@ -272,7 +272,7 @@ Log("[Hiper] 连接步骤:" & Intro) LoadStep = [Step] RunInUiWait(Sub() - If FrmLinkHiper Is Nothing OrElse Not FrmLinkHiper.LabLoadDesc.IsLoaded Then Exit Sub + If FrmLinkHiper Is Nothing OrElse Not FrmLinkHiper.LabLoadDesc.IsLoaded Then Return FrmLinkHiper.LabLoadDesc.Text = Intro FrmLinkHiper.UpdateProgress() End Sub) @@ -280,7 +280,7 @@ '承接重试 Private Sub CardLoad_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles CardLoad.MouseLeftButtonUp - If Not InitLoader.State = LoadState.Failed Then Exit Sub + If Not InitLoader.State = LoadState.Failed Then Return InitLoader.Start(IsForceRestart:=True) End Sub @@ -299,7 +299,7 @@ Private Sub UpdateProgress(Optional Value As Double = -1) If Value = -1 Then Value = InitLoader.Progress Dim DisplayingProgress As Double = ColumnProgressA.Width.Value - If Math.Round(Value - DisplayingProgress, 3) = 0 Then Exit Sub + If Math.Round(Value - DisplayingProgress, 3) = 0 Then Return If DisplayingProgress > Value Then ColumnProgressA.Width = New GridLength(Value, GridUnitType.Star) ColumnProgressB.Width = New GridLength(1 - Value, GridUnitType.Star) @@ -327,7 +327,7 @@ '退出 Private Sub BtnFinishExit_Click(sender As Object, e As EventArgs) Handles BtnFinishExit.Click - If IsServerSide AndAlso MyMsgBox("你确定要关闭联机房间吗?", "确认退出", "确定", "取消", IsWarn:=True) = 2 Then Exit Sub + If IsServerSide AndAlso MyMsgBox("你确定要关闭联机房间吗?", "确认退出", "确定", "取消", IsWarn:=True) = 2 Then Return HiperExit(False) End Sub @@ -338,7 +338,7 @@ 'Ping 房主 Private Sub BtnFinishPing_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles BtnFinishPing.MouseLeftButtonUp LabFinishPing.Text = "检测中" - If TaskPingHost.State = LoadState.Loading Then Exit Sub + If TaskPingHost.State = LoadState.Loading Then Return TaskPingHost.Start(True, IsForceRestart:=True) End Sub Private Shared TaskPingHost As New LoaderTask(Of Boolean, Integer)("HiPer Ping Host", @@ -362,7 +362,7 @@ Return _CurrentSubpage End Get Set(value As Subpages) - If _CurrentSubpage = value Then Exit Property + If _CurrentSubpage = value Then Return _CurrentSubpage = value Log("[Hiper] 子页面更改为 " & GetStringFromEnum(value)) PageOnContentExit() @@ -384,7 +384,7 @@ If InitLoader.State = LoadState.Loading Then InitLoader.Abort() If InitLoader.State = LoadState.Failed Then InitLoader.State = LoadState.Waiting RunInUi(Sub() - If FrmLinkHiper Is Nothing OrElse Not FrmLinkHiper.IsLoaded Then Exit Sub + If FrmLinkHiper Is Nothing OrElse Not FrmLinkHiper.IsLoaded Then Return FrmLinkHiper.CurrentSubpage = If(ExitToCertPage, Subpages.PanCert, Subpages.PanSelect) FrmLinkHiper.PageOnContentExit() End Sub) diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml index eee6bba..cc093ec 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml @@ -4,13 +4,13 @@ xmlns:System="clr-namespace:System;assembly=mscorlib" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageLinkIoi" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> - + diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml b/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml index bf07b19..2d06a86 100644 --- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:PCL" - mc:Ignorable="d" AnimatedControl="{Binding ElementName=PanItem}" MinWidth="150"> + mc:Ignorable="d" AnimatedControl="PanItem" MinWidth="150"> Public Sub PageChange(ID As FormMain.PageSubType) - If PageID = ID Then Exit Sub + If PageID = ID Then Return AniControlEnabled += 1 IsPageSwitched = True Try diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml index 27ef038..0f8afa1 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageOtherAbout" - PanScroll="{Binding ElementName=PanBack}" RenderOptions.BitmapScalingMode="LowQuality"> + PanScroll="PanBack" RenderOptions.BitmapScalingMode="LowQuality"> @@ -17,7 +17,7 @@ - + @@ -29,7 +29,7 @@ - + @@ -51,11 +51,12 @@ - + - - - + + + + @@ -70,12 +71,12 @@ - + - + @@ -104,192 +105,288 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -304,11 +401,13 @@ - + - - - + + + + + @@ -316,7 +415,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb index 237bd2a..13e7a3f 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml.vb @@ -7,7 +7,7 @@ PanBack.ScrollToHome() '非重复加载部分 - If IsLoaded Then Exit Sub + If IsLoaded Then Return IsLoaded = True ItemAboutPcl.Info = ItemAboutPcl.Info.Replace("%VERSION%", VersionDisplayName).Replace("%VERSIONCODE%", VersionCode).Replace("%BRANCH%", VersionBranchCode) @@ -18,12 +18,6 @@ End Sub - Private Sub BtnAboutBmclapi_Click(sender As Object, e As EventArgs) Handles BtnAboutBmclapi.Click - OpenWebsite("https://afdian.com/a/bangbang93") - End Sub - Private Sub BtnAboutWiki_Click(sender As Object, e As EventArgs) Handles BtnAboutWiki.Click - OpenWebsite("https://www.mcmod.cn") - End Sub Public Shared Sub CopyUniqueAddress() Handles BtnDonateCopy.Click ClipboardSet(UniqueAddress) End Sub diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml index 81a1636..411d5bb 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageOtherHelp" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb index f6df291..2802cd4 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb @@ -55,7 +55,7 @@ If Type = "指南" Then MyCard.StackInstall(NewStack, 11, "指南") Else - NewCard.IsSwaped = True + NewCard.IsSwapped = True End If PanList.Children.Add(NewCard) Next diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml index 397f398..8ad8d1b 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:PCL" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb index fda05ce..dafcd2e 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb @@ -14,17 +14,20 @@ ''' 根据特定帮助项初始化页面 UI,返回是否成功加载。 ''' Public Function Init(Entry As HelpEntry) As Boolean - Dim FileContent = "" & If(Entry.XamlContent, "") & "" + Dim Content = If(Entry.XamlContent, "") + If Content = "" Then Throw New Exception("帮助 xaml 文件为空") Try - If String.IsNullOrEmpty(Entry.XamlContent) Then Throw New Exception("帮助 xaml 文件为空") + '修改时应同时修改 PageLaunchRight.LoadContent + Content = HelpArgumentReplace(Content) + If Content.Contains("xmlns") Then Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间 + Content = "" & Content & "" Me.Entry = Entry PanCustom.Children.Clear() - FileContent = HelpArgumentReplace(FileContent) - PanCustom.Children.Add(GetObjectFromXML(FileContent)) + PanCustom.Children.Add(GetObjectFromXML(Content)) Return True Catch ex As Exception - Log("[System] 自定义信息内容:" & vbCrLf & FileContent) - Log(ex, "加载帮助 xaml 文件失败", LogLevel.Msgbox) + Log("[System] 自定义信息内容:" & vbCrLf & Content) + Log(ex, "加载帮助 XAML 文件失败", LogLevel.Msgbox) Return False End Try End Function diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml index 0c62e42..e423e76 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml @@ -1,7 +1,7 @@  + xmlns:local="clr-namespace:PCL" AnimatedControl="PanItem"> diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb index 59df742..5ecaa33 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb @@ -10,12 +10,12 @@ If ItemTest.Checked AndAlso Setup.Get("UiHiddenOtherTest") Then IsHiddenPage = True If PageSetupUI.HiddenForceShow Then IsHiddenPage = False '若页面错误,或尚未加载,则继续 - If IsLoad AndAlso Not IsHiddenPage Then Exit Sub + If IsLoad AndAlso Not IsHiddenPage Then Return IsLoad = True '刷新子页面隐藏情况 PageSetupUI.HiddenRefresh() '选择第一个未被禁用的子页面 - If IsPageSwitched Then Exit Sub + If IsPageSwitched Then Return If Not Setup.Get("UiHiddenOtherHelp") Then ItemHelp.SetChecked(True, False, False) ElseIf Not Setup.Get("UiHiddenOtherAbout") Then @@ -76,7 +76,7 @@ ''' 切换现有页面。 ''' Public Sub PageChange(ID As FormMain.PageSubType) - If PageID = ID Then Exit Sub + If PageID = ID Then Return AniControlEnabled += 1 IsPageSwitched = True Try @@ -126,29 +126,29 @@ '打开网页 Private Sub TryFeedback(sender As Object, e As RouteEventArgs) Handles ItemFeedback.Changed - If Not ItemFeedback.Checked Then Exit Sub + If Not ItemFeedback.Checked Then Return TryFeedback() e.Handled = True End Sub Public Shared Sub TryFeedback() - If Not CanFeedback(True) Then Exit Sub - Select Case MyMsgBox("在提交新反馈前,建议先搜索反馈列表,以避免重复提交。" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。", + If Not CanFeedback(True) Then Return + Select Case MyMsgBox("在提交新反馈前,建议先搜索反馈列表,以避免重复提交。" & vbCrLf & "如果无法打开该网页,请使用 VPN 改善网络环境。", "反馈", "提交新反馈", "查看反馈列表", "取消") Case 1 Feedback(True, False) Case 2 - OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/") + OpenWebsite("https://github.com/Meloong-Git/PCL/issues/") End Select End Sub Private Sub TryVote(sender As Object, e As RouteEventArgs) Handles ItemVote.Changed - If Not ItemVote.Checked Then Exit Sub + If Not ItemVote.Checked Then Return TryVote() e.Handled = True End Sub Public Shared Sub TryVote() - If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。", - "新功能投票", "打开", "取消") = 2 Then Exit Sub - OpenWebsite("https://github.com/Hex-Dragon/PCL2/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Adate_created") + If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请使用 VPN 改善网络环境。", + "新功能投票", "打开", "取消") = 2 Then Return + OpenWebsite("https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Adate_created") End Sub End Class diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherTest.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherTest.xaml index 9623808..a8ba073 100644 --- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherTest.xaml +++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherTest.xaml @@ -5,7 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:PCL" xmlns:sys="clr-namespace:System;assembly=mscorlib" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml index 2dfe903..e7b6bc6 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml @@ -1,7 +1,7 @@  + AnimatedControl="PanList"> diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb index 254d081..7d8d927 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb @@ -21,7 +21,7 @@ Exit For End If Next - If IsEqual Then Exit Sub + If IsEqual Then Return End If McFolderListLast = McFolderList @@ -127,7 +127,7 @@ For i = 0 To McFolderList.Count - 1 If McFolderList(i).Path = PathMcFolder Then CType(FrmSelectLeft.PanList.Children(i + 1), MyListItem).Checked = True '去掉第一个标题 - Exit Sub + Return End If Next If Not McFolderList.Any() Then @@ -151,20 +151,20 @@ '检查是否有下载任务 If HasDownloadingTask() Then Hint("在下载任务进行时,无法添加游戏文件夹!", HintType.Critical) - Exit Sub + Return End If Try '获取输入 NewFolder = SelectFolder() - If NewFolder = "" Then Exit Sub - If NewFolder.Contains("!") OrElse NewFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Exit Sub + If NewFolder = "" Then Return + If NewFolder.Contains("!") OrElse NewFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Return '要求输入显示名称 Dim SplitedNames As String() = NewFolder.TrimEnd("\").Split("\") Dim DefaultName As String = If(SplitedNames.Last = ".minecraft", If(SplitedNames.Count >= 3, SplitedNames(SplitedNames.Count - 2), ""), SplitedNames.Last) If DefaultName.Length > 40 Then DefaultName = DefaultName.Substring(0, 39) Dim NewName As String = MyMsgBoxInput("输入显示名称", "输入该文件夹在左边栏列表中显示的名称。", DefaultName, New ObjectModel.Collection(Of Validate) From {New ValidateNullOrWhiteSpace, New ValidateLength(1, 30), New ValidateExcept({">", "|"})}) - If String.IsNullOrWhiteSpace(NewName) Then Exit Sub + If String.IsNullOrWhiteSpace(NewName) Then Return '添加文件夹 AddFolder(NewFolder, NewName, True) Catch ex As Exception @@ -183,7 +183,7 @@ If Not CheckPermission(FolderPath) Then If ShowHint Then Hint("添加文件夹失败:PCL 没有访问该文件夹的权限!", HintType.Critical) - Exit Sub + Return Else Throw New Exception("PCL 没有访问文件夹的权限:" & FolderPath) End If @@ -208,7 +208,7 @@ IsAdded = True If Folder.Split(">")(0) = DisplayName Then If ShowHint Then Hint("此文件夹已在列表中!", HintType.Info) - Exit Sub + Return Else Folders(i) = DisplayName & ">" & FolderPath IsReplace = True @@ -225,15 +225,15 @@ Setup.Set("LaunchFolderSelect", FolderPath.Replace(Path, "$")) McFolderListLoader.Start(IsForceRestart:=True) '提示 - If IsReplace Then Exit Sub + If IsReplace Then Return If ShowHint Then Hint("文件夹 " & DisplayName & " 已添加!", HintType.Finish) '检查是否为根目录整合包,自动关闭版本隔离 '1. 根目录中存在数个 Mod Dim ModFolder As New DirectoryInfo(FolderPath & "mods\") - If Not (ModFolder.Exists AndAlso ModFolder.EnumerateFiles.Count >= 3) Then Exit Sub + If Not (ModFolder.Exists AndAlso ModFolder.EnumerateFiles.Count >= 3) Then Return '2. 版本数较少,可能为整合包 Dim VersionFolder As New DirectoryInfo(FolderPath & "versions\") - If Not (VersionFolder.Exists AndAlso VersionFolder.EnumerateDirectories.Count <= 3) Then Exit Sub + If Not (VersionFolder.Exists AndAlso VersionFolder.EnumerateDirectories.Count <= 3) Then Return '3. 能够找到可安装 Mod 的版本 For Each VersionPath In VersionFolder.EnumerateDirectories Dim Version As New McVersion(VersionPath.FullName) @@ -241,7 +241,7 @@ If Not Version.Modable Then Continue For '4. 该版本的隔离文件夹下不存在 mods Dim ModIndieFolder As New DirectoryInfo(Version.Path & "mods\") - If ModIndieFolder.Exists AndAlso ModIndieFolder.EnumerateFiles.Any Then Exit Sub + If ModIndieFolder.Exists AndAlso ModIndieFolder.EnumerateFiles.Any Then Return '满足以上全部条件则视为根目录整合包 Setup.Set("VersionArgumentIndie", 2, Version:=Version) Setup.Set("VersionArgumentIndieV2", False, Version:=Version) @@ -258,7 +258,7 @@ '检查是否有下载任务 If HasDownloadingTask() Then Hint("在下载任务进行时,无法创建游戏文件夹!", HintType.Critical) - Exit Sub + Return End If If Not Directory.Exists(Path & ".minecraft\") Then Directory.CreateDirectory(Path & ".minecraft\") @@ -290,7 +290,7 @@ '不删除 Case 3 '取消 - Exit Sub + Return End Select End If '若修改了本部分代码,应对应修改 Delete_Click 中的代码 @@ -317,8 +317,8 @@ Public Sub Delete_Click(sender As Object, e As RoutedEventArgs) Dim Folder As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag Dim DeleteText As String = If((Folder.Type = McFolderType.Original OrElse Folder.Type = McFolderType.RenamedOriginal) AndAlso Folder.Path = Path & ".minecraft\" AndAlso McFolderList.Count = 1, "清空", "删除") - If MyMsgBox("你确定要" & DeleteText & "这个文件夹吗?" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这会导致该文件夹中的所有存档与其他文件永久丢失,且不可恢复!", "删除警告", "取消", "确认", "取消") <> 2 Then Exit Sub - If MyMsgBox("如果你在该文件夹中存放了除 MC 以外的其他文件,这些文件也会被一同删除!" & vbCrLf & "继续删除会导致该文件夹中的所有文件永久丢失,请在仔细确认后再继续!" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这是最后一次警告!", "删除警告", "确认" & DeleteText, "取消", IsWarn:=True) <> 1 Then Exit Sub + If MyMsgBox("你确定要" & DeleteText & "这个文件夹吗?" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这会导致该文件夹中的所有存档与其他文件永久丢失,且不可恢复!", "删除警告", "取消", "确认", "取消") <> 2 Then Return + If MyMsgBox("如果你在该文件夹中存放了除 MC 以外的其他文件,这些文件也会被一同删除!" & vbCrLf & "继续删除会导致该文件夹中的所有文件永久丢失,请在仔细确认后再继续!" & vbCrLf & "目标文件夹:" & Folder.Path & vbCrLf & vbCrLf & "这是最后一次警告!", "删除警告", "确认" & DeleteText, "取消", IsWarn:=True) <> 1 Then Return '移出列表 If Folder.Type = McFolderType.Custom Then Dim Folders As New List(Of String)(Setup.Get("LaunchFolders").ToString.Split("|")) @@ -369,7 +369,7 @@ Dim NewName As String = MyMsgBoxInput("输入新名称", "", Folder.Name, New ObjectModel.Collection(Of Validate) From {New ValidateNullOrWhiteSpace, New ValidateLength(1, 30), New ValidateExcept({">", "|"})}) - If String.IsNullOrWhiteSpace(NewName) Then Exit Sub + If String.IsNullOrWhiteSpace(NewName) Then Return '修改自定义名 Dim Folders As New List(Of String)(Setup.Get("LaunchFolders").ToString.Split("|")) Dim IsAdded As Boolean = False @@ -380,7 +380,7 @@ IsAdded = True If FolderCurrent.Split(">")(0) = NewName Then '名称未修改 - Exit Sub + Return Else Folders(i) = NewName & ">" & Folder.Path End If @@ -400,12 +400,12 @@ '点击选项 Public Sub Folder_Change(sender As MyListItem, e As RouteEventArgs) - If Not e.RaiseByMouse OrElse Not sender.Checked Then Exit Sub + If Not e.RaiseByMouse OrElse Not sender.Checked Then Return '检查是否有下载任务 If HasDownloadingTask(True) Then Hint("在下载任务进行时,无法切换游戏文件夹!", HintType.Critical) e.Handled = True - Exit Sub + Return End If '更换 Setup.Set("LaunchFolderSelect", CType(sender.Tag, McFolder).Path.Replace(Path, "$")) diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml index 2ded4ab..bcc54aa 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageSelectRight" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb index 5fa1311..e6e7d27 100644 --- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb @@ -81,15 +81,15 @@ PanMain.Children.Add(NewCard) '确定卡片是否展开 If Card.Key = McVersionCardType.Rubbish OrElse Card.Key = McVersionCardType.Error OrElse Card.Key = McVersionCardType.Fool Then - NewCard.IsSwaped = True + NewCard.IsSwapped = True Else MyCard.StackInstall(NewStack, 0, CardTitle) End If Next '若只有一个卡片,则强制展开 - If PanMain.Children.Count = 1 AndAlso CType(PanMain.Children(0), MyCard).IsSwaped Then - CType(PanMain.Children(0), MyCard).IsSwaped = False + If PanMain.Children.Count = 1 AndAlso CType(PanMain.Children(0), MyCard).IsSwapped Then + CType(PanMain.Children(0), MyCard).IsSwapped = False End If '判断应该显示哪一个页面 @@ -227,11 +227,11 @@ DeleteDirectory(Version.Path) Hint("版本 " & Version.Name & " 已永久删除!", HintType.Finish) Else - FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin) + FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin) Hint("版本 " & Version.Name & " 已删除到回收站!", HintType.Finish) End If Case 2 - Exit Sub + Return End Select '从 UI 中移除 If Version.DisplayType = McVersionCardType.Hidden OrElse Not Version.IsStar Then diff --git a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb index e521663..38dca35 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb @@ -14,7 +14,6 @@ {"HintDownloadThread", New SetupEntry(False, Source:=SetupSource.Registry)}, {"HintNotice", New SetupEntry(0, Source:=SetupSource.Registry)}, {"HintDownload", New SetupEntry(0, Source:=SetupSource.Registry)}, - {"HintInstallBack", New SetupEntry(False, Source:=SetupSource.Registry)}, {"HintHide", New SetupEntry(False, Source:=SetupSource.Registry)}, {"HintHandInstall", New SetupEntry(False, Source:=SetupSource.Registry)}, {"HintBuy", New SetupEntry(False, Source:=SetupSource.Registry)}, @@ -42,6 +41,7 @@ {"SystemSystemCache", New SetupEntry("", Source:=SetupSource.Registry)}, {"SystemSystemUpdate", New SetupEntry(0)}, {"SystemSystemActivity", New SetupEntry(0)}, + {"SystemSystemTelemetry", New SetupEntry(True, Source:=SetupSource.Registry)}, {"CacheExportConfig", New SetupEntry("", Source:=SetupSource.Registry)}, {"CacheSavedPageUrl", New SetupEntry("", Source:=SetupSource.Registry)}, {"CacheSavedPageVersion", New SetupEntry("", Source:=SetupSource.Registry)}, @@ -118,12 +118,13 @@ {"ToolHelpChinese", New SetupEntry(True, Source:=SetupSource.Registry)}, {"ToolDownloadThread", New SetupEntry(63, Source:=SetupSource.Registry)}, {"ToolDownloadSpeed", New SetupEntry(42, Source:=SetupSource.Registry)}, - {"ToolDownloadVersion", New SetupEntry(0, Source:=SetupSource.Registry)}, + {"ToolDownloadSource", New SetupEntry(1, Source:=SetupSource.Registry)}, + {"ToolDownloadVersion", New SetupEntry(1, Source:=SetupSource.Registry)}, {"ToolDownloadTranslate", New SetupEntry(0, Source:=SetupSource.Registry)}, {"ToolDownloadTranslateV2", New SetupEntry(1, Source:=SetupSource.Registry)}, {"ToolDownloadIgnoreQuilt", New SetupEntry(True, Source:=SetupSource.Registry)}, {"ToolDownloadCert", New SetupEntry(False, Source:=SetupSource.Registry)}, - {"ToolDownloadMod", New SetupEntry(1, Source:=SetupSource.Registry)}, + {"ToolDownloadMod", New SetupEntry(2, Source:=SetupSource.Registry)}, {"ToolModLocalNameStyle", New SetupEntry(0, Source:=SetupSource.Registry)}, {"ToolUpdateAlpha", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)}, {"ToolUpdateRelease", New SetupEntry(False, Source:=SetupSource.Registry)}, @@ -180,6 +181,7 @@ {"VersionAdvanceRun", New SetupEntry("", Source:=SetupSource.Version)}, {"VersionAdvanceRunWait", New SetupEntry(True, Source:=SetupSource.Version)}, {"VersionAdvanceDisableJLW", New SetupEntry(False, Source:=SetupSource.Version)}, + {"VersionAdvanceDisableModUpdate", New SetupEntry(False, Source:=SetupSource.Version)}, {"VersionRamType", New SetupEntry(2, Source:=SetupSource.Version)}, {"VersionRamCustom", New SetupEntry(15, Source:=SetupSource.Version)}, {"VersionRamOptimize", New SetupEntry(0, Source:=SetupSource.Version)}, @@ -245,7 +247,7 @@ Value = CTypeDynamic(Value, E.Type) If E.State = 2 Then '如果已应用,且值相同,则无需再次更改 - If E.Value = Value AndAlso Not ForceReload Then Exit Sub + If E.Value = Value AndAlso Not ForceReload Then Return Else '如果未应用,则直接更改并应用 If E.Source <> SetupSource.Version Then E.State = 2 @@ -362,7 +364,7 @@ ''' Private Sub Read(Key As String, ByRef E As SetupEntry, Version As McVersion) Try - If Not E.State = 0 Then Exit Sub + If Not E.State = 0 Then Return Dim SourceValue As String = Nothing '先用 String 储存,避免类型转换 Select Case E.Source Case SetupSource.Normal @@ -422,7 +424,7 @@ '游戏内存 Public Sub LaunchRamType(Type As Integer) - If FrmSetupLaunch Is Nothing Then Exit Sub + If FrmSetupLaunch Is Nothing Then Return FrmSetupLaunch.RamType(Type) End Sub @@ -493,11 +495,9 @@ Public Sub UiLauncherTransparent(Value As Integer) FrmMain.Opacity = Value / 1000 + 0.4 End Sub -#If Not BETA Then Public Sub UiLauncherTheme(Value As Integer) ThemeRefresh(Value) End Sub -#End If Public Sub UiBackgroundColorful(Value As Boolean) ThemeRefresh() End Sub @@ -515,7 +515,7 @@ FrmMain.ImgBack.Margin = New Thickness(-(Value + 1) / 1.8) End Sub Public Sub UiBackgroundSuit(Value As Integer) - If IsNothing(FrmMain.ImgBack.Background) Then Exit Sub + If IsNothing(FrmMain.ImgBack.Background) Then Return Dim Width As Double = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Width Dim Height As Double = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Height If Value = 0 Then @@ -586,7 +586,7 @@ '主页 Public Sub UiCustomType(Value As Integer) - If FrmSetupUI Is Nothing Then Exit Sub + If FrmSetupUI Is Nothing Then Return Select Case Value Case 0 '无 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Collapsed @@ -611,7 +611,7 @@ FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible) FrmSetupUI.HintCustom.Text = $"从指定网址联网获取主页内容。服主也可以用于动态更新服务器公告。{vbCrLf}如果你制作了稳定运行的联网主页,可以点击这条提示投稿,若合格即可加入预设!" FrmSetupUI.HintCustom.EventType = "打开网页" - FrmSetupUI.HintCustom.EventData = "https://github.com/Hex-Dragon/PCL2/discussions/2528" + FrmSetupUI.HintCustom.EventData = "https://github.com/Meloong-Git/PCL/discussions/2528" Case 3 '预设 FrmSetupUI.PanCustomPreset.Visibility = Visibility.Visible FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed @@ -748,16 +748,16 @@ '游戏内存 Public Sub VersionRamType(Type As Integer) - If FrmVersionSetup Is Nothing Then Exit Sub + If FrmVersionSetup Is Nothing Then Return FrmVersionSetup.RamType(Type) End Sub '服务器 Public Sub VersionServerLogin(Type As Integer) - If FrmVersionSetup Is Nothing Then Exit Sub + If FrmVersionSetup Is Nothing Then Return '为第三方登录清空缓存以更新描述 WriteIni(PathMcFolder & "PCL.ini", "VersionCache", "") - If PageVersionLeft.Version Is Nothing Then Exit Sub + If PageVersionLeft.Version Is Nothing Then Return PageVersionLeft.Version = New McVersion(PageVersionLeft.Version.Name).Load() LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\") End Sub diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml index ba28390..f207a1d 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml @@ -4,7 +4,7 @@ xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:System="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" x:Class="PageSetupLaunch" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> @@ -118,7 +118,7 @@ - + @@ -171,9 +171,9 @@ - + - + @@ -205,7 +205,7 @@ - + @@ -218,7 +218,7 @@ - + @@ -226,37 +226,37 @@ + + + + - - - - - - - + + - - - - - + + + - - + + Public Sub RefreshRam(ShowAnim As Boolean) - If LabRamGame Is Nothing OrElse LabRamUsed Is Nothing OrElse FrmMain.PageCurrent <> FormMain.PageType.Setup OrElse FrmSetupLeft.PageID <> FormMain.PageSubType.SetupLaunch Then Exit Sub + If LabRamGame Is Nothing OrElse LabRamUsed Is Nothing OrElse FrmMain.PageCurrent <> FormMain.PageType.Setup OrElse FrmSetupLeft.PageID <> FormMain.PageSubType.SetupLaunch Then Return '获取内存情况 Dim RamGame As Double = Math.Round(GetRam(McVersionCurrent, False), 5) Dim RamTotal As Double = Math.Round(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024 / 1024, 1) @@ -409,7 +409,7 @@ PreFin: '刷新 Java 下拉框显示 Public Sub RefreshJavaComboBox() - If ComboArgumentJava Is Nothing Then Exit Sub + If ComboArgumentJava Is Nothing Then Return '初始化列表 ComboArgumentJava.Items.Clear() ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "自动选择合适的 Java", .Tag = "自动选择"}) @@ -448,9 +448,9 @@ PreFin: '下拉框选择更改 Private Sub JavaSelectionUpdate() Handles ComboArgumentJava.SelectionChanged - If AniControlEnabled <> 0 Then Exit Sub + If AniControlEnabled <> 0 Then Return 'Java 不可用时也不清空,会导致刷新时找不到对象 - If ComboArgumentJava.SelectedItem Is Nothing OrElse ComboArgumentJava.SelectedItem.Tag Is Nothing Then Exit Sub + If ComboArgumentJava.SelectedItem Is Nothing OrElse ComboArgumentJava.SelectedItem.Tag Is Nothing Then Return '设置新的 Java Dim SelectedJava = ComboArgumentJava.SelectedItem.Tag If "自动选择".Equals(SelectedJava) Then @@ -469,11 +469,11 @@ PreFin: Private Sub BtnArgumentJavaSelect_Click(sender As Object, e As EventArgs) Handles BtnArgumentJavaSelect.Click If JavaSearchLoader.State = LoadState.Loading Then Hint("正在搜索 Java,请稍候!", HintType.Critical) - Exit Sub + Return End If '选择 Java Dim JavaSelected As String = SelectFile("javaw.exe|javaw.exe", "选择 bin 文件夹中的 javaw.exe 文件") - If JavaSelected = "" Then Exit Sub + If JavaSelected = "" Then Return JavaSelected = GetPathFromFullPath(JavaSelected) Try '验证 Java 可用 @@ -492,14 +492,14 @@ PreFin: Hint("已将该 Java 加入 Java 列表!", HintType.Finish) Catch ex As Exception Log(ex, "该 Java 存在异常,无法使用", LogLevel.Msgbox, "异常的 Java") - Exit Sub + Return End Try End Sub '自动查找 Private Sub BtnArgumentJavaSearch_Click(sender As Object, e As EventArgs) Handles BtnArgumentJavaSearch.Click If JavaSearchLoader.State = LoadState.Loading Then Hint("正在搜索 Java,请稍候!", HintType.Critical) - Exit Sub + Return End If RunInThread( Sub() @@ -518,7 +518,7 @@ PreFin: #Region "其他选项" Private Sub WindowTypeUIRefresh() Handles ComboArgumentWindowType.SelectionChanged - If ComboArgumentWindowType Is Nothing Then Exit Sub + If ComboArgumentWindowType Is Nothing Then Return If ComboArgumentWindowType.SelectedIndex = 3 AndAlso LabArgumentWindowMiddle IsNot Nothing AndAlso LabArgumentWindowMiddle.Visibility = Visibility.Collapsed Then LabArgumentWindowMiddle.Visibility = Visibility.Visible TextArgumentWindowHeight.Visibility = Visibility.Visible @@ -532,7 +532,7 @@ PreFin: '可见性选择直接关闭的警告 Private Sub ComboArgumentVisibie_SizeChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboArgumentVisibie.SelectionChanged - If AniControlEnabled <> 0 Then Exit Sub + If AniControlEnabled <> 0 Then Return If ComboArgumentVisibie.SelectedIndex = 0 Then If MyMsgBox("若在游戏启动后立即关闭启动器,崩溃检测、更改游戏标题等功能将失效。" & vbCrLf & "如果想保留这些功能,可以选择让启动器在游戏启动后隐藏,游戏退出后自动关闭。", "提醒", "继续", "取消") = 2 Then @@ -543,7 +543,7 @@ PreFin: '开启自动内存优化的警告 Private Sub CheckArgumentRam_Change() Handles CheckArgumentRam.Change - If AniControlEnabled <> 0 Then Exit Sub + If AniControlEnabled <> 0 Then Return If Not CheckArgumentRam.Checked Then Return If MyMsgBox("内存优化会显著延长启动耗时,建议仅在内存不足时开启。" & vbCrLf & "如果你在使用机械硬盘,这还可能导致一小段时间的严重卡顿。" & @@ -555,8 +555,8 @@ PreFin: '版本隔离提示 Private Sub ComboArgumentIndie_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboArgumentIndieV2.SelectionChanged - If AniControlEnabled <> 0 Then Exit Sub - MyMsgBox("默认策略只会对今后新安装的版本生效。" & vbCrLf & "已有版本的隔离策略需要在它的版本设置中调整。") + If AniControlEnabled <> 0 Then Return + MyMsgBox("本设置仅会对之后新安装的版本生效。" & vbCrLf & "如果要修改已安装的版本的隔离方式,请在它的版本独立设置中调整。") End Sub #End Region @@ -585,4 +585,12 @@ PreFin: FrmMain.PageChange(FormMain.PageType.VersionSetup, FormMain.PageSubType.VersionSetup) End Sub + '去除参数中的回车 + Private Sub ReplaceEnter(sender As MyTextBox, e As TextChangedEventArgs) Handles TextAdvanceJvm.TextChanged, TextAdvanceGame.TextChanged + Dim NewText = sender.Text.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, " ") + If NewText = sender.Text Then Return + Dim CaretIndex = sender.CaretIndex + sender.Text = NewText + sender.CaretIndex = Math.Max(0, CaretIndex - 1) + End Sub End Class diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml index 22a9a63..a4c7d3e 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageSetupLeft" - d:DesignWidth="152" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}"> + d:DesignWidth="152" AnimatedControl="PanItem"> diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml.vb index 9c98d0b..5744a66 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml.vb @@ -11,12 +11,12 @@ If ItemLink.Checked AndAlso Setup.Get("UiHiddenSetupLink") Then IsHiddenPage = True If PageSetupUI.HiddenForceShow Then IsHiddenPage = False '若页面错误,或尚未加载,则继续 - If IsLoad AndAlso Not IsHiddenPage Then Exit Sub + If IsLoad AndAlso Not IsHiddenPage Then Return IsLoad = True '刷新子页面隐藏情况 PageSetupUI.HiddenRefresh() '选择第一个未被禁用的子页面 - If IsPageSwitched Then Exit Sub + If IsPageSwitched Then Return If Not Setup.Get("UiHiddenSetupLaunch") Then ItemLaunch.SetChecked(True, False, False) ElseIf Not Setup.Get("UiHiddenSetupUi") Then @@ -91,7 +91,7 @@ ''' 切换现有页面。 ''' Public Sub PageChange(ID As FormMain.PageSubType) - If PageID = ID Then Exit Sub + If PageID = ID Then Return AniControlEnabled += 1 IsPageSwitched = True Try @@ -144,25 +144,25 @@ Public Sub Reset(sender As Object, e As EventArgs) Select Case Val(sender.Tag) Case FormMain.PageSubType.SetupLaunch - If MyMsgBox("是否要初始化启动页的所有设置?该操作不可撤销。", "初始化确认",, "取消", IsWarn:=True) = 1 Then + If MyMsgBox("是否要初始化 启动 页面的所有设置?该操作不可撤销。", "初始化确认",, "取消", IsWarn:=True) = 1 Then If IsNothing(FrmSetupLaunch) Then FrmSetupLaunch = New PageSetupLaunch FrmSetupLaunch.Reset() ItemLaunch.Checked = True End If Case FormMain.PageSubType.SetupUI - If MyMsgBox("是否要初始化个性化页的所有设置?该操作不可撤销。" & vbCrLf & "(背景图片与音乐、自定义主页等外部文件不会被删除)", "初始化确认",, "取消", IsWarn:=True) = 1 Then + If MyMsgBox("是否要初始化 个性化 页面的所有设置?该操作不可撤销。" & vbCrLf & "(背景图片与音乐、主页等外部文件不会被删除)", "初始化确认",, "取消", IsWarn:=True) = 1 Then If IsNothing(FrmSetupUI) Then FrmSetupUI = New PageSetupUI FrmSetupUI.Reset() ItemUI.Checked = True End If Case FormMain.PageSubType.SetupSystem - If MyMsgBox("是否要初始化启动器页的所有设置?该操作不可撤销。", "初始化确认",, "取消", IsWarn:=True) = 1 Then + If MyMsgBox("是否要初始化 其他 页面的所有设置?该操作不可撤销。", "初始化确认",, "取消", IsWarn:=True) = 1 Then If IsNothing(FrmSetupSystem) Then FrmSetupSystem = New PageSetupSystem FrmSetupSystem.Reset() ItemSystem.Checked = True End If Case FormMain.PageSubType.SetupLink - If MyMsgBox("是否要初始化联机页的所有设置?该操作不可撤销。", "初始化确认",, "取消", IsWarn:=True) = 1 Then + If MyMsgBox("是否要初始化 联机 页面的所有设置?该操作不可撤销。", "初始化确认",, "取消", IsWarn:=True) = 1 Then If IsNothing(FrmSetupLink) Then FrmSetupLink = New PageSetupLink FrmSetupLink.Reset() ItemLink.Checked = True diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml index 4780c9d..9403796 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageSetupLink" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb index 73fa03e..3069a96 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb @@ -8,7 +8,7 @@ PanBack.ScrollToHome() '非重复加载部分 - If IsLoaded Then Exit Sub + If IsLoaded Then Return IsLoaded = True AniControlEnabled += 1 diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml index c8ea952..6cdea43 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml @@ -4,7 +4,7 @@ xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sys="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" x:Class="PageSetupSystem" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> @@ -15,6 +15,8 @@ + + @@ -22,28 +24,34 @@ - - - - - + + + + + - - + + + + + + + - - + - - - + + - - + + @@ -54,30 +62,32 @@ + + - - - - - - - - - - + + + + + + + + + - @@ -101,7 +111,7 @@ - + @@ -116,6 +126,7 @@ + @@ -137,7 +148,10 @@ - + + @@ -147,7 +161,7 @@ - + @@ -174,7 +188,7 @@ --> - + @@ -190,7 +204,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb index c348df0..7ead8ef 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb @@ -15,7 +15,7 @@ #End If '非重复加载部分 - If IsLoaded Then Exit Sub + If IsLoaded Then Return IsLoaded = True AniControlEnabled += 1 @@ -29,12 +29,13 @@ '下载 SliderDownloadThread.Value = Setup.Get("ToolDownloadThread") SliderDownloadSpeed.Value = Setup.Get("ToolDownloadSpeed") + ComboDownloadSource.SelectedIndex = Setup.Get("ToolDownloadSource") ComboDownloadVersion.SelectedIndex = Setup.Get("ToolDownloadVersion") CheckDownloadCert.Checked = Setup.Get("ToolDownloadCert") 'Mod 与整合包 ComboDownloadTranslateV2.SelectedIndex = Setup.Get("ToolDownloadTranslateV2") - 'ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod") + ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod") ComboModLocalNameStyle.SelectedIndex = Setup.Get("ToolModLocalNameStyle") CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt") @@ -49,6 +50,7 @@ ComboSystemUpdate.SelectedIndex = Setup.Get("SystemSystemUpdate") ComboSystemActivity.SelectedIndex = Setup.Get("SystemSystemActivity") TextSystemCache.Text = Setup.Get("SystemSystemCache") + CheckSystemTelemetry.Checked = Setup.Get("SystemSystemTelemetry") '调试选项 CheckDebugMode.Checked = Setup.Get("SystemDebugMode") @@ -63,6 +65,7 @@ Try Setup.Reset("ToolDownloadThread") Setup.Reset("ToolDownloadSpeed") + Setup.Reset("ToolDownloadSource") Setup.Reset("ToolDownloadVersion") Setup.Reset("ToolDownloadTranslateV2") Setup.Reset("ToolDownloadIgnoreQuilt") @@ -79,6 +82,7 @@ Setup.Reset("SystemSystemCache") Setup.Reset("SystemSystemUpdate") Setup.Reset("SystemSystemActivity") + Setup.Reset("SystemSystemTelemetry") Log("[Setup] 已初始化启动器页设置") Hint("已初始化启动器页设置!", HintType.Finish, False) @@ -90,13 +94,13 @@ End Sub '将控件改变路由到设置改变 - Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadIgnoreQuilt.Change, CheckDownloadCert.Change + Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckDebugMode.Change, CheckDebugDelay.Change, CheckDebugSkipCopy.Change, CheckUpdateRelease.Change, CheckUpdateSnapshot.Change, CheckHelpChinese.Change, CheckDownloadIgnoreQuilt.Change, CheckDownloadCert.Change, CheckSystemTelemetry.Change If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked) End Sub Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderDebugAnim.Change, SliderDownloadThread.Change, SliderDownloadSpeed.Change If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value) End Sub - Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboModLocalNameStyle.SelectionChanged, ComboDownloadTranslateV2.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged + Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboModLocalNameStyle.SelectionChanged, ComboDownloadTranslateV2.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged, ComboDownloadSource.SelectionChanged, ComboDownloadMod.SelectionChanged If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex) End Sub Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextSystemCache.ValidatedTextChanged @@ -122,7 +126,7 @@ SliderDebugAnim.GetHintText = Function(v) If(v > 29, "关闭", (v / 10 + 0.1) & "x") End Sub Private Sub SliderDownloadThread_PreviewChange(sender As Object, e As RouteEventArgs) Handles SliderDownloadThread.PreviewChange - If SliderDownloadThread.Value < 100 Then Exit Sub + If SliderDownloadThread.Value < 100 Then Return If Not Setup.Get("HintDownloadThread") Then Setup.Set("HintDownloadThread", True) MyMsgBox("如果设置过多的下载线程,可能会导致下载时出现非常严重的卡顿。" & vbCrLf & @@ -145,8 +149,8 @@ '自动更新 Private Sub ComboSystemActivity_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboSystemActivity.SelectionChanged - If AniControlEnabled <> 0 Then Exit Sub - If ComboSystemActivity.SelectedIndex <> 2 Then Exit Sub + If AniControlEnabled <> 0 Then Return + If ComboSystemActivity.SelectedIndex <> 2 Then Return If MyMsgBox("若选择此项,即使在将来出现严重问题时,你也无法获取相关通知。" & vbCrLf & "例如,如果发现某个版本游戏存在严重 Bug,你可能就会因为无法得到通知而导致无法预知的后果。" & vbCrLf & vbCrLf & "一般选择 仅在有重要通知时显示公告 就可以让你尽量不受打扰了。" & vbCrLf & @@ -155,8 +159,8 @@ End If End Sub Private Sub ComboSystemUpdate_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboSystemUpdate.SelectionChanged - If AniControlEnabled <> 0 Then Exit Sub - If ComboSystemUpdate.SelectedIndex <> 3 Then Exit Sub + If AniControlEnabled <> 0 Then Return + If ComboSystemUpdate.SelectedIndex <> 3 Then Return If MyMsgBox("若选择此项,即使在启动器将来出现严重问题时,你也无法获取更新并获得修复。" & vbCrLf & "例如,如果官方修改了登录方式,从而导致现有启动器无法登录,你可能就会因为无法更新而无法开始游戏。" & vbCrLf & vbCrLf & "一般选择 仅在有重大漏洞更新时显示提示 就可以让你尽量不受打扰了。" & vbCrLf & diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml index 2a360f0..b995ec8 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageSetupUI" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> @@ -116,7 +116,7 @@ - + @@ -206,9 +206,9 @@ - + - @@ -251,6 +251,7 @@ + @@ -282,9 +283,9 @@ - + - + @@ -293,7 +294,7 @@ - + diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb index 1f697f4..92fe4e1 100644 --- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb @@ -30,7 +30,7 @@ AniControlEnabled -= 1 '非重复加载部分 - If IsLoaded Then Exit Sub + If IsLoaded Then Return IsLoaded = True SliderLoad() @@ -89,7 +89,7 @@ Catch Setup.Reset("UiCustomPreset") End Try - CType(FindName("RadioCustomType" & Setup.Load("UiCustomType")), MyRadioBox).Checked = True + CType(FindName("RadioCustomType" & Setup.Load("UiCustomType", ForceReload:=True)), MyRadioBox).Checked = True TextCustomNet.Text = Setup.Get("UiCustomNet") '功能隐藏 @@ -195,7 +195,7 @@ BackgroundRefresh(True, True) End Sub Public Sub BackgroundRefreshUI(Show As Boolean, Count As Integer) - If IsNothing(PanBackgroundOpacity) Then Exit Sub + If IsNothing(PanBackgroundOpacity) Then Return If Show Then PanBackgroundOpacity.Visibility = Visibility.Visible PanBackgroundBlur.Visibility = Visibility.Visible @@ -274,13 +274,14 @@ '顶部栏 Private Sub BtnLogoChange_Click(sender As Object, e As EventArgs) Handles BtnLogoChange.Click - Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片") - If FileName = "" Then Exit Sub + Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.jpeg;*.gif;*.webp)|*.png;*.jpg;*.jpeg;*.gif;*.webp", "选择图片") + If FileName = "" Then Return Try '拷贝文件 File.Delete(Path & "PCL\Logo.png") CopyFile(FileName, Path & "PCL\Logo.png") '设置当前显示 + FrmMain.ImageTitleLogo.Source = Nothing '防止因为 Source 属性前后的值相同而不更新 (#5628) FrmMain.ImageTitleLogo.Source = Path & "PCL\Logo.png" Catch ex As Exception If ex.Message.Contains("参数无效") Then @@ -293,11 +294,12 @@ End Try End Sub Private Sub RadioLogoType3_Check(sender As Object, e As RouteEventArgs) Handles RadioLogoType3.PreviewCheck - If Not (AniControlEnabled = 0 AndAlso e.RaiseByMouse) Then Exit Sub + If Not (AniControlEnabled = 0 AndAlso e.RaiseByMouse) Then Return Refresh: '已有图片则不再选择 If File.Exists(Path & "PCL\Logo.png") Then Try + FrmMain.ImageTitleLogo.Source = Nothing '防止因为 Source 属性前后的值相同而不更新 (#5628) FrmMain.ImageTitleLogo.Source = Path & "PCL\Logo.png" Catch ex As Exception If ex.Message.Contains("参数无效") Then @@ -314,10 +316,10 @@ Refresh: Log(exx, "清理错误的标题栏图片失败", LogLevel.Msgbox) End Try End Try - Exit Sub + Return End If '没有图片则要求选择 - Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片") + Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpeg;*.jpg;*.gif;*.webp)|*.png;*.jpeg;*.jpg;*.gif;*.webp", "选择图片") If FileName = "" Then FrmMain.ImageTitleLogo.Source = Nothing e.Handled = True @@ -350,12 +352,14 @@ Refresh: MusicRefreshPlay(True) End Sub Public Sub MusicRefreshUI() - If PanBackgroundOpacity Is Nothing Then Exit Sub + If PanBackgroundOpacity Is Nothing Then Return If MusicAllList.Any Then PanMusicVolume.Visibility = Visibility.Visible PanMusicDetail.Visibility = Visibility.Visible BtnMusicClear.Visibility = Visibility.Visible - CardMusic.Title = "背景音乐(" & EnumerateFiles(Path & "PCL\Musics\").Count & " 首)" + CardMusic.Title = "背景音乐(" & + EnumerateFiles(Path & "PCL\Musics\").Count(Function(f) Not {".ini", ".jpg", ".txt", ".cfg", ".lrc", ".db", ".png"}.Contains(f.Extension.ToLower)) & + " 首)" Else PanMusicVolume.Visibility = Visibility.Collapsed PanMusicDetail.Visibility = Visibility.Collapsed @@ -391,11 +395,11 @@ Refresh: End If End Sub Private Sub CheckMusicStart_Change() Handles CheckMusicStart.Change - If AniControlEnabled <> 0 Then Exit Sub + If AniControlEnabled <> 0 Then Return If CheckMusicStart.Checked Then CheckMusicStop.Checked = False End Sub Private Sub CheckMusicStop_Change() Handles CheckMusicStop.Change - If AniControlEnabled <> 0 Then Exit Sub + If AniControlEnabled <> 0 Then Return If CheckMusicStop.Checked Then CheckMusicStart.Checked = False End Sub @@ -403,7 +407,7 @@ Refresh: Private Sub BtnCustomFile_Click(sender As Object, e As EventArgs) Handles BtnCustomFile.Click Try If File.Exists(Path & "PCL\Custom.xaml") Then - If MyMsgBox("当前已存在布局文件,继续生成教学文件将会覆盖现有布局文件!", "覆盖确认", "继续", "取消", IsWarn:=True) = 2 Then Exit Sub + If MyMsgBox("当前已存在布局文件,继续生成教学文件将会覆盖现有布局文件!", "覆盖确认", "继续", "取消", IsWarn:=True) = 2 Then Return End If WriteFile(Path & "PCL\Custom.xaml", GetResources("Custom")) Hint("教学文件已生成!", HintType.Finish) @@ -422,7 +426,7 @@ Refresh: "3. 点击 刷新主页 按钮,查看主页现在长啥样了。" & vbCrLf & vbCrLf & "你可以在生成教学文件后直接刷新主页,对照着进行修改,更有助于理解。" & vbCrLf & - "直接将自定义主页文件拖进 PCL 窗口也可以快捷加载。", "主页自定义教程") + "直接将主页文件拖进 PCL 窗口也可以快捷加载。", "主页自定义教程") End Sub '主题 @@ -462,7 +466,7 @@ Refresh: '主题自定义 Private Sub RadioLauncherTheme14_Change(sender As Object, e As RouteEventArgs) Handles RadioLauncherTheme14.Changed If RadioLauncherTheme14.Checked Then - If LabLauncherHue.Visibility = Visibility.Visible Then Exit Sub + If LabLauncherHue.Visibility = Visibility.Visible Then Return LabLauncherHue.Visibility = Visibility.Visible SliderLauncherHue.Visibility = Visibility.Visible LabLauncherSat.Visibility = Visibility.Visible @@ -472,7 +476,7 @@ Refresh: LabLauncherLight.Visibility = Visibility.Visible SliderLauncherLight.Visibility = Visibility.Visible Else - If LabLauncherHue.Visibility = Visibility.Collapsed Then Exit Sub + If LabLauncherHue.Visibility = Visibility.Collapsed Then Return LabLauncherHue.Visibility = Visibility.Collapsed SliderLauncherHue.Visibility = Visibility.Collapsed LabLauncherSat.Visibility = Visibility.Collapsed @@ -485,7 +489,7 @@ Refresh: CardLauncher.TriggerForceResize() End Sub Private Sub HSL_Change() Handles SliderLauncherHue.Change, SliderLauncherLight.Change, SliderLauncherSat.Change, SliderLauncherDelta.Change - If AniControlEnabled <> 0 OrElse SliderLauncherSat Is Nothing OrElse Not SliderLauncherSat.IsLoaded Then Exit Sub + If AniControlEnabled <> 0 OrElse SliderLauncherSat Is Nothing OrElse Not SliderLauncherSat.IsLoaded Then Return ThemeRefresh() End Sub @@ -509,7 +513,7 @@ Refresh: ''' 更新功能隐藏带来的显示变化。 ''' Public Shared Sub HiddenRefresh() Handles Me.Loaded - If FrmMain.PanTitleSelect Is Nothing OrElse Not FrmMain.PanTitleSelect.IsLoaded Then Exit Sub + If FrmMain.PanTitleSelect Is Nothing OrElse Not FrmMain.PanTitleSelect.IsLoaded Then Return Try '顶部栏 If Not HiddenForceShow AndAlso Setup.Get("UiHiddenPageDownload") AndAlso Setup.Get("UiHiddenPageLink") AndAlso Setup.Get("UiHiddenPageSetup") AndAlso Setup.Get("UiHiddenPageOther") Then @@ -640,7 +644,7 @@ Refresh: CheckHiddenPageOther.Checked = False End If '修改无具体内容的项 - If Not user Then Exit Sub + If Not user Then Return If Setup.Get("UiHiddenOtherHelp") AndAlso Setup.Get("UiHiddenOtherAbout") AndAlso Setup.Get("UiHiddenOtherTest") Then CheckHiddenOtherFeedback.Checked = True CheckHiddenOtherVote.Checked = True @@ -648,7 +652,7 @@ Refresh: End Sub Private Sub HiddenOtherNet(sender As Object, user As Boolean) Handles CheckHiddenOtherFeedback.Change, CheckHiddenOtherVote.Change '更多子页面(无具体内容的) - If Not user Then Exit Sub + If Not user Then Return If Setup.Get("UiHiddenOtherHelp") AndAlso Setup.Get("UiHiddenOtherAbout") AndAlso Setup.Get("UiHiddenOtherTest") AndAlso (Not Setup.Get("UiHiddenOtherFeedback") OrElse Not Setup.Get("UiHiddenOtherVote")) Then CheckHiddenOtherAbout.Checked = False diff --git a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb index b8c81b0..3796d42 100644 --- a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb +++ b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb @@ -13,7 +13,7 @@ Public Class PageSpeedLeft '如果在页面切换动画的 “上一页消失” 部分已经完成了下载,就直接尝试返回 TryReturnToHome() - If IsLoad Then Exit Sub + If IsLoad Then Return IsLoad = True '监控定时器 @@ -34,7 +34,7 @@ Public Class PageSpeedLeft '定时器任务 Private ReadOnly RightCards As New Dictionary(Of String, MyCard) Private Sub Watcher() - If Not FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then Exit Sub + If Not FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then Return Try #Region "更新左边栏" @@ -46,7 +46,8 @@ Public Class PageSpeedLeft LabThread.Text = "0 / " & NetTaskThreadLimit Else '有任务,输出基本信息 - Dim RawPercent As Double = LoaderTaskbarProgress + Dim Tasks = LoaderTaskbar.Where(Function(l) l.Show).ToList() '筛选掉启动 MC 的任务(#6270) + Dim RawPercent As Double = If(Tasks.Any, MathClamp(Tasks.Select(Function(l) l.Progress).Average(), 0, 1), 1) Dim PredictText As String = Math.Floor(RawPercent * 100) & "." & StrFill(Math.Floor((RawPercent * 100 - Math.Floor(RawPercent * 100)) * 100), "0", 2) & " %" LabProgress.Text = If(RawPercent > 0.999999, "100 %", PredictText) LabSpeed.Text = GetString(NetManager.Speed) & "/s" @@ -58,7 +59,7 @@ Public Class PageSpeedLeft Catch ex As Exception Log(ex, "下载管理左栏监视出错", LogLevel.Feedback) End Try - If FrmSpeedRight Is Nothing OrElse FrmSpeedRight.PanMain Is Nothing Then Exit Sub + If FrmSpeedRight Is Nothing OrElse FrmSpeedRight.PanMain Is Nothing Then Return Try For Each Loader In LoaderTaskbar.ToList TaskRefresh(Loader) @@ -68,7 +69,7 @@ Public Class PageSpeedLeft End Try End Sub Public Sub TaskRefresh(Loader As LoaderBase) - If Loader Is Nothing OrElse Not Loader.Show Then Exit Sub + If Loader Is Nothing OrElse Not Loader.Show Then Return Try '获取实际加载器列表 Dim LoaderList As List(Of LoaderBase) = CType(Loader, Object).GetLoaderList() @@ -76,11 +77,11 @@ Public Class PageSpeedLeft '已有此卡片 Dim Card As Grid = RightCards(Loader.Name) Dim NewValue As Double = Loader.Progress + Loader.State - If Val(Card.Tag) = NewValue Then Exit Sub + If Val(Card.Tag) = NewValue Then Return Card.Tag = NewValue If Card.Children.Count <= 3 Then Log("[Watcher] 元素不足的卡片:" & Loader.Name, LogLevel.Debug) - Exit Sub + Return End If Card = Card.Children(3) Try @@ -91,7 +92,7 @@ Public Class PageSpeedLeft Card.Children.Clear() Card.Children.Add(GetObjectFromXML("")) Dim Tb As TextBlock = GetObjectFromXML("") - Tb.Text = GetExceptionDetail(Loader.Error) + Tb.Text = Loader.Error.GetDetail() AddHandler Tb.MouseLeftButtonDown, Sub(sender As TextBlock, e As EventArgs) ClipboardSet(sender.Text, False) diff --git a/Plain Craft Launcher 2/Pages/PageSpeedRight.xaml b/Plain Craft Launcher 2/Pages/PageSpeedRight.xaml index 9efbd9f..6253c46 100644 --- a/Plain Craft Launcher 2/Pages/PageSpeedRight.xaml +++ b/Plain Craft Launcher 2/Pages/PageSpeedRight.xaml @@ -3,7 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="PageSpeedRight" - PanScroll="{Binding ElementName=PanBack}"> + PanScroll="PanBack"> - + @@ -14,17 +14,17 @@ - + - - + + + Text="卡片(local:MyCard)的 Title 属性决定了它的标题。 为卡片添加 CanSwap 属性让它可以被折叠,True 代表是,False 代表否。在此基础上,再使用 IsSwapped 属性调整它是否默认被折叠。" /> - + @@ -34,7 +34,7 @@ - + @@ -53,7 +53,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -87,7 +87,7 @@ - + @@ -106,14 +106,14 @@ - + + Text="查看 PCL 源代码" EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL/blob/main/Plain%20Craft%20Launcher%202/FormMain.xaml.vb"/> + + + + - + @@ -152,7 +160,7 @@ - + @@ -177,7 +185,7 @@ - + @@ -194,7 +202,7 @@ - + @@ -232,7 +240,7 @@ - + @@ -253,11 +261,10 @@ - + - - + @@ -288,9 +295,8 @@ - + - - + - + Text="如果你在尝试制作联网更新的主页,可以查看下方的 GitHub 讨论页。 你可以根据其中的介绍为主页添加版本号检查以节省流量,也可以通过检查 Referer 和 User Agent 来确定对方的 PCL 版本。" /> + Text="打开 GitHub 讨论页" EventType="打开网页" EventData="https://github.com/Meloong-Git/PCL/discussions/2528" /> @@ -350,7 +355,7 @@ local:MyImage(图片): local:MyCard(卡片): - Title:设置显示的标题文本 - CanSwap:卡片是否可以折叠,True 为是,False 为否 - - IsSwaped:卡片是否默认折叠,要求 CanSwap 必须为 True + - IsSwapped:卡片是否默认折叠,要求 CanSwap 必须为 True - HorizontalAlignment:若使用,要求 CanSwap 必须为 False - UseAnimation:是否在展开等高度改变时触发动画;True 为是(默认),False 为否 - SwapLogoRight:卡片折叠时的箭头是朝下还是朝右;True 为朝右,False 为朝下(默认) @@ -358,7 +363,7 @@ local:MyCard(卡片): local:MyHint(提示条): - Text:设置显示的文本( 代表换行) - - IsWarn:设置为 True 代表警告样式,False 代表提示样式 + - Theme:设置颜色主题(Blue、Yellow 或 Red) local:MyButton(按钮): - Text:设置显示的文本 diff --git a/Plain Craft Launcher 2/Resources/Help.zip b/Plain Craft Launcher 2/Resources/Help.zip index d204879..224f7d5 100644 Binary files a/Plain Craft Launcher 2/Resources/Help.zip and b/Plain Craft Launcher 2/Resources/Help.zip differ diff --git a/Plain Craft Launcher 2/Resources/Language/en_US.xaml b/Plain Craft Launcher 2/Resources/Language/en_US.xaml deleted file mode 100644 index f0714da..0000000 --- a/Plain Craft Launcher 2/Resources/Language/en_US.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - More - Download - Launch - Setting - - diff --git a/Plain Craft Launcher 2/Resources/Language/zh_CN.xaml b/Plain Craft Launcher 2/Resources/Language/zh_CN.xaml deleted file mode 100644 index a92d95b..0000000 --- a/Plain Craft Launcher 2/Resources/Language/zh_CN.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - 更多 - 下载 - 启动 - 设置 - - diff --git a/Plain Craft Launcher 2/Resources/ModData.txt b/Plain Craft Launcher 2/Resources/ModData.txt index bda7ce3..f40150f 100644 --- a/Plain Craft Launcher 2/Resources/ModData.txt +++ b/Plain Craft Launcher 2/Resources/ModData.txt @@ -19,7 +19,7 @@ modular-forcefield-system|模块化力场系统* minechem-recompile|化学 (MineChem) - 非官方重编译版¨73373|化学 (MineChem) - 已失效 iron-chests@|更多箱子*¨iron-chests-for-fabric@cyberanner-ironchest|更多箱子 (Iron Chests)¨iron-chests-fabric@|更多箱子 (Iron Chests) balkons-weaponmod|更多武器 (Balkon's WeaponMod) -advanced-machines|高级机械 (AdvancedMachines)¨@advanced-machines-uuum|高级机械 (AdvancedMachines) - Immibis版 +advanced-machines|高级机械 (AdvancedMachines) - Chocohead版¨@advanced-machines-uuum|高级机械 (AdvancedMachines) - Immibis版 advanced-solar-panels|高级太阳能* nuclear-control-2|核电控制2* @@ -92,7 +92,7 @@ applied-energistics-1@ae1|应用能源* Universal-Electricity|通用电力* @better-than-wolves|比狼好 (Better Than Wolves) aether@|天境 (The Aether) - +@miscperipherals|电脑外设 (MiscPeripherals) assassincraft-assassins-creed-mod|刺客信条 (AssassinCraft) @@ -120,8 +120,8 @@ blockphysics_by_id_miner|方块物理学 (BlockPhysics) remain-in-motion|运动红石机构 (Remain in Motion) - -modular-powersuits|模块化动力套装* +openperipheralcore|开放式电脑外设 (OpenPeripheral) - 核心¨openperipheraladdons|开放式电脑外设 (OpenPeripheral) - 附加¨openperipheralintegration|开放式电脑外设 (OpenPeripheral) - 联动 +modular-powersuits@modularpowersuits|模块化动力套装* @@ -189,7 +189,7 @@ mekanism@|通用机械* magic-crusade-rpg-mod|魔法远征:魔法十字军 (Magic Crusade) invasions|入侵 (Invasion) -mb-battlegear-2|挖矿与砍杀:双持与战备2 (Mine & Blade: Battlegear 2) +mb-battlegear-2|挖矿与砍杀:双持与战备 2 (Mine & Blade: Battlegear 2) galacticraft-legacy@|星系 (GalactiCraft) @@ -368,7 +368,7 @@ steves-factory-manager|史蒂夫工厂管理 (Steve's Factory Manager)¨steves-f thaumcraftgates|奥法管道 (ThaumcraftGates) - +thaumcraft-recipes|神秘合成* player-heads-forge|玩家死亡掉头 (Player Heads) notenoughkeys|更好的按键控制 (Not Enough Keys) @@ -397,7 +397,7 @@ gem-buffs-infinite-potion-effects|宝石效果/增益宝石 (Gem Buffs) rftools@|RF工具箱 (RFTools) atomicstrykers-magic-yarn|AtomicStryker的魔法线 (AtomicStryker's Magic Yarn) - +teds-old-world-gen-mod|怀旧地形 (Old World Gen) academycraft|超能力/学园都市 (AcademyCraft) @@ -490,7 +490,7 @@ sim-u-kraft-old|模拟城市 (Sim-U-Kraft) superheroes-unlimited|超级英雄无限* oceancraft-mod|海洋世界 (Oceancraft) - +more-enchantments-legacy|更多附魔 (More Enchantments) witching-gadgets|巫师宝具* buildcraft-compat@|建筑兼容* @@ -522,7 +522,7 @@ inventory-pets|背包宠物* levels|装备等级* keeping-inventory orespawn|矿石菌种 (OreSpawn) -reliquary-reincarnations|圣遗物 (Reliquary Reincarnations (Reliquary) / Xeno's Reliquary)¨reliquary|圣遗物 (Reliquary Reincarnations* / Xeno's Reliquary) +reliquary-reincarnations@|圣遗物 (Reliquary Reincarnations (Reliquary) / Xeno's Reliquary)¨reliquary|圣遗物 (Reliquary Reincarnations* / Xeno's Reliquary) mo-bends|更多弯曲 (Mo' Bends) quark@|夸克* @@ -782,7 +782,7 @@ sleeping-bag|睡袋* the-one-probe@|检测器* simply-jetpacks-2|简易喷气背包2* -snad|子沙* +snad@|子沙* storage-drawers-extras solar-flux-reborn@|日光通量:重制版* simple-void-world|简单虚空世界* @@ -802,7 +802,7 @@ infinitylib@ dark-utilities@ flux-networks|通量网络* flat-colored-blocks-forge|平滑色块 (Flat Colored Blocks)¨flat-colored-blocks-fabric|平滑色块 (Flat Colored Blocks) -jei-bees +jei-bees|JEI 蜜蜂 (JEI Bees) chisels-bits|雕凿工艺 (Chisels & Bits)¨chisels-bits-for-fabric|雕凿工艺 (Chisels & Bits) architecturecraft|建筑师工艺 (Architecture Craft) chameleon@texel-chameleon|变色龙* @@ -871,7 +871,7 @@ auto-paths|自动路径* react shared-resource-packs|共享资源包* i-know-what-im-doing@|我知道我在干什么 (I Know What I'm Doing) -Leather-Works|皮革加工厂* +leather-works|皮革加工厂* cyberware|机械改造* thermal-cultivation@|热力农业* better-builders-wands|更好的建筑之杖 (Better Builder's Wands) @@ -963,7 +963,7 @@ blue-power|蓝石力量 (Blue power) redstone-armory|红石军械库* qmunitylib quest-for-blue-name -the-last-smith|最后的太刀匠人* +the-last-smith@|最后的太刀匠人* mineamp|我的音乐 (MineAmp) quiverbow|Quiver Bow - 官方原版¨quiverbow-restrung|Quiver Bow - 重制版 cookiecore @@ -1006,7 +1006,7 @@ roost|鸡窝* croparia@|矿石作物/魔种之咏* simple-corn|简单玉米* thaumic-jei@ -tinkers-steelworks|匠魂炼钢厂 (Tinkers' Steelworks) +tinkers-steelworks|匠魂炼钢厂 (Tinkers' Steelworks)¨tinkers-steelworks-fork|匠魂炼钢厂 (Tinkers' Steelworks) unicode-font-extension|Unicode 字体扩展* placeable-items|可放置物品* compendium|工匠防御 (Tinkers' Compendium (Tinkers' Defense)) @@ -1054,7 +1054,7 @@ radixcore familiar-fauna|熟悉的动物* tinkers-tool-leveling|匠魂工具升级 (Tinkers' Tool Leveling) -arcana-rpg-final-version|神秘RPG (Arcana RPG) + ingame-info-xml|游戏信息显示XML版 (InGame Info XML) lovelyrobot|萌化机器人 (LovelyRobot) @@ -1187,7 +1187,7 @@ minecraft-transport-simulator@immersive-vehicles|沉浸车辆 (Immersive Vehicle chest-transporter|搬箱器* i18nupdatemod@|自动汉化更新 (I18nUpdateMod) realistic-item-drops|真实物品掉落* -pixelmon@|像素精灵宝可梦 重铸* +pixelmon@|像素宝可梦 重铸 (Pixelmon Reforged) controlling@|键位冲突显示*¨controlling-for-fabric|键位冲突显示 (Controlling) @@ -1250,7 +1250,7 @@ career-bees|职业蜜蜂* level-up|升级!重置 (Level Up!Reloaded) level-up-legacy|升级!前版 (Level Up!Legacy) moar-boats|模块化船* -numina|模块化动力装甲前置* +numina@|模块化动力装甲前置* tinkers-complement@|匠魂补充 (Tinkers' Complement) everlastingabilities@everlasting-abilities|永恒能力 (Everlasting Abilities) abyssalcraft-heads|深渊国度头颅 (AbyssalCraft Heads) @@ -1274,7 +1274,7 @@ just-enough-pattern-banners primitive-mobs|远古生物 / 原始生物* realistic-cobwebs@ simpleharvest|简单收获 (Simple Harvest) -mrcrayfishs-device-mod|MrCrayfish的设备 (MrCrayfish's Device Mod) +mrcrayfishs-device-mod|MrCrayfish 的设备 (MrCrayfish's Device Mod) block-armor|方块盔甲* stockpile|储物桶* loot-slash-conquer @@ -1341,7 +1341,7 @@ thaumic-calculations|神秘计算器* pams-weee-flowers|潘马斯花园 (Pam's Weee! Flowers) -paragliders|滑翔伞* +paragliders@|滑翔伞* ender-io-endergy|末影接口:管道拓展 (Ender IO:Endergy) bwm-suite ambience-music-mod|环境音乐 (Ambience) @@ -1378,13 +1378,13 @@ arcade-mod|街机* asmodeuscore@ building-gadgets|建筑小帮手* dooglamoo-worlds|Dooglamoo的世界* -scp-lockdown|SCP基金会:封锁 (SCP: Lockdown) +scp-lockdown@|SCP基金会:封锁 (SCP: Lockdown) middle-earth-thaumaturgy|中土神秘学 (Middle-Earth Thaumaturgy) structpro-mod-fast-schematic-spawning-system|自然神殿 (Structpro) betterfps|更好的FPS (BetterFPS) sky-compression reap-mod@|收获 (Reap) -geolosys|地质矿脉* +geolosys@|地质矿脉* patchouli@|帕秋莉手册*¨patchouli-fabric|帕秋莉手册 (Patchouli) jeid gases-framework @@ -1393,7 +1393,7 @@ bagginses|背包* ic2-classic@ic2classic|工业时代2经典版 (IC2 Classic) opencubicchunks@cubicchunks cubicworldgen@cwg -rally-health +rally-health@ open-terrain-generator|开放式地形生成器* noshelter|无庇护所 (NoShelter) @@ -1425,7 +1425,7 @@ texfix@|材质修复 (TexFix) mapmakers-gadgets|地图作者小帮手 (Mapmaker's Gadgets) travellers-gear|旅者之器 (Traveller's Gear) ceramics@|陶瓷器* -slashblade-japanese-addon-pack@sjap|拔刀剑日系附属包 (SlashBlade Japanese Addon Pack) +sjap@|拔刀剑日系附属包 (SlashBlade Japanese Addon Pack) planet-progression|星球研究* space-advanced-addon-for-galactic-craft-3 @@ -1489,7 +1489,7 @@ bitcoin|比特币* hungering-darkness roots-classic|根源魔法经典版* embers-rekindled@embers|余烬复刻版* -vending-machines-revamped|自动贩卖机 (VendingMachinesRevamped) +vending-machines-revamped|自动贩卖机* thaumic-alchemy|神秘炼金学* move-plus|移动方式增强* @@ -1516,7 +1516,7 @@ xl-food-mod|超多食物 (XL Food Mod) soot|烟尘* hp-spells|哈利·波特:法术 (Harry Potter Spells) recurrent-complex|自然建筑生成* -capabilityproxy|功能代理 (CapabilityProxy) +capabilityproxy@capability-proxy|功能代理 (CapabilityProxy) reauth¨reauth-fabric autoverse@ transprot|运输* @@ -1525,12 +1525,12 @@ better-questing-standard-expansion|更好的任务-基础扩展 (Better Questing exchangers|方块交换器* flexible-tools|灵活的工具* dimensional-control|维度控制* -valkyrien-warfare@valkyrien-skies|瓦尔基里天空/瓦尔基里战争 (Valkyrien Skies/Valkyrien Warfare) +valkyrien-skies@|瓦尔基里天空* item-scroller@|物品滚轮* better-advancements@|更好的进度* lucraft-core progressive-bosses@|进化的 BOSS*¨fabric-progressive-bosses|进化的 BOSS (Progressive Bosses) -persistent-bits +persistent-bits@ rc-roads|真实公路 (RC Roads) emoticons|动作表情* simple-diving-gear|简单的潜水装备* @@ -1557,7 +1557,7 @@ exo-craft@ simple-login|简单登录* grim-pack|Grim的Mod整合* artisan-worktables|工匠之作/工匠工作台* -ptrlib +ptrlib@ creeper-awareness|爬行者意识* what-fluid-fix blue-skies@|蔚蓝浩空* @@ -1592,7 +1592,7 @@ organic-creepers|苦力怕蕨* -industrial-renewal|工业复兴* +industrial-renewal@|工业复兴* draconicadditions|龙之进化拓展 (Draconic Additions) hated-mobs|令人讨厌的生物* @@ -1706,14 +1706,14 @@ the-emerald-haven|翡翠天堂* simple-grinder@simplegrinder|简易研磨机* redstone-gauges-and-switches|红石测量与开关 (Gauges and Switches) apotheosis|神化* -lighting-wand|照明魔杖 🌟 (Lighting Wand 🌟)¨lighting-wand-fabric|照明魔杖 🌟 (Lighting Wand 🌟) +lighting-wand@|照明魔杖 🌟 (Lighting Wand 🌟)¨lighting-wand-fabric|照明魔杖 🌟 (Lighting Wand 🌟) terrarium|地球* climatic-biomes howling-moon|狼人* hostile-worlds-invasions|敌对世界-入侵 (Hostile Worlds - Invasions) simple-storage-network|简单存储* 2973537|虚无世界1 (Eternal Isles) -scaling-health +scaling-health@ minecoprocessors|微处理器* difficult-life @@ -1752,7 +1752,7 @@ toolscombine|工具组合 (Tools Combine) pollutant-pump|污染泵* infinite-pollution-filter|无限污染过滤器* jei-hider -fastflyblockbreaking|飞行速破 (Fast Fly Block Breaking) +fastflyblockbreaking|飞行速破 (FastFlyBlockBreaking) planttech-2|植物科技2 (PlantTech 2) toast-control|消息框控制* @@ -1837,7 +1837,7 @@ spice-of-life-carrot-edition|生活调味料:胡萝卜版 (Spice of Life: Carr refined-exchange rs-requestify|精致存储:请求者 (Refined Storage: Requestify) pizzacraft@|披萨工艺 (PizzaCraft) -super-factory-manager@|超级工厂管理* +super-factory-manager@|超级工厂管理器* sakura|樱* sips|更多液体容器* gregs-construct|格雷与匠魂 (Greg's Construct) @@ -1873,7 +1873,7 @@ auto-feeder-helmet|自动进食头盔* cc-tweaked-fabric simple-farming|简单农业* bigger-packets-please -lockyz-extra-dimensions-mod|更多维度世界 (Lockyz Extra Dimensions) +lockyz-extra-dimensions-mod|更多维度世界* - 已失效¨@ledm|更多维度世界 (Lockyz Extra Dimensions Mod) psi-combat-magic dtphc|动态的树:潘马斯附属 (Dynamic Trees - Pam's Harvestcraft Compat) largefluidtank@large-fluid-tank|大型储罐 (Large Fluid Tank)¨largefluidtank-fabric|大型储罐 (Large Fluid Tank) @@ -1969,13 +1969,13 @@ hearth-well tea-and-biscuits|茶和饼干* controllable|手柄控制*¨controllable-fabric|手柄控制 (Controllable) -genetic-animals|遗传动物* +genetic-animals@|遗传动物* stone-chest|石头箱子* iron-shulker-boxes@|更多潜影盒* mob-dismemberment|怪物肢解* zoo-wild-animals-rebuild|野生动物园:重制版 (Zoo & Wild Animals Rebuilt) kottle -customdrones|自定义无人机 (Custom Drones) +customdrones|自定义无人机 (CustomDrones) call-to-battle-2-authentic-world-war-2-experience|战争召唤2 (Call To Battle 2)¨call-to-battle-the-wwii-mod|战争召唤2 (Call To Battle 2) - 远古 some-like-it-dry yungs-better-caves|YUNG 的洞穴优化 (YUNG's Better Caves)¨yungs-better-caves-fabric|YUNG 的洞穴优化 (YUNG's Better Caves) @@ -2021,7 +2021,7 @@ underwater-biome|深海生态* create@|机械动力*¨create-fabric@|机械动力 (Create) -energeticsheep|脉冲羊 (EnergeticSheep) +energeticsheep@energetic-sheep|脉冲羊 (EnergeticSheep) ensorcellation@|万象* inventory-sorter|物品分拣* speedster-heroes|闪电侠* @@ -2093,7 +2093,7 @@ healing-campfire@|治愈营火*¨healing-campfire-fabric|治愈营火 (Healing C minetunes jetif|物品丢入流体 (Just Enough Throwing In Fluids) rftools-base@|RF工具:基础 (RFTools Base) -flopper|流体漏斗* +flopper@|流体漏斗* exhausted-stamina|竭力攻击* staminaplus|耐力值 (StaminaPlus) enhanced-armaments|增强装备* @@ -2154,8 +2154,8 @@ usefultnt|更有用的TNT (UsefulTNT) crow-flight|乌鸦坐飞机* findme@|东寻西觅 (FindMe) -minecraft-transit-railway|我的世界铁路* - 现已更新4.0.0 Beta9及以上¨@minecraft-transit-railway|我的世界铁路 (Minecraft Transit Railway) -random-loot-mod|随机战利品* +minecraft-transit-railway@|我的世界铁路* +random-loot-mod@randomloot|随机战利品* tinkers-aether|天境工匠* mine-and-slash-auto-compatibility|挖矿与砍杀自动兼容 (Mine and Slash Auto Compatibility) @@ -2165,7 +2165,7 @@ betterachievements|更好的成就 (Better Achievements) extra-bows|更多的弓* forgotten-relics|失落遗物学* ore-core@|矿石核心* -ordinary-coins|普通硬币 (Ordinary Coin) +ordinary-coins|普通硬币* peanut-mod|花生! (Peanuts!) in-game-wiki-mod @@ -2189,7 +2189,7 @@ glibys-physics|Gliby的物理 (Gliby's Physics) electro-magic-tools-ic2-classic|电子神秘工具 (Electro-Thaumic Tools - IC2 Classic) forceasciifont|强制ASCII字体 (ForceASCIIFont) skewers|烤串* -get-in-the-bucket-mod|进入水桶*¨get-in-the-bucket-mod-fabric|进入水桶 (Get In The Bucket Mod) +get-in-the-bucket-mod@|进入水桶*¨get-in-the-bucket-mod-fabric|进入水桶 (Get In The Bucket Mod) fertilization|施肥*¨fertilization-fabric|施肥 (Fertilization) additional-banners@|更多旗帜* dropt @@ -2233,7 +2233,7 @@ citadel@ screenshot-browser|内置截图管理器* -curio-of-undying@charm-of-undying|不死图腾插槽 (Curio of Undying/Trinket of Undying/Charm of Undying)¨curio-of-undying-fabric|不死图腾插槽 (Curio of Undying/Trinket of Undying/Charm of Undying) +curio-of-undying@charm-of-undying|不死图腾插槽/不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying)¨curio-of-undying-fabric|不死图腾插槽/不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying) explorercraft-worldexpansion|探险者 (Explorercraft) enigmatic-legacy@|神秘遗物* @@ -2246,7 +2246,7 @@ the-rc-mod rubybyimxiaoanag|红宝石 (Ruby) jojo-hamon-era@|JoJo-波纹时代 (JoJo-Hamon Era) -OpenSecurity|开放式防卫* +OpenSecurity|开放式安全* the-legend-era|传说纪元* level-up-hp-forge|生命提升 (Level Up HP)¨level-up-hp|生命提升 (Level Up HP) more-planet-extras @@ -2300,7 +2300,7 @@ infraredstone incorporeal|幻想多媒体* amun-ra -mysterious-mountain-lib|妖怪之山通用库 (MysteriousMountainLib) +mysterious-mountain-lib@mmlib|妖怪之山通用库 (MysteriousMountainLib) loottweaker rediscovered@|特性重现* @@ -2343,7 +2343,7 @@ multiverse-pouch|多元宇宙袋* electroblobs-wizardry-twilight-forest-spell-pack|巫术学:暮色森林法术包 (Electroblob's Wizardry: Twilight Forest Spell Pack) blockbuster fishs-undead-rising|Fish的不死崛起/Fish的亡灵崛起 (Fish's Undead Rising) -cloth-config@ +cloth-config@¨cloth-config-legacy just-map too-realistic-inventory|真实背包 (Realistic Inventory) mob-farm|生物农场* @@ -2442,7 +2442,7 @@ not-enough-crashes@notenoughcrashes|崩溃优化*¨not-enough-crashes-forge|崩 crash-to-main-menu|崩溃返回主菜单* tinkers-jei -better-questing-quest-book|更好的任务-任务书 (Better Questing - Quest Book) +better-questing-quest-book@|更好的任务-任务书 (Better Questing - Quest Book) engineers-doors|工程师的门 (Engineer's Doors) tps-generator|TPS发电机 (TPS Generator) better-drowning@|更好的溺水* @@ -2451,7 +2451,7 @@ kubejs@¨kubejs-fabric tickcentral lowocalizatiwn redlib -modern-ui|现代化 UI (Modern UI) - 主页¨@modern-ui|现代化 UI (Modern UI) +modern-ui@|现代化 UI (Modern UI) auto-config-updated-api medieval-madness|中世纪的疯狂* @@ -2478,7 +2478,7 @@ lang_tool|加长工具 (lang_tool) emojiful item-compatibility -aether-lost-content@|天境: 遗失之物 (Aether: Lost Content) +aether-lost-content@|天境: 遗失之物 (Aether: Lost Content Addon) gunpowderlib nuclearcraft-overhauled@|核电工艺:重制版 (NuclearCraft: Overhauled) nei-lotr|魔戒NEI插件 (NEI LotR) @@ -2491,7 +2491,7 @@ beesourceful middle-earth-industry|中洲工业 (Middle-Earth Industry) thut-wearables after-the-drizzle|细雨田园 (After the Drizzle) -cmdcam|CMD 指令摄影机 (CMDCam) +cmdcam@|CMD 指令摄影机 (CMDCam) climate-control-geographicraft@geographicraft|气候控制 / 地理世界 (Climate Control / Geographicraft) cosmetic-armor-reworked|时装盔甲重置版* @@ -2743,7 +2743,7 @@ vials|瓶瓶罐罐* advanced-lightsabers|高级光剑* fall_attack|下落击杀 (fall_attack) swimsuit|泳装 (swimsuit) -more-overlays-updated +more-overlays-updated@ dothack-hack-weapons immersive-energy|沉浸能源* crimson-moon|绯红之月* @@ -2785,7 +2785,7 @@ stepup|平滑自动上坡 (StepUp) sodium@|钠* netherlicious -yungs-better-mineshafts-forge@yungs-better-mineshafts|YUNG 的矿井优化 (YUNG's Better Mineshafts)¨yungs-better-mineshafts-fabric|YUNG 的矿井优化 (YUNG's Better Mineshafts) +yungs-better-mineshafts-forge@yungs-better-mineshafts|YUNG 的矿井优化 (YUNG's Better Mineshafts)¨yungs-better-mineshafts-fabric|YUNG 的矿井优化 (YUNG's Better Mineshafts)¨yungs-better-mineshafts-neoforge|YUNG 的矿井优化 (YUNG's Better Mineshafts) terrible-chest|可怕的箱子/可怖箱子* spartan-shields@|斯巴达之盾* silent-gear@|寂静装备* @@ -2923,7 +2923,7 @@ netherite-horse-armor@netherite_horse_armor|下界合金马铠* tacocraft-fabric lootr@¨lootr-fabric biolib@bio-library -scavenge +scavenge@ heaven-earth-ring|乾坤戒指* @@ -2965,7 +2965,7 @@ armor-toughness-bar|盔甲韧性显示* block-meter time-to-live dual-hotbar-1-12-2|双重快捷栏1.12.2 (Dual Hotbars 1.12.2) -cha-s|可合成的马铠和鞍 (Craftable Horse Armour & Saddle) +cha-s@|可合成的马铠和鞍 (Craftable Horse Armour & Saddle) dual-hotbar|双重快捷栏 (Dual Hotbars) travellers-boots@ @@ -3050,7 +3050,7 @@ light-overlay@|亮度覆盖* smallerfpsincrements regex-filters|正则表达式过滤器* -fluidsystem|流体系统 (FluidSystem) +fluidsystem@|流体系统 (FluidSystem) falling-tree@fallingtree|伐树 (FallingTree) untranslated-items|物品名称双语同屏* particle-culling@|粒子渲染机制优化* @@ -3128,7 +3128,7 @@ cardinal-energy|能量基本* travel-anchors@|旅行锚* bnbgaminglib triumph -overloaded-armor-bar|护甲上限突破*¨overloaded-armor-bar-fabric|护甲上限突破 (Overloaded Armor Bar) +overloaded-armor-bar@|护甲上限突破*¨overloaded-armor-bar-fabric|护甲上限突破 (Overloaded Armor Bar) toomanydanyores glass-pipe|玻璃管道* origins-classes@|起源:职业 (Origins: Classes) @@ -3210,7 +3210,7 @@ monospace-font|等宽字体 (Monospace font) mana-liquidizer|魔力液化器* neapolitan@|那不勒斯风味* -clear-despawn-fabric@cleardespawn +clear-despawn@cleardespawn¨clear-despawn-fabric tinkers-modifier-modifier|匠魂强化修改 (Tinkers' Modifier Modifier) randomtweaks|随意微调 (RandomTweaks) zettai-magic @@ -3233,7 +3233,7 @@ geckolib@ memory-cleaner-mod|内存自动清理* ore-flowers|矿物花卉* aqua-creepers|水下苦力怕 (Aqua Creepers!) -extrastorage|更多存储 (ExtraStorage) +extrastorage@|更多存储 (ExtraStorage) thermal-locomotion@|热力运输* external-tweaker morebiomesxl @@ -3262,7 +3262,7 @@ devtech|GTCEu-CrT开发套件* -jurassic-world-reborn-mod|侏罗纪世界重生* +jurassic-world-reborn-mod|侏罗纪重生 (Jurassic Reborn) cardboard@ enchanting-with-thaumcraft|神秘附魔学* @@ -3374,7 +3374,7 @@ dawn assembly-line-machines|装配线* netherending-ores Flex-FOV|扭曲视野* -hooked|抓钩* +hooked@|抓钩* createtweaker@ iTorch @@ -3407,7 +3407,7 @@ avatar-mod-2-out-of-the-iceberg|降世神通:突破冰山 (Avatar Mod 2: Out o lazydfu@|DFU载入优化 (LazyDFU) ridable-ender-pearls|可骑乘末影珍珠* -gildedarmor|镀金盔甲 (GildedArmor) +gildedarmor@|镀金盔甲 (GildedArmor) deadly-book|致命书本* speedy-hoppers|极速漏斗* indium@|铟* @@ -3452,7 +3452,7 @@ police-mod|警察* enhanced-celestials@|月亮事件*¨enhanced-celestials-fabric|月亮事件 (Enhanced Celestials) resourceful-bees@|缤纷蜜蜂* held-item-info@|手持物品信息* -cleancut|干净利落 (CleanCut) +cleancut@|干净利落 (CleanCut) plus-tweaks global-gamerules@|全局游戏规则 (Global GameRules) @@ -3484,7 +3484,7 @@ somnia@|梦醒时分 (Somnia Awoken) industrial-reborn@ mo-structures-forge|更多自然生成结构 (Mo' Structures)¨mo-structures|更多自然生成结构 (Mo' Structures) wallpapercraft-a-fresh-roll|墙纸创造 (Wallpapercraft - A Fresh Roll) -clear-despawn + unionlib@ @@ -3509,7 +3509,7 @@ pattysmorestuff@ hats|帽子* smoother-bedrock|平滑基岩层* c2me-fabric@|C^2M 引擎 (C^2M-Engine/Concurrent Chunk Management Engine)¨@c2me-neoforge|C^2M 引擎 (C^2M-Engine/Concurrent Chunk Management Engine) -datapack-anvil +datapack-anvil@dpanvil nuclear-science@|核科学* glaretorch|强光火把 (Glare Torch) mrcrayfishs-jumping-castle-mod|MrCrayfish的充气城堡 (MrCrayfish's Jumping Castle) @@ -3658,7 +3658,7 @@ farmers-tea|农夫的茶 (Farmer's Tea) farmers-delight-integration magical-psi-redux anvil-fix|铁砧修复* -no-increasing-repair-cost +no-increasing-repair-cost@ wool-temples@|羊毛神殿* exp-bottling|经验装瓶 (EXP Bottling) buildersaddition@ @@ -3666,7 +3666,7 @@ gt-remastered|格雷科技重制版 (GT: Remastered) tips@|提示* Interactio advancement-book|进度之书* -drawers-tooltip|抽屉提示* +drawers-tooltip@|抽屉提示* paradise-lost@|天境:重生* @gregfood|格雷的食物 (GregFood) @@ -3757,7 +3757,7 @@ loading-timer@|加载时长显示* ocrocketry chime-fabric@chime witherite@|凋灵合金* -mermod-fabric@mermod¨mermaid-tail-mod|人鱼尾*¨mermod-forge +mermod-fabric@mermod¨mermaid-tail-mod|人鱼尾*¨mermod-neoforge astromine-core|天体矿工:核心 (Astromine: Core) dungeondq dynamic-trees-integrated-dynamics|动态的树:动态联合/集成动力附属 (Dynamic Trees - Integrated Dynamics) @@ -3776,7 +3776,7 @@ flower-seeds astemirs-forest-craft astromine-foundations|天体矿工:基础 (Astromine: Foundations) -create-stuff-additions@|机械动力:物品附加 (Create Stuff & Additions) +create-stuff-additions@|机械动力:物品附加 (Create Stuff 'N Additions) slab-machines|台阶型设备* more-create-stuffs|更多机械动力物品* rsinfinitybooster|RS 无限范围增幅卡 (RSInfinityBooster) @@ -3885,10 +3885,10 @@ easy-villagers@|村民物品化/简单村民* largebar tis-3d@tis3d entity-nan-health-fix|假死修复 (Entity NaN Health Fix) -ferritecore@ferrite-core|铁氧体磁芯 (FerriteCore)¨ferritecore-fabric|铁氧体磁芯 (FerriteCore) +ferritecore-fabric@ferrite-core|铁氧体磁芯 (FerriteCore)¨ferritecore|铁氧体磁芯 (FerriteCore) thaumcraft-research-patcher masterful-machinery@ -minecraft-earth-mod|Derec 的地球生物 (Derec's Earth Mobs) +minecraft-earth-mod@earth-mobs-mod|Derec 的地球生物 (Derec's Earth Mobs) scotts-essential drawerfps|储物抽屉渲染配置 (DrawerFPS) @@ -3920,7 +3920,7 @@ the-elemelon-mod|元素西瓜* dcsp|钻石压缩 (Diamonds Compressor SP) corpse-complex@ modularvoicechat|模块化语音聊天 (ModularVoiceChat) -elytra-slot@|鞘翅插槽 (Curious Elytra / Elytra Trinket / Elytra Slot)¨elytra-slot-fabric|鞘翅插槽 (Curious Elytra / Elytra Trinket / Elytra Slot) +elytra-slot@|鞘翅插槽/鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot)¨elytra-slot-fabric|鞘翅插槽/鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot) tconstructcompatible|匠魂兼容 (TConstructCompatible) @@ -3955,7 +3955,7 @@ satako-library¨satako-fabric unique-enchantments-utils|独特的附魔:效用扩展 (Unique Enchantments - Utils) forge-ae2-additional-opportunity@ae2ao¨ae2ao¨legacy-ae2-additional-opportunity iconexporter@icon-exporter -curious-shulker-boxes@shulker-box-slot|潜影盒插槽 (Shulker Box Slot)¨curious-shulker-boxes-fabric|潜影盒插槽 (Shulker Box Slot) +curious-shulker-boxes@shulker-box-slot|潜影盒插槽/潜影盒槽位 (Shulker Box Slot)¨curious-shulker-boxes-fabric|潜影盒插槽/潜影盒槽位 (Shulker Box Slot) xenoclus-2 darker-depths|深岩之下* terracraft-journey @@ -4066,7 +4066,7 @@ incendium@ cooperative-advancements@|合作进度*¨cooperative-advancements-fabric|合作进度 (Cooperative Advancements) lightstones openbackup -fat-experience-orbs@|肥胖经验球 ([Fabric] Fat Experience Orbs) +fat-experience-orbs@|肥胖经验球* better-pipes|更好的管道* truetype-font-replacement fluidlogged-api@ @@ -4089,7 +4089,7 @@ minemenufabric@|我的菜单 Fabric 版 (MineMenuFabric) @tabtps afkpeace@ - +secret-rooms-fabric@ smooth-chunks|平滑区块加载*¨@smooth-chunks|平滑区块加载 (Smooth Chunks) - 已失效 saturationoverflow|饱和度溢出 (SaturationOverflow) @@ -4136,7 +4136,7 @@ falling-leaves-forge@fallingleavesforge|落叶 (Falling Leaves (NeoForge/Forge)) custom-entity-models-cem@cem|自定义实体模型 (Custom Entity Models) -tweakmyclient +tweakmyclient@ lovely-snails@lovely_snails|可爱蜗牛* clipboard|备忘录* @@ -4160,7 +4160,7 @@ selene@moonlight compatibilitylayerforcustomskinloader|万用皮肤补丁兼容层 (CompatibilityLayerForCustomSkinLoader) no-potion-shift no-potion-offset -demonslayer|鬼灭之刃 (Kimetsu no Yaiba) +demonslayer@orca-kimetsu-no-yaiba|鬼灭之刃 (Kimetsu no Yaiba) nautilium|鹦鹉螺* auto-attack|自动攻击* tamagotti @@ -4192,7 +4192,7 @@ wooden-armors-stone-armor@woodenarmor|木制盔甲 (Wooden Armors Mod) insanelib@ daydreamer phantomfaces|幻灵接口* -improved-villagers|改进的村民 (Improved Villager Mod) +improved-villagers|改进的村民* equipment-compare@|装备比较*¨equipment-compare-fabric|装备比较 (Equipment Compare) PickupWidely @@ -4211,8 +4211,8 @@ tfc-caffeine-addon tfc-cellars-add-on boats-and-beeps konkrete@¨konkrete-fabric -xp-from-harvest|收获经验 (XP From Harvest) -modern-beta¨modern-beta-forge +xp-from-harvest@|收获经验 (XP From Harvest) +modern-beta@modern-beta-forge¨modern-beta-forge tfcflux tfc-medicinal water-flasks @@ -4288,7 +4288,7 @@ reroll|刷新附魔* tax-free-levels@|免税附魔* portable-dimensions|便携式传送门* stone-age-by-yanny -terra-world-generator|Terra - 已经失效¨@terra +terra-world-generator|Terra - 已失效¨@terra omnis|灾厄村民拓展* omnis-backpacks @@ -4297,7 +4297,7 @@ bag-of-yurting@|帐篷袋 (Bag of Yurting) better-combat-rebirth|更好的战斗:重生* iron-and-gold-from-1-17|来自 1.17 的铁和金 (iron and gold from 1.17) immersive-cooking@|沉浸烹饪* -structured-crafting +structured-crafting@ random-shulker-colours@|随机潜影贝颜色*¨random-shulker-colours-fabric|随机潜影贝颜色 (Random Shulker Colours) create-automated random-sheep-colours@|随机绵羊颜色*¨random-sheep-colours-fabric|随机绵羊颜色 (Random Sheep Colours) @@ -4349,7 +4349,7 @@ big-beacons|大信标* wooden-hopper@|木漏斗* ages-api -fancyhud¨spiffy-hud-fabric +fancyhud@spiffyhud¨spiffy-hud-fabric advanced-compass|高级指南针* salutem the-sol|太阳系* @@ -4472,7 +4472,7 @@ travelers-titles@|旅人标题 (Traveler's Titles)¨travelers-titles-fabric|旅 expand-chinese-sword|中式剑拓展 (Arsenal Integration) littleframes@littlepictureframes|小画 (LittlePictureFrames) -lavalogged-blocks|岩浆充填 (Lavalogged blocks) +lavalogged-blocks@|岩浆充填 (Lavalogged blocks) awoken-world gt4-reimagined|格雷科技4:重构想 (GregTech 4 Reimagined) camouflaged-creepers @@ -4531,7 +4531,7 @@ herobrines-origin|Herobrine 的起源 (Herobrine's Origin) the-abyss|深渊:远古 (The Abyss: Legacy) sekiro-sense-symbols lurker|Cold的潜伏者 (Colds: Lurker) -cavebiomeapi¨cavebiomeapi-fabric +cavebiomeapi@¨cavebiomeapi-fabric extended-block-shapes@extshape|扩展方块形状*¨extended-block-shapes-blockus@extshape_blockus|扩展方块形状 (Extended Block Shapes) passive-mobs@passivemobs sponsor|赞助者* @@ -4655,7 +4655,7 @@ draggable-resource-packs@draggable-lists|可拖动列表/可拖动资源包 (Dra fastchest|快速箱子渲染 (FastChest) - 已不再更新¨@fastchest|快速箱子渲染 (FastChest) - 在此处更新 yuushya-townscape@|方块小镇* architects-palette-fabric@|建筑师的调色板 (Architect's Palette)¨architects-palette@architects-palette#all-versions|建筑师的调色板 (Architect's Palette) -whisperwoods|低语之森* +whisperwoods@|低语之森* dimensional-dungeons@|维度地牢* bosses-of-mass-destruction@|祸乱鬼魅 (Bosses of Mass Destruction) neoelfeos-medieval-pub-decoration @@ -4816,7 +4816,7 @@ hourglass@|沙漏* relics-mod@|遗物 (Relics) comfortable-nether|舒适下界* arcanecraft-ii|奥法工艺2 (ArcaneCraft II) -presence-footsteps-forge@resence-footsteps-forge|脚步声Forge版 (Presence Footsteps (Forge)) +presence-footsteps-forge|脚步声Forge版 (Presence Footsteps (Forge))¨@resence-footsteps-forge|脚步声Forge版 (Presence Footsteps (Forge)) - 已失效 aardvarks-weird-wonderful-wild @@ -4868,7 +4868,7 @@ damage-tint@|伤害色调* mo-spells prehistoric-flora|史前自然 (Prehistoric Nature)¨prehistoric-nature@|史前自然* blade-works|剑制 (Blade Works/nikgub_'s Blades+) -create-confectionery|机械动力:甜食* +create-confectionery@|机械动力:甜食* @@ -4925,7 +4925,7 @@ illagers@illagers+|更多的灾厄村民 (Illagers+) villagers-follow-emeralds-fabric@|村民跟随绿宝石 (Villagers follow Emeralds) -diabloloot|暗黑破坏神战利品 (DiabloLoot) +diabloloot|暗黑破坏神战利品 (DiabloLoot - Bosses Update) yggdrasil|世界树* ftb-dripper-forge forge-snad|子沙1.14+ (Snad)¨fabric-snad|子沙1.14+ (Snad) @@ -4941,7 +4941,7 @@ open-to-lan simple-translation@|简单翻译* fx-control@|效果控制 (Fx control) what-are-you-voting-for@ -steam-powered-create|蒸汽动力 (Create: Steam Powered) +steam-powered-create@create-steam-powered|蒸汽动力 (Create: Steam Powered) creepers-burn@CreepersBurn pretty-game-enhhancing-gobblet-pgeg-remake|魔法森林重置版 (Pretty, Game-Enhhancing Gobblet Remake) @@ -4955,7 +4955,7 @@ enderscape@ goblin-traders-fabric@|哥布林商人Fabric版 (Goblin Traders (Fabric)) lakeside@|湖畔* chenziqius-originality|CHENZIQIU的创意 (CHENZIQIU's Originality) -mc-modsoy-forgery@alloy-forgery +mc-modsoy-forgery@alloy-forgery|合金冶炼炉 (Alloy Forgery) mechanical-tech@|机械科技* hadesgame|阴间游戏 (HadesGame) one-block-forge @@ -4967,7 +4967,7 @@ happiness-is-a-warm-gun@ kvc-blocks|蛋挞君的原版补全方块篇 (KVCBlocks) enigtech2-util -environmental-creepers|环保型苦力怕* +environmental-creepers@|环保型苦力怕* look-at-that advanced-peripherals@advancedperipherals|高级外设* @@ -4986,9 +4986,9 @@ expandability squirrels|松鼠* ftb-power-pots the-graveyard-forge@|墓园 (The Graveyard)¨the-graveyard-fabric@|墓园 (The Graveyard) -nether-agriculture|下界农业* +nether-agriculture@netheragriculture|下界农业* conjuring@ -awesome-dungeon-forge@¨awesome-dungeon-fabric@ +awesome-dungeon-forge@awesome-dungeon¨awesome-dungeon-fabric¨awesome-dungeon-neoforge out-of-sight@ decorative-blocks-modded-compat|装饰方块模组兼容 (Decorative Blocks modded compat) camp-chair @@ -5236,7 +5236,7 @@ edible-nether-wart|可食用下界疣* plasmo-voice@|Plasmo 语音* armor-sound-tweak|盔甲音效调整* hexerei@|魔法巫师* -morerefinedstorage +morerefinedstorage@ give-me-water-or-give-me-death lost-and-found better-fps-render-distance¨better-fps-render-distance-fabric @@ -5270,20 +5270,20 @@ better-bedrock-generator¨better-bedrock-generator-fabric prehistoric-fauna@|史前生物* better-conduit-placement@|更好的潮涌核心放置*¨better-conduit-placement-fabric|更好的潮涌核心放置 (Better Conduit Placement) death-counter@death-counter-ichun|死亡计数器*¨death-counter-for-fabric|死亡计数器 (Death Counter) -dimension-stages|维度阶段* +dimension-stages@|维度阶段* passive-dragon i-want-to-die-at-home morevanillaweapons@|更多原版武器 (MoreVanillaWeapons) ore-tree-reborn|矿物树:重制 (Ore Tree:Reborn)¨ore-tree-reborn-fabric|矿物树:重制 (Ore Tree:Reborn) player-tracking-compass@ -tooltipfix|信息提示修复 (ToolTipFix) +tooltipfix@|信息提示修复 (ToolTipFix) extended-noteblock-forge|音符盒扩展 (Extended Noteblock)¨extended-noteblock-fabric|音符盒扩展 (Extended Noteblock) ore-prospecting-stick-forge|探矿之戒 (Rings Of Prospecting)¨ore-prospecting-stick-fabric|探矿之戒 (Rings Of Prospecting) worldedit-express-forge¨worldedit-express-fabric awesome-dungeon-edition-ocean-forge@awesome-dungeon-edition-ocean¨awesome-dungeon-edition-ocean-fabric pistorder -escape-rope-pokemon-item-forge¨awesome-dungeon-neoforge +escape-rope-pokemon-item-forge awesome-flooring-forge@¨awesome-flooring-fabric@ medievial-entity-forge@medieval-entity-forge|中世纪生物 (Medieval Entity)¨medievial-entity-fabric@medieval-entity-fabric|中世纪生物 (Medieval Entity) upgraded-shulkers|更多潜影盒* @@ -5292,7 +5292,7 @@ bobby@|服务器区块缓存* boosted-brightness@|亮度增强* effective@¨effective-forge -voidtotem|虚空图腾 (Void Totem)¨voidtotem-fabric|虚空图腾 (Void Totem) +voidtotem@|虚空图腾 (Void Totem)¨voidtotem-fabric|虚空图腾 (Void Totem) tool-leveling-plus@tool-leveling|工具升级 (Tool Leveling+)¨tool-leveling-plus-fabric|工具升级 (Tool Leveling+) mythicmetals@|神话金属 (Mythic Metals) creeper-firework@|烟花苦力怕* @@ -5434,7 +5434,7 @@ aquatic-armors immersive-industry|沉浸工业* lightmans-currency@¨lightmans-currency-fabric hellfire-fireball-launcher|地狱火:火焰弹发射器 (Hellfire: Fireball Launcher)¨hellfire-fireball-launcher-fabric|地狱火:火焰弹发射器 (Hellfire: Fireball Launcher) -guild +guild@ endless-deep-space|无尽深空* - 已弃用¨@endless-deep-space|无尽深空 (Endless Deep Space) dynamic-trees-for-sakura|动态的树:樱附属 (Dynamic Trees for Sakura) @@ -5482,15 +5482,15 @@ agadars-brewing-api ae-shield|奕 秉甲* -giant-player-boss|巨人玩家Boss* +giant-player-boss|巨人玩家 Boss* set-bonus -tntutils +tntutils@ giant-swords|巨剑* terrablender@¨terrablender-fabric¨terrablender-neoforge commandhelper|命令助手 (CommandHelper) bilibili -creatures@frikinzis-fauna +creatures|Frikinzi's Fauna - 已失效¨@frikinzis-fauna mcdw@|我的世界:地下城武器 (MC Dungeons Weapons) beta-creepers @@ -5536,10 +5536,10 @@ wtfcore ore-remover betweenlands-redstone more-peripherals@ -ageing-spawners +ageing-spawners@ loot-extra|战利品表拓展 (LootExtra) cloud-boots-mod@cloud-boots|云靴 (Cloud Boots) -better-compatibility-checker +better-compatibility-checker@ fastfurnace-minus-replacement harvest craftpresence@ @@ -5593,7 +5593,7 @@ permanent-light-generator@plg|恒久光发电机*¨permanent-light-generator-fab immortuos-calyx claim-chunk -dicemc-money-mod +dicemc-money-mod@money-sign-shops client-command-aliases interactive-corporea|交互多媒体* just-another-auto-clicker @@ -5625,7 +5625,7 @@ it-fell-from-the-sky lyonheart@ genshin-12|原神12* -ae-additions-extra-cells-2-fork +ae-additions-extra-cells-2-fork@ae-additions silent-addons ars-arsenal gps-mod-stalker-mod @@ -5635,7 +5635,7 @@ just-enough-piglin-bartering|JEI猪灵以物易物* @aftersans53228-fabric-road|暗访谈的Fabric道路 (aftersans53228's fabric roads) primal-magick@|原初魔法* -ore-creeper|矿石苦力怕* +ore-creeper@|矿石苦力怕* panthalassa|泛古洋* stardew-armory|星露谷武器*¨stardew-armory-fabric|星露谷武器 (Stardew Armory) no-more-nether-desert-edition @@ -5678,14 +5678,14 @@ delogger just-another-vanilla-addon-java|迫真原版增强 (Just Another Vanilla Addon) spectrite-mod|彩虹 (Spectrite) genshin-nature@|原神:自然* -power-utilities-remastered|能源转换重制* +power-utilities-remastered@power-utilities|能源转换重制* create-gear-addon gun-mod-tl energymeter@ -@headindex|头颅菜单 (Head Index) -quantum-generators|量子发电机* -simply-quarries|简易采石场* +headindex@|头颅菜单 (Head Index) +quantum-generators@|量子发电机* +simply-quarries@|简易采石场* lethal-peaceful @@ -5693,13 +5693,13 @@ storage-drawers-crafting-recipe enhanced-attack-indicator@|增强攻击提示器* parasites-addon-overlast|寄生兽:末世扩展 (OverLast) cancel-block-update@|取消方块更新* -franciscofunari|中世纪工艺 (Medieval Craft) +franciscofunari@medieval-craft-(structures)|中世纪工艺 (Medieval Craft) d3core green-thumb-v1-2-03 greenthumb tfc-adventure|群峦冒险 (TFC+ Adventure) just-zoom-forge@just-zoom -@holograms|全息图 (Holograms) +server-holograms@holograms|全息图 (Holograms) eidolons-hexblades|幻梦咒剑 (Eidolon's Hexblades) monke-madness @@ -5734,7 +5734,7 @@ portable-craft|便携工艺* @polypack_host etched@ caves-cliffs-backport-additions -cobblefordays +cobblefordays@ admin-shop@shop|官方商店* vanillamagic|纯净魔法 (Vanilla Magic) deep-mob-learning-simulacrum @@ -5770,11 +5770,11 @@ zettai-psi psicaster tweakermore@ legacy-display@ -the-backrooms|后室* +the-backrooms creatures-of-the-snow@creatures-from-the-snow-fabric|雪地里的生物! (Creatures From The Snow!)¨creatures-from-the-snow-forge|雪地里的生物! (Creatures From The Snow!) collisions-lib@ wooled-boots@|羊毛靴子* -zawa-evolved|野生动物园:进化 (Zoo and Wild Animals: Evolved) +zawa-evolved@|野生动物园:进化 (Zoo and Wild Animals: Evolved) bongo@|宾果游戏* impersonate@ calemi-core@ccore @@ -5817,7 +5817,7 @@ delete-worlds-to-trash-forge@delete-worlds-to-trash¨delete-worlds-to-trash-fabr simple-magnets@|简易磁铁* -guns-without-roses@ +guns-without-roses@|枪炮“没”瑰* buddycards-expansions|巴迪卡牌扩展* zettai-ars cant-sleep-clowns-will-eat-me@|夜不能寐 (Can't Sleep Clowns Will Eat Me) @@ -5827,10 +5827,10 @@ realistic-block-physics@ domestication-innovation@|驯养革新* snuffles@|魄罗* flan-forge@flan¨flan -awesome-dungeon-the-end-forge@¨awesome-dungeon-the-end-fabric@ +awesome-dungeon-the-end-forge@awesome-dungeon-the-end¨awesome-dungeon-the-end-fabric tehnuts-torcherino -library-ferret-forge@library-ferret¨library-ferret-fabric -awesome-dungeon-nether-forge@¨awesome-dungeon-nether-fabric@ +library-ferret-forge@library-ferret¨library-ferret-fabric¨library-ferret-neoforge +awesome-dungeon-nether-forge@awesome-dungeon-nether¨awesome-dungeon-nether-fabric ae2-extended-life|应用能源非官方延续版 (AE2 Unofficial Extended Life) refined-cooking@|精致厨房* @@ -5852,7 +5852,7 @@ pirates|海盗* comics-bubbles-chat@ fabric-extended-armor-bars-forked eating-animation-forge@eating-animations|进食动画 (Eating Animation)¨eating-animation-fabric@eating-animation|进食动画 (Eating Animation) -waterdripsound@¨dripsounds-fabric@ +waterdripsound@dripsounds¨dripsounds@waterdripsound better-horse-hud|更好的骑马 HUD* thermopolium|汤饮铺* @@ -5866,7 +5866,7 @@ double-jump-attribute¨@double-jump-attribute|Double Jump Attribute - 已失效 overweight-farming@ healthcare@ betterloadingscreen|加载画面改良 (Better Loading Screen) -fabric-title-changer +fabric-title-changer@ window-title-changer @betterlight @@ -5946,7 +5946,7 @@ slender-man-mod cookielicious|曲奇佳味* the-forbidden|禁忌者* memory-usage-screen@ -evergreenhud|EvergreenHUD - 未及时更新,请从MR¨@evergreenhud +evergreenhud@ nuit@ @level-border @fvt @@ -5996,7 +5996,7 @@ oedldoedl-resources oedldoedl-gear disguiseheads@ simple-tcon -enchanted-tooltips +enchanted-tooltips@ stackablepotions-forge¨stackablepotions mobifier¨mobifier-fabric art-of-forging-a-tetra-addon|锻造之艺 (Art of Forging) @@ -6045,7 +6045,7 @@ hygiene worldeditcui-forge-edition-3 external-view-reload fluid-drawers-legacy|储液抽屉:遗产* -my-server-is-compatible +my-server-is-compatible@ medieval-siege-machines@|中世纪攻城机器* leapoffaith-forge|信仰之跃 (Leap of Faith) top-plus @@ -6084,7 +6084,7 @@ onastick-forge@onastick¨onastick tradernpcs more-pads|更多Pad* texture-dump -spartan-weaponry-ice-and-fire|斯巴达的武器:冰火传说 (Spartan Weaponry: Ice and Fire) +spartan-weaponry-ice-and-fire@|斯巴达的武器:冰火传说 (Spartan Weaponry: Ice and Fire) hefty-crops@ @strangefuelsmod|奇奇怪怪的燃料 (Strange Fuels Mod) custom-player-models@|自定义玩家模型 (Customizable Player Models)¨custom-player-models-fabric|自定义玩家模型 (Customizable Player Models) @@ -6160,7 +6160,7 @@ sandwichable@ @shelve-refabricated mod-erate-loading-screen@moderate-loading-screen shrink_@shrink -cable-tiers +cable-tiers@ jumbo-furnace@ no-villager-death-messages@ tank-null @@ -6189,7 +6189,7 @@ spelling-table-mod|拼写台* advancedchatcore@AdvancedChatCore dirt-deco multiblocked -arcanus@ +arcanus-legacy@ armor-weight mc-mods-the-tweaks runelic@ @@ -6198,7 +6198,7 @@ visuality@|可视性* ecologics@|丰富的生态* mob-scarecrows trolls-community -g4mespeed +g4mespeed@ ogres-community goblin-community kobolds-community @@ -6269,14 +6269,14 @@ extrasounds@ brooms the-stormlight-archive-mod|飓光志* extrae -projecte-integration|等价兼容 (ProjectE Integration) +projecte-integration@|等价兼容 (ProjectE Integration) mekajs antique-dragons@ badpackets@ undead-army ironagefurniture -minekea-fabric +minekeamod aeble trident-mod|三叉戟 (Trident) custom-professions@custom-villager-professions|自定义村民职业 (Custom Villager Professions)¨custom-villager-professions-fabric|自定义村民职业 (Custom Villager Professions) @@ -6314,7 +6314,7 @@ pirates-and-looters-mod @mudrock|泥岩 (Mudrock) itemchat -resident-evil-8-mod|生化危机8 (Resident Evil 8) +resident-evil-8-mod@resident-evil-84|生化危机8 (Resident Evil 8) solar-forge colored-allays|染色悦灵* @@ -6349,7 +6349,7 @@ extended-illagers-mod midnightcontrols@ wonderful-enchantments@majruszs-enchantments a-lot-of-useless-stuff -lankaster-origins +lankaster-origins@lankasters-origins priority-target|优先目标* @@ -6482,7 +6482,7 @@ ninjaxxs-needs pixelmon-gui-blocks more-mekanism-processing@ -item-banning +item-banning@ probejs@ impaled@|更多三叉戟 (Impaled🔱) laserio @@ -6507,7 +6507,7 @@ advancements-enlarger better-muffling|更好的消音器* lightest-lamps@¨lightest-lamps-fabric bigbuckets|大铁桶 (Big Buckets)¨bigbuckets-fabric|大铁桶 (Big Buckets) -dimstorage +dimstorage@ spawner-imbuer@ unnamed-delight|无名乐事* @@ -6524,7 +6524,7 @@ better-sponges easyexcavate wooden-shears@|木剪刀* brick-furnace@|砖熔炉* -brick-hopper|砖漏斗* +brick-hopper@|砖漏斗* human-companions odysseyhud colorblindness@ @@ -6658,7 +6658,7 @@ astemirs-fireflies ender-crop@ modupdater -fivehead +fivehead@5head attained-drops|可种植掉落物* attained-drops-2|可种植掉落物 2* @@ -6673,7 +6673,7 @@ dynamx mob-cards mavm@ @waxednotwaxed -hidden-names|隐藏名称* +hidden-names@|隐藏名称* doph@|远程命中提示 (Ding On Projectile Hit) keybinds@ hat-list@hatlist @@ -6689,12 +6689,12 @@ ftb-backups-2|FTB备份2 (FTB Backups 2) darkkore@ -illagers-wear-armor +illagers-wear-armor@ hudium@ ocean-world@|海洋的世界* cobblegen@ zombie-extreme -modularmovements|模块化动作 (ModularMovements) +modularmovements|模块化动作 (Modular Movements) uncle-js wither-bones|凋灵之骨* sleep-warp@ @@ -6710,7 +6710,7 @@ coffee-spawner timber-mod@timber-mod-by-mycf¨@timber-by-mycf-(server-side)|Timber Mod - 服务端版 sophisticated-storage@|精妙存储* advanced-lootable-weapons -what-the-bucket +what-the-bucket@ quickhomes real-time-clock tardim@ @@ -6858,7 +6858,7 @@ the-nexus-deep-dark-dimension jepp|JEI绘画预览 (Just Enough Painting Previews) create-curios -ctm-refabricated +ctm-refabricated¨@ctm-refabricated placeable-pumpkin-pie|可放置南瓜派* birch-forest-upgrade|白桦林更新* ok-zoomer-forge @@ -6888,7 +6888,7 @@ ftb-jar-mod-forge dimensional-paintings|维度绘画* item-recycler linsapi@ -titlechanger@mctitlechanger|标题切换器 (TitleChanger) +titlechanger|标题切换器 (TitleChanger) - 已失效¨@mctitlechanger|标题切换器 (TitleChanger) adobe-blocks-2 menacing-monsters packing-tape @@ -6919,7 +6919,7 @@ building-wands@ advancedchat@AdvancedChat aiots-expanded-fabric moremcmeta@¨moremcmeta-fabric -starry-skies +starry-skies@starryskies flesh2leather-forge¨flesh2leather-fabric fall-damage-indicator aww-man-achievement-mod @@ -6931,7 +6931,7 @@ moss-and-monsters detonation @rpgdurability|RPG装备耐久 (RPG Durability) coal-processing-machine -joy-of-painting@ +joy-of-painting@|绘画之乐 (Joy of Painting) warden-loot-forge¨warden-loot @vmwcr|维克的现代战争补充合成表 (Crafting Recipes of Vic's Modern Warfare Mod) @@ -6954,8 +6954,8 @@ prehistoric-flora-integration better-bad-omen can-you-trash-it croptopias-chocolaterie-forge@croptopia-chocolaterie|作物盛景:巧克力工坊 (Croptopia's Chocolaterie)¨croptopias-chocolaterie-fabric|作物盛景:巧克力工坊 (Croptopia's Chocolaterie) -crawl@ -fabric-chisel@chisel-reborn +crawl@|爬行* +chisel-reborn@ roost-ultimate@roostultimate cammies-wearable-backpacks@ @@ -6976,12 +6976,12 @@ dragon-fire-mod sandy-ores|沙子矿石* rainbowify@ be-quiet-please|请安静* -prehistoric-revival-a-prehistoric-fauna-add-on|史前复生 (Prehistoric Revival) +prehistoric-revival-a-prehistoric-fauna-add-on|史前复生 (Prehistoric Revival - A Prehistoric Fauna Add-On) - Issues提交处¨@prehistoric-revival-a-prehistoric-fauna-add-on|史前复生 (Prehistoric Revival - A Prehistoric Fauna Add-On) had-enough-characters medieval-origins -elytra-chestplate-swapper +elytra-chestplate-swapper@ the-ends-flora apollos-additional-structures @@ -7011,7 +7011,7 @@ withcoffee|咖啡相伴/天堂之路 (WithCoffee) emeraldstuff grass-slabs-carpets-stairs not-mutually-exclusive|不冲突*¨@not-mutually-exclusive|不冲突 (Not Mutually Exclusive) - 已失效 -builders-delight-forge +builders-delight-forge@builders-delight miners-minerals@ days-to-mine@7-days-to-mine|七日杀 (7 Days to Mine) pieces-of-ore @@ -7025,7 +7025,7 @@ easy-mob-farm@ material-elements raised-clouds@ music-triggers@ -sneaky-screens +sneaky-screens@ inmis@¨inmis-forge @@ -7082,7 +7082,7 @@ auto-workstations-fabric@auto-workstations|自动化工作站 (Auto Workstations l2-backpack@l2backpack|莱特兰-背包* @mbp -capybara-fabric¨unnamed-animal-mod-capybara-only +capybara-uam¨unnamed-animal-mod-capybara-only just-enough-petroleum diamethysts@diamethysts! twigs@ @@ -7134,7 +7134,7 @@ fogbox@ @hopper lots-of-shields timestopclock|Mega的时间静止 (MegaTimeStopMod) -umu-little-maid|UMU Little Maid - 已失效¨@umu-little-maid +umu-little-maid@ loot-games|战利品游戏* ydms-scorpions@|YDM的蝎子 (YDM's Scorpions) @@ -7165,14 +7165,14 @@ alcocraft-beer-and-stuff simplevillagers@ autoreconnect@ - +twelvefold-better-combat|更好的战斗* - 已失效¨@twelvefold-better-combat|更好的战斗 (Twelvefold Better Combat) dusk copper-craft|铜制工艺* extrabotany2|额外植物学重置版 (ExtraBotany2) terramine@ -aeinfinitybooster +aeinfinitybooster@ endertech-infinity|Endertech Infinity - 已失效¨@endertech-infinity -more-frogs-fabric@morefrogs|更多青蛙 (More Frogs) +more-frogs-fabric@morefrogs|更多青蛙2 (More Frogs 2) create-big-cannons@|机械动力:火炮* @@ -7225,7 +7225,7 @@ dynamic-surrounding-resurrected spells-shields -player-companions +player-companions@ technical-engineering-3|科能工程3* extra-utilities-reborn|更多实用设备:重生* @@ -7331,7 +7331,7 @@ textrues-rubidium-options@|TexTrue的铷视频界面 (TexTrue's Rubidium Options rubber-duck|橡皮鸭* unlimited-dragon-eggs-fabric@unlimited-dragon-eggs -moreplayermodels-plus@moreplayermodels+ +moreplayermodels-plus@moreplayermodels+|更多玩家模型+ (MorePlayerModels+) @actioninventory custom-void @@ -7345,7 +7345,7 @@ unusual-end@unusual_end|不同寻常的末地* solar-flux|日光通量* custom-background-color@cbc|自定义背景颜色* -simple-shulker-preview|Simple Shulker Preview - 已弃用¨@simple-shulker-preview +simple-shulker-preview@ bygone-nether|逝去的下界*¨bygone-nether-fabric|逝去的下界 (Bygone Nether) @clickopener kokoalinux-backport @@ -7547,7 +7547,7 @@ moar-fps@moarfps flighthud meal-api@mealapi seed-drop -fishery-expansion +fishery-expansion|🎣 Fishery‎‎‎‏‏‎ ‎ - 已失效¨@fishery arrowcamfabric overpowered-mending@overpoweredmending @@ -7576,7 +7576,7 @@ elytra-bounce@ no-report-button@nrb mana-wizardry aileron@ -@devworld +devworld@ immersive-weapons-compatibility-bridge gameidiots-craftable-wizardry-items @@ -7610,7 +7610,7 @@ recursive-resources@recursiveresources inventory-actions lavafishing@|熔岩钓鱼 (Lava Fishing) haunted-harvest@ -dungeonsenhanced +dungeonsenhanced@dungeons-enhanced create-crystal-clear@|机械动力:晶莹剔透 (Create: Crystal Clear) @create-crystal-clear-fabric fix-experience-bug @@ -7624,13 +7624,13 @@ it-takes-a-pillage¨it-takes-a-pillage-fabric get-it-together-drops@ fire-n-blood chunknogobyebye -beaming-drops@loot-beams-backport|掉落物光束 (Beaming drops) +beaming-drops@loot-beams-backport|掉落物光束 (Beaming drops / Loot Beams Backport) protocharset@protocharset-rift the-hordes@ inventory-management@ soul-fired@soul-fire-d -faithfulbackrooms +faithfulbackrooms@ ex-machinis@¨ex-machinis-divitiae-deorum@ goety@|诡厄巫法* torch-hit @@ -7713,7 +7713,7 @@ odysseymod libipn@ hulsealib blues-dynamic-lights -gtc-expansion +gtc-expansion@ nether-depths-upgrade@|炽海生机* cloud-factory disc-holders-dj @@ -7900,8 +7900,8 @@ quickstore|快速贮藏重制版 (QuickStoreRemake) your-items-are-safe@¨your-items-are-safe-fabric techemistry the-missing-villages -continents@ -yungs-better-ocean-monuments@|YUNG的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-fabric|YUNG的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-neoforge|YUNG的海底神殿优化 (YUNG's Better Ocean Monuments) +continents +yungs-better-ocean-monuments@|YUNG 的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-fabric|YUNG 的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-neoforge|YUNG 的海底神殿优化 (YUNG's Better Ocean Monuments) echoing-depths advanced-finders @@ -7968,13 +7968,13 @@ status-effect-timer@statuseffecttimer logical-zoom@ the-space-age-mod|太空时代* -touhou-origins@|起源:东方project附加包* +touhou-origins@|起源:东方 Project 附加包* earth-mobs@earthmobs|地球生物* simpleores-tin just-enough-keys¨just-enough-keys-fabric why-am-i-on-fire@ -bouncing-balls-api@ +bouncing-balls-api@|弹跳球 API (Bouncing Balls API) bouncing-balls@|弹跳球* teletubbies-mod|天线宝宝* almost-unified@ @@ -7985,15 +7985,15 @@ time-arrow catch-me-if-you-can-cmiyc mc-modsis-one-way-glass|Alli的单向玻璃 (Alli's One Way Glass) sanguis -ender-trigon +ender-trigon@ litlaunch puzzle@ - +why-am-i-on-fire-refoxed @tdfp|暮色乐事:冰雪豪饮 (Twilight Delight: Frozen Pride) ftb-pack-companion teams-stages -kiwi-boi +kiwi-boi@ rubidium-toolkit@|铷 · 工具箱* advanced-combat|高级战斗装备* advanced-combat-revitalized@|高级战斗装备重置 (Advanced Combat (Revitalized)) @@ -8033,7 +8033,7 @@ better-stats@|更好的统计信息界面 (Better Statistics Screen) abyssalblocks|深渊国度幸运方块 (AbyssalBlocks) -archon +archon@ stackie @cullclouds @superfancyclouds @@ -8050,15 +8050,15 @@ berdinskiybears-armor-hud tiered-tooltips rgb-blocks-forge|调色方块 (RGB Blocks) -quicksand-fabric|流沙 (Quicksand) -better-durability|更好的耐久度* +quicksand-fabric@quicksand|流沙 (Quicksand) +better-durability@|更好的耐久度* passthrough-signs excorrelation@ packet-size-doubler @packet-size-double-forge arachnophobia-mode-for-minecraft@arachnophobia-mode -villagerconfig@|村民配置 (VillagerConfig) -jasm +villagerconfig@|村民配置 (Villager Config) +jasm@speedometer paginated-advancements@paginatedadvancements|分页进度 (Paginated Advancements & Custom Frames) @backport-gamerules structures-compass @@ -8070,7 +8070,7 @@ ingame-info-xml-addon-serene-seasons staff-of-building|建筑之杖 (Staff of Building) -dark-mode-everywhere|夜间模式无处不在* +dark-mode-everywhere@|夜间模式无处不在* custom-shop|自定义商店 (CustomShop) another-liquid-milk@ dark-mode-everywhere-fabric@|夜间模式无处不在Fabric版 (Dark Mode Everywhere - Fabric) @@ -8101,10 +8101,10 @@ youdroppedthis@ toomanyorigins@ -evil-regeneration +evil-regeneration@ food-treasure-box|食物宝盒* revolution -apugli|Apugli - 在此处更新¨@apugli|Apugli - 已停更 +apugli¨@apugli harvest-season @ic2fix @extended-bookshelves @@ -8136,9 +8136,9 @@ fallout-wastelands @letitsnow dons-lightning-rod-mod rainglow@ -nethers-delight-fabric|下界乐事(Fabric版) (Nether's Delight (Fabric)) -quicksand-forge@|流沙Forge版 (Quicksand (Forge)) -axolotl-bucket-fix-forge@|美西螈桶修复Forge版 (Axolotl Bucket Fix (Forge)) +nethers-delight-fabric|下界乐事(Fabric 版) (Nether's Delight (Fabric)) +quicksand-neoforge@|流沙(NeoForge) (Quicksand (NeoForge)) +axolotl-bucket-fix-forge@|美西螈桶修复 Forge 版 (Axolotl Bucket Fix (Forge)) scape-and-run-parasites-survival-addon-by-nocube scape-and-run-parasites-nests-addon-by-nocube @emiffect @@ -8153,29 +8153,29 @@ diamond-caliber world-stripper@ dayz-remastered|DayZ 重制版 (DayZ Remastered) visuality-reforged@visuality-forge|可视性:重铸 (Visuality: Reforged) -no-night-vision-flickering@ +ease-out-night-vision@no-night-vision-flickering athelas|阿塞拉斯* -more-beautiful-buttons +more-beautiful-buttons@¨more-beautiful-buttons-fabric aoa-end-realmstone adaptive-tooltips@ -variant-barrels-fabric¨@variant-barrels|Variant Barrels - 已失效 +variantbarrels@variant-barrels variant-crafting-tables-fabric@variant-crafting-tables immersive-structures@ avaritia-port-fabric explorify -variant-bookshelves-fabric¨@variant-bookshelves|Variant Bookshelves - 已失效 +variant-bookshelves-fabric@variant-bookshelves variant-tiered-shields-fabric¨@variant-shields|Variant Shields - 已失效 libertys-villagers@libertyvillagers|自由村民 (Liberty's Villagers) server-translation-api rough-mobs-2|粗暴的怪物2* barren-isles korgelin -variant-composters-fabric¨@variant-composters|Variant Composters - 已失效 +variant-composters-fabric@variant-composters variant-grindstones-fabric¨@variant-grindstones|Variant Grindstones - 已失效 custom-villager-trades-new|自定义村民交易 (Custom Villager Trades) -spice-of-life-apple-pie-edition|生活调味料:苹果派版 (Spice of Life: Apple Pie Edition) -xp-obelisk|经验方尖碑 (XP Obelisk) +spice-of-life-apple-pie-edition@|生活调味料:苹果派版 (Spice of Life: Apple Pie Edition) +xp-obelisk@|经验方尖碑 (XP Obelisk) enhanced-searchability@|搜索增强* compact-ui@ lets-do-wine@lets-do-vinery|葡园酒香 (Let's do Wine / Vinery) @@ -8186,13 +8186,13 @@ clockhud@clock-hud variant-lanterns-fabric¨@variant-lanterns|Variant Lanterns - 已失效 lanterns-bow@ auto-crouch@ -my-village-pack -variant-sticks-stuff¨@variant-sticks-and-stuff|Variant Sticks & Stuff - 已失效 +my-village-pack@my_village_pack +variant-sticks-stuff@¨@variant-sticks-and-stuff|Variant Sticks & Stuff - 已失效 multiple-server-lists@ superflat-biomes combat-music@ yuoenchants|Yuo的更多附魔 (YuoEnchants) -hibernal-herbs +hibernal-herbs@ better-resource-pack-sorting@ sound-physics-remastered@|物理声效重制版* @fix-quark-3951|Quark3951号issue修复 (FixQuark3951) @@ -8213,7 +8213,7 @@ daylight-mobs-reborn@ toast-manager explosives-i-approve -miners-delight-plus@miners-delight|矿工乐事 (Miner's Delight) +miners-delight-plus@miners-delight|矿工乐事 (Miner's Delight / Miner's Delight +) igniting-arrows rise-of-the-animagus lost-books @@ -8229,7 +8229,7 @@ stack-to-nearby-chests@|堆叠到附近的箱子 (Stack to Nearby Chests) gmod-title-screen@ create-steam-n-rails@|机械动力:汽鸣铁道 (Create: Steam 'n' Rails) neutron -applied-cooking|应用厨房* +applied-cooking@|应用厨房* @litematica-printer|投影打印机 (Litematica Printer) recipe-printer@ botarium@ @@ -8246,7 +8246,7 @@ xps-things|经验之谈 (XP's Things) gravityapi@gravity-api fabric-super-secret-settings@super-secret-revival item-alchemy-fabric@item-alchemy -bat-elytra@ +bat-elytra@|蝙蝠鞘翅* super-secret-settings@ @plumeconfig @@ -8257,9 +8257,9 @@ nosecrets|不秘密 (NoSecrets) extra-creeper-types loot-casket farmers-bundle-of-joy -@musical-allays +musical-allays@ pure-chaos-chaos-awakens -flatter-entities +flatter-entities@ daves-potioneering@ last-days-of-humanity-tweaks space-bosstools-giselle-addon @@ -8269,25 +8269,25 @@ inventory-essentials@¨inventory-essentials-fabric astral-additions|星辉附加* simple-copper-pipes@¨simple-copper-pipes-forge advanced-nbt-tooltips@|高级NBT提示框 (Advanced NBT Tooltips) -follow-me -stabx-modern-guns +follow-me@ +stabx-modern-guns@ justhammers@just-hammers reinforced-deepslate-crafting|强化深板岩的合成* jaopcacustom|JAOPCA自定义 (JAOPCA Custom) friendly-fire@ -companion¨companion-fabric -sebastrnlib +companion@¨companion-fabric +sebastrnlib@ what-music-is-this elemental-combat|元素战斗* roasted@|烘烤* -lotr-attack-indicator-addon@lotr-attack-indicator|魔戒攻击指示器 (LOTR Attack Indicator Addon) +lotr-attack-indicator-addon|魔戒攻击指示器 (LOTR Attack Indicator Addon)¨@lotr-attack-indicator|魔戒攻击指示器 (LOTR Attack Indicator Addon) - 已失效 steels-delight lotr-pouch-viewer-addon|魔戒口袋内容展示 (LOTR Pouch Viewer Addon)¨@lotr-pouch-viewer|魔戒口袋内容展示 (LOTR Pouch Viewer Addon) - 已失效 fastfood-delight|快餐乐事 (FastFood Delight) festive-delight@|节日乐事* project-arsenal -bathroom-blocks@ +bathroom-blocks@|浴室方块* galosphere@ lazydfu-agent|DFU 禁用器 (Lazydfu-agent) @@ -8312,7 +8312,7 @@ tooltip-scroll-fabric@tooltip-scroll legoatooms-golden-berries@golden_berries nyfcore call-of-the-wild|荒野之声 (Call of the Wild) -@more-lapis-lazuli +more-lapis-lazuli@ slimefun-essentials@ decoration-delight|装饰乐事 (Decoration Delight: Refurbished) @havio-cookbook|Havio的烹饪书 (Havio's Cookbook) @@ -8330,7 +8330,7 @@ fancy-dyes@ extra-mod-integrations@ mod-settings@ dont-disappear-on-me@dont-dissapear -fastquit@ +fastquit@|快速退出 (FastQuit) violeta-cannons war-of-valor @@ -8355,7 +8355,7 @@ giant-spawn@¨giant-spawn-fabric @mtr-nte|纸板箱的交通扩展 (Nemo's Transit Expansion) serene-shrubbery@ gardens-of-the-dead@|死亡花园 (Gardens of the Dead) -pirate-hats +pirate-hats|海盗帽子* decor4fabric mob-skills clay-no-more-balanced|黏土不再制衡* @@ -8374,11 +8374,11 @@ biomes-in-jars-forge@biomes-in-jars|群系罐子 (Biomes in Jars)¨biomes-in-jar @bocchitheblock craftable-potion-combinations@craftablepotioncombinations polylib@ -@creeper-fire-charge-fix|Creeper火焰弹修复 (Creeper Fire Charge Fix) +@creeper-fire-charge-fix|苦力怕火焰弹修复 (Creeper Fire Charge Fix) tags-binder@ fastcaps -throwable-fluids -time-in-a-bottle@|时间之瓶 Fabric 版 (Time in a Bottle (Fabric)) +throwable-fluids@ +time-in-a-bottle|时间之瓶 Fabric 版 (Time in a Bottle (Fabric)) - 已失效¨@time-in-a-bottle|时间之瓶 Fabric 版 (Time in a Bottle (Fabric)) upgraded-netherite-spartan-weaponry|下界合金增强:斯巴达的武器 (Upgraded Netherite : Spartan Weaponry) simple-jams nethers-cruelty @@ -8431,7 +8431,7 @@ windchimes@|风铃* diamond-ingots@|钻石锭* inventory-sorting@ -mine-piece +mine-piece@orca-mine-piece mine-piece-animation hbms-nuclear-tech-mod-extended-edition custom-skyboxes @@ -8458,7 +8458,7 @@ compact-status-effects|迷你状态效果配置*¨compact-status-effects-forge| end-goblin-traders-fabric|末地哥布林商人 (End Goblin Traders) @mikulib -playersync|玩家数据同步 (PlayerSync) +playersync@|玩家数据同步 (PlayerSync) create-more-potatoes@|机械动力:更多土豆 (Create: More potatoes)¨create-more-potatoes-fabric|机械动力:更多土豆 (Create: More potatoes) primitive-beasts raided@ @@ -8531,9 +8531,9 @@ pasterdream|帕斯特之梦 (PasterDream) animalistic @qdresloader -fabricskyboxes-interop@ +fabricskyboxes-interop@nuit-interop old-inventorism -title-fix@title-fix-mod +title-fix@title-fix-mod|标题修复* create-alloyed-guns @luna-slimes @@ -8568,7 +8568,7 @@ emi-loot@ backhand@ @stones-cookbook|Stone的烹饪书 (Stone's Cookbook) -farmers-delight-jei-plugin +farmers-delight-jei-plugin@just-enough-farmers-recipes keybind-fix@ tales-of-villagers-the-infiltrators|村民之书:渗透者 (Tales of Villagers: The Infiltrators) @morecullingextra @@ -8590,10 +8590,10 @@ lamps generic-ecosphere wired-redstone@wiredredstone pixelgalaxy -hopper +hopper|漏斗+ (Hopper+) excalibur-the-mighty-sword undergarden-paths|深园小径* -undergarden-tetra-patch|深暗之园:Tetra兼容 (Undergarden/Tetra Patch) +undergarden-tetra-patch|深暗之园:Tetra 兼容 (Undergarden/Tetra Patch) uncrafter@ @pebblegetter @@ -8606,16 +8606,16 @@ shipping@ structureutils @cloudmusic|云音乐 (CloudMusic) i-like-wood -macaws-furnitures-biomes-o-plenty@|Macaw的家具:超多生物群系附属 (Macaw's Furnitures - Biomes O' Plenty) -macaws-furnitures-oh-the-biomes-youll-go@|Macaw的家具:你将去的生物群系附属 (Macaw's Furnitures - Oh The Biomes You'll Go) +macaws-furnitures-biomes-o-plenty@|Macaw 的家具:超多生物群系附属 (Macaw's Furnitures - Biomes O' Plenty) +macaws-furnitures-oh-the-biomes-youll-go@|Macaw 的家具:你将去的生物群系附属 (Macaw's Furnitures - Oh The Biomes You'll Go) armiger news-pillagers rangers-haven -heyberryshutup -destroy-on-death +heyberryshutup@ +destroy-on-death@ @yes-steve-model|是,史蒂夫模型 (Yes Steve Model) -macaws-roofs-oh-the-biomes-youll-go@|Macaw的屋顶:你将去的生物群系附属 (Macaw's Roofs - Oh The Biomes You'll Go) -macaws-roofs-biomes-o-plenty|Macaw的屋顶:超多生物群系附属 (Macaw's Roofs - Biomes O' Plenty)¨macaws-terraformersmc@¨@macaws-roofs-biomes-o-plenty|Macaw的屋顶:超多生物群系附属 (Macaw's Roofs - Biomes O' Plenty) - 原,已失效 +macaws-roofs-oh-the-biomes-youll-go@|Macaw 的屋顶:你将去的生物群系附属 (Macaw's Roofs - Oh The Biomes You'll Go) +macaws-roofs-biomes-o-plenty|Macaw 的屋顶:超多生物群系附属 (Macaw's Roofs - Biomes O' Plenty)¨macaws-terraformersmc@¨@macaws-roofs-biomes-o-plenty|Macaw 的屋顶:超多生物群系附属 (Macaw's Roofs - Biomes O' Plenty) - 原,已失效 configured-feature-saplings@ coyote-time@ @@ -8625,7 +8625,7 @@ betterlands koremods@ heart-of-ender@ heart-of-spartan@ -macaws-fences-biomes-o-plenty|Macaw的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty)¨macaws-biomes-o-plenty@|Macaw的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty)¨@macaws-fences-biomes-o-plenty|Macaw的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty) - 原,已失效 +macaws-fences-biomes-o-plenty|Macaw 的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty)¨macaws-biomes-o-plenty@|Macaw的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty)¨@macaws-fences-biomes-o-plenty|Macaw 的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty) - 原,已失效 endemic@ lucys-sloths @gdtweaker @@ -8660,7 +8660,7 @@ reid@ its-been-twelve-years riftlin sorcerium@ -2603972 +zeropoint campful well-rested@ @@ -8710,7 +8710,7 @@ markdownmanual@|Markdown 手册(Markdown Manual) powershot@ @crockpot-tweaker automatic-tool-swap@ -knight-quest@¨knight-quest-fabric¨knight-quest-reforged +knight-quest-reforged@knight-quest¨knight-quest-fabric¨knight-quest modernfix@|现代化修复 (ModernFix) blades-plus homeostatic@|稳态* @@ -8728,7 +8728,7 @@ moon-phase-info@ advanced-tweakery creeper-hector tightened-village-security -@mcr_blahaj|布罗艾Fabric版 (Blahaj (Fabric))¨@bttf_blahaj|布罗艾Fabric版 (Blahaj (Fabric)) - 已失效 +@mcr_blahaj|布罗艾扩展版 (Blahaj Expanded) @blahaj-reforged|布罗艾:重铸 (Blåhaj - Reforged) buddingcrystals dynamic-trees-tectonic|动态的树:Tectonic附属 (Dynamic Trees - Tectonic) @@ -8804,7 +8804,7 @@ vanity-metallurgical minespaceex gregious-maximus shut-up-model-loader -cultural-delights-fabric@|多元乐事(Fabric版) (Cultural Delights [Fabric]) +cultural-delights-fabric@|多元乐事(Fabric 版) (Cultural Delights [Fabric]) collectors-reap@ bones-and-swords vanity-shedds-mystical-toolsets-pack @@ -8820,7 +8820,7 @@ glacier-ice gtnhmixins@ dawn-to-dusk open-tower-defence@|开放式塔防* -mtr-station-decoration@station-decoration|MTR车站装饰 (MTR Station Decoration) +mtr-station-decoration@station-decoration|MTR车站装饰 (Station Decoration) perviaminvenire silverbirch@silver-birch @@ -8829,19 +8829,19 @@ silverbirch@silver-birch creepypastas-mod more-slab landk-brick -kubejs-offline|KubeJS离线文档 (KubeJS Offline Documentation) +kubejs-offline@|KubeJS 离线文档 (KubeJS Offline Documentation) -ex-nihilonatureminerals|无中生有:传承 - Nature's Minerals扩展 (Ex Nihilo: Sequentia - Nature's Minerals Addon) +ex-nihilonatureminerals|无中生有:传承 - Nature's Minerals 扩展 (Ex Nihilo: Sequentia - Nature's Minerals Addon) memory-settings|内存设置* tiny-item-animations|Tiny Item Animations - 已失效¨@tiny-item-animations more-plates-revamped@|更多金属板重置版* extended-slabs-plus -brewin-and-chewin-fabric@|饮酒作乐(Fabric版) (Brewin' And Chewin' [Fabric]) +brewin-and-chewin-fabric@|饮酒作乐 Fabric 版 (Brewin' And Chewin' [Fabric]) particular longer-login-times -broglis-plants +broglis-plants@ rlmixins@ simplehats@simple-hats|简单帽子 (Simple Hats) timeoutout-fabric@timeoutout @@ -8855,7 +8855,7 @@ create-industrial-chemistry@|机械动力:工业化学 (Create: Industrial Che cruelars-triforcemod @morespawneggs|更多刷怪蛋 (MoreSpawnEggs) -beautify-refabricated@|美化!Fabric版 (Beautify: Refabricated) +beautify-refabricated@|美化!重织 (Beautify: Refabricated) manatweaks chicken-nugget @@ -8889,7 +8889,7 @@ waymaker@ dmod extra-redstone-craft|更多红石合成* -farmers-respite-fabric|农夫暇事(Fabric版) (Farmer's Respite (Fabric)) +farmers-respite-fabric|农夫暇事(Fabric 版) (Farmer's Respite (Fabric)) wizards@|魔法师* basketcase primal-lib @@ -8901,26 +8901,26 @@ leos-illagers modularmachinery-community-edition@|模块化机械:社区版 (Modular Machinery: Community Edition) wearable-backpacks-rlcraft-edition@|可穿戴背包-RLCraft版 (Wearable Backpacks: RLCraft Edition) -spartan-fire-rlcraft-edition|斯巴达之冰与火-RLCraft版 (Spartan and Fire: RLCraft Edition) +spartan-fire-rlcraft-edition@spartan-and-fire-rlcraft-edition|斯巴达之冰与火-RLCraft版 (Spartan and Fire: RLCraft Edition) frame-void-patch-mc-59363 archprp@|帕秋莉手册加载补丁跨平台版 (Patchouli Resource Patch - Arch/Architectured Patchouli Resource Patch) clear-skies-forge-port spell-power@ morecrashinfo|更多崩溃报告信息 (MoreCrashInfo) -epicfight-saopack@epicacg|史诗战斗:ACG附属包 (EpicFight ACG AddonPack) +epicfight-saopack|史诗战斗:ACG附属包 (EpicFight ACG AddonPack) screenshot-viewer@|截图查看器* gml@ @futurepack-additions|飞向未来附加 (Futurepack Additions) bee-fix|蜜蜂修复* spawner-fix|刷怪笼漏洞修复 (Spawner Bug Fix) -block-limit-fix +block-limit-fix@block_limit_fix nochatlag@|聊天无卡顿 (NoChatLag) spell-engine@|法术引擎* spellblade-next@spellblades-and-such netherite-road-2@|下界之路2* @tileentity-reset-fix -notenoughrecipebook@nerb +notenoughrecipebook@ urushi-mod|漆 (Urushi) nicephore¨nicephore-fabric terraria-paintings¨terraria-paintings-fabric @@ -8934,7 +8934,7 @@ fred-qol@fred mekanism-ce-unofficial|通用机械社区版:非官方 (Mekanism CE Unofficial) guntopia-legend-of-the-guns -carpet-ams-addition@ +carpet-ams-addition@¨@carpet-ams-addition-legacy pixelmon-gameshark hidden-creepers-creepers-pretend-to-be-other-mobs ob-core @@ -8971,8 +8971,8 @@ json-entity-models-jems@|JSON实体模型 (JSON Entity Models) rain-of-hell -suggestion-provider-fix -worldblender¨world-blender-fabric +suggestion-provider-fix@suggestionproviderfix +worldblender@¨world-blender-fabric@worldblender-fabric no-oceans|无海洋的世界* monoblocks tinkers-things-json@tinkers-things|匠魂杂物 (Tinkers' Things) @@ -8980,7 +8980,7 @@ free-cam¨@freecam|Freecam - 无作弊版 workings@ @mod-loading-screen|模组加载屏幕 (Mod Loading Screen) lucky-cases -atmosfera +atmosfera@ feruchemy-allomancy|藏金术 (Feruchemy) alive-combat|战斗生机* - 已不再使用¨@alive-combat|战斗生机 (Alive Combat) dialog|对话框* @@ -9000,7 +9000,7 @@ dregora-o-plenty integrated-api@ the-betweenlands-eternal-melodies|交错次元:永恒旋律 (The Betweenlands: Eternal Melodies) -frozen-apocalypse|冰封启示录* +frozen-apocalypse@|冰封启示录* whymap@ craftify@ overgrown-cities-fabric @@ -9011,7 +9011,7 @@ enchanting-max_level@enchanting.max_level fixbookgui@ custom-explosions|自定义爆炸* datapacks-screen|数据包屏幕 (Datapack Screen) -pandoras-box|潘多拉的魔盒 (Pandora's Box) +pandoras-box@|潘多拉的魔盒 (Pandora's Box) boss-rifts random-teleport-altar syncpotioneffects @@ -9024,7 +9024,7 @@ structure-void-toggle@|结构空位切换* @cactusfix morezombies|更多僵尸 (More Zombies) mushroom-quest -chowder-express +chowder-express@ mod-browser|模组浏览器* fastback@ copper-hopper@|铜漏斗* @@ -9033,9 +9033,9 @@ ping-wheel@ dark-waters@ sneak-tweak@ -serene-seasons-fix|静谧四季修复*¨@sereneseasonsfix|静谧四季修复 (Serene Seasons Fix) - 已失效 +serene-seasons-fix@|静谧四季修复* cross-dim-commands|跨维度指令 (Cross-Dim Commands) -temporary-spawners +temporary-spawners@ slyde@ phonos@ spelunkery@ @@ -9049,8 +9049,8 @@ corail-backpack chomper flamelib -better-planting -superfancyclouds-refabricated@|超级华丽云:重筑版 (SuperFancyClouds: Rearchitected) +better-planting@ +superfancyclouds-refabricated@|超级华丽云:重织版 (SuperFancyClouds: Refabricated) pet-tracker @splashfox|狐狸加载界面 (SplashFox) @@ -9065,7 +9065,7 @@ graphutil@ comfy-sky@|舒适空岛* entropy@ -ngv-forge¨ngv-fabric +ngv-forge@ngv¨ngv-fabric world-of-wonder@ full-brightness-toggle@ @@ -9086,7 +9086,7 @@ coherent-villages terrains @bugtorch better-tridents@|更好的三叉戟* -undead-unleashed +undead-unleashed@ missing-wilds@|遗落荒野*¨missing-wilds-forge|遗落荒野 (Missing Wilds) stellar-fluid-conduits|恒星流体导管* @@ -9102,7 +9102,7 @@ hopperplus-instant-hoppers sbm-merpig lwjgl3ify@ - +mine-treasure creates-delight@|机械动力乐事 (Create's Delight) mo-villager@mo-villagers|更多村民 (Mo' Villager) @@ -9155,7 +9155,7 @@ donthurtthatboat@ @horsebuff enchanting-table-descriptions@|附魔台描述* harvest-with-ease@ -trade-refresh|交易刷新* +trade-refresh@|交易刷新* create-molten-geodes thats-a-lot-of-items nether-skeletons@netherskeletons @@ -9168,7 +9168,7 @@ magmafurnace-weapons-bosses-and-more monstersregulararmy why-does-my-food-taste-like-that iron-generators -elytra-swapper-forge|鞘翅切换 (Elytra Swapper) +elytra-swapper-forge|鞘翅切换* ftb-team-islands-forge market-crates|集市板条箱* @@ -9176,7 +9176,7 @@ industrial-renewal-port|工业复兴移植版* tfcm|群峦合金非官方版 (TFC Metallum U) tnt-time-mod drunken-mug -cartoonish-horde-library +cartoonish-horde-library@villainous-horde-manager limited-lives|有限生命*¨limited-lives-fabric|有限生命 (Limited Lives) universe-box@ invtweaks-emu-for-ipn@ @@ -9188,11 +9188,11 @@ create-compact-exp|机械动力:压缩经验* heat-and-processing skytheorylib ad-astra-giselle-addon@ -shift-scroll-fix +shift-scroll-fix@ gim|原神农业 (Genshin Impact Cropper) oxygen-daily-rewards -structure-compass|结构指南针* -cold-snap-horde +structure-compass@|结构指南针* +cold-snap-horde@ tis-the-season @baubles-expanded @@ -9248,7 +9248,7 @@ animalnet biome-backlog doespotatotick|远物停载:土豆版 (Does Potato Tick?)¨@does-potato-tick|远物停载:土豆版 (Does Potato Tick?) - 已失效 -welcome-to-meadow@lets-do-meadow|青青草甸 (Let's do Cheese! / Meadow)¨@lets-do-cheese|青青草甸 (Let's do Cheese! / Meadow) - 已失效 +welcome-to-meadow@lets-do-meadow|青青草甸 ([Let's do] Meadow / Cheese!)¨@lets-do-cheese|青青草甸 ([Let's do] Meadow / Cheese!) - 已失效 universal-enchants-forge@universal-enchants|通用附魔 (Universal Enchants)¨universal-enchants-fabric|通用附魔 (Universal Enchants) aa-farmer-compat @@ -9267,11 +9267,11 @@ clayworks@ miniscaled@ lost-cities-potato-edition|失落的城市:土豆版 (Lost Cities: Potato Edition) endless-noend -matmos-2 +matmos-2@matmos-ambient-sound tfc-item-barrels -villagersplus-forge¨villagersplus-fabric -data-criteria +villagersplus-forge@villagersplus¨villagersplus-fabric +data-criteria@ japanese-emoji-commands attack-on-christmas @@ -9281,7 +9281,7 @@ the-printer@ flying-cullers@ timeless-power@ create-deco-fabric-fixed -calibrated +calibrated@ frozen-memories atlantis-api¨@atlantis-api|Atlantis API - 已失效 nutritional-balance @@ -9310,7 +9310,7 @@ easy-experience full-turtle-armor@ -tokusatsu-hero-completion-plan@tokusatsu-hero-instrumentality-project|特摄英雄补完计划 (Tokusatsu Hero Instrumentality Project) +thip@tokusatsu-hero-instrumentality-project|特摄英雄补完计划 (Tokusatsu Hero Instrumentality Project) spyglass-astronomy@ @@ -9355,7 +9355,7 @@ bejs screenjs pioneer @sha1runtime -night-vision-enchantment-forge +night-vision-enchantment-forge@night-vision-enchantment galactic-computers@ mini-cites ad-tetra@ad-tetra! @@ -9373,7 +9373,7 @@ reusable-rockets@ item-info florist omni -frostrealm +frostrealm@ nameless-trinkets@ unsafe-spawns @@ -9381,7 +9381,7 @@ bubbles@pop nan-health-fixer@|假死修复:土豆版 (Potato NaN Health Fix) @voxelmap-updated blood-and-stuff@ -yungs-better-nether-fortresses@|YUNG的下界要塞优化 (YUNG's Better Nether Fortresses)¨yungs-better-nether-fortresses-fabric|YUNG的下界要塞优化 (YUNG's Better Nether Fortresses)¨yungs-better-nether-fortresses-neoforge|YUNG的下界要塞优化 (YUNG's Better Nether Fortresses) +yungs-better-nether-fortresses@|YUNG 的下界要塞优化 (YUNG's Better Nether Fortresses)¨yungs-better-nether-fortresses-fabric|YUNG 的下界要塞优化 (YUNG's Better Nether Fortresses)¨yungs-better-nether-fortresses-neoforge|YUNG 的下界要塞优化 (YUNG's Better Nether Fortresses) squidless enders-delight@|末影乐事 (Ender's Delight) @@ -9410,7 +9410,7 @@ shield-expansion@ barbarians|野蛮人* minecraft-startup-sound-aka-mojaaaaaang realistic-terrain-generation-unofficial|真实地形生成非官方版* -combat-enchantments@combatenchantments +combat-enchantments@combatenchantments|战斗附魔* fastentitytransfer@ chalice @@ -9440,12 +9440,12 @@ falsetweaks@ metaworlds-unofficial@metaworldsunofficial|元世界:非官方版* tslateffectslib@ tslatentitystatus-tes@tslatentitystatus -@invisible-frames-mod +rounds-invisible-frames@invisible-frames-mod elytra-capes@elytracapes config-swapper viafabricplus@ menu-companions -entity-render-distance-extender +entity-render-distance-extender@erde rpg-stamina|RPG 耐力 (RPG Stamina) mjs-animations deepslate-generator @@ -9496,12 +9496,12 @@ scotts-tweaks kore-sample-l book-wyrms@ nakaroths-herdcraft-l|群兽行为L* -mystical-oak-tree +mystical-oak-tree@ ancient-trees-l -butchercraft|屠宰工艺* +butchercraft@|屠宰工艺* zombie-invasion@ clay-soldiers-reborn -geore +geore@ mt-claysoldiers2-forge safekeystring|安全键位名 (SafeKeyString) loadingtips @@ -9526,7 +9526,7 @@ steamagerevolution|蒸汽时代革命 (SteamAgeRevolution) brewcraft zombie-awareness-freeze-fix|僵尸意识冻结修复* -integrated-stronghold@ +integrated-stronghold¨@integrated-stronghold|Integrated Stronghold - 未及时更新 grass-overhaul@ @purpurclient alkimicraft-part-i-project-oasis @@ -9569,14 +9569,14 @@ skybox-loader-forge easy-feed@mcf-easyfeed|简单饲养 (Easy feeding) create-buffers-beams small-boats-mod -reimplemented +redev@ wands-and-wizardry -geore-nouveau +geore-nouveau@ crimsonsteves-more-mobs the-ulterlands mt-lotr asjcore@ -bane-tooltips-mod|克星信息显示* +bane-tooltips|克星信息显示* lava-crystal literal-sky-block @literal-sky-block-fabric @@ -9587,7 +9587,7 @@ mob-stacker-farming@ lightspeedmod@lightspeed coffee-delight@|咖啡乐事* simple-sculk-dimension-deeper-dark -no-more-inifinite-water +no-more-inifinite-water@finite-water choo-choo-craft eco-mobs classic-hostiles @@ -9632,7 +9632,7 @@ more-ladders@ reblured@ spellbound-weapons -sword-parry +sword-parry@ @create-ruins @minegpt respiteful@|闲暇风味* @@ -9643,22 +9643,22 @@ kelvins-better-player-animations tfc-casting-with-channels tfc-create max-health-fixer -gjeb +gjeb@ bundled-delight@ -@travellerstoasts +travellerstoasts@ @ijo-pona-poki sk1llful@|身怀绝技 (Skillful) salt@ -monobank -sooty-chimneys +monobank@ +sooty-chimneys@ areas-undefined trivialthoughts|魔金琐想 (TrivialThoughts) tfc-delight myssyheads-sculk-adventures waystone-towers@ -smitheesfoundry@ +smitheesfoundry|Smithee's Foundry - 已失效¨@smitheesfoundry beach-slimes rideable-ravagers@|可骑乘的劫掠兽* @@ -9670,7 +9670,7 @@ skills-v|技能:香草 (Skills: Vanilla) flower-foxes pack-it-up@ mine-trading-cards-open-source-edition@mine-trading-cards -chainsaw-man-csm +chainsaw-man-csm|电锯人 (Chainsaw Man) superior-shields@ alternative-world-save-location@ skills-firearms|技能:枪械 (Skills: Firearms) @@ -9695,8 +9695,8 @@ cursed-difficulty@ algane -dawn-of-time@dawn-of-time-builder-edition -kubejs-projecte +dawn-of-time@ +kubejs-projecte@ biome-backport infernal-geode geode-o-plenty @@ -9801,7 +9801,7 @@ create-encased@|机械动力:封装工艺* @treeplacer death-knell@ moreiotas@ -numismatic-overhaul@¨numismatic-overhaul-reforged +numismatic-overhaul@|货币学拓展 (Numismatic Overhaul/Numismatic Overhaul: Reforged(forge))¨numismatic-overhaul-reforged|货币学拓展 (Numismatic Overhaul/Numismatic Overhaul: Reforged(forge)) hexbound-fork@|咒法组构学 (Hexbound)¨@hexbound|咒法组构学 (Hexbound) - 原-已失效 vanity-druid-pack vanity-farcrs-colossal-weapons @@ -9859,7 +9859,7 @@ item-obliterator@ you-shall-not-spawn@ hell-on-earth zombie-friends@ - +@stone-craft|石头工艺 (Stone Craft) trade-cycling@ illager-revolution@ extra-trades @@ -9981,13 +9981,13 @@ tskimi-seirans-fruit-wine@tskimiseirans-fruit-wine|月见清兰的果酒 (Tskimi graduated-cylinders@|刻度量筒* fps-monitor -athena@athena-ctm +athena¨@athena-mod|Athena / Stitch - 已失效 calm-mornings@ fog-looks-good-now@ guarding@ cats-expanded@ modular-bosses -experience-obelisk@ +experience-obelisk@cognition here-be-no-dragons gnumus-settlement fading-night-vision @@ -10003,13 +10003,13 @@ mannequins simple-mango@ betterdrawhoveringtext|更好的工具信息显示 (betterDrawHoveringText) -badges-by-harmex +badges-by-harmex@cobblebadges2 terrarian-arsenal zombie-veterinarian|僵尸科兽医* just-enough-sculk-sensor hearth-and-home better-leveling|更好的升级* - +applied-mana|应用魔法* boundless-flux @axolotlclient @@ -10081,7 +10081,7 @@ mob-lassos@ mutant-monsters@ @physics-toys -tarot-cards@ +tarot-cards@|塔罗牌* chatmove@ @forgedloaderapi @@ -10282,7 +10282,7 @@ primal-stage@primalstage spontaneous-replace@|自然更替 (Spontaneous-Replace) pixel-loader|像素加载器 (pixel loader) kubejs-ars-nouveau@ -cataclysmweaponery +cataclysmweaponery@cataclysm-weaponery twilight-paintings twilight-origins cpmoscc@|自定义玩家模型OSC兼容 (Customizable Player Models OSC Compat) @@ -10362,7 +10362,7 @@ patpatpat@¨patpatpat-fabric wildberries -kubejs-powah +kubejs-powah@ @hexkinetics|咒法动力学 (HexKinetics) sand_box@sandbox @@ -10385,7 +10385,7 @@ quickcure@quick-cure¨quickcure-fabric@quick-cure-fabric spoiled@ entity-specific-damage-invulnerability@ hungryanimals -opolis-utilities +opolis-utilities@ better-breeding frostiful@|霜冻* camo-creepers@ @@ -10453,7 +10453,7 @@ easydropper|轻松投掷 (Easy Dropper) varied-adventure kubejs-delight block-physics-overhaul -searchlight-forge|探照灯 (Searchlight(& Wall Lights))¨searchlight|探照灯 (Searchlight(& Wall Lights)) +searchlight-forge|探照灯 (Searchlight (& Wall Lights))¨searchlight|探照灯 (Searchlight (& Wall Lights)) ancient-manuscripts@ gazebos@ scoreboard-get-time|记分板获取时间 (scoreboard get time) @@ -10479,7 +10479,7 @@ bettergolem trampledisabler-fabric spotting|索敌* advanced-tfc-tech-unofficial@|进阶群峦科技非官方版 (Advanced TFC Tech Unofficial) - +pineapple-tags|凤梨标签* impostore effishiency@ glumbis @@ -10499,7 +10499,7 @@ arboria mekanism-evolution chiseledadditions - +@tinkers_ingenuity|工匠匠心 (Tinkers Ingenuity) gregtech-4|格雷科技4移植版 (GregTech 4 Rewritten) lacrimis productive-villagers¨@productive-villagers|Productive Villagers - 已失效 @@ -10510,7 +10510,7 @@ crafttweaker-sixik-utils-ftb-quest wares@ difficult-spawners recoil-shotgun@ -stratum +stratum|Stratum - 无法¨@stratum observed@ ex-sartagine-requiem@ giant-pacman-1-16-5 @@ -10573,7 +10573,7 @@ pokefood armor-souls-reforged aether-redux@the-aether-redux|天境:新生代 (The Aether: Redux) -@banner + staaaaaaaaaaaack@ thanks-i-have-rei@tihr @@ -10602,7 +10602,7 @@ bocchud@|孤独HUD (BoccHUD / MiniHUD-Forge) arrowcollector@arrow-collector|你返箭吗 (Arrow Collector) saplingslayer|鸽子衔枝 (SaplingSlayer) quality-quails -hydration +hydration@ halohud@ resourcify@ @@ -10636,7 +10636,7 @@ server-utilities su-addon-silk-spawners su-addon-quests harder-hardcore -argentinasdelight@argentinas-delight|阿根廷乐事 (Argentina's delight) +argentinasdelight@argentinas-delight|阿根廷乐事 (Argentina's Delight) colonies everythingcopper@|万物皆铜 (Everything is Copper) passive-endermen@|被动型末影人* @@ -10705,7 +10705,7 @@ enhancedsnowman new-world-height-and-depth @inertiaanticheat -scroll-of-harvests +scroll-of-harvests@ @scripts-chunk-loaders clean-tooltips@ @@ -10840,7 +10840,7 @@ growmeal@ moa-decor-art -easier-armor-trim-duplication +easier-armor-trim-duplication@ steppy@ new-thin-air@thin-air|稀薄的空气 (Thin Air) the-armory @@ -10848,7 +10848,7 @@ xannoszs-better-minecarts|Xannosz的更好的矿车 (Xannosz's Better Minecarts) meliusvanish@vanish tooltip-tool-tips@ sanity-descent-into-madness@ -@no-darkness-effect +no-darkness-effect-mod@no-darkness-effect|无黑暗效果 (No darkness effect) @seamless-loading-screen-renewed armorskin@ @translucent-glass @@ -10893,7 +10893,7 @@ do-api@ grabby-mobs@ pandas-falling-trees@ iron-ender-chests@ -delightful-creators-fabric@|动力乐事 (Delightful Creators) +delightful-creators-forge@delightful-creators|动力乐事 (Delightful Creators)¨delightful-creators-fabric|动力乐事 (Delightful Creators) deathbutthree@death-but-three|逝不过三 (Death But Three) comparties|组队兼容* @@ -10906,7 +10906,7 @@ light-the-night tweakerge@ terrafirmaships@ better-ocelots -actually-useful-smithing-table +actually-useful-smithing-table@ denyblocks sculk-horn@-sculk-horn ydms-red-panda@ @@ -11052,7 +11052,7 @@ toomuchrain@ thirst-was-remade|口渴-重制*¨@thirst-was-remade|口渴-重制 (Thirst Was Remade) - 已失效 ies-tweaks-fabric@ brown-mooshrooms -@whetstones +whetstones@ @better-log4j-config @harvest wooden-hoppers @@ -11143,7 +11143,7 @@ split-shulker-boxes@ tinkers-leveling curios-compat @cloudbackup|云备份 (CloudBackup) -@serverpingerfixer +serverpingerfixer@ @world-days such-a-delight create-compressed@ @@ -11168,7 +11168,7 @@ shulker-meat accurate-block-placement-reborn@ favorite-items -@smart-villagers-follow-emeralds|见钱眼开 (Smart Villagers Follow Emeralds) +smart-villagers-follow-emeralds@|见钱眼开* @villager-pickup step-it-up@stepitup @@ -11215,7 +11215,7 @@ netherless-quartz @reforged-fabric-api caffeinated@ buildcraft-rf -gilded-ingot +gilded-ingot@ chatpatches@|聊天补丁 (Chat Patches) burning-furnace@ shadowizardlib@ @@ -11242,7 +11242,7 @@ novam-terram-continuation @jade-feet|玉足 (Jade Feet) cutting-edge -xp-from-harvest-forked +xp-from-harvest-reworked@ papi-project eventhorizon|事件视界 (EventHorizon) mob-options @@ -11252,7 +11252,7 @@ mod-sets@|模组集* hold-that-chunk@ dark-matter@ @steal_dogs|偷狗 (Steal Dogs) -@faster-random +faster-random@ simply-steel-continued@ @@ -11274,7 +11274,7 @@ recipe-book-delight@|配方书乐事* chest-hoppers|箱子漏斗* @gravy-delight ubes-delight@|香芋乐事 (Ube's Delight) -torcherino-unofficial|加速火把非官方版 (Torcherino(Unofficial)) +torcherino-unofficial@torcherino(unofficial)|加速火把非官方版 (Torcherino(Unofficial)) more-bows-and-arrows@ xp-shop acclaimed-origins@ @@ -11307,7 +11307,7 @@ jagms-kiwis@ improved-trident@ mystic-bows forgiving-world -@skillcloaks +skill-cloaks@skillcloaks swordinthestone@ lazy-ae2-patch|懒人AE2补丁 (Lazy AE2 Patch) @@ -11322,7 +11322,7 @@ rei-custom-command|REI自定义命令 (REI Custom Command) copperative@|铜心协力* growable-storage-cells growable-cells-2 -cinchcraft +cinchcraft@ day-counter@ag-day-counter makecold|冷境 (MakeCold) @@ -11333,8 +11333,8 @@ unbreakable-enchantment-forge@unbreakable-enchantment immersive-portal-gun@portal-gun|沉浸式传送枪* tac-craft-tactical-armor-pack @flexible-arms|灵巧双手 (Flexible Arms) -boat-tweaks|船只调整* -new-effect-descriptions@effect-descriptions +boat-tweaks@|船只调整* +new-effect-descriptions@effect-insights diagonal-windows@ illager-invasion@|灾厄侵扰* @@ -11362,13 +11362,13 @@ cute-kiwi-birds-new-animal moremobheads@ ouchies@ @traditional-asphalt -@thaumic_boots +thaumic-boots@thaumic_boots carpeted-stairs@ -just-enough-archaeology +just-enough-archaeology@ bedrock_world world-preview@|世界预览* - +not-enough-trinket-slots-nets the-five-nights-at-freddys-mod@the-five-nights-at-freddys-mod#all-versions|玩具熊的五夜后宫 (The Five Nights at Freddy's Mod) easylan@|自定义LAN局域网联机服务器 (EasyLAN) pelagic-prehistory @@ -11426,7 +11426,7 @@ gigeresque lookaround projectileimmunityfix @betterzoom -@player-visibility +player-visibility@ bag-of-holding-forge@bag-of-holding @@ -11438,9 +11438,9 @@ godly-vampirism itemlocks@ pick-block-pro@|选择方块专业版* skin-and-bones -@pet-armor +pet-armor@ maxs-deco|Max的装饰 (Max's deco) -more-nugget@|更多矿物粒*¨more-nugget-fabric|更多矿物粒 (More Nugget) - Farbic +more-nugget@|更多矿物粒*¨more-nugget-fabric|更多矿物粒 (More Nugget) - Farbic,已失效 dual-swords@ organizable-play-screens@ labelling-containers@ @@ -11455,7 +11455,7 @@ ilmusu-enchantments@ilmusuenchantments desired-servers@ attributizer@ -reactive@ +reactive@|反应炼金术 (Reactive Alchemy) simple-deepslate minunrpg more-sheep-wools @@ -11473,7 +11473,7 @@ green-thing-mod lmft@ peripheralium@ resource-switcher@|资源切换器 (ResourceSwitcher) -serializationisbad +serializationisbad@ clutter@ no-enchantment-cap-1-12-2-backport survivingtheaftermath|劫后余生 (Surviving The Aftermath) @@ -11645,7 +11645,7 @@ simply-skills@ cctv-craft -redeco@ +redeco@|重铸家具 (Re:Deco) pocket-storage animated-player-mod|动画化玩家* its-thirst@ @@ -11753,7 +11753,7 @@ dangerous-oceans @mc-249136-fix|MC-249136 修复 (MC-249136 Fix) @featherfallflying @lidar -anvil-never-too-expensive +anvil-never-too-expensive@ante @crossbow|弩 (Crossbow) @place-item @@ -11847,7 +11847,7 @@ simple-enchantments simple-weapons-for-better-combat@ wither-drops-netherite-templates @spineless -immersive-melodies@ +immersive-melodies@|沉浸式奏乐* theobsidianboat dragon-survival-compatibility@|龙之生存:兼容* bettercookedaxolotls @@ -11868,7 +11868,7 @@ ootn @signal i-see-what-you-did-there@ hit-streak@ -new-slab-variants +new-slab-variants@ blue-archive-banner-and-shield-patterns mimic-mod@mimic bocchium @@ -11939,7 +11939,7 @@ prehistoric-nature-ordovician-dimension fadeless@ create-tweaked-controllers@ -@auto-elytra +auto-elytra@ villager-names@villager-names-serilum bedrockwaters@ @antishulkerdupe @@ -12049,7 +12049,7 @@ styled-chat@ infiniteinvo playerex-reimagine startup-timer -cc-mb@|纸带八音盒 +cc-mb@|纸带八音盒 (Music Box) oryls-adventures elden-craft ruok@|钌 (RuOK) @@ -12177,7 +12177,7 @@ yungs-better-end-island@|YUNG的末地岛屿优化 (YUNG's Better End Island)¨y bellsandwhistles@ daniinatorul uuid-offline -vanilla-cubed@ +vanilla-cubed@|原版立方* @@ -12221,7 +12221,7 @@ akkamaddis-hadite-coal|Hadite Coal - 新 akkamaddis-arsenic-and-lace|Arsenic and Lace - 新 -xiyus-exchanted-book@xiyus-enchanted-book|饩雨的附魔书 (xiyu's Exchanted book) +xiyus-exchanted-book@xiyus-enchanted-book|饩雨的附魔书 (Xiyu's Enchanted Book) la-abuelita rotted-forge@rotted rustic-furniture-compat @@ -12245,8 +12245,8 @@ enchantsmith snifferent jei-recipe-history@ @yeet -macaws-paths-and-pavings@ -@chiseled-bookshelf-visualizer +macaws-paths-and-pavings@|Macaw的小径和路面 (Macaw's Paths and Pavings) +chiseled-bookshelf-visualizer@ @elytra-skins mcv-biome-makeover-lieonlion@ more-chest-variants-lieonlion@ @@ -12254,9 +12254,9 @@ more-crafting-tables-mct@more-crafting-tables-lieonlion @cobblemon-quests akkamaddis-classical-alchemy|Classical Alchemy - 新 @big-pony|彩虹大马 (Big Pony) - +qraftyfied @theft -akkamaddis-golden-glitter|Golden Glitter - 新 +akkamaddis-golden-glitter@ @obsidianbucket enhanced-annihilation-planes akkamaddis-simple-cthon@ @@ -12316,7 +12316,7 @@ dragon-nest-forge-fabric geode-creatures haraldsbetterstructures squake -ae2-uel-extended|应用能源2非官方拓展 (AE2 Unofficial Extended) +ae2-uel-extended@|应用能源2非官方拓展 (AE2 Unofficial Extended) terraria-calamity-mod|泰拉瑞亚灾厄 (Terraria Calamity) darkrtp-rtp-random-teleport@darkrtp cute-bears @@ -12331,7 +12331,7 @@ advancement-based-spawning@ heroic-armory-legacy yungs-menu-tweaks@|YUNG的菜单调整 (YUNG's Menu Tweaks)¨yungs-menu-tweaks-fabric|YUNG的菜单调整 (YUNG's Menu Tweaks)¨yungs-menu-tweaks-neoforge|YUNG的菜单调整 (YUNG's Menu Tweaks) -entity-distance-1-12-2|实体距离 (Entity Distance) +entity-distance-1-12-2@entity-distance-1.12.2|实体距离 (Entity Distance) coolesthardcore@coolest-hardcore taxcastlepillager pesky-pirates@ @@ -12392,7 +12392,7 @@ swutms-extra-dungeons@ swutms-extra-desert@ @refill-chests -@cookeymod#all-versions +@cookeymod @useful-brush @horse-spawn tw-resurrection @@ -12412,7 +12412,7 @@ halloween-luckyblocks spooky-halloween isle-of-berk-variant-loader@ spooky-arms - +qraftys-end-villages bed-is-not-too-far-away@ @enderman-tweaks @@ -12467,7 +12467,7 @@ enchantgiver diabolical-delights@ ore-growth@ @real-arrow-tip - +dnt-ancient-city-overhaul cropariaplus soularia@ @@ -12481,8 +12481,8 @@ weapons-of-miracles-epicfight@weapons-of-miracles|奇迹武器 (Weapons of Mirac @hsrpaths -mcdar|我的世界:地下城法器 (MC Dungeons Artifacts) - +mcdar@|我的世界:地下城法器 (MC Dungeons Artifacts) +dnt-stronghold-overhaul dnt-swamp-hut-overhaul jaopcagt@ smidgeon-o-bliss @@ -12529,7 +12529,7 @@ uniquedungeons cobblemon-info@ enderman-overhaul@|末影人革新* cloak-dagger@cloak-and-dagger -oworld2create|别创建新世界! (0World2Create)¨oworld2create-refoxed|别创建新世界! (0World2Create) +oworld2create|别创建新世界! (OWorld2Create)¨oworld2create-refoxed|别创建新世界! (OWorld2Create) dungeons-in-dimensions infernal-bugz @cobblemon-gts @@ -12542,7 +12542,7 @@ the-monkey-bomb@ @cobblemon-hunt ancient-animals beavery@ - +qraftys-halloween-villages vemerioraptor technique-to-kill-thrasher|毙鲨技 (technique to kill thrasher) the-open-sauce-toast-killer@ @@ -12552,7 +12552,7 @@ better-wake-up-position@|更好的起床位置 (Better Wake-up Position) railroad-blocks-forge@railroad-blocks¨railroad-blocks fossilmon@ -opack2reload|资源包惰性重载/抑验怠载 (0Pack2Reload) +opack2reload|资源包惰性重载/抑验怠载 (0Pack2Reload/OPack2Reload) wits@ @motleys-music-discs @@ -12600,7 +12600,7 @@ dynamic-village@ jammies@ dj-villager -ars-energistique +ars-energistique@ chatimages@chatimageN|聊天图片 (ChatImages) create-more-drill-heads@|机械动力:更多钻头 (Create: More Drill Heads) create-liquid-fuel@|机械动力:液体燃料 (Create: Liquid Fuel) @@ -12678,7 +12678,7 @@ cheat-command pumpkinpal johnnys-biological-notes@johnnys-biological-notes-1.0-beta|Johnny的生物笔记 (Johnny's biological notes) -@autodrop +autodrop@ mutation-rerun @compactspawners @@ -12699,7 +12699,7 @@ boliviamod leawind-third-person@|Leawind的第三人称 (Leawind's Third Person) fabled-weaponry lazurite@ -lets-do-nethervinery@ +lets-do-nethervinery@|下界葡园 (Let's Do: NetherVinery) shinnyladders|发光梯子/洇荧登纵 (Shinny Ladders) macaws-holidays@ unions-upgrade-scrolls @@ -12820,7 +12820,7 @@ more-slabs-stairs-and-walls@ caves-canyons dragon-mounts-legacy-continued@|龙骑士重制版延续* chloride-mod@chloride -aggro-indicator +aggro-indicator@aggroindicator haveasoltime@ tothestars@ translater@ @@ -12884,7 +12884,7 @@ mob-pets|驯兽师* ultimate-compression@ farming-in-rain -bosses-of-mass-destruction-forge@ +bosses-of-mass-destruction-forge@|祸乱鬼魅 [FORGE] (Bosses of Mass Destruction [FORGE]) armor-set-bonuses@¨armor-set-bonuses-fabric nemos-creatures@ immersive-jungles@ @@ -12930,13 +12930,13 @@ mrcrayfishs-gun-mod-unofficial|MrCrayfish的枪:非官方版 (MrCrayfish's Gun 4913730@ram-compat @wcsm|中国式学校 (Chinese School Mod) zombies-reworked-forge@zombies-reworked -legendary-monsters@ +legendary-monsters@|传奇怪物* catontoasts-mobs combatify@ go-fish-updated|一起钓鱼吧!(更新版) (Go Fish (updated)) what-are-they-up-to@ -@fumo +fumofumo|Fumo - 仅 16.0.0+¨@fumo @elytraplus mcaic@ @zombification @@ -12985,7 +12985,7 @@ k-clear-lag @hexdim|咒法维度 (Hexxy Dimensions) simple-backups@|简单备份* sky-guis@ -botaniverse@ +botaniverse@|寰宇植物学* betterer-p2p recall-staffs @@ -12994,8 +12994,8 @@ default-world-generator-ssp homyatos-explosives infchest@|无限箱子 (Inf Chest) @squashed -unremovableeffects -traders-in-disguise-the-illager-assassin-project +unremovableeffects@unremovable-effects +traders-in-disguise-the-illager-assassin-project@ cherished-items verdantvibes@|绿意盎然 (VerdantVibes) crafting-manipulator @@ -13007,7 +13007,7 @@ disabletools@disable-tools|禁用工具 (Disable Tools) @humility-se push-everything-away@|推开一切* -create-power-loader@|机械动力:动力加载器 (Create: Power Loader)¨create-power-loader-fabric|机械动力:动力加载器 (Create: Power Loader) +create-power-loader@|机械动力:动力加载器 (Create: Power Loader)¨create-power-loader-fabric@|机械动力:动力加载器 (Create: Power Loader) epiceldenringweaponsarmorandmobs restart-server@ @@ -13077,7 +13077,7 @@ rpg-parties@ astikor-carts-tfc@tfc-astikor-carts nanexs-mobs no-feather-trample@ -embers-extended-life|余烬非官方延续版 (Embers Unofficial Extended Life) +embers-extended-life@embers-unofficial-extended-life|余烬非官方延续版 (Embers Unofficial Extended Life) terrafirmajousting@ jujutsu-animation @@ -13115,7 +13115,7 @@ cpas@ awesome-magic@awesome_magic christmas-dweller create-curio-goggles@ -@cobblemon-loot-balls +@cobbleloots dontusecommand@|请别用指令 (DontUseCommand) beans-backpacks@ @@ -13199,15 +13199,15 @@ pescatore@ apocalypse-now@ cenozoic-nightmare-the-horror-of-prehistory -quickbackupmulti@|多槽位备份 (QuickBackupMulti-Fabric) +quickbackupmulti@|多槽位备份 (QuickBackupMulti) slashblade-japanese-addon-pack-reforked|拔刀剑日系附属重置版 (SlashBlade Japanese Addon Pack Reforked) @create-fabric-fixes crate-delight-forge@crate-delight|装箱乐事 (Crate Delight)¨crate-delight-fabric|装箱乐事 (Crate Delight) -cultural-creators-fabric-create-and-cultural +cultural-creators-forge-create-and-cultural@cultural-crators¨cultural-creators-fabric-create-and-cultural shuffle@ probejs-legacy -create-destroy@destroy +create-destroy@destroy|坍毁化合 (Destroy) flourish@ pitch-dark@ survival-debug-stick @@ -13256,7 +13256,7 @@ bagus-cosmetic@ floatingitems ars-ocultas@ tetracelium@ - +fugue@ create-food@ @@ -13320,13 +13320,13 @@ fleshz@ depths-of-madness tree-lib @moehreag-legacy-lwjgl3 -real-camera@ +real-camera@|真实相机* unofficial-mod-name-tooltip-backport|模组名称显示:非官方版 (Mod Name Tooltip Unofficial) custom-health-bar@ lotrimprovements|魔戒改进 (LOTR Improvements) mousekeyinventoryfix@ visible-offhand@|可见副手* -phosphorcrashfix +phosphorcrashfix@phosphor-crash-fix sound-physics-remixin|籁* hextended-staves@ aquaculture-delight@|水产乐事* @@ -13367,7 +13367,7 @@ magical-jewelry epicsoulsbosses epic-puss-in-boots soul-like-npc -epic-empires@ +epic-empires¨@epic-empires|Epic Empires - 已失效 @@ -13395,7 +13395,7 @@ zombie-more@zombies-more gunscraft|枪械工艺 (Guns Craft) audino@ tabbychat-unofficial@ -heracles@ +odyssey-quests@ flapjacks-economy @onion-onion relics-vivid-light-addon@ @@ -13416,7 +13416,7 @@ everyxdance@|一起跳舞! (EveryXDance) applied-flux|应用通量* tics-delight@tinkers-delight|匠魂乐事 (Tinkers' Delight) -unnamed-desert-structures@ +unnamed-desert-structures@unnamed-desert bloomingnature@lets-do-bloomingnature|绽芳自然 (Let's Do: BloomingNature) waila-inhibitors@ thingamabobs-and-doohickeys @@ -13430,7 +13430,7 @@ macos-input-fixes@|MacOS 输入修复 (MacOS Input Fixes) @astronomical @millet-delight|小米乐事 (Millet Delight) -ned@|猫艺:终焉 (Neko End) +telos@|终焉* magical-torches@ dynamical-compass@ better-questing-lord-of-the-rings-addon|更好的任务-魔戒扩展 (Better Questing - Lord of the Rings addon) @@ -13466,7 +13466,7 @@ narrus-yeetus@ login-shield blockinteraction|禁止右键方块 (BlockInteractionMod) linkart-fabric -leftist-potions@ +leftist-potions¨@leftist-potions|Leftist Potions - 已失效 more-delight-fabric@more-delight|多趣乐事 (More Delight)¨more-delight-forge|多趣乐事 (More Delight) mceconomy3 @antique-atlas-4 @@ -13530,7 +13530,7 @@ timeless-heroes@superhero sky-origins@sky glitchcore@ light-level-overlay-reloaded -eirin-yagokoros-pharmacy@eirin-yagokoro-pharmacy|八意永琳的药房 (Eirin Yagokoro's Pharmacy) +eirin-yagokoros-pharmacy@eirin-yagokoro-pharmacy|蓬莱药局 (Eirin Yagokoro's Pharmacy) tfc-ore-excavation tac-interact-key|TAC交互优化 (TAC Interact Key) @@ -13567,7 +13567,7 @@ hexagonal-heresy hardcore-spelunking dark-menagerie icarus-mod -epic-knightsnmages-fabric@epic-knightsnmages +ars-knightsnmages@epic-knightsnmages¨epic-knightsnmages-fabric hotai|绷带* zeta@ taxtowncitizen @@ -13624,7 +13624,7 @@ extended-potions dimension-inventorys|维度物品栏* dimension-rules|维度规则* erroring-entity-remover-reforged@eer-reforged -@vs-tournament +@vs-tournament|瓦尔基里天空:竞赛 (VS: Tournament Continued) fabricated-exchange@ more-variants-and-biomes badoptimizations@ @@ -13651,7 +13651,7 @@ tfc-weather conrad@ nendermoo -forgedcarpet +forgedcarpet@ framed gear-core@ hideasterisk@hidemodded|隐藏“已修改” (HideModded) @@ -13687,7 +13687,7 @@ missions@ votemission@ immersive-vehicles-automobilwerke-schwikau-veb cheesemaking -cardiac +cardiac@ materialtweaker@ whirlwind@ warden-tools@ @@ -13788,14 +13788,14 @@ the-dawn-era-delight@|黎明纪元乐事* dynamic-craft startinginventory wireless-speakers|无线扬声器* - +sagerfx@|SA特效 (SagerFX) jewelrycraft-2|珠宝工艺2* fallflyinglib enchantable-spike xaeros-map-chest-tracker-integration@ -warden-horn +warden-horn@ too-many-efficiency-losses srp-addon-cotesia-glomerata@|SRP:寄生蜂 (Scape and Run Parasites Addon: Cotesia Glomerata) @@ -13888,7 +13888,7 @@ ad-astra-extra-additions@ infinite-fluid-bucket@|无限流体桶* @voxy -vmod|VMod - 已失效¨@vmod +vmod@ minestuck star-wars-planets-ad-astra@star-wars-planets @@ -13900,10 +13900,10 @@ cat_jam@ remastered-structure spacore fancyvideo-api -the-goatman-by-flamco4 +goatman-jar-v2-dweller@the-goatman-dwller mineral-overhaul-shellstone-tuff -clear-gpu-cache|清除GPU缓存* +clear-gpu-cache@|清除GPU缓存* stalker-creepers the-ghoul-remastered@ @transitmanager @@ -13911,7 +13911,7 @@ integrated-scripting@ @nyc-mtr @mtr-utility creativetabsearch@|创造物品栏检索 (CreativeTabSearch) -@moonbetween|在月亮之间 (Moon Between) +moonbetween@|在月亮之间 (Moon Between) tetrachord-lib@|四弦琴* screenshot-sharing|屏幕截图分享/移影睹展 (ScreenshotSharing) leaf-pile@|落叶堆* @@ -13932,7 +13932,7 @@ sky-lands@ vocalized @kaleidos|森罗万象 (Kaleidos) -@advanced-framework|高级框架 (Advanced Framework) + kubejs-irons-spells@isskjs revelationaryjs@ @@ -13958,7 +13958,7 @@ ash-of-sin-custom-entity-item|罪业余烬:自定义实体装备 (Ash Of Sin: ash-of-sin-eternal-entity|罪业余烬:永恒实体 (Ash Of Sin: Eternal Entity) fundamental@ -wendigo +wendigo-jar-dweller the-super-god-road@|超神之路 (TheSuperGodRoad) @backrooms @@ -13978,7 +13978,7 @@ composite-material|复合材料* timerecorder@|光阴:莉拉提娅 (TimeRecorder) bettersmithingtable@ trajectory-estimation@|弹道估计* - +@foxcore|狐狐核心 (FoxCore) @@ -14042,7 +14042,7 @@ melee-ex-machina-meexma@melee-ex-machina arc@ cores@ -@citymod|我的城市 (City Mod) +@citymod|我的城市:交通与生活 (CityMod:Traffic and life) too-hard-to-dig@|隐岩砥凿 (Too Hard to Dig) simplecobblestonegenerator@simple-cobble-gen natural-baby-animals @@ -14067,7 +14067,7 @@ tinyfoes pyradium anvilcraft@|铁砧工艺:重力科技 (AnvilCraft) tiny-mob-farm-remastered@|迷你刷怪场:重制* -create-aquatic-ambitions@ +create-aquatic-ambitions@|机械动力:瀚海雄心 (Create: Aquatic Ambitions) ffl-trinkets-compat kaleidios-guns mc-vr-playground @@ -14079,14 +14079,14 @@ geysers@|间歇泉* @lose-your-touch man-of-many-planes@ caliber-lib@ -@kriforfab +kriforfab@ multiscoreboard@ @minerally-me! @cover-them-in-armor -jerotes-village|Jerotes村庄* +jerotes-village|Jerotes村庄-二轮世界 (Jerotes Village - Second Round World) @a-cold-day-in-the-nether @translucencyfix -create-ponder@|机械动力: 思索* +create-ponder@|机械动力:思索* mopeds advanced-shulkerboxes@ @@ -14138,7 +14138,7 @@ mace-backport @steve-eat-stew imaginary kuvalich|赤毒玄骸 (KuvaLich) -ae2-import-export-card@ +ae2-import-export-card@|AE2输入输出卡 (AE2 Import Export Card) @magick-experience @repairable-disc-11 @@ -14183,8 +14183,8 @@ spit-splat@|唾溅兽* maced|重锤一击* nether-villager-trader @no-exp-enchanting-reimagined -cadmus@ -argonauts@ +odyssey-claims@ +odyssey-allies@ giacomos-travelogue giacomos-maps descension @@ -14217,7 +14217,7 @@ farlands-reborn@ momento@ -prometheus@ +odyssey-roles@ waystones-jawsawn-fork|传送石碑:Jawsawn版* nautilium-spirits@|鹦鹉螺重置版* @@ -14231,7 +14231,7 @@ bountiful-fares|丰饶食记* - 2.0.0 及以上¨@bountiful-fares|丰饶食记 ( @ancient-debris-tweak|远古残骸微调 (Ancient Debris Tweak) -crydolph +deer-jar-dweller anti-id-conflict|反ID冲突* confighelper|配置助手 (config helper) better-boat|更好的船* @@ -14255,7 +14255,7 @@ simple-dimensions|简单维度* portals-gui@|传送门GUI* interworlds-splashscreens@|进入维度加载界面* @creative-menu-tweaks -vampires-delight@ +vampires-delight@|血族乐事 (Vampire's Delight) thats-my-bed@ caligo@ @sharpenedswords @@ -14264,7 +14264,7 @@ donut-and-mochi ducky@ducky! lost-idols simple-trashcan@ -darwin-water-ambience +darwin-water-ambience@ decorative-wooden-lattices apotheotic-additions lithosphere @@ -14341,7 +14341,7 @@ giacomos-experience-seedling config-api¨@config-api|Config API - 缺少部分 nethers-delight-refabricated@|下界乐事:重织* @pumpkin-pie-delight -tipsylib@runiclib¨@tipsylib|TipsyLib - 已失效 +runiclib@ dragon-mounts-more-dragons@|龙骑士附属:更多龙 (Dragon Mounts: More Dragons) @@ -14672,14 +14672,14 @@ the-fletching-table-mod@ @create-militarized customemoji|自定义表情 (CustomEmoji) -sword-soaring|史诗战斗:御剑飞行 (Epic Fight - Sword Soaring) +sword-soaring|史诗战斗:御剑凌虚 (Epic Fight - Sword Soaring) bigger-ae2@ -scalar -trials-towers@trials-chambers-backport¨@trials-towers|Trials Chambers - 已失效 +scalar¨scalar-legacy|Scalar - Scala 2.11,一般使用此 +trials-chambers-backport@ blus-sign-pack-mts-iv alexs-mediumcore@ sherdsapi@ -@kontraption +@kontraption|精致机械 (Kontraption) musketeer-illager daker-acg|Daker ACG工艺 (DakerACG) @@ -14687,8 +14687,8 @@ bananarangs@ remorphed@ particle-core@ fermiumbooter@ -ytech -@bbrb +ytech@ +better-biome-reblend@bbrb mcterra@ siren-head-the-arrival classic-musical-discs@ @@ -14706,7 +14706,7 @@ paniclecraft cpmpvc@|自定义玩家模型Plasmo语音兼容 (Customizable Player Models Plasmo Voice compat) ydms-gunblades-mod@ydms-gunblades warp-pads@warppads -marbleds-arsenal@marbledsarsenal +marbleds-arsenal¨@marbledsarsenal|Marbled's Arsenal - 已失效 @inventory-tabs mob-spawning-fix|生物生成修复* lh-miracle-road|奇迹之路之魂系加点 (LH Miracle Road) @@ -14740,7 +14740,7 @@ seed-delight@|种子乐事* transmog@|幻化* nuclear-guitars cubic-racers - +lotr-ucp|魔戒非官方兼容性补丁 (LOTR UCP) delightful-sandwiches@ shader-fixer@|光影修复 (Shader fixer) resiever@ @@ -14834,7 +14834,7 @@ capejs@ advanced-machines-patcher|高级机械补丁* @immersive-tooltip|沉浸式提示框 (Immersive:Tooltip) -sound-controller|Sound Controller - 已弃用¨@sound-controller +sound-controller@ cagerium advanced-inventory-mod@ rolling-down-in-the-deep@ @@ -14842,7 +14842,7 @@ dwarven-realm|矮人诸国* renewed-eras-of-arda-addon|阿尔达纪元:复兴 (Eras of Arda: Renewed Addon (First and Second Age)) legendary-item@|魔戒传说物品* lotr-middleearth-tweaks|中洲微调 (MiddleEarth Tweaks) -thaumcraft-integration +thaumcraft-integration@ reach-display@ gravitation-suite-patcher anti-rat-dungeon @@ -14898,7 +14898,7 @@ froglins create-armor-trim slashblade-resharped@|拔刀剑:重锋 (SlashBlade: Resharped) spawn-mod@ -@smooth-swapping-1.20.4-port +@smooth-swapping-1.20.4-port|平滑转移 1.20.4 移植 (Smooth Swapping 1.20.4 Port) @create-tab-fix scape-and-run-origins@srp-origins origins-extra-keybinds@origins-extra-keybinds-fabric @@ -14916,7 +14916,7 @@ frostbytes-improved-inventory@ mekanism-extras@mekanism_extra|通用机械:扩展* leaf-beds@ unidentified-enchantments@unidentified_enchantments -protection-pixel@|机素防护* +protection-pixel|机素防护*¨@protection-pixel|机素防护 (Protection Pixel) - 已失效 createdpcompat@ @@ -15047,7 +15047,7 @@ renderjs@ dehand@ jadecolonies @cobblemon-mike-skills -diamond-star-combat +diamond-star-combat@ modern-ae2-additions@ create-metallurgy@|机械动力:冶金学 (Create: Metallurgy) @client-info-fix @@ -15196,7 +15196,7 @@ dralards-rock-candy-mod -@explosion-breaks-no-block|爆炸不破坏方块 (Explosion Breaks No Block) +explosion-breaks-no-block@|爆炸不破坏方块* via-romana@ lithic-coins dynamic-trees-for-natures-spirit@|动态的树:自然之灵附属 (Dynamic Trees for Nature's Spirit) @@ -15276,7 +15276,7 @@ no-shield-delay@ musicpower@|音乐之力 (MusicPower) macrocraft@ camerapture@ -simple-voice-radio@ +simple-voice-radio@simple-radio adorned fence-on-slab @@ -15301,19 +15301,19 @@ rogues-and-warriors@ -@tfc-support-indicator +tfc-support-indicator@ tfc-poisoned-drinks -smartercontraptionstorage|智能动态结构存储 (SmarterContraptionStorage) +smartercontraptionstorage@smarter-contraption-storage|智能动态结构存储 (SmarterContraptionStorage) lexiconfig@ -@advancementsfullscreen +@advancementsfullscreen|进度全屏 (AdvancementsFullscreen) luckys-wardrobe@ @forkkit more-affordable-than-ever-never-too-expensive@more-affordable-than-ever what-am-i @camoverlay -autoplant +autoplant|自动种植 (AutoPlant) @skip-backup-screen @polysprint @crashpatch @@ -15348,13 +15348,13 @@ edgeless-chat-screen@|无边界聊天框* villager-death-messages@ @smooth-skies clist@ -tothestarsremake@ +tothestarsremake@|ToTheStars重制版 (ToTheStarsRemake) galacticraft-gravity dragon-mounts-even-more-dragons|龙骑士附属:附加龙 (Dragon Mounts: Even More Dragons) xplus-contingameime@|游戏内输入法 XPlus 版 (XPlus ContingameIME) @watersource2|水源 2 (Water Source 2) radomraidspawn|随机袭击生成 (Random Raid Spawn) -enigmatic-legacy-legacy|神秘²遗物* +enigmatic-legacy-legacy|神秘遗物经典版* nco-java-legacy-lib forge-souls-like-universe@souls-like-universe @@ -15370,7 +15370,7 @@ pianocraft@ jazzymusicality@musicality @boids buckshotroulette@|恶魔轮盘赌 (BuckshotRoulette) -customnpcs-unofficial@ +customnpcs-unofficial@|自定义NPC 非官方版 (CustomNPCs Unofficial) @fast-trading simple-wooden-pipes simplywalk@ @@ -15458,7 +15458,7 @@ lush-cave-trader@ oceans-enhancements@ xp-jellies aromatic -onekeyminer-nf@onekeyminer_nf|一键连锁(Forge/NeoForge) (OneKeyMiner(Forge/NeoForge)) +onekeyminer-nf@onekeyminer_nf|一键连锁(Neo)Forge (OneKeyMiner(Neo)Forge) retargetato@|约谊敌众 (ReTargetato) iceandfire-ce@|冰火传说社区版 (IceAndFire Community Edition) @@ -15479,13 +15479,13 @@ nc-steam-additions@nuclearcraft-steam-additions aura-cascade|气场流注* rac-compat@ spartan-weaponry-addon-toolkit@ -smsn@ +smsn@|救救我的答辩网络 (Save My Shaky Network) sneaky-tree-growing-forge@sneaky-tree-growing¨sneaky-tree-growing-fabric hungry-zombies @gravity-gourds usa-delight|USA Delight - 已失效¨@usa-delight experience-farm -ad-extendra-dandi2k8-version@ad_extendra-continuation +ad-extendra-dandi2k8-version|Dandi2k8's Ad Extendra - 已失效¨@ad_extendra-continuation nebulus-juice-trader@ cthulhu-fishing@ large-super-potato|大型超级马铃薯* @@ -15496,7 +15496,7 @@ squinkers mokels-bossfight-kinora @create-high-pressure create-framed@|机械动力:多彩边框 (Create: Framed) -trackwork@ +trackwork@|驱动工艺* oritech@ the-impossible-library@ perspective-mod-redux@ @@ -15597,7 +15597,7 @@ sparkle@ @visible-traders villagers-inventory removehud@ -@orthocamera +@orthocamera|正交相机 (OrthoCamera) third-person-crosshair-fabric @hitrange black-moon@ @@ -15643,11 +15643,11 @@ tilt-break@ item-frament@ water-vision@ randomized-default-fireworks@randomizeddefaultfireworks -glowing-torchflower@glowing-torchflower#all-versions +glowing-torchflower@ @clock-hud-renew cameranoclip barriers-dont-block-rain@ -no-render +no-render@ runningshoes|跑鞋 (Running Shoes) fluid-void-fading@fluidvoidfading @@ -15799,13 +15799,13 @@ mage-flame@ @powerless-refined-storage forgotten-graves@ day-zombies -morecoal|更多煤炭 +morecoal|更多煤炭 (More Coal) jjku immersive-combat-feathers ghosties-monocle ender-dragon-loot - +epic-terrain|史诗地形* - 史诗地形(实验版) oh-the-trees-youll-grow@ oh-the-biomes-weve-gone@|我们走过的生物群系 (Oh The Biomes We've Gone) dblockbusterdisplays@ @@ -15851,7 +15851,7 @@ nbt-tool -dglab-x-minecraft@ +dglab-x-minecraft¨@dglab-x-minecraft|Minecraft x DgLab - 已失效 twintails|双马尾 (TwinTails)¨twin-tails|双马尾 (TwinTails) gt-wireless|格雷无线科技 (GT Wireless) forcejava21@|强制 Java21 (ForceJava21) @@ -15906,7 +15906,7 @@ no-32768-limit@|没有32768限制* reairdrop-supply@|空投供应:重生 (ReAirdrop Supply) @minequery|MC宝 (MineQuery)¨@minequery-legacy|MC宝 (MineQuery) -@simply-create-model-mod|简单动力 (Simply Create Model)¨@simply-create-model|简单动力 (Simply Create Model) - 资源包 +@simply-create-model-mod|简单动力 (Simply Create Model) mountains-poem@|峰峦辞 (Mountain's Poem) @playtime-reborn|游戏时长计数器:重生 (Playtime Reborn) @@ -15917,7 +15917,7 @@ thaumcraftfix|神秘时代6修复 (Thaumcraft Fix) extended-industrialization@ small-tetra-additions -lets-do-furniture@ +lets-do-furniture@|家居装饰 ([Let's Do] Furniture) tesseract-api-neoforge@tesseract-api tickrate-api spectrelib @@ -15942,14 +15942,14 @@ improved-grindstone themetip@ woodwalkers@ peaceful-moon@ -to-tweaks-irons-spells@ +to-tweaks-irons-spells¨@to-tweaks-irons-spells|T.O Magic 'n Extras - Iron's Spells Addon - 已失效 horde-moon@ laser-mod-2 -mekanism-lasers +mekanism-lasers@ wizards-reborn@ tinkervillager|工匠村民 (Tinker Villager) -customnpc-contentback +customnpc-contentback@cnpc-items debug-stick useful-bat runic-inscription @@ -15966,7 +15966,7 @@ agenta@ more-beehive-variants@ -persistent-creative-inventory@ +persistent-creative-inventory lets-do-wildernature@|野性自然 ([Let's Do] WilderNature) @quitconfirm @ressourcepackcommand @@ -15992,7 +15992,7 @@ refreshdisplaylists vulkanmod-android-libs @black-bar-concealer building-shift@ -mob-ai-tweaks +mob-ai-tweaks@ the-masquerade-illager-boss@the-masquerade-(illager-boss) better-armory-x sweetys-horse-armor-tweaks@ @@ -16083,7 +16083,7 @@ glowy-players-renewed@|玩家高亮:重置版* epic-fight-kenjis-combat-forms mekanism-unleashed@ -easy-cake +easy-cake|简易蛋糕* weave-craft morefarming@|更多农耕 (MoreFarming) iron-bushes @@ -16163,7 +16163,7 @@ ynet-an-xnet-fork rosatech nutrition-gtceu@ nutrition-unofficial-extended-life@|营养学非官方延续版* -@manametalmod-tweaks|魔金微调 (ManaMetalMod Tweaks) +manametalmod-tweaks-m3-t@manametalmod-tweaks|魔金微调 (ManaMetalMod Tweaks) ae2-crafting-tree|合成树 (AE2: Crafting Tree) client-automatic-doors@client_automatic_doors|客户端自动门* @@ -16186,9 +16186,9 @@ jmx@imx another-dyeable-shulkers@|可染色潜影贝* @thatskyinteractions - +@tinker-transplant|匠魂移植 (Tinker Transplant) chisatos-more-redstone@|Chisato的更多红石 (Chisato's More Redstone) -assetmover +assetmover@ duclib@ orecrop|矿石作物 (OreCrop) @@ -16207,7 +16207,7 @@ fibers-guns-pack-for-tacz beams@ discarnate@ @fishmc -mob-armor-trims@ +mob-armor-trims bubbles-a-baubles-fork arst-motion-blur@ @mpalladium|钯 (Palladium) @@ -16233,7 +16233,7 @@ ambidextrous tea-the-story-recount|茶风·纪事:追叙 (Tea the Story : Recount) xp-challenge@|经验挑战 (XP Challenge) @styled-renaming -@simple-block-overlay +simple-block-overlay@ @helpfull-action-bar repeatable-trial-vaults@ fast-async-world-save-forge-fabric @@ -16274,7 +16274,7 @@ dragon-gear-renewed @archers-plushies @hephaestus-expansion create-structures-arise@ - +kilt@ gregtech-extended-chemistry-extended ulv-covers-modern farmers-structures @@ -16393,7 +16393,7 @@ mts-iv-aq-studios-himars immersive-vehicles-iv-mts-lindqvist@lav immersive-vehicles-iv-mts-zroskyan-ordinary petroleums-wigs -revenge-mark|遗怨待战/复仇标记* +revenge-mark|遗怨待战 / 复仇标记* @@ -16411,7 +16411,7 @@ scaling-mobs multiline-mastery@ @confetti-stuff pirates-treasures -ars-nouveau-delight@ +ars-nouveau-delight|Ars Nouveau Delight - 已失效¨@ars-nouveau-delight crossbow-enchants@ extendeddrawersaddon@ minecolonies-letsdo @@ -16469,13 +16469,13 @@ water-balloon@ cataclysm-spellbooks@ call-of-drowner@|溺亡者之嚎 (Call of Drowner) - +endofday|终焉之日 (Endofdays) fastfood-delight-unofficial|重启速食/快餐乐事非官方版 (FastFood Delight Unofficial) faster-fly-block-breaking|飞行速速破* upgraded-additional-trims create-let-the-adventure-begin@ -bountiful-critters +bountiful-critters@ flashlight-mcowners smartpot@|智能锅 (SmartPot) @@ -16537,7 +16537,7 @@ mca-api sodium-leaf-culling@sodiumleafculling|钠:树叶剔除* homing-xp-orb|引验导追/归航经验球* -multiblocked2 +multiblocked2@ armorstatushud-updated|耐久信息显示更新版 (ArmorStatusHUD Updated) tfc-better-blast-furnace tfc-textile@ @@ -16572,7 +16572,7 @@ unofficial-bosses-of-mass-destruction-dungeon @more-things|更多物品 (MoreThings) -explosionvisualizer@|爆炸信息显示 +explosionvisualizer@|爆炸信息显示 (ExplosionVisualizer) @more-vanilla-foods|更多原版食物 (More Vanilla Foods) let-me-feed-you|吃取送食/玩家喂食 (Let Me Feed You!) @@ -16626,10 +16626,10 @@ ftb-rickety-water-wheel cobblegen-galore @ringles-gun-turret create-sky-village -create-crushing +create-crushing|Create: Crushing - 已失效¨@create-crushing cushy-pillows|舒适枕头* ice-and-fire-patcher@iaf-patcher|冰火传说随意修复* -goetyrevelation|诡厄巫法:启示录 (Goety: Revelation) - 已失效¨@goety-revelation|诡厄巫法:启示录 (Goety: Revelation) +goety-revelation@|诡厄巫法:启示录 (Goety: Revelation) the-mandela-catalogue-alternates@ the-craftela-catalogue the-ceilands@|垂顶之地* @@ -16644,7 +16644,7 @@ better-modlist-neoforge@better-modlist sky-aesthetics@ headshot-bonus|已无可惧/简单爆头* enchantlevelbrek@enchantmentlevelbreak|附魔等级上限突破2 (EnchantmentLevelBreak) -global-gamerule-fabric@|全局游戏规则Fabric版 (Global GameRule [Fabric]) +the-ruler@ biome-borders-revived-liteloader-only chunkborders|区块边界 (ChunkBorders) burnt@ @@ -16675,7 +16675,7 @@ german-uniforms-ww2 tameable@ -slashblade-gamerelic|拔刀剑重锋附属·游戏扩增 (srelic) +slashblade-gamerelic|游戏扩增·异界之刃 (SlashBladeGameRelic) theovergrowth jadens-nether-expansion@ @@ -16695,7 +16695,7 @@ scape-and-run-meshi@|逃逸:寄生饭 (Scape and Run: Meshi) @bad-apple-world-preset gastronomy-works@ @create-air-fabric - +@clockwork-additions colorfulwater yungs-cave-biomes@|YUNG的洞穴生物群系 (YUNG's Cave Biomes)¨yungs-cave-biomes-fabric|YUNG的洞穴生物群系 (YUNG's Cave Biomes) @legacy-command-registry|旧式命令注册 (Legacy Command Registry) @@ -16711,7 +16711,7 @@ foodvariations@|生活调味料:经典版 (Spice of Life: Classic Edition) pizza-delight simplefog@ @butter -tnt-plus-mod|TNT Plus - 所有¨@tnt-plus-mod|TNT Plus - 部分失效 +tnt-plus-mod|TNT Plus - 所有¨@tnt-plus-mod @kelvins-better-player-animations-(port) tokusatsurecord|特摄唱片 (Tokusatsu Record) @@ -16754,7 +16754,7 @@ slashblade-resharped-renderfix-patch@|拔刀剑:重锋 渲染修复补丁 (Sla @invulnerable-wheat skin3d-but-not-cubic@skins3d-but-not-cubic leavesly@ -create-totem-factory +create-totem-factory@ snad-redstone-edition|子沙:红石版 (Snad:Redstone Edition) @party-flames subtle-effects@ @@ -16802,7 +16802,7 @@ gold-nugget-recycling sodium-options-api@ @totom-register -crazyae|疯狂的AE (CrazyAE) +crazyae@|疯狂的AE (CrazyAE) ae2-crafting-tree-legacy|AE2合成树-传统版 (AE2CT-Legacy) ae2-alchemistry-addon @@ -16810,7 +16810,7 @@ novaultlimit@ game-discs@|游戏光碟* luncheon-meat-s-delight@|午餐肉乐事 (Luncheon Meat 'S Delight) -vanilla-plus-resourceful-tools +vanilla-plus-resourceful-tools¨resource-tools-fabric create-horse-power@ ars-technica plustic-reforged@ @@ -16830,7 +16830,7 @@ languagereloadunofficial@ mekanism-covers@ enchanted-wilderness-elegant-countryside@enchanted-wilderness-elegant-countryside.|雅致乡下 (Elegant Countryside) better-durability-reforged|更好的耐久度:重铸* -sos|我来助你! (SOS) +fast-tpa|我来助你! (Fast TPA) cherry-on-1-12-2@cherry_on_1.12.2|1.12.2的樱花 (Cherry_on_1.12.2) scape-and-spartan-parasites|寄体斯巴达 (Scape and Spartan: Parasites) @ripples-of-the-past-extra-stands-addon @@ -16845,12 +16845,12 @@ chrysalis@ alekis-ridiculously-simple-roofs@alekiroofs @trails rainboows@ -texturized-particles@ +texturized-particles¨@texturized-particles|Texturized Particles - 已失效 rple@ reign-of-nether-rts-in-minecraft@reign-of-nether-rts|下界之治 (Reign of Nether RTS) the-secret-doors - +immersive-vehicles-iv-mts-veb-iav-improved-2000|“改进-2000” tame-tools@|驯兽工具* @bia vounierns-turrets @@ -16893,7 +16893,7 @@ findmyitemsandfluids @numeric-experience-info aquapatch aviator-dreams@ -macaws-stairs@ +macaws-stairs@|Macaw的楼梯与露台 (Macaw's Stairs) cross-stitch-colours@ gaming-deco@ @reactive-music|韵律回响 (Reactive Music) @@ -16926,9 +16926,9 @@ project-lambda @pretty-hitboxes-create-addition|可配置的碰撞箱显示-机械动力功能附加 (Pretty Hitbox-Create Addition) watersource-reborn@|水源:重生 (WaterSource: Reborn) locks-unofficial@|锁:非官方版 (Locks-Unofficial) -@epicfight_improve|史诗战斗:改进 (EpicFight:Improve) +epicfight-improve@epicfight_improve|史诗战斗:改进 (EpicFight:Improve) just-in-nether@ -jinghui|镜回 (JingHui) +jinghui@|镜回 (JingHui) @ripples-of-the-past-mandom-addon|RotP:男人领域附属 (Ripples of the Past: Mandom Addon) @auto-gg better-brightness-slider-respawn@ @@ -16937,7 +16937,7 @@ better-brightness-slider-respawn@ animatica-foxified@ mcchatgpt@ @aimobs -ambiances@ambiance|氛围! (Ambiance!) +ambiances@aurae|氛围! (Aurae!) screw-your-items@ mace-port bloodlines @@ -16947,7 +16947,7 @@ enchanting-tweaker@enchanting-tweaks just-a-lot-more-enchantments@ alessandrvs-enchantments@alessandrv-enchantments armor-abilities@ -sleepy-hollows@ +sleepy-hollows@|沉眠空谷* nametag-tweaks@nametagtweaks|名称标签调整 (Nametag (Display) Tweaks) @sodium-no-alerts @@ -16994,7 +16994,7 @@ rank-system|评分系统* @creative-one-punch-forge bundle-inventory|Bundle Inventory - 问题反馈¨@bundle-inventory scroll-tweaks@scrolltweaks|鼠标滚轮调整* -neko-server@ +st@server-tools|服务器工具 (Server Tools) ride-every-thing@|骑乘一切* cit-resewn-neopatcher@ legendary-creatures|传说生物* @@ -17009,7 +17009,7 @@ eventjs backup-manager@ the-root-of-fear@ the-modifiger@ -@keypreset|键位预设 (KeyPreset) +keypreset@|键位预设 (KeyPreset) @tweakeroo-features-menu sanity-prequel|理智:前传 (Sanity: Prequel) ayame-paperdoll@|Ayame 纸娃娃 (Ayame PaperDoll) @@ -17019,7 +17019,7 @@ passive-shield@ trap-expansion roost-2-flying-higher @amber -@liteminer +liteminer@ @lvlz-better-nether @lvlz-better-end cc-shops@ @@ -17065,11 +17065,11 @@ sws-wukong dragns-valiant-vehicles@dragns-valiant-vehicles! bio-factory@biofactory|机械动力:血肉工厂 (Create: Bio-Factory) create-ad-astra-compatibility -create-mechanical-chicken@1.20.1-1.1.1.h-48 +create-mechanical-chicken@ zbgt@ casinocraft @mql-tir5d-turtle -toadlib@ +toadlib|ToadLib - 不全¨@toadlib crosshair-tweaks@crosshairtweaks|十字准星调整* @@ -17081,7 +17081,7 @@ iron-nugget-recycling re-exposer@|Exposer 重置版 (Re-Exposer) gtmoreoreprocessing@ computercraftedu -bibliocraft-legacy +bibliocraft-legacy@ simple-clouds@ @@ -17103,11 +17103,11 @@ neepmeat@ @huangsheng1s-more-monster|更多怪物 (HuangSheng1's More Monster) creeping-woods -@zombie-apocalypse-core -dragon-mounts-2-expanded@dragon-mounts-2|龙骑士2:延续 (DragonMounts2: Expanded) +@zac +dragon-mounts-2@|龙骑士2 (DragonMounts2) life-fruits@|生命果* ati-structures -fantasy-armor@fantasy_armor +fantasy-armor@fantasy_armor|幻想铠甲* beautiful-enchanted-books@beautiful-enchanted-books-mod-edition @@ -17125,7 +17125,7 @@ paladins-oath roundabout-the-jojo-mod@roundabout_the_jojo_mod -enviromine-continuation@ +enviromine-continuation@|更多生存要素:延续 (EnviroMine continuation) @toffys-hooks mutants-buff @@ -17136,7 +17136,7 @@ craterlib@ spark-unforged@ pack-mode-reborn mc-mods-the-wood-weve-got@all-the-wood-weve-got -bibliowoods-legacy +bibliowoods-legacy@ hydrological@ unilib@ @@ -17152,7 +17152,7 @@ mutils@ better-campfires@|更好的营火* @mtr-renfe-addon gt-community-additions@ -more-create-burners|机械动力:燃烧室附加* +more-create-burners|机械动力:更多燃烧室* create-logistics@ create-pneumatic-equipment@ collisiondamage|碰撞伤害 (CollisionDamage) @@ -17161,7 +17161,7 @@ tensura-reincarnated@|关于我转生变成史莱姆这档事:转生 (Tensura: transformer|奥特化身:无限 (Becomes Ultraman:Infinity) henshin|变身* ridiculous-world - +enhanced-biomes quartz_craft@|石英工艺 (Quartz_Craft) cursed-stare@ legendblade|传世之刃 (LegendBlade) @@ -17181,7 +17181,7 @@ formidulus@ ames-stranger-things herobrine-origins milkable-blazes@ - +@rotp-stone-legacy|RotP:暗之遗物 (Stone Legacy (Ripples of the Past addon)) furniture-beetle@ mekanism-x-create-northstar@ @createz @@ -17214,7 +17214,7 @@ galacticraft-automatic-resources|星系自动资源* roberts-building-pack|Robert的建筑扩展包 (Robert's Building Pack) -arcanus-continuum@ +arcanus@ ultimate-chess@ sparkweave@ visualjs @@ -17277,7 +17277,7 @@ tfcgenviewer@ terra-math@ lukis-crazy-chambers tumbleweed-wasteland -irons-jewelry +irons-jewelry|Iron的宝石与饰品 (Iron's Gems 'n Jewelry) atlas-api loading-screen-messages @pipesounds @@ -17293,7 +17293,7 @@ storage-drawers-unofficial@|储物抽屉非官方版* centrifuge-tiers-reproduced@ dense-mekanism@ simple-missles@ -galosphere-spellbooks +galosphere-spellbooks@ discerning-the-eldritch @hexcassettes wardens-plus @@ -17302,7 +17302,7 @@ ender-wyrmlings new-classic-battle-towers chitin-equipment@|甲壳装备* -mowzies-cataclysm|Mowzie的灾变 (Mowzie's Cataclysm) +mowzies-cataclysm@|Mowzie的灾变 (Mowzie's Cataclysm) stardew-fishing-fabric@ piglin-slayer@ cataclysm-tools@ @@ -17313,13 +17313,13 @@ oo-ee-a-e-a@ sodium-embeddium-options-mod-compat@sodium-options-mod-compat -secureseed-reborn +secureseed-reborn@ creative-eats mazerooms@ friendly-chests @chitinous_ties brewable-dragons-breath@|龙息可酿造 (Brewable Dragon's Breath) -flerovium|鈇* +flerovium@|鈇* gathering-torches-become-sunlight|薪火,造炬成阳* @@ -17382,7 +17382,7 @@ explosive-enhancement-reforged@explosive-enhancement-forge tmtravlrs-arrow-trails|Tmtravlr的箭矢轨迹 (Tmtravlr's Arrow Trails) biome-replacer@|群系替换器*¨biome-replacer-neoforge@|群系替换器 (Biome Replacer) custom-suspicious-stew|自定义谜之炖菜* - +@child-folder-mod-scanner|子文件夹Mod扫描器 (Child Folder Mod Scanner) @create-stress-debug create-kart @@ -17460,17 +17460,17 @@ book-scroll@ -@qca +qualia-carpet-addition@qca mouse-tweaks-unofficial|鼠标手势非官方版* baubles-lts elysium-api@ -noisethreader +noisethreader@ @no-bats antibat@ illage-and-spell-age origincore|起源核心* - +pet-connect|收容宠物* heaven-destiny-moment|天命时刻* industrial-foregoing-extra-upgrades|工业先锋:更多升级 (Industrial Foregoing: Extra Upgrades) growableores-extension@|甘蔗转换机 (GrowableOres Extension) @@ -17618,7 +17618,7 @@ nanny@ resource-library@ resource-config-api@ @threatengl -hybridfix|混合修复 (HybridFix) +hybridfix@|混合修复 (HybridFix) @disable-portal-checks eat-eat-eat|吃吃吃* @recipe-stages-js|配方阶段 (Recipe Stages JS) @@ -17678,8 +17678,8 @@ cute-companions-ducks loot-n-explore@ epic-knights-ice-and-fire medieval-origins-revival@ -construction-sticks@ -sophisticated-storage-in-motion@|精妙存储:矿车物流 (Sophisticated Storage in Motion) +construction-sticks@|建筑棒* +sophisticated-storage-in-motion@|精妙物流 (Sophisticated Storage in Motion) imgur-display@ extra-sponges@extrasponges|更多海绵* more-wolf-armors@|更多狼铠* @@ -17687,7 +17687,7 @@ not-enough-rocks@ pedestal@ @snowball-and-egg-knockback forestry-worktable-display -accessorify +accessorify@ high-quality-crops|高品质作物* immortal-enchantment@|不死附魔* awesome-storage|魔法存储* @@ -17708,7 +17708,7 @@ mekanism-upgrades-reborn|通用机械升级突破:重生 (Mekanism Upgrades: R epic-fight-x-irons-spells-animation-fix@ epic-fight-x-wings-reborn-animation-fix ef-bs@battle_arts -alshanexs-familiars +alshanexs-familiars@ feur-skyland@ balkons-expansion @@ -17727,7 +17727,7 @@ notenoughids-unofficial-1-7-10@notenoughids-unofficial|增加ID上限非官方 tornado-mod-classic@weather-and-tornadoes-classic|局部气候&风暴经典版 (Weather and Tornadoes Classic) strange-berries@ jeffs-cursed-walking-structures -terra-entity|泰拉生物* +terra-entity@|泰拉生物* not-interested@ @new-soviet @@ -17813,7 +17813,7 @@ warium @ait_plus fabulous-fletching universal-wrench@ -@bottle_ship|瓶中船 (Bottle Ship) +@bottle-ship|瓶中船 (Bottle Ship) smallblueslimes-ladders doomsday-decoration|末日装饰* overclocked-watches@ @@ -17885,7 +17885,7 @@ minecart-turning-reforged@¨@minecart-turning offlimits@ apotheosis-threat @string-duper-fix-remover - +@superresolution|超分辨率 (Super Resolution) special-model-loader@ vs-ship-handler@ @@ -17939,10 +17939,10 @@ mythic-charms@|神话护符* minecraft-add@|原版补充* stacked-blocks@|捆扎方块* stacked-blocks-farmers-delight@|捆扎方块:农夫乐事 (Stacked Blocks: Farmer's Delight) -@fowlplay +fowlplay@ ender-totem -@faewufs-diversity +diversity@faewufs-diversity tonsofenchants @@ -17951,7 +17951,7 @@ cave-root whos-there-ghost-players@ the-cuckoo-clock final-samurai -classic-battle-towers|经典战斗高塔* +classic-battle-towers@|经典战斗高塔* sihywtcamd-extensions@difficulty-extensions @heart-crystals @@ -17975,7 +17975,7 @@ distinctpaintings@ @search-on-mcmod|MC百科搜索 (Search on MCMOD) infinite-lava-renewed @better-fabric-console - +mine-tooltips fastchest-reforged @better-caves-archaic|古早洞穴优化 (Better Caves Archaic) @@ -17995,12 +17995,12 @@ lost-cities-survive-the-dead-edition power-of-the-void|虚空之力 (Power of the Void) aerlune-rpg -hitboxbackport +hitboxbackport@ fsmm@ create-pantographs-and-wires@ footwork-api@footwork @halplibe - +warmachinelib@|战争机器库 (WarMachineLib) crash-assistant@ @@ -18018,7 +18018,7 @@ sky-breaker-blue-skies-limit-remover-1-18-2|天穹突破_1.18.2 (Sky Breaker_1.1 xiyus-lucky-block|饩雨的幸运方块 (Xiyu's Lucky Block) @epicfight_oldsolar|史诗战斗:旧版solar (EpicFight_oldsolar) reskillable-reimagined -renaissance-core +renaissance-core@ @startup-chime|启动音效 (Startup Chime) carpet-takeneko-addition@tnca|竹猫的地毯扩展* chain-mod @@ -18046,7 +18046,7 @@ noteblocks chainvein@ mobs-on-rails@ @no-ones-view-model -gravestone-x-curios-api-compat +gravestone-x-curios-api-compat@ nolijium@ eventslib etst-lib|乙硫醇的匠魂词条库 (EtST Lib)¨@etstlib|乙硫醇的匠魂词条库 (EtST Lib) - 更新可能较慢 @@ -18072,7 +18072,7 @@ irons-spell-book-mob-attribute-addon epic-fightexsilium-gladius|EpicFight—放逐之刃 (Epic Fight — Exsilium Gladius) @kamenriderweaponcraft|假面骑士武器大全集 (KamenRiderWeaponCraft) - +advancearmy@|先遣部队 (Advance Army) @blood-blade @mineimpact|方神 (MineImpact) jadens-nether-expansion-delight@ @@ -18091,7 +18091,7 @@ cubic-combat-simulator webmapview@|网页地图浏览 (webmapview) f3-is-my-shawty -ollamachat¨@ollamachat|ollamachat - 正在审核 +ollamachat@ potion-stacker @betterweather better-gamemode @@ -18117,7 +18117,7 @@ potion-core-reborn|药水核心:重生* rolling-gate@|卷帘门* bettertab@better-tab|更好的Tab (BetterTab) - +let-me-see-see-your-code@let-me-see-see-(your-code)|让我看看 (LetMeSeeSee(YourCode)) put-mana-in|手充魔力* ars-botania @@ -18174,7 +18174,7 @@ stacksonstacks more-of-all@ -popular-biology|热门生物 +popular-biology|热门生物* tolkien-tweaks tolkien-tweaks-mobs-edition soulplied-energistics @@ -18202,10 +18202,10 @@ vehicular-movement-light vehicular-movement onysdvehicles@onysd-vehicles -zunpet-forge|Zun号非官方Forge版 (ZUNPet Forge) +zunpet-forge|Zun号非官方NeoForge/Forge版 (ZUNPet NeoForge/Forge) more-hitboxes@|更多碰撞箱* -shipwreck-fix|沉船修复* +shipwreck-fix|沉船修复/沉船卡顿优化* narcissus-farewell|水仙辞* @mcmod-splashes-plusplus|百科闪烁标语++ (MCMod Splashes PlusPlus) @@ -18258,7 +18258,7 @@ create-picky-waterwheels@create-picky-wheels fancy-lamps resizing-potion miku -advanced-smelter|荒古炼金炉 (Advanced Alchemical Furnace) +advanced-smelter@|荒古炼金炉 (Advanced Alchemical Furnace) @@ -18283,7 +18283,7 @@ craftformers-prime@ @vbe arts-by-kev-mod-2023@ the-lion-king-mod -epic-terrain-compatible +epic-terrain-compatible|史诗地形兼容版* the-beginning create-simple-ore-doubling@ create-low-heated@ @@ -18361,8 +18361,8 @@ scopic memory-leak-fix-unofficial-remastered @distance-traveled-calculation -@control-craft -reforbidden-magic|禁忌魔法:重生 (ReForbidden Magic) +@control-craft|控制学 (Control Craft) +reforbidden-magic@|禁忌魔法:重生 (ReForbidden Magic) moses-mod craftable-animals|可合成动物* avaritia-1-1x-kedition @@ -18384,12 +18384,12 @@ thaumic-staff|神秘法杖* thaumic-bases-mystical-rose-rework|神秘基础学玫瑰重制* rhyme-pvz@|PVZ:润 (Rhyme PVZ) ichorium-kit -reforgotten-relics|失落遗物学:重生 (ReForgotten Relics) +reforgotten-relics@|失落遗物学:重生 (ReForgotten Relics) in-your-world@ -warpapocalypse +warpapocalypse@warp-apocalypse arcane-arteries-kedition|奥法献祭学非官方版* -thaumic-tinkerer-unofficial|神秘工匠非官方版* +thaumic-tinkerer-unofficial@|神秘工匠非官方版* asyncparticles@|异步粒子 (AsyncParticles) @dragonfly-model-library @@ -18457,11 +18457,11 @@ gud_coolers suppergerrie2s-drone-mod data-essence@ -nautec@ +nautec@|海蓝科技 (NauTec) thorny-bush-protection@ gudlib -reflex-antilag +reflex-antilag@ tfc-nei nolan @@ -18480,7 +18480,7 @@ apotheosis-totem tbone - +fastfurnace-decreased|熔炉性能优化-- (FastFurnace--) @cdnperspective3d @volume-fix @@ -18519,13 +18519,13 @@ tinkers-advanced@|前沿匠艺 (Tinkers' Advanced) more-structure-processors@ quick-stack-to-nearby-chests@quickstack|快速堆叠 (QuickStack / Quick Stack To Nearby Chests) - +@kallfix yggdrasilproxy@yggdrasil-proxy thaumic-meme|神秘吐槽* starlightxae|星光xAE2修复 (StarlightxAE) - +@levelclear @drops-into-shulker ftb-team-dimensions epicfight-resurrection|史诗战斗-重生 (EpicFight : Resurrection) @@ -18606,7 +18606,7 @@ bountifulbaubles-reforked@bountifulbaublesreforked|丰富的饰品:重铸 (Bou @mekanismmoremodules|通用机械:更多单元 (Mekanism: More Modules) @better-armor-swap -the-man-from-the-shadow +the-man-from-the-shadow@the-man-from-the-shadow-(horror) @@ -18619,30 +18619,30 @@ persistent-inventory-search@ @litematica-enderchest-materials @mob-protect only-bags@ -@the-broken-script +the-broken-script-official@the-broken-script torojimas-buildhelper@torojima_s_buildhelper expanded-armory-legacy@|扩展装备:传承 (Expanded Armory: Legacy) loquat-forge@loquat¨loquat @waterbeds @customhealthhud @fix-horizontal-camera-lag -@create-cyber-goggles|机械动力:赛博护目镜 (Create: Cyber Goggles) -mekanism-science +create-cyber-goggles@|机械动力:赛博护目镜 (Create: Cyber Goggles) +mekanism-elements bots-lib@ botaniaoverpowered@ @simple_no_survival|蓟县生存 (Simple ? Survival) -@auto-pillar-generator|一键柱子生成 (Auto Pillar Generator) -vanilla-delight +auto-pillar-generator@|一键柱子生成* +vanilla-delight@vanilla-delights jade-vs@ vics-modern-warfare-patched|维克的现代战争(修复版) (Vic's Modern Warfare (Patched)) waterframesbackported tweakeroo-update-port -xkballs-auto-translate|xkball的自动汉化 (xkball's Auto Translate) +xkballs-auto-translate@|xkball的自动汉化 (xkball's Auto Translate) block-replacement-mc1-7-10 @mekanismqq @@ -18656,7 +18656,7 @@ data-packs-helper|数据包助手* tconstructjs|有头脑的工匠 (TConstructJs) maid-storage-manager|女仆仓管* - +@mystias_izakaya|夜雀食堂 (Mystia's Izakaya) lush-scented-paradise|芬芳茶园* hold-my-items@ @@ -18667,7 +18667,7 @@ configurable-keepinventory better-chat@betterchat mekalus-oculus-fork-with-fixed-mekanism-mekasuit view-emc -@fortitude +fortitude@ experience-ore|经验矿石* passive-torch productive-metalworks @@ -18677,7 +18677,7 @@ spirit-of-fight@|战魂 (Spirit of Fight) grim-bleak@grim-and-bleak carrymyboss ranged-damage-limit|远域挡招/远程伤害保护* -magichem +magichem|魔法炼金 (MagiChem) iglee-library@ component-model-hider @@ -18687,7 +18687,7 @@ keybindjs@ @autototem @totem-cooldown sound-physics-legacy|物理声效:遗产* - +astatine|砹* marblegates-exotic-enchantment-reborn@marblegates-exotic-enchantment-flowing-agony-reborn|白门的奇异附魔:苦痛长河:重生 (MarbleGate's Exotic Enchantment: Flowing Agony: Reborn) @@ -18707,14 +18707,14 @@ forgotten-nunchakus create-big-cannons-advanced-technologies@|机械动力火炮:先进技术 (Create Big Cannons: Advanced Technologies) quality-food@ snekcraft@ -hearth-and-harvest|暖灶丰年 (Hearth and Harvest) +hearth-and-harvest@|暖灶丰年 (Hearth and Harvest) wjys|无尽药水 (WJYS) schematicpreview@|原理图预览 (SchematicPreview) -@collect-emerald|聚集石 (Collect Emerald) +@collect-emerald|聚灵石 (Collect Emerald) sakura-tinker|魂樱工匠* @appspec heart-crystal|心之水晶* @@ -18756,7 +18756,7 @@ dungeon-revival new-order create-more-recipes-addon cbc-nuclear@ -@smart-zombies +smarter-zombies¨@smart-zombies|Smart Zombies - 已失效 libguifoxified@ @@ -18804,7 +18804,7 @@ tea-aroma@|茶香* overpowered-inventory bbrains-umfixes -foodstats|食物增强 (foodstats) +foodstats@|食物增强 (foodstats) hxchud sorted-enchantments@|排序附魔* assassins-creed-parkour-mod|刺客信条跑酷 (Assassin's Creed Parkour Mod) @@ -18839,18 +18839,18 @@ health-bar see-the-rarities-str|查看稀有度 (See The Rarities) craftpilot@ @game-of-reborn|复活赛 (Game of Reborn) -legendary-slash|传说之刃* +rough-blade|无锋道* @dota.rg @appledog|苹狗 (Appledog) superb-warfare@|卓越前线* -aetherworks-extended-life +aetherworks-extended-life@aetherworks-unofficial-extended-life pomkots-mechs-extension-pack@ -@monkey-config +@monkey-utils some-more-blocks@ projecte-advanced-alchemy - +nff-girls-grimoire-of-gaia-extension|NFF: Girls - 盖亚魔典扩展 (NFF: Girls - Grimoire of Gaia Extension) replicore@|复制核心 (RepliCore) create-reforged-foundations@ @@ -18861,7 +18861,7 @@ thdilos-fox-origin@|ThDilos的狐狸起源 (ThDilos Fox Origin) narutofix|火影修复 (NarutoFix) sbr-i-need-more-enchantment@slashblade-resharped-i-need-more-enchantment|重锋:施魔更哥 (SlashBlade Resharped: I need more Enchantment!!!) configurable-health|可配置的生命值* -create-better-fps +create-better-fps@createbetterfps create-security-program@ furnies@ cobblefurnies@ @@ -18881,7 +18881,7 @@ brewing-helper@ theatrical@ -@taczaddon +tacz-addon@taczaddon @heartcanister|心之容器 (Heart Canisters) gunswithoutroses-expansions@gunswithoutrosesexpansion-s|枪炮“没”瑰:扩展 (Guns Without Roses : Expansions) ati-structures-datapack-vanilla @@ -18890,13 +18890,13 @@ crazy-alloy spellbound-enchantments@spellbound - +ultramarine-rekindled|群青:重明* fortunas-anvil create-deep-dark@ create-ranged@ create-enlightend@ create-winery@ -create-filters-anywhere +create-filters-anywhere@ create-cheese@ create-copper-zinc@ create-ultimate-factory@ @@ -18930,18 +18930,18 @@ apothic-science-moa-stats ye-gamol-chattels-reborn@ -@my-more-enchantments|我的更多附魔 (My More Enchantments) +mme@my-more-enchantments|我的更多附魔 (My More Enchantments) @some-little-enchant|一些小附魔 (Some Little Enchant) minepulse@|一脉相挖 (Mine Pulse) -trinkets-and-baubles-reforked|饰品与小玩意:重铸 (Trinkets and Baubles Reforked) +trinkets-and-baubles-reforked@|饰品与小玩意:重铸 (Trinkets and Baubles Reforked) @mace_expand|绵阳的重锤拓展 (SunSheep's MaceExpand) astral-dimensions-create-compats cbc-more-shells|机械动力火炮:更多炮弹 (CBC More Shells) create-tools-n-weapons -@create_factory_logistics +create-factory-logistics@create_factory_logistics|机械动力:流体物流* wiidudes-custom-origin-mod@wiidudes-custom-origins wolfkin-origin@ horrorgins@ @@ -18964,24 +18964,24 @@ create-gunpowder-crafting luminax@ cable-facades@ easy-storage@ezstorage|简易存储* - +@tmwin|问君何所需 (TellMeWhatINeed) elrols-gui-elevator -@minecraft-found-footage +backrooms-found-footage@minecraft-found-footage exposure-polaroid@ +surpass@|超限* +fallingalchemy|坠落炼金 (FallingAlchemy) - - -fluxloading -inventory-swapping +fluxloading@ +inventory-swapping@ seamless-trading@ sophisticated-auto-walk mmmmmmmmmmmmpatch|试验假人补丁 (MmmMmmMmmMmmPatch) global-datapacks@datapacks -world-guard - +world-guard@ +more-murphy-pumpkins|更多南瓜 (morepumpkins) tetradelight -dragon-mounts-2 + heroes-of-might-and-magic-3@ another-changed randomly-tp|随意传送 (Freely/Randomly TP) @@ -19009,7 +19009,7 @@ gluttony-oversaturation-overeat overeat oversaturation -etheria +etheria@ legends-of-slugterra@ gussdx-decoration @@ -19041,26 +19041,26 @@ configbackuper@ inhrtburys-gilding|Inhrtbury 的镀金 (Inhrtbury's gilding) -@tree-miner|矿树节 +@tree-miner|矿树节 (Tree Miner) beyond-dimensions@beyonddimensions|超越维度* throughput-quest|ME吞吐量检测任务* lord-of-the-rings-qualities-of-middle-earth|魔戒:中洲特质 (Lord of the Rings: Qualities of Middle-Earth) lord-of-the-rings-qualities-of-middle-earth-1-7-10|魔戒-中洲特质:传承 (Lord of the Rings – Qualities of Middle-earth: Legacy) happy-ghast-backported -happy-ghast-backport +happy-ghast-backport@happy-ghast snowy-tents@ nuhayuponosiks-bosses goety-cataclysm@|诡厄灾变* -cave-delight +cave-delight@|洞穴乐事* @forkcart silentgear-compat timeless-ivy-reborn@|永恒常春藤:重生 (Timeless Ivy:Reborn) simply-more@simplymore -wetland-whimsy@ - +wetland-whimsy@|奇湿妙想* +@deputy-placement-switch|求你别放了 (NoG8Put) ileafcore dirt-tools-and-armor@|泥土工具与盔甲 (Dirt Tools and Armor) thaumic-rings-of-power|神秘众戒* @@ -19074,15 +19074,15 @@ screenshot-with-coords@|带标截图 (Screenshot with Coords) ccddi-damage-indicator@ccddi lidar @segmented -goety-gotta-summon-em-all +goety-gotta-summon-em-all@goety-gotta-summon-em-all! aerlune-rpg-2 origin-furs@ grounded-origins-forge@groundedorigins¨grounded-origins-fabric - +@sand-and-gravel-are-forbidden-to-fall|禁止下落 (Nofall) tinkers-tool-leveling-2@|匠魂工具升级2* remodifier-reborn|你别造了!!!* exporbrecall@|经验球召回 (ExpOrbRecall) -functional-economy|功能性钱币 (FunctionalEconomy) +functional-economy@functionaleconomy|功能性钱币 (FunctionalEconomy) ranckors-rings-of-power|Ranckor的力量之戒 (Ranckor's Rings of Power) lotr-improvements-lite|魔戒改进:轻量版 (LOTR Improvements Lite) @@ -19099,7 +19099,7 @@ immersive-armor-hud@|沉浸式盔甲 HUD (Immersive Armor HUD) yeast-n-feast@ - +vampirism-tinker|血族工匠 (VampirismTinker) essentia-pipes@|源质管道* msp-backpacks @@ -19108,7 +19108,7 @@ salis-arcana@ thaumic-mixins sanguimancypatch sanguimancy -jungle-villager-trader +jungle-villager-trader@ origin-furs-unofficial-forge-fork@origin-furs-unofficial-forge-port ephemeral-wither-skulls@|消逝的凋灵之首* fox-friend@|狐友* @@ -19135,7 +19135,7 @@ economy-api@ no-enchantment-cap-level@ -create-ultimine +create-ultimine@ serene-wild@ kitchen-decorations refined-storage-curios-integration@ @@ -19148,15 +19148,15 @@ refined-storage-mekanism-integration@|精致存储 - 通用机械集成 (Refined lukis-ancient-cities -ungebauts-wildlife +ungebauts-wildlife@ changed-mcreator-slop forgotten-battle-towers @back-at-it-again summer-cottage@ marius0x-furniture youssefs-currency@youssefs_currency -bibliobiomes-legacy -sniffers-delight +bibliobiomes-legacy@ +sniffers-delight@sniffers_delight easters-delight@ @@ -19169,24 +19169,24 @@ create-advanced-logistics@createadvlogistics create-electric-drive @lachryvisions the-heaven-sword-and-dragon-saber|倚天屠龙 (The Heaven Sword and Dragon Saber) - +dreamtinker@|工匠幻梦 (DreamTinker) wolfs-enchantment-lib|狼王的附魔库 (Wolf's Enchantment Lib) - -disturb-not-my-dream@|勿扰吾梦 / 起床气 +@caoplus|槽 (CaoPlus) +disturb-not-my-dream@|勿扰吾梦 / 起床气* @contigency-shop-lite|轻量兑换所 (Exchange Site Lite) -lootbagsrenewal|战利品袋重制版 (LootBagsRenewal) +lootbagsrenewal@lootbags|战利品袋重制版 (LootBagsRenewal) - -@no-elytra-mod +autoswaplowdurabilityelytra|自动鞘翅替换 (AutoSwapLowDurabilityElytra) +no-elytra-equip@no-elytra-mod no-elytra title-changer@ - -itemzoomer +clickthrough-plus@clickthrough+|穿牌开箱+ (ClickThrough Plus) +itemzoomer@ @surveystones -particular-reforged +particular-reforged@ cloud-revive@cloud_revive|云生* paranoia|偏执狂* thaumic-brewing|神秘酿造学* @@ -19202,9 +19202,2759 @@ sophisticated-storage-create-integration@ @removespeeddetection|移除服务端速度检测消息 (RemoveSpeedDetection) shared-advancements@ @polytime -wild-growth +golden-meadows ssmlegacy@ super-sound-muffler-revived current-game-music-track@ -create-mobile-packages -create-cardboarded-conveynience@ \ No newline at end of file +create-mobile-packages@|机械动力:无人机物流 (Create: Mobile Packages) +create-cardboarded-conveynience@ +blacksmith-weapon|铁匠武器 (Blacksmith_Weapon) + +mrqx-s-slashblade-core@mrqx%60s-slashblade-core|墨染琴弦的拔刀剑核心 (mrqx's Slashblade Core) +goldblade|神刀 (GodBlade) + +shanghai-metro-line-one-mod@|上海地铁一号线* + + +cat-burger|猫猫汉堡* +mineral-backpacks@ +@furry_bohe|兽薄荷 (Furry Bohe) +narcissusfarewell-reflorescence|水仙辞:重绽 (NarcissusFarewell: Reflorescence) + +bibliocraft-bibliowoods-forestry-edition +bibliocraft-bibliowoods-biomes-oplenty-edition +bibliocraft-bibliowoods-natura-edition +bibliocraft-bibliowoods-terrafirmacraft-edition +bibliocraft-bibliowoods-highlands-edition +bibliocraft-bibliowoods-extrabiomesxl-edition +@blockprintr + + +assorted-tools@ +assorted-core@ +tlotd@ +infinity-golem-boss-fight-beta@infinity-golem-boss-fight-(beta) +actually-divisions +extra-trinkets@ +portable-tanks +happier-ghasts@ +tinker-in-caves|洞穴工匠 (Tinker in Caves) +@better-tfcr|更好的群峦:重生 (Better TFCR) + +mobreaker|坚壁重构* +man-from-the-fogs-delight|雾中人乐事 (Man From The Fog's Delight) +forsaken-items@|被遗忘的物品:重生 (Forsaken Items Reforked) +@soybean-fun-item|豆浆你提我造 (Soybean Fun Item) +epic-fight-mesh-model|史诗战斗网格模型模组* +monster-of-the-night-skies@ +spooky-doors@ +@mclientapi +moffs-addonapi-dynload@addonapi +@shiv +@tab-list +dokimobs@|萌化生物 (DokiMobs) +bookling-gear@ +neuracraft@|神经工艺 (NeuraCraft) +moves-like-mafuyu@|高速企鹅* + +vanillin@flw-vanillin +chain-mining-reforged@chainminingreforged|连锁采集重铸* +hrtffix@|HRTF修复 (HRTF Fix) +iammusicplayer-renewed@iam-music-player-renewed +misc-tweaks@misctweaks +ftb-filter-system +neonium@ +@super-fast-math + + +simple-loot-viewer@ + +greg-tech-chisel|凿子 GTNH 版 (Chisel - GTNH / Greg Tech Chisel) +baubles-expanded-gtnh|饰品栏 GTNH 版 (Baubles-Expanded-GTNH) +gregs-tinkering +metaworlds-mixins|元世界 - Mixin 重制版 (MetaWorlds - Mixins) +create-cobblemon-potion@|宝可梦动力药水* + +jarjar@ +javascriptjs@ +blood-n-particles-mod +@sstool|绵阳的特效工具 (Sun Sheep's Tool) +xiaofanweis-items|小烦维的物品 (xiaofanwei's items[Iron's Spellbooks and Terra Curios]) + +easycolony-addon-for-minecolonies|简易殖民地 (Easycolony) +blades-derby@|骐骥刀剑风云* + +create-simple-generator@|机械动力:简单发电机 (Create: Simple Generator) +sophisticated-backpacks-create-integration@ +ticex-tinkers-construct-ex@ticex +eternal-starlight-vanilla-ores@eternal-starlight-vo +supercritical +supercritical-gtqt +gtconsolidate@ +implosionnobomb@ +villager-golem-healer@ +yakurum-mod +@fantasytools +metaworlds-mod|元世界 / 变幻世界* +create-dragons-plus@|机械动力:龙+ (Create: Dragons Plus) +appliances¨@appliances-mod|Appliances - 已失效 +@twelvefold-booter +omniconfig@ + +@linearxp-legacy|线性经验值 (LinearXP) +@better-thunder|更好的雷声 (Better Thunder) +@enhanced-packet-compression|增强数据包压缩 (Enhanced Packet Compression) +gnetum@ +accessible-step@ +terrastorage-icons +terrastorage@ +@euphoria-companion +jump-bridge-mod +huskhomes +blahajasm@ +mireoles-gtceu-additions@ +expanded-ae +modular-machinery-reborn-ars-nouveau@ +bonsai-crops@|盆栽作物* +playing-cards-chips|扑克牌与筹码 (Playing Cards & Chips) +enderdrives@ +lucky77 + +i-dont-see-you@ + +epic-fight-valour-guard +sweet-calamity@ +vanillabackport@ +stylish-stiles@ +stylish-stiles-renewed@ +undermod@mystronics-undermod +spitting-image@ +@repo-heads + +@create-blueprint-tweaks|机械动力蓝图微调 (Blueprint Tweaks: A Create Addon) +mob-journal@|生物图鉴* + +sdm-shop@ +economy-coins-bills-and-gems +multi-hotbar-core +multi-hotbar|多重快捷栏 (Multi-Hotbar) +properties-panel@propertiespanel|属性面板* +whimcraft@|奇思妙想 (WhimCraft) +recipeconflict-fixer|合成冲突消除:修复版 (RecipeConflict Fixer) +dooglamoo-painter-mod|Dooglamoo 的绘画* +notafreaks-plushies@ +enchanting-vanilla@|原韵新生* + + +quest-items-rpg@quest-items +experimental-mobs +advanced-combat-patched|高级战斗装备修复版* +@authproxy +ezstorage-2-patch|EZ 存储 2 补丁 (EZStorage 2 Patch) +squake-patched +immersive-hotbar@ +create-classic-blaze-enchanter@|机械动力:经典烈焰人附魔室 (Create: Classic Blaze Enchanter) + +ifpatcher@|工业先锋补丁 (IFPatcher) +sakura-orihime|樱:织姬 (Sakura-Orihime) + +tinkers-antique|匠魂怀古 (Tinkers' Antique) +ex-fusile +better-player-respawn + + +another-experiment-researcher +spiders-1-999 +villager-comfort-updated|村民舒适度更新版* +flight-affinity@ +emcgadgets + +@minecraft-engineer-edition + +custom-armor-bar@ +roughly-enough-input-methods@ +pirates-doom@ +techrebornjei@|科技复兴JEI (TechRebornJEI) +little-joys@ +blasting-raw-metal-blocks-into-blocks-fabric-forge@blasting-raw-metal-blocks-into-blocks + +@re_mob-control-wands|重制:怪物控制魔杖 (Re: Mob Control Wands) + +nekoration-reborn|猫咪装饰:重生 (Nekoration-Reborn) +mine-reputation@|失去的声望* +ruined-lighthouse +frogs-medieval-paintings +bad-apple@ +podium-sodium@podium +compressed-create-recipes +aether-overworld-ores@ +slashbalde-useful-addon|拔刀剑实用扩展* + +powerutilites-unofficial|电力转换非官方版 (PowerUtilites Unofficial) +@gameruleplease +custom-fov@custom-fov-illusive¨custom-fov-fabric +apotheosis-x-irons-spellbooks-compat +customtab@ +nightmares|噩梦* +graveyards +sky-arena@demis-sky-arena +progressive-mechanics-library@ +@shy + + +tradepreview@|交易预览 (TradePreview) +enhanced-celestials-shader-support@|月亮事件光影支持* +incendium-biomes-only +thaumic-tweaks@ +selectivebounds@ +@bactromod +@exit-on-startup|一启动就退出 (Exit On Startup) +@kotlin-for-forge-unofficial +@circlemc +nearbymobchecker@ +gputape@ +@limitless-anvil|铁砧限制修复 (Limitless Anvil) +carry-on-extend@|搬运:拓展* +kaleidoscope-doll@|森罗物语:玩偶* +kamen-tinker|蓝钢匠艺/假面匠艺* +agritech-automated-crops@ +@pots-idea|脑洞瓦工 (Pot's Idea) +kogtyv-tav@|Kogtyv 的城镇与村庄 (kogtyv-Towny and Village) +gothic-church|哥特式教堂* +sky-whale-ship@ + +no-mans-land@ +bindable-mana-pool@|可绑定魔力池* +@blood-mending +craziness-awakened@craziness-awakened-(orespawn-remake) +mischief-illagers@ + +dynamic-trees-caverns-and-chasms|动态的树:Caverns & Chasms 附属 (Dynamic Trees - Caverns & Chasms) + + +apex-a-proper-equipment-expansion +dimension-of-caves|Alex的洞穴维度 (Dimension of Caves) +herobrine-a-friend@ +stattweaker@ + + +atsassistmod +@i-cant-believe-its-not-1215 +@slbcf|服务器列表缓冲崩溃修复版 (ServerlistBufferCrashFixer) +dynamic-tooltips|动态提示框* + +mixed-litter@ +groovyduvet@ +create-egg-production@createegg-production|机械动力:鸡蛋产线 (Create: Egg Production) +create-fishery-industry@|机械动力:渔业 (Create: Fishery Industry) +create-rustic-structures@|机械动力:乡村结构 (Create: Rustic Structures) +slightly-newer-art-of-forging +custom-treasure-maps@ +evolved-mekanism|通用机械:进化* + +brain-delight|脑子乐事* +villager-library|村民图书馆* +trade-enchant-overhaul +more-additional-paintings@ +simplebackport@ +biomebeats@ +decorative-core@ +@key-bindings-keeper|按键绑定不减 (Key Bindings Keeper) +@mekanism-output-faster|通用机械加速输出 (Mekanism Output Faster) +@infinite-capacity-cell|无限容量元件 (Infinite Capacity Cell) +@mcutilities +pickuptorches@ + +pointblank-magazines-vpb +pointblank-projectiles-vpb +deathly-hallows@|死亡圣器* +tetra-loopback@ +appliedenergisticsoddities +tweakermoreforge@ +vs2-cbc-armor-blocks +create-integrated-farming@|机械动力:集成农业 (Create: Integrated Farming) +ring-of-blink-forge|闪现戒指 (Ring of Blink) +golem-overhaul@ +voile@ +title-tweaks@titletweaks +ugoblock|动块 (UgoBlock) + + +scriptasm + + +aimbot|瞄准修正 (Aim Bot) +@server-chat-log-history +hud-compass + +familiar-friends@|亲密伙伴* +golem-spawn-animation@ +realistic-night-vision +stackable-items + +discrafted@ +survival-plus-plus@ +server-plus-plus@ +happier-ghast@|更好的快乐恶魂 (Better Happy Ghast) +bhgc@|更好的快乐恶魂控制 (Better Happy Ghast Control) +@dominatus +unfocused@ +jerotes-warehouse|Jerotes仓库* +happy-ghast-legacy@ +spider-jockey@ +digger-helmet@ +acecraft + +@familiar-magic +lumenized|流光溢彩* +chili-bullet-weapons@ +applied-web-terminal@|AE网络终端* +@dusts-keymap +itemzoommore +@noitemtooltips +shuttfup|Stfu - 已停更¨@shuttfup +baublesreforked@|饰品重铸 (BaublesReforked) +@wills-lively-villages +tmrv@ + +twilight-treehouses@ +improved-rails@|更好的铁轨(可转弯の动力铁轨!) (Improved Rail (TURNABLE POWERED RAILS)) +redstone-signal-bind-wand@|非Create版无线红石* + +@ingametranslator|游戏内聊天翻译 (In game Translator) +carnivorous-plants-addition +mekanism-curios +tetra-compat +hex-casting-additions +ghasts-crash + +futurecherry@|未来的樱花 (FutureCherry) +food-fix +mining-speed-tooltips@ +smithing-template-viewer@ +picks-of-power@ + +super-hot +chill-thrill +pyrellium@ +luminous-nether@ +brush-mining-machine|刷矿机 (Ore Generator) + +@night_vision_amulet|夜视护符 (NightVisionAmulet) +set-effects|套装效果* +tramways@ +projecte-kubejs +actually-additions-v2-unofficial|实用拓展 V2 非官方版* +create-ender-link@ +progressive-tweaks +portable-blueprints@ +carrasconlib@ +blockparticlespawning@|方块粒子生成 (Block Particle Spawning) + + + +aether-dragon-dragon-survival-addon +tundra-dragon-dragon-survival-add-on +naturalist-delight +civilizations@ +block-factory-biomes@block-factorys-biomes +underwater-swim-fix-mc-220390|水下游泳修复 (Underwater Swim Fix) +scaffolding-backported +hold-my-items-reforged@ + +distraction-free-recipes@ +seeking-immortals-nightmare@|月之石:噩梦之章/求仙者:噩梦之章 (Moonstone :Nightmare/Seeking Immortals : Nightmare) +enchanted-crystal-arrow|魔法水晶箭 (Enchanted Crystal Arrows) +touhou-danmaku-kagura-core|东方弹幕神乐核心 (Touhou Danmaku Kagura core) +hah-ueuh@hah!-ueuh +eternal-nether@|永恒下界* + + + +gui-followers@¨gui-followers-fabric +omnidirectional-sound-ods@omnidirectional-sound-(ods) + +diligentstalker@|勤劳跟踪狂 (Diligent Stalker) +maid-useful-tasks|女仆实用任务 (Maid Useful Task) +@titlechanger-next|标题切换器 Next (TitleChanger Next) +embers-underground-rooms@ + +dontstarve4@|不要饿死4 (Don't Starve 4) +spark-core@|星火核心* +nether-core-useful-nether-rock|下界核心 (Nether Core) +slashblade-js|拔刀剑 JS (SlashBlade JS) +caerula-arbor@|方块与深蓝之树* +alices-doll|元歌 (Alice's Doll) +bedrock-redux@|基岩再现* +item-entity-control|掉落物清理* +wishing-fountain|许愿泉* +@unbelievable-enchantment|令人不可置信的附魔 (Unbelievable) + +we-do-copyright@ +useful-clipboard +seeking-immortals-virus@seeking-immortal-virus|月之石:基因计划/求仙者:基因计划 (Moonstone :Virus/Seeking Immortals :Virus) +gambler-illager-irons-spells-and-spellbooks-addon +chimericlib +pokecube-alternative +@intuitive-netherite +create-propulsion@|机械动力:推进工程 (Create: Propulsion) + + +event-wrapper@|事件包装器* + +@modwhitelist|模组白名单 (ModWhiteList) +@instant-build|瞬时建造 (Instant Build) +beacon-conduit-tweaks|信标与潮涌核心调整 (Beacon & Conduit Tweaks) +tfc-tools-trim@|群峦传说:工具纹饰 (TFC Tools Trim) +@kill-youkai-with-knives +mo-bees@|更多蜜蜂 (Mo' Bees) + +faux-custom-entity-data@ + +poopsky@|空中厕所 (PoopSky) +@solarus-classic + +create-unlimited@ +skeleton-ai-fix@ +climate-rivers@ +dungeonsdelight@dungeons_delight|地牢乐事 (Dungeons Delight) +cleanroom-relauncher-unofficial@|Cleanroom 模组式启动器非官方版* +silents-power-scale@ +trident-return-fabric +resource-world@|资源世界* +ijm-tweaks@ +no-mans-delight@ + +applemilktea2-tweaked +@carpet-blue-addition +@yaca|毯上添花 (Yet Another Carpet Addition) +@steel_myfirstmod|钢铁 +movie-discs|Minecraft 大电影插曲唱片 (A Minecraft Movie Discs) + + +materials-integration|资源统合* + + + +modular-machinery-reborn-create +sheep-variety@ + + +@tetraclip|易验锻造 (TetraClip) +@rozhud + +mc-mods-in-zero@all-in-zero|一切于零 (All In Zero) +@yunzhu-transit-extension|云竹交通拓展 (Yunzhu Transit Extension) + +goodbye-dirt-screen@|澄清素视 / 去你的泥土屏幕* +create-stock-bridge@ +create-escalated@escalated +create-mechanical-confection@createmechanical-confection|机械动力:蛋糕工坊 (Create: Mechanical Confection) +farmers-delight-compat@ +gtwoodprocessing@gtwp + +vintagedisplay@ +voidless-framework +mixin-args +melonlib@ +@no-kebab +mechanicals-lib@ +create-mechanical-cow@ +global-options@ +kubejs-projecte-fork@ +untoggle-sprint@ +vndialog@nvdialog|VN对话框引擎 (VNDialog) +@elytrabugfix +@ezbowls +soundtrack-events +commandkeys@ +iron-chests-with-netherite-chest@iron-chests-(with-netherite-chest) +emerald-on-a-stick-villager-follower@emerald-on-a-stick-(villager-follower) +fishing-indicators@ + +just-another-witchery-remake@ +@inexorable-origins +cybersus@ +@passivearmour +zettai-rpg@ +md-units@md-unit-robots +extreme-core +dreamless-spells-and-spellbooks +gregtech-meowmelmuku|格雷科技非官方版-喵呜酱版 (GregTech-MeowmelMuku) +@namedloot +@enchantment-decay +create-jet-boots +train-deco@|机械动力:地铁列车装饰* +packed-packs@ +dyed-flames@ +hopper-gadgetry@ + + +quickcopy@|迅摹天工 (QuickCopy) +item-informations|物品信息* +shootscreen|拍屏 (ShootScreen) +@server-expansion|扫地小狐 (Server Expansion) +@valkyrien_skies-defense|瓦尔基里:防御 (Valkyrien_Skies: defense) + +tinkerslashblade-patch|刀锻冶匠魂修复 (TinkerSlashBlade Patch) +alienevo + +banit|干掉它! (BanIt) +alexs-mobs-naturalist-compat@ + +tinkers-ingenuity|匠心之作 (Tinkers' Ingenuity) +curiosities@|趣味物品* + +@cs2_c4|Qis的C4 (C4) +bomb-disposal-expert@bde|拆弹专家* +sprintify|长疾速适* +shield-system +@burythelight +command-aliases@ +pigium-performance-mod +spectrumjei@|光谱世界JEI (SpectrumJEI) +@itemban +@skiesclear +vminus@ +@villagertalk1 +debug-utils-forge@debugutils¨debug-utils-fabric +slimefun-essentials-port +uma-little-maid@uma_maid|马儿小女仆* +i-like-wood-twilight-forest-plugin +i-like-wood-oh-the-biomes-youll-go-plugin +i-like-wood-immersive-engineering-plugin +click2pick@ +hudless@ +dungeons-perspective +miniutilities-flight-fix@ +linked-chests@new-linked-chests +create-direct-chute@direct-chute +create-packagers-psi-compat@packagerspsic +create-more-packages@ +create-extra-gauges@extra-gauges +ships-legacy +traders-backport +banner-patterns-backport@ +convenient-effects@ +baguettelib +fast-event@fastevent +@citreforged +better-meteorite|更好的陨石* +paragraphs-reforged@ + +tpsbooster +custom-portal-api-reforged@ +forgedpaginatedadvancements@|分页进度 (Neo)Forge 版 (ForgedPaginatedAdvancements) +colorfulanvils-reforged@ +keymap-maintained +create-mechanical-companion@ +crazy-ae2-addons@ +schizophrenia|精神分裂症* +@mobswithpowersaddon +wolf-sword-mod@ + +charged-mobs@ +the-mangled-hound +avp@ +@giantess-toki +enchant-limiter@ +crafter-backport +citadel-fix|Citadel优化修复* +knit +gallant-tireless-altruist@|热心市民* + +cerulean@cerulean-advancements +color-format + +swim-in-lava +orevolution@ +even-better-nether@ +plastoid-armor@ +corpse-x-curios-api-compat@ +@trinkets-canary +@a-strange-world-we-live-in +spellhud-addon@ +replication-ae2-bridge@ +@deltarune-mod +eternalpotions@ +@l-mans-cat-clocks +life-leech-enchantment@lifeleech|生命汲取附魔* +sakaz-hand|奇语之手* +clayium-unofficial@|粘土工业非官方版* +mekanism-weaponry +wardends-revenge +mob-control-rod +unlimitedperipheralworks@ +particle-effects@ +verdant-wonders@ +underlay@ + +lazy-item-generator|咸鱼物品生成器* +crab-street-light@|螃蟹的路灯* + +undertale-delight@|决心乐事* + +@origins-algorithmic-phantom|起源:算法幻影 (Origins:Algorithmic Phantom) + +spawn-egg-information@|刷怪蛋信息显示 (Spawn Egg Infomation) +calypsos-candy-workshop@|卡里普索的糖果工坊 (Calypso's Candy Workshop) +fill-tool +growth-tonic +quivermob + + +world-drop + + +shell-armor +cart-livery +territorial-dealings +potion-packs +armor-smelter + +carpet-sls-addition@sls-addition +@fsu|(Neo)Forge 常亮绊线特性恢复 (Forge String Unpatched - Powered Permanent String) +quests-kill-task-tweaks + +immersive-messages-api@ +immersive-damage-indicators@ +easy-disenchanting@ +respect-my-trims@ + +cloudgolem-edited|湫渃云魔修改 (CloudGolem Edited) +maidaddition +webcam|网络摄像头* +projecteteams@ +battle-frenzy|越战越勇* +@plane-advancements +cosmopolitan@|四海一家* +redtassel|红缨 (redtassel) +sridr|拔刀剑锻造增伤改革 (SlashBlade Refine Increased Damage Reform) + +mianbaos-modernwarfare|面包军火库 (Mianbao's ModernWarfare) +creator-sword@|机械动力:机械师之剑 (Create: Creator Sword) +create-limited-draining@|机械动力:流体抽取限制 (Create: Limited Draining) +create-no-touching +create-aether-industry|机械动力:天境工业* +create-entitycontroller@create-entity-control|机械动力:实体控制 (Create: Entity Control) +better-advancements-blessed-edtion|更好的进度 - 祝福版 (Better Advancements - Blessed Edtion) +@gloom-raiders +herobrine-prime +legend-of-the-dwerden@ +dnt-stronghold-overhaul-lite-edition +dnt-nether-fortress-overhaul +gtfocraftprotocol@gtfo-craft-protocol + +keerdm-zombie-apocalypse-essentials@keerdm-zombie-apocolypse-essentials +chovys-apocalypse@ +bexlas-enhanced-ores +starve-library@|饿死图书馆* + + +infinite-refills|无限续杯* +systrace@|一导即出* +fluxed-core@ +myluckyblock@|我的幸运方块 (MyLuckyBlock) + +backpack-plus|背倍加* +@hydration-mod + +defiled-lands-reborn@|污秽之地重置版* +@copper-bulb-early|铜灯补充 (Copper Bulb Early) +future-fireproof@|未来防火* +@low-durability-switcher +dimensional-potions +ghasts-drop-records|恶魂掉落唱片* +ftb-chunks-compatibility-mod@ +charm-forked@ +early-bedtime@ + +instant-picking@ +@quick-craft +kubejs-curios +copper-carrots@ +tinkers-jewelry@|工匠珠宝 (Tinkers' Jewelry) +thief@ +applied-schematicannon +more-functional-storage@|更多功能性存储* +vital-herbs + +hbm-ntm-structure-1-12-2 +illusioners +cataclysms@ +bas-cave-enemies +sniffer-ride +aesthetic-shelving +arboreal-nature@ + +warborn-resurgence@warbornresurgence +@ceres + +super-creeper|智能爆破专家 (Creeper Super) +expanded-experience +reworked-phantoms@ +ex-nihilo-fabricio@ +hybrid-delights@ +create-clothes@|机械动力:服装 (Create: Clothes) +create-delivery-director@delivery-director +create-cardboard-things@ + +watkinsbase + + + +toggleable-enchantments@ + + + +scape-and-run-parasites-deep-sea-danger@ + +craft-in-shadow +@jewelryz +better-armory-mod@seths-better-armory +twilight-forest-dungeons-villages@the-twilight-forest-dungeons-villages|暮色森林 - 地牢与村庄 (Twilight Forest - Dungeons & Villages) +mice@ +ancient-golems@|上古傀儡* +model-citizens +botanical-machinery-extra@tPTR9GcJ +flowerary@ +extrabotany-reburn@extrabotany|额外植物学:重燃 (Extrabotany: Reburn) + + + +umapyoi-delight|马儿跳乐事* +lne-rogues@ +lne-archers@ +lne-wizards@ +lne-paladins@ +@protomanlys-weather +tinkers-mechworks-legacy +bbl-core@ +cooparticlesapi@|空栈粒子框架 (CooParticlesAPI) +fdlib@ +potion-icons@potionicons +catvision +spectre-things@ +chatplus@ +ritual-enchanting +peaceful-progression@ + + +usefulmagic@|实用魔法 (UsefulMagic) +keepinventorynext|死亡不掉落增强 (Keeping Inventoiry Next) + +tinkers-jewelry-ex|工匠珠宝拓展 (Tinkers' Jewelry EX) +lovely-sparkle-pieces|璀璨饰品* + +yuns-weather@ +qliphoth-awakening@ + + +universal-mod-localizer@universal_mod_localizer|通用模组汉化器* +smart-creeper@|智慧苦力怕* +peaceful-depletion@peaceful_depletion|和平消耗* +@everything-is-tnt +structure-pool-api@ +create-bluemap@ +tragicmc-2-continuation|悲惨世界2:延续 (TragicMC 2 Continuation) +nebula-parasites@ +bonsai-trees-unofficial-extended-life|盆栽非官方延续版* +@its-as-shrimple-as-that +worldedit-items@ +slashblade-skin@|拔刀皮肤 (SlashBlade Skin) +iron-door-key@ +just-enough-fishing-jef@just-enough-fishing-(jef) +containersearch@container-search +mousepets@ +backported-minecarts@ +simple-incubator@ +amethyst-stairs-slabs-and-walls +@vessels + +legendary-weapons-byititor@legendaryweapons +slender-reborn +craft-vania-lite +pollution-of-create +create-chocolate-fountain@|机械动力:巧克力喷泉 (Create: Chocolate Fountain) +create-currency-shops@|机械动力:货币商店 (Create: Currency Shops) +create-nether-industry@|机械动力:下界工业 (Create: Nether Industry) +backported-diamond-ore-gen +tfthreadsafetyaddon +serverautosaver +infinistack@|无限堆叠 (InfiniStack) +powerful-dummy|强力假人* +beyond-bosses@beyondbosses +mini-utilities-y +concoction@concoction! +zombietactics2@ +radiation-zone-reborn|辐射荒原:再行 (Radiation Zone: Reborn) + +@crumbling-hearts + +dye-the-world@ +modernmarkings@ +hololive-product@ +fetzis-displays@ +shadered@ +sweety-garden@ + +@twilight-forest-aurora-palace-regeared +changshengjue|长生诀 (ChangShengJue) + +medieval-buildings-nether-edition +@ribbits-croaks +echoes-of-time@ +horror-alexbrine +zzz-craft|绝区零·星见雅 (ZZZ Miyabi) + +cozy-cabins-and-cottages@ +servereye +deepseek@deepseek-neoforge +ai-command|AI指令生成 (AI Command Generator) + +flighthud-do-a-barrel-roll-fix + +fast-container-placement@|快速容器放置* +@teleport-mod +@what-meme +crackers-wither-storm-mod-v-sides-soundtrack@ + +roberts-game-tweaks|Robert的游戏调整 (Robert's Game Tweaks) + +@datapack-load-error-fix|数据包错误修复:重生 (Datapack Load Error Fix Reborn) +@whats-this +@additional-jewelryz +tacz-tes-compat@ + + +directionhud +raw-netherite-reforged +blast-travel-reborn@blasttravelreborn|旅行火炮·重生* +shanxi-skeleton@shanxiskeleton|山西骷髅* +@gunpowder-overhaul|火药改革 (Gunpowder Overhaul) +smoking-stuff-a-wizards-hobby +@alloy-smelter +@autocrafter-early +time-in-a-bottle-charger@ +just-a-sculk-vial@ +lockylocks@ +server-i18n-api@ +fragmentum-forge@fragmentum¨fragmentum-neoforge¨fragmentum-fabric +@monument-data-fix|Monument 数据修复 (Monument Data Fix) +@better_tp +waystone-currencys@|消币则至 / 传送石碑:货币 (Waystone: Currencys) +@armor-stand|盔甲架 (ArmorStand) +threadtweak-reforged|ThreadTweak (Neo)Forge 移植版 (ThreadTweak Reforged) +@environmental-creepers-backport +com-bat +touhou-little-maid-rpg-class-task + +heat-and-climate-patch|热量与气候修复* + +@clientupdater + +modern-lucky-block@ +foxrecipemaker +@global-mob-health-multiplier|全局生物血量倍率 (Global Mob Health Multiplier) +chatbox-for-mc@chatbox|对话框 (ChatBox) +perfectly-balanced-bows +@playerex-alcohol|PlayerEx 酒精版 (PlayerEx: Alcohol) +hak-minepiece +grease@ +cleaver-compedium +phayriosis-parasite-infection +srp-calamity-catastrophe-cataclysm@|逃寄:无妄之灾 (SRP: Calamity Catastrophe Cataclysm) +moss-overgrowth +create-pillager-arise +create-qol@ +create-remote-terminal@|机械动力:远程终端 (Create: Remote Terminal) + + +songs-of-war@|战争之歌 (Songs of War) +cataclysm-summons@ +cutepuppymod@ +giant-swamp-tree@|沼泽巨树* +big-oak-tree@|大橡树* +giant-tree-stump|大树桩* + +polars-mad-tweaks@ +cobblemon-ranked@|方块宝可梦-排位赛* +eiramoticons +horse-breeding-fix@ +tablistextension@|TAB 列表扩展 (TabListExtension) +rally-of-the-guard-guardvillagers +bncci +@recipe-maker +portaltransform@|传送门嬗变 (PortalTransform) +@a555sd|更多的画 (More Paintings) +unusual-furniture@ +xiaoanmooncake@|小安的月饼狂热 (Xiaoan's Mooncake Fever) +foragers-insight@|巧觅巧食 (Forager's Insight) +palestinian-delight@ +mekanism-more-machine@mekanismmoremachine|通用机械:更多机器 (Mekanism: More Machine) +custom-battleroyale@|自定义大逃杀 (Custom BattleRoyale) +improved-pillager-outpost +rebalanced-enchanting@ +dark-doppelganger@ +enders-spellsandstuff +hazen-n-stuff +spongeforge@ + +@don%22t-die|补药趋势啊! (Don't Die!) +sdm-market@ +@omnilook + +survivalist-essentials@ +villager-trading-plus@trading+ +mending-fix|经验修补修复* + +colorwheel@ +tlib@ +smoothscreen@ + +@fuzz +advanced-loot-info@|高级战利品信息显示* +resourcepackcached@ +aotake-sweep|竹叶清* + +effectual@ +cold-light@|冷晖之枪* + +huanles-weapons@|Huanle的武器 (Huanle's weapons) +@clay-soldiers-remake +threateningly-mobs@ +basic-spinosaurus +european-wildlife +marvelous-menagerie@ +roman-weaponary +compat-structure@ +flower-bed@ +archery-expansion-squared +create-more-linked-remote@ + + + +gambagoons-better-emeralds +ingameime-vintage@|游戏内输入法-1.12.2 (IngameIME-Vintage) +extreme-sound-muffler-legacy@ +amazing-trophies + + +global-server-configs@global-server-config|全局服务器配置* +expandedbonemeal +@aireplyassistant|AI回复助手 (AIReplyAssistant) +utiljs +moon-info-updated@ +@server-market|服务器市场 (Server Market) +nbt-tooltip@nbttooltip +delicate-dyes@ +@modernmixins +sdm-economy +sdm-ui-lib@ +mcllm¨@mcllm|MCLLM - 审核中 +@inventory-pause-yipeekiyaay +chestsearch|容器检索 (ChestSearch) +clicktor|Clicktor连点器 (Clicktor - Auto Clicker) +extended-bone-meal@¨extended-bone-meal-fabric +show-me-the-enchantment|怎魔,你不附器* +better-sign-edit@ +disc-recipes@|唱片合成 (Disc Recipe) + + +scoutreforked@|侦察兵 重构 (Scout Reforked) +immersion-in-traffic-context@|沉浸交通道路 (Immersion in Traffic Context) +arknightsfurniture@|明日方舟家具 (ArknightsFurniture) +gunsmith-lib +@emixx +experiencelib@ +mystical-mobs + +progressive-difficulty-chaos-integration@ +ottapomos-scps +scp-foundation-requisites@ +thermal-construct@ +war-wither|战争凋灵* +status-effect-bars-reforged +dark-tower-maze + +the-occult +wolftailui@ +better-client@better-client-enc|更好的客户端* +clientharvest@ +auto-afk@ +particle-effects-reforged +hold-my-items-refoxed +dont-claim-our-structures +cosy-critters-creepy-crawlies@cosy-critters-creepy-crawlies-neo +thin-air-ad-astra-compat +yyzsbackpack@yyzs-backpack|yyz的背包 (Yyz's Backpack) + +vital-delight + +@mining-in-place +pathfinder-navigation|寻路者导航* +adorable-eggs@ +food-addition@ +everything-totem|万物皆可图腾* +thaumic-renaissance@thaumcraft-renaissance +@executive +@make-i18n-great-again +steve-goes-fishing@ +voltaic-api@voltaic +boat-delete-begone +zs-packet +raise-them-hotbars +quick-leafdecay +footprint-particle-returns +inventory-crafter-returns +colorwheel-patcher@ + + + +@llpswsounds|llpsw的大喇叭 (llpswsounds) +difficulty-lock@¨difficulty-lock-fabric +ryymr@ +@lazybeefix + +lazy-ae2-tweaks +coordinates-hud@coordinateshud +just-enough-botania@ +fumo-fumo +touhoupixelcanteen-a-farmers-delight-plugin@touhoupixelcanteen|东方像素食堂 (TouhouPixelCanteen) +perfect-parity-the-garden-awakens-edition@ +perfect-parity-spring-to-life-reforged +hypertubes@hypertube|机械动力:超级管道 (Create: Hypertubes) +perfect-parity-spring-to-life-edition@perfect-parity-1-21-5 +create-geography@|机械动力:地理学 (Create: Geography) +create-apothic-enchanting@ +demis-enigmatic-dice@ + +@mpem|月夜性能优化 (Moonlit Performance Enhancement Module) +@eventbenchmark|事件性能测试 (EventBenchmark) +no-more-garbage-in-cell +pick-up-filter-puf@pick-up-filter + +identity-fix-morph@identity-fix + +stick-it@ +aimod +srp-reset-mod|逃逸寄生体:重置 (SRP Reset) +legendary-relics|传说遗物* + +true-power@|真正的力量 (True POWER) +ender-villages|末地村庄* +twilight-forest-villages@|暮色森林村庄* +poland-nature +amethyst-extra +villagers-windmill +extended-wrenches@ +why-stack-only-64|为什么才64个呢? (Why Stack Only 64?) +@digcooldown|挖掘冷却控制 (DigCoolDown) + + +potentials@ +deltaboxlib@ +gobbobs-animated-blocks|Gobbob 的动画方块 (GobBob's Animated Blocks) +glowingfootsteps|发光足迹粒子 (GlowingFootsteps) +simply-swords-overhaul@ +farms-and-foods@farms-foods +@achievement-fix|成就修复 (Achievement Fix) + +lang-magic-tweaker +spawn-dimension-choice@|诞生维度自选* +reforge-stone@|重铸石* +numericalbotany +cameraoverhaul-vintage@ + + +advanced-hoppers + +ars-meteorites +the-hard-herobrine-mod + +tektopia-addons +jason-voorhees +biome-improvement-mindthemoods-pile-of-rocks +mochila@|简彩跃光* +soulytra +@advanced-tfc-blueprint|高级群峦蓝图 (Advanced TFC Blueprint) +@accesswidener-loader +improperui-forged@ + +enchantment-to-attack-faster|并非环取/攻击速度附魔非官方版 (Attack Speed Enchantment Unofficial) +@stopgrowth|停止生长 (StopGrowth) +squid-no-glitch +@permanent-night-vision|无痕夜视 (Permanent Night Vision) +@rems-carpet-addition + +colorful-hex@ + +bbl-no-rain-fog + +@be-quiet-negotiator +link15s-no-i-frames +jei-integration-reborn@|JEI扩展:重生 (JEI Integration: Reborn) +appliedsync +jei-uu-assembler@ +ominous-overlays +create-nuclear-shell@ +world-of-wonders +horrible-meteor-shower|陨石雨* +desertification@ +cinchs-abandoned-houses@ +curse-breaker@ +hundred-years-warfare@|百年战争* +biologica@ +oritech-things +potato-vehicle-industries +warfare-wings@ +avaritias-delight@|无尽乐事 (Avaritia's Delight) +mamas-herbs-and-harvest@ +vs-orbit@|瓦尔基里:轨道学 (VS Orbit) +touhou-little-maid-spell@|车万女仆:万法皆通 (Touhou Little Maid: Spell) +despawn-notifier|余影待终/掉落物消失提醒* +customnpcs-unofficial-from-betazavr|自定义 NPC BetaZavr 非官方版 (CustomNPCs Unofficial from BetaZavr) + +efficient-core-automatic-mining|Efficient Core自动挖掘器* +@bento_box|便当盒 (Bento Box) +custom-100-day-boss@|自定义100天Boss (Custom 100-Day BOSS) + +cinchs-extra-torches@ +corpse-x-cosmetic-armor-reworked-compat +modular-angel-ring +magic-feather-replumed@magicfeather +essentialcraft-magic-reflux +ad-astra-star +neomopeds +create-more-parallel-pipes@ +create-garnished-reworked@ +create-shimmer|机械动力:微光 (Create: Shimmer) +blue-archive-school-banners +cialloblade|重锋:cia出如llo (CialloBlade) +windows-keyboard-fixes-returns +aquatic-torches-returns +cratetech-crate-storage +bigger-and-better@ +blabber@ +reducedores@ +durabilityrarity@ +ironfist-lite +shear-chicken@shear-chickens +no-moon-jar@no_moon.jar +@deep_space +blue-grove +quarkponders@ +sussy-sniffers +firefly-bush-backport@ +agritech-trees-automated-trees@agritech-trees +agritech-evolved@ +wealizh|工业故事* +@hydrometallurgy|湿法冶金学 (Hydrometallurgy) +mekanism-ecoenergistics-unofficial + +woodlandfoxverses@|林间狐咏 (WoodlandFoxVerses) + + +swaying-garden|摇曳花园* +time-feeds-villager@|逝者如饲:村民 (Time Feeds: Villager) +ftb-backups-3|FTB 备份 3 (FTB Backups 3) +kurolib@ +trimmed@ +free-camera-api-tripod@|自由相机API-三脚架 (Free Camera API Tripod) +abelia¨@abelia|Abelia - 审核中 + +simple-weather +simple-copy@ +natures-aura-render-fix@ +creakingchopperenchantment@ +daily-rewards@ +initialdimensionreborn@ + +@oo-ee-a-e-a-fabric + + +the-plugin-mod@ +cave-vision +@el-addon-retrieve-to-slot +aether-gloves-for-all +reducedloot@ +@irisfixes|Iris 修复 (IrisFixes) +musicjs + +suspendtheserver|自动挂起服务器 (Suspend The Server) +screenshot-layers@ +anti-spider-climbing +rechiseled-chipped@ + +weatherdisplay@ +radium-fabric|Radium - 已失效¨@radium-mod +cinnabar + +pquality@ + +mte-patches +happy-ghast-breeding@ +happy-ghast-flight@ +the-angry-ghast@ +@morfonica-in-minecraft +for-vanilla-flowers +@skillpractise|技能练习 (SkillPractise) + + + +bedrock-hotbar-offset@bedrock-hotbar +mss-more-slot-swap¨more-slot-swap +show-all-advancements|显示所有进度*¨@show-all-advancements|显示所有进度 (Show All Advancements) - 审核中 + +mana-display@ + +curse-forge|诅咒锻造* + + +disconnect-packet-fix@ +happyghast-on-1-12-2@happyghast_on_1.12.2|1.12.2的快乐恶魂 (HappyGhast_on_1.12.2) +automationera@|自动化时代 (Automation Era) +@tears-backport +lava-chicken-disc +lava-dennis +album-go-wall +forgotten-java +johanness-renaissance-rebirth@johanness-renaissancerebirth|约翰内斯的文艺复兴:火器重生 (Johannes's Renaissance: Musket Rebirth) +swarm-infection@ +necro-rooted +mycelial-crisis@|菌丝危机:崛起 (Mycelial Crisis: Mushroom Rising) + +lethalsteelproject|致命拔刀计划 (Lethal Steel Project) +lovecraft-girlfriends-3@ +@faithful-mace +bard-craft +@bilibilimedia|哔哩哔哩WaterMedia兼容模组 +items-timer +neo-language-reload@ +@oxidizium +ixeris@ +substrate-sodium-addon@substrate +hide-item-frame@|隐藏物品展示框* +@smoothmaps +neoessentials@ +structure-layout-optimizer@ +mutant-creatures-legacy +dynamites-overhaul@ +dynamite-addon¨dynamite-addon-fabric +maid-mana-source|女仆魔源* +maidens-merry-making@mamas-merrymaking +bbl-invisible-lights@ +superior-trees@ +underground-rivers@ +improved-village-placement@ +@photosynthesis +@mineno +slashbladetetra + +irons-spells-n-spellbooks-dynamic-skill-trees@ +spell-elemental@ +ward-ring-extension-for-irons-spells-n-spellbooks +dungeons-and-combat-x-irons-spells +smallcolonies +farmers-cutting-regions-unexplored +serene-landscapes@ +blood-arsenal-reawakened@ + + +spell-colonies +nature-reimagined +ominous-bottle-add +only-hammers@ +auto-resource|自动资源* +iu-additions@ +rubiks-cube|魔方* +clockworklib + +fabridge@ + +true-fullbright +quickslots +hide-hud-in-third-person-view@hide-hud-third-person +automlg@auto_mlg +elytraautopilot@elytra_autopilot +simple-conveyor-belts@ +smidgeon-o-bliss-remastered +xl-food-reborn|超多食物:重生 (XL Food Mod Reborn) +create-spin-spawner +vs-rotanator +create-refilling-boxes +cbc-chemical-warfare|机械动力火炮:化学与生物战争 (CBC: Chemical Warfare) +cbc-warium-projectiles|机械动力火炮:Warium炮弹 (CBC: Warium Projectiles) +create-colored +create-law-and-order|机械动力:法律与秩序 (Create: Law and Order) +blockexporter@ +fix-attack-lag@|修复攻击卡顿* +@clienthings +@misinput +lavablocker@ +gildedarmors@gilded-armor|鎏金盔甲 (Gilded Armor) +inventory-management-deluxe +runningmapmusic|跑图音乐 (RunningMapMusic) +lava-chicken-music-disc +porting-dead-libs +panoramica¨panoramica-fabric +create-mt-integration|资源统合-机械动力配方附加* +@valkyrien-warpeace|瓦尔基里:战争与和平 (Valkyrien War and Peace) +sillys-auto-totem@ +repairables-anvil@|可修复的铁砧* +@throwable-bricks +@now-time|准点报时 (Now Time) +fluidjs¨@fluidjs|FluidJS - 审核中 +hybridcompat|混合兼容 (HybridCompat) +@recipecooldown +counter-mod +lotr-renewed-class-mod|魔戒种族 (LOTR Races) + +craftable-chain-armors +item-cooling-reduction@|物品冷却缩减* +yosbs@ + + +@no-bugs +dark-window-bar@ +sodium-core-shader-support@ +@anvilcraft-headtap|铁砧工艺:敲你脑袋 (AnvilCraft: HeadTap) +hearts-in-a-bottle@ +copper-tools-and-shears@ +windchime-unofficial-continued@ + +potionsplus +tinkers-wood@ +illuminated@ +galosphere-argent-edge +brutality-a-terramity-addon +free-epic-games@|FE 游戏框架 (FreeEpicGames) + +fabric-database-h2@|H2数据库* +tokien-theme-bq|更好的任务-托尔金主题 (Tolkien Theme [BQ]) +tpa-utilities +more-diamond-spawn|更多钻石生成* +shutup-opengl +maid-command|女仆指令* +speedy-hopper-vanilla +suggestions-fix-fabric +no-ixapi@no.ixmodapi + +epicfight-zylob|史诗战斗:刀光修改 (EpicFight Sword Light Modifications) +perfect-parity-pale-garden-awakens-neo +expressive-advanced +philia-amulet|友爱护符 (PhiLia Amulet) +stronger-fire-boss|原焰帝臻* +myec@|更多渴望:附魔与制造 (More of Yearn: Enchants & Crafts) +abandoned-urban|废弃城市 (Abandoned urban) +fungal-hazard|蕈骸迷途* +more-useful-copper-2-0@more-useful-copper|更有用的铜 (More Useful Copper) +boom-bombs@ +polars-exploration-additions@ + +ozymandias-sundries +tensura-enigmatic +createtraincontrol@|机械动力:列车控制 (CreateTrainControl) +create-collection-bundles@ +create-enhanced-mastery +create-industrial-tools@ +create-smart-bounds@ +create-fantasizing-again@|机械动力:又在幻想了 (Create: Fantasizing Again) +crafting-station-jei-edition-updated +uncraft-everything@ +@videoplayer +@net-music-play-list|网络音乐机:播放列表 (NetMusicList) + +gregs-modern-construct + +smart-key-prompts@|灵活按键提示 (SmartKeyPrompts) +@zero-whitelist|Zero白名单 (Zero Whitelist) +milometer|计程器* +@pearlchunkloading|珍珠加载区块移植 (Pearl Chunk Loading) +clean-drops +auto-replace +@footprint + +simple-villager-follow@ +tinkers-experience +hedgehog-companion@ +i-want-to-spin@|我要开转* +@kiss-fabric + +neo-the-titans@|新泰坦生物 (Neo Titans Mod) + +nuggets-repair-items@|矿粒修复物品* + + +terra-atmos@terra_atmos|群峦环境音效* +tlm-true-power@true-power-of-maid|车万女仆:真正的力量 (TLM: True POWER) + + + +@splash-window + +@showcase +enhancedtooltips@ +locate-fixer +journeymap-fixes +ancient-structure@ancient-structures + +wizard-samurai-irons-spells-addon¨@wizard-samurai-irons-spells-addon|Wizard Samurai - Iron's Spells Addon - 更新较慢 +survival-instinct +enderzoo-patched@enderzoopatched +illager-keep +codex-of-champions +ars-controle@ +medieval-craft-historical-firearms +too-many-withers +reliquified-l-ender-s-cataclysm@ + +findshit@log-creator-finder-findshit|找史大王 / 日志溯源 (find log's creator / findshit) +enchantment-lookup@|附魔查询* + + + + +@mods-checker|模组校验 (ModsChecker) +no-enchanting-xp-requirment +client-dynamic-light|客户端动态光源* + +multiyggdrasil@ +thaumcraft-4-patched +patchful +gtnh-customizer@ +view-sounds@ +ae-inventory-profiles +tooltips-reforged@|提示框:重铸* +@wyr +soaring-phantoms@ + +ritchies-projectile-library@rpl +modopedia@ +@translate-allinone|一体化翻译 (Translate All in One) +button-fix@ +@birthday-fabric|生日模组 (Birthday Fabric) +bucketstacking +creeper-no-break-blocks@ +create-cog-hud@|机械动力:齿轮 HUD (Create: Cog HUD) +@createtools-n-weapons +create-pipe-networks@ +jiagu-reappearance|岁在甲骨* +multicolored-redstone-lamps +reliquified-twilight-forest@ +witch-tower +the-nightmare-000@ +meowmere +@fallenflowers-server-economy|Fallenflower的服务器经济 (Fallenflower's Server Economy) +deadout-medical-supplies|死亡突围医疗用品 (Deadout Medical) +@thermal-shock +better-questing-npc-integration|更好的任务-NPC扩展 (Better Questing - NPC Integration) + +eu2emc|EU转EMC能量转换器 (EU2EMC) + +simplesorter@|简单整理 (SimpleSorter) +spawncursion|生成式入侵 (SpawnCursion) +grass-be-gone|不要草* +tacz-unidict|TACZ:铳械协议 (TACZ: Unidict) +gem-lichens|宝石地衣* +lethality-a-terramity-addon@ +lootr-fixes +no-name-provideds-potion-fruit@potion-fruit +fungus-stew +auto-sapling|自动树苗种植* +alans-bees +snack-sticks@ +fish-fix +stuttering-screen-lag-protection +tilefinder@ +immersive-furniture@|沉浸式家具* +modernnetworking +like1-8 +spoduos-future-mineration +enderio-continuous +enchantment-broker@ + +new-dawn +enhanced-glass +hospitals +comfy-cozy +epicnpc|史诗战斗NPC (epicnpc) +epic-fight-script-extension|史诗战斗:脚本扩展版* +@undead-transformation + +skullskinless|头颅无皮肤/易颜得帧 (SkullSkinless) +staynight|永夜定驻 (Stay Night) +blockblocker|方块放置阻止/抑用挡置 (BlockBlocker) +shoving-shovels@ +electric-coating|不止电池 / 电力镀层 (NotOnlyBattery / Electric Coating) +araxers-bestiary +better-questing-immersive-engineering-multiblock +bloodyquests +croaker@ +crawl-on-demand@ +gaze-a-malum-addon +osce|矿石菌种社区版 (OreSpawn Community Edition) +explosive-arrow +more-relics@ +no-sun@no-sun-mod|暗无天日* +opposing-force@ + +@pingfix +@volumescroll +talk-balloons@ +health-indicator-txf@ + +snowy-weather + +dimensionlock@ +fluidium|锍* + +peritia + +inventory-sort|背包整理* +corpse-kibou-no-hana|希望の花 (Corpse: Kibou no Hana) +redyeable +@natural-motion-blur +@modpack-checker +convenient-achievements +vintage-vibes@ + +@smelter-the-hedgehog +@hopper-the-hedgehog +custom-trades-mod|自定义村民交易* +tameable-zombie-horse-and-skeleton-horse|可驯服的僵尸马和骷髅马* +mi-tweaks +qa-villager-tweaks@qa-villager-extends|Qa 村拓展 & 废物革新 (Qa Villager Extends & Waste Innovations) +multiblocked-extended-compatibility +enum-extender-js +@btc + +no-pigs +vanimals@ +wind-charged-weaponry@ +untagged-mobs +enchanted-potato +cit-for-nbt + +bonemealtweaker +hellstone-golem|狱岩魔像* +netherstar-crafting|可合成的下界之星* +tutes-cn-sweetinks-items|塔兹与甜墨的物品 (Tutes-cn & Sweetink's Items) +i-am-steve@i..am-steve +backported-stufff + +tumbleweeds¨@tumbleweeds|Tumbleweeds - 审核中 +mending-mirror|破镜重圆* + +natures-life +yggdrasils + + +@deepseeks-enchant|DeepSeek的附魔 (DeepSeek's Enchant) + + + +isitwaxed +simple-hp-bar +login-hp-fix +shape-shifter-curse|幻形者诅咒* + + +tinkers-planner-reborn|匠魂蓝图:重燃 (Tinker's Planner Reborn)¨@tinkers-planner-reborn|匠魂蓝图:重燃 (Tinker's Planner Reborn) - 审核中 +hunger-overflow +nocreeper +mo-creatures-extended +skippy-pearls +spelunkery-plus +forge-better-redstone +jeb-just-enough-book@justenoughbook +pal@ +data-anchor@ +elytra-hopping +oh-more-wandering-trades@|更多游商交易* +beacon-anti-phantom@|不时轻轻肘击幻翼的邻座信标同学 (Beacon Anti-Phantom) +@flexible-painting|灵活的画 (Flexible Painting) + +cooler-animations + +hardcore-architect + +carpet-neoforged@neoforge-carpet +no-campfire-smoke +tiny-tasks +fancy-outline +echo-compass-hud@ +decorative-stands +confluence-otherworld-gun-fix@ + +sbm-cardboardboxes + + +bedrock-age|基岩时代* + +return-scepter|回程魔杖* +@koniavacraft|娜拉工艺 (Koniavacraft) +emojicraft|Emoji工艺* +kylin-arm|麒麟臂* +server-hats@ +forgotten-ruins@ + +creeperhost-presents-steves-carts@ +perspatium@per-spatium +face-stealer@ +offlawn-legacy +metalborn +@wizards-mod +mo-creatures-nostalgia-edition@ +mocreatures-awakened +sterling +cuisine-tfc|料理群峦 (Cuisine TFC) +@natural-temperature +silent-gear-metalworks +treetap@ +better-cave-update +@affix +starry-end +dynamic-trees-for-cobblemon +immersive-portal-gun-for-forge@ +adorable-hamster-pets@ +infinite-lava@|无限熔岩* +ethereal-shots|无限箭制 (Unlimited Arrow Works) + +epic-fight-super-golem|超级铁傀儡 (Epic Fight - Super Golem) +fallen-log-hideout +pastel@pastel-mod|彩绘世界* +mob-catcher-unofficial +retro-pet-items +confluence-dimension-patch|汇流来世维度补丁* +convenient-storage|便捷存储* +legendreliclib|传世遗产库 (LegendRelicLib) +jauml@ +nemexlib@ +cryonicconfig@ +knight-lib@ + + +@okom + +craft-slabs-back-into-blocks@ +highland-statue +restore-ore@|复原矿石* +factory-blocks@|工厂方块* +camouflaged-doors +rotten-planks-and-logs + +create-unify-updated-to-create-6@ +create-arm-made-cuisine|机械动力:臂力臂力炒菜 (Create: Arm-made Cuisine) +create-cultivation-craft@|机械动力:栽培工艺 (Create: Cultivation Craft) +kaleidoscope-cookery@|森罗物语:厨房* +retroemi-forge@ +@pearlecho +@mcdaycounter +minecolonies-war-n-taxes +falling-down@ +more-red-x-cc-tweaked-compat@ + +@speed-uuuuuuup|加加加加加加加速! (Speed Uuuuuuup) + +wither-reincarnated + +toros-auto-attack@ +creeperfire|自定义烧货 / 苦力怕阳光下燃烧 (CreeperFire) +fastspawner|刷怪笼渲染优化/产器速算 (FastSpawner) +sophisticated-storage-plus|存取思深/精妙存储扩容* +whaleborne +uniful +newspaper-big-book-bibliocraft-legacy-backport +bic-clipboard +templates-2@ + + + + +workstation-recipe-exporter@|可视化配方编写* +simple-auto-attack +hostile-to-neutral +tracer +fishing-tick-fix +fzmm@ +pasteljei@|彩绘世界JEI (PastelJEI) +e4mc-retro@ + +mantlejs@ +one-enough-hand|只手遮天* +persistent-moon-phases +sakurapack-compatability-patch +cattlestrophic@cattlestrophic! +ae-recipe-tool +minecraft-cursor@ +stalker-creepers-fabric@ +epiv-fight-l-enders-cataclysm-compatibility|史诗战斗灾变兼容 (Epic Fight & L_Enders Cataclysm compatibility) +lunar-nether-backport +lookinmyeyes +more-relics-more-rpg-content@more-relics-rpg +backstab-reforged +armamentarium + +remnant-bosses@ +choccos-mobs@ + + +uncrafting-table-xiaoyu-2009|拆解台 (Uncrafting Table) + +aewirelesschannel|AE无线频道 (AE Wireless Channel) +infinitepower|无尽能源 / 能量叠生 (Infinite Power) +hoshikima@|随意作品* +create-bugfix-clipboard-patch|机械动力:剪贴板修复 (Create Bugfix: Clipboard Patch)¨@create-bugfix-clipboard-patch|机械动力:剪贴板修复 (Create Bugfix: Clipboard Patch) - 审核中 +tool-disassemble|拆器碎散/匠魂工具拆解* +summer-to-life|夏意盎然 (Summer to Life) +@freedraw +geckojs@ +sanandreasps-manager-pack + +touhoulittlemaid-orihime@|车万女仆:织姬 (Touhou Little Maid: Orihime) +more-mod-tetra|更多Tetra模组锻造材料* +cataclysm-dimensions|灾变:维度* + +misttaczskills@|TaCZ:圣化技能扩展 (MistCloud Tacz Skills Extension) +quality-railway@|品质铁路* + +custom-mob-attributes@ +custom-durability@ +@dragons-egg(s) +zombie-variants@ +extra-carts +fairy-factions-revived +netherited@netherited-fireproof¨fabric-netherited +creeperhost-presents-soul-shards@ +myths-n-legends@myths-and-legends +snackcraft|零食工艺 (SnackCraft) + +copper-backport¨@copper-backport|Leaf's Copper Backport - 审核中 + +@shield-indicator|盾牌指示器 (Shield Indicator) +a-bastion-remnant-treasure-reset|堡垒遗迹藏宝室重置 (Bastion Remnant Treasure Reset) +a-bastion-remnant-hoglin-stable-reforged|堡垒遗迹疣猪兽棚重铸 (Bastion Remnant Hoglin Stable Reforged) +colonial-arcana-minecolonies-addon|殖民地魔法 (Colonial Arcana) + +fires-ender-expansion +thetitansneo@|泰坦生物Neo (The Titans Neo) +vectorientation-reneoforge +copyid@|复制ID (CopyID) +@a-lightcolor-beacon +@surveyor +@create-portable-packaging +create-more-pipe-bombs-in-packages@ +create-bugfix-schematic-patch|机械动力:蓝图补丁 (Create Bugfix: Schematic Patch)¨@create-bugfix-schematic-patch|机械动力:蓝图补丁 (Create Bugfix: Schematic Patch) - 待审核 + +create-ornithopter-glider +create-train-parts@ +@breakable-blocks +flux-network-x-cc-tweaked +twotility +opticubes +closets-by-smplesco@ +variety-chests-mod +light-detector +quality-hails + +tiger-shark@tigershark|海虎 (TigerShark) + + + +better-end-cities-better-end@better-end-cities-for-betterend¨better-end-cities-vanilla@better-end-cities-base +fallen-gems-affixes +skill-tree +blatium@ +trimica@ +fantasy-weapons-medieval-series@fantasy_weapons + +ksit@ +bestia@ +@itemfinderinall-items +filterchat +kit-keep-inventory-timer-keepinventorytimer +useful-luck@ +@sneak-fertilizing +p1neros-epic-fight-leawind-compat +custom-entity-leveling@|自定义实体等级* +no-monster-rooms +@carpetrmsaddition +@fireworkindicator +squeedometer-reforged-special-port + +difficulty-patch +squake-reforged@squake-fabric-updated¨squake-fabric-1-20 +squake-reforged-special-port +brigo@ +ae2-web-integration@ +skillet-man-core +p1neros-dialogue-lib + + +create-arm-made-barbeque|机械动力:臂力臂力烧烤 (Create : Arm-made Barbeque) +indestructible-blocks + +epic-fight-super-chef +epicfight-nightfall|史诗战斗:黄昏 (EpicFight-Nightfall) +genesis-official +featurecreep + +fabric-orm-jimmer@ +aces-spell-utils +targeteventbackport +silk-kt@silk + + +@playercommands-(old-mc) +legends-forge-series-vikings-n-northerners +tinkers-battle-spades +@its-not-real +ly-meteors +ashvehicle +@smart-absorption +@mooshees +city-of-chambers +tower-of-chambers@tower-of-chambers-neoforge + + +eypipes +shipping-bin +hbm-nuclear-tech-mod-community-edition|HBM的核科技社区版 (HBM's Nuclear Tech Mod Community Edition) +gte2-coremod@ +japan-props@ + +one-enough-item@ +o123456789@ +friendly-chests-no-fluid +herios-floral-expansion@ +pinkcabbage-transparent-betterscreen|粉红大包菜的屏幕透明度优化 (PinkCabbage Transparent BetterScreen) +wastify-returns +sillys-crossbow-fix@ +thaumpotion-tweaker@ +novoatlas@ +resource-pack-tweaks +night-vision-decay-fix|夜视衰减修复* +roid-tweaker +translatorpp@translatorppv +modular-machinery-community-edition-addons +randomaddon|随意之作 (Random Addon) +myhead|持器射首 / MyGO 你个头 (BanG Dream! It's MyHead!!!) +boss-dps-tracker|查其所伤/Boss伤害追踪器 (Boss DPS Tracker) +wandering-trader-may-leave@ +legendary-tabs-neoport +totem-bar +visualores-meowmel +savebabystriders +bygone-nether-fixes@|逝去的下界修复* +bossesrise + +advancementinfo-update +expanded-mace-enchanting@ +silky-spawners +starhud@ +celeritas-dynamic-lights@ +resistance-balancer@ +silkier-touch@ +wither-spawn-fix@|凋灵生成修复* +accelerated-rendering@acceleratedrendering|加速渲染* +cii@ +simpler-wooden-pipes@ +garden-cloche-backport +bh-boss-hatred@boss-hatred|首领仇恨 (Boss Hatred) + +camol +bbb-unforged +performance-monitoring-enhancer +create-upsizing@ +better-world-loading-forge@better-world-loading¨better-world-loading-fabric + +apocalypse-minimap-hud@apocalypseminimaphud|启示录:小地图Hud* +advancementnomore|进度移除 (AdvancementNoMore) +collisium +dypsis +@chat-plus +mcreatormemfix@ +@atlas +legacy-tweaks@legacytweaks +easy-elevators@|简单电梯* +dakimakura-unofficial|抱枕-非官方 (Dakimakura-Unofficial) +davincing +aeronautical-radio|航空无线电* +hunter-vs-mice@|猎人鼠鼠游戏* +citymod-one-block-one-building@city_mod +gedrite-mod@ +@shorkie +enchantable-boat@ +throw-bricks +animal-pens@animalpens + + + +@glys-can|GLY的罐头 (GLYCan) +fargo-soul|Fargo魂石* +@brokenbows|破损弓 (Broken Bows) +mekanism-trimming|通用机械纹饰* +teleportcakes@|跃迁蛋糕 (Teleport Cakes) +repair-amulet +alpine +clientchatclean|客户端消息清理 (ClientChatClean) +clickmobs +vlib@ +wabbanode-companion +@mcmti|麦克风语音识别文本输入 (Microphone Text Input) +powernexus-menu +hit-delay-fix +betterexcavate|更好的挖掘机制 (BetterExcavate) +optimization-of-campfire-smoke|营火烟雾优化 (Optimization of Campfire Smoke) +armored-combat-enhancer + +@animal-hats-yeah +@raid-restore +asgardandelion-for-botania +trotting-wagons@ +grindstonehoning +rainbowwrenchforjei-reforge|彩虹扳手:Forge版 (RainbowWrenchForJei:ReForge) +better-locate-structure-command|更好的结构定位指令* +att-miss|闪避攻击* +energizexp@ +no-more-magic-choices|更好的铁魔法UI* +patternbetter@|更好的ME样板供应器 (Pattern Better) +legacy-skins@ +@oh-my-pc +vsprinter@ +clickvillagers +@button-packet-id-fix +accessories-compat-layer@ +world-downloader@ +tameable-guinea-pigs +familiarslib +dmr@|龙骑士重制版 (Dragon Mounts Remastered) + +avaritia-expand|无尽贪婪 : 拓展 (Avaritia : Expand) + +ecliptic-seasons-multimod-patch +random-things-returns +infinite-storage-cell +operation-eight-treasure-porridge|八宝粥行动* +elegant-spells@|雅致咒术* +@seelescraft|希儿的工坊 (Seele's Craft) +readyhomes|随时可住 (ReadyHomes) +arknights-weapon|方舟武器* + +illager-warship@ +asian-fountains@ +illager-chef-trader +skyblock-villager +@redstoneer-villager +perrys-urban@ +neo-blahaj + +teleportation-crystal|传送水晶* +majo-spell-enchantment|魔女的魔咒 (Majo's Spell Enchantment) +oaks-capes@ + +cobblemon-ride-on@|Cobblemon:骑乘! (Cobblemon: Ride On!) +clayium-additions +farmers-tfc|农夫群峦 (Farmers TFC) +oaks-delight@ + +pathfinding-edition-for-minecolonies|殖民地寻路大修* +clicksigns@ +@salted-fish-craft|咸鱼 (Salted Fish) +herios-helian-mod@ +@decorative-food|装饰性食物 (Decorative Food) + +anvilcraft-alloyextension@|铁砧工艺:合金扩展 (Anvilcraft: Alloy Extension) +arkifs-hoverboard-mod +glow@glows +leaf-villager-trader +guard-villager-trader +hunter-villager-trader +garbage-collector-villager +more-traders@ +ae2-better-villagers@ +harvester-trader +railman-villager@ +dimensionaltraders +taxbiometrader@tax-leons-biome-trader +quickelytraswapperplus-client@elytraswapper+ +@elytrakey +elytraswapperplus +taste-fatigue@|味觉疲劳* +realistic-nametag@ +hitboxplus@ +@hittable +hitmarker +autolandingwater@|自动落地水 (AutoLandingWater) +skygrid-reloaded@ +@stone-wood-industry|石木工艺 (Stone & Wood Industry) +roomopolis@ +bbl-placers@ + +draconic-legacy-models|龙之进化旧版模型* +furnaces +tinkersconstruct-classicsfluidvalues +rain-be-gone-ritual@ +the-vegan-option|素食者的选择* +gensokyo-delight-youkais-feasts@|幻想乡乐事 ~~ 妖怪们的飨宴 (Gensokyo Delight ~ Youkais' Feasts) + +tuttas-doors@ +@effectlivingdead|行尸走肉 (Effect Living Dead) +vs-chunkloader@|VS区块加载器 (VS ChunkLoader) +create-blocks-bogies@blocks-bogies +create-netherite-additions@ +create-sophisticated-backpacks-compat@ +create-item-pets +c-bc-nukes-6-0-port +plushieverse@ +thavma@ +arsarms +yorupo-mod@ +yande-mod@ +gtcefucontent +mcheli-overdrive-ext@mcheli-o-ext +iron-biplane@ +realistic-computers + +boss-mob-enhancer +flight-api|飞行 API (Flight API) +unlimited-spawners@|无限刷怪笼* + +@minecart-visualizer|敞骑速视 / 矿车信息可视化 (MinecartVisualizer) +@shadowborders +authenticate@|认证* + +unbinilium +veinbreaker +actions@ +i-have-no-mouth-but-i-must-chat@ihavenomouthbutimustchat +renderscale@ +proportional-destruction-particles +tacz-ammo-query +fast-happy-ghasts +@starhudforged +@slotted-armor-hud + +@say-sorry! +@playerautoma +im-fast@ +@unpath +no-more-popups@ +sussypatches@ +@simple-item-editor +baby-zombie-removal@ +immersive-sounds +boatview360@ +@my-resource-pack +oaks-bits@oaks-bits-and-bops +@starsource|星源拔刀剑 (Star Source Blade) +snow-waifu-spell|雪下的誓言* +mob-farm-trap@|香草刷怪塔陷阱强化:涌渊敌冢* +twograve +perrys-decorations@ +perry-s-decorations-2 +bookshelf-extended-enchanting-reach +enchanter-fix@ +dynamic-regen-enhancer +guinea-pig-variety@ + +@areas-hint|域名 (areas-hint) +yammo + +fps-display-doubler@ +inventory-stages|背包阶段* +gregmek|格雷矿物通用机械支持 (GregMek) +@armor-hud +@ukus-armor-hud +flowtech-mob-systems@ + +ftb-materials +the-great-below +illagerblabber@ + +serene-season-extended-fix +create-enchantment-industry-plus@ +boiled-one-unleashed +creaturecraft-plus +war-elephants + +one-click-one-block-1c1b@oneclickoneblock|点到为止 (One Click One Block) +@translatabledebugoptions|可翻译调试选项屏幕 (TranslatableDebugOptions) + +suppressopengl1280@|OpenGL错误隐藏 (Suppress OpenGL Error 128(0-2)) +fexs-vehicle-mod@fvtm +otterlib +dimban|维度禁用 (DimBan) +onlyoneitem|仅存一物 (Only One Item) +bedload|自定义区块加载器 (BedLoad) +@unreachable-nether + +@detail-armor-bar-reconstructed +@auto-fishing + +indrevjei@|工业革命JEI (IndRevJEI) +fpresearchpatcher|飞向未来研究数据包兼容 (Futurepack Research Patcher) +me-infinity-cell|ME无限元件 (ME Infinity Cell) +air-jump +more-sniffer-flowers@|更多嗅探兽花卉* + +enchantability|附魔能力* +ender-lead|末影拴绳* + + +create-growableores-compat@creategrowableores-compat + +gears-n-kinetics@ +valkyrien-sails@ + +moo-fluids-modern +yan-from-ff14@|FF14的羊 + + +tfc-cuisine@|群峦料理 (TFC Cuisine) +tfc-no-collapse|群峦传说:塌方禁止 (TFC No Collapse) +no-sleep-chat-overlay-nsco@no-sleep-chat-overlay-(nsco) +funkys-fancy-furnaces@ +timber-enchantment +@alternative-chunkloading +@particulate +haydenapi +spore-inquisition +ice-and-fire-spartan-weaponry-extras +@vanitydungeons +@fcitx5-enhancer +kubejs-actually-additions@ +vibrative-voice@ +spawnlock +enchantment-custom-table@|自定义附魔台* + + + +keeping-inventory-reborn +camel-extend|骆驼拓展* +confluence-music|汇流音乐* +cancel-attack-cooldown|取消冷却间隔* +surprising-loot|藏奇速释/惊喜战利品* +@onlykeys +flair +minecraft-comes-alive-reforged|凡家物语·重制* +cave-mita-dweller@mita-dweller +eeeabs-mobs-death-mode +revivetoken@|复活币 (ReviveToken) +real-filing-reborn-cabinet-based-item-storage@ +quivers +infused-crystals@ +mendingremover@ +auto-vanilla@autovanilla +underground-villages-stoneholm@underground-village,-stoneholm +gep@ + +more-harnesses@|更多挽具* +vanilla-aiots@ +nether-villager-merchant@ +end-villager-trader@ +deep-dark-trader@ +random-generator-block +devour +quantum-storage-reborn@ +library-section@ +move-subtitles@ +@modrenf3 +modern-inline@ +culler@ +@playernoticeboard + +@ae2uel-extra + +gems-realm@ +arrowcollect@|箭矢回收修复 (ArrowCollect) +@primitivemobsrevival +steampunk-clocktower +piglin-outpost +nolando-structurez +skinlayers3d-customskinloader-bridge@ +puffish-biome-dither@biome-dither +voracious +@minecraft-dungeons-weaponry +gentech-block-generation@ +byzantine-styles-pack-for-minecolonies + +aquaculture-tinkers@ +cups +@blue-archive-halo|仿碧蓝档案光环 (Blue Archive Halo) + +ecological-replenishment-station|生态补给站* +@advancedhostility|进阶仇恨 (Advanced Hostility) +aggro-fix@ +chest-curio-items@|箱子里的小饰品* +galaxysimagination|Galaxy的奇思妙想 (Galaxy's Imagination) +delightful-cuisine-for-woodheads-reforged|懒人厨房·重制 (Delightful Cuisine for Woodheads Reforged) +@loans-cinema +banana-baking@ +duacan-delight|岩城乐事* +slashblade-refabricated@|拔刀剑:重织 (SlashBlade: Refabricated) +ender-dragon-fix + +detailab-compat@|细节盔甲兼容 (Detail Armor Bar Compat) +calypsos-mobs@|卡里普索的生物 (Calypso's Mobs) +dirtcraft-unoffical|泥土工艺-非官方版 (DirtCraft-Unofficial) + +@onlytpnext|仅传送Next (OnlyTPNext) +easy-survival-command|简单生存指令TP (EasySurvivalCommand) +@gensokyo-reverie-of-lost-dreams +reliquified-ars-nouveau@ +requesting-interface@irequesting +applied-experienced@ +plain-cars-pack@plaincars +gt-craft-iv-mts@gt-craft-ivmts +ad-astra-more-structures@ +perfect-dodge|完美闪避* +simple-paraglider@|简易滑翔翼* +human-gunner +tacz-headshot-extension|TaCZ 爆头机制扩展 (TaCZ Headshot Extension) +dream-in-a-manhunt + + +@thesunrise +more-critters +oelib@ +itemblacklistfixed|物品黑名单修复版 (ItemBlackListFIXED) + +voxelmap-patch +xaero-train-map@ +traversable-leaves-neoforge¨traversable-leaves¨traversable-leaves-fabric +@etched-extension +@vs-addition-continue + +clavis@ +luckyblock-lib + +nopackcompatcheck@|禁用资源包兼容性检查 (NoPackCompatCheck) + + +crabbers-delight-expand|蟹农乐事:渔冶锭整 (Crabber's Delight Expand) + +offlinegrowth@ + +fauna-and-orchestra@faunaandorchestra + + +contact-love-rekindled|往来:旧情复燃 (Contact: LoveRekindled) +autosow-by-neni-o@ +piglin-join-wto|猪灵加入了WTO (Piglin Join WTO) +@keyboard-detector|键位检测器 (Keyboard Detector) +potion-pill|药水药丸* +inhrtburys-coin|Inhrtbury的金币 (Inhrtbury's Coin) +@firesight +epic-villages +@brainfk-in-minecraft +@aviator-dreams-reloaded +russian-dream + +cbc-ballistics +create-programmable-pals +create-ore-excavation-plus@ +@createtreadmill|机械动力:跑步机 (Create:Treadmill) +create-more-package-couriers@ +regius + + + + + +peyros-scythe|佩伊洛的镰刀 (Peyro's Scythe) + +@readstar|占星术 (ReadStar) +illage-and-legend +giant-dead-oak-tree@ + +ascension-nether +ascension-end +@echovoids +@soulfulnether +zefs-nether +the-end-update-concept +@ending-biomes +charred-horizons@ +@nether-update-expanded +i-see-my-armored-hand@ismah +naturally-spawning-withers@|自然生成凋灵* +wither-infection@ +creeper-infection@ +long-books@ +ftb-chunks-recoloured +jei-hover-search@ +rollable@ +invisibility-aggro-fix@ +quick-skin@ +countryball@minecraft-countryball-mod +tinkers-construct-emergence +pointblank-jelly +terrabnormals +extra-mining-world +herobrine011840s-nether-biomes +my-end +update-ender-a-reimagine-of-the-end +@air-puffers +companions-mod@ +@ai-chat|AI聊天 (AI Chat) +apothic-attributes-extension|神化属性拓展* +databank@ +randomtp@ +@kium-optimizer +instantunify-reforged +@blockfire +morphoverlay +display-prior-work-count +extended-scripts +natures-aura-simplified|自然灵气:简化 (Nature's Aura Simplified) +bed-spawn-backport@|想设就设* +visible-effect@|药水效果显示* +@config-manager|设置管理器 (Config Manager) +simple-datapacks@ +shulkerrrt@srrt +custom-icon-sets +gregic-probe-ceu +@antilootgame +@graphene|石墨烯 (Graphene) + +talents-fabric@talents +@combat-maids +voxelfurni +voxels +voxel-pairing +no-stuck-healing +pixelcompress +matrinoxes-village-names +redlink-wireless-redstone-control@ +@nekohashizuku-blade|雫拔刀剑 (shizukublade) +xtones-reworked@ +@tinkers-enchanting +new-woods +ennuis-bigger-inventories@ennuis_bigger_inventories +@soldisco +missing-cookery +@farmers-tea-resteeped +peruvians-delight@ +productivefarming +brewin-compat-delight@cocktails-delight|鸡尾酒乐事 (Cocktails Delight) +kubejs-tweaks +krypton-fnp@ +neoauth@ +crystalix +chisel-chipped-integration +refined-types +mystical-automation@ +utility-vest +little-big-redstone@ +@stone-is-stone +almost-unified-ie +@trident-are-weapon +collect-seeds +create-delicious-teas@ +@showtime +@windup-music-box +babylonian-empire-origins +mc-mods-the-arcanist-gear +epitaphs +biologist|生物学家* +wandering-trapper2|流浪猎人2* +skiing2 +backported-animal-variants@ +explore-ruins-aether@ +warpdrive-extras@ +@chest-transport-reloaded +lrs-blockollection +@the-what-tools +tacz-chaoscrafts-apocalyptic-arsernal +detail-armor-bar-re-equipment@|细节盔甲条:重装 (Detail Armor Bar Re-equepment) +@jeac +boat-fly|船飞行* +finite-water-infinite-lava +transfer-labels@ +growth-accelerator-tiers +astages-curios +astages-pufferfishs-skills +sound-physics-perfected@ +the-sound-of-rain@ + +greekfantasy-unofficial +musketeer-illager-port +cataclysm-apotheosis-addon-neo +iron-apples-iron-series@ + +tinkers-oreganized@ +simple-basketballs@simple-basketball +decorative-blocks-repurposed +create-mechanical-botany@ + + +naked-gauntlet|焰眼掉装* +only-one-item@ + +aimbow-bow-hit-indicator|弓箭瞄准 (AimBow) + +@tt20forged +@worldfarview +@fixmcstats +soul-return-scroll|还魂符* +slashblade-murasame@|拔刀剑:丛雨丸* + +dungeon-tools-2|地牢工具 2* +gregtech-beyond-coremod +create-compatible-storage@ +antique-rockets@ +meta-changer +chromatic-enchantments@ + + +tfc-detail-armor-bar@|群峦传说:细节盔甲条 (TFC Detail Armor Bar) + +tinkers-compatibility@ +fabric-trade@ +giant-nether-tree@ +gamerule-doblockupdates +terralith-restoned +guitas-woodworks@ +power-grid@|机械动力:交错电网 (Create: Power Grid) + +audaki-cart-engine@ + + +humansmod@humans-mod +aquaacrobaticslegacy + +armored-arms@ +armored-golems +disable-fog@ +accrelatedrendering-refabricated@|加速渲染 重织版 (AccrelatedRendering-reFabricated) +minemenureforged|我的菜单 重制 (MineMenuReforged) +function-command-deduplicator|函数命令去重器* +@hide-armour +ftb-quests-freeze-fix@|FTB任务冻结修复 (FTB Quests Freeze Fix) +playeranimatorapi@ +strictly-origins@ +born-in-configuration@ +eyes-of-ice-and-fire@ +better-hp@betterhp +jsonate +plsgodie|残血了就给我乖乖去死啊! (PlsGoDie) +tetra-patchouli-book|Tetra帕秋莉手册* + +configurable-death@ +invenaudio@ +client-curse-of-binding-prevention +simpleclockmod + +audio-improvements@ + + + +simple-crop-harvester|简易农作物收割* +darkness-curse|暗影诅咒·重生 (Darkness Curse • Reborn) +@scroll-whell-command|滚轮命令 (Scroll Wheel Command) +infoplus@ +@bin-slot +cobblemon-utils +flamingo-mania + +endangered-plants|濒危植物* + +better-melon@|更好的西瓜* +better-snifferty@|更好的嗅探* +@bobs-originium-dream|鲍勃之源石梦想 (BOB's Originium Dream) + +minibloom +teatime@tea_time +garden-trails + +@gd656killicon|GD656击杀图标 (GD656Killicon) +itemcleaner@|掉落物清洁者 (ItemCleaner) +@tinkers-dolabrae +metallic-tinkering@ +kubejs-draconic-evolution +kubejs-mystical-agriculture@ +modern-beta-forge-islands +modern-beta-forge-release-plus +pomkots-world@ +black-dragon-armor|怪物猎人·世界:黑龙盔甲* +kimetsu-no-yaiba-reload|鬼灭之刃 重制* +resonantisshelomoh|同调造物·所罗门 (Fabrica Resonantis:Shelomoh) +withering-away-reborn +villager-extend-localbully +dancing-villagers +natures-of-witchery +tct-flashlight@tctflashlight +mace-attack-assistance +changed-doomsday +changed-ice +tile-entity-breaker +frozen-ocean +portable-pet-sphere@ +@thaumicthermae +bbl-routers +bed-sapwnpoint +wans-storage-shelves@storage-shelves +@winefoxs_spellbooks|酒狐的魔法书 (Winefox's Spellbooks) +better-allay@ +codechicken-lib-cre@ +solcarrot-fix|生活调味料:胡萝卜版 - 死吃修复* + +nanexs-cherry-biome +digital-botania + +crab-city-facilities|螃蟹的城建 (Crab city facilities) +bbl-casting@casting +bbl-smart-crafting +foolproof@ +realistic-terrain-generation-community +more-outlines@ +phantomtweaks|移除幻翼 (RemovePhantoms) +simple-item-blacklist +titlescreen-cleaner + + +manaitaplus-legacy|砧板传承 (ManaitaPlus Legacy) + +blockoffensive@|方块攻势 (BlockOffensive) + +hugme-forge-edition|抱抱我!Forge 版* + + + + + + + +better-turtle-scute@|更好的海龟壳 (Better turtle scute) + +floatable-pressure-plates@|可浮空压力板* + + +more-rust@rust|锈 (Rust) +@cryofloric-arts|严寒插花术 (Cryofloric Arts) +tacz-refabricated@|TaCZ:重织 (Timeless and Classics Zero: Refabricated) + +@ashen-witch-broom|魔女的扫帚:重制版 (Ashen Witch Broom) +maidswordsoaring|女仆御剑 (Maid Sword Soaring) +lembrary +optiboxes@ +create-furnace-lava-adapter +create-trainutilities@ +@createtrainscustomportalapi1.20.1 +create-xptool@cxptcreatexptool|机械动力:经验抽取 (Create:XPTool) +graveyard-death-mode +beautiful-campfires +kimetsu-no-yaiba-final-hope +legend-ary@ +tfc-hourglass|床起速时 / 梦该醒了 (TFC Hourglass)¨@tfc-hourglass|床起速时 / 梦该醒了 (TFC Hourglass) - 审核中 + + + + + +@alan-unified-ui +@cntiertagger + +jei-tetra +eyes-in-the-shadows@ +bandagez +kineticdamage@ +underwater-village@ +dimension-parasite|维度寄生者* +@realistic-stamina +ciscos-content +pet-home@pet_home +changed-xtras@ +alexs-caves-enriched@|Alex 的洞穴:浓缩 (Alex's Caves: Enriched) +@fps-to-the-trash +@entity-desync-viewer +re-entity-outliner +@c2mef +hellspawn-starting-dimension@hellspawn +jj-weapon-config +bbl-compat@opolis-compat +tinkertime-geology +spectral-arrow-tweaks +too-many-bows@ + +@soulbinditem|灵魂绑定 (Soulbind) + + + +maid-beside|女仆伴行* +key|键* +vzlinglib +@signfinder|告示牌查找器 (Signfinder) + + + +@colorful-leaves-mod +decorative-computers@|装饰性电脑 (DECORATIVE COMPUTERS) +landk-furniture@ +derfl007s-road-mod +medieval-paintings@ +glymworld-deepslate + +roadarchitect@ + + +lazybank|咸鱼物品银行* + + + + +@projectile-ding +client-time@clienttime +replanter-plus@ +ae2-retex-port +tinker-tantrum +amethyst-additions +@tetravsikt +@advanced-netherite-upgrade-templates +@falling-sand +gregified-energistics@ +scalable-storage-ceu@ +luminous-blocks@ +epic-structures-witch-huts + +void-block-fabric@void-block-fabric-1.21.1 +more-stews-mod +@ported +better-music-player +berries-and-mobs +applied-soul +packagedfluidcrafting|封包流体合成 (PackagedFluidCrafting) +packagingprovider|封包供应器 (PackagingProvider) +mts-iv-recurring-air-war + + + +@explodee +swift-wheel|迅捷轮盘*¨@swift-wheel|迅捷轮盘 (Swift Wheel) - 审核中 + +better-spectral-arrows +polymorphic-tom@polymorphic-toms-simple-storag +shoulder-surfing-ultimine@ +artisan-pottery@ +epic-fight-avalon +@neospeedzero|自定义速通挑战:Zero (NeoSpeed Zero) +fluffy-dandelions@ +nether-update-concept-mod +@little-maid-mood-flutter|车万女仆:情绪萌动 (Little Maid: Mood Flutter) +mobvotes-sniffer@sniffer +ae-wireless-transceiver@|AE无线收发器 (AE Wireless Transceiver) +create-train-lights@ +kabooms +zoo-architect@ + +@noverifychat|无验证聊天 (NoVerifyChat / FixChatVerify) +giant-cherry-blossom-bonsai-tree@ +lemonskin@|柠檬皮 (LemonSkin) +water-erosion +@undertale-healthbars + +tetra-gui-compat +@shortratio-mod +quantumchunks@ + +@citrus +@accessory-api +@catalyst +@day-counter-enhanced +messenger-crow +epic-fight-super-warden|史诗战斗:超级监守者 (Epic Fight - Super Warden) +armageddon +cobblemon-mega-showdown@ +golem-dungeons@|傀儡地牢* + +explosion-overhaul-a-new-level-of-destruction +@skillz + +can-you-survive +eternal-shield-bead|永御盾珠* +slashnouveau +@aether-stapi +@notenoughminecraft +originiumarts@|源石技艺 (OriginiumArts) +simplebackhome|简单回家 (SimpleBackHome) +@qismorecommands|齐的更多命令 (Qi's More Commands) +polymorphic-travelers-backpack@ +playernbt-quests-ftb-quests +interactive-block-prompt@ibp|可交互物品提示* +@timeshifts +@weathershift +chat-binds@chatbinds +tridentperf-1-0-0 +maid-s-bakeries|女仆的烘焙坊 (Maid's Bakeries) + +elytraautocollect@|鞘翅自动搜集 (ElytraAutoCollect) +@banitems|物品封禁 (Banitems) + +tt20-forged@tt20-reforged +default-server-properties|默认服务器属性* +f3-lite@ +text-animator@ + +yet-another-furniture@ + +qarsans-insurgency +yum-delicious|Yum!美味! (yum! delicious!) +lunarinispread@|丰盛晚宴 (Lunarini Spread) + +rs-backpack +no-template-netherite@ + +@mobcapture +koifish + + +@mcinspects +@csgo-inspects +armor-hud-by-mcjunky33@ +@can-i-sleep-now +@zenxarch-damage-vignette +@teleportg +too-many-binds +too-many-binds-plus +astral-dragon +middle-earth-additions-npc@|中洲拓展:NPC (Middle-Earth Additions: Npc) +middle-earth-additions-factions@|中洲拓展:派系 (Middle-Earth Additions: Factions) +middle-earth-additions-war@|中洲拓展:战争 (Middle-Earth Additions: War) +brews-and-bevs@|酿造与饮料 (Brews and Bevs) +@kimetsu-no-yaiba-x-epic-fight + +lrs-dynamic-dungeon +@buff-mobs +banner-claim@|旗帜圈地* +@reformundyingtotem|不死图腾重制 (ReformUndyingTotem) + +more-world-crafting|更多世界合成*¨@more-world-crafting|更多世界合成 (More World Crafting) - 审核中 +@simplelocks|简单的锁 (SimpleLocks) +luminous-helmet +steel-armor-blocks@sab|机械动力:钢铁装甲方块 (Create: Steel armor blocks) +create-armor-blocks@|机械动力:装甲方块 (Create: Armor Blocks) +rolled-homogenous|轧压均质装甲* +elemental-end-portal +unseens-better-stronghold +salutation +minecraft-1-7-10-reloaded +global-customnpc-data +modern-beta-forge-rivers +zombie-improvements@zombieimprovements +rewithered@ +better-lost-candle +openblocks-reopened +@rc-toys +fancy-toasts + + +@trueuuid|正版离线共存 (TrueUUID) +owapote-bc-exlaser +truly-custom-horse-tack@ +@tacz-applied-ammo-box +azurum-miner@ +thermal-recycling|热力回收* + +revoted-wildfire@ \ No newline at end of file diff --git a/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll b/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll index 8069902..341d08f 100644 Binary files a/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll and b/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll differ diff --git a/Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll b/Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll new file mode 100644 index 0000000..e9e80be Binary files /dev/null and b/Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll differ diff --git a/Plain Craft Launcher 2/Resources/java-wrapper.jar b/Plain Craft Launcher 2/Resources/java-wrapper.jar index bd14bd6..9ab92b2 100644 Binary files a/Plain Craft Launcher 2/Resources/java-wrapper.jar and b/Plain Craft Launcher 2/Resources/java-wrapper.jar differ diff --git a/README.md b/README.md index 99e8076..8b989a8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Plain Craft Launcher -[![Stars](https://img.shields.io/github/stars/Hex-Dragon/PCL2?style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTggLjI1YS43NS43NSAwIDAgMSAuNjczLjQxOGwxLjg4MiAzLjgxNSA0LjIxLjYxMmEuNzUuNzUgMCAwIDEgLjQxNiAxLjI3OWwtMy4wNDYgMi45Ny43MTkgNC4xOTJhLjc1MS43NTEgMCAwIDEtMS4wODguNzkxTDggMTIuMzQ3bC0zLjc2NiAxLjk4YS43NS43NSAwIDAgMS0xLjA4OC0uNzlsLjcyLTQuMTk0TC44MTggNi4zNzRhLjc1Ljc1IDAgMCAxIC40MTYtMS4yOGw0LjIxLS42MTFMNy4zMjcuNjY4QS43NS43NSAwIDAgMSA4IC4yNVoiIGZpbGw9IiNlYWM1NGYiLz48L3N2Zz4=&logoSize=auto&label=Stars&labelColor=444444&color=eac54f)](https://github.com/Hex-Dragon/PCL2/) -[![Issues](https://img.shields.io/github/issues/Hex-Dragon/PCL2?style=flat&label=Issues&labelColor=444444&color=1F883D)](https://github.com/Hex-Dragon/PCL2/issues) +[![Stars](https://img.shields.io/github/stars/Meloong-Git/PCL?style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZD0iTTggLjI1YS43NS43NSAwIDAgMSAuNjczLjQxOGwxLjg4MiAzLjgxNSA0LjIxLjYxMmEuNzUuNzUgMCAwIDEgLjQxNiAxLjI3OWwtMy4wNDYgMi45Ny43MTkgNC4xOTJhLjc1MS43NTEgMCAwIDEtMS4wODguNzkxTDggMTIuMzQ3bC0zLjc2NiAxLjk4YS43NS43NSAwIDAgMS0xLjA4OC0uNzlsLjcyLTQuMTk0TC44MTggNi4zNzRhLjc1Ljc1IDAgMCAxIC40MTYtMS4yOGw0LjIxLS42MTFMNy4zMjcuNjY4QS43NS43NSAwIDAgMSA4IC4yNVoiIGZpbGw9IiNlYWM1NGYiLz48L3N2Zz4=&logoSize=auto&label=Stars&labelColor=444444&color=eac54f)](https://github.com/Meloong-Git/PCL/) +[![Issues](https://img.shields.io/github/issues/Meloong-Git/PCL?style=flat&label=Issues&labelColor=444444&color=1F883D)](https://github.com/Meloong-Git/PCL/issues) [![哔哩哔哩](https://img.shields.io/badge/动态-BiliBili-00A4DB?style=flat&labelColor=444444&logoSize=auto)](https://space.bilibili.com/11343203/dynamic) [![爱发电](https://img.shields.io/badge/赞助-爱发电-946ce6?style=flat&labelColor=444444&logoSize=auto)](https://afdian.com/@LTCat) @@ -13,5 +13,5 @@ ### 相关资源 - [PCL 下载](https://afdian.com/p/0164034c016c11ebafcb52540025c377):下载可免费使用的正式版 PCL! -- [PCL 功能投票](https://github.com/Hex-Dragon/PCL2/discussions/2):来参加投票吧,开发者会优先处理票数较高的项目! +- [PCL 功能投票](https://github.com/Meloong-Git/PCL/discussions/2):来参加投票吧,开发者会优先处理票数较高的项目! - [帮助文档库](https://github.com/LTCatt/PCL2Help):PCL 内置帮助文档的存储库 diff --git a/最新正式版.zip b/最新正式版.zip index 9eec8e2..4118273 100644 Binary files a/最新正式版.zip and b/最新正式版.zip differ