diff --git a/.github/ISSUE_TEMPLATE/bug1.yml b/.github/ISSUE_TEMPLATE/bug1.yml
index 6a1f699..68cd2ba 100644
--- a/.github/ISSUE_TEMPLATE/bug1.yml
+++ b/.github/ISSUE_TEMPLATE/bug1.yml
@@ -1,6 +1,6 @@
name: "Minecraft 崩溃"
description: "PCL 提示 “Minecraft 出现错误”,或游戏崩溃"
-labels: [· Bug, 新反馈]
+labels: [· Bug, 新提交]
body:
- type: checkboxes
id: "yml-1"
diff --git a/.github/ISSUE_TEMPLATE/bug2.yml b/.github/ISSUE_TEMPLATE/bug2.yml
index b2db13b..de636f0 100644
--- a/.github/ISSUE_TEMPLATE/bug2.yml
+++ b/.github/ISSUE_TEMPLATE/bug2.yml
@@ -1,6 +1,6 @@
name: "下载或登录时卡住或失败"
description: "下载游戏、Mod 或整合包,或登录正版账号时出现问题"
-labels: [· Bug, 新反馈]
+labels: [· Bug, 新提交]
body:
- type: checkboxes
id: "yml-1"
diff --git a/.github/ISSUE_TEMPLATE/bug9.yml b/.github/ISSUE_TEMPLATE/bug9.yml
index 124b0c7..be20097 100644
--- a/.github/ISSUE_TEMPLATE/bug9.yml
+++ b/.github/ISSUE_TEMPLATE/bug9.yml
@@ -1,6 +1,6 @@
name: "综合 Bug 反馈"
description: "遇见了没有细分选项的其他 Bug"
-labels: [· Bug, 新反馈]
+labels: [· Bug, 新提交]
body:
- type: checkboxes
id: "yml-1"
diff --git a/.github/ISSUE_TEMPLATE/ch.yml b/.github/ISSUE_TEMPLATE/ch.yml
index 77ed3fd..0ddd693 100644
--- a/.github/ISSUE_TEMPLATE/ch.yml
+++ b/.github/ISSUE_TEMPLATE/ch.yml
@@ -1,6 +1,6 @@
name: "优化建议"
description: "对已有功能的小幅度优化或改进建议"
-labels: [· 优化, 新反馈]
+labels: [· 优化, 新提交]
body:
- type: checkboxes
id: "yml-1"
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index feb73a1..082755e 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,8 @@
blank_issues_enabled: false
contact_links:
+ - name: 主页预设反馈
+ url: https://github.com/Hex-Dragon/PCL2/discussions/categories/自定义主页
+ about: 提交与预设的主页(设置 → 个性化 → 主页预设)中的具体内容相关的反馈
- name: 帮助文档反馈
url: https://github.com/LTCatt/PCL2Help/issues
about: 提交与 PCL 帮助文档(更多 → 帮助)中的具体内容相关的反馈
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
index e799ee4..e27452b 100644
--- a/.github/ISSUE_TEMPLATE/feature.yml
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -1,6 +1,6 @@
name: "新功能提案"
description: "对已有功能的大幅度修改,或添加一个新内容或选项"
-labels: [· 新功能, 新反馈]
+labels: [· 新功能, 新提交]
body:
- type: checkboxes
id: "yml-1"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f2c4927..fbebe6a 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,5 +1,4 @@
name: Build
-
on:
push:
paths:
@@ -12,16 +11,15 @@ on:
workflow_dispatch:
jobs:
-
build:
-
- strategy:
- matrix:
- configuration: [Debug, Release, Snapshot, BETA, ReleaseUpdate]
-
+ name: Build
runs-on: windows-latest
+ strategy:
+ matrix:
+ configuration: [Debug, Release]
steps:
+
- name: Checkout
uses: actions/checkout@v4
with:
@@ -30,19 +28,39 @@ jobs:
- name: Set Describe
shell: bash
run: |
- Describe=`git describe --tags --always`
- echo "Describe=$Describe" >> $GITHUB_ENV
+ describe=`git describe --tags --always`
+ echo "describe=$describe" >> $GITHUB_ENV
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v2
with:
msbuild-architecture: x64
+
+ - 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
+ Remove-Item -Path *.* -Recurse -Force
+ Compress-Archive -Path .\* -DestinationPath .\Help.zip
+ Copy-Item -Path .\Help.zip -Destination "${{ GITHUB.WORKSPACE }}\Plain Craft Launcher 2\Resources\Help.zip" -Force
+ Set-Location ..
+ Remove-Item PCL2Help -Recurse -Force
+ Set-Location ${{ GITHUB.WORKSPACE }}
+
+ - 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\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
run: msbuild "Plain Craft Launcher 2\Plain Craft Launcher 2.vbproj" -p:Configuration=${{ matrix.configuration }}
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4
with:
- name: ${{ matrix.configuration }} ${{ env.Describe }}
+ name: ${{ matrix.configuration }} ${{ env.describe }}
path: Plain Craft Launcher 2\obj\${{ matrix.configuration }}\Plain Craft Launcher 2.exe
diff --git a/LICENCE b/LICENCE
index 47848b2..f9e821b 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,25 +1,40 @@
-本文件仅作为指引您合理使用本存储库内容的指南,不是法律文件或协议。
+一、声明
-一、承诺与声明。
+本文件仅作为指南,不是法律文件或协议。但开发者在此承诺,在您遵守本指南且善意、合理使用的情况下,不会追究您的责任。
+法律上,开发者仍保留所有权利(All Rights Reserved),若情况恶劣,仍可能将诉讼作为最终手段。
- 开发者承诺,在您遵守本指南的情况下,不会追究您的责任,但最终裁量权仍由开发者保留。
- 法律上,开发者仍保留所有权利(All Rights Reserved),且可能随时修改本指南,本指南并不是永久性保证。
-二、少量使用情形。
- 如果您仅合理使用本存储库中的少量内容,或是参考实现思路,您必须:
- 1、给出适当的署名,且提供指向本存储库的链接。
- 2、不得以任何方式暗示开发者为您或您的使用作担保。
+二、轻度使用
-三、大量使用情形。
+若您仅参考了本存储库中极小部分的内容,或您在制作与 PCL 的实质功能不存在任何重叠的软件,则为轻度使用。
+其常见情形例如:
+· 使用或参考存储库中极小部分的内容,来开发启动器或其他软件。
+· 虽参考了较大量的内容,但制作的软件与 PCL 的实质功能不存在任何重叠。
- 如果您基于该存储库创作衍生作品,或合理使用了本存储库中的较大量内容,您必须:
- 1、给出适当的署名,且提供指向本存储库的链接。GitHub 上的 Fork 可视为满足此条。
- 2、不得以任何方式暗示开发者为您或您的使用作担保,且需要给出适当的说明,表示这是非官方内容。
- 3、使用本指南文件作为协议,不得附加额外条款。
- 4、该衍生作品不能被允许用于再次分发。包括但不限于用于制作整合包。
- 5、该衍生作品的名称必须以 “PCL” 或 “Plain Craft Launcher” 开头,并在其后添加后缀,例如 “PCL 启动修复版”、“PCL PR Collection”。
- 6、该衍生作品不得包含 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 的赞助解锁功能类似的功能或表现。
+ 例如:界面的主要色相必须为蓝色(即使没有修改功能,改为其他色相也和需要赞助解锁的主题功能类似)。
————————————
@@ -28,5 +43,7 @@
因为没有法律专业人士检查,所以这不敢作为正式的法律协议,如果有专业人士愿意帮忙改一改我也很乐意……
这些规则主要是为了阻止恶意的使用和 “山寨版” 的出现,常规、善意的使用都没啥问题的,放心吧!
-如果你只是想参考一下,或者用了一小部分代码,署个名就行啦。
-如果你整了个魔改版,或者用了一大堆代码,请仔细参考上面的第三条。
+如果你只是参考了一小段代码,署个名就行啦,不用担心。
+如果对具体细节有疑问,欢迎在 https://github.com/Hex-Dragon/PCL2/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 7ea53f5..8d134eb 100644
--- a/Plain Craft Launcher 2/App.config
+++ b/Plain Craft Launcher 2/App.config
@@ -1,33 +1,35 @@
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plain Craft Launcher 2/Application.xaml b/Plain Craft Launcher 2/Application.xaml
index ec434f0..adbf7c0 100644
--- a/Plain Craft Launcher 2/Application.xaml
+++ b/Plain Craft Launcher 2/Application.xaml
@@ -13,6 +13,13 @@
+
+
+
+
+
+
+
#343d4a
@@ -281,21 +288,21 @@
-
+
-
+
-
+
-
+
diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb
index 2ee8be9..eddd8d5 100644
--- a/Plain Craft Launcher 2/Application.xaml.vb
+++ b/Plain Craft Launcher 2/Application.xaml.vb
@@ -1,5 +1,4 @@
-Imports System.Net
-Imports System.Reflection
+Imports System.Reflection
Imports System.Windows.Threading
Public Class Application
@@ -26,12 +25,20 @@ Public Class Application
If e.Args(0) = "--update" Then
'自动更新
UpdateReplace(e.Args(1), e.Args(2).Trim(""""), e.Args(3).Trim(""""), e.Args(4))
- Environment.Exit(Result.Cancel)
+ Environment.Exit(ProcessReturnValues.TaskDone)
+ ElseIf e.Args(0) = "--gpu" Then
+ '调整显卡设置
+ Try
+ SetGPUPreference(e.Args(1).Trim(""""))
+ Environment.Exit(ProcessReturnValues.TaskDone)
+ Catch ex As Exception
+ Environment.Exit(ProcessReturnValues.Fail)
+ End Try
ElseIf e.Args(0).StartsWithF("--memory") Then
'内存优化
Dim Ram = My.Computer.Info.AvailablePhysicalMemory
Try
- PageOtherTest.MemoryOptimizeInternal()
+ PageOtherTest.MemoryOptimizeInternal(False)
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Critical, "内存优化失败")
Environment.Exit(-1)
@@ -45,10 +52,10 @@ Public Class Application
'制作更新包
ElseIf e.Args(0) = "--edit1" Then
ExeEdit(e.Args(1), True)
- Environment.Exit(Result.Cancel)
+ Environment.Exit(ProcessReturnValues.TaskDone)
ElseIf e.Args(0) = "--edit2" Then
ExeEdit(e.Args(1), False)
- Environment.Exit(Result.Cancel)
+ Environment.Exit(ProcessReturnValues.TaskDone)
#End If
End If
End If
@@ -72,14 +79,22 @@ Public Class Application
Directory.CreateDirectory(PathAppdata)
'检测单例
#If Not DEBUG Then
+ Dim ShouldWaitForExit As Boolean = e.Args.Length > 0 AndAlso e.Args(0) = "--wait" '要求等待已有的 PCL 退出
+ Dim WaitRetryCount As Integer = 0
+WaitRetry:
Dim WindowHwnd As IntPtr = FindWindow(Nothing, "Plain Craft Launcher ")
If WindowHwnd = IntPtr.Zero Then FindWindow(Nothing, "Plain Craft Launcher 2 ")
If WindowHwnd <> IntPtr.Zero Then
+ If ShouldWaitForExit AndAlso WaitRetryCount < 20 Then '至多等待 10 秒
+ WaitRetryCount += 1
+ Thread.Sleep(500)
+ GoTo WaitRetry
+ End If
'将已有的 PCL 窗口拖出来
ShowWindowToTop(WindowHwnd)
'播放提示音并退出
Beep()
- Environment.[Exit](Result.Cancel)
+ Environment.[Exit](ProcessReturnValues.Cancel)
End If
#End If
'设置 ToolTipService 默认值
@@ -96,29 +111,27 @@ Public Class Application
End If
'动态 DLL 调用
AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf AssemblyResolve
- SetDllDirectory(Path & "PCL\")
- If Is32BitSystem Then
- File.WriteAllBytes(Path & "PCL\libwebp.dll", GetResources("libwebp32"))
- Else
- File.WriteAllBytes(Path & "PCL\libwebp.dll", GetResources("libwebp64"))
- End If
'日志初始化
LogStart()
'添加日志
- Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode})")
+ Log($"[Start] 程序版本:{VersionDisplayName} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
Log($"[Start] 识别码:{UniqueAddress}{If(ThemeCheckOne(9), ",已解锁反馈主题", "")}")
Log($"[Start] 程序路径:{PathWithName}")
- Log($"[Start] 系统编码:{Encoding.Default} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
+ Log($"[Start] 系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
Log($"[Start] 管理员权限:{IsAdmin()}")
- '检测压缩包运行
+ '检测异常环境
If Path.Contains(IO.Path.GetTempPath()) OrElse Path.Contains("AppData\Local\Temp\") Then
- MyMsgBox("PCL 正在临时文件夹运行,设置、游戏存档等很可能无法保存,且部分功能会无法使用或出错。" & vbCrLf & "请将 PCL 从压缩文件中解压,或是更换文件夹后再继续使用!", "环境警告", "我知道了", IsWarn:=True)
+ MyMsgBox("请将 PCL 从压缩包中解压之后再使用!" & vbCrLf & "在当前环境下运行可能会导致丢失游戏存档或设置,部分功能也可能无法使用!", "环境警告", "我知道了", IsWarn:=True)
+ End If
+ If Is32BitSystem Then
+ MyMsgBox("PCL 和新版 Minecraft 均不再支持 32 位系统,部分功能将无法使用。" & vbCrLf & "非常建议重装为 64 位系统后再进行游戏!", "环境警告", "我知道了", IsWarn:=True)
End If
'设置初始化
Setup.Load("SystemDebugMode")
Setup.Load("SystemDebugAnim")
Setup.Load("ToolDownloadThread")
Setup.Load("ToolDownloadCert")
+ Setup.Load("ToolDownloadSpeed")
'网络配置初始化
ServicePointManager.Expect100Continue = True
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 Or SecurityProtocolType.Tls Or SecurityProtocolType.Tls11 Or SecurityProtocolType.Tls12
@@ -138,7 +151,7 @@ Public Class Application
Catch
End Try
MsgBox(GetExceptionDetail(ex, True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
- FormMain.EndProgramForce(Result.Exception)
+ FormMain.EndProgramForce(ProcessReturnValues.Exception)
End Try
End Sub
@@ -155,7 +168,7 @@ Public Class Application
If IsProgramEnded Then Exit Sub
If IsCritErrored Then
'在汇报错误后继续引发错误,知道这次压不住了
- FormMain.EndProgramForce(Result.Exception)
+ FormMain.EndProgramForce(ProcessReturnValues.Exception)
Exit Sub
End If
IsCritErrored = True
@@ -165,8 +178,8 @@ Public Class Application
ExceptionString.Contains(".NET Framework") OrElse ' “自动错误判断” 的结果分析
ExceptionString.Contains("未能加载文件或程序集") 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(Result.Cancel)
+ MsgBox("你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.6.2!", MsgBoxStyle.Information, "运行环境错误")
+ FormMain.EndProgramForce(ProcessReturnValues.Cancel)
Else
FeedbackInfo()
Log(e.Exception, "程序出现未知错误", LogLevel.Assert, "锟斤拷烫烫烫")
@@ -177,11 +190,12 @@ Public Class Application
Private Shared AssemblyNAudio As Assembly
Private Shared AssemblyJson As Assembly
Private Shared AssemblyDialog As Assembly
- Private Shared AssemblyWebp 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 AssemblyWebpLock 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
@@ -208,12 +222,14 @@ Public Class Application
Return AssemblyDialog
End SyncLock
ElseIf args.Name.StartsWithF("Imazen.WebP") Then
- SyncLock AssemblyWebpLock
- If AssemblyWebp Is Nothing Then
+ SyncLock AssemblyImazenWebpLock
+ If AssemblyImazenWebp Is Nothing Then
Log("[Start] 加载 DLL:Imazen.WebP")
- AssemblyWebp = Assembly.Load(GetResources("WebP"))
+ AssemblyImazenWebp = Assembly.Load(GetResources("Imazen_WebP"))
+ SetDllDirectory(PathPure.TrimEnd("\"))
+ WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64"))
End If
- Return AssemblyWebp
+ Return AssemblyImazenWebp
End SyncLock
Else
Return Nothing
diff --git a/Plain Craft Launcher 2/Controls/MyButton.xaml b/Plain Craft Launcher 2/Controls/MyButton.xaml
index 5cff437..ad6142d 100644
--- a/Plain Craft Launcher 2/Controls/MyButton.xaml
+++ b/Plain Craft Launcher 2/Controls/MyButton.xaml
@@ -7,7 +7,7 @@
-
+
diff --git a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb
index dae17b2..72a968c 100644
--- a/Plain Craft Launcher 2/Controls/MyButton.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyButton.xaml.vb
@@ -1,22 +1,30 @@
-Public Class MyButton
+Imports System.Windows.Markup
+
+
+Public Class MyButton
'声明
Public Event Click(sender As Object, e As MouseButtonEventArgs) '自定义事件
'自定义属性
Public Uuid As Integer = GetUuid()
+ Public ReadOnly Property Inlines As InlineCollection
+ Get
+ Return LabText.Inlines
+ End Get
+ End Property
Public Property Text As String
Get
- Return LabText.Text
+ Return GetValue(TextProperty)
End Get
Set(value As String)
- LabText.Text = value
+ SetValue(TextProperty, value)
End Set
End Property '显示文本
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyButton), New PropertyMetadata(New PropertyChangedCallback(
- Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
- If Not IsNothing(sender) Then CType(sender, MyButton).LabText.Text = e.NewValue
- End Sub)))
+ Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
+ If sender IsNot Nothing Then CType(sender, MyButton).LabText.Text = e.NewValue
+ End Sub)))
Public Property TextPadding As Thickness
Get
Return LabText.Padding
diff --git a/Plain Craft Launcher 2/Controls/MyCard.vb b/Plain Craft Launcher 2/Controls/MyCard.vb
index 8ae95ef..1b1a5b5 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
- Private ReadOnly MainChrome As SystemDropShadowChrome
+ Public ReadOnly Property MainChrome As SystemDropShadowChrome
Private ReadOnly MainBorder As Border
Public Property BorderChild As UIElement
Get
@@ -36,6 +36,20 @@
'属性
Public Uuid As Integer = GetUuid()
+ Public ReadOnly Property Inlines As InlineCollection
+ Get
+ Return MainTextBlock.Inlines
+ End Get
+ End Property
+ Public Property CornerRadius As CornerRadius
+ Get
+ Return MainChrome.CornerRadius
+ End Get
+ Set(value As CornerRadius)
+ MainChrome.CornerRadius = value
+ MainBorder.CornerRadius = value
+ End Set
+ End Property
Public Property Title As String
Get
Return GetValue(TitleProperty)
@@ -62,7 +76,7 @@
If IsLoad Then Exit Sub
IsLoad = True
'初次加载限定
- If Title <> "" AndAlso MainTextBlock Is Nothing Then
+ If MainTextBlock Is Nothing Then
MainTextBlock = New TextBlock With {.HorizontalAlignment = HorizontalAlignment.Left, .VerticalAlignment = VerticalAlignment.Top, .Margin = New Thickness(15, 12, 0, 0), .FontWeight = FontWeights.Bold, .FontSize = 13, .IsHitTestVisible = False}
MainTextBlock.SetResourceReference(TextBlock.ForegroundProperty, "ColorBrush1")
MainTextBlock.SetBinding(TextBlock.TextProperty, New Binding("Title") With {.Source = Me, .Mode = BindingMode.OneWay})
@@ -98,13 +112,13 @@
'排序
Select Case Type
Case 3
- Stack.Tag = Sort(CType(Stack.Tag, List(Of DlOptiFineListEntry)), Function(a, b) VersionSortBoolean(a.NameDisplay, b.NameDisplay))
+ Stack.Tag = CType(Stack.Tag, List(Of DlOptiFineListEntry)).Sort(Function(a, b) VersionSortBoolean(a.NameDisplay, b.NameDisplay))
Case 4, 10
- Stack.Tag = Sort(CType(Stack.Tag, List(Of DlLiteLoaderListEntry)), Function(a, b) VersionSortBoolean(a.Inherit, b.Inherit))
+ Stack.Tag = CType(Stack.Tag, List(Of DlLiteLoaderListEntry)).Sort(Function(a, b) VersionSortBoolean(a.Inherit, b.Inherit))
Case 6
- Stack.Tag = Sort(CType(Stack.Tag, List(Of DlForgeVersionEntry)), Function(a, b) a.Version > b.Version)
+ Stack.Tag = CType(Stack.Tag, List(Of DlForgeVersionEntry)).Sort(Function(a, b) a.Version > b.Version)
Case 8, 9
- Stack.Tag = Sort(CType(Stack.Tag, List(Of CompFile)), Function(a, b) a.ReleaseDate > b.ReleaseDate)
+ Stack.Tag = CType(Stack.Tag, List(Of CompFile)).Sort(Function(a, b) a.ReleaseDate > b.ReleaseDate)
End Select
'控件转换
Select Case Type
@@ -180,10 +194,10 @@
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))
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, SystemDropShadowChrome.ColorProperty, "ColorObject2", 180),
+ AaColor(MainBorder, Border.BackgroundProperty, New MyColor(230, 255, 255, 255) - MainBorder.Background, 180),
+ AaOpacity(MainChrome, 0.3 - MainChrome.Opacity, 180)
+ })
AniStart(AniList, "MyCard Mouse " & Uuid)
End Sub
Private Sub MyCard_MouseLeave(sender As Object, e As MouseEventArgs) Handles Me.MouseLeave
@@ -192,10 +206,10 @@
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))
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, SystemDropShadowChrome.ColorProperty, "ColorObject1", 300),
+ AaColor(MainBorder, Border.BackgroundProperty, New MyColor(205, 255, 255, 255) - MainBorder.Background, 300),
+ AaOpacity(MainChrome, 0.1 - MainChrome.Opacity, 300)
+ })
AniStart(AniList, "MyCard Mouse " & Uuid)
End Sub
@@ -222,20 +236,21 @@
'高度增加较大,使用弹起动画
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))
- })
+ 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))
+ })
Else
'普通的改变就行啦
AnimList.AddRange({
- AaHeight(Me, DeltaHeight, MathClamp(Math.Abs(DeltaHeight) * 4, 150, 250),, New AniEaseOutFluent)
- })
+ AaHeight(Me, DeltaHeight, MathClamp(Math.Abs(DeltaHeight) * 4, 150, 250),, New AniEaseOutFluent)
+ })
End If
- AnimList.Add(AaCode(Sub()
- IsHeightAnimating = False
- Height = ActualUsedHeight
- If IsSwaped Then SwapControl.Visibility = Visibility.Collapsed
- End Sub,, True))
+ AnimList.Add(AaCode(
+ Sub()
+ IsHeightAnimating = False
+ Height = ActualUsedHeight
+ If IsSwaped Then SwapControl.Visibility = Visibility.Collapsed
+ End Sub,, True))
AniStart(AnimList, "MyCard Height " & Uuid)
IsHeightAnimating = True
@@ -253,7 +268,8 @@
#End Region
- '折叠
+#Region "折叠"
+
'若设置了 CanSwap,或 SwapControl 不为空,则判定为会进行折叠
'这是因为不能直接在 XAML 中设置 SwapControl
Public SwapControl As Object
@@ -293,49 +309,54 @@
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 (IsNothing(SwapControl) OrElse Pos > SwapedHeight OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判
+ If Not IsSwaped AndAlso
+ (SwapControl Is Nothing OrElse Pos > If(IsSwaped, SwapedHeight, SwapedHeight - 6) OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判
IsMouseDown = True
End Sub
Private Sub MyCard_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseLeftButtonUp
- If IsMouseDown Then
+ If Not IsMouseDown Then Return
+ 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 '检测点击位置;或已经不在可视树上的误判
+
+ Dim ee = New RouteEventArgs(True)
+ RaiseEvent PreviewSwap(Me, ee)
+ If ee.Handled Then
IsMouseDown = False
- Dim Pos As Double = Mouse.GetPosition(Me).Y
- If Not IsSwaped AndAlso (IsNothing(SwapControl) OrElse Pos > SwapedHeight OrElse (Pos = 0 AndAlso Not IsMouseDirectlyOver)) Then Exit Sub '检测点击位置;或已经不在可视树上的误判
-
- Dim ee = New RouteEventArgs(True)
- RaiseEvent PreviewSwap(Me, ee)
- If ee.Handled Then
- IsMouseDown = False
- Exit Sub
- End If
-
- IsSwaped = Not IsSwaped
- Log("[Control] " & If(IsSwaped, "折叠卡片", "展开卡片") & If(Title Is Nothing, "", ":" & Title))
- RaiseEvent Swap(Me, ee)
+ Return
End If
+
+ IsSwaped = Not IsSwaped
+ Log("[Control] " & If(IsSwaped, "折叠卡片", "展开卡片") & 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
IsMouseDown = False
End Sub
+#End Region
+
End Class
Partial Public Module ModAnimation
Public Sub AniDispose(Control As MyCard, RemoveFromChildren As Boolean, Optional CallBack As ParameterizedThreadStart = Nothing)
If Control.IsHitTestVisible Then
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
- If Control.Parent Is Nothing Then Exit Sub
- CType(Control.Parent, Object).Children.Remove(Control)
- Else
- Control.Visibility = Visibility.Collapsed
- End If
- If CallBack IsNot Nothing Then CallBack(Control)
- End Sub,, True)
+ 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
+ If Control.Parent Is Nothing Then Exit Sub
+ 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)
Else
If RemoveFromChildren Then
diff --git a/Plain Craft Launcher 2/Controls/MyCheckBox.xaml b/Plain Craft Launcher 2/Controls/MyCheckBox.xaml
index 0f369e5..c0cac06 100644
--- a/Plain Craft Launcher 2/Controls/MyCheckBox.xaml
+++ b/Plain Craft Launcher 2/Controls/MyCheckBox.xaml
@@ -4,7 +4,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="MyCheckBox"
FocusVisualStyle="{x:Null}"
MinWidth="20" x:Name="PanBack" UseLayoutRounding="False" SnapsToDevicePixels="False" MinHeight="20" Background="{StaticResource ColorBrushSemiTransparent}" Focusable="True" d:DesignWidth="126.4" d:DesignHeight="44.8">
-
+
diff --git a/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb b/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb
index d531cc8..4211286 100644
--- a/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyCheckBox.xaml.vb
@@ -1,4 +1,7 @@
-Public Class MyCheckBox
+Imports System.Windows.Markup
+
+
+Public Class MyCheckBox
'基础
@@ -14,29 +17,32 @@
End Sub '使外部程序引发本控件的 Change 事件
'自定义属性
-
- Private _Checked As Boolean = False '是否选中
Public Property Checked As Boolean
Get
- Return _Checked
+ Return GetValue(CheckedProperty)
End Get
Set(value As Boolean)
- SetChecked(value, False, True)
+ SetChecked(value, False)
End Set
End Property
+ Public Shared ReadOnly CheckedProperty As DependencyProperty =
+ DependencyProperty.Register("Checked", GetType(Boolean), GetType(MyCheckBox), New PropertyMetadata(False,
+ Sub(d As MyCheckBox, e As DependencyPropertyChangedEventArgs)
+ '在使用 XAML 设置 Checked 属性时,不会触发 Checked_Set 方法,所以需要在这里手动触发 UI 改变
+ If Not d.IsLoaded Then d.SyncUI()
+ End Sub))
+
Private Const AnimationTimeOfCheck As Integer = 150 '勾选状态变更动画长度
'''
''' 手动设置 Checked 属性。
'''
''' 新的 Checked 属性。
''' 是否由用户引发。
- ''' 是否执行动画。
- Public Sub SetChecked(value As Boolean, user As Boolean, anime As Boolean)
+ Public Sub SetChecked(value As Boolean, user As Boolean)
Try
- If value = _Checked Then Exit Sub
+ If value = Checked Then Exit Sub
'Preview 事件
-
If value AndAlso user Then
Dim e = New RouteEventArgs(user)
RaiseEvent PreviewChange(Me, e)
@@ -48,60 +54,67 @@
End If
End If
- _Checked = value
+ SetValue(CheckedProperty, value)
If IsLoaded Then RaiseEvent Change(Me, user)
'更改动画
-
- If IsLoaded AndAlso AniControlEnabled = 0 AndAlso anime Then '防止默认属性变更触发动画
- AllowMouseDown = False
- If Checked Then
- '由无变有
- AniStart({
- AaScale(ShapeBorder, 12 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True),
- AaScaleTransform(ShapeCheck, 1 - CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack(AniEasePower.Weak)),
- AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
- }, "MyCheckBox Scale " & Uuid)
- AniStart({
- AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush2"), "ColorBrushGray4"), AnimationTimeOfCheck)
- }, "MyCheckBox BorderColor " & Uuid)
- AniStart({
- AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
- }, "MyCheckBox AllowMouseDown " & Uuid)
- Else
- '由有变无
- AniStart({
- AaScale(ShapeBorder, 12 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True),
- AaScaleTransform(ShapeCheck, -CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX, AnimationTimeOfCheck * 0.9, , New AniEaseInFluent(AniEasePower.Weak)),
- AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
- }, "MyCheckBox Scale " & Uuid)
- AniStart({
- AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfCheck)
- }, "MyCheckBox BorderColor " & Uuid)
- AniStart({
- AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
- }, "MyCheckBox AllowMouseDown " & Uuid)
- End If
- Else
- '不使用动画
- AniStop("MyCheckBox Scale " & Uuid)
- AniStop("MyCheckBox BorderColor " & Uuid)
- AniStop("MyCheckBox AllowMouseDown " & Uuid)
- If Checked Then
- CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 1
- CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 1
- ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray4"))
- Else
- CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 0
- CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 0
- ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
- End If
- End If
-
+ SyncUI()
Catch ex As Exception
Log(ex, "设置 Checked 失败")
End Try
End Sub
+ Private Sub SyncUI()
+ If AniControlEnabled = 0 AndAlso IsLoaded Then '防止默认属性变更触发动画
+ AllowMouseDown = False
+ If Checked Then
+ '由无变有
+ AniStart({
+ AaScale(ShapeBorder, 12 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True),
+ AaScaleTransform(ShapeCheck, 1 - CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack(AniEasePower.Weak)),
+ AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
+ }, "MyCheckBox Scale " & Uuid)
+ AniStart({
+ AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush2"), "ColorBrushGray4"), AnimationTimeOfCheck)
+ }, "MyCheckBox BorderColor " & Uuid)
+ AniStart({
+ AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
+ }, "MyCheckBox AllowMouseDown " & Uuid)
+ Else
+ '由有变无
+ AniStart({
+ AaScale(ShapeBorder, 12 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True),
+ AaScaleTransform(ShapeCheck, -CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX, AnimationTimeOfCheck * 0.9, , New AniEaseInFluent(AniEasePower.Weak)),
+ AaScale(ShapeBorder, 6, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.7, New AniEaseOutBack, , True)
+ }, "MyCheckBox Scale " & Uuid)
+ AniStart({
+ AaColor(ShapeBorder, Border.BorderBrushProperty, If(IsEnabled, If(IsMouseOver, "ColorBrush3", "ColorBrush1"), "ColorBrushGray4"), AnimationTimeOfCheck)
+ }, "MyCheckBox BorderColor " & Uuid)
+ AniStart({
+ AaCode(Sub() AllowMouseDown = True, AnimationTimeOfCheck * 2)
+ }, "MyCheckBox AllowMouseDown " & Uuid)
+ End If
+ Else
+ '不使用动画
+ AniStop("MyCheckBox Scale " & Uuid)
+ AniStop("MyCheckBox BorderColor " & Uuid)
+ AniStop("MyCheckBox AllowMouseDown " & Uuid)
+ If Checked Then
+ CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 1
+ CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 1
+ ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray4"))
+ Else
+ CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleX = 0
+ CType(ShapeCheck.RenderTransform, ScaleTransform).ScaleY = 0
+ ShapeBorder.SetResourceReference(Border.BorderBrushProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
+ End If
+ End If
+ End Sub
+
+ Public ReadOnly Property Inlines As InlineCollection
+ Get
+ Return LabText.Inlines
+ End Get
+ End Property
Public Property Text As String
Get
Return GetValue(TextProperty)
@@ -110,10 +123,10 @@
SetValue(TextProperty, value)
End Set
End Property '内容
- Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyCheckbox), New PropertyMetadata(New PropertyChangedCallback(
- Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
- If Not IsNothing(sender) Then CType(sender, MyCheckbox).LabText.Text = e.NewValue
- End Sub)))
+ Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyCheckBox), New PropertyMetadata(New PropertyChangedCallback(
+ Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
+ If Not IsNothing(sender) Then CType(sender, MyCheckBox).LabText.Text = e.NewValue
+ End Sub)))
'点击事件
@@ -123,7 +136,7 @@
If Not MouseDowned Then Exit Sub
Log("[Control] 按下复选框(" & (Not Checked).ToString & "):" & Text)
MouseDowned = False
- SetChecked(Not Checked, True, True)
+ SetChecked(Not Checked, True)
AniStart(AaColor(ShapeBorder, Border.BackgroundProperty, "ColorBrushHalfWhite", 100), "MyCheckBox Background " & Uuid)
End Sub
Private Sub Checkbox_MouseDown() Handles Me.MouseLeftButtonDown
diff --git a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
index d6da198..c26cb79 100644
--- a/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyExtraButton.xaml.vb
@@ -113,9 +113,9 @@
Private Sub Button_LeftMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonDown
If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then
AniStart({
- AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
- AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
- }, "MyExtraButton Scale " & Uuid)
+ AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
+ AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
+ }, "MyExtraButton Scale " & Uuid)
End If
IsLeftMouseHeld = True
Focus()
@@ -124,9 +124,9 @@
If Not CanRightClick Then Exit Sub
If Not IsLeftMouseHeld AndAlso Not IsRightMouseHeld Then
AniStart({
- AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
- AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
- }, "MyExtraButton Scale " & Uuid)
+ AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
+ AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
+ }, "MyExtraButton Scale " & Uuid)
End If
IsRightMouseHeld = True
Focus()
@@ -134,8 +134,8 @@
Private Sub Button_LeftMouseUp() Handles PanClick.MouseLeftButtonUp
If Not IsRightMouseHeld Then
AniStart({
- AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
- }, "MyExtraButton Scale " & Uuid)
+ AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
+ }, "MyExtraButton Scale " & Uuid)
End If
IsLeftMouseHeld = False
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
@@ -144,8 +144,8 @@
If Not CanRightClick Then Exit Sub
If Not IsLeftMouseHeld Then
AniStart({
- AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
- }, "MyExtraButton Scale " & Uuid)
+ AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
+ }, "MyExtraButton Scale " & Uuid)
End If
IsRightMouseHeld = False
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
@@ -154,8 +154,8 @@
IsLeftMouseHeld = False
IsRightMouseHeld = False
AniStart({
- AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
- }, "MyExtraButton Scale " & Uuid)
+ AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
+ }, "MyExtraButton Scale " & Uuid)
RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
End Sub
@@ -163,11 +163,14 @@
'务必放在 IsMouseDown 更新之后
Private Const AnimationColorIn As Integer = 120
Private Const AnimationColorOut As Integer = 150
- Public Sub RefreshColor() Handles PanClick.MouseEnter, PanClick.MouseLeave, Me.Loaded
+ Public Sub RefreshColor() Handles PanClick.MouseEnter, PanClick.MouseLeave, Me.Loaded, Me.IsEnabledChanged
Try
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
- If IsMouseOver Then
+ If Not IsEnabled Then
+ '禁用
+ AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrushGray4", AnimationColorIn), "MyExtraButton Color " & Uuid)
+ ElseIf IsMouseOver Then
'指向
AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush4", AnimationColorIn), "MyExtraButton Color " & Uuid)
Else
@@ -178,7 +181,9 @@
Else
AniStop("MyExtraButton Color " & Uuid)
- If IsMouseOver Then
+ If Not IsEnabled Then
+ PanColor.SetResourceReference(BackgroundProperty, "ColorBrushGray4")
+ ElseIf IsMouseOver Then
PanColor.SetResourceReference(BackgroundProperty, "ColorBrush4")
Else
PanColor.SetResourceReference(BackgroundProperty, "ColorBrush3")
diff --git a/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml
new file mode 100644
index 0000000..795a7bd
--- /dev/null
+++ b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb
new file mode 100644
index 0000000..67ea22f
--- /dev/null
+++ b/Plain Craft Launcher 2/Controls/MyExtraTextButton.xaml.vb
@@ -0,0 +1,163 @@
+Imports System.Windows.Markup
+
+
+Public Class MyExtraTextButton
+
+ '声明
+ Public Event Click(sender As Object, e As MouseButtonEventArgs) '自定义事件
+
+ '自定义属性
+ Public Uuid As Integer = GetUuid()
+ Private _Logo As String = ""
+ Public Property Logo As String
+ Get
+ Return _Logo
+ End Get
+ Set(value As String)
+ If value = _Logo Then Exit Property
+ _Logo = value
+ Path.Data = (New GeometryConverter).ConvertFromString(value)
+ End Set
+ End Property
+ Private _LogoScale As Double = 1
+ Public Property LogoScale() As Double
+ Get
+ Return _LogoScale
+ End Get
+ Set(value As Double)
+ _LogoScale = value
+ If Path IsNot Nothing Then Path.RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale}
+ End Set
+ End Property
+ '显示文本
+ Public ReadOnly Property Inlines As InlineCollection
+ Get
+ Return LabText.Inlines
+ End Get
+ End Property
+ Public Property Text As String
+ Get
+ Return GetValue(TextProperty)
+ End Get
+ Set(value As String)
+ SetValue(TextProperty, value)
+ End Set
+ End Property
+ Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyExtraTextButton), New PropertyMetadata(New PropertyChangedCallback(
+ Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
+ If sender IsNot Nothing Then CType(sender, MyExtraTextButton).LabText.Text = e.NewValue
+ End Sub)))
+
+ '动画
+ Private _Show As Boolean = False
+ Public Property Show As Boolean
+ Get
+ Return _Show
+ End Get
+ Set(value As Boolean)
+ If _Show = value Then Exit Property
+ _Show = value
+ RunInUi(
+ Sub()
+ If value Then
+ '有了
+ Opacity = 0
+ AniStart({
+ AaOpacity(Me, 1 - Opacity, 80, 50),
+ AaScaleTransform(Me, 0.15 - CType(RenderTransform, ScaleTransform).ScaleX, 400, 50, New AniEaseOutBack),
+ AaScaleTransform(Me, 0.85, 160, 50, New AniEaseOutFluent(AniEasePower.Middle))
+ }, "MyExtraTextButton MainScale " & Uuid)
+ Else
+ '没了
+ AniStart({
+ AaOpacity(Me, -Opacity, 50, 50),
+ AaScaleTransform(Me, -CType(RenderTransform, ScaleTransform).ScaleX, 100,, New AniEaseInFluent(AniEasePower.Weak))
+ }, "MyExtraTextButton MainScale " & Uuid)
+ End If
+ IsHitTestVisible = value '防止缩放动画中依然可以点进去
+ End Sub)
+ End Set
+ End Property
+
+ '触发点击事件
+ Private Sub Button_LeftMouseUp(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonUp
+ If IsLeftMouseHeld Then
+ Log("[Control] 按下附加图标按钮:" & Text)
+ RaiseEvent Click(sender, e)
+ e.Handled = True
+ Button_LeftMouseUp()
+ End If
+ End Sub
+
+ '鼠标点击判定(务必放在点击事件之后,以使得 Button_MouseUp 先于 Button_MouseLeave 执行)
+ Private IsLeftMouseHeld As Boolean = False
+ Private Sub Button_LeftMouseDown(sender As Object, e As MouseButtonEventArgs) Handles PanClick.MouseLeftButtonDown
+ If Not IsLeftMouseHeld Then
+ AniStart({
+ AaScaleTransform(PanScale, 0.85 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 800,, New AniEaseOutFluent(AniEasePower.Strong)),
+ AaScaleTransform(PanScale, -0.05, 60,, New AniEaseOutFluent)
+ }, "MyExtraTextButton Scale " & Uuid)
+ End If
+ IsLeftMouseHeld = True
+ Focus()
+ End Sub
+ Private Sub Button_LeftMouseUp() Handles PanClick.MouseLeftButtonUp
+ AniStart({
+ AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
+ }, "MyExtraTextButton Scale " & Uuid)
+ IsLeftMouseHeld = False
+ RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
+ End Sub
+ Private Sub Button_RightMouseUp() Handles PanClick.MouseRightButtonUp
+ If Not IsLeftMouseHeld Then
+ AniStart({
+ AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 300,, New AniEaseOutBack)
+ }, "MyExtraTextButton Scale " & Uuid)
+ End If
+ RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
+ End Sub
+ Private Sub Button_MouseLeave() Handles PanClick.MouseLeave
+ IsLeftMouseHeld = False
+ AniStart({
+ AaScaleTransform(PanScale, 1 - CType(PanScale.RenderTransform, ScaleTransform).ScaleX, 500,, New AniEaseOutFluent)
+ }, "MyExtraTextButton Scale " & Uuid)
+ RefreshColor() '直接刷新颜色以判断是否已触发 MouseLeave
+ End Sub
+
+ '自定义事件
+ '务必放在 IsMouseDown 更新之后
+ Private Const AnimationColorIn As Integer = 120
+ Private Const AnimationColorOut As Integer = 150
+ Public Sub RefreshColor() Handles PanClick.MouseEnter, PanClick.MouseLeave, Me.Loaded, Me.IsEnabledChanged
+ Try
+ If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
+
+ If Not IsEnabled Then
+ '禁用
+ AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrushGray4", AnimationColorIn), "MyExtraTextButton Color " & Uuid)
+ ElseIf IsMouseOver Then
+ '指向
+ AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush4", AnimationColorIn), "MyExtraTextButton Color " & Uuid)
+ Else
+ '普通
+ AniStart(AaColor(PanColor, BackgroundProperty, "ColorBrush3", AnimationColorOut), "MyExtraTextButton Color " & Uuid)
+ End If
+
+ Else
+
+ AniStop("MyExtraTextButton Color " & Uuid)
+ If Not IsEnabled Then
+ PanColor.SetResourceReference(BackgroundProperty, "ColorBrushGray4")
+ ElseIf IsMouseOver Then
+ PanColor.SetResourceReference(BackgroundProperty, "ColorBrush4")
+ Else
+ PanColor.SetResourceReference(BackgroundProperty, "ColorBrush3")
+ End If
+
+ End If
+ Catch ex As Exception
+ Log(ex, "刷新附加图标按钮颜色出错")
+ End Try
+ End Sub
+
+End Class
diff --git a/Plain Craft Launcher 2/Controls/MyHint.xaml b/Plain Craft Launcher 2/Controls/MyHint.xaml
index 41570ca..09b5694 100644
--- a/Plain Craft Launcher 2/Controls/MyHint.xaml
+++ b/Plain Craft Launcher 2/Controls/MyHint.xaml
@@ -17,7 +17,7 @@
-
+
diff --git a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb
index fd025f0..9473374 100644
--- a/Plain Craft Launcher 2/Controls/MyHint.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyHint.xaml.vb
@@ -1,4 +1,7 @@
-Public Class MyHint
+Imports System.Windows.Markup
+
+
+Public Class MyHint
Public Uuid As Integer = GetUuid()
@@ -30,14 +33,24 @@
End Set
End Property
+ Public ReadOnly Property Inlines As InlineCollection
+ Get
+ Return LabText.Inlines
+ End Get
+ End Property
Public Property Text As String
Get
- Return LabText.Text
+ Return GetValue(TextProperty)
End Get
Set(value As String)
- LabText.Text = value
+ SetValue(TextProperty, value)
End Set
End Property
+ Public Shared ReadOnly TextProperty As DependencyProperty =
+ DependencyProperty.Register("Text", GetType(String), GetType(MyHint), New PropertyMetadata("",
+ Sub(d As MyHint, e As DependencyPropertyChangedEventArgs)
+ d.LabText.Text = e.NewValue
+ End Sub))
Public Property CanClose As Boolean
Get
@@ -48,7 +61,7 @@
End Set
End Property
- Public RelativeSetup As String = ""
+ 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
diff --git a/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb
index 48a3f1b..53ce505 100644
--- a/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyIconTextButton.xaml.vb
@@ -1,4 +1,7 @@
-Public Class MyIconTextButton
+Imports System.Windows.Markup
+
+
+Public Class MyIconTextButton
'基础
@@ -30,6 +33,11 @@
End Set
End Property
+ Public ReadOnly Property Inlines As InlineCollection
+ Get
+ Return LabText.Inlines
+ End Get
+ End Property
Public Property Text As String
Get
Return GetValue(TextProperty)
@@ -39,25 +47,25 @@
End Set
End Property '内容
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyIconTextButton), New PropertyMetadata(New PropertyChangedCallback(
- Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
- If Not IsNothing(sender) Then CType(sender, MyIconTextButton).LabText.Text = e.NewValue
- End Sub)))
+ Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
+ If Not IsNothing(sender) Then CType(sender, MyIconTextButton).LabText.Text = e.NewValue
+ End Sub)))
Public Enum ColorState
Black
Highlight
End Enum
- Private _ColorType As ColorState = ColorState.Black
Public Property ColorType As ColorState
Get
- Return _ColorType
+ Return GetValue(ColorTypeProperty)
End Get
Set(value As ColorState)
- _ColorType = value
+ If ColorType = value Then Return
+ SetValue(ColorTypeProperty, value)
RefreshColor()
End Set
End Property '颜色类别
Public Shared ReadOnly ColorTypeProperty As DependencyProperty =
- DependencyProperty.Register("ColorType", GetType(ColorState), GetType(MyIconTextButton), New PropertyMetadata())
+ DependencyProperty.Register("ColorType", GetType(ColorState), GetType(MyIconTextButton), New PropertyMetadata(ColorState.Black))
'点击事件
diff --git a/Plain Craft Launcher 2/Controls/MyImage.vb b/Plain Craft Launcher 2/Controls/MyImage.vb
new file mode 100644
index 0000000..eaf7289
--- /dev/null
+++ b/Plain Craft Launcher 2/Controls/MyImage.vb
@@ -0,0 +1,189 @@
+Public Class MyImage
+ Inherits Image
+
+#Region "公开属性"
+
+ '''
+ ''' 网络图片的缓存有效期。
+ ''' 在这个时间后,才会重新尝试下载图片。
+ '''
+ Public FileCacheExpiredTime As New TimeSpan(7, 0, 0, 0) '7 天
+
+ '''
+ ''' 是否允许将网络图片存储到本地用作缓存。
+ '''
+ Public Property EnableCache As Boolean
+ Get
+ Return GetValue(EnableCacheProperty)
+ End Get
+ Set(value As Boolean)
+ SetValue(EnableCacheProperty, value)
+ End Set
+ End Property
+ Public Shared Shadows ReadOnly EnableCacheProperty As DependencyProperty = DependencyProperty.Register(
+ "EnableCache", GetType(Boolean), GetType(MyImage), New PropertyMetadata(True))
+
+ '''
+ ''' 与 Image 的 Source 类似。
+ ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。
+ ''' 支持 WebP 格式的图片。
+ '''
+ Public Shadows Property Source As String '覆写 Image 的 Source 属性
+ Get
+ Return _Source
+ End Get
+ Set(value As String)
+ If value = "" Then value = Nothing
+ If _Source = value Then Exit Property
+ _Source = value
+ If Not IsInitialized Then Exit Property '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
+ Load()
+ End Set
+ End Property
+ Private _Source As String = ""
+ Public Shared Shadows ReadOnly SourceProperty As DependencyProperty = DependencyProperty.Register(
+ "Source", GetType(String), GetType(MyImage), New PropertyMetadata(New PropertyChangedCallback(
+ Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
+
+ '''
+ ''' 当 Source 首次下载失败时,会从该备用地址加载图片。
+ '''
+ Public Property FallbackSource As String
+ Get
+ Return _FallbackSource
+ End Get
+ Set(value As String)
+ _FallbackSource = value
+ End Set
+ End Property
+ Private _FallbackSource As String = Nothing
+
+ '''
+ ''' 正在下载网络图片时显示的本地图片。
+ '''
+ Public Property LoadingSource As String
+ Get
+ Return _LoadingSource
+ End Get
+ Set(value As String)
+ _LoadingSource = value
+ End Set
+ End Property
+ Private _LoadingSource As String = "pack://application:,,,/images/Icons/NoIcon.png"
+
+#End Region
+
+ '''
+ ''' 实际被呈现的图片地址。
+ '''
+ Public Property ActualSource As String
+ Get
+ Return _ActualSource
+ End Get
+ Set(value As String)
+ If value = "" Then value = Nothing
+ If _ActualSource = value Then Exit Property
+ _ActualSource = value
+ Try
+ Dim Bitmap As MyBitmap = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件
+ RunInUiWait(Sub() MyBase.Source = Bitmap)
+ Catch ex As Exception
+ Log(ex, $"加载图片失败({value})")
+ Try
+ If value.StartsWithF(PathTemp) AndAlso File.Exists(value) Then File.Delete(value)
+ Catch
+ End Try
+ End Try
+ End Set
+ End Property
+ Private _ActualSource As String = Nothing
+
+ Private Sub Load() _
+ Handles Me.Initialized '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
+ '空
+ If Source Is Nothing Then
+ ActualSource = Nothing
+ Exit Sub
+ End If
+ '本地图片
+ If Not Source.StartsWithF("http") Then
+ ActualSource = Source
+ Exit Sub
+ 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
+ If EnableCache AndAlso TempFile.Exists Then
+ ActualSource = TempPath
+ If (Date.Now - TempFile.LastWriteTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存
+ 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
+ '保存缓存并显示
+ If File.Exists(TempPath) Then File.Delete(TempPath)
+ FileSystem.Rename(TempDownloadingPath, TempPath)
+ RunInUi(Sub() ActualSource = TempPath)
+ Else
+ '直接显示
+ RunInUiWait(Sub() ActualSource = TempDownloadingPath)
+ File.Delete(TempDownloadingPath)
+ 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
+ End Try
+ End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
+ End Sub
+ Public Shared Function GetTempPath(Url As String) As String
+ Return $"{PathTemp}MyImage\{GetHash(Url)}.png"
+ End Function
+
+End Class
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb
index 60706f9..1f0e4ce 100644
--- a/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyListItem.xaml.vb
@@ -1,4 +1,7 @@
-Public Class MyListItem
+Imports System.Windows.Markup
+
+
+Public Class MyListItem
Implements IMyRadio
Public Event Click(sender As Object, e As MouseButtonEventArgs)
@@ -161,12 +164,17 @@
End Property
'标题
+ Public ReadOnly Property Inlines As InlineCollection
+ Get
+ Return LabTitle.Inlines
+ End Get
+ End Property
Public Property Title As String
Get
Return GetValue(TitleProperty)
End Get
Set(value As String)
- SetValue(TitleProperty, value)
+ SetValue(TitleProperty, value.Replace(vbCr, "").Replace(vbLf, ""))
End Set
End Property
Public Shared ReadOnly TitleProperty As DependencyProperty = DependencyProperty.Register("Title", GetType(String), GetType(MyListItem))
@@ -212,33 +220,26 @@
If Not _Logo = "" Then
If _Logo.StartsWithF("http", True) Then
'网络图片
- PathLogo = New Image With {
+ PathLogo = New MyImage With {
.Tag = Me,
.IsHitTestVisible = LogoClickable,
- .Source = New ImageSourceConverter().ConvertFromString(_Logo),
+ .Source = _Logo,
.RenderTransformOrigin = New Point(0.5, 0.5),
.RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale},
.SnapsToDevicePixels = True, .UseLayoutRounding = False}
RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.Linear)
- ElseIf _Logo.EndsWithF(".png", True) OrElse _Logo.EndsWithF(".jpg", True) Then
+ ElseIf _Logo.EndsWithF(".png", True) OrElse _Logo.EndsWithF(".jpg", True) OrElse _Logo.EndsWithF(".webp", True) Then
'位图
- Dim Bitmap = New MyBitmap(_Logo)
PathLogo = New Canvas With {
.Tag = Me,
.IsHitTestVisible = LogoClickable,
- .Background = Bitmap,
+ .Background = New MyBitmap(_Logo),
.RenderTransformOrigin = New Point(0.5, 0.5),
.RenderTransform = New ScaleTransform With {.ScaleX = LogoScale, .ScaleY = LogoScale},
- .SnapsToDevicePixels = True, .UseLayoutRounding = False}
- 'If Bitmap.Pic.Width = 16 AndAlso Bitmap.Pic.Height = 16 Then
- ' '使用最适合 16x16 物品图片显示的大小
- ' RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.NearestNeighbor)
- ' PathLogo.HorizontalAlignment = HorizontalAlignment.Center : PathLogo.VerticalAlignment = VerticalAlignment.Center
- ' PathLogo.Width = GetWPFSize(Math.Floor(GetPixelSize(32) / 16) * 16) : PathLogo.Height = PathLogo.Width
- 'Else
- PathLogo.HorizontalAlignment = HorizontalAlignment.Stretch : PathLogo.VerticalAlignment = VerticalAlignment.Stretch
- RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.HighQuality)
- 'End If
+ .SnapsToDevicePixels = True, .UseLayoutRounding = False,
+ .HorizontalAlignment = HorizontalAlignment.Stretch, .VerticalAlignment = VerticalAlignment.Stretch
+ }
+ RenderOptions.SetBitmapScalingMode(PathLogo, BitmapScalingMode.Linear)
Else
'矢量图
PathLogo = New Shapes.Path With {
@@ -327,8 +328,8 @@
Private Sub OnSizeChanged() Handles Me.SizeChanged
ColumnCheck.Width = New GridLength(If(_Type = CheckType.None OrElse _Type = CheckType.Clickable, If(Height < 40, 4, 2), 6))
ColumnLogo.Width = New GridLength(If(_Logo = "", 0, 34) + If(Height < 40, 0, 4))
- If Not IsNothing(PathLogo) Then
- If _Logo.EndsWithF(".png", True) Then
+ If PathLogo IsNot Nothing Then
+ If _Logo.EndsWithF(".png", True) OrElse _Logo.EndsWithF(".jpg", True) OrElse _Logo.EndsWithF(".webp", True) Then
PathLogo.Margin = New Thickness(4, 5, 3, 5)
Else
PathLogo.Margin = New Thickness(If(Height < 40, 6, 8), 8, If(Height < 40, 4, 6), 8)
@@ -661,6 +662,8 @@
Dim Unused = New HelpEntry(GetEventAbsoluteUrls(EventData, EventType)(0)).SetToListItem(Me)
Catch ex As Exception
Log(ex, "设置帮助 MyListItem 失败", LogLevel.Msgbox)
+ EventType = Nothing
+ EventData = Nothing
End Try
End If
End Sub
diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml
index bf23281..282ecb2 100644
--- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml
+++ b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml
@@ -25,7 +25,7 @@
+ VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMult="0.7">
diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb
index 56ae475..09ad043 100644
--- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgInput.xaml.vb
@@ -1,4 +1,4 @@
-Public Class MyMsgInput
+Public Class MyMsgInput
Private ReadOnly MyConverter As MyMsgBoxConverter
Private ReadOnly Uuid As Integer = GetUuid()
@@ -72,7 +72,8 @@
End Sub
Public Sub Btn1_Click() Handles Btn1.Click
- If MyConverter.IsExited OrElse Not TextArea.ValidateResult = "" Then Exit Sub
+ TextArea.Validate()
+ If MyConverter.IsExited OrElse Not TextArea.IsValidated Then Exit Sub
MyConverter.IsExited = True
MyConverter.Result = TextArea.Text
Close()
@@ -85,7 +86,7 @@
End Sub
Private Sub TextCaption_ValidateChanged(sender As Object, e As EventArgs) Handles TextArea.ValidateChanged
- Btn1.IsEnabled = TextArea.ValidateResult = ""
+ Btn1.IsEnabled = TextArea.IsValidated
End Sub
Private Sub Drag(sender As Object, e As MouseButtonEventArgs) Handles PanBorder.MouseLeftButtonDown, LabTitle.MouseLeftButtonDown
diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml
index 5c21822..b4f75ba 100644
--- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml
+++ b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgSelect.xaml
@@ -24,7 +24,7 @@
+ VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMult="0.7">
diff --git a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml
index 10f2a92..0bbf157 100644
--- a/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml
+++ b/Plain Craft Launcher 2/Controls/MyMsg/MyMsgText.xaml
@@ -24,7 +24,7 @@
+ VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled" DeltaMult="0.7">
diff --git a/Plain Craft Launcher 2/Controls/MyPageLeft.vb b/Plain Craft Launcher 2/Controls/MyPageLeft.vb
index b09897b..9c368a6 100644
--- a/Plain Craft Launcher 2/Controls/MyPageLeft.vb
+++ b/Plain Craft Launcher 2/Controls/MyPageLeft.vb
@@ -104,3 +104,7 @@
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 4fafd60..4c427b5 100644
--- a/Plain Craft Launcher 2/Controls/MyPageRight.vb
+++ b/Plain Craft Launcher 2/Controls/MyPageRight.vb
@@ -353,12 +353,16 @@
If Control.RenderTransform IsNot Nothing AndAlso TypeOf Control.RenderTransform Is TranslateTransform Then Control.RenderTransform = Nothing
Next
For Each Control As FrameworkElement In GetAllAnimControls(Element)
- Control.Opacity = 0
- Control.RenderTransform = New TranslateTransform(0, -16)
- AniList.Add(AaOpacity(Control, 1, 150, 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
+ If TypeOf Control Is MyExtraTextButton Then
+ CType(Control, MyExtraTextButton).Show = True
+ Else
+ Control.Opacity = 0
+ Control.RenderTransform = New TranslateTransform(0, -16)
+ AniList.Add(AaOpacity(Control, 1, 150, 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
+ End If
Next
Next
'滚动条动画
@@ -379,10 +383,14 @@
Dim Delay As Integer = 0
For Each Element In RealElements
For Each Control As FrameworkElement In GetAllAnimControls(Element)
- Control.IsHitTestVisible = False
- AniList.Add(AaOpacity(Control, -1, 90, Delay))
- AniList.Add(AaTranslateY(Control, -6, 90, Delay))
- Delay += 20
+ If TypeOf Control Is MyExtraTextButton Then
+ 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
+ End If
Next
Next
'滚动条动画
@@ -392,31 +400,38 @@
AniList.Add(AaTranslateX(Scroll, 10 - CType(Scroll.RenderTransform, TranslateTransform).X, 90, 0, New AniEaseInFluent))
End If
'结束
- AniList.Add(AaCode(Sub()
- For Each Element In RealElements
- Element.Visibility = Visibility.Collapsed
- Next
- PageOnExitAnimationFinished()
- End Sub,, True))
+ AniList.Add(AaCode(
+ Sub()
+ For Each Element In RealElements
+ Element.Visibility = Visibility.Collapsed
+ Next
+ PageOnExitAnimationFinished()
+ End Sub,, True))
AniStart(AniList, "PageRight PageChange " & PageUuid)
End Sub
- '遍历获取所有需要生成动画的控件
- Friend Function GetAllAnimControls(Element As FrameworkElement, Optional IgnoreInvisibility As Boolean = False) As List(Of FrameworkElement)
+ '''
+ ''' 禁用页面切换动画的控件列表。
+ '''
+ Public DisabledPageAnimControls As New List(Of FrameworkElement)
+ '''
+ ''' 遍历获取所有需要生成动画的控件。
+ '''
+ Friend Function GetAllAnimControls(Element As FrameworkElement, Optional IgnoreInvisibility As Boolean = False) As IEnumerable(Of FrameworkElement)
Dim AllControls As New List(Of FrameworkElement)
- GetAllAnimControls(Element, AllControls, IgnoreInvisibility)
- Return AllControls
+ _GetAllAnimControls(Element, AllControls, IgnoreInvisibility)
+ Return AllControls.Except(DisabledPageAnimControls)
End Function
- Private Sub GetAllAnimControls(Element As FrameworkElement, ByRef AllControls As List(Of FrameworkElement), IgnoreInvisibility As Boolean)
+ 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 TypeOf Element Is MyCard OrElse TypeOf Element Is MyHint OrElse TypeOf Element Is TextBlock OrElse TypeOf Element Is MyTextButton Then
+ 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
Dim Content = CType(Element, ContentControl).Content
- If Content IsNot Nothing AndAlso TypeOf Content Is FrameworkElement Then GetAllAnimControls(Content, AllControls, IgnoreInvisibility)
+ If Content IsNot Nothing AndAlso TypeOf Content Is FrameworkElement Then _GetAllAnimControls(Content, AllControls, IgnoreInvisibility)
ElseIf TypeOf Element Is Panel Then
For Each Element2 In CType(Element, Panel).Children
- If TypeOf Element2 Is FrameworkElement Then GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
+ If TypeOf Element2 Is FrameworkElement Then _GetAllAnimControls(Element2, AllControls, IgnoreInvisibility)
Next
End If
End Sub
diff --git a/Plain Craft Launcher 2/Controls/MyRadioBox.xaml b/Plain Craft Launcher 2/Controls/MyRadioBox.xaml
index d461839..2a8849e 100644
--- a/Plain Craft Launcher 2/Controls/MyRadioBox.xaml
+++ b/Plain Craft Launcher 2/Controls/MyRadioBox.xaml
@@ -3,7 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FocusVisualStyle="{x:Null}"
MinWidth="20" x:Name="PanBack" UseLayoutRounding="False" SnapsToDevicePixels="False" MinHeight="20" Background="{StaticResource ColorBrushSemiTransparent}" Focusable="True">
-
+
diff --git a/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb b/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb
index 3cb814c..816d7f9 100644
--- a/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyRadioBox.xaml.vb
@@ -1,4 +1,7 @@
-Public Class MyRadioBox
+Imports System.Windows.Markup
+
+
+Public Class MyRadioBox
Implements IMyRadio
'基础
@@ -10,27 +13,29 @@
Public Event Changed(sender As Object, e As RouteEventArgs) Implements IMyRadio.Changed
'自定义属性
-
- Private _Checked As Boolean = False '是否选中
Public Property Checked As Boolean
Get
- Return _Checked
+ Return GetValue(CheckedProperty)
End Get
Set(value As Boolean)
- SetChecked(value, False, True)
+ SetChecked(value, False)
End Set
End Property
+ Public Shared ReadOnly CheckedProperty As DependencyProperty =
+ DependencyProperty.Register("Checked", GetType(Boolean), GetType(MyRadioBox), New PropertyMetadata(False,
+ Sub(d As MyRadioBox, e As DependencyPropertyChangedEventArgs)
+ '在使用 XAML 设置 Checked 属性时,不会触发 Checked_Set 方法,所以需要在这里手动触发 UI 改变
+ If Not d.IsLoaded Then d.SyncUI()
+ End Sub))
+
'''
''' 手动设置 Checked 属性。
'''
''' 新的 Checked 属性。
''' 是否由用户引发。
- ''' 是否执行动画。
- Public Sub SetChecked(value As Boolean, user As Boolean, anime As Boolean)
+ Public Sub SetChecked(value As Boolean, user As Boolean)
Try
-
'Preview 事件
-
If value AndAlso user Then
Dim e = New RouteEventArgs(user)
RaiseEvent PreviewCheck(Me, e)
@@ -41,28 +46,24 @@
End If
'自定义属性基础
-
Dim IsChanged As Boolean = False
- If IsLoaded AndAlso Not value = _Checked Then RaiseEvent PreviewChange(Me, New RouteEventArgs(user))
- If Not value = _Checked Then
- _Checked = value
+ If IsLoaded AndAlso Not value = Checked Then RaiseEvent PreviewChange(Me, New RouteEventArgs(user))
+ If Not value = Checked Then
+ SetValue(CheckedProperty, value)
IsChanged = True
End If
'保证只有一个单选框选中
-
If Parent Is Nothing Then Exit Sub
Dim RadioboxList As New List(Of MyRadioBox)
Dim CheckedCount As Integer = 0
- '收集控件列表与选中个数
- For Each Control In CType(Parent, Object).Children
+ For Each Control In CType(Parent, Object).Children '收集控件列表与选中个数
If TypeOf Control Is MyRadioBox Then
RadioboxList.Add(Control)
If Control.Checked Then CheckedCount += 1
End If
Next
- '判断选中情况
- Select Case CheckedCount
+ Select Case CheckedCount '判断选中情况
Case 0
'没有任何单选框被选中,选择第一个
RadioboxList(0).Checked = True
@@ -87,67 +88,74 @@
Next
End If
End Select
- 'End If
-
- '更改动画
-
- If Not IsChanged Then Exit Sub
- If IsLoaded AndAlso AniControlEnabled = 0 AndAlso anime Then '防止默认属性变更触发动画
- If Checked Then
- '由无变有
- If ShapeDot.Opacity < 0.01 Then ShapeDot.Opacity = 1
- AniStart({
- AaScale(ShapeBorder, 10 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent(AniEasePower.Weak), , True),
- AaScale(ShapeBorder, 8, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.6, New AniEaseOutBack, , True)
- }, "MyRadioBox Border " & Uuid)
- AniStart({
- AaScale(ShapeDot, 9 - ShapeDot.Width, AnimationTimeOfCheck * 2.6,, New AniEaseOutBack(AniEasePower.Weak), , True),
- AaOpacity(ShapeDot, 1 - ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.6)
- }, "MyRadioBox Dot " & Uuid)
- AniStart({
- AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush2", "ColorBrushGray4")), AnimationTimeOfCheck)
- }, "MyRadioBox BorderColor " & Uuid)
- Else
- '由有变无
- AniStart({
- AaScale(ShapeBorder, 18 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True)
- }, "MyRadioBox Border " & Uuid)
- AniStart({
- AaScale(ShapeDot, -ShapeDot.Width, AnimationTimeOfCheck, , New AniEaseInFluent, , True),
- AaOpacity(ShapeDot, -ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.2)
- }, "MyRadioBox Dot " & Uuid)
- AniStart({
- AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush1", "ColorBrushGray4")), AnimationTimeOfCheck)
- }, "MyRadioBox BorderColor " & Uuid)
- End If
- Else
- '不使用动画
- AniStop("MyRadioBox Border " & Uuid)
- AniStop("MyRadioBox Dot " & Uuid)
- AniStop("MyRadioBox BorderColor " & Uuid)
- If Checked Then
- ShapeDot.Width = 9
- ShapeDot.Height = 9
- ShapeDot.Opacity = 1
- ShapeDot.Margin = New Thickness(5.5, 0, 0, 0)
- ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray4"))
- Else
- ShapeDot.Width = 0
- ShapeDot.Height = 0
- ShapeDot.Opacity = 0
- ShapeDot.Margin = New Thickness(10, 0, 0, 0)
- ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
- End If
- End If
'触发事件
- If Checked Then RaiseEvent Check(Me, New RouteEventArgs(user))
- RaiseEvent Changed(Me, New RouteEventArgs(user))
+ If IsChanged Then
+ If Checked Then RaiseEvent Check(Me, New RouteEventArgs(user))
+ RaiseEvent Changed(Me, New RouteEventArgs(user))
+ End If
+ '更改动画
+ SyncUI()
Catch ex As Exception
Log(ex, "单选框勾选改变错误", LogLevel.Hint)
End Try
End Sub
+ Private Sub SyncUI()
+ If AniControlEnabled = 0 AndAlso IsLoaded Then '防止默认属性变更触发动画
+ If Checked Then
+ '由无变有
+ If ShapeDot.Opacity < 0.01 Then ShapeDot.Opacity = 1
+ AniStart({
+ AaScale(ShapeBorder, 10 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent(AniEasePower.Weak), , True),
+ AaScale(ShapeBorder, 8, AnimationTimeOfCheck * 2, AnimationTimeOfCheck * 0.6, New AniEaseOutBack, , True)
+ }, "MyRadioBox Border " & Uuid)
+ AniStart({
+ AaScale(ShapeDot, 9 - ShapeDot.Width, AnimationTimeOfCheck * 2.6,, New AniEaseOutBack(AniEasePower.Weak), , True),
+ AaOpacity(ShapeDot, 1 - ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.6)
+ }, "MyRadioBox Dot " & Uuid)
+ AniStart({
+ AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush2", "ColorBrushGray4")), AnimationTimeOfCheck)
+ }, "MyRadioBox BorderColor " & Uuid)
+ Else
+ '由有变无
+ AniStart({
+ AaScale(ShapeBorder, 18 - ShapeBorder.Width, AnimationTimeOfCheck, , New AniEaseOutFluent, , True)
+ }, "MyRadioBox Border " & Uuid)
+ AniStart({
+ AaScale(ShapeDot, -ShapeDot.Width, AnimationTimeOfCheck, , New AniEaseInFluent, , True),
+ AaOpacity(ShapeDot, -ShapeDot.Opacity, AnimationTimeOfCheck * 0.5, AnimationTimeOfCheck * 0.2)
+ }, "MyRadioBox Dot " & Uuid)
+ AniStart({
+ AaColor(ShapeBorder, Ellipse.StrokeProperty, If(IsMouseOver, "ColorBrush3", If(IsEnabled, "ColorBrush1", "ColorBrushGray4")), AnimationTimeOfCheck)
+ }, "MyRadioBox BorderColor " & Uuid)
+ End If
+ Else
+ '不使用动画
+ AniStop("MyRadioBox Border " & Uuid)
+ AniStop("MyRadioBox Dot " & Uuid)
+ AniStop("MyRadioBox BorderColor " & Uuid)
+ If Checked Then
+ ShapeDot.Width = 9
+ ShapeDot.Height = 9
+ ShapeDot.Opacity = 1
+ ShapeDot.Margin = New Thickness(5.5, 0, 0, 0)
+ ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush2", "ColorBrushGray4"))
+ Else
+ ShapeDot.Width = 0
+ ShapeDot.Height = 0
+ ShapeDot.Opacity = 0
+ ShapeDot.Margin = New Thickness(10, 0, 0, 0)
+ ShapeBorder.SetResourceReference(Ellipse.StrokeProperty, If(IsEnabled, "ColorBrush1", "ColorBrushGray4"))
+ End If
+ End If
+ End Sub
+
+ Public ReadOnly Property Inlines As InlineCollection
+ Get
+ Return LabText.Inlines
+ End Get
+ End Property
Public Property Text As String
Get
Return GetValue(TextProperty)
@@ -157,9 +165,7 @@
End Set
End Property '内容
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyRadioBox), New PropertyMetadata(New PropertyChangedCallback(
- Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
- If Not IsNothing(sender) Then CType(sender, MyRadioBox).LabText.Text = e.NewValue
- End Sub)))
+ Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyRadioBox).LabText.Text = e.NewValue)))
'点击事件
@@ -167,9 +173,9 @@
Private AllowMouseDown As Boolean = True
Private Sub Radiobox_MouseUp() Handles Me.MouseLeftButtonUp
If Not MouseDowned Then Exit Sub
- MouseDowned = False
Log("[Control] 按下单选框:" & Text)
- SetChecked(True, True, True)
+ SetChecked(True, True)
+ MouseDowned = False
AniStart(AaColor(ShapeBorder, Ellipse.FillProperty, "ColorBrushHalfWhite", 100), "MyRadioBox Background " & Uuid)
End Sub
Private Sub Radiobox_MouseDown() Handles Me.MouseLeftButtonDown
diff --git a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml
index e5753e0..aa42f53 100644
--- a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml
+++ b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml
@@ -4,6 +4,6 @@
HorizontalAlignment="Center" VerticalAlignment="Center" Background="{StaticResource ColorBrushSemiTransparent}" CornerRadius="13.5" MinHeight="27" MaxHeight="27">
-
+
diff --git a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb
index c147a2f..6478a78 100644
--- a/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb
+++ b/Plain Craft Launcher 2/Controls/MyRadioButton.xaml.vb
@@ -1,4 +1,7 @@
-Public Class MyRadioButton
+Imports System.Windows.Markup
+
+
+Public Class MyRadioButton
'基础
@@ -108,6 +111,12 @@
Log(ex, "单选按钮勾选改变错误", LogLevel.Hint)
End Try
End Sub
+
+ Public ReadOnly Property Inlines As InlineCollection
+ Get
+ Return LabText.Inlines
+ End Get
+ End Property
Public Property Text As String
Get
Return GetValue(TextProperty)
@@ -117,9 +126,7 @@
End Set
End Property '内容
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(MyRadioButton), New PropertyMetadata(New PropertyChangedCallback(
- Sub(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
- If Not IsNothing(sender) Then CType(sender, MyRadioButton).LabText.Text = e.NewValue
- End Sub)))
+ Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyRadioButton).LabText.Text = e.NewValue)))
Public Enum ColorState
White
Highlight
diff --git a/Plain Craft Launcher 2/Controls/MyResizer.vb b/Plain Craft Launcher 2/Controls/MyResizer.vb
index e0c21ed..1200a4b 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
@@ -312,4 +312,5 @@ Public Class MyResizer
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 beaeb1c..42da890 100644
--- a/Plain Craft Launcher 2/Controls/MyScrollViewer.vb
+++ b/Plain Craft Launcher 2/Controls/MyScrollViewer.vb
@@ -1,7 +1,7 @@
Public Class MyScrollViewer
Inherits ScrollViewer
- Public Property DeltaMuity As Double = 1
+ Public Property DeltaMult As Double = 1
Private RealOffset As Double
@@ -25,10 +25,12 @@
Next
End Sub
Public Sub PerformVerticalOffsetDelta(Delta As Double)
- AniStart(AaDouble(Sub(AnimDelta As Double)
- RealOffset = MathClamp(RealOffset + AnimDelta, 0, ExtentHeight - ActualHeight)
- ScrollToVerticalOffset(RealOffset)
- End Sub, Delta * DeltaMuity, 300,, New AniEaseOutFluent(6)))
+ AniStart(
+ AaDouble(
+ Sub(AnimDelta As Double)
+ RealOffset = MathClamp(RealOffset + AnimDelta, 0, ExtentHeight - ActualHeight)
+ ScrollToVerticalOffset(RealOffset)
+ End Sub, Delta * DeltaMult, 300,, New AniEaseOutFluent(6)))
End Sub
Private Sub MyScrollViewer_ScrollChanged(sender As Object, e As ScrollChangedEventArgs) Handles Me.ScrollChanged
RealOffset = VerticalOffset
@@ -43,4 +45,7 @@
ScrollBar = GetTemplateChild("PART_VerticalScrollBar")
End Sub
+ Private Sub MyScrollViewer_PreviewGotKeyboardFocus(sender As Object, e As KeyboardFocusChangedEventArgs) Handles Me.PreviewGotKeyboardFocus
+ If e.NewFocus IsNot Nothing AndAlso TypeOf e.NewFocus Is MySlider Then e.Handled = True '#3854,阻止获得焦点时自动滚动
+ End Sub
End Class
diff --git a/Plain Craft Launcher 2/Controls/MySearchBox.xaml b/Plain Craft Launcher 2/Controls/MySearchBox.xaml
index 63b0e67..efdf8dd 100644
--- a/Plain Craft Launcher 2/Controls/MySearchBox.xaml
+++ b/Plain Craft Launcher 2/Controls/MySearchBox.xaml
@@ -8,7 +8,7 @@
d:DesignHeight="450" d:DesignWidth="800"
Height="40">
-
+
diff --git a/Plain Craft Launcher 2/Controls/MyTextBox.vb b/Plain Craft Launcher 2/Controls/MyTextBox.vb
index 4f78ee4..ae3f0ae 100644
--- a/Plain Craft Launcher 2/Controls/MyTextBox.vb
+++ b/Plain Craft Launcher 2/Controls/MyTextBox.vb
@@ -3,8 +3,18 @@
'自定义属性
- Public Property HasAnimation As Boolean = True
+ Public Property HasBackground As Boolean = True
Public Property ShowValidateResult As Boolean = True
+ Public Property CornerRadius As CornerRadius
+ Get
+ Return GetValue(CornerRadiusProperty)
+ End Get
+ Set(value As CornerRadius)
+ SetValue(CornerRadiusProperty, value)
+ End Set
+ End Property
+ Public Shared ReadOnly CornerRadiusProperty As DependencyProperty =
+ DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(MyTextBox), New PropertyMetadata(New CornerRadius(3)))
'额外控件初始化
@@ -62,7 +72,24 @@
SetValue(ValidateResultProperty, value)
End Set
End Property
- Public Shared ReadOnly ValidateResultProperty As DependencyProperty = DependencyProperty.Register("ValidateResult", GetType(String), GetType(MyTextBox), New PropertyMetadata(""))
+ Public Shared ReadOnly ValidateResultProperty As DependencyProperty =
+ DependencyProperty.Register("ValidateResult", GetType(String), GetType(MyTextBox), New PropertyMetadata("",
+ Sub(d As MyTextBox, e As DependencyPropertyChangedEventArgs)
+ d.SetValue(IsValidatedPropertyKey, String.IsNullOrEmpty(e.NewValue))
+ End Sub))
+
+ '''
+ ''' 是否通过了输入验证。
+ '''
+ Public ReadOnly Property IsValidated As Boolean
+ Get
+ Return GetValue(IsValidatedProperty)
+ End Get
+ End Property
+ Private Shared ReadOnly IsValidatedPropertyKey As DependencyPropertyKey =
+ DependencyProperty.RegisterReadOnly("IsValidated", GetType(Boolean), GetType(MyTextBox), New PropertyMetadata(True))
+ Public Shared ReadOnly IsValidatedProperty As DependencyProperty = IsValidatedPropertyKey.DependencyProperty
+
'''
''' 输入验证的规则。
'''
@@ -81,50 +108,49 @@
'''
Public Sub Validate() Handles Me.Loaded
'执行输入验证
- ValidateResult = ""
- For Each ValidateRule As Validate In ValidateRules
- ValidateResult = ValidateRule.Validate(Text)
- If IsNothing(ValidateResult) OrElse Not ValidateResult = "" Then Exit For
- Next
- Dim IsSuccessful As Boolean = ValidateResult = ""
+ ValidateResult = ModValidate.Validate(Text, ValidateRules)
'根据结果改变样式
- If ShownValidateResult <> If(IsSuccessful, ValidateState.Success, ValidateState.FailedAndShowDetail) Then
+ If ShownValidateResult <> If(IsValidated, ValidateState.Success, ValidateState.FailedAndShowDetail) Then
If IsLoaded AndAlso labWrong IsNot Nothing Then
- ChangeValidateResult(IsSuccessful, True)
+ ChangeValidateResult(IsValidated, True)
Else
- RunInNewThread(Sub()
- Thread.Sleep(30)
- RunInUi(Sub() ChangeValidateResult(IsSuccessful, False))
- End Sub, "DelayedValidate Change")
+ RunInNewThread(
+ Sub()
+ Thread.Sleep(30)
+ RunInUi(Sub() ChangeValidateResult(IsValidated, False))
+ End Sub, "DelayedValidate Change")
End If
End If
'更新错误信息
- If ShowValidateResult AndAlso Not ValidateResult = "" Then
+ If ShowValidateResult AndAlso Not IsValidated Then
If IsLoaded AndAlso labWrong IsNot Nothing Then
labWrong.Text = ValidateResult
Else
- RunInNewThread(Sub()
- Dim IsFinished As Boolean = False
- Do Until IsFinished
- Thread.Sleep(20)
- RunInUiWait(Sub()
- If labWrong IsNot Nothing Then
- labWrong.Text = ValidateResult
- IsFinished = True
- End If
- If Not IsLoaded Then IsFinished = True
- End Sub)
- Loop
- End Sub, "DelayedValidate Text")
+ RunInNewThread(
+ Sub()
+ Dim IsFinished As Boolean = False
+ Do Until IsFinished
+ Thread.Sleep(20)
+ RunInUiWait(
+ Sub()
+ If labWrong IsNot Nothing Then
+ labWrong.Text = ValidateResult
+ IsFinished = True
+ End If
+ If Not IsLoaded Then IsFinished = True
+ End Sub)
+ Loop
+ End Sub, "DelayedValidate Text")
End If
End If
End Sub
+
'''
''' 强制显示结果为正常,类似尚未输入过文本的状态。不影响实际的检查结果。
'''
Public Sub ForceShowAsSuccess()
IsTextChanged = False
- ChangeValidateResult(ValidateResult = "", True)
+ ChangeValidateResult(IsValidated, True)
End Sub
Private ShownValidateResult As ValidateState = ValidateState.NotInited
Private Sub ChangeValidateResult(IsSuccessful As Boolean, IsLoaded As Boolean)
@@ -133,18 +159,18 @@
'变为正确
ShownValidateResult = If(IsSuccessful, ValidateState.Success, ValidateState.FailedButTextNotChanged)
AniStart({
- AaOpacity(labWrong, -labWrong.Opacity, 150),
- AaHeight(labWrong, -labWrong.Height, 150,, New AniEaseOutFluent),
- AaCode(Sub() labWrong.Visibility = Visibility.Collapsed,, True)
- }, "MyTextBox Validate " & Uuid)
+ AaOpacity(labWrong, -labWrong.Opacity, 150),
+ AaHeight(labWrong, -labWrong.Height, 150,, New AniEaseOutFluent),
+ AaCode(Sub() labWrong.Visibility = Visibility.Collapsed,, True)
+ }, "MyTextBox Validate " & Uuid)
ElseIf ShowValidateResult Then
'变为错误
ShownValidateResult = ValidateState.FailedAndShowDetail
labWrong.Visibility = Visibility.Visible
AniStart({
- AaOpacity(labWrong, 1 - labWrong.Opacity, 170),
- AaHeight(labWrong, 21 - labWrong.Height, 300,, New AniEaseOutBack(AniEasePower.Weak))
- }, "MyTextBox Validate " & Uuid)
+ AaOpacity(labWrong, 1 - labWrong.Opacity, 150),
+ AaHeight(labWrong, 21 - labWrong.Height, 150,, New AniEaseOutFluent)
+ }, "MyTextBox Validate " & Uuid)
Else
'变为错误,但不显示文本
ShownValidateResult = ValidateState.FailedAndHideDetail
@@ -170,16 +196,19 @@
''' 是否已经由用户输入过文本,若尚未输入过,则不显示输入检查的失败。
'''
Private IsTextChanged As Boolean = False
- Private _HintText As String = ""
Public Property HintText As String
Get
- Return _HintText
+ Return GetValue(HintTextProperty)
End Get
Set(value As String)
- _HintText = value
- If labHint IsNot Nothing Then labHint.Text = If(Text = "", HintText, "")
+ SetValue(HintTextProperty, value)
End Set
End Property
+ Public Shared ReadOnly HintTextProperty As DependencyProperty =
+ DependencyProperty.Register("HintText", GetType(String), GetType(MyTextBox), New PropertyMetadata("",
+ Sub(t As MyTextBox, e As DependencyPropertyChangedEventArgs)
+ If t.labHint IsNot Nothing Then t.labHint.Text = If(t.Text = "", t.HintText, "")
+ End Sub))
Private Sub MyTextBox_TextChanged(sender As MyTextBox, e As TextChangedEventArgs) Handles Me.TextChanged
Try
'改变提示文本
@@ -188,7 +217,7 @@
IsTextChanged = IsLoaded
'进行输入验证
Validate()
- If Not ValidateResult = "" Then Exit Sub
+ If Not IsValidated Then Exit Sub
'改变文本
RaiseEvent ValidatedTextChanged(sender, e)
Catch ex As Exception
@@ -200,15 +229,13 @@
Private Sub RefreshColor() Handles Me.IsEnabledChanged, Me.MouseEnter, Me.MouseLeave, Me.GotFocus, Me.LostFocus
Try
- '手动关闭动画
- If Not HasAnimation Then Exit Sub
'不对 ComboBox 从属进行动画
If TemplatedParent IsNot Nothing AndAlso TypeOf TemplatedParent Is MyComboBox Then Exit Sub
'判断当前颜色
Dim ForeColorName As String, BackColorName As String
Dim AnimationTime As Integer
If IsEnabled Then
- If ValidateResult = "" OrElse Not IsTextChanged Then
+ If IsValidated OrElse Not IsTextChanged Then
If IsFocused Then
ForeColorName = "ColorBrush3"
BackColorName = "ColorBrush7"
@@ -232,6 +259,7 @@
BackColorName = "ColorBrushGray6"
AnimationTime = 200
End If
+ If Not HasBackground Then BackColorName = "ColorBrushTransparent"
'触发颜色动画
If IsLoaded AndAlso AniControlEnabled = 0 Then '防止默认属性变更触发动画
'有动画
diff --git a/Plain Craft Launcher 2/Controls/MyTextButton.vb b/Plain Craft Launcher 2/Controls/MyTextButton.vb
index 3d8245f..58fcd6e 100644
--- a/Plain Craft Launcher 2/Controls/MyTextButton.vb
+++ b/Plain Craft Launcher 2/Controls/MyTextButton.vb
@@ -90,21 +90,21 @@
Get
Return GetValue(EventTypeProperty)
End Get
-
Set(value As String)
SetValue(EventTypeProperty, value)
End Set
End Property
- Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register("EventType", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
+ Public Shared ReadOnly EventTypeProperty As DependencyProperty = DependencyProperty.Register(
+ "EventType", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
Public Property EventData As String
Get
Return GetValue(EventDataProperty)
End Get
-
Set(value As String)
SetValue(EventDataProperty, value)
End Set
End Property
- Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register("EventData", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
+ Public Shared ReadOnly EventDataProperty As DependencyProperty = DependencyProperty.Register(
+ "EventData", GetType(String), GetType(MyTextButton), New PropertyMetadata(Nothing))
End Class
diff --git a/Plain Craft Launcher 2/FormMain.xaml b/Plain Craft Launcher 2/FormMain.xaml
index 2dd9671..89def5a 100644
--- a/Plain Craft Launcher 2/FormMain.xaml
+++ b/Plain Craft Launcher 2/FormMain.xaml
@@ -104,7 +104,7 @@
-
+
-
+
@@ -133,7 +133,7 @@
+ LogoScale="0.87" Logo="M1097 584 250 584 562 896C591 925 591 972 562 1001 533 1030 487 1030 458 1001L21 565C6 550-0 531 0 511L0 511 0 511C-0 492 6 472 21 457L458 21C487-7 533-7 562 21 591 50 591 97 562 126L250 438 1097 438C1137 438 1170 471 1170 511 1170 551 1137 584 1097 584L1097 584Z" />
@@ -166,7 +166,7 @@
-
+
diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb
index 88ddb85..175685e 100644
--- a/Plain Craft Launcher 2/FormMain.xaml.vb
+++ b/Plain Craft Launcher 2/FormMain.xaml.vb
@@ -10,123 +10,55 @@ Public Class FormMain
Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
'统计更新日志条目
#If BETA Then
- If LastVersion < 336 Then 'Release 2.8.6
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "下载 Mod 时会使用 MCIM 国内镜像源"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Mod 管理页面允许筛选可更新/启用/禁用的 Mod"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "打开 PCL 时会自动安装同目录下的 modpack.zip"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "爱发电域名迁移至 afdian.com"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复 1.20.1+ 离线登录使用正版皮肤时无法保存游戏的 Bug"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复安装的 1.14~1.15 Forge+OptiFine 无法进入世界的 Bug"))
- FeatureCount += 19
- BugCount += 24
+ 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, "优化:使用新的版本隔离策略"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:添加了在全局启动设置与版本独立设置之间互相跳转的按钮"))
+ FeatureCount += 20
+ BugCount += 28
End If
- If LastVersion < 332 Then 'Release 2.8.3
- If LastVersion = 330 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复部分玩家无法启动 MC 的 Bug"))
- End If
- If LastVersion < 330 Then 'Release 2.8.2
- FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "NeoForge 兼容与自动安装"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持编译、运行 PCL 开源代码"))
- FeatureCount += 15
- BugCount += 22
- End If
- If LastVersion < 326 Then 'Release 2.7.4
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "会自动隐藏明显不可用的自动安装选项"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化正版登录流程和 MC 性能"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复正版登录时弹出脚本错误提示的 Bug"))
- FeatureCount += 17
- BugCount += 19
- End If
- If LastVersion < 323 Then 'Release 2.7.3
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "添加 启动游戏前进行内存优化 设置"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化 MC 性能"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复安装 OptiFine 有概率失败的 Bug"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复启动 Fabric 1.20.5+ 时无法正确选择 Java 的 Bug"))
- FeatureCount += 22
- BugCount += 21
- End If
- If LastVersion < 321 Then 'Release 2.7.1
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复启动部分整合包导致设置丢失的 Bug"))
- BugCount += 1
- End If
- If LastVersion < 319 Then 'Release 2.7.0
- FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持更新 Mod"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持查看可更新的 Mod 的更新日志"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持滑动鼠标快速选中、取消选中多个 Mod"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法启动 MC 24w14a+ 的 Bug"))
- FeatureCount += 10
+ If LastVersion < 352 Then 'Release 2.9.1
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:导出整合包功能"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:支持在超长路径下安装、启动游戏"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法安装 1.21 OptiFine"))
+ FeatureCount += 23
BugCount += 10
End If
- If LastVersion < 317 Then 'Release 2.6.15
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法安装 Forge 的 Bug"))
- FeatureCount += 2
- BugCount += 2
- End If
- If LastVersion < 315 Then 'Release 2.6.14
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化 MC 下载,尝试解决各种导致 MC 下载失败的问题"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "由于 MCBBS 关站,移除其相关内容"))
- FeatureCount += 17
- BugCount += 16
- End If
- If LastVersion < 313 Then 'Release 2.6.13
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法启动 Forge 1.18.3+ 的 Bug"))
+ If LastVersion < 349 Then 'Release 2.8.13
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:网络环境不佳时,Mod、整合包页面的加载速度"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:部分电脑无法打开程序"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:可能无法识别、更新 Mod"))
FeatureCount += 6
- BugCount += 10
+ BugCount += 14
End If
- If LastVersion < 311 Then 'Release 2.6.12
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Mod 管理页面将显示 Mod 的中文名、图标、标签等信息"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "从 Mod 管理页面查看 Mod 信息时会跳转到其下载详情页面"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重新设计 Mod 管理页面的交互与样式"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法安装 Forge 1.18.3+ 的 Bug"))
- FeatureCount += 27
- BugCount += 25
- End If
- If LastVersion < 308 Then 'Release 2.6.10
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "为版本独立设置添加忽略 Java 兼容性警告选项"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复开始或结束游戏时可能报错的 Bug"))
- FeatureCount += 31
+ If LastVersion < 347 Then 'Release 2.8.12
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:Mod 管理页面添加下载 Mod、安装 Mod 选项"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:Mod 详情页面支持按加载器、游戏版本进行分类和筛选"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:支持安装同时包含 modpack 文件和启动器的懒人包"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:整合包导入流程"))
+ FeatureCount += 43
BugCount += 37
End If
- If LastVersion < 302 Then 'Release 2.6.7
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "再次修复网络条件差时可能无法下载 MC 的 Bug"))
- BugCount += 1
+ If LastVersion < 342 Then 'Release 2.8.9
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:支持下载原版服务端"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:本地 Mod 的标题支持选择显示 Mod 原始文件名"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:搜索后启用/禁用 Mod 时出错"))
+ FeatureCount += 17
+ BugCount += 13
End If
- If LastVersion < 300 Then 'Release 2.6.6
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持选择多种预设的主页"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod 管理中允许多选 Mod 进行批量操作"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化崩溃分析,添加多种崩溃情况的判断"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化联网获取的主页的加载与缓存"))
- FeatureCount += 40
- BugCount += 34
+ If LastVersion < 340 Then 'Release 2.8.8
+ If LastVersion = 338 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:数个与新正版登录相关的严重 Bug"))
+ FeatureCount += 3
+ BugCount += 7
End If
- If LastVersion < 296 Then 'Release 2.6.3
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增内存优化功能,可以将所有程序的物理内存占用降低约 1/3"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "在选择 OptiFine 与 Fabric 后会自动选择 OptiFabric"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化崩溃分析,添加多种崩溃情况的判断"))
- FeatureCount += 21
- BugCount += 30
- End If
- If LastVersion < 293 Then 'Release 2.6.1
- BugCount += 1
- End If
- If LastVersion < 292 Then 'Release 2.6.0
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持在多个正版账号间切换"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "在缺少 Java 时会自动下载所需的 Java"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重新制作正版登录页面"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "彻底移除 Mojang 登录"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "添加 CurseForge / Modrinth 来源筛选"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "Mod / 整合包下载会单独列出筛选的版本"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "为下载管理和音乐播放按钮添加进度条"))
- FeatureCount += 24
- BugCount += 24
- End If
- If LastVersion < 286 Then 'Release 2.5.2
- FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持搜索、安装 Modrinth 的 Mod 与整合包"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "资源搜索页面支持翻页"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "在全局启动设置中添加了 启动前执行命令 选项"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "重做资源下载的资源项界面"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "在选择下载 Fabric 时会自动选择 Fabric API"))
- FeatureCount += 35
- BugCount += 36
+ If LastVersion < 338 Then 'Release 2.8.7
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "优化:使用新的正版登录方式,以提高安全性"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:安装整合包、检索 Mod 的稳定性"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法加载部分 Mod 的图标"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:在 Mod 管理页面删除 Mod 导致报错"))
+ FeatureCount += 11
+ BugCount += 21
End If
#Else
'5: FEAT+
@@ -134,194 +66,85 @@ Public Class FormMain
'3:BUG+ IMP* FEAT-
'2:BUG* IMP-
'1:BUG-
- If LastVersion < 335 Then 'Snapshot 2.8.6
- BugCount += 2
- End If
- If LastVersion < 334 Then 'Snapshot 2.8.5
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Mod 管理页面允许筛选可更新/启用/禁用的 Mod"))
- If LastVersion = 333 Then
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法安装愚人节和预发布版本的 Bug"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法导出错误报告的 Bug"))
+ 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+"))
End If
- FeatureCount += 6
- BugCount += 7
- End If
- If LastVersion < 333 Then 'Snapshot 2.8.4
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "下载 Mod 时会使用 MCIM 国内镜像源"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "打开 PCL 时会自动安装同目录下的 modpack.zip"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "爱发电域名迁移至 afdian.com"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复 1.20.1+ 离线登录使用正版皮肤时无法保存游戏的 Bug"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复安装的 1.14~1.15 Forge+OptiFine 无法进入世界的 Bug"))
- FeatureCount += 13
- BugCount += 17
- End If
- If LastVersion < 331 Then 'Snapshot 2.8.3
- If LastVersion = 329 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复部分玩家无法启动 MC 的 Bug"))
- End If
- If LastVersion < 329 Then 'Snapshot 2.8.2
- If LastVersion >= 327 Then
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法安装 Beta 版 NeoForge 的整合包的 Bug"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复自动安装无法选择部分 OptiFine 的 Bug"))
- End If
- FeatureCount += 4
+ FeatureCount += 8
BugCount += 8
End If
- If LastVersion < 328 Then 'Snapshot 2.8.1
- If LastVersion = 327 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法安装 Forge 1.12.2- 的 Bug"))
- If LastVersion = 327 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法输入解锁码的 Bug"))
- If LastVersion = 327 Then BugCount += 1
- End If
- If LastVersion < 327 Then 'Snapshot 2.8.0
- FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "NeoForge 兼容与自动安装"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持编译、运行 PCL 开源代码"))
- FeatureCount += 11
- BugCount += 14
- End If
- If LastVersion < 325 Then 'Snapshot 2.7.4
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "会自动隐藏明显不可用的自动安装选项"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化正版登录流程和 MC 性能"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复正版登录时弹出脚本错误提示的 Bug"))
- FeatureCount += 17
- BugCount += 19
- End If
- If LastVersion < 324 Then 'Snapshot 2.7.3
- FeatureCount += 4
- BugCount += 3
- End If
- If LastVersion < 322 Then 'Snapshot 2.7.2
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "添加 启动游戏前进行内存优化 设置"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复安装 OptiFine 有概率失败的 Bug"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复启动 Fabric 1.20.5+ 时无法正确选择 Java 的 Bug"))
- FeatureCount += 18
- BugCount += 18
- End If
- If LastVersion < 320 Then 'Snapshot 2.7.1
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复启动部分整合包导致设置丢失的 Bug"))
- BugCount += 1
- End If
- If LastVersion < 318 Then 'Snapshot 2.7.0
- FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持更新 Mod"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持查看可更新的 Mod 的更新日志"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持滑动鼠标快速选中、取消选中多个 Mod"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法启动 MC 24w14a+ 的 Bug"))
- FeatureCount += 10
- BugCount += 10
- End If
- If LastVersion < 316 Then 'Snapshot 2.6.15
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复无法安装 Forge 的 Bug"))
- FeatureCount += 2
- BugCount += 2
- End If
- If LastVersion < 314 Then 'Snapshot 2.6.14
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化 MC 下载,尝试解决各种导致 MC 下载失败的问题"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "由于 MCBBS 关站,移除其相关内容"))
- FeatureCount += 17
- BugCount += 16
- End If
- If LastVersion < 312 Then 'Snapshot 2.6.13
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法启动 Forge 1.18.3+ 的 Bug"))
- FeatureCount += 6
- BugCount += 10
- End If
- If LastVersion < 310 Then 'Snapshot 2.6.12
- If LastVersion = 309 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod 管理页面添加启用、禁用单个 Mod 的快捷按钮"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复无法安装 Forge 1.18.3+ 的 Bug"))
- FeatureCount += 13
- BugCount += 12
- End If
- If LastVersion < 309 Then 'Snapshot 2.6.11
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "Mod 管理页面将显示 Mod 的中文名、图标、标签等信息"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "从 Mod 管理页面查看 Mod 信息时会跳转到其下载详情页面"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重新设计 Mod 管理页面的交互与样式"))
- FeatureCount += 15
- BugCount += 13
- End If
- If LastVersion < 307 Then 'Snapshot 2.6.10
- FeatureCount += 2
- BugCount += 3
- End If
- If LastVersion < 306 Then 'Snapshot 2.6.9
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "为版本独立设置添加忽略 Java 兼容性警告选项"))
- FeatureCount += 9
- BugCount += 11
- End If
- If LastVersion < 305 Then 'Snapshot 2.6.8
- FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复开始或结束游戏时可能报错的 Bug"))
- FeatureCount += 20
- BugCount += 25
- End If
- If LastVersion < 303 Then 'Snapshot 2.6.7
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "再次修复网络条件差时可能无法下载 MC 的 Bug"))
- BugCount += 1
- End If
- If LastVersion < 301 Then 'Snapshot 2.6.6
- FeatureCount += 4
- BugCount += 2
- End If
- If LastVersion < 299 Then 'Snapshot 2.6.5
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持选择多种预设的主页"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化联网获取的主页的加载与缓存"))
- FeatureCount += 18
- BugCount += 13
- End If
- If LastVersion < 298 Then 'Snapshot 2.6.4
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "Mod 管理中允许多选 Mod 进行批量操作"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化崩溃分析,添加多种崩溃情况的判断"))
- FeatureCount += 18
- BugCount += 19
- End If
- If LastVersion < 297 Then 'Snapshot 2.6.3
+ If LastVersion < 353 Then 'Snapshot 2.9.2
+ 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, "优化:使用新的版本隔离策略"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:添加了在全局启动设置与版本独立设置之间互相跳转的按钮"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:使用快照版导出带 PCL 的整合包可能失败"))
FeatureCount += 12
- BugCount += 14
+ BugCount += 20
End If
- If LastVersion < 294 Then 'Snapshot 2.6.2
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增内存优化功能,可以将所有程序的物理内存占用降低约 1/3"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "在选择 OptiFine 与 Fabric 后会自动选择 OptiFabric"))
- FeatureCount += 9
- BugCount += 16
- End If
- If LastVersion < 291 Then 'Snapshot 2.6.0
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "支持在多个正版账号间切换"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "Mod / 整合包下载会单独列出筛选的版本"))
- FeatureCount += 10
+ If LastVersion < 351 Then 'Snapshot 2.9.1
+ If LastVersion = 350 Then
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "优化:对导出整合包功能进行了 7 项优化,详见完整更新日志"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法进行第三方登录"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:在部分版本下,打开导出页面会导致启动器崩溃"))
+ BugCount += 1
+ End If
BugCount += 2
End If
- If LastVersion < 289 Then 'Snapshot 2.5.4
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "重新制作正版登录页面"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "彻底移除 Mojang 登录"))
+ If LastVersion < 350 Then 'Snapshot 2.9.0
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:导出整合包功能"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:支持在超长路径下安装、启动游戏"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法安装 1.21 OptiFine"))
+ FeatureCount += 21
+ BugCount += 10
+ End If
+ If LastVersion < 348 Then 'Snapshot 2.8.13
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:网络环境不佳时,Mod、整合包页面的加载速度"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:部分电脑无法打开程序"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:可能无法识别、更新 Mod"))
FeatureCount += 6
BugCount += 14
End If
- If LastVersion < 288 Then 'Snapshot 2.5.3
- FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "在缺少 Java 时会自动下载所需的 Java"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "添加 CurseForge / Modrinth 来源筛选"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "为下载管理和音乐播放按钮添加进度条"))
- FeatureCount += 8
- BugCount += 8
+ If LastVersion < 346 Then 'Snapshot 2.8.12
+ If LastVersion = 345 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:帮助页面报错"))
End If
- If LastVersion < 285 Then 'Snapshot 2.5.2
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "重做资源下载的资源项界面"))
- If LastVersion = 284 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复打开部分帮助报错的 Bug"))
- FeatureCount += 8
- BugCount += 8
- End If
- If LastVersion < 284 Then 'Snapshot 2.5.1
- If LastVersion >= 275 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "在全局启动设置中添加了 启动前执行命令 选项"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "在选择下载 Fabric 时会自动选择 Fabric API"))
- FeatureCount += 22
+ If LastVersion < 345 Then 'Snapshot 2.8.11
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:Mod 管理页面添加下载 Mod、安装 Mod 选项"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:Mod 详情页面支持按加载器、游戏版本进行分类和筛选"))
+ FeatureCount += 23
BugCount += 21
End If
- If LastVersion < 283 Then 'Snapshot 2.5.0
- FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "支持搜索、下载 Modrinth 中的 Mod 与整合包"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "资源搜索页面支持翻页"))
- FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "支持安装 Modrinth 整合包"))
- FeatureCount += 5
- BugCount += 5
+ If LastVersion < 343 Then 'Snapshot 2.8.10
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:支持安装同时包含 modpack 文件和启动器的懒人包"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:整合包导入流程"))
+ FeatureCount += 20
+ BugCount += 16
+ End If
+ If LastVersion < 341 Then 'Snapshot 2.8.9
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "新增:支持下载原版服务端"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:本地 Mod 的标题支持选择显示 Mod 原始文件名"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:搜索后启用/禁用 Mod 时出错"))
+ FeatureCount += 17
+ BugCount += 13
+ End If
+ If LastVersion < 339 Then 'Snapshot 2.8.8
+ If LastVersion = 337 Then FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:数个与新正版登录相关的严重 Bug"))
+ FeatureCount += 3
+ BugCount += 7
+ End If
+ If LastVersion < 337 Then 'Snapshot 2.8.7
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(4, "优化:使用新的正版登录方式,以提高安全性"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:安装整合包、检索 Mod 的稳定性"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法加载部分 Mod 的图标"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:在 Mod 管理页面删除 Mod 导致报错"))
+ FeatureCount += 11
+ BugCount += 21
End If
#End If
'整理更新日志文本
Dim ContentList As New List(Of String)
- Dim SortedFeatures = Sort(FeatureList, Function(Left, Right) Left.Key > Right.Key)
+ Dim SortedFeatures = FeatureList.OrderByDescending(Function(f) f.Key).ToList
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)
@@ -360,9 +183,19 @@ Public Class FormMain
'触发降级
DowngradeSub(LastVersion)
End If
- ''刷新语言
- 'Lang = ReadReg("Lang", "zh_CN")
- 'Application.Current.Resources.MergedDictionaries(1) = New ResourceDictionary With {.Source = New Uri("Resources\Language\" & Lang & ".xaml", UriKind.Relative)}
+ '版本隔离设置迁移
+ If Setup.IsUnset("LaunchArgumentIndieV2") Then
+ If Not Setup.IsUnset("LaunchArgumentIndie") Then
+ Log("[Start] 从老 PCL 迁移版本隔离")
+ Setup.Set("LaunchArgumentIndieV2", Setup.Get("LaunchArgumentIndie"))
+ ElseIf Not Setup.IsUnset("WindowHeight") Then
+ Log("[Start] 从老 PCL 升级,但此前未调整版本隔离,使用老的版本隔离默认值")
+ Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndie"))
+ Else
+ Log("[Start] 全新的 PCL,使用新的版本隔离默认值")
+ Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndieV2"))
+ End If
+ End If
'刷新主题
ThemeCheckAll(False)
Setup.Load("UiLauncherTheme")
@@ -380,7 +213,9 @@ Public Class FormMain
If Not IsNothing(FrmLaunchLeft.Parent) Then FrmLaunchLeft.SetValue(ContentPresenter.ContentProperty, Nothing)
If Not IsNothing(FrmLaunchRight.Parent) Then FrmLaunchRight.SetValue(ContentPresenter.ContentProperty, Nothing)
PanMainLeft.Child = FrmLaunchLeft
+ PageLeft = FrmLaunchLeft
PanMainRight.Child = FrmLaunchRight
+ PageRight = FrmLaunchRight
FrmLaunchRight.PageState = MyPageRight.PageStates.ContentStay
'模式提醒
#If DEBUG Then
@@ -425,13 +260,17 @@ Public Class FormMain
'加载窗口
ThemeRefreshMain()
Try
- Height = ReadReg("WindowHeight", MinHeight + 50)
- Width = ReadReg("WindowWidth", MinWidth + 50)
+ Height = Setup.Get("WindowHeight")
+ Width = Setup.Get("WindowWidth")
Catch ex As Exception '修复 #2019
Log(ex, "读取窗口默认大小失败", LogLevel.Hint)
- Height = MinHeight + 50
- Width = MinWidth + 50
+ Height = MinHeight + 100
+ Width = MinWidth + 100
End Try
+ '#If DEBUG Then
+ ' MinHeight = 50
+ ' MinWidth = 50
+ '#End If
Topmost = False
If FrmStart IsNot Nothing Then FrmStart.Close(New TimeSpan(0, 0, 0, 0, 400 / AniSpeed))
'更改窗口
@@ -462,7 +301,7 @@ Public Class FormMain
'EULA 提示
If Not Setup.Get("SystemEula") Then
Select Case MyMsgBox("在使用 PCL 前,请同意 PCL 的用户协议与免责声明。", "协议授权", "同意", "拒绝", "查看用户协议与免责声明",
- Button3Action:=Sub() OpenWebsite("https://shimo.im/docs/rGrd8pY8xWkt6ryW"))
+ Button3Action:=Sub() OpenWebsite("https://shimo.im/docs/rGrd8pY8xWkt6ryW"))
Case 1
Setup.Set("SystemEula", True)
Case 2
@@ -476,6 +315,7 @@ Public Class FormMain
DlClientListMojangLoader.Start(1)
RunCountSub()
ServerLoader.Start(1)
+ RunInNewThread(AddressOf TryClearTaskTemp, "TryClearTaskTemp", ThreadPriority.BelowNormal)
Catch ex As Exception
Log(ex, "初始化加载池运行失败", LogLevel.Feedback)
End Try
@@ -565,8 +405,23 @@ Public Class FormMain
Setup.Set("UiHiddenOtherHelp", False)
Log("[Start] 已解除帮助页面的隐藏")
End If
+ '单向迁移微软登录结果(#4836)
+ If Not Setup.Get("CacheMsV2Migrated") Then
+ Setup.Set("CacheMsV2Migrated", True)
+ Setup.Set("CacheMsV2OAuthRefresh", Setup.Get("CacheMsOAuthRefresh"))
+ Setup.Set("CacheMsV2Access", Setup.Get("CacheMsAccess"))
+ Setup.Set("CacheMsV2ProfileJson", Setup.Get("CacheMsProfileJson"))
+ Setup.Set("CacheMsV2Uuid", Setup.Get("CacheMsUuid"))
+ Setup.Set("CacheMsV2Name", Setup.Get("CacheMsName"))
+ Log("[Start] 已从老版本迁移微软登录结果")
+ End If
+ 'Mod 命名设置迁移
+ If Not Setup.IsUnset("ToolDownloadTranslate") AndAlso Setup.IsUnset("ToolDownloadTranslateV2") Then
+ Setup.Set("ToolDownloadTranslateV2", Setup.Get("ToolDownloadTranslate") + 1)
+ Log("[Start] 已从老版本迁移 Mod 命名设置")
+ End If
'输出更新日志
- If LastVersionCode = 0 Then Exit Sub
+ If LastVersionCode <= 0 Then Exit Sub
If LowerVersionCode >= VersionCode Then Exit Sub
ShowUpdateLog(LowerVersionCode)
End Sub
@@ -606,45 +461,45 @@ Public Class FormMain
End If
'关闭
RunInUiWait(
- Sub()
- IsHitTestVisible = False
- If PanBack.RenderTransform Is Nothing Then
- Dim TransformPos As New TranslateTransform(0, 0)
- Dim TransformRotate As New RotateTransform(0)
- Dim TransformScale As New ScaleTransform(1, 1)
- PanBack.RenderTransform = New TransformGroup() With {.Children = New TransformCollection({TransformRotate, TransformPos, TransformScale})}
- AniStart({
- AaOpacity(Me, -Opacity, 140, 40, New AniEaseOutFluent(AniEasePower.Weak)),
- AaDouble(
- Sub(i)
- TransformScale.ScaleX += i
- TransformScale.ScaleY += i
- End Sub, 0.88 - TransformScale.ScaleX, 180),
- AaDouble(Sub(i) TransformPos.Y += i, 20 - TransformPos.Y, 180, 0, New AniEaseOutFluent(AniEasePower.Weak)),
- AaDouble(Sub(i) TransformRotate.Angle += i, 0.6 - TransformRotate.Angle, 180, 0, New AniEaseInoutFluent(AniEasePower.Weak)),
- AaCode(
- Sub()
- IsHitTestVisible = False
- Top = -10000
- ShowInTaskbar = False
- End Sub, 210),
- AaCode(AddressOf EndProgramForce, 230)
- }, "Form Close")
- Else
- EndProgramForce()
- End If
- Log("[System] 收到关闭指令")
- End Sub)
+ Sub()
+ IsHitTestVisible = False
+ If PanBack.RenderTransform Is Nothing Then
+ Dim TransformPos As New TranslateTransform(0, 0)
+ Dim TransformRotate As New RotateTransform(0)
+ Dim TransformScale As New ScaleTransform(1, 1)
+ PanBack.RenderTransform = New TransformGroup() With {.Children = New TransformCollection({TransformRotate, TransformPos, TransformScale})}
+ AniStart({
+ AaOpacity(Me, -Opacity, 140, 40, New AniEaseOutFluent(AniEasePower.Weak)),
+ AaDouble(
+ Sub(i)
+ TransformScale.ScaleX += i
+ TransformScale.ScaleY += i
+ End Sub, 0.88 - TransformScale.ScaleX, 180),
+ AaDouble(Sub(i) TransformPos.Y += i, 20 - TransformPos.Y, 180, 0, New AniEaseOutFluent(AniEasePower.Weak)),
+ AaDouble(Sub(i) TransformRotate.Angle += i, 0.6 - TransformRotate.Angle, 180, 0, New AniEaseInoutFluent(AniEasePower.Weak)),
+ AaCode(
+ Sub()
+ IsHitTestVisible = False
+ Top = -10000
+ ShowInTaskbar = False
+ End Sub, 210),
+ AaCode(AddressOf EndProgramForce, 230)
+ }, "Form Close")
+ Else
+ EndProgramForce()
+ End If
+ Log("[System] 收到关闭指令")
+ End Sub)
End Sub
Private Shared IsLogShown As Boolean = False
- Public Shared Sub EndProgramForce(Optional ReturnCode As Result = Result.Success)
+ Public Shared Sub EndProgramForce(Optional ReturnCode As ProcessReturnValues = ProcessReturnValues.Success)
On Error Resume Next
IsProgramEnded = True
AniControlEnabled += 1
PageLinkIoi.IoiStop(False)
PageLinkHiper.HiperStop(False)
If IsUpdateWaitingRestart Then UpdateRestart(False)
- If ReturnCode = Result.Exception Then
+ If ReturnCode = ProcessReturnValues.Exception Then
If Not IsLogShown Then
FeedbackInfo()
Log("请在 https://github.com/Hex-Dragon/PCL2/issues 提交错误报告,以便于作者解决此问题!")
@@ -655,7 +510,7 @@ Public Class FormMain
End If
Log("[System] 程序已退出,返回值:" & GetStringFromEnum(CType(ReturnCode, [Enum])))
LogFlush()
- If ReturnCode = Result.Success Then
+ If ReturnCode = ProcessReturnValues.Success Then
Process.GetCurrentProcess.Kill()
Else
Environment.Exit(ReturnCode)
@@ -679,8 +534,8 @@ Public Class FormMain
Public IsSizeSaveable As Boolean = False
Private Sub FormMain_SizeChanged() Handles Me.SizeChanged, Me.Loaded
If IsSizeSaveable Then
- WriteReg("WindowHeight", Height)
- WriteReg("WindowWidth", Width)
+ Setup.Set("WindowHeight", Height)
+ Setup.Set("WindowWidth", Width)
End If
RectForm.Rect = New Rect(0, 0, BorderForm.ActualWidth, BorderForm.ActualHeight)
PanForm.Width = BorderForm.ActualWidth + 0.001
@@ -709,7 +564,7 @@ Public Class FormMain
Exit Sub
ElseIf e.Key = Key.Escape Then
Dim Msg As Object = PanMsg.Children(0)
- If TypeOf Msg Is MyMsgText AndAlso Msg.Btn3.Visibility = Visibility.Visible Then
+ If TypeOf Msg IsNot MyMsgInput AndAlso TypeOf Msg IsNot MyMsgSelect AndAlso Msg.Btn3.Visibility = Visibility.Visible Then
Msg.Btn3_Click()
ElseIf Msg.Btn2.Visibility = Visibility.Visible Then
Msg.Btn2_Click()
@@ -738,6 +593,12 @@ Public Class FormMain
PageSetupUI.HiddenRefresh()
Exit Sub
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
+ End If
'调用启动游戏
If e.Key = Key.Enter AndAlso PageCurrent = FormMain.PageType.Launch Then
If IsAprilEnabled AndAlso Not IsAprilGiveup Then
@@ -751,6 +612,7 @@ Public Class FormMain
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 e.ChangedButton = MouseButton.XButton1 OrElse e.ChangedButton = MouseButton.XButton2 Then TriggerPageBack()
End Sub
Private Sub TriggerPageBack()
@@ -875,14 +737,14 @@ Public Class FormMain
If FilePathList.Count > 1 Then
'必须要求全部为 jar 文件
For Each File In FilePathList
- If Not {"jar", "litemod", "disabled", "old"}.Contains(File.After(".").ToLower) Then
+ If Not {"jar", "litemod", "disabled", "old"}.Contains(File.AfterLast(".").ToLower) Then
Hint("一次请只拖入一个文件!", HintType.Critical)
Exit Sub
End If
Next
End If
'自定义主页
- Dim Extension As String = FilePath.After(".").ToLower
+ Dim Extension As String = FilePath.AfterLast(".").ToLower
If Extension = "xaml" Then
Log("[System] 文件后缀为 XAML,作为自定义主页加载")
If File.Exists(Path & "PCL\Custom.xaml") Then
@@ -891,53 +753,27 @@ Public Class FormMain
End If
End If
CopyFile(FilePath, Path & "PCL\Custom.xaml")
- RunInUi(Sub()
- Setup.Set("UiCustomType", 1)
- FrmLaunchRight.ForceRefresh()
- Hint("已加载主页自定义文件!", HintType.Finish)
- End Sub)
- Exit Sub
- End If
- 'Mod 安装
- If {"jar", "litemod", "disabled", "old"}.Any(Function(t) t = Extension) Then
- Log("[System] 文件为 jar/litemod 格式,尝试作为 Mod 安装")
- '获取并检查目标版本
- Dim TargetVersion As McVersion = McVersionCurrent
- If PageCurrent = PageType.VersionSetup Then TargetVersion = PageVersionLeft.Version
- If PageCurrent = PageType.VersionSelect OrElse TargetVersion Is Nothing OrElse Not TargetVersion.Modable Then
- '正在选择版本,或当前版本不能安装 Mod
- Hint("若要安装 Mod,请先选择一个可以安装 Mod 的版本!")
- ElseIf Not (PageCurrent = PageType.VersionSetup AndAlso PageCurrentSub = PageSubType.VersionMod) Then
- '未处于 Mod 管理页面
- If MyMsgBox($"是否要将这{If(FilePathList.Count = 1, "个", "些")}文件作为 Mod 安装到 {TargetVersion.Name}?", "Mod 安装确认", "确定", "取消") = 1 Then GoTo Install
- Else
- '处于 Mod 管理页面
-Install:
- Try
- For Each ModFile In FilePathList
- Dim NewFileName = GetFileNameFromPath(ModFile).Replace(".disabled", "")
- If Not NewFileName.Contains(".") Then NewFileName += ".jar" '#4227
- CopyFile(ModFile, TargetVersion.PathIndie & "mods\" & NewFileName)
- Next
- If FilePathList.Count = 1 Then
- Hint($"已安装 {GetFileNameFromPath(FilePathList.First).Replace(".disabled", "")}!", HintType.Finish)
- Else
- Hint($"已安装 {FilePathList.Count} 个 Mod!", HintType.Finish)
- End If
- '刷新列表
- If PageCurrent = PageType.VersionSetup AndAlso PageCurrentSub = PageSubType.VersionMod Then
- LoaderFolderRun(McModLoader, TargetVersion.PathIndie & "mods\", LoaderFolderRunType.ForceRun)
- End If
- Catch ex As Exception
- Log(ex, "复制 Mod 文件失败", LogLevel.Msgbox)
- End Try
- End If
+ RunInUi(
+ Sub()
+ Setup.Set("UiCustomType", 1)
+ FrmLaunchRight.ForceRefresh()
+ Hint("已加载主页自定义文件!", HintType.Finish)
+ End Sub)
Exit Sub
End If
+ '安装 Mod
+ If PageVersionMod.InstallMods(FilePathList) Then Exit Sub
'安装整合包
If {"zip", "rar", "mrpack"}.Any(Function(t) t = Extension) Then '部分压缩包是 zip 格式但后缀为 rar,总之试一试
Log("[System] 文件为压缩包,尝试作为整合包安装")
- If ModpackInstall(FilePath, ShowHint:=False) IsNot Nothing Then Exit Sub
+ Try
+ ModpackInstall(FilePath)
+ Exit Sub
+ Catch ex As CancelledException
+ Exit Sub '用户主动取消
+ Catch ex As Exception
+ '安装失败,继续往后尝试
+ End Try
End If
'RAR 处理
If Extension = "rar" Then
@@ -1011,18 +847,19 @@ Install:
''' 把当前窗口拖到最前面。
'''
Public Sub ShowWindowToTop()
- RunInUi(Sub()
- '这一坨乱七八糟的,别改,改了指不定就炸了,自己电脑还复现不出来
- Visibility = Visibility.Visible
- ShowInTaskbar = True
- WindowState = WindowState.Normal
- Hidden = False
- Topmost = True '偶尔 SetForegroundWindow 失效
- Topmost = False
- SetForegroundWindow(Handle)
- Focus()
- Log("[System] 窗口已置顶,位置:(" & Left & ", " & Top & "), " & Width & " x " & Height)
- End Sub)
+ RunInUi(
+ Sub()
+ '这一坨乱七八糟的,别改,改了指不定就炸了,自己电脑还复现不出来
+ Visibility = Visibility.Visible
+ ShowInTaskbar = True
+ WindowState = WindowState.Normal
+ Hidden = False
+ Topmost = True '偶尔 SetForegroundWindow 失效
+ Topmost = False
+ SetForegroundWindow(Handle)
+ Focus()
+ Log($"[System] 窗口已置顶,位置:({Left}, {Top}), {Width} x {Height}")
+ End Sub)
End Sub
#End Region
@@ -1090,6 +927,7 @@ Install:
DownloadLiteLoader = 9
DownloadMod = 11
DownloadPack = 12
+ DownloadResourcePack = 13
DownloadShader = 14
SetupLaunch = 0
SetupUI = 1
@@ -1107,6 +945,7 @@ Install:
VersionSetup = 1
VersionMod = 2
VersionModDisabled = 3
+ VersionExport = 4
End Enum
'''
''' 获取次级页面的名称。若并非次级页面则返回空字符串,故可以以此判断是否为次级页面。
@@ -1169,9 +1008,6 @@ Install:
Case PageType.Download
If FrmDownloadLeft Is Nothing Then FrmDownloadLeft = New PageDownloadLeft
Return FrmDownloadLeft.PageID
- Case PageType.Link
- If FrmLinkLeft Is Nothing Then FrmLinkLeft = New PageLinkLeft
- Return FrmLinkLeft.PageID
Case PageType.Setup
If FrmSetupLeft Is Nothing Then FrmSetupLeft = New PageSetupLeft
Return FrmSetupLeft.PageID
@@ -1240,9 +1076,6 @@ Install:
CType(PanTitleSelect.Children(Stack), MyRadioButton).SetChecked(True, True, PageNameGet(PageCurrent) = "")
IsChangingPage = False
Select Case Stack.Page
- Case PageType.Link
- If FrmLinkLeft Is Nothing Then FrmLinkLeft = New PageLinkLeft
- CType(FrmLinkLeft.PanItem.Children(SubType), MyListItem).SetChecked(True, True, Stack = PageCurrent)
Case PageType.Download
If FrmDownloadLeft Is Nothing Then FrmDownloadLeft = New PageDownloadLeft
CType(FrmDownloadLeft.PanItem.Children(SubType), MyListItem).SetChecked(True, True, Stack = PageCurrent)
@@ -1271,14 +1104,6 @@ Install:
If IsChangingPage Then Exit Sub
PageChangeActual(Val(sender.Tag))
End Sub
- Private Sub CancelLink(sender As Object, e As RouteEventArgs) Handles BtnTitleSelect2.PreviewClick
- If MyMsgBox("由于联机提供商要求新联机强制付费,且高度商业化,PCL 将暂时关闭联机功能,不再使用该联机模块。" & vbCrLf &
- "PCL、HMCL、BakaXL 将合作开发新的跨启动器联机功能,在开发结束后将同步开放,请各位多多理解。",
- "联机功能已暂时关闭", "查看详情", "确定") = 1 Then
- OpenWebsite("https://www.bilibili.com/read/cv19845645")
- End If
- e.Handled = True
- End Sub
'''
''' 通过点击返回按钮或手动触发返回来改变页面。
'''
@@ -1309,9 +1134,9 @@ Install:
If PageStack.Any Then
'子页面 → 另一个子页面,更新
AniStart({
- AaOpacity(LabTitleInner, -LabTitleInner.Opacity, 130),
- AaCode(Sub() LabTitleInner.Text = PageName,, True),
- AaOpacity(LabTitleInner, 1, 150, 30)
+ AaOpacity(LabTitleInner, -LabTitleInner.Opacity, 130),
+ AaCode(Sub() LabTitleInner.Text = PageName,, True),
+ AaOpacity(LabTitleInner, 1, 150, 30)
}, "FrmMain Titlebar SubLayer")
If PageStack.Contains(Stack) Then
'返回到更上层的子页面
@@ -1405,37 +1230,41 @@ Install:
AniControlEnabled -= 1
'执行动画
AniStart({
- AaCode(Sub()
- AniControlEnabled += 1
- '把新页面添加进容器
- PanMainLeft.Child = PageLeft
- PageLeft.Opacity = 0
- PanMainLeft.Background = Nothing
- AniControlEnabled -= 1
- RunInUi(Sub() PanMainLeft_Resize(PanMainLeft.ActualWidth), True)
- End Sub, 130),
- AaCode(Sub()
- '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
- PageLeft.Opacity = 1
- PageLeft.TriggerShowAnimation()
- End Sub, 30, True)
+ AaCode(
+ Sub()
+ AniControlEnabled += 1
+ '把新页面添加进容器
+ PanMainLeft.Child = PageLeft
+ PageLeft.Opacity = 0
+ PanMainLeft.Background = Nothing
+ AniControlEnabled -= 1
+ RunInUi(Sub() PanMainLeft_Resize(PanMainLeft.ActualWidth), True)
+ End Sub, 130),
+ AaCode(
+ Sub()
+ '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
+ PageLeft.Opacity = 1
+ PageLeft.TriggerShowAnimation()
+ End Sub, 30, True)
}, "FrmMain PageChangeLeft")
AniStart({
- AaCode(Sub()
- AniControlEnabled += 1
- CType(PanMainRight.Child, MyPageRight).PageOnForceExit()
- '把新页面添加进容器
- PanMainRight.Child = PageRight
- PageRight.Opacity = 0
- PanMainRight.Background = Nothing
- AniControlEnabled -= 1
- RunInUi(Sub() BtnExtraBack.ShowRefresh(), True)
- End Sub, 130),
- AaCode(Sub()
- '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
- PageRight.Opacity = 1
- PageRight.PageOnEnter()
- End Sub, 30, True)
+ AaCode(
+ Sub()
+ AniControlEnabled += 1
+ CType(PanMainRight.Child, MyPageRight).PageOnForceExit()
+ '把新页面添加进容器
+ PanMainRight.Child = PageRight
+ PageRight.Opacity = 0
+ PanMainRight.Background = Nothing
+ AniControlEnabled -= 1
+ RunInUi(Sub() BtnExtraBack.ShowRefresh(), True)
+ End Sub, 130),
+ AaCode(
+ Sub()
+ '延迟触发页面通用动画,以使得在 Loaded 事件中加载的控件得以处理
+ PageRight.Opacity = 1
+ PageRight.PageOnEnter()
+ End Sub, 30, True)
}, "FrmMain PageChangeRight")
End Sub
'''
@@ -1448,12 +1277,12 @@ Install:
PanTitleMain.IsHitTestVisible = True
PanTitleInner.IsHitTestVisible = False
AniStart({
- AaOpacity(PanTitleInner, -PanTitleInner.Opacity, 150),
- AaX(PanTitleInner, -18 - PanTitleInner.Margin.Left, 150,, New AniEaseInFluent),
- AaOpacity(PanTitleMain, 1 - PanTitleMain.Opacity, 150, 200),
- AaX(PanTitleMain, -PanTitleMain.Margin.Left, 350, 200, New AniEaseOutBack(AniEasePower.Weak)),
- AaCode(Sub() PanTitleInner.Visibility = Visibility.Collapsed,, True)
- }, "FrmMain Titlebar FirstLayer")
+ AaOpacity(PanTitleInner, -PanTitleInner.Opacity, 150),
+ AaX(PanTitleInner, -18 - PanTitleInner.Margin.Left, 150,, New AniEaseInFluent),
+ AaOpacity(PanTitleMain, 1 - PanTitleMain.Opacity, 150, 200),
+ AaX(PanTitleMain, -PanTitleMain.Margin.Left, 350, 200, New AniEaseOutBack(AniEasePower.Weak)),
+ AaCode(Sub() PanTitleInner.Visibility = Visibility.Collapsed,, True)
+ }, "FrmMain Titlebar FirstLayer")
PageStack.Clear()
Else
'主页面 → 主页面,无事发生
@@ -1472,16 +1301,16 @@ Install:
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, 400,, New AniEaseOutFluent(AniEasePower.ExtraStrong)),
+ AaOpacity(RectLeftShadow, 1 - RectLeftShadow.Opacity, 200),
+ AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 250)
}, "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, 200,, New AniEaseOutFluent),
+ AaOpacity(RectLeftShadow, -RectLeftShadow.Opacity, 200),
+ AaCode(Sub() PanMainLeft.IsHitTestVisible = True, 170)
}, "FrmMain LeftChange", True)
End If
Else
diff --git a/Plain Craft Launcher 2/Images/Icons/Unavaliable.png b/Plain Craft Launcher 2/Images/Icons/Unavailable.png
similarity index 100%
rename from Plain Craft Launcher 2/Images/Icons/Unavaliable.png
rename to Plain Craft Launcher 2/Images/Icons/Unavailable.png
diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb
index 994dfaa..bdfa0c9 100644
--- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb
+++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb
@@ -1,9 +1,11 @@
-Imports System.IO.Compression
+Imports System.Globalization
+Imports System.IO.Compression
+Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Security.Cryptography
Imports System.Security.Principal
Imports System.Text.RegularExpressions
-Imports System.Windows.Markup
+Imports System.Xaml
Imports Newtonsoft.Json
Public Module ModBase
@@ -11,12 +13,13 @@ Public Module ModBase
#Region "声明"
'下列版本信息由更新器自动修改
- Public Const VersionBaseName As String = "2.8.6" '不含分支前缀的显示用版本名
- Public Const VersionStandardCode As String = "2.8.6." & VersionBranchCode '标准格式的四段式版本号
+ Public Const VersionBaseName As String = "2.9.3" '不含分支前缀的显示用版本名
+ Public Const VersionStandardCode As String = "2.9.3." & VersionBranchCode '标准格式的四段式版本号
+ Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
#If BETA Then
- Public Const VersionCode As Integer = 336 'Release
+ Public Const VersionCode As Integer = 355 'Release
#Else
- Public Const VersionCode As Integer = 335 'Snapshot
+ Public Const VersionCode As Integer = 354 'Snapshot
#End If
'自动生成的版本信息
Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -64,7 +67,7 @@ Public Module ModBase
'''
Public ApplicationOpenTime As Date = Date.Now
'''
- ''' 设备唯一标识符。
+ ''' 识别码。
'''
Public UniqueAddress As String = SecretGetUniqueAddress()
'''
@@ -174,6 +177,10 @@ Public Module ModBase
'''
Public Const IconButtonOffline As String = "M533.293176 788.841412a60.235294 60.235294 0 1 1 85.202824 85.202823l-42.616471 42.586353c-129.355294 129.385412-339.124706 129.385412-468.510117 0-129.385412-129.385412-129.385412-339.124706 0-468.510117l42.586353-42.616471a60.235294 60.235294 0 1 1 85.202823 85.202824l-42.61647 42.586352a210.823529 210.823529 0 1 0 298.164706 298.164706l42.586352-42.61647z m255.548236-255.548236l42.61647-42.586352a210.823529 210.823529 0 1 0-298.164706-298.164706l-42.586352 42.61647a60.235294 60.235294 0 1 1-85.202824-85.202823l42.616471-42.586353c129.355294-129.385412 339.124706-129.385412 468.510117 0 129.385412 129.385412 129.385412 339.124706 0 468.510117l-42.586353 42.616471a60.235294 60.235294 0 1 1-85.202823-85.202824zM192.542118 192.542118a60.235294 60.235294 0 0 1 85.202823 0l553.712941 553.712941a60.235294 60.235294 0 0 1-85.202823 85.202823L192.542118 277.744941a60.235294 60.235294 0 0 1 0-85.202823z"
'''
+ ''' 图标,服务端,1x
+ '''
+ Public Const IconButtonServer As String = "M224 160a64 64 0 0 0-64 64v576a64 64 0 0 0 64 64h576a64 64 0 0 0 64-64V224a64 64 0 0 0-64-64H224z m0 384h576v256H224v-256z m192 96v64h320v-64H416z m-128 0v64h64v-64H288zM224 224h576v256H224V224z m192 96v64h320v-64H416z m-128 0v64h64v-64H288z"
+ '''
''' 图标,音符,1x
'''
Public Const IconMusic As String = "M348.293565 716.53287V254.797913c0-41.672348 28.004174-78.358261 68.919652-90.37913L815.994435 40.826435c62.775652-18.610087 125.907478 26.579478 125.907478 89.933913v539.158261c8.013913 42.25113-8.94887 89.177043-47.014956 127.109565a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c21.014261 1.024 40.514783 5.164522 57.878261 12.065391V233.338435c0-12.109913-10.551652-20.034783-20.569044-20.034783a24.620522 24.620522 0 0 0-5.787826 0.934957L439.785739 338.18713a19.545043 19.545043 0 0 0-14.825739 19.144348v438.984348H423.846957c11.53113 43.987478-5.164522 94.208-45.412174 134.322087a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c20.791652 1.024 40.069565 5.075478 57.299478 11.842783z"
@@ -414,7 +421,7 @@ Public Module ModBase
'''
''' 执行返回值。
'''
- Public Enum Result
+ Public Enum ProcessReturnValues
'''
''' 执行成功,或进程被中断。
'''
@@ -426,19 +433,23 @@ Public Module ModBase
'''
''' 执行失败。
'''
- Fail
+ Fail = 1
'''
''' 执行时出现未经处理的异常。
'''
- Exception
+ Exception = 2
'''
''' 执行超时。
'''
- Timeout
+ Timeout = 3
'''
''' 取消执行。可能是由于不满足执行的前置条件。
'''
- Cancel
+ Cancel = 4
+ '''
+ ''' 任务成功完成。
+ '''
+ TaskDone = 5
End Enum
'''
@@ -567,62 +578,59 @@ Public Module ModBase
#Region "文件"
- '注册表
+ '=============================
+ ' 注册表
+ '=============================
+
'''
- ''' 重命名一个注册表子键。不可用于包含子键的子键。
- '''
- Public Sub RenameReg(parentKey As Microsoft.Win32.RegistryKey, subKeyName As String, newSubKeyName As String)
- If parentKey.GetSubKeyNames().Contains(newSubKeyName) Then parentKey.DeleteSubKeyTree(newSubKeyName, False)
- Dim SourceKey As Microsoft.Win32.RegistryKey = parentKey.OpenSubKey(subKeyName)
- If IsNothing(SourceKey) Then Exit Sub '没有目标项
- Dim NewKey As Microsoft.Win32.RegistryKey = parentKey.CreateSubKey(newSubKeyName)
- If SourceKey.GetSubKeyNames().Length > 0 Then Throw New NotSupportedException("不支持对包含子键的子键进行重命名:" & SourceKey.GetSubKeyNames()(0) & "。")
- For Each valueName As String In SourceKey.GetValueNames()
- Dim objValue As Object = SourceKey.GetValue(valueName)
- Dim valKind As Microsoft.Win32.RegistryValueKind = SourceKey.GetValueKind(valueName)
- NewKey.SetValue(valueName, objValue, valKind)
- Next
- parentKey.DeleteSubKeyTree(subKeyName, False)
- End Sub
- '''
- ''' 读取程序所属注册表。
+ ''' 读取注册表键。如果失败则返回默认值。
'''
Public Function ReadReg(Key As String, Optional DefaultValue As String = "") As String
Try
- Dim parentKey As Microsoft.Win32.RegistryKey, softKey As Microsoft.Win32.RegistryKey
- parentKey = My.Computer.Registry.CurrentUser
- softKey = parentKey.OpenSubKey("Software\" & RegFolder, True)
- If softKey Is Nothing Then
- ReadReg = DefaultValue '不存在则返回默认值
- Else
- Dim readValue As New Text.StringBuilder
- readValue.AppendLine(softKey.GetValue(Key))
- Dim value = readValue.ToString.Replace(vbCrLf, "") '去除莫名的回车
- Return If(value = "", DefaultValue, value) '错误则返回默认值
- End If
+ Dim SubKey = My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, False)
+ Return If(SubKey?.GetValue(Key), DefaultValue)
Catch ex As Exception
Log(ex, "读取注册表出错:" & Key, LogLevel.Hint)
Return DefaultValue
End Try
End Function
'''
- ''' 写入程序所属注册表。
+ ''' 写入注册表键。
'''
- Public Sub WriteReg(Key As String, Value As String, Optional ShowException As Boolean = False)
+ Public Sub WriteReg(Key As String, Value As String, Optional ThrowException As Boolean = False)
Try
- Dim parentKey As Microsoft.Win32.RegistryKey, softKey As Microsoft.Win32.RegistryKey
- parentKey = My.Computer.Registry.CurrentUser
- softKey = parentKey.OpenSubKey("Software\" & RegFolder, True)
- If softKey Is Nothing Then softKey = parentKey.CreateSubKey("Software\" & RegFolder) '如果不存在就创建
- softKey.SetValue(Key, Value)
+ Dim SubKey As Microsoft.Win32.RegistryKey = My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, True)
+ If SubKey Is Nothing Then SubKey = My.Computer.Registry.CurrentUser.CreateSubKey("Software\" & RegFolder) '如果不存在就创建
+ SubKey.SetValue(Key, Value)
Catch ex As Exception
- Log(ex, "写入注册表出错:" & Key, If(ShowException, LogLevel.Hint, LogLevel.Developer))
- If ShowException Then Throw '如果显示错误就丢一个
+ Log(ex, "写入注册表出错:" & Key, If(ThrowException, LogLevel.Hint, LogLevel.Developer))
+ If ThrowException Then Throw
+ End Try
+ End Sub
+ '''
+ ''' 是否存在某个注册表键。
+ '''
+ Public Function HasReg(Key As String) As Boolean
+ Return ReadReg(Key, Nothing) IsNot Nothing
+ End Function
+ '''
+ ''' 删除注册表键。
+ '''
+ Public Sub DeleteReg(Key As String, Optional ThrowException As Boolean = False)
+ Try
+ Dim SubKey As Microsoft.Win32.RegistryKey = My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, True)
+ SubKey?.DeleteValue(Key)
+ Catch ex As Exception
+ Log(ex, "删除注册表出错:" & Key, If(ThrowException, LogLevel.Hint, LogLevel.Developer))
+ If ThrowException Then Throw
End Try
End Sub
- 'Ini 文件
- Private ReadOnly IniCache As New Dictionary(Of String, Dictionary(Of String, String))
+ '=============================
+ ' ini
+ '=============================
+
+ Private ReadOnly IniCache As New SafeDictionary(Of String, SafeDictionary(Of String, String))
'''
''' 清除某 ini 文件的运行时缓存。
'''
@@ -636,7 +644,7 @@ Public Module ModBase
''' 在文件不存在或读取失败时返回 Nothing。
'''
''' 文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。
- Private Function IniGetContent(FileName As String) As Dictionary(Of String, String)
+ Private Function IniGetContent(FileName As String) As SafeDictionary(Of String, String)
Try
'还原文件路径
If Not FileName.Contains(":\") Then FileName = $"{Path}PCL\{FileName}.ini"
@@ -644,7 +652,7 @@ Public Module ModBase
If IniCache.ContainsKey(FileName) Then Return IniCache(FileName)
'读取文件
If Not File.Exists(FileName) Then Return Nothing
- Dim Ini As New Dictionary(Of String, String)
+ Dim Ini As New SafeDictionary(Of String, String)
For Each Line In ReadFile(FileName).Split(vbCrLf.ToArray(), StringSplitOptions.RemoveEmptyEntries)
Dim Index As Integer = Line.IndexOfF(":")
If Index > 0 Then Ini(Line.Substring(0, Index)) = Line.Substring(Index + 1) '可能会有重复键,见 #3616
@@ -668,7 +676,21 @@ Public Module ModBase
Return Content(Key)
End Function
'''
+ ''' 判断 ini 文件中是否包含某个键。这可能会使用到缓存。
+ '''
+ Public Function HasIniKey(FileName As String, Key As String) As Boolean
+ Dim Content = IniGetContent(FileName)
+ Return Content IsNot Nothing AndAlso Content.ContainsKey(Key)
+ End Function
+ '''
+ ''' 从 ini 文件中移除某个键。这会更新缓存。
+ '''
+ Public Sub DeleteIniKey(FileName As String, Key As String)
+ WriteIni(FileName, Key, Nothing)
+ End Sub
+ '''
''' 写入 ini 文件,这会更新缓存。
+ ''' 若 Value 为 Nothing,则删除该键。
'''
''' 文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。
''' 键。
@@ -677,28 +699,38 @@ Public Module ModBase
Public Sub WriteIni(FileName As String, Key As String, Value As String)
Try
'预处理
- Key = Key.Replace(vbCr, "").Replace(vbLf, "").Replace(":", "")
- Value = Value.Replace(vbCr, "").Replace(vbLf, "") '允许 Value 中包含冒号
- '获取目前文件
- Dim Content As Dictionary(Of String, String) = IniGetContent(FileName)
- If Content Is Nothing Then Content = New Dictionary(Of String, String)
- If Content.ContainsKey(Key) AndAlso Content(Key) = Value Then Exit Sub '如果值一样就不处理
- '更新缓存
- Content(Key) = Value
- '写入文件
- Dim FileContent As New StringBuilder
- For Each Pair In Content
- FileContent.Append(Pair.Key)
- FileContent.Append(":")
- FileContent.Append(Pair.Value)
- FileContent.Append(vbCrLf)
- Next
- If Not FileName.Contains(":\") Then FileName = $"{Path}PCL\{FileName}.ini"
- WriteFile(FileName, FileContent.ToString)
+ If Key.Contains(":") Then Throw New Exception($"尝试写入 ini 文件 {FileName} 的键名中包含了冒号:{Key}")
+ Key = Key.Replace(vbCr, "").Replace(vbLf, "")
+ Value = Value?.Replace(vbCr, "").Replace(vbLf, "")
+ '防止争用
+ SyncLock WriteIniLock
+ '获取目前文件
+ Dim Content As SafeDictionary(Of String, String) = IniGetContent(FileName)
+ If Content Is Nothing Then Content = New SafeDictionary(Of String, String)
+ '更新值
+ If Value Is Nothing Then
+ If Not Content.ContainsKey(Key) Then Return '无需处理
+ Content.Remove(Key)
+ Else
+ If Content.ContainsKey(Key) AndAlso Content(Key) = Value Then Return '无需处理
+ Content(Key) = Value
+ End If
+ '写入文件
+ Dim FileContent As New StringBuilder
+ For Each Pair In Content
+ FileContent.Append(Pair.Key)
+ FileContent.Append(":")
+ FileContent.Append(Pair.Value)
+ FileContent.Append(vbCrLf)
+ Next
+ If Not FileName.Contains(":\") Then FileName = $"{Path}PCL\{FileName}.ini"
+ WriteFile(FileName, FileContent.ToString)
+ End SyncLock
Catch ex As Exception
Log(ex, $"写入文件失败({FileName} → {Key}:{Value})", LogLevel.Hint)
End Try
End Sub
+ Private WriteIniLock As New Object
'路径处理
'''
@@ -827,21 +859,21 @@ 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
'确保目录存在
Directory.CreateDirectory(GetPathFromFullPath(FilePath))
'写入文件
- If File.Exists(FilePath) Then
- '如果文件存在,刷新目前文件
- Using writer As New StreamWriter(FilePath, Append, If(Encoding, GetEncoding(ReadFileBytes(FilePath))))
+ If Append Then
+ '追加目前文件
+ Using writer As New StreamWriter(FilePath, True, If(Encoding, GetEncoding(ReadFileBytes(FilePath))))
writer.Write(Text)
writer.Flush()
writer.Close()
End Using
Else
- '如果文件不存在,则新建并写入
- File.WriteAllText(FilePath, Text, If(Encoding, New UTF8Encoding(False)))
+ '直接写入字节
+ File.WriteAllBytes(FilePath, If(Encoding Is Nothing, New UTF8Encoding(False).GetBytes(Text), Encoding.GetBytes(Text)))
End If
End Sub
'''
@@ -852,7 +884,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
'确保目录存在
Directory.CreateDirectory(GetPathFromFullPath(FilePath))
@@ -951,25 +983,25 @@ Public Module ModBase
''' 要求的格式。如:“常用图片文件(*.png;*.jpg)|*.png;*.jpg”。
''' 弹窗的标题。
''' 默认的文件名。
- Public Function SelectAs(Title As String, FileName As String, Optional FileFilter As String = Nothing, Optional DefaultDir As String = Nothing) As String
+ Public Function SelectSaveFile(Title As String, FileName As String, Optional FileFilter As String = Nothing, Optional InitialDirectory As String = Nothing) As String
Using fileDialog As New Forms.SaveFileDialog
fileDialog.AddExtension = True
fileDialog.AutoUpgradeEnabled = True
fileDialog.Title = Title
fileDialog.FileName = FileName
If FileFilter IsNot Nothing Then fileDialog.Filter = FileFilter
- If DefaultDir IsNot Nothing Then fileDialog.InitialDirectory = DefaultDir
+ If Not String.IsNullOrEmpty(InitialDirectory) AndAlso Directory.Exists(InitialDirectory) Then fileDialog.InitialDirectory = InitialDirectory
fileDialog.ShowDialog()
- SelectAs = If(fileDialog.FileName.Contains(":\"), fileDialog.FileName, "")
- Log("[UI] 选择文件返回:" & SelectAs)
+ SelectSaveFile = If(fileDialog.FileName.Contains(":\"), fileDialog.FileName, "")
+ Log("[UI] 选择文件返回:" & SelectSaveFile)
End Using
End Function
'''
- ''' 弹出选取文件对话框并且要求选取文件。
+ ''' 弹出选取文件对话框,要求选择一个文件。
'''
''' 要求的格式。如:“常用图片文件(*.png;*.jpg)|*.png;*.jpg”。
''' 弹窗的标题。
- Public Function SelectFile(FileFilter As String, Title As String) As String
+ Public Function SelectFile(FileFilter As String, Title As String, Optional InitialDirectory As String = Nothing) As String
Using fileDialog As New Forms.OpenFileDialog
fileDialog.AddExtension = True
fileDialog.AutoUpgradeEnabled = True
@@ -978,13 +1010,34 @@ Public Module ModBase
fileDialog.Multiselect = False
fileDialog.Title = Title
fileDialog.ValidateNames = True
+ If Not String.IsNullOrEmpty(InitialDirectory) AndAlso Directory.Exists(InitialDirectory) Then fileDialog.InitialDirectory = InitialDirectory
fileDialog.ShowDialog()
- SelectFile = fileDialog.FileName
- Log("[UI] 选择文件返回:" & SelectFile)
+ Log("[UI] 选择单个文件返回:" & fileDialog.FileName)
+ Return fileDialog.FileName
End Using
End Function
'''
- ''' 弹出选取文件夹对话框并且要求选取文件夹。如果没有选择就返回空字符串。
+ ''' 弹出选取文件对话框,要求选择多个文件。
+ '''
+ ''' 要求的格式。如:“常用图片文件(*.png;*.jpg)|*.png;*.jpg”。
+ ''' 弹窗的标题。
+ Public Function SelectFiles(FileFilter As String, Title As String) As String()
+ Using fileDialog As New Forms.OpenFileDialog
+ fileDialog.AddExtension = True
+ fileDialog.AutoUpgradeEnabled = True
+ fileDialog.CheckFileExists = True
+ fileDialog.Filter = FileFilter
+ fileDialog.Multiselect = True
+ fileDialog.Title = Title
+ fileDialog.ValidateNames = True
+ fileDialog.ShowDialog()
+ Log("[UI] 选择多个文件返回:" & fileDialog.FileNames.Join(","))
+ Return fileDialog.FileNames
+ End Using
+ End Function
+ '''
+ ''' 弹出选取文件夹对话框,要求选取文件夹。
+ ''' 返回以 \ 结尾的完整路径,如果没有选择则返回空字符串。
'''
Public Function SelectFolder(Optional Title As String = "选择文件夹") As String
Dim folderDialog As New Ookii.Dialogs.Wpf.VistaFolderBrowserDialog With {.ShowNewFolderButton = True, .RootFolder = Environment.SpecialFolder.Desktop, .Description = Title, .UseDescriptionForTitle = True}
@@ -1054,6 +1107,37 @@ Re:
End Try
End Function
'''
+ ''' 获取文件 SHA512,若失败则返回空字符串。
+ '''
+ Public Function GetFileSHA512(FilePath As String) As String
+ Dim Retry As Boolean = False
+Re:
+ Try
+ ''检测该文件是否在下载中,若在下载则放弃检测
+ 'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
+ '获取 SHA512
+ Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
+ Dim sha512 As SHA512 = New SHA512CryptoServiceProvider()
+ Dim retval As Byte() = sha512.ComputeHash(file)
+ file.Close()
+ Dim Result As New StringBuilder()
+ For i As Integer = 0 To retval.Length - 1
+ Result.Append(retval(i).ToString("x2"))
+ Next
+ Return Result.ToString
+ Catch ex As Exception
+ If Retry OrElse TypeOf ex Is FileNotFoundException Then
+ Log(ex, "获取文件 SHA512 失败:" & FilePath)
+ Return ""
+ Else
+ Retry = True
+ Log(ex, "获取文件 SHA512 可重试失败:" & FilePath, LogLevel.Normal)
+ Thread.Sleep(RandomInteger(200, 500))
+ GoTo Re
+ End If
+ End Try
+ End Function
+ '''
''' 获取文件 SHA256,若失败则返回空字符串。
'''
Public Function GetFileSHA256(FilePath As String) As String
@@ -1064,8 +1148,8 @@ Re:
'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
'获取 SHA256
Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
- Dim sha1 As SHA256 = New SHA256CryptoServiceProvider()
- Dim retval As Byte() = sha1.ComputeHash(file)
+ Dim sha256 As SHA256 = New SHA256CryptoServiceProvider()
+ Dim retval As Byte() = sha256.ComputeHash(file)
file.Close()
Dim Result As New StringBuilder()
For i As Integer = 0 To retval.Length - 1
@@ -1114,7 +1198,7 @@ Re:
End Try
End Function
'''
- ''' 获取流 SHA1,若失败则返回空字符串。
+ ''' 获取流的 SHA1,若失败则返回空字符串。
'''
Public Function GetAuthSHA1(Stream As Stream) As String
Try
@@ -1169,8 +1253,14 @@ Re:
Dim Info As New FileInfo(LocalPath)
If Not Info.Exists Then Return "文件不存在:" & LocalPath
Dim FileSize As Long = Info.Length
- If ActualSize >= 0 AndAlso ActualSize <> FileSize Then Return "文件大小应为 " & ActualSize & " B,实际为 " & FileSize & " B"
- If MinSize >= 0 AndAlso MinSize > FileSize Then Return "文件大小应大于 " & MinSize & " B,实际为 " & FileSize & " B"
+ If ActualSize >= 0 AndAlso ActualSize <> FileSize Then
+ Return $"文件大小应为 {ActualSize} B,实际为 {FileSize} B" &
+ If(FileSize < 2000, ",内容为:" & ReadFile(LocalPath), "")
+ End If
+ If MinSize >= 0 AndAlso MinSize > FileSize Then
+ Return $"文件大小应大于 {MinSize} B,实际为 {FileSize} B" &
+ If(FileSize < 2000, ",内容为:" & ReadFile(LocalPath), "")
+ End If
If Not String.IsNullOrEmpty(Hash) Then
If Hash.Length < 35 Then 'MD5
If Hash.ToLowerInvariant <> GetFileMD5(LocalPath) Then Return "文件 MD5 应为 " & Hash & ",实际为 " & GetFileMD5(LocalPath)
@@ -1201,7 +1291,8 @@ Re:
''' 尝试根据后缀名判断文件种类并解压文件,支持 gz 与 zip,会尝试将 jar 以 zip 方式解压。
''' 会尝试创建,但不会清空目标文件夹。
'''
- Public Sub ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing)
+ Public Sub ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing,
+ Optional ProgressIncrementHandler As Action(Of Double) = Nothing)
Directory.CreateDirectory(DestDirectory)
If CompressFilePath.EndsWithF(".gz", True) Then
'以 gz 方式解压
@@ -1217,7 +1308,9 @@ Re:
Else
'以 zip 方式解压
Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030")))
+ Dim TotalCount As Integer = Archive.Entries.Count
For Each Entry As ZipArchiveEntry In Archive.Entries
+ If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / TotalCount)
Dim DestinationPath As String = IO.Path.Combine(DestDirectory, Entry.FullName)
If DestinationPath.EndsWithF("\") OrElse DestinationPath.EndsWithF("/") Then
Continue For '不创建空文件夹
@@ -1236,9 +1329,15 @@ Re:
Public Function DeleteDirectory(Path As String, Optional IgnoreIssue As Boolean = False) As Integer
If Not Directory.Exists(Path) Then Return 0
Dim DeletedCount As Integer = 0
- Dim Temp As String()
- Temp = Directory.GetFiles(Path)
- For Each FilePath As String In Temp
+ Dim Files As String()
+ Try
+ Files = Directory.GetFiles(Path)
+ Catch ex As DirectoryNotFoundException '#4549
+ Log(ex, $"疑似为孤立符号链接,尝试直接删除({Path})", LogLevel.Developer)
+ Directory.Delete(Path)
+ Return 0
+ End Try
+ For Each FilePath As String In Files
Dim RetriedFile As Boolean = False
RetryFile:
Try
@@ -1257,8 +1356,7 @@ RetryFile:
End If
End Try
Next
- Temp = Directory.GetDirectories(Path)
- For Each str As String In Temp
+ For Each str As String In Directory.GetDirectories(Path)
DeleteDirectory(str, IgnoreIssue)
Next
Dim RetriedDir As Boolean = False
@@ -1282,24 +1380,38 @@ RetryDir:
'''
''' 复制文件夹,失败会抛出异常。
'''
- Public Sub CopyDirectory(FromPath As String, ToPath As String)
+ Public Sub CopyDirectory(FromPath As String, ToPath As String, Optional ProgressIncrementHandler As Action(Of Double) = Nothing)
FromPath = FromPath.Replace("/", "\")
If Not FromPath.EndsWithF("\") Then FromPath &= "\"
ToPath = ToPath.Replace("/", "\")
If Not ToPath.EndsWithF("\") Then ToPath &= "\"
- For Each File In EnumerateFiles(FromPath)
+ Dim AllFiles = EnumerateFiles(FromPath).ToList
+ Dim FileCount As Integer = AllFiles.Count
+ For Each File In AllFiles
CopyFile(File.FullName, File.FullName.Replace(FromPath, ToPath))
+ If ProgressIncrementHandler IsNot Nothing Then ProgressIncrementHandler(1 / FileCount)
Next
End Sub
'''
''' 遍历文件夹中的所有文件。
'''
Public Function EnumerateFiles(Directory As String) As IEnumerable(Of FileInfo)
- Dim Info As New DirectoryInfo(Directory)
+ Dim Info As New DirectoryInfo(ShortenPath(Directory))
If Not Info.Exists Then Return New List(Of FileInfo)
Return Info.EnumerateFiles("*", SearchOption.AllDirectories)
End Function
+ '''
+ ''' 若路径长度大于指定值,则将长路径转换为短路径。
+ '''
+ Public Function ShortenPath(LongPath As String, Optional ShortenThreshold As Integer = 247) As String
+ If LongPath.Length <= ShortenThreshold Then Return LongPath
+ Dim ShortPath As New StringBuilder(260)
+ GetShortPathName(LongPath, ShortPath, 260)
+ Return ShortPath.ToString
+ End Function
+ Private Declare Function GetShortPathName Lib "kernel32" Alias "GetShortPathNameA" (ByVal lpszLongPath As String, ByVal lpszShortPath As StringBuilder, ByVal cchBuffer As Integer) As Integer
+
#End Region
#Region "文本"
@@ -1309,8 +1421,8 @@ RetryDir:
'''
''' 提取 Exception 的具体描述与堆栈。
'''
- ''' 是否必须显示所有堆栈。通常用于判定堆栈信息。
- Public Function GetExceptionDetail(Ex As Exception, Optional ShowAllTrace As Boolean = False) As String
+ ''' 是否必须显示所有堆栈。通常用于判定堆栈信息。
+ Public Function GetExceptionDetail(Ex As Exception, Optional ShowAllStacks As Boolean = False) As String
If Ex Is Nothing Then Return "无可用错误信息!"
'获取最底层的异常
@@ -1321,24 +1433,25 @@ RetryDir:
'获取各级错误的描述与堆栈信息
Dim DescList As New List(Of String)
- Dim StackList As New List(Of String)
+ Dim IsInner As Boolean = False
Do Until Ex Is Nothing
- DescList.Add(Ex.Message.Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr & vbCr, vbCr).Replace(vbCr, vbCrLf))
+ DescList.Add(If(IsInner, "→ ", "") & Ex.Message.Replace(vbLf, vbCr).Replace(vbCr & vbCr, vbCr).Replace(vbCr, vbCrLf))
If Ex.StackTrace IsNot Nothing Then
- For Each St As String In Ex.StackTrace.Split(vbCrLf)
- If ShowAllTrace OrElse St.ToLower.Contains("pcl") Then StackList.Add(St.Replace(vbCr, String.Empty).Replace(vbLf, String.Empty))
+ For Each Stack As String In Ex.StackTrace.Split(vbCrLf.ToCharArray, StringSplitOptions.RemoveEmptyEntries)
+ If ShowAllStacks OrElse Stack.ContainsF("pcl", True) Then
+ DescList.Add(Stack.Replace(vbCr, String.Empty).Replace(vbLf, String.Empty))
+ End If
Next
End If
+ If Ex.GetType.FullName <> "System.Exception" Then DescList.Add(" 错误类型:" & Ex.GetType.FullName)
Ex = Ex.InnerException
+ IsInner = True
Loop
- DescList = DescList.Distinct.ToList
- Dim Desc As String = Join(DescList, vbCrLf & "→ ")
- Dim Stack As String = If(StackList.Any, vbCrLf & Join(StackList, vbCrLf), "")
'常见错误(记得同时修改下面的)
Dim CommonReason As String = Nothing
- If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
- CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。"
+ 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
@@ -1346,20 +1459,15 @@ RetryDir:
ElseIf TypeOf InnerEx Is Runtime.InteropServices.COMException Then
CommonReason = "由于操作系统或显卡存在问题,导致出现错误。请尝试重启 PCL。"
ElseIf {"远程主机强迫关闭了", "远程方已关闭传输流", "未能解析此远程名称", "由于目标计算机积极拒绝",
- "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) Desc.Contains(s)) Then
+ "操作已超时", "操作超时", "服务器超时", "连接超时"}.Any(Function(s) DescList.Any(Function(l) l.Contains(s))) Then
CommonReason = "你的网络环境不佳,导致难以连接到服务器。请检查网络,多重试几次,或尝试使用 VPN。"
End If
- '获取错误类型
- Dim TypeDesc As String = If(InnerEx.GetType.FullName = "System.Exception", "", vbCrLf & "错误类型:" & InnerEx.GetType.FullName)
-
'构造输出信息
If CommonReason Is Nothing Then
- Return Desc & Stack & TypeDesc
+ Return DescList.Join(vbCrLf)
Else
- Dim Result As String = DescList.First & vbCrLf & CommonReason & vbCrLf & "————————————" & vbCrLf
- DescList(0) = "详细错误信息:"
- Return Result & Join(DescList, vbCrLf & "→ ") & Stack & TypeDesc
+ Return CommonReason & vbCrLf & vbCrLf & "————————————" & vbCrLf & "详细错误信息:" & vbCrLf & DescList.Join(vbCrLf)
End If
End Function
'''
@@ -1385,8 +1493,8 @@ RetryDir:
'常见错误(记得同时修改上面的)
Dim CommonReason As String = Nothing
- If TypeOf InnerEx Is TypeLoadException OrElse TypeOf InnerEx Is MissingMethodException OrElse TypeOf InnerEx Is NotImplementedException OrElse TypeOf InnerEx Is TypeInitializationException Then
- CommonReason = "PCL 的运行环境存在问题。请尝试重新安装 .NET Framework 4.6.2 然后再试。"
+ 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
@@ -1400,10 +1508,10 @@ RetryDir:
'构造输出信息
If CommonReason IsNot Nothing Then
- Return DescList.First & ":" & CommonReason
+ Return CommonReason & "详细错误:" & DescList.First
Else
DescList.Reverse() '让最深层错误在最左边
- Return Join(DescList, " → ")
+ Return Join(DescList, " ← ")
End If
End Function
@@ -1440,14 +1548,14 @@ RetryDir:
End Function
'''
- ''' 获取 Json 对象。
+ ''' 获取 JSON 对象。
'''
Public Function GetJson(Data As String)
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 > 10000, Data.Substring(0, 100) & $"...(全长 {Length} 个字符)..." & Right(Data, 100), Data))
+ Throw New Exception("格式化 JSON 失败:" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data))
End Try
End Function
@@ -1541,10 +1649,10 @@ RetryDir:
End Function
'''
- ''' 获取在子字符串之前的部分。
- ''' 会裁切尽可能多的内容,但如果未找到子字符串则不裁切。
+ ''' 获取在子字符串第一次出现之前的部分,例如对 2024/11/08 拆切 / 会得到 2024。
+ ''' 如果未找到子字符串则不裁切。
'''
- Public Function Before(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
+ Public Function BeforeFirst(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.IndexOfF(Text, IgnoreCase))
If Pos >= 0 Then
Return Str.Substring(0, Pos)
@@ -1553,10 +1661,34 @@ RetryDir:
End If
End Function
'''
- ''' 获取在子字符串之后的部分。
- ''' 会裁切尽可能多的内容,但如果未找到子字符串则不裁切。
+ ''' 获取在子字符串最后一次出现之前的部分,例如对 2024/11/08 拆切 / 会得到 2024/11。
+ ''' 如果未找到子字符串则不裁切。
'''
- Public Function After(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
+ Public Function BeforeLast(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
+ Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.LastIndexOfF(Text, IgnoreCase))
+ If Pos >= 0 Then
+ Return Str.Substring(0, Pos)
+ Else
+ Return Str
+ End If
+ End Function
+ '''
+ ''' 获取在子字符串第一次出现之后的部分,例如对 2024/11/08 拆切 / 会得到 11/08。
+ ''' 如果未找到子字符串则不裁切。
+ '''
+ Public Function AfterFirst(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
+ Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.IndexOfF(Text, IgnoreCase))
+ If Pos >= 0 Then
+ Return Str.Substring(Pos + Text.Length)
+ Else
+ Return Str
+ End If
+ End Function
+ '''
+ ''' 获取在子字符串最后一次出现之后的部分,例如对 2024/11/08 拆切 / 会得到 08。
+ ''' 如果未找到子字符串则不裁切。
+ '''
+ Public Function AfterLast(Str As String, Text As String, Optional IgnoreCase As Boolean = False) As String
Dim Pos As Integer = If(String.IsNullOrEmpty(Text), -1, Str.LastIndexOfF(Text, IgnoreCase))
If Pos >= 0 Then
Return Str.Substring(Pos + Text.Length)
@@ -1565,8 +1697,9 @@ RetryDir:
End If
End Function
'''
- ''' 获取处于两个子字符串之间的部分。
- ''' 会裁切尽可能多的内容:匹配开始使用 LastIndexOf,匹配结束使用 IndexOf,但如果未找到子字符串则不裁切。
+ ''' 获取处于两个子字符串之间的部分,裁切尽可能多的内容。
+ ''' 等效于 AfterLast 后接 BeforeFirst。
+ ''' 如果未找到子字符串则不裁切。
'''
Public Function Between(Str As String, After As String, Before As String, Optional IgnoreCase As Boolean = False) As String
Dim StartPos As Integer = If(String.IsNullOrEmpty(After), -1, Str.LastIndexOfF(After, IgnoreCase))
@@ -1657,12 +1790,27 @@ RetryDir:
Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("'", "'").
Replace("""", """).Replace(vbCrLf, "
")
End Function
+ '''
+ ''' 为字符串进行 Like 关键字转义。
+ '''
+ Public Function EscapeLikePattern(input As String) As String
+ Dim sb As New StringBuilder()
+ For Each c As Char In input
+ Select Case c
+ Case "["c, "]"c, "*"c, "?"c, "#"c
+ sb.Append("["c).Append(c).Append("]"c)
+ Case Else
+ sb.Append(c)
+ End Select
+ Next
+ Return sb.ToString()
+ End Function
'正则
'''
''' 搜索字符串中的所有正则匹配项。
'''
- Public Function RegexSearch(str As String, regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As List(Of String)
+ Public Function RegexSearch(str As String, regex As String, Optional options As RegexOptions = RegexOptions.None) As List(Of String)
Try
RegexSearch = New List(Of String)
Dim RegexSearchRes = New Regex(regex, options).Matches(str)
@@ -1678,7 +1826,7 @@ RetryDir:
'''
''' 获取字符串中的第一个正则匹配项,若无匹配则返回 Nothing。
'''
- Public Function RegexSeek(str As String, regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As String
+ Public Function RegexSeek(str As String, regex As String, Optional options As RegexOptions = RegexOptions.None) As String
Try
Dim Result = RegularExpressions.Regex.Match(str, regex, options).Value
Return If(Result = "", Nothing, Result)
@@ -1690,7 +1838,7 @@ RetryDir:
'''
''' 检查字符串是否匹配某正则模式。
'''
- Public Function RegexCheck(str As String, regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As Boolean
+ Public Function RegexCheck(str As String, regex As String, Optional options As RegexOptions = RegexOptions.None) As Boolean
Try
Return RegularExpressions.Regex.IsMatch(str, regex, options)
Catch ex As Exception
@@ -1701,14 +1849,14 @@ RetryDir:
'''
''' 进行正则替换,会抛出错误。
'''
- Public Function RegexReplace(Input As String, Replacement As String, Regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As String
- Return RegularExpressions.Regex.Replace(Input, Regex, Replacement, options)
+ Public Function RegexReplace(AllContents As String, SearchRegex As String, ReplaceTo As String, Optional options As RegexOptions = RegexOptions.None) As String
+ Return Regex.Replace(AllContents, SearchRegex, ReplaceTo, options)
End Function
'''
''' 对每个正则匹配分别进行替换,会抛出错误。
'''
- Public Function RegexReplaceEach(Input As String, Replacement As MatchEvaluator, Regex As String, Optional options As RegexOptions = RegularExpressions.RegexOptions.None) As String
- Return RegularExpressions.Regex.Replace(Input, Regex, Replacement, options)
+ Public Function RegexReplaceEach(AllContents As String, SearchRegex As String, ReplaceTo As MatchEvaluator, Optional options As RegexOptions = RegexOptions.None) As String
+ Return Regex.Replace(AllContents, SearchRegex, ReplaceTo, options)
End Function
#End Region
@@ -1801,13 +1949,13 @@ RetryDir:
'进行搜索,获取相似信息
For Each Entry In Entries
Entry.Similarity = SearchSimilarityWeighted(Entry.SearchSource, Query)
- Entry.AbsoluteRight = False
- For Each Pair In Entry.SearchSource
- If Pair.Key.Replace(" ", "").ContainsF(Query.Replace(" ", ""), True) Then Entry.AbsoluteRight = True
- Next
+ Entry.AbsoluteRight =
+ Query.Split(" ").All( '对于按空格分割的每一段
+ Function(QueryPart) Entry.SearchSource.Any( '若与任意一个搜索源完全匹配,则标记为完全匹配项
+ Function(Source) Source.Key.Replace(" ", "").ContainsF(QueryPart, True)))
Next
'按照相似度进行排序
- Entries = Sort(Entries,
+ Entries = Entries.Sort(
Function(Left, Right) As Boolean
If Left.AbsoluteRight Xor Right.AbsoluteRight Then
Return Left.AbsoluteRight
@@ -1833,6 +1981,184 @@ RetryDir:
#Region "系统"
+ '''
+ ''' 线程安全的,可以直接使用 For Each 的 List。
+ ''' 在使用 For Each 循环时,列表的结果可能并非最新,但不会抛出异常。
+ '''
+ Public Class SafeList(Of T)
+ Inherits SynchronizedCollection(Of T)
+ Implements IEnumerable, IEnumerable(Of T)
+ '构造函数
+ Public Sub New()
+ MyBase.New()
+ End Sub
+ Public Sub New(Data As IEnumerable(Of T))
+ MyBase.New(New Object, Data)
+ End Sub
+ Public Shared Widening Operator CType(Data As List(Of T)) As SafeList(Of T)
+ Return New SafeList(Of T)(Data)
+ End Operator
+ Public Shared Widening Operator CType(Data As SafeList(Of T)) As List(Of T)
+ Return New List(Of T)(Data)
+ End Operator
+ '基于 SyncLock 覆写
+ Public Overloads Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator
+ SyncLock SyncRoot
+ Return Items.ToList.GetEnumerator()
+ End SyncLock
+ End Function
+ Private Overloads Function GetEnumeratorGeneral() As IEnumerator Implements IEnumerable.GetEnumerator
+ SyncLock SyncRoot
+ Return Items.ToList.GetEnumerator()
+ End SyncLock
+ End Function
+ End Class
+
+ '''
+ ''' 线程安全的,可以直接使用 For Each 的字典。
+ ''' 在使用 For Each 循环时,字典的结果可能并非最新,但不会抛出异常。
+ '''
+ Public Class SafeDictionary(Of TKey, TValue)
+ Implements IDictionary(Of TKey, TValue)
+ Implements IEnumerable(Of KeyValuePair(Of TKey, TValue))
+
+ Private ReadOnly SyncRoot As New Object
+ Private ReadOnly _Dictionary As New Dictionary(Of TKey, TValue)
+
+ '构造函数
+ Public Sub New()
+ End Sub
+ 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
+
+ '线程安全的方法实现
+ Public Sub Add(key As TKey, value As TValue) Implements IDictionary(Of TKey, TValue).Add
+ SyncLock SyncRoot
+ _Dictionary.Add(key, value)
+ End SyncLock
+ End Sub
+ Public Function ContainsKey(key As TKey) As Boolean Implements IDictionary(Of TKey, TValue).ContainsKey
+ SyncLock SyncRoot
+ Return _Dictionary.ContainsKey(key)
+ End SyncLock
+ End Function
+ Public ReadOnly Property Keys As ICollection(Of TKey) Implements IDictionary(Of TKey, TValue).Keys
+ Get
+ SyncLock SyncRoot
+ Return New List(Of TKey)(_Dictionary.Keys)
+ End SyncLock
+ End Get
+ End Property
+ Public Function Remove(key As TKey) As Boolean Implements IDictionary(Of TKey, TValue).Remove
+ SyncLock SyncRoot
+ Return _Dictionary.Remove(key)
+ End SyncLock
+ End Function
+ Public Function TryGetValue(key As TKey, ByRef value As TValue) As Boolean Implements IDictionary(Of TKey, TValue).TryGetValue
+ SyncLock SyncRoot
+ Return _Dictionary.TryGetValue(key, value)
+ End SyncLock
+ End Function
+ Public ReadOnly Property Values As ICollection(Of TValue) Implements IDictionary(Of TKey, TValue).Values
+ Get
+ SyncLock SyncRoot
+ Return New List(Of TValue)(_Dictionary.Values)
+ End SyncLock
+ End Get
+ End Property
+ Default Public Property Item(key As TKey) As TValue Implements IDictionary(Of TKey, TValue).Item
+ Get
+ SyncLock SyncRoot
+ Return _Dictionary(key)
+ End SyncLock
+ End Get
+ Set(value As TValue)
+ SyncLock SyncRoot
+ _Dictionary(key) = value
+ End SyncLock
+ End Set
+ End Property
+ Public Sub Add(item As KeyValuePair(Of TKey, TValue)) Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Add
+ SyncLock SyncRoot
+ _Dictionary.Add(item.Key, item.Value)
+ End SyncLock
+ End Sub
+ Public Sub Clear() Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Clear
+ SyncLock SyncRoot
+ _Dictionary.Clear()
+ End SyncLock
+ End Sub
+ Public Function Contains(item As KeyValuePair(Of TKey, TValue)) As Boolean Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Contains
+ SyncLock SyncRoot
+ Return DirectCast(_Dictionary, IDictionary(Of TKey, TValue)).Contains(item)
+ End SyncLock
+ End Function
+ Public Sub CopyTo(array() As KeyValuePair(Of TKey, TValue), arrayIndex As Integer) Implements ICollection(Of KeyValuePair(Of TKey, TValue)).CopyTo
+ SyncLock SyncRoot
+ DirectCast(_Dictionary, IDictionary(Of TKey, TValue)).CopyTo(array, arrayIndex)
+ End SyncLock
+ End Sub
+ Public ReadOnly Property Count As Integer Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Count
+ Get
+ SyncLock SyncRoot
+ Return _Dictionary.Count
+ End SyncLock
+ End Get
+ End Property
+ Public ReadOnly Property IsReadOnly As Boolean Implements ICollection(Of KeyValuePair(Of TKey, TValue)).IsReadOnly
+ Get
+ Return False
+ End Get
+ End Property
+ Public Function Remove(item As KeyValuePair(Of TKey, TValue)) As Boolean Implements ICollection(Of KeyValuePair(Of TKey, TValue)).Remove
+ SyncLock SyncRoot
+ Return DirectCast(_Dictionary, IDictionary(Of TKey, TValue)).Remove(item)
+ End SyncLock
+ End Function
+
+ '枚举器
+ Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of TKey, TValue)) Implements IEnumerable(Of KeyValuePair(Of TKey, TValue)).GetEnumerator
+ SyncLock SyncRoot
+ Return New List(Of KeyValuePair(Of TKey, TValue))(_Dictionary).GetEnumerator()
+ End SyncLock
+ End Function
+ Private Function GetEnumeratorGeneral() As IEnumerator Implements IEnumerable.GetEnumerator
+ Return GetEnumerator()
+ End Function
+ End Class
+
+ '''
+ ''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。
+ '''
+ Public PathPure As String = GetPureASCIIDir()
+ Private Function GetPureASCIIDir() As String
+ If (Path & "PCL").IsASCII() Then
+ Return Path & "PCL\"
+ ElseIf PathAppdata.IsASCII() Then
+ Return PathAppdata
+ ElseIf PathTemp.IsASCII() Then
+ Return PathTemp
+ Else
+ Return OsDrive & "ProgramData\PCL\"
+ End If
+ End Function
+
+ '''
+ ''' 指示接取到这个异常的函数进行重试。
+ '''
+ Public Class RestartException
+ Inherits Exception
+ End Class
+ '''
+ ''' 指示用户手动取消了操作,或用户已知晓操作被取消的原因。
+ '''
+ Public Class CancelledException
+ Inherits Exception
+ End Class
+
'''
''' 当前程序是否拥有管理员权限。
'''
@@ -1851,6 +2177,13 @@ RetryDir:
Return NewProcess.ExitCode
End Function
+ '''
+ ''' 判断当前系统语言是否为 zh-CN。
+ '''
+ Public Function IsSystemLanguageChinese() As Boolean
+ Return CultureInfo.CurrentCulture.Name = "zh-CN" OrElse CultureInfo.CurrentUICulture.Name = "zh-CN"
+ End Function
+
Private Uuid As Integer = 1
Private UuidLock As Object
'''
@@ -1880,7 +2213,7 @@ RetryDir:
'''
''' 数组去重。
'''
- Public Function Distinct(Of T)(Arr As ICollection(Of T), IsEqual As CompareThreadStart(Of T)) As List(Of T)
+ Public Function Distinct(Of T)(Arr As ICollection(Of T), IsEqual As ComparisonBoolean(Of T)) As List(Of T)
Dim ResultArray As New List(Of T)
For i = 0 To Arr.Count - 1
For ii = i + 1 To Arr.Count - 1
@@ -1942,10 +2275,10 @@ NextElement:
'12~60 月,“1 年 2 个月”
GetTimeSpanString = Math.Floor(TotalMonthes / 12) & " 年" & If((TotalMonthes Mod 12) > 0, " " & (TotalMonthes Mod 12) & " 个月", "")
ElseIf TotalMonthes >= 4 Then
- '4~11 月,“5 月”
- GetTimeSpanString = TotalMonthes & " 月"
+ '4~11 月,“5 个月”
+ GetTimeSpanString = TotalMonthes & " 个月"
ElseIf TotalMonthes >= 1 Then
- '1~4 月,“2 月 13 天”
+ '1~4 月,“2 个月 13 天”
GetTimeSpanString = TotalMonthes & " 月" & If((Span.Days Mod 30) > 0, " " & (Span.Days Mod 30) & " 天", "")
ElseIf Span.TotalDays >= 4 Then
'4~30 天,“23 天”
@@ -2000,6 +2333,7 @@ NextElement:
''' 文件名。可以为“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
@@ -2013,7 +2347,7 @@ NextElement:
''' 文件名。可以为“notepad”等缩写。
''' 运行参数。
''' 等待该程序结束的最长时间(毫秒)。超时会返回 Result.Timeout。
- Public Function ShellAndGetExitCode(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000) As Result
+ 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
@@ -2023,12 +2357,12 @@ NextElement:
If Program.WaitForExit(Timeout) Then
Return Program.ExitCode
Else
- Return Result.Timeout
+ Return ProcessReturnValues.Timeout
End If
End Using
Catch ex As Exception
Log(ex, "执行命令失败:" & FileName, LogLevel.Msgbox)
- Return Result.Fail
+ Return ProcessReturnValues.Fail
End Try
End Function
'''
@@ -2045,7 +2379,7 @@ NextElement:
.CreateNoWindow = True,
.RedirectStandardError = True,
.RedirectStandardOutput = True,
- .WorkingDirectory = If(WorkingDirectory, Path.TrimEnd("\"c))
+ .WorkingDirectory = ShortenPath(If(WorkingDirectory, Path.TrimEnd("\"c)))
}
If WorkingDirectory IsNot Nothing Then
If Info.EnvironmentVariables.ContainsKey("appdata") Then
@@ -2067,7 +2401,7 @@ NextElement:
'''
''' 在新的工作线程中执行代码。
'''
- Public Function RunInNewThread(Action As Action, Name As String, Optional Priority As ThreadPriority = ThreadPriority.Normal) As Thread
+ Public Function RunInNewThread(Action As Action, Optional Name As String = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal) As Thread
Dim th As New Thread(
Sub()
Try
@@ -2077,7 +2411,7 @@ NextElement:
Catch ex As Exception
Log(ex, Name & ":线程执行失败", LogLevel.Feedback)
End Try
- End Sub) With {.Name = Name, .Priority = Priority}
+ End Sub) With {.Name = If(Name, "Runtime New Invoke " & GetUuid() & "#"), .Priority = Priority}
th.Start()
Return th
End Function
@@ -2131,9 +2465,9 @@ NextElement:
'''
''' 按照既定的函数进行选择排序。
+ ''' 传入两个对象,若第一个对象应该排在前面,则返回 True。
'''
- ''' 传入两个对象,若第一个对象应该排在前面,则返回 True。
- Public Function Sort(Of T)(List As IList(Of T), SortRule As CompareThreadStart(Of T)) As List(Of T)
+ Public Function Sort(Of T)(List As IList(Of T), SortRule As ComparisonBoolean(Of T)) As List(Of T)
Dim NewList As New List(Of T)
While List.Any
Dim Highest = List(0)
@@ -2145,7 +2479,7 @@ NextElement:
End While
Return NewList
End Function
- Public Delegate Function CompareThreadStart(Of T)(Left As T, Right As T) As Boolean
+ Public Delegate Function ComparisonBoolean(Of T)(Left As T, Right As T) As Boolean
'''
''' 返回列表的浅表副本。
@@ -2154,6 +2488,27 @@ NextElement:
Return New List(Of T)(list)
End Function
+ '''
+ ''' 尝试从字典中获取某项,如果该项不存在,则返回默认值。
+ '''
+ 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
+ End Function
+ '''
+ ''' 将某项添加到以列表作为值的字典中。
+ '''
+ Public Sub AddToList(Of TKey, TValue)(Dict As Dictionary(Of TKey, List(Of TValue)), Key As TKey, Value As TValue)
+ If Dict.ContainsKey(Key) Then
+ Dict(Key).Add(Value)
+ Else
+ Dict.Add(Key, New List(Of TValue) From {Value})
+ End If
+ End Sub
+
'''
''' 获取程序启动参数。
'''
@@ -2203,11 +2558,18 @@ NextElement:
End Try
End Sub
'''
- ''' 打开 explorer。注意参数中的路径要尽量加上双引号!
+ ''' 打开 explorer。
+ ''' 若不以 \ 结尾,则将视作文件路径,打开并选中此文件。
'''
- Public Sub OpenExplorer(Argument As String)
+ Public Sub OpenExplorer(Location As String)
Try
- ShellOnly("explorer.exe", Argument)
+ Location = ShortenPath(Location.Replace("/", "\").Trim(" "c, """"c))
+ Log("[System] 正在打开资源管理器:" & Location)
+ If Location.EndsWith("\") Then
+ ShellOnly(Location)
+ Else
+ ShellOnly("explorer", $"/select,""{Location}""")
+ End If
Catch ex As Exception
Log(ex, "打开资源管理器失败,请尝试关闭安全软件(如 360 安全卫士)", LogLevel.Msgbox)
End Try
@@ -2387,13 +2749,27 @@ Retry:
'''
''' 将 XML 转换为对应 UI 对象。
'''
- Public Function GetObjectFromXML(Str As String)
- Using Stream As New MemoryStream
+ Public Function GetObjectFromXML(Str As String) As Object
+ Using Stream As New MemoryStream(Encoding.UTF8.GetBytes(Str))
+ '类型检查
+ 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} 值。")
+ Next
+ For Each BlackListMember In {"Code", "FactoryMethod", "Static"}
+ If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListMember} 成员。")
+ Next
+ End While
+ End Using
+ '实际的加载
+ Stream.Position = 0
Using Writer As New StreamWriter(Stream)
Writer.Write(Str)
Writer.Flush()
Stream.Position = 0
- Return XamlReader.Load(Stream)
+ Return Markup.XamlReader.Load(Stream)
End Using
End Using
End Function
@@ -2416,6 +2792,25 @@ Retry:
Return rect.Contains(bounds.TopLeft) OrElse rect.Contains(bounds.BottomRight)
End Function
+ '''
+ ''' 控件是否受到 TextTrimming 属性影响,导致内容被截取。
+ '''
+ Public Function IsTextTrimmed(Control As TextBlock) As Boolean
+ Control.Measure(New Size(Double.MaxValue, Double.MaxValue))
+ Return Control.DesiredSize.Width > Control.ActualWidth
+ End Function
+
+ '''
+ ''' 获取文本在被渲染后的宽度。
+ '''
+ Public Function MeasureStringWidth(text As String, Optional fontSize As Double = 14, Optional fontFamily As FontFamily = Nothing) As Double
+ If fontFamily Is Nothing Then fontFamily = New FontFamily("微软雅黑")
+ Dim formattedText = New FormattedText(
+ text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, New Typeface(fontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
+ fontSize, Brushes.Black, New NumberSubstitution(), TextFormattingMode.Display, New DpiScale(1, 1).PixelsPerDip)
+ Return formattedText.Width
+ End Function
+
#End Region
#Region "Debug"
@@ -2534,7 +2929,7 @@ Retry:
If IsProgramEnded OrElse Level = LogLevel.Normal Then Exit Sub
'去除前缀
- Text = RegexReplace(Text, "", "\[[^\]]+?\] ")
+ Text = Text.RegexReplace("\[[^\]]+?\] ", "")
'输出提示
Select Case Level
@@ -2568,9 +2963,9 @@ Retry:
If GetTimeTick() - Time < 1500 Then
'弹窗无法保留
Log("[System] PCL 已崩溃:" & vbCrLf & Text)
- FormMain.EndProgramForce(Result.Exception)
+ FormMain.EndProgramForce(ProcessReturnValues.Exception)
Else
- FormMain.EndProgramForce(Result.Fail)
+ FormMain.EndProgramForce(ProcessReturnValues.Fail)
End If
End Select
@@ -2637,9 +3032,9 @@ Retry:
If GetTimeTick() - Time < 1500 Then
'弹窗无法保留
Log("[System] PCL 已崩溃:" & vbCrLf & ExFull)
- FormMain.EndProgramForce(Result.Exception)
+ FormMain.EndProgramForce(ProcessReturnValues.Exception)
Else
- FormMain.EndProgramForce(Result.Fail)
+ FormMain.EndProgramForce(ProcessReturnValues.Fail)
End If
End Select
@@ -2650,13 +3045,17 @@ Retry:
On Error Resume Next
FeedbackInfo()
If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then
- OpenExplorer("""" & Path & "PCL\""")
+ OpenExplorer(Path & "PCL\Log1.txt")
End If
OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/")
End Sub
Public Function CanFeedback(ShowHint As Boolean) As Boolean
If False.Equals(PageSetupSystem.IsLauncherNewest) Then
- If ShowHint Then MyMsgBox("你的 PCL 不是最新版,因此无法提交反馈。" & vbCrLf & "请先在 设置 → 启动器 中更新启动器,确认该问题在最新版中依然存在,然后再提交反馈。", "无法提交反馈")
+ If ShowHint Then
+ If MyMsgBox($"你的 PCL 不是最新版,因此无法提交反馈。{vbCrLf}请在更新后,确认该问题在最新版中依然存在,然后再提交反馈。", "无法提交反馈", "更新", "取消") = 1 Then
+ UpdateCheckByButton()
+ End If
+ End If
Return False
Else
Return True
@@ -2722,4 +3121,88 @@ Retry:
#End Region
-End Module
\ No newline at end of file
+End Module
+
+#Region "WPF"
+
+'''
+''' 对数据绑定进行加法运算,使用参数决定加数。
+'''
+Public Class AdditionConverter
+ Implements IValueConverter
+ Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
+ If value Is Nothing Then Return 0
+ Dim before As Double
+ If Not Double.TryParse(value.ToString(), before) Then Return 0
+ Dim scale As Double = 1
+ If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
+ Return before + scale
+ End Function
+ Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
+ If value Is Nothing Then Return Binding.DoNothing
+ Dim before As Double
+ If Not Double.TryParse(value.ToString(), before) Then Return Binding.DoNothing
+ Dim scale As Double = 1
+ If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
+ If scale = 0 Then Return Binding.DoNothing
+ Return before - scale
+ End Function
+End Class
+
+'''
+''' 对数据绑定进行乘法运算,使用参数决定乘数。
+'''
+Public Class MultiplicationConverter
+ Implements IValueConverter
+ Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
+ If value Is Nothing Then Return 0
+ Dim before As Double
+ If Not Double.TryParse(value.ToString(), before) Then Return 0
+ Dim scale As Double = 1
+ If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
+ Return before * scale
+ End Function
+ Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
+ If value Is Nothing Then Return Binding.DoNothing
+ Dim before As Double
+ If Not Double.TryParse(value.ToString(), before) Then Return Binding.DoNothing
+ Dim scale As Double = 1
+ If parameter IsNot Nothing Then Double.TryParse(parameter.ToString(), scale)
+ If scale = 0 Then Return Binding.DoNothing
+ Return before / scale
+ End Function
+End Class
+
+'''
+''' 将取反的 Boolean 绑定到 Visibility。
+'''
+Public Class InverseBooleanToVisibilityConverter
+ Implements IValueConverter
+ Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
+ If value Is Nothing Then Return Visibility.Visible
+ Dim boolValue As Boolean
+ Return If(Boolean.TryParse(value.ToString(), boolValue), If(boolValue, Visibility.Collapsed, Visibility.Visible), Visibility.Visible)
+ End Function
+ Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
+ If value Is Nothing Then Return False
+ Return If(TypeOf value Is Visibility, value <> Visibility.Visible, False)
+ End Function
+End Class
+
+'''
+''' 将 Boolean 取反。
+'''
+Public Class InverseBooleanConverter
+ Implements IValueConverter
+ Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
+ If value Is Nothing Then Return False
+ Dim boolValue As Boolean
+ Return If(Boolean.TryParse(value.ToString(), boolValue), Not boolValue, False)
+ End Function
+ Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
+ If value Is Nothing Then Return False
+ Return If(Boolean.TryParse(value.ToString(), value), Not value, False)
+ End Function
+End Class
+
+#End Region
diff --git a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb
index 05596a2..a408f14 100644
--- a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb
+++ b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb
@@ -91,17 +91,18 @@
_State = value
Log("[Loader] 加载器 " & Name & " 状态改变:" & GetStringFromEnum(value))
'实现 ILoadingTrigger 接口与 OnStateChanged 回调
- RunInUi(Sub()
- Select Case value
- Case LoadState.Loading
- LoadingState = MyLoading.MyLoadingState.Run
- Case LoadState.Failed
- LoadingState = MyLoading.MyLoadingState.Error
- Case Else
- LoadingState = MyLoading.MyLoadingState.Stop
- End Select
- RaiseEvent OnStateChangedUi(Me, value, OldState)
- End Sub)
+ RunInUi(
+ Sub()
+ Select Case value
+ Case LoadState.Loading
+ LoadingState = MyLoading.MyLoadingState.Run
+ Case LoadState.Failed
+ LoadingState = MyLoading.MyLoadingState.Error
+ Case Else
+ LoadingState = MyLoading.MyLoadingState.Stop
+ End Select
+ RaiseEvent OnStateChangedUi(Me, value, OldState)
+ End Sub)
If HasOnStateChangedThread Then RunInThread(Sub() RaiseEvent OnStateChangedThread(Me, value, OldState))
End Set
End Property
@@ -287,15 +288,13 @@
End SyncLock
End Try
'检验输入以确定情况
- If (Not IsForceRestart) AndAlso '不要求强行重启
- ((Input IsNot Nothing AndAlso Input.Equals(Me.Input)) OrElse (Input Is Nothing AndAlso Me.Input Is Nothing)) AndAlso '输入相同
- (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已加载成功
+ 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
+ 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)
@@ -316,23 +315,26 @@
Sub()
Try
IsForceRestarting = IsForceRestart
- If ModeDebug Then Log("[Loader] 加载线程 " & Name & " (" & Thread.CurrentThread.ManagedThreadId & ") 已启动")
+ 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)
+ 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 & ") 已触发线程中断")
+ 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 & ") 发生运行时错误", LogLevel.Developer)
+ Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 出错,已完成 {Math.Round(Progress * 100)}%", LogLevel.Developer)
[Error] = ex
State = LoadState.Failed
End Try
@@ -348,7 +350,7 @@
End Sub
Private Sub TriggerThreadAbort()
If LastRunningThread Is Nothing Then Exit Sub
- If ModeDebug Then Log("[Loader] 加载线程 " & Name & " (" & LastRunningThread.ManagedThreadId & ") 已中断")
+ If ModeDebug Then Log($"[Loader] 加载线程 {Name} ({LastRunningThread.ManagedThreadId}) 已中断")
If LastRunningThread.IsAlive Then LastRunningThread.Interrupt()
LastRunningThread = Nothing
End Sub
@@ -440,12 +442,13 @@
Exit Sub
End If
End SyncLock
- RunInThread(Sub()
- '中断加载器
- For Each Loader In Loaders
- Loader.Abort()
- Next
- End Sub)
+ RunInThread(
+ Sub()
+ '中断加载器
+ For Each Loader In Loaders
+ Loader.Abort()
+ Next
+ End Sub)
End Sub
'''
@@ -484,7 +487,7 @@
Private Sub Update()
If State = LoadState.Finished OrElse State = LoadState.Failed OrElse State = LoadState.Aborted Then Exit Sub
Dim IsFinished As Boolean = True
- Dim Ignore As Boolean = False
+ Dim Blocked As Boolean = False
Dim Input As Object = Me.Input
For Each Loader In Loaders
Select Case Loader.State
@@ -492,28 +495,30 @@
'检查是否需要重启
If Loader.GetType.Name.StartsWithF("LoaderTask") Then '类型名后面带有泛型,必须用 StartsWith
If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
- If ModeDebug Then Log("[Loader] 由于输入条件变更,重启已完成的加载器 " & Loader.Name)
+ Log("[Loader] 由于输入条件变更,重启已完成的加载器 " & Loader.Name)
GoTo Restart
End If
'更新下一个加载器的输入
Input = CType(Loader, Object).Output
End If
+ '如果不让继续启动,且已有加载器正在加载中,就不继续启动
+ If Loader.Block AndAlso Not IsFinished Then Blocked = True
Case LoadState.Loading
'检查是否需要重启
If Loader.GetType.Name.StartsWithF("LoaderTask") Then
If CType(Loader, Object).ShouldStart(If(Input IsNot Nothing AndAlso Loader.GetType.GenericTypeArguments.First Is Input.GetType, Input, Nothing), IgnoreReloadTimeout:=True) Then
- If ModeDebug Then Log("[Loader] 由于输入条件变更,重启进行中的加载器 " & Loader.Name, LogLevel.Developer)
+ Log("[Loader] 由于输入条件变更,重启进行中的加载器 " & Loader.Name, LogLevel.Developer)
GoTo Restart
End If
End If
'已经有正在加载中的了,不需要再启动了
IsFinished = False
- Ignore = True
+ Blocked = True
Case Else
Restart:
'未启动,则启动加载器
IsFinished = False
- If Ignore Then Continue For
+ If Blocked Then Continue For
If Input IsNot Nothing Then
'若输入类型与下一个加载器相同才继续
Dim LoaderType As String = Loader.GetType.Name
@@ -527,8 +532,8 @@ Restart:
Else
Loader.Start(IsForceRestart:=IsForceRestarting)
End If
- '如果不让继续启动,那就不启动呗
- If Loader.Block Then Ignore = True
+ '阻止继续
+ If Loader.Block Then Blocked = True
End Select
Next
If IsFinished Then
@@ -566,7 +571,7 @@ Restart:
End Class
'任务栏进度条
- Public LoaderTaskbar As New Concurrent.ConcurrentBag(Of LoaderBase)
+ Public LoaderTaskbar As New SafeList(Of LoaderBase)
Public LoaderTaskbarProgress As Double = 0 '平滑后的进度
Private LoaderTaskbarProgressLast As Shell.TaskbarItemProgressState = Shell.TaskbarItemProgressState.None
@@ -582,14 +587,14 @@ Restart:
'检查任务是否完成,若完成则移除
'外显任务是否已经全部完成
Dim IsAllDownloadTaskCompleted As Boolean = True
- For Each Loader In LoaderTaskbar
+ 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)
- LoaderTaskbar.TryTake(Task)
+ LoaderTaskbar.Remove(Task)
Log($"[Taskbar] {Task.Name} 已移出任务列表")
End If
Next
diff --git a/Plain Craft Launcher 2/Modules/Base/ModNet.vb b/Plain Craft Launcher 2/Modules/Base/ModNet.vb
index b2775a3..ba0e019 100644
--- a/Plain Craft Launcher 2/Modules/Base/ModNet.vb
+++ b/Plain Craft Launcher 2/Modules/Base/ModNet.vb
@@ -1,6 +1,4 @@
-Imports System.Net
-
-Public Module ModNet
+Public Module ModNet
Public Const NetDownloadEnd As String = ".PCLDownloading"
'''
@@ -140,20 +138,21 @@ Retry:
'''
''' 网页的 Url。
''' 网页的编码,通常为 UTF-8。
- Public Function NetGetCodeByRequestMulty(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", Optional IsJson As Boolean = False)
+ Public Function NetGetCodeByRequestMultiple(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", Optional IsJson As Boolean = False)
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
- Dim th As New Thread(Sub()
- Try
- RequestResult = NetGetCodeByRequestOnce(Url, Encode, 30000, IsJson, Accept)
- Catch ex As Exception
- FailCount += 1
- RequestEx = ex
- End Try
- End Sub)
+ Dim th As New Thread(
+ Sub()
+ Try
+ RequestResult = NetGetCodeByRequestOnce(Url, Encode, 30000, IsJson, Accept)
+ Catch ex As Exception
+ FailCount += 1
+ RequestEx = ex
+ End Try
+ End Sub)
th.Start()
Threads.Add(th)
Thread.Sleep(i * 250)
@@ -220,12 +219,12 @@ RequestFinished:
''' 以多线程下载网页文件的方式获取网页源代码。
'''
''' 网页的 Url。
- Public Function NetGetCodeByDownload(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
- Dim Temp As String = PathTemp & "Cache\Code\" & Url.GetHashCode() & "_" & GetUuid()
+ 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 & ")")
- NetGetCodeByDownload = ReadFile(Temp)
+ NetGetCodeByLoader = ReadFile(Temp)
File.Delete(Temp)
Finally
NewTask.Abort()
@@ -235,12 +234,12 @@ RequestFinished:
''' 以多线程下载网页文件的方式获取网页源代码。
'''
''' 网页的 Url 列表。
- Public Function NetGetCodeByDownload(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
- Dim Temp As String = PathTemp & "Cache\Code\" & Urls.First.GetHashCode() & "_" & GetUuid()
+ 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
+ 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)})
Try
NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(第一下载源:" & Urls.First & ")")
- NetGetCodeByDownload = ReadFile(Temp)
+ NetGetCodeByLoader = ReadFile(Temp)
File.Delete(Temp)
Finally
NewTask.Abort()
@@ -248,13 +247,12 @@ RequestFinished:
End Function
'''
- ''' 从网络中直接下载文件。这不能下载 CDN 中的文件。
+ ''' 使用 WebClient 从网络中下载文件。这不能下载 CDN 中的文件。
'''
''' 网络 Url。
''' 下载的本地地址。
- Public Sub NetDownload(Url As String, LocalFile As String, Optional UseBrowserUserAgent As Boolean = False)
+ Public Sub NetDownloadByClient(Url As String, LocalFile As String, Optional UseBrowserUserAgent As Boolean = False)
Log("[Net] 直接下载文件:" & Url)
-
'初始化
Try
'建立目录
@@ -262,9 +260,8 @@ RequestFinished:
'尝试删除原文件
File.Delete(LocalFile)
Catch ex As Exception
- Throw New WebException("预处理下载文件路径失败(" & LocalFile & ")。", ex)
+ Throw New WebException($"预处理下载文件路径失败({LocalFile})", ex)
End Try
-
'下载
Using Client As New WebClient
Try
@@ -272,10 +269,41 @@ RequestFinished:
Client.DownloadFile(Url, LocalFile)
Catch ex As Exception
File.Delete(LocalFile)
- Throw New WebException("直接下载文件失败(" & Url & ")。", ex)
+ 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)
+ Catch ex As Exception
+ Throw New WebException($"多线程直接下载文件失败({Url})", ex)
+ Finally
+ NewTask.Abort()
+ End Try
+ End Sub
+
+ '''
+ ''' 简单的多线程下载文件。可以下载 CDN 中的文件。
+ '''
+ ''' 文件的 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)})
+ Try
+ NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress)
+ Catch ex As Exception
+ Throw New WebException($"多线程直接下载文件失败(第一下载源:" & Urls.First() & ")", ex)
+ Finally
+ NewTask.Abort()
+ End Try
End Sub
'''
@@ -329,7 +357,7 @@ Retry:
'''
''' 同时发送多个网络请求并要求返回内容。
'''
- Public Function NetRequestMulty(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)
+ 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
@@ -338,7 +366,7 @@ Retry:
Dim th As New Thread(
Sub()
Try
- RequestResult = NetRequestOnce(Url, Method, Data, ContentType, 30000, Headers)
+ RequestResult = NetRequestOnce(Url, Method, Data, ContentType, 30000, Headers, MakeLog)
Catch ex As Exception
FailCount += 1
RequestEx = ex
@@ -701,12 +729,11 @@ RequestFinished:
'''
''' 所属的文件列表任务。
'''
- Public Tasks As New Concurrent.ConcurrentBag(Of LoaderDownload)
-
+ Public Tasks As New SafeList(Of LoaderDownload)
'''
''' 所有下载源。
'''
- Public Sources As NetSource()
+ Public Sources As SafeList(Of NetSource)
'''
''' 用于在第一个线程出错时切换下载源。
'''
@@ -714,7 +741,7 @@ RequestFinished:
'''
''' 所有已经被标记为失败的,但未完整尝试过的,不允许断点续传的下载源。
'''
- Public SourcesOnce As New List(Of NetSource)
+ Public SourcesOnce As New SafeList(Of NetSource)
'''
''' 获取从某个源开始,第一个可用的源。
'''
@@ -915,7 +942,7 @@ RequestFinished:
Sources.Add(New NetSource With {.FailCount = 0, .Url = SecretCdnSign(Source.Replace(vbCr, "").Replace(vbLf, "").Trim), .Id = Count, .IsFailed = False, .Ex = Nothing})
Count += 1
Next
- Me.Sources = Sources.ToArray
+ Me.Sources = Sources
Me.LocalPath = LocalPath
Me.Check = Check
Me.UseBrowserUserAgent = UseBrowserUserAgent
@@ -931,7 +958,7 @@ RequestFinished:
'条件检测
If NetTaskThreadCount >= NetTaskThreadLimit OrElse IsSourceFailed() OrElse
- (IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing
+ (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
@@ -940,7 +967,6 @@ RequestFinished:
Dim StartPosition As Long, StartSource As NetSource = Nothing
Dim Th As Thread, ThreadInfo As NetThread
SyncLock LockChain
-
'获取线程起点与下载源
'不分割
If IsNoSplit Then GoTo Capture
@@ -976,7 +1002,8 @@ Capture:
Next
'是否禁用多线程,以及规定碎片大小
Dim TargetUrl As String = GetSource().Url
- If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("gitcode.net") OrElse TargetUrl.Contains("github.com") OrElse TargetUrl.Contains("modrinth") Then Return Nothing
+ 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
'寻找最大碎片
'FUTURE: 下载引擎重做,计算下载源平均链接时间和线程下载速度,按最高时间节省来开启多线程
Dim FilePieceMax As NetThread = Threads
@@ -1050,6 +1077,15 @@ StartThread:
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
@@ -1114,7 +1150,7 @@ NotSupportRange:
Info.Temp = Nothing
SmailFileCache = New Queue(Of Byte)
Else
- Info.Temp = PathTemp & "Download\" & Uuid & "_" & Info.Uuid & "_" & RandomInteger(0, 999999) & ".tmp"
+ Info.Temp = $"{PathTemp}Download\{Uuid}_{Info.Uuid}_{RandomInteger(0, 999999)}.tmp"
ResultStream = New FileStream(Info.Temp, FileMode.Create, FileAccess.Write, FileShare.Read)
End If
'开始下载
@@ -1134,7 +1170,7 @@ NotSupportRange:
If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
End SyncLock
Dim DeltaTime = GetTimeTick() - Info.LastReceiveTime
- If DeltaTime > 1000000 Then DeltaTime = 0 '时间刻反转导致出现极大值
+ If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值
If RealDataCount > 0 Then
'有数据
If Info.DownloadDone = 0 Then
@@ -1175,7 +1211,7 @@ NotSupportRange:
ResultStream.Write(HttpData, 0, RealDataCount)
End If
'检查速度是否过慢
- If DeltaTime > 1000 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1s,且速度小于 1K/s
+ If DeltaTime > 1500 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1.5s,且速度小于 1.5K/s
Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
End If
Info.LastReceiveTime = GetTimeTick()
@@ -1287,6 +1323,17 @@ Wrong:
If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso State < NetState.Merge Then Merge()
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
'下载文件的最终收束事件
'''
@@ -1344,7 +1391,7 @@ Retry:
If Check.ActualSize = -1 Then
Check.ActualSize = FileSize
ElseIf Check.ActualSize <> FileSize Then
- Throw New Exception("文件大小不一致:任务要求为 " & Check.ActualSize & " B,网络获取结果为 " & FileSize & "B")
+ Throw New Exception($"文件大小不一致:任务要求为 {Check.ActualSize} B,网络获取结果为 {FileSize}B")
End If
End If
'检查文件
@@ -1403,7 +1450,7 @@ Retry:
'''
Public Sub Abort(CausedByTask As LoaderDownload)
'从特定任务中移除,如果它还属于其他任务,则继续下载
- Tasks.TryTake(CausedByTask)
+ Tasks.Remove(CausedByTask)
If Tasks.Any Then Exit Sub
'确认中断
SyncLock LockState
@@ -1451,8 +1498,7 @@ Retry:
'''
''' 需要下载的文件。
'''
- Public Files As List(Of NetFile)
- Private ReadOnly FilesLock As New Object
+ Public Files As SafeList(Of NetFile)
'''
''' 剩余未完成的文件数。(用于减轻 FilesLock 的占用)
'''
@@ -1465,9 +1511,7 @@ Retry:
Public Overrides Property Progress As Double
Get
If State >= LoadState.Finished Then Return 1
- SyncLock FilesLock
- If Not Files.Any() Then Return 0 '必须返回 0,否则在获取列表的时候会错觉已经下载完了
- End SyncLock
+ If Not Files.Any() Then Return 0 '必须返回 0,否则在获取列表的时候会错觉已经下载完了
Return _Progress
End Get
Set(value As Double)
@@ -1489,16 +1533,14 @@ Retry:
Log("[Download] 由于同加载器中失败次数过多引发强制失败:连续失败了 " & value & " 次", LogLevel.Debug)
On Error Resume Next
Dim ExList As New List(Of Exception)
- SyncLock FilesLock
- For Each File In Files
- For Each Source In File.Sources
- If Source.Ex IsNot Nothing Then
- ExList.Add(Source.Ex)
- If ExList.Count > 10 Then GoTo FinishExCatch
- End If
- Next
+ For Each File In Files
+ For Each Source In File.Sources
+ If Source.Ex IsNot Nothing Then
+ ExList.Add(Source.Ex)
+ If ExList.Count > 10 Then GoTo FinishExCatch
+ End If
Next
- End SyncLock
+ Next
FinishExCatch:
OnFail(ExList)
End If
@@ -1515,46 +1557,41 @@ FinishExCatch:
'计算进度
Dim NewProgress As Double = 0
Dim TotalProgress As Double = 0
- SyncLock FilesLock
- For Each File In Files
- If File.IsCopy Then
- NewProgress += File.Progress * 0.2
- TotalProgress += 0.2
- Else
- NewProgress += File.Progress
- TotalProgress += 1
- End If
- Next
- End SyncLock
- If TotalProgress > 0 Then NewProgress /= TotalProgress
+ For Each File In Files
+ If File.IsCopy Then
+ NewProgress += File.Progress * 0.2
+ TotalProgress += 0.2
+ Else
+ NewProgress += File.Progress
+ TotalProgress += 1
+ End If
+ Next
+ If TotalProgress > 0 AndAlso Not Double.IsNaN(TotalProgress) Then NewProgress /= TotalProgress
'刷新进度
- If NewProgress < 1 AndAlso NewProgress > 0 Then NewProgress = 2 * (NewProgress ^ 3) - 3 * (NewProgress ^ 2) + 2 * NewProgress '2x^3-3x^2+2x,模拟开头和结尾更慢的情况
_Progress = NewProgress
End Sub
Public Sub New(Name As String, FileTasks As List(Of NetFile))
Me.Name = Name
- Files = FileTasks
+ Files = New SafeList(Of NetFile)(FileTasks)
End Sub
Public Overrides Sub Start(Optional Input As Object = Nothing, Optional IsForceRestart As Boolean = False)
- SyncLock FilesLock
- If Input IsNot Nothing Then Files = Input
- '去重
- Dim ResultArray As New List(Of NetFile)
- For i = 0 To Files.Count - 1
- For ii = i + 1 To Files.Count - 1
- If Files(i).LocalPath = Files(ii).LocalPath Then GoTo NextElement
- Next
- ResultArray.Add(Files(i))
+ If Input IsNot Nothing Then Files = New SafeList(Of NetFile)(Input)
+ '去重
+ Dim ResultArray As New SafeList(Of NetFile)
+ For i = 0 To Files.Count - 1
+ For ii = i + 1 To Files.Count - 1
+ If Files(i).LocalPath = Files(ii).LocalPath Then GoTo NextElement
+ Next
+ ResultArray.Add(Files(i))
NextElement:
- Next i
- Files = ResultArray
- '设置剩余文件数
- SyncLock FileRemainLock
- For Each File In Files
- If File.State <> NetState.Finish Then FileRemain += 1
- Next
- End SyncLock
+ Next
+ Files = ResultArray
+ '设置剩余文件数
+ SyncLock FileRemainLock
+ For Each File In Files
+ If File.State <> NetState.Finish Then FileRemain += 1
+ Next
End SyncLock
State = LoadState.Loading
'开始执行
@@ -1597,25 +1634,23 @@ NextElement:
Next
End If
'最多 5 个线程,最少每个线程分配 10 个文件
- SyncLock FilesLock
- Dim FilesPerThread As Integer = Math.Max(5, Files.Count / 10 + 1)
- Dim FilesInThread As New List(Of NetFile)
- For Each File In Files
- FilesInThread.Add(File)
- If FilesInThread.Count = FilesPerThread Then
- Dim FilesToRun As New List(Of NetFile)
- FilesToRun.AddRange(FilesInThread)
- RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid)
- FilesInThread.Clear()
- End If
- Next
- If FilesInThread.Any Then
+ Dim FilesPerThread As Integer = Math.Max(5, Files.Count / 10 + 1)
+ Dim FilesInThread As New List(Of NetFile)
+ For Each File In Files
+ FilesInThread.Add(File)
+ If FilesInThread.Count = FilesPerThread Then
Dim FilesToRun As New List(Of NetFile)
FilesToRun.AddRange(FilesInThread)
RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid)
FilesInThread.Clear()
End If
- End SyncLock
+ Next
+ If FilesInThread.Any Then
+ Dim FilesToRun As New List(Of NetFile)
+ FilesToRun.AddRange(FilesInThread)
+ RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid)
+ FilesInThread.Clear()
+ End If
Catch ex As Exception
OnFail(New List(Of Exception) From {ex})
End Try
@@ -1654,6 +1689,9 @@ NextElement:
'复制已存在的文件
For Each FileToken In ExistFiles
Dim File As NetFile = FileToken.Key
+ SyncLock LockState
+ If File.State > NetState.WaitForCopy Then Exit Sub
+ End SyncLock
Dim LocalPath As String = FileToken.Value
Dim RetryCount As Integer = 0
Retry:
@@ -1707,24 +1745,20 @@ Retry:
Dim UsefulExs = ExList.Where(Function(e) Not e.Message.Contains("(404)")).ToList
[Error] = If(UsefulExs.Any, UsefulExs(0), ExList(0))
'获取实际失败的文件
- SyncLock FilesLock
- 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
- Next
- End SyncLock
+ 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
+ Next
'在设置 Error 对象后再更改为失败,避免 WaitForExit 无法捕获错误
State = LoadState.Failed
End SyncLock
'中断所有文件
- SyncLock FilesLock
- For Each TaskFile In Files
- If TaskFile.State < NetState.Merge Then TaskFile.State = NetState.Error
- Next
- End SyncLock
+ For Each TaskFile In Files
+ If TaskFile.State < NetState.Merge Then TaskFile.State = NetState.Error
+ Next
'在退出同步锁后再进行日志输出
Dim ErrOutput As New List(Of String)
For Each Ex As Exception In ExList
@@ -1739,11 +1773,9 @@ Retry:
End SyncLock
Log("[Download] " & Name & " 已取消!")
'中断所有文件
- SyncLock FilesLock
- For Each TaskFile In Files
- TaskFile.Abort(Me)
- Next
- End SyncLock
+ For Each TaskFile In Files
+ TaskFile.Abort(Me)
+ Next
End Sub
End Class
@@ -1765,8 +1797,7 @@ Retry:
'''
''' 当前的所有下载任务。
'''
- Public Tasks As New List(Of LoaderDownload)
- Private ReadOnly LockTasks As New Object
+ Public Tasks As New SafeList(Of LoaderDownload)
'''
''' 已下载完成的大小。
@@ -1815,6 +1846,7 @@ Retry:
Private Sub RefreshStat()
Try
Dim DeltaTime As Long = GetTimeTick() - RefreshStatLast
+ If DeltaTime = 0 Then Return
RefreshStatLast += DeltaTime
#Region "刷新整体速度"
'计算瞬时速度
@@ -1839,11 +1871,9 @@ Retry:
End If
#End Region
#Region "刷新下载任务属性"
- SyncLock LockTasks
- For Each Task In Tasks
- Task.RefreshStat()
- Next
- End SyncLock
+ For Each Task In Tasks
+ Task.RefreshStat()
+ Next
#End Region
Catch ex As Exception
Log(ex, "刷新下载公开属性失败")
@@ -1882,7 +1912,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(40) '减少 BMCLAPI 请求频率,避免 Too Many Requests
+ If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次)
Next
'为进行中的文件追加线程
If Speed >= NetTaskSpeedLimitLow Then Continue While '下载速度足够,无需新增
@@ -1902,7 +1932,7 @@ Retry:
'新增线程
If PreparingCount > DownloadingCount Then Continue For '准备中的线程已多于下载中的线程,不再新增
Dim NewThread = File.TryBeginThread()
- If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi.") Then Thread.Sleep(40) '减少 BMCLAPI 请求频率,避免 Too Many Requests
+ If NewThread IsNot Nothing AndAlso NewThread.Source.Url.Contains("bmclapi") Then Thread.Sleep(30) '减少 BMCLAPI 请求频率(目前每分钟限制 4000 次)
Next
End While
Catch ex As Exception
@@ -1989,9 +2019,7 @@ Retry:
Task.Files(i) = File '回设
Next
End SyncLock
- SyncLock LockTasks
- Tasks.Add(Task)
- End SyncLock
+ Tasks.Add(Task)
End Sub
End Class
@@ -2000,7 +2028,7 @@ Retry:
''' 是否有正在进行中、需要在下载管理页面显示的下载任务?
'''
Public Function HasDownloadingTask(Optional IgnoreCustomDownload As Boolean = False) As Boolean
- For Each Task In LoaderTaskbar
+ For Each Task In LoaderTaskbar.ToList()
If (Task.Show AndAlso Task.State = LoadState.Loading) AndAlso
(Not IgnoreCustomDownload OrElse Not Task.Name.ToString.Contains("自定义下载")) Then
Return True
diff --git a/Plain Craft Launcher 2/Modules/Base/ModValidate.vb b/Plain Craft Launcher 2/Modules/Base/ModValidate.vb
index b7f818b..e873030 100644
--- a/Plain Craft Launcher 2/Modules/Base/ModValidate.vb
+++ b/Plain Craft Launcher 2/Modules/Base/ModValidate.vb
@@ -1,4 +1,20 @@
'提供不同的输入验证方法,名称以 Validate 开头
+Public Module ModValidate
+
+ '''
+ ''' 进行输入验证,并返回错误原因。
+ ''' 如果没有错误则返回空字符串。
+ '''
+ Public Function Validate(Text As String, ValidateRules As IEnumerable(Of Validate)) As String
+ Dim Result As String = ""
+ For Each ValidateRule As Validate In ValidateRules
+ Result = ValidateRule.Validate(Text)
+ If Result <> "" Then Return Result
+ Next
+ Return Result
+ End Function
+
+End Module
'''
''' 输入验证规则基类。要查看所有的输入验证规则,可在输入 Validate 后查看自动补全。
@@ -134,13 +150,13 @@ Public Class ValidateExcept
Public Property Excepts As ObjectModel.Collection(Of String) = New ObjectModel.Collection(Of String)
Public Property ErrorMessage As String
Public Sub New()
- ErrorMessage = "输入内容不能包含 %!"
+ ErrorMessage = "输入内容不能包含 %"
End Sub '用于 XAML 初始化
- Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能包含 %!")
+ Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能包含 %")
Me.Excepts = Excepts
Me.ErrorMessage = ErrorMessage
End Sub
- Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能包含 %!")
+ 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
@@ -169,12 +185,12 @@ Public Class ValidateExceptSame
Public Property IgnoreCase As Boolean = False
Public Sub New()
End Sub
- Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能为 %!", Optional IgnoreCase As Boolean = False)
+ Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
Me.Excepts = Excepts
Me.ErrorMessage = ErrorMessage
Me.IgnoreCase = IgnoreCase
End Sub
- Public Sub New(Excepts As IEnumerable, Optional ErrorMessage As String = "输入内容不能为 %!", Optional IgnoreCase As Boolean = False)
+ 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)
@@ -233,6 +249,8 @@ Public Class ValidateFolderName
'检查特殊字符串
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
+ '检查 NTFS 8.3 文件名(#4505)
+ If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
'检查文件夹重名
Dim Arr As New List(Of String)
If PathIgnore IsNot Nothing Then
@@ -286,6 +304,8 @@ Public Class ValidateFileName
'检查特殊字符串
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
+ '检查 NTFS 8.3 文件名(#4505)
+ If RegexCheck(Str, ".{2,}~\d") Then Return "文件名不能包含这一特殊格式!"
'检查文件重名
If ParentFolder IsNot Nothing Then
Dim DirInfo As New DirectoryInfo(ParentFolder)
@@ -351,6 +371,8 @@ Fin:
'检查特殊字符串
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"}, "文件夹名不可为 %!").Validate(SubStr)
If Not InvalidStrCheck = "" Then Return InvalidStrCheck
+ '检查 NTFS 8.3 文件名(#4505)
+ If RegexCheck(Str, ".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
Next
Return ""
End Function
diff --git a/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb b/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb
index 0b074e6..c5303d6 100644
--- a/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb
+++ b/Plain Craft Launcher 2/Modules/Base/MyBitmap.vb
@@ -7,7 +7,7 @@ Public Class MyBitmap
'''
''' 位图缓存。
'''
- Public Shared BitmapCache As New Dictionary(Of String, MyBitmap)
+ Public Shared BitmapCache As New Concurrent.ConcurrentDictionary(Of String, MyBitmap)
'''
''' 存储的图片
@@ -17,15 +17,19 @@ Public Class MyBitmap
'自动类型转换
'支持的类:Image,ImageSource,Bitmap,ImageBrush,BitmapSource
Public Shared Widening Operator CType(Image As System.Drawing.Image) As MyBitmap
+ If Image Is Nothing Then Return Nothing
Return New MyBitmap(Image)
End Operator
Public Shared Widening Operator CType(Image As MyBitmap) As System.Drawing.Image
+ If Image Is Nothing Then Return Nothing
Return Image.Pic
End Operator
Public Shared Widening Operator CType(Image As ImageSource) As MyBitmap
+ If Image Is Nothing Then Return Nothing
Return New MyBitmap(Image)
End Operator
Public Shared Widening Operator CType(Image As MyBitmap) As ImageSource
+ If Image Is Nothing Then Return Nothing
Dim Bitmap = Image.Pic
Dim rect = New System.Drawing.Rectangle(0, 0, Bitmap.Width, Bitmap.Height)
Dim bitmapData = Bitmap.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)
@@ -37,15 +41,19 @@ Public Class MyBitmap
End Try
End Operator
Public Shared Widening Operator CType(Image As System.Drawing.Bitmap) As MyBitmap
+ If Image Is Nothing Then Return Nothing
Return New MyBitmap(Image)
End Operator
Public Shared Widening Operator CType(Image As MyBitmap) As System.Drawing.Bitmap
+ If Image Is Nothing Then Return Nothing
Return Image.Pic
End Operator
Public Shared Widening Operator CType(Image As ImageBrush) As MyBitmap
+ If Image Is Nothing Then Return Nothing
Return New MyBitmap(Image)
End Operator
Public Shared Widening Operator CType(Image As MyBitmap) As ImageBrush
+ If Image Is Nothing Then Return Nothing
Return New ImageBrush(New MyBitmap(Image.Pic))
End Operator
@@ -61,23 +69,32 @@ Public Class MyBitmap
Pic = BitmapCache(FilePathOrResourceName).Pic
Else
Pic = New MyBitmap(CType((New ImageSourceConverter).ConvertFromString(FilePathOrResourceName), ImageSource))
- BitmapCache.Add(FilePathOrResourceName, Pic)
+ BitmapCache.TryAdd(FilePathOrResourceName, Pic)
End If
Else
'使用这种自己接管 FileStream 的方法加载才能解除文件占用
Using InputStream As New FileStream(FilePathOrResourceName, FileMode.Open)
- Pic = New System.Drawing.Bitmap(InputStream)
- InputStream.Dispose()
+ '判断是否为 WebP 文件头
+ Dim Header(1) As Byte
+ InputStream.Read(Header, 0, 2)
+ InputStream.Seek(0, SeekOrigin.Begin)
+ If Header(0) = 82 AndAlso Header(1) = 73 Then
+ '读取 WebP
+ Dim FileBytes(InputStream.Length - 1) As Byte
+ InputStream.Read(FileBytes, 0, FileBytes.Length)
+ Pic = WebPDecoder.DecodeFromBytes(FileBytes) '将代码隔离在另外一个类中,这样只要不走进这个分支就不会加载 Imazen.WebP.dll
+ Else
+ Pic = New System.Drawing.Bitmap(InputStream)
+ End If
End Using
End If
Catch ex As Exception
Pic = My.Application.TryFindResource(FilePathOrResourceName)
If Pic Is Nothing Then
Pic = New System.Drawing.Bitmap(1, 1)
- Log(ex, $"加载位图失败({FilePathOrResourceName})")
- Throw
+ Throw New Exception($"加载 MyBitmap 失败({FilePathOrResourceName})", ex)
Else
- Log(ex, $"指定类型有误的位图加载({FilePathOrResourceName})", LogLevel.Developer)
+ Log(ex, $"指定类型有误的 MyBitmap 加载({FilePathOrResourceName})", LogLevel.Developer)
Exit Try
End If
End Try
@@ -104,6 +121,13 @@ Public Class MyBitmap
Pic = New System.Drawing.Bitmap(MS)
End Using
End Sub
+ Private Class WebPDecoder '将代码隔离在另外一个类中,这样只要不调用这个方法就不会加载 Imazen.WebP.dll
+ Public Shared Function DecodeFromBytes(Bytes As Byte()) As System.Drawing.Bitmap
+ If Is32BitSystem Then Throw New Exception("不支持在 32 位系统下加载 WebP 图片。")
+ Dim Decoder As New Imazen.WebP.SimpleDecoder()
+ Return Decoder.DecodeFromBytes(Bytes, Bytes.Length)
+ End Function
+ End Class
'''
''' 获取裁切的图片,这个方法不会导致原对象改变且会返回一个新的对象。
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
index 9ddc09d..bcde80f 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
@@ -251,7 +251,13 @@
Website = Data("links")("websiteUrl").ToString.TrimEnd("/")
LastUpdate = Data("dateReleased") '#1194
DownloadCount = Data("downloadCount")
- If Data("logo").Count > 0 Then LogoUrl = Data("logo")("thumbnailUrl")
+ If Data("logo").Count > 0 Then
+ If Data("logo")("thumbnailUrl") Is Nothing OrElse Data("logo")("thumbnailUrl") = "" Then
+ LogoUrl = Data("logo")("url")
+ Else
+ LogoUrl = Data("logo")("thumbnailUrl")
+ End If
+ End If
'FileIndexes / GameVersions / ModLoaders
ModLoaders = New List(Of CompModLoaderType)
Dim Files As New List(Of KeyValuePair(Of Integer, List(Of String))) 'FileId, GameVersions
@@ -269,7 +275,7 @@
Next
CurseForgeFileIds = Files.Select(Function(f) f.Key).Distinct.ToList
GameVersions = Files.SelectMany(Function(f) f.Value).Where(Function(v) v.StartsWithF("1.")).
- Select(Function(v) CInt(Val(v.Split(".")(1).Before("-")))).Where(Function(v) v > 0).
+ 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
@@ -332,6 +338,24 @@
Case 6553 : Tags.Add("写实")
Case 6554 : Tags.Add("幻想")
Case 6555 : Tags.Add("原版风")
+ '资源包
+ Case 5244 : Tags.Add("字体包")
+ Case 5193 : Tags.Add("数据包")
+ Case 399 : Tags.Add("蒸汽朋克")
+ Case 396 : Tags.Add("128x")
+ Case 398 : Tags.Add("512x 或更高")
+ Case 397 : Tags.Add("256x")
+ Case 405 : Tags.Add("其他")
+ Case 395 : Tags.Add("64x")
+ Case 400 : Tags.Add("仿真")
+ Case 393 : Tags.Add("16x")
+ Case 403 : Tags.Add("传统")
+ Case 394 : Tags.Add("32x")
+ Case 404 : Tags.Add("动态效果")
+ Case 4465 : Tags.Add("模组支持")
+ Case 402 : Tags.Add("中世纪")
+ Case 401 : Tags.Add("现代")
+
End Select
Next
If Not Tags.Any() Then Tags.Add("杂项")
@@ -352,7 +376,7 @@
'搜索结果的键为 versions,获取特定工程的键为 game_versions
GameVersions = If(CType(If(Data("game_versions"), Data("versions")), JArray), New JArray).
Select(Function(v) v.ToString).Where(Function(v) v.StartsWithF("1.")).
- Select(Of Integer)(Function(v) Val(v.Split(".")(1).Before("-"))).Where(Function(v) v > 0).
+ Select(Of Integer)(Function(v) Val(v.Split(".")(1).BeforeFirst("-"))).Where(Function(v) v > 0).
Distinct.OrderByDescending(Function(v) v).ToList
'Type
Select Case Data("project_type").ToString
@@ -364,6 +388,16 @@
'Tags & ModLoaders
Tags = New List(Of String)
ModLoaders = New List(Of CompModLoaderType)
+ If Data.ContainsKey("loaders") Then
+ For Each Category In Data("loaders").Select(Function(t) t.ToString)
+ Select Case Category
+ Case "forge" : ModLoaders.Add(CompModLoaderType.Forge)
+ Case "fabric" : ModLoaders.Add(CompModLoaderType.Fabric)
+ Case "quilt" : ModLoaders.Add(CompModLoaderType.Quilt)
+ Case "neoforge" : ModLoaders.Add(CompModLoaderType.NeoForge)
+ End Select
+ Next
+ End If
For Each Category In Data("categories").Select(Function(t) t.ToString)
Select Case Category
'加载器
@@ -396,7 +430,7 @@
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("轻量")
'光影包
Case "cartoon" : Tags.Add("卡通")
@@ -425,6 +459,36 @@
Case "iris" : Tags.Add("Iris")
Case "optifine" : Tags.Add("OptiFine")
Case "vanilla" : Tags.Add("原版光影")
+ '资源包
+ Case "8x-" : Tags.Add("8x-")
+ 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("512x+")
+ Case "audio" : Tags.Add("声音")
+ Case "blocks" : Tags.Add("方块")
+ Case "combat" : Tags.Add("战斗")
+ Case "core-shaders" : Tags.Add("核心着色器")
+ Case "cursed" : Tags.Add("Cursed")
+ Case "decoration" : Tags.Add("装饰")
+ Case "entities" : Tags.Add("实体")
+ Case "environment" : Tags.Add("环境")
+ Case "equipment" : Tags.Add("装备")
+ Case "fonts" : Tags.Add("字体")
+ Case "gui" : Tags.Add("GUI")
+ Case "items" : Tags.Add("物品")
+ Case "locale" : Tags.Add("本地化")
+ Case "modded" : Tags.Add("Modded")
+ Case "models" : Tags.Add("模型")
+ Case "realistic" : Tags.Add("写实")
+ Case "simplistic" : Tags.Add("扁平")
+ Case "themed" : Tags.Add("主题")
+ Case "tweaks" : Tags.Add("优化")
+ Case "utility" : Tags.Add("实用")
+ Case "vanilla-like" : Tags.Add("类原生")
End Select
Next
If Not Tags.Any() Then Tags.Add("杂项")
@@ -453,7 +517,7 @@
Json("DownloadCount") = DownloadCount
If ModLoaders IsNot Nothing AndAlso ModLoaders.Any Then Json("ModLoaders") = New JArray(ModLoaders.Select(Function(m) CInt(m)))
Json("Tags") = New JArray(Tags)
- If LogoUrl IsNot Nothing Then Json("LogoUrl") = LogoUrl
+ If Not String.IsNullOrEmpty(LogoUrl) Then Json("LogoUrl") = LogoUrl
If GameVersions.Any Then Json("GameVersions") = New JArray(GameVersions)
Json("CacheTime") = Date.Now '用于检查缓存时间
Return Json
@@ -465,7 +529,7 @@
'获取版本描述
Dim GameVersionDescription As String
If GameVersions Is Nothing OrElse Not GameVersions.Any() Then
- GameVersionDescription = "未知"
+ GameVersionDescription = "仅快照版本" '#5412
Else
Dim SpaVersions As New List(Of String)
Dim IsOld As Boolean = False
@@ -488,14 +552,14 @@
If StartVersion = EndVersion Then
SpaVersions.Add("1." & StartVersion)
ElseIf McVersionHighest > -1 AndAlso StartVersion >= McVersionHighest Then
- If EndVersion <= 10 Then
+ If EndVersion < 10 Then
SpaVersions.Clear()
SpaVersions.Add("全版本")
Exit For
Else
SpaVersions.Add("1." & EndVersion & "+")
End If
- ElseIf EndVersion <= 10 Then
+ ElseIf EndVersion < 10 Then
SpaVersions.Add("1." & StartVersion & "-")
Exit For
ElseIf StartVersion - EndVersion = 1 Then
@@ -508,25 +572,32 @@
End If
'获取 Mod 加载器描述
Dim ModLoaderDescriptionFull As String, ModLoaderDescriptionPart As String
- Select Case ModLoaders.Count
+ Dim ModLoadersForDesc As New List(Of CompModLoaderType)(ModLoaders)
+ If Setup.Get("ToolDownloadIgnoreQuilt") Then ModLoadersForDesc.Remove(CompModLoaderType.Quilt)
+ Select Case ModLoadersForDesc.Count
Case 0
- ModLoaderDescriptionFull = "未知"
- ModLoaderDescriptionPart = ""
+ If ModLoaders.Count = 1 Then
+ ModLoaderDescriptionFull = "仅 " & ModLoaders.Single.ToString
+ ModLoaderDescriptionPart = ModLoaders.Single.ToString
+ Else
+ ModLoaderDescriptionFull = "未知"
+ ModLoaderDescriptionPart = ""
+ End If
Case 1
- ModLoaderDescriptionFull = "仅 " & ModLoaders.Single.ToString
- ModLoaderDescriptionPart = ModLoaders.Single.ToString
- Case 2, 3
- If Setup.Get("ToolDownloadIgnoreQuilt") AndAlso
- ModLoaders.Contains(CompModLoaderType.Forge) AndAlso ModLoaders.Contains(CompModLoaderType.Fabric) Then
+ ModLoaderDescriptionFull = "仅 " & ModLoadersForDesc.Single.ToString
+ ModLoaderDescriptionPart = ModLoadersForDesc.Single.ToString
+ Case Else
+ Dim MaxVersion As Integer = If(GameVersions.Any, GameVersions.Max, 99)
+ If ModLoaders.Contains(CompModLoaderType.Forge) AndAlso
+ (MaxVersion < 14 OrElse ModLoaders.Contains(CompModLoaderType.Fabric)) AndAlso
+ (MaxVersion < 20 OrElse ModLoaders.Contains(CompModLoaderType.NeoForge)) AndAlso
+ (MaxVersion < 14 OrElse ModLoaders.Contains(CompModLoaderType.Quilt) OrElse Setup.Get("ToolDownloadIgnoreQuilt")) Then
ModLoaderDescriptionFull = "任意"
ModLoaderDescriptionPart = ""
Else
- ModLoaderDescriptionFull = ModLoaders.Join(" / ")
- ModLoaderDescriptionPart = ModLoaders.Join(" / ")
+ ModLoaderDescriptionFull = ModLoadersForDesc.Join(" / ")
+ ModLoaderDescriptionPart = ModLoadersForDesc.Join(" / ")
End If
- Case Else
- ModLoaderDescriptionFull = "任意"
- ModLoaderDescriptionPart = ""
End Select
'实例化 UI
Dim NewItem As New MyCompItem With {.Tag = Me, .Logo = GetControlLogo()}
@@ -572,7 +643,7 @@
Return NewItem
End Function
Public Function GetControlLogo() As String
- If LogoUrl Is Nothing Then
+ If String.IsNullOrEmpty(LogoUrl) Then
Return PathImage & "Icons/NoIcon.png"
Else
Return LogoUrl
@@ -613,9 +684,9 @@
'有中文翻译
'尝试将文本分为三段:Title (EnglishName) - Suffix
'检查时注意 Carpet:它没有中文译名,但有 Suffix
- Title = TranslatedName.Before(" (").Before(" - ")
+ Title = TranslatedName.BeforeFirst(" (").BeforeFirst(" - ")
Dim Suffix As String = ""
- If TranslatedName.After(")").Contains(" - ") Then Suffix = TranslatedName.After(")").After(" - ")
+ If TranslatedName.AfterLast(")").Contains(" - ") Then Suffix = TranslatedName.AfterLast(")").AfterLast(" - ")
Dim EnglishName As String = TranslatedName
If Suffix <> "" Then EnglishName = EnglishName.Replace(" - " & Suffix, "")
EnglishName = EnglishName.Replace(Title, "").Trim("("c, ")"c, " "c)
@@ -679,6 +750,9 @@ NoSubtitle:
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
+ '最近更新时间差距在一周以内
+ If LastUpdate IsNot Nothing AndAlso Project.LastUpdate IsNot Nothing AndAlso
+ Math.Abs((LastUpdate - Project.LastUpdate).Value.TotalDays) > 7 Then Return False
'MCMOD 翻译名 / 原名 / 描述文本 / Slug 的英文部分相同
If TranslatedName = Project.TranslatedName OrElse
RawName = Project.RawName OrElse Description = Project.Description OrElse
@@ -787,8 +861,10 @@ NoSubtitle:
Address += "&classId=4471"
Case CompType.Shader
Address += "&classId=6552"
+ Case CompType.ResourcePack
+ Address += "&classId=12"
End Select
- Address += "&categoryId=" & If(Tag = "", "0", Tag.Before("/"))
+ Address += "&categoryId=" & If(Tag = "", "0", Tag.BeforeFirst("/"))
If ModLoader <> CompModLoaderType.Any Then Address += "&modLoaderType=" & CType(ModLoader, Integer)
If Not String.IsNullOrEmpty(GameVersion) Then Address += "&gameVersion=" & GameVersion
If Not String.IsNullOrEmpty(SearchText) Then Address += "&searchFilter=" & Net.WebUtility.UrlEncode(SearchText)
@@ -809,7 +885,7 @@ NoSubtitle:
'facets=[["categories:'game-mechanics'"],["categories:'forge'"],["versions:1.19.3"],["project_type:mod"]]
Dim Facets As New List(Of String)
Facets.Add($"[""project_type:{GetStringFromEnum(Type).ToLower}""]")
- If Not String.IsNullOrEmpty(Tag) Then Facets.Add($"[""categories:'{Tag.After("/")}'""]")
+ If Not String.IsNullOrEmpty(Tag) Then Facets.Add($"[""categories:'{Tag.AfterLast("/")}'""]")
If ModLoader <> CompModLoaderType.Any Then Facets.Add($"[""categories:'{GetStringFromEnum(ModLoader).ToLower}'""]")
If Not String.IsNullOrEmpty(GameVersion) Then Facets.Add($"[""versions:'{GameVersion}'""]")
Address += "&facets=[" & String.Join(",", Facets) & "]"
@@ -917,7 +993,7 @@ NoSubtitle:
If Not SearchResults(i).AbsoluteRight AndAlso i >= Math.Min(2, SearchResults.Count - 1) Then Exit For '把 3 个结果拼合以提高准确度
If SearchResults(i).Item.CurseForgeSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.CurseForgeSlug.Replace("-", " ").Replace("/", " ") & " "
If SearchResults(i).Item.ModrinthSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.ModrinthSlug.Replace("-", " ").Replace("/", " ") & " "
- SearchResult += SearchResults(i).Item.ChineseName.After(" (").TrimEnd(") ").Before(" - ").
+ SearchResult += SearchResults(i).Item.ChineseName.AfterLast(" (").TrimEnd(") ").BeforeFirst(" - ").
Replace(":", "").Replace("(", "").Replace(")", "").ToLower.Replace("/", " ") & " "
Next
Log("[Comp] 中文搜索原始关键词:" & SearchResult, LogLevel.Developer)
@@ -933,7 +1009,7 @@ NoSubtitle:
End If
'驼峰英文请求关键字处理
- Dim SpacedKeywords = RegexReplace(Task.Input.SearchText, "$& ", "([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)")
+ 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
@@ -980,30 +1056,30 @@ Retry:
Dim CurseForgeFailed As Boolean = False
If CurseForgeUrl IsNot Nothing Then
CurseForgeThread = RunInNewThread(
- Sub()
- Try
- '获取工程列表
- Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl)
- Dim RequestResult As JObject = DlModRequest(CurseForgeUrl, IsJson:=True)
- Task.Progress += 0.2
- Dim ProjectList As New List(Of CompProject)
- For Each JsonEntry As JObject In RequestResult("data")
- ProjectList.Add(New CompProject(JsonEntry))
- Next
- '更新结果
- SyncLock ResultsLock
- RawResults.AddRange(ProjectList)
- End SyncLock
- Storage.CurseForgeOffset += ProjectList.Count
- Storage.CurseForgeTotal = RequestResult("pagination")("totalCount").ToObject(Of Integer)
- Log($"[Comp] 从 CurseForge 获取到了 {ProjectList.Count} 个工程(已获取 {Storage.CurseForgeOffset} 个,共 {Storage.CurseForgeTotal} 个)")
- Catch ex As Exception
- Log(ex, "从 CurseForge 获取工程列表失败")
- Storage.CurseForgeTotal = -1 'Storage.CurseForgeOffset
- [Error] = ex
- CurseForgeFailed = True
- End Try
- End Sub, "CurseForge Project Request")
+ Sub()
+ Try
+ '获取工程列表
+ Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl)
+ Dim RequestResult As JObject = DlModRequest(CurseForgeUrl, IsJson:=True)
+ Task.Progress += 0.2
+ Dim ProjectList As New List(Of CompProject)
+ For Each JsonEntry As JObject In RequestResult("data")
+ ProjectList.Add(New CompProject(JsonEntry))
+ Next
+ '更新结果
+ SyncLock ResultsLock
+ RawResults.AddRange(ProjectList)
+ End SyncLock
+ Storage.CurseForgeOffset += ProjectList.Count
+ Storage.CurseForgeTotal = RequestResult("pagination")("totalCount").ToObject(Of Integer)
+ Log($"[Comp] 从 CurseForge 获取到了 {ProjectList.Count} 个工程(已获取 {Storage.CurseForgeOffset} 个,共 {Storage.CurseForgeTotal} 个)")
+ Catch ex As Exception
+ Log(ex, "从 CurseForge 获取工程列表失败")
+ Storage.CurseForgeTotal = -1 'Storage.CurseForgeOffset
+ [Error] = ex
+ CurseForgeFailed = True
+ End Try
+ End Sub, "CurseForge Project Request")
End If
'启动 Modrinth 线程
@@ -1011,32 +1087,32 @@ Retry:
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)
- Task.Progress += 0.2
- Dim ProjectList As New List(Of CompProject)
- For Each JsonEntry As JObject In RequestResult("hits")
- ProjectList.Add(New CompProject(JsonEntry))
+ Sub()
+ Try
+ Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl)
+ Dim RequestResult As JObject = DlModRequest(ModrinthUrl, IsJson:=True)
+ Task.Progress += 0.2
+ Dim ProjectList As New List(Of CompProject)
+ For Each JsonEntry As JObject In RequestResult("hits")
+ ProjectList.Add(New CompProject(JsonEntry))
+ Next
+ '更新结果
+ 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
- '更新结果
- 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
- Storage.ModrinthOffset += ProjectList.Count
- Storage.ModrinthTotal = RequestResult("total_hits").ToObject(Of Integer)
- Log($"[Comp] 从 Modrinth 获取到了 {ProjectList.Count} 个工程(已获取 {Storage.ModrinthOffset} 个,共 {Storage.ModrinthTotal} 个)")
- Catch ex As Exception
- Log(ex, "从 Modrinth 获取工程列表失败")
- Storage.ModrinthTotal = -1 'Storage.ModrinthOffset
- [Error] = ex
- ModrinthFailed = True
- End Try
- End Sub, "Modrinth Project Request")
+ End SyncLock
+ Storage.ModrinthOffset += ProjectList.Count
+ Storage.ModrinthTotal = RequestResult("total_hits").ToObject(Of Integer)
+ Log($"[Comp] 从 Modrinth 获取到了 {ProjectList.Count} 个工程(已获取 {Storage.ModrinthOffset} 个,共 {Storage.ModrinthTotal} 个)")
+ Catch ex As Exception
+ Log(ex, "从 Modrinth 获取工程列表失败")
+ Storage.ModrinthTotal = -1 'Storage.ModrinthOffset
+ [Error] = ex
+ ModrinthFailed = True
+ End Try
+ End Sub, "Modrinth Project Request")
End If
'等待线程结束
@@ -1080,7 +1156,7 @@ Retry:
#Region "提取非重复项,存储于 RealResults"
'将 Modrinth 排在 CurseForge 的前面,避免加载结束顺序不同导致排名不同
- '这样做的话,去重后将优先保留 CurseForge 内容(考虑到 CurseForge 热度更高)
+ '这样做的话,去重后将优先保留 CurseForge 内容
RawResults = RawResults.Where(Function(x) Not x.FromCurseForge).Concat(RawResults.Where(Function(x) x.FromCurseForge)).ToList
'RawResults 去重
RawResults = RawResults.Distinct(Function(a, b) a.IsLike(b))
@@ -1097,7 +1173,7 @@ Retry:
If RealResults.Count + Storage.Results.Count < Task.Input.TargetResultCount Then
Log($"[Comp] 总结果数需求最少 {Task.Input.TargetResultCount} 个,仅获得了 {RealResults.Count + Storage.Results.Count} 个")
- If Task.Input.CanContinue Then
+ If Task.Input.CanContinue AndAlso [Error] Is Nothing Then '如果有下载源失败则不再重试,这时候重试可能导致无限循环
Log("[Comp] 将继续尝试加载下一页")
GoTo Retry
Else
@@ -1113,7 +1189,7 @@ Retry:
If String.IsNullOrEmpty(Task.Input.SearchText) Then
'如果没有搜索文本,按下载量将结果排序
For Each Result As CompProject In RealResults
- Scores.Add(Result, Result.DownloadCount * If(Result.FromCurseForge, 1, 10))
+ Scores.Add(Result, Result.DownloadCount * If(Result.FromCurseForge, 1, 5))
Next
Else
'如果有搜索文本,按关联度将结果排序
@@ -1121,7 +1197,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, 10)) / 9)
+ Math.Log10(Math.Max(Result.DownloadCount, 1) * If(Result.FromCurseForge, 1, 5)) / 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)}})
@@ -1133,7 +1209,7 @@ Retry:
End If
'根据排序分得出结果并添加
Storage.Results.AddRange(
- Sort(Scores.ToList, Function(a, b) a.Value > b.Value).Select(Function(r) r.Key).ToList)
+ Scores.OrderByDescending(Function(s) s.Value).Select(Function(r) r.Key))
#End Region
@@ -1186,7 +1262,7 @@ Retry:
'''
Public ReadOnly ModLoaders As List(Of CompModLoaderType)
'''
- ''' 支持的游戏版本列表。类型包括:"1.18.5","1.18","1.18 快照","21w15a","未知版本"。
+ ''' 支持的游戏版本列表。类型包括:"1.18.5","1.18","1.18 预览版","21w15a","未知版本"。
'''
Public ReadOnly GameVersions As List(Of String)
'''
@@ -1287,10 +1363,7 @@ Retry:
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 = (New List(Of String) From {Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge", "://media"),
- Url.Replace("-service.overwolf.wtf", ".forgecdn.net"),
- Url.Replace("://edge", "://media"),
- Url}).Distinct.ToList '对脑残 CurseForge 的下载地址进行多种修正
+ DownloadUrls = HandleCurseForgeDownloadUrls(Url) '对脑残 CurseForge 的下载地址进行多种修正
DownloadUrls.AddRange(DownloadUrls.Select(Function(u) DlSourceModGet(u)).ToList) '添加镜像源,这个写法是为了让镜像源排在后面
DownloadUrls = DownloadUrls.Distinct.ToList '最终去重
'Dependencies
@@ -1302,9 +1375,9 @@ Retry:
End If
'GameVersions
Dim RawVersions As List(Of String) = Data("gameVersions").Select(Function(t) t.ToString.Trim.ToLower).ToList
- GameVersions = RawVersions.Where(Function(v) v.StartsWithF("1.")).Select(Function(v) v.Replace("-snapshot", " 快照")).ToList
+ GameVersions = RawVersions.Where(Function(v) v.StartsWithF("1.")).Select(Function(v) v.Replace("-snapshot", " 预览版")).ToList
If GameVersions.Count > 1 Then
- GameVersions = Sort(GameVersions, AddressOf VersionSortBoolean).ToList
+ GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList
If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)}
ElseIf GameVersions.Count = 1 Then
GameVersions = GameVersions.ToList
@@ -1343,9 +1416,9 @@ Retry:
'GameVersions
Dim RawVersions As List(Of String) = Data("game_versions").Select(Function(t) t.ToString.Trim.ToLower).ToList
GameVersions = RawVersions.Where(Function(v) v.StartsWithF("1.") OrElse v.StartsWithF("b1.")).
- Select(Function(v) If(v.Contains("-"), v.Before("-") & " 快照", If(v.StartsWithF("b1."), "远古版本", v))).ToList
+ Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).ToList
If GameVersions.Count > 1 Then
- GameVersions = Sort(GameVersions, AddressOf VersionSortBoolean).ToList
+ GameVersions = GameVersions.Sort(AddressOf VersionSortBoolean).ToList
If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)}
ElseIf GameVersions.Count = 1 Then
'无需处理
@@ -1365,6 +1438,19 @@ Retry:
End If
End If
End Sub
+
+ '''
+ ''' 重新整理 CurseForge 的下载地址。
+ '''
+ 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"),
+ Url.Replace("://edge", "://media"),
+ Url
+ }.Distinct.ToList
+ End Function
+
'''
''' 将当前实例转为可用于保存缓存的 Json。
'''
@@ -1392,28 +1478,26 @@ Retry:
Optional BadDisplayName As Boolean = False) As MyListItem
'获取描述信息
- Dim Info As String = ""
+ 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
- Info += If(ModLoaders.Any,
- "适用于 " & Join(ModLoaders.Select(Function(m) GetStringFromEnum(m)).ToList, "/") & ",", "")
- Info += If(ModeDebug AndAlso Dependencies.Any, Dependencies.Count & " 个前置 Mod,", "")
+ If Dependencies.Any Then Info.Add(Dependencies.Count & " 个前置 Mod")
Case CompType.ModPack
- If GameVersions.All(Function(v) v.Contains("w")) Then
- Info += $"游戏版本 {Join(GameVersions, "、")},"
- End If
+ If GameVersions.All(Function(v) v.Contains("w")) Then Info.Add($"游戏版本 {Join(GameVersions, "、")}")
End Select
If DownloadCount > 0 Then 'CurseForge 的下载次数经常错误地返回 0
- Info += If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次下载,", DownloadCount & " 次下载,")
+ Info.Add("下载 " & If(DownloadCount > 100000, Math.Round(DownloadCount / 10000) & " 万次", DownloadCount & " 次"))
End If
- Info += GetTimeSpanString(ReleaseDate - Date.Now, False) & "更新"
- Info += If(Status = CompFileStatus.Release, "", "," & StatusDescription)
+ Info.Add("更新于 " & GetTimeSpanString(ReleaseDate - Date.Now, False))
+ If Status <> CompFileStatus.Release Then Info.Add(StatusDescription)
'建立控件
Dim NewItem As New MyListItem With {
- .Title = If(BadDisplayName, FileName, DisplayName),
+ .Title = Title,
.SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Me,
- .Info = Info
+ .Info = Info.Join(",")
}
Select Case Status
Case CompFileStatus.Release
@@ -1452,14 +1536,12 @@ Retry:
'''
''' 已知文件信息的缓存。
'''
- Private CompFilesCache As New Dictionary(Of String, List(Of CompFile))
+ Public CompFilesCache As New Dictionary(Of String, List(Of CompFile))
'''
''' 获取某个工程下的全部文件列表。
''' 必须在工作线程执行,失败会抛出异常。
'''
Public Function CompFilesGet(ProjectId As String, FromCurseForge As Boolean) As List(Of CompFile)
- '使用缓存
- If CompFilesCache.ContainsKey(ProjectId) Then Return CompFilesCache(ProjectId)
'获取工程对象
Dim TargetProject As CompProject
If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存
@@ -1470,25 +1552,29 @@ Retry:
TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId, IsJson:=True))
End If
'获取工程对象的文件列表
- Log("[Comp] 开始获取文件列表:" & ProjectId)
- Dim ResultJsonArray As JArray
- If FromCurseForge Then
- 'CurseForge
- 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")
+ If Not CompFilesCache.ContainsKey(ProjectId) Then '有缓存也不能直接返回,这时候前置 Mod 可能没获取(#5173)
+ Log("[Comp] 开始获取文件列表:" & ProjectId)
+ Dim ResultJsonArray As JArray
+ 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
+ Else
+ 'Modrinth
+ ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version", IsJson:=True)
End If
- Else
- 'Modrinth
- ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version", IsJson:=True)
+ 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)
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)
'获取前置 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 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
@@ -1506,10 +1592,10 @@ Retry:
End If
'更新前置 Mod 信息
If Deps.Any Then
- For Each DepProject In Deps.Select(Function(id) CompProjectCache(id))
+ For Each DepProject In Deps.Where(Function(id) CompProjectCache.ContainsKey(id)).Select(Function(id) CompProjectCache(id))
For Each File In CompFilesCache(ProjectId)
If File.RawDependencies.Contains(DepProject.Id) AndAlso DepProject.Id <> ProjectId Then
- File.Dependencies.Add(DepProject.Id)
+ If Not File.Dependencies.Contains(DepProject.Id) Then File.Dependencies.Add(DepProject.Id)
End If
Next
Next
@@ -1526,10 +1612,11 @@ Retry:
Dim Deps As List(Of String) = Files.SelectMany(Function(f) f.Dependencies).Distinct.ToList()
Deps.Sort()
If Not Deps.Any() Then Exit Sub
- Deps = Deps.Where(Function(dep)
- If Not CompProjectCache.ContainsKey(dep) Then Log($"[Comp] 未找到 ID {dep} 的前置 Mod 信息", LogLevel.Debug)
- Return CompProjectCache.ContainsKey(dep)
- End Function).ToList
+ Deps = Deps.Where(
+ Function(dep)
+ If Not CompProjectCache.ContainsKey(dep) Then Log($"[Comp] 未找到 ID {dep} 的前置 Mod 信息", 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 列表
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
index f1f070e..8b208e1 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
@@ -1,24 +1,10 @@
Public Class CrashAnalyzer
- Private Shared IsAnalyzeCacheCleared As Boolean = False
- Private Shared IsAnalyzeCacheClearedLock As New Object
'构造函数
Private TempFolder As String
Public Sub New(UUID As Integer)
- '清理缓存
- SyncLock IsAnalyzeCacheClearedLock
- If Not IsAnalyzeCacheCleared Then
- Try
- DeleteDirectory(PathTemp & "CrashAnalyzer\")
- Catch ex As Exception
- Log(ex, "清理崩溃分析缓存失败")
- End Try
- IsAnalyzeCacheCleared = True
- End If
- End SyncLock
'构建文件结构
- TempFolder = PathTemp & "CrashAnalyzer\" & UUID & RandomInteger(0, 99999999) & "\"
- DeleteDirectory(TempFolder)
+ TempFolder = RequestTaskTempFolder()
Directory.CreateDirectory(TempFolder & "Temp\")
Directory.CreateDirectory(TempFolder & "Report\")
Log("[Crash] 崩溃分析暂存文件夹:" & TempFolder)
@@ -381,6 +367,7 @@ Extracted:
没有可用的分析文件
使用32位Java导致JVM无法分配足够多的内存
Mod重复安装
+ Mod互不兼容
OptiFine与Forge不兼容
Fabric报错
Fabric报错并给出解决方案
@@ -389,7 +376,7 @@ Extracted:
版本Json中存在多个Forge
Mod过多导致超出ID限制
NightConfig的Bug
- ShadersMod与Optifine同时安装
+ ShadersMod与OptiFine同时安装
Forge安装不完整
Mod需要Java11
Mod缺少前置或MC版本错误
@@ -413,7 +400,7 @@ Extracted:
'崩溃日志
If LogCrash IsNot Nothing Then
Log("[Crash] 开始进行崩溃日志堆栈分析")
- Keywords.AddRange(AnalyzeStackKeyword(LogCrash.Before("System Details")))
+ Keywords.AddRange(AnalyzeStackKeyword(LogCrash.BeforeFirst("System Details")))
End If
'Minecraft 日志
If LogMc IsNot Nothing Then
@@ -516,7 +503,7 @@ Done:
If LogMc.Contains("java.lang.ClassNotFoundException: org.spongepowered.asm.launch.MixinTweaker") Then AppendReason(CrashReason.MixinBootstrap缺失)
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.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("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))
@@ -525,6 +512,7 @@ Done:
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("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名称包含特殊字符)
If LogMc.Contains("has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to") Then AppendReason(CrashReason.Mod需要Java11)
If LogMc.Contains("java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual") Then AppendReason(CrashReason.Mod需要Java11)
@@ -540,12 +528,14 @@ Done:
End If
'确定的 Mod 导致崩溃
If LogMc.Contains("Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(RegexSeek(LogMc, "(?<=Caught exception from )[^\n]+?")?.TrimEnd((vbCrLf & " ").ToCharArray)))
- 'Mod 重复安装
+ 'Mod 重复 / 前置问题
If LogMc.Contains("DuplicateModsFoundException") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=\n\t[\w]+ : [A-Z]{1}:[^\n]+(/|\\))[^/\\\n]+?.jar", RegularExpressions.RegexOptions.IgnoreCase))
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))
- 'Mod 缺少前置
+ If LogMc.Contains("Incompatible mods found!") Then '#5006
+ AppendReason(CrashReason.Mod互不兼容, If(RegexSeek(LogMc, "(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )"), ""))
+ End If
If LogMc.Contains("Missing or unsupported mandatory dependencies:") Then
AppendReason(CrashReason.Mod缺少前置或MC版本错误,
RegexSearch(LogMc, "(?<=Missing or unsupported mandatory dependencies:)([\n\r]+\t(.*))+", RegularExpressions.RegexOptions.IgnoreCase).
@@ -573,8 +563,9 @@ Done:
If LogCrash.Contains("has mods that were not found") AndAlso RegexCheck(LogCrash, "The Mod File [^\n]+optifine\\OptiFine[^\n]+ has mods that were not found") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
'Mod 导致的崩溃
If LogCrash.Contains("-- MOD ") Then
- If LogCrash.Between("-- MOD ", "Failure message:").ContainsF(".jar", True) Then
- AppendReason(CrashReason.确定Mod导致游戏崩溃, If(RegexSeek(LogCrash, "(?<=Mod File: ).+"), "").TrimEnd((vbCrLf & " ").ToCharArray))
+ Dim LogCrashMod As String = LogCrash.Between("-- MOD ", "Failure message:")
+ If LogCrashMod.ContainsF(".jar", True) Then
+ AppendReason(CrashReason.确定Mod导致游戏崩溃, If(RegexSeek(LogCrashMod, "(?<=Mod File: ).+"), "").TrimEnd((vbCrLf & " ").ToCharArray))
Else
AppendReason(CrashReason.Mod加载器报错, If(RegexSeek(LogCrash, "(?<=Failure message: )[\w\W]+?(?=\tMod)"), "").Replace(vbTab, " ").TrimEnd((vbCrLf & " ").ToCharArray))
End If
@@ -591,40 +582,57 @@ Done:
'''
Private Sub AnalyzeCrit2()
+ 'Mixin 分析
+ Dim MixinAnalyze =
+ Function(LogText As String) As Boolean
+ Dim IsMixin As Boolean =
+ LogText.Contains("Mixin prepare failed ") OrElse LogText.Contains("Mixin apply failed ") OrElse
+ LogText.Contains("MixinApplyError") OrElse LogText.Contains("MixinTransformerError") OrElse
+ LogText.Contains("mixin.injection.throwables.") OrElse LogText.Contains(".json] FAILED during )")
+ If Not IsMixin Then Return False
+ 'Mod 名称匹配
+ Dim ModName As String = RegexSeek(LogText, "(?<=from mod )[^.\/ ]+(?=\] from)")
+ If ModName Is Nothing Then ModName = RegexSeek(LogText, "(?<=for mod )[^.\/ ]+(?= failed)")
+ If ModName IsNot Nothing Then
+ AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(ModName.TrimEnd((vbCrLf & " ").ToCharArray)))
+ Return True
+ End If
+ 'JSON 名称匹配
+ For Each JsonName In RegexSearch(LogText, "(?<=^[^\t]+[ \[{(]{1})[^ \[{(]+\.[^ ]+(?=\.json)", RegularExpressions.RegexOptions.Multiline)
+ AppendReason(CrashReason.ModMixin失败,
+ TryAnalyzeModName(JsonName.Replace("mixins", "mixin").Replace(".mixin", "").Replace("mixin.", "")))
+ Return True
+ Next
+ '没有明确匹配
+ AppendReason(CrashReason.ModMixin失败)
+ Return True
+ End Function
+
'游戏日志分析
If LogMc IsNot Nothing Then
+ 'Mixin 崩溃
+ 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))
- 'Mixin 崩溃
- If LogMc.Contains("Mixin prepare failed ") OrElse LogMc.Contains("Mixin apply failed ") OrElse LogMc.Contains("MixinApplyError") OrElse
- LogMc.Contains("mixin.injection.throwables.") OrElse LogMc.Contains(".json] FAILED during )") Then
- Dim ModId As String = RegexSeek(LogMc, "(?<=in )[^./ ]+(?=.mixins.json.+failed injection check)")
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=in mixins.)[^./ ]+(?=.json.+failed injection check)")
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in )[^./ ]+(?=.mixins.json)")
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in mixins.)[^./ ]+(?=.json)")
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed mixins.)[^./ ]+(?=.json:)")
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[)[^./ ]+(?=.mixins.json\] FAILED during )")
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[mixins.)[^./ ]+(?=.json\] FAILED during )")
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=from mod )[^./ ]+(?=\] from)")
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=for mod )[^./ ]+(?= failed)")
- '兜底名称判断
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "[^./ \]]+(?=.mixins.json)")
- If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=mixins.)[^./ \]]+(?=.json)")
- AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(If(ModId, "").TrimEnd((vbCrLf & " ").ToCharArray)))
- Else
- '在 #3104 的情况下,这一句导致 OptiFabric 的 Mixin 失败错判为 Fabric Loader 加载失败
- If LogMc.Contains("due to errors, provided by ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=due to errors, provided by ')[^']+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
+ 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
End If
'崩溃报告分析
If LogCrash IsNot Nothing Then
+ 'Mixin 崩溃
+ MixinAnalyze(LogCrash)
+ '常规信息
If LogCrash.Contains("Suspected Mod") Then
- Dim Suspects = RegexSearch(LogCrash.Between("Suspected Mod", "Stacktrace"), "(?<=\n\t[^(\t]+\()[^)\n]+")
- If Suspects.Any Then AppendReason(CrashReason.怀疑Mod导致游戏崩溃, TryAnalyzeModName(Suspects))
+ Dim SuspectsRaw As String = LogCrash.Between("Suspected Mod", "Stacktrace")
+ If Not SuspectsRaw.StartsWithF("s: None") Then 'Suspected Mods: None
+ Dim Suspects = RegexSearch(SuspectsRaw, "(?<=\n\t[^(\t]+\()[^)\n]+")
+ If Suspects.Any Then AppendReason(CrashReason.怀疑Mod导致游戏崩溃, TryAnalyzeModName(Suspects))
+ End If
End If
End If
@@ -640,6 +648,8 @@ Done:
If Not (LogMc.Contains("at net.") OrElse LogMc.Contains("INFO]")) AndAlso LogHs Is Nothing AndAlso LogCrash Is Nothing AndAlso LogMc.Length < 100 Then
AppendReason(CrashReason.极短的程序输出, LogMc)
End If
+ 'Mod 解析错误(常见于 Fabric 前置校验失败)
+ If LogMc.Contains("Mod resolution failed") Then AppendReason(CrashReason.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! 不一定是崩溃原因,它可能是单纯的警报
@@ -695,7 +705,7 @@ NextStack:
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",
- "gitlab", "common", "server", "config", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe", "render", "packet", "events",
+ "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"
@@ -738,13 +748,13 @@ NextStack:
Details = Details.Replace("Fabric Mods", "¨")
Log("[Crash] 崩溃报告中检测到 Fabric Mod 信息格式")
End If
- Details = Details.After("¨")
+ Details = Details.AfterLast("¨")
'[Forge] 获取所有包含 .jar 的行
'[Fabric] 获取所有包含 Mod 信息的行
Dim ModNameLines As New List(Of String)
For Each Line In Details.Split(vbLf)
- If Line.ContainsF(".jar", True) OrElse
+ If (Line.ContainsF(".jar", True) AndAlso Line.Length - Line.Replace(".jar", "").Length = 4) OrElse '只有一个 .jar
(IsFabricDetail AndAlso Line.StartsWithF(vbTab & vbTab) AndAlso Not RegexCheck(Line, "\t\tfabric[\w-]*: Fabric")) Then ModNameLines.Add(Line)
Next
Log("[Crash] 崩溃报告中找到 " & ModNameLines.Count & " 个可能的 Mod 项目行")
@@ -848,21 +858,25 @@ NextStack:
"确定", If(IsHandAnalyze OrElse DirectFile Is Nothing, "", "查看日志"), If(IsHandAnalyze, "", "导出错误报告"),
Button2Action:=If(IsHandAnalyze OrElse DirectFile Is Nothing, Nothing,
Sub()
+ Try
'弹窗选择:查看日志
If File.Exists(DirectFile.Value.Key) Then
- ShellOnly("notepad", DirectFile.Value.Key)
+ ShellOnly(DirectFile.Value.Key)
Else
Dim FilePath As String = PathTemp & "Crash.txt"
WriteFile(FilePath, Join(DirectFile.Value.Value, vbCrLf))
ShellOnly(FilePath)
End If
+ Catch ex As Exception
+ Log(ex,"尝试打开文件失败",LogLevel.Hint)
+ End Try
End Sub))
Case 3
'弹窗选择:导出错误报告
Dim FileAddress As String = Nothing
Try
'获取文件路径
- RunInUiWait(Sub() FileAddress = SelectAs("选择保存位置", "错误报告-" & Date.Now.ToString("G").Replace("/", "-").Replace(":", ".").Replace(" ", "_") & ".zip", "Minecraft 错误报告(*.zip)|*.zip"))
+ RunInUiWait(Sub() FileAddress = SelectSaveFile("选择保存位置", "错误报告-" & Date.Now.ToString("G").Replace("/", "-").Replace(":", ".").Replace(" ", "_") & ".zip", "Minecraft 错误报告(*.zip)|*.zip"))
If String.IsNullOrEmpty(FileAddress) Then Exit Sub
Directory.CreateDirectory(GetPathFromFullPath(FileAddress))
If File.Exists(FileAddress) Then File.Delete(FileAddress)
@@ -873,19 +887,23 @@ NextStack:
If ExtraFiles IsNot Nothing Then OutputFiles.AddRange(ExtraFiles)
For Each OutputFile In OutputFiles
Dim FileName As String = GetFileNameFromPath(OutputFile)
+ Dim FileEncoding As Encoding = Nothing
Select Case FileName
Case "LatestLaunch.bat"
FileName = "启动脚本.bat"
Case "Log1.txt"
FileName = "PCL 启动器日志.txt"
+ FileEncoding = Encoding.UTF8
Case "RawOutput.log"
FileName = "游戏崩溃前的输出.txt"
+ FileEncoding = Encoding.UTF8
End Select
If File.Exists(OutputFile) Then
- Dim FileEncoding As Encoding = GetEncoding(ReadFileBytes(OutputFile))
+ If FileEncoding Is Nothing Then FileEncoding = GetEncoding(ReadFileBytes(OutputFile))
WriteFile(TempFolder & "Report\" & FileName,
SecretFilter(ReadFile(OutputFile, FileEncoding), If(FileName = "启动脚本.bat", "F", "*")),
Encoding:=FileEncoding)
+ Log($"[Crash] 导出文件:{FileName},编码:{FileEncoding.HeaderName}")
End If
Next
'导出报告
@@ -896,7 +914,7 @@ NextStack:
Log(ex, "导出错误报告失败", LogLevel.Feedback)
Exit Sub
End Try
- OpenExplorer("/select,""" & FileAddress & """")
+ OpenExplorer(FileAddress)
End Select
End Sub
'''
@@ -909,7 +927,7 @@ NextStack:
If IsHandAnalyze Then
Return "很抱歉,PCL 无法确定错误原因。"
Else
- Return $"很抱歉,你的游戏出现了一些问题……{vbCrLf}如果要寻求帮助,请导出错误报告并发给他人,而不是发送这个窗口的截图。"
+ Return $"很抱歉,你的游戏出现了一些问题……{vbCrLf}如果要寻求帮助,请把错误报告文件发给对方,而不是发送这个窗口的照片或者截图。"
End If
End If
@@ -942,9 +960,9 @@ NextStack:
End If
Case CrashReason.Mod缺少前置或MC版本错误
If Additional.Any Then
- Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n未满足的依赖项:\n - " & Join(Additional, "\n - ") & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
+ Results.Add("由于未安装正确的前置 Mod,导致游戏退出。\n缺失的依赖项:\n - " & Join(Additional, "\n - ") & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
Else
- Results.Add("由于未满足 Mod 的依赖项,导致游戏退出。\n请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
+ Results.Add("由于未安装正确的前置 Mod,导致游戏退出。\n请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
End If
Case CrashReason.堆栈分析发现关键字
If Additional.Count = 1 Then
@@ -965,7 +983,9 @@ NextStack:
Results.Add("以下 Mod 导致了游戏出错:\n - " & Join(Additional, "\n - ") & "\n\n你可以尝试依次禁用上述 Mod,然后观察游戏是否还会崩溃。\n\e\h")
End If
Case CrashReason.ModMixin失败
- If Additional.Count = 1 Then
+ If Additional.Count = 0 Then
+ Results.Add("部分 Mod 注入失败,导致游戏出错。\n这一般代表着部分 Mod 与其他 Mod 或当前环境不兼容,或是它存在 Bug。\n你可以尝试逐步禁用 Mod,然后观察游戏是否还会崩溃,以此定位导致崩溃的 Mod。\n\e\h")
+ ElseIf Additional.Count = 1 Then
Results.Add("名为 " & Additional.First & " 的 Mod 注入失败,导致游戏出错。\n这一般代表着它与其他 Mod 或当前环境不兼容,或是它存在 Bug。\n你可以尝试禁用此 Mod,然后观察游戏是否还会崩溃。\n\e\h")
Else
Results.Add("以下 Mod 导致了游戏出错:\n - " & Join(Additional, "\n - ") & "\n这一般代表着它们与其他 Mod 或当前环境不兼容,或是它存在 Bug。\n你可以尝试依次禁用上述 Mod,然后观察游戏是否还会崩溃。\n\e\h")
@@ -980,7 +1000,7 @@ NextStack:
If Additional.Count = 1 Then
Results.Add("名为 " & Additional.First & " 的 Mod 初始化失败,导致游戏无法继续加载。\n你可以尝试禁用此 Mod,然后观察游戏是否还会崩溃。\n\e\h")
Else
- Results.Add("以下 Mod 初始化失败,导致游戏无法继续加载:\n - " & Join(Additional, "\n - ") & "\n\n你可以尝试依次禁用上述 Mod,然后观察游戏是否还会崩溃。\n\e\h")
+ Results.Add("以下 Mod 初始化失败,导致游戏出错:\n - " & Join(Additional, "\n - ") & "\n\n你可以尝试依次禁用上述 Mod,然后观察游戏是否还会崩溃。\n\e\h")
End If
Case CrashReason.特定方块导致崩溃
If Additional.Count = 1 Then
@@ -992,7 +1012,7 @@ NextStack:
If Additional.Count >= 2 Then
Results.Add("你重复安装了多个相同的 Mod:\n - " & Join(Additional, "\n - ") & "\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。")
Else
- Results.Add("你可能重复安装了多个相同的 Mod,导致游戏无法继续加载。\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。\e\h")
+ Results.Add("你可能重复安装了多个相同的 Mod,导致游戏出错。\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。\e\h")
End If
Case CrashReason.特定实体导致崩溃
If Additional.Count = 1 Then
@@ -1002,12 +1022,12 @@ NextStack:
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 后,游戏即可正常运行。")
+ Case CrashReason.ShadersMod与OptiFine同时安装
+ 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
- Results.Add("可能由于使用其他启动器修改了 Forge 版本,当前版本的文件存在异常,导致了游戏崩溃。\n请尝试重新全新安装 Forge,而非使用其他启动器修改 Forge 版本。")
+ Results.Add("可能由于其他启动器修改了 Forge 版本,当前版本的文件存在异常,导致了游戏崩溃。\n请尝试重新全新安装 Forge,而非使用其他启动器修改 Forge 版本。")
Case CrashReason.玩家手动触发调试崩溃
Results.Add("* 事实上,你的游戏没有任何问题,这是你自己触发的崩溃。\n* 你难道没有更重要的事要做吗?")
Case CrashReason.Mod需要Java11
@@ -1033,30 +1053,36 @@ NextStack:
Case CrashReason.文件或内容校验失败
Results.Add("部分文件或内容校验失败,导致游戏出现了问题。\n\n请尝试删除游戏(包括 Mod)并重新下载,或尝试在重新下载时使用 VPN。\h")
Case CrashReason.Forge安装不完整
- Results.Add("由于 Forge 安装不完整,导致游戏无法正常运行。\n请尝试重新安装 Forge。\h")
+ Results.Add("由于安装的 Forge 文件丢失,导致游戏无法正常运行。\n请重新安装一次相同版本的 Forge,然后再启动游戏。\n在打包游戏时删除 libraries 文件夹可能导致此错误。\h")
Case CrashReason.Fabric报错
If Additional.Count = 1 Then
Results.Add("Fabric 提供了以下错误信息:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
Else
- Results.Add("Fabric 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
+ Results.Add("Fabric 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
+ End If
+ Case CrashReason.Mod互不兼容
+ If Additional.Count = 1 Then
+ Results.Add("你所安装的 Mod 不兼容:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
+ Else
+ Results.Add("你所安装的 Mod 不兼容,Mod 加载器可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
End If
Case CrashReason.Mod加载器报错
If Additional.Count = 1 Then
Results.Add("Mod 加载器提供了以下错误信息:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
Else
- Results.Add("Mod 加载器可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
+ Results.Add("Mod 加载器可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
End If
Case CrashReason.Fabric报错并给出解决方案
If Additional.Count = 1 Then
Results.Add("Fabric 提供了以下解决方案:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
Else
- Results.Add("Fabric 可能已经提供了解决方案,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
+ Results.Add("Fabric 可能已经提供了解决方案,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
End If
Case CrashReason.Forge报错
If Additional.Count = 1 Then
Results.Add("Forge 提供了以下错误信息:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
Else
- Results.Add("Forge 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
+ Results.Add("Forge 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\h")
End If
Case CrashReason.没有可用的分析文件
Results.Add("你的游戏出现了一些问题,但 PCL 未能找到相关记录文件,因此无法进行分析。\h")
@@ -1072,7 +1098,7 @@ NextStack:
Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, vbCrLf).
Trim(vbCrLf.ToCharArray) &
If(Not Results.Any(Function(r) r.EndsWithF("\h")) OrElse IsHandAnalyze, "",
- vbCrLf & "如果要寻求帮助,请向他人发送错误报告文件,而不是发送这个窗口的截图。" &
+ vbCrLf & "如果要寻求帮助,请把错误报告文件发给对方,而不是发送这个窗口的照片或者截图。" &
If(If(PageSetupSystem.IsLauncherNewest(), True), "",
vbCrLf & vbCrLf & "此外,你正在使用老版本 PCL,更新 PCL 或许也能解决这个问题。" & vbCrLf & "你可以点击 设置 → 启动器 → 检查更新 来更新 PCL。"))
End Function
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
index 867b417..b7bbc6d 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
@@ -21,12 +21,7 @@
End If
'检查文件
Dim Checker As New FileChecker(MinSize:=1024, ActualSize:=If(Version.JsonObject("downloads")("client")("size"), -1), Hash:=Version.JsonObject("downloads")("client")("sha1"))
- If ReturnNothingOnFileUseable Then
- '是否跳过
- Dim IsSetupSkip As Boolean = ShouldIgnoreFileCheck(Version)
- If IsSetupSkip AndAlso File.Exists(Version.Path & Version.Name & ".jar") Then Return Nothing '跳过校验
- If Checker.Check(Version.Path & Version.Name & ".jar") Is Nothing Then Return Nothing '通过校验
- End If
+ If ReturnNothingOnFileUseable AndAlso Checker.Check(Version.Path & Version.Name & ".jar") Is Nothing Then Return Nothing '通过校验
'返回下载信息
Dim JarUrl As String = Version.JsonObject("downloads")("client")("url")
Return New NetFile(DlSourceLauncherOrMetaGet(JarUrl), Version.Path & Version.Name & ".jar", Checker)
@@ -56,7 +51,7 @@
'''
''' 构造补全某 Minecraft 版本的所有文件的加载器列表。失败会抛出异常。
'''
- Public Function DlClientFix(Version As McVersion, CheckAssetsHash As Boolean, AssetsIndexBehaviour As AssetsIndexExistsBehaviour, SkipAssetsDownloadWhileSetupRequired As Boolean) As List(Of LoaderBase)
+ Public Function DlClientFix(Version As McVersion, CheckAssetsHash As Boolean, AssetsIndexBehaviour As AssetsIndexExistsBehaviour) As List(Of LoaderBase)
Dim Loaders As New List(Of LoaderBase)
#Region "下载支持库文件"
@@ -69,9 +64,9 @@
#End Region
#Region "下载资源文件"
- Dim IsSetupSkip As Boolean = ShouldIgnoreFileCheck(Version)
- If IsSetupSkip Then Log("[Download] 已跳过 Assets 下载")
- If (Not SkipAssetsDownloadWhileSetupRequired) OrElse Not IsSetupSkip Then
+ If ShouldIgnoreFileCheck(Version) Then
+ Log("[Download] 已跳过所有 Assets 检查")
+ Else
Dim LoadersAssets As New List(Of LoaderBase)
'获取资源文件索引地址
LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析资源文件索引地址",
@@ -104,7 +99,7 @@
Task.Output = New List(Of NetFile) From {BackAssetsFile}
'检查是否需要更新:每天只更新一次
If File.Exists(RealAddress) AndAlso Math.Abs((File.GetLastWriteTime(RealAddress).Date - Now.Date).TotalDays) < 1 Then
- Log("[Download] 无需更新资源文件索引")
+ Log("[Download] 无需更新资源文件索引,取消")
Task.Abort()
End If
End Sub))
@@ -121,7 +116,7 @@
'获取资源文件地址
LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析缺失资源文件",
Sub(Task As LoaderTask(Of String, List(Of NetFile)))
- Task.Output = McAssetsFixList(McAssetsGetIndexName(Version), CheckAssetsHash, Task)
+ Task.Output = McAssetsFixList(Version, CheckAssetsHash, Task)
End Sub) With {.ProgressWeight = 3})
'下载资源文件
LoadersAssets.Add(New LoaderDownload("下载资源文件", New List(Of NetFile)) With {.ProgressWeight = 25})
@@ -172,24 +167,25 @@
End Structure
'''
''' Minecraft 客户端 版本列表,主加载器。
+ ''' 若要求镜像源必须包含某个版本,则将该版本 ID 作为输入(#5195)。
'''
- Public DlClientListLoader As New LoaderTask(Of Integer, DlClientListResult)("DlClientList Main", AddressOf DlClientListMain)
- Private Sub DlClientListMain(Loader As LoaderTask(Of Integer, DlClientListResult))
+ Public DlClientListLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Main", AddressOf DlClientListMain)
+ Private Sub DlClientListMain(Loader As LoaderTask(Of String, DlClientListResult))
Select Case Setup.Get("ToolDownloadVersion")
Case 0
- DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)) From {
- New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListBmclapiLoader, 30),
- New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListMojangLoader, 60)
+ DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)) From {
+ New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListBmclapiLoader, 30),
+ New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListMojangLoader, 30 + 60)
}, Loader.IsForceRestarting)
Case 1
- DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)) From {
- New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListMojangLoader, 5),
- New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListBmclapiLoader, 35)
+ DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)) From {
+ New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListMojangLoader, 5),
+ New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListBmclapiLoader, 5 + 30)
}, Loader.IsForceRestarting)
Case Else
- DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)) From {
- New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListMojangLoader, 60),
- New KeyValuePair(Of LoaderTask(Of Integer, DlClientListResult), Integer)(DlClientListBmclapiLoader, 60)
+ DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)) From {
+ New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListMojangLoader, 60),
+ New KeyValuePair(Of LoaderTask(Of String, DlClientListResult), Integer)(DlClientListBmclapiLoader, 60 + 60)
}, Loader.IsForceRestarting)
End Select
End Sub
@@ -198,9 +194,9 @@
'''
''' Minecraft 客户端 版本列表,Mojang 官方源加载器。
'''
- Public DlClientListMojangLoader As New LoaderTask(Of Integer, DlClientListResult)("DlClientList Mojang", AddressOf DlClientListMojangMain)
+ Public DlClientListMojangLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Mojang", AddressOf DlClientListMojangMain)
Private IsNewClientVersionHinted As Boolean = False
- Private Sub DlClientListMojangMain(Loader As LoaderTask(Of Integer, DlClientListResult))
+ 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)
Try
Dim Versions As JArray = Json("versions")
@@ -230,14 +226,21 @@
'''
''' Minecraft 客户端 版本列表,BMCLAPI 源加载器。
'''
- Public DlClientListBmclapiLoader As New LoaderTask(Of Integer, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain)
- Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of Integer, DlClientListResult))
+ 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)
Try
Dim Versions As JArray = Json("versions")
If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
'添加 PCL 特供项
If File.Exists(PathTemp & "Cache\download.json") Then Versions.Merge(GetJson(ReadFile(PathTemp & "Cache\download.json")))
+ '检查是否有要求的版本(#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
+ Throw New Exception("BMCLAPI 源未包含目标版本 " & Id)
+ End If
+ End If
'返回
Loader.Output = New DlClientListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Json}
Catch ex As Exception
@@ -250,26 +253,31 @@
'''
Public Function DlClientListGet(Id As String)
Try
- '确认 Minecraft 版本列表已完成获取
- Select Case DlClientListLoader.State
- Case LoadState.Loading
- DlClientListLoader.WaitForExit()
- Case LoadState.Failed, LoadState.Aborted, LoadState.Waiting
- DlClientListLoader.WaitForExit(IsForceRestart:=True)
- End Select
'确认版本格式标准
Id = Id.Replace("_", "-") '1.7.10_pre4 在版本列表中显示为 1.7.10-pre4
If Id <> "1.0" AndAlso Id.EndsWithF(".0") Then Id = Left(Id, Id.Length - 2) 'OptiFine 1.8 的下载会触发此问题,显示版本为 1.8.0
- '查找版本并开始
+ '获取 Minecraft 版本列表
+ Select Case DlClientListLoader.State
+ Case LoadState.Finished
+ '从当前的结果获取目标版本…
+ For Each Version As JObject In DlClientListLoader.Output.Value("versions")
+ If Version("id") = Id Then Return Version("url").ToString
+ Next
+ '…如果没有,则重新尝试获取(在版本刚更新时可能出现这种情况,#5195)
+ DlClientListLoader.WaitForExit(Id, IsForceRestart:=True)
+ Case LoadState.Loading
+ DlClientListLoader.WaitForExit(Id)
+ Case LoadState.Failed, LoadState.Aborted, LoadState.Waiting
+ DlClientListLoader.WaitForExit(Id, IsForceRestart:=True)
+ End Select
+ '重新查找版本
For Each Version As JObject In DlClientListLoader.Output.Value("versions")
- If Version("id") = Id Then
- Return Version("url").ToString
- End If
+ If Version("id") = Id Then Return Version("url").ToString
Next
- Log("未发现版本 " & Id & " 的 json 下载地址,版本列表返回为:" & vbCrLf & DlClientListLoader.Output.Value.ToString, LogLevel.Debug)
+ Log($"未发现版本 {Id} 的 json 下载地址,版本列表返回为:{vbCrLf}{DlClientListLoader.Output.Value.ToString}", LogLevel.Debug)
Return Nothing
Catch ex As Exception
- Log(ex, "获取版本 " & Id & " 的 json 下载地址失败")
+ Log(ex, $"获取版本 {Id} 的 json 下载地址失败")
Return Nothing
End Try
End Function
@@ -342,17 +350,17 @@
Case 0
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 30),
- New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 30 + 60)
}, Loader.IsForceRestarting)
Case 1
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 5),
- New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 35)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 5 + 30)
}, Loader.IsForceRestarting)
Case Else
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListOfficialLoader, 60),
- New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlOptiFineListResult), Integer)(DlOptiFineListBmclapiLoader, 60 + 60)
}, Loader.IsForceRestarting)
End Select
End Sub
@@ -448,17 +456,17 @@
Case 0
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 30),
- New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 30 + 60)
}, Loader.IsForceRestarting)
Case 1
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 5),
- New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 35)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 5 + 30)
}, Loader.IsForceRestarting)
Case Else
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListOfficialLoader, 60),
- New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlForgeListResult), Integer)(DlForgeListBmclapiLoader, 60 + 60)
}, Loader.IsForceRestarting)
End Select
End Sub
@@ -593,17 +601,17 @@
Case 0
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)) From {
New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 30),
- New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 30 + 60)
}, Loader.IsForceRestarting)
Case 1
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)) From {
New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 5),
- New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 35)
+ New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 5 + 30)
}, Loader.IsForceRestarting)
Case Else
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)) From {
New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionOfficialLoader, 60),
- New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of String, List(Of DlForgeVersionEntry)), Integer)(DlForgeVersionBmclapiLoader, 60 + 60)
}, Loader.IsForceRestarting)
End Select
End Sub
@@ -614,12 +622,12 @@
Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
Dim Result As String
Try
- Result = NetGetCodeByDownload("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" &
+ Result = NetGetCodeByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" &
Loader.Input.Replace("-", "_") & '兼容 Forge 1.7.10-pre4,#4057
".html", UseBrowserUserAgent:=True)
Catch ex As Exception
If GetExceptionSummary(ex).Contains("(404)") Then
- Throw New Exception("没有可用版本")
+ Throw New Exception("不可用")
Else
Throw
End If
@@ -677,7 +685,7 @@
Catch ex As Exception
Throw New Exception("Forge 官方源版本列表解析失败(" & Result & ")", ex)
End Try
- If Not Versions.Any() Then Throw New Exception("没有可用版本")
+ If Not Versions.Any() Then Throw New Exception("不可用")
Loader.Output = Versions
End Sub
@@ -732,7 +740,7 @@
Catch ex As Exception
Throw New Exception("Forge BMCLAPI 版本列表解析失败(" & Json.ToString & ")", ex)
End Try
- If Not Versions.Any() Then Throw New Exception("没有可用版本")
+ If Not Versions.Any() Then Throw New Exception("不可用")
Loader.Output = Versions
End Sub
@@ -785,7 +793,7 @@
Inherit = "1.20.1"
Else '20.4.30-beta
VersionName = ApiName
- Version = New Version(ApiName.Before("-"))
+ Version = New Version(ApiName.BeforeFirst("-"))
Inherit = $"1.{Version.Major}" & If(Version.Minor = 0, "", "." & Version.Minor)
End If
End Sub
@@ -800,17 +808,17 @@
Case 0
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListBmclapiLoader, 30),
- New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListOfficialLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListOfficialLoader, 30 + 60)
}, Loader.IsForceRestarting)
Case 1
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListOfficialLoader, 5),
- New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListBmclapiLoader, 35)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListBmclapiLoader, 5 + 30)
}, Loader.IsForceRestarting)
Case Else
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListOfficialLoader, 60),
- New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListBmclapiLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlNeoForgeListResult), Integer)(DlNeoForgeListBmclapiLoader, 60 + 60)
}, Loader.IsForceRestarting)
End Select
End Sub
@@ -821,8 +829,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 = NetGetCodeByDownload("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True)
- Dim ResultLegacy As String = NetGetCodeByDownload("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True)
+ 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)
If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
'解析
Try
@@ -839,8 +847,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 = NetGetCodeByDownload("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True)
- Dim ResultLegacy As String = NetGetCodeByDownload("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True)
+ 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)
If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
'解析
Try
@@ -857,7 +865,7 @@
Dim Versions = VersionNames.
Where(Function(name) name <> "47.1.82"). '这个版本虽然在版本列表中,但不能下载
Select(Function(name) New DlNeoForgeListEntry(name)).ToList
- If Not Versions.Any() Then Throw New Exception("没有可用版本")
+ If Not Versions.Any() Then Throw New Exception("不可用")
Versions = Versions.OrderByDescending(Function(a) a.Version).ToList
Return Versions
End Function
@@ -925,17 +933,17 @@
Case 0
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 30),
- New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 30 + 60)
}, Loader.IsForceRestarting)
Case 1
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 5),
- New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 35)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 5 + 30)
}, Loader.IsForceRestarting)
Case Else
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListOfficialLoader, 60),
- New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlLiteLoaderListResult), Integer)(DlLiteLoaderListBmclapiLoader, 60 + 60)
}, Loader.IsForceRestarting)
End Select
End Sub
@@ -1024,17 +1032,17 @@
Case 0
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 30),
- New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 30 + 60)
}, Loader.IsForceRestarting)
Case 1
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 5),
- New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 35)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 5 + 30)
}, Loader.IsForceRestarting)
Case Else
DlSourceLoader(Loader, New List(Of KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)) From {
New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListOfficialLoader, 60),
- New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 60)
+ New KeyValuePair(Of LoaderTask(Of Integer, DlFabricListResult), Integer)(DlFabricListBmclapiLoader, 60 + 60)
}, Loader.IsForceRestarting)
End Select
End Sub
@@ -1087,29 +1095,34 @@
'''
''' 对可能涉及 Mod 镜像源的请求进行处理,返回字符串或 JObject。
- ''' 调用 NetGetCodeByRequestOnce。
+ ''' 调用 NetGetCodeByRequest,会进行重试。
'''
Public Function DlModRequest(Url As String, Optional IsJson As Boolean = False) As Object
- Dim McimUrl As String = DlSourceModGet(Url)
Dim Urls As New List(Of KeyValuePair(Of String, Integer))
- If McimUrl <> Url Then
- Select Case Setup.Get("ToolDownloadMod")
- Case 0
- Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
- Urls.Add(New KeyValuePair(Of String, Integer)(Url, 60))
- Case 1
- Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
- Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 35))
- Case Else
- Urls.Add(New KeyValuePair(Of String, Integer)(Url, 60))
- Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 60))
- End Select
- End If
+ 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)
+ 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
@@ -1119,28 +1132,34 @@
'''
''' 对可能涉及 Mod 镜像源的请求进行处理。
- ''' 调用 NetRequestOnce。
+ ''' 调用 NetRequest,会进行重试。
'''
- Public Function DlModRequest(Url As String, Method As String, Data As String, ContentType As String, Optional Headers As Dictionary(Of String, String) = Nothing) As String
- Dim McimUrl As String = DlSourceModGet(Url)
+ Public Function DlModRequest(Url As String, Method As String, Data As String, ContentType As String) As String
Dim Urls As New List(Of KeyValuePair(Of String, Integer))
- If McimUrl <> Url Then
- Select Case Setup.Get("ToolDownloadMod")
- Case 0
- Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
- Urls.Add(New KeyValuePair(Of String, Integer)(Url, 60))
- Case 1
- Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
- Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 35))
- Case Else
- Urls.Add(New KeyValuePair(Of String, Integer)(Url, 60))
- Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 60))
- End Select
- End If
+ 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 NetRequestOnce(Source.Key, Method, Data, ContentType, Timeout:=Source.Value * 1000, Headers:=Headers)
+ Return NetRequestOnce(Source.Key, Method, Data, ContentType, Timeout:=Source.Value * 1000)
Catch ex As Exception
Exs += ex.Message + vbCrLf
End Try
@@ -1164,28 +1183,42 @@
End Function
Public Function DlSourceLibraryGet(Original As String) As String()
- Return {
- 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.
- 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
- }
+ If {"minecraftforge", "fabricmc", "neoforged"}.Any(Function(k) Original.Contains(k)) Then '不添加原版源
+ Return {
+ 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.
+ 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")
+ }
+ Else
+ Return {
+ 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.
+ 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
+ }
+ End If
End Function
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")
+ 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()
@@ -1205,71 +1238,63 @@
Optional IsForceRestart As Boolean = False)
Dim WaitCycle As Integer = 0
Do While True
- '检查加载结束
- Dim IsAllFailed As Boolean = True
+ '检查状态
+ Dim BeforeLoadersAllFailed As Boolean = True
For Each SubLoader In LoaderList
- If WaitCycle = 0 Then
- '如果要强制刷新,就不使用已经加载好的值
- If IsForceRestart Then Exit For
- '如果输入不一样,就不使用已经加载好的值
+ If WaitCycle = 0 Then '判断是否可以不加载,直接使用已经加载好的结果
+ If IsForceRestart Then Continue For '强制刷新,不行
If (SubLoader.Key.Input Is Nothing Xor MainLoader.Input Is Nothing) OrElse
- (SubLoader.Key.Input IsNot Nothing AndAlso Not SubLoader.Key.Input.Equals(MainLoader.Input)) Then Continue For
+ (SubLoader.Key.Input IsNot Nothing AndAlso Not SubLoader.Key.Input.Equals(MainLoader.Input)) Then Continue For '父子加载器的输入不一样,也不行
End If
- If SubLoader.Key.State <> LoadState.Failed Then IsAllFailed = False
+ If SubLoader.Key.State <> LoadState.Failed Then BeforeLoadersAllFailed = False
If SubLoader.Key.State = LoadState.Finished Then
+ '检查加载器成功
MainLoader.Output = SubLoader.Key.Output
DlSourceLoaderAbort(LoaderList)
Exit Sub
- ElseIf IsAllFailed Then
+ ElseIf BeforeLoadersAllFailed Then
+ '此前的加载器全部失败,直接启动后续加载器
If WaitCycle < SubLoader.Value * 100 Then WaitCycle = SubLoader.Value * 100
End If
- '由于 Forge BMCLAPI 没有可用版本导致强制失败
- '在没有可用版本时,官方源会一直卡住,直接使用 BMCLAPI 判定失败即可
- If SubLoader.Key.Error IsNot Nothing AndAlso SubLoader.Key.Error.Message.Contains("没有可用版本") Then
- For Each SubLoader2 In LoaderList
- If WaitCycle < SubLoader2.Value * 100 Then WaitCycle = SubLoader2.Value * 100
- Next
- End If
Next
- '启动加载源
+ '第一轮时:既然不直接使用已经加载好的结果,那就启动第一个加载器
If WaitCycle = 0 Then
- '启动第一个源
- LoaderList(0).Key.Start(MainLoader.Input, IsForceRestart)
- '将其他源标记为未启动,以确保可以切换下载源(#184)
- For i = 1 To LoaderList.Count - 1
- LoaderList(i).Key.State = LoadState.Waiting
+ LoaderList.First.Key.Start(MainLoader.Input, IsForceRestart)
+ For Each Loader In LoaderList.Skip(1)
+ Loader.Key.State = LoadState.Waiting '将其他源标记为未启动,以确保可以切换下载源(#184)
Next
End If
+ '检查加载器失败或超时
For i = 0 To LoaderList.Count - 1
- If WaitCycle = LoaderList(i).Value * 100 Then
- If i < LoaderList.Count - 1 Then
- '启动下一个源
- LoaderList(i + 1).Key.Start(MainLoader.Input, IsForceRestart)
- Else
- '失败
- Dim ErrorInfo As Exception = Nothing
- For ii = 0 To LoaderList.Count - 1
- LoaderList(ii).Key.Input = Nothing '重置输入,以免以同样的输入“重试加载”时直接失败
- If LoaderList(ii).Key.Error IsNot Nothing Then
- If ErrorInfo Is Nothing OrElse LoaderList(ii).Key.Error.Message.Contains("没有可用版本") Then
- ErrorInfo = LoaderList(ii).Key.Error
- End If
+ If WaitCycle <> LoaderList(i).Value * 100 Then Continue For
+ If i < LoaderList.Count - 1 AndAlso Not LoaderList.All(Function(l) l.Key.State = LoadState.Failed) Then
+ '若还有下一个源,则启动下一个源
+ LoaderList(i + 1).Key.Start(MainLoader.Input, IsForceRestart)
+ Else
+ '若没有,则失败
+ Dim ErrorInfo As Exception = Nothing
+ For ii = 0 To LoaderList.Count - 1
+ LoaderList(ii).Key.Input = Nothing '重置输入,以免以同样的输入“重试加载”时直接失败
+ If LoaderList(ii).Key.Error IsNot Nothing Then
+ If ErrorInfo Is Nothing OrElse LoaderList(ii).Key.Error.Message.Contains("不可用") Then
+ ErrorInfo = LoaderList(ii).Key.Error
End If
- Next
- If ErrorInfo Is Nothing Then ErrorInfo = New TimeoutException("下载源连接超时")
- DlSourceLoaderAbort(LoaderList)
- Throw ErrorInfo
- End If
- Exit For
+ End If
+ Next
+ If ErrorInfo Is Nothing Then ErrorInfo = New TimeoutException("下载源连接超时")
+ DlSourceLoaderAbort(LoaderList)
+ Throw ErrorInfo
End If
+ Exit For
Next
'计时
+ Thread.Sleep(10)
+ WaitCycle += 1
+ '检查父加载器中断
If MainLoader.IsAborted Then
DlSourceLoaderAbort(LoaderList)
Exit Sub
End If
- Thread.Sleep(10)
- WaitCycle += 1
Loop
End Sub
Private Sub DlSourceLoaderAbort(Of InputType, OutputType)(LoaderList As List(Of KeyValuePair(Of LoaderTask(Of InputType, OutputType), Integer)))
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
index af527ef..8247568 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
@@ -131,7 +131,7 @@
Version = New Version(1, Version.Major, Version.Build, Version.Revision)
End If
Is64Bit = Output.Contains("64-bit")
- If Version.Minor <= 4 OrElse Version.Minor >= 25 Then Throw New ApplicationException("分析详细信息失败,获取的版本为 " & Version.ToString)
+ If Version.Minor <= 4 OrElse Version.Minor >= 100 Then Throw New ApplicationException("分析详细信息失败,获取的版本为 " & Version.ToString)
'基于 #3649,在 64 位系统上禁用 32 位 Java
If Not Is64Bit AndAlso Not Is32BitSystem Then Throw New Exception("该 Java 为 32 位版本,请安装 64 位的 Java")
'基于 #2249 发现 JRE 17 似乎也导致了 Forge 安装失败,干脆禁用更多版本的 JRE
@@ -342,7 +342,7 @@ RetryGet:
Requirement = "需要 Java " & If(Left = Right, Left, Left & " ~ " & Right)
End If
Dim JavaCurrent As String = UserJava.VersionCode & If(ShowRevision, "." & UserJava.Version.MajorRevision & "." & UserJava.Version.MinorRevision, "")
- If Setup.Get("LaunchAdvanceJava") OrElse (RelatedVersion IsNot Nothing AndAlso Setup.Get("VersionAdvanceJava", RelatedVersion)) Then
+ If RelatedVersion IsNot Nothing AndAlso Setup.Get("VersionAdvanceJava", RelatedVersion) Then
'直接跳过弹窗
Log("[Java] 设置中指定了使用 Java " & JavaCurrent & ",但当前版本" & Requirement & ",这可能会导致游戏崩溃!", LogLevel.Debug)
AllowedJavaList = New List(Of JavaEntry) From {UserJava}
@@ -372,6 +372,8 @@ ExitUserJavaCheck:
For Each Java In AllowedJavaList
'如果在官启文件夹启动,会将官启自带 Java 错误视作 MC 文件夹指定 Java,导致了 #2054 的第二例
If Java.PathFolder.Contains(".minecraft\cache\java") Then Continue For
+ '#5780
+ If Java.PathFolder.Contains("PCL\MyDownload") Then Continue For
If TargetJavaList.Contains(Java) Then
'直接使用指定的 Java
AllowedJavaList = New List(Of JavaEntry) From {Java}
@@ -382,7 +384,7 @@ ExitUserJavaCheck:
UserPass:
'对适合的 Java 进行排序
- AllowedJavaList = Sort(AllowedJavaList, AddressOf JavaSorter)
+ AllowedJavaList = AllowedJavaList.Sort(AddressOf JavaSorter)
Log($"[Java] 排序后的 Java 优先顺序:")
For Each Java In AllowedJavaList
Log($"[Java] - {Java}")
@@ -489,16 +491,18 @@ NoUserJava:
Public JavaSearchLoader As New LoaderTask(Of Integer, Integer)("查找 Java", AddressOf JavaSearchLoaderSub) With {.ProgressWeight = 2}
Private Sub JavaSearchLoaderSub(Loader As LoaderTask(Of Integer, Integer))
If FrmSetupLaunch IsNot Nothing Then
- RunInUiWait(Sub()
- FrmSetupLaunch.ComboArgumentJava.Items.Clear()
- FrmSetupLaunch.ComboArgumentJava.Items.Add(New ComboBoxItem With {.Content = "加载中……", .IsSelected = True})
- End Sub)
+ RunInUiWait(
+ Sub()
+ FrmSetupLaunch.ComboArgumentJava.Items.Clear()
+ FrmSetupLaunch.ComboArgumentJava.Items.Add(New ComboBoxItem With {.Content = "加载中……", .IsSelected = True})
+ End Sub)
End If
If FrmVersionSetup IsNot Nothing Then
- RunInUiWait(Sub()
- FrmVersionSetup.ComboArgumentJava.Items.Clear()
- FrmVersionSetup.ComboArgumentJava.Items.Add(New ComboBoxItem With {.Content = "加载中……", .IsSelected = True})
- End Sub)
+ RunInUiWait(
+ Sub()
+ FrmVersionSetup.ComboArgumentJava.Items.Clear()
+ FrmVersionSetup.ComboArgumentJava.Items.Add(New ComboBoxItem With {.Content = "加载中……", .IsSelected = True})
+ End Sub)
End If
Try
@@ -520,6 +524,7 @@ NoUserJava:
Next
'查找磁盘中的 Java
For Each Disk As DriveInfo In DriveInfo.GetDrives()
+ If Disk.DriveType = DriveType.Network Then Continue For '跳过网络驱动器(#3705)
JavaSearchFolder(Disk.Name, JavaPreList, False)
Next
'查找 APPDATA 文件夹中的 Java
@@ -552,7 +557,7 @@ 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
+ 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 不含特殊引用")
@@ -583,7 +588,7 @@ NoUserJava:
For Each Entry In JavaPreList.Distinct(Function(a, b) a.Key.ToLower = b.Key.ToLower) '#794
NewJavaList.Add(New JavaEntry(Entry.Key, Entry.Value))
Next
- NewJavaList = Sort(JavaCheckList(NewJavaList), AddressOf JavaSorter)
+ NewJavaList = JavaCheckList(NewJavaList).Sort(AddressOf JavaSorter)
'修改设置项
Dim AllList As New JArray
@@ -650,7 +655,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(OriginalPath), Results, Source, IsFullSearch)
+ JavaSearchFolder(New DirectoryInfo(ShortenPath(OriginalPath)), Results, Source, IsFullSearch)
Catch ex As UnauthorizedAccessException
Log("[Java] 遍历查找 Java 时遭遇无权限的文件夹:" & OriginalPath)
Catch ex As Exception
@@ -671,18 +676,19 @@ Wait:
'若该目录有 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",
"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", "国服", "网易", "ext", "netease", "1.", "启动"}
+ "4297127d64ec6", "1.", "启动"}
For Each FolderInfo As DirectoryInfo In OriginalPath.EnumerateDirectories
If FolderInfo.Attributes.HasFlag(FileAttributes.ReparsePoint) Then Continue For '跳过符号链接
Dim SearchEntry = GetFolderNameFromPath(FolderInfo.Name).ToLower '用于搜索的字符串
- If IsFullSearch OrElse FolderInfo.Parent.Name.ToLower = "users" OrElse
- Keywords.Any(Function(w) SearchEntry.Contains(w)) OrElse SearchEntry = "bin" Then
+ If IsFullSearch OrElse
+ OriginalPath.Name.ToLower = "users" OrElse Val(SearchEntry) > 0 OrElse Keywords.Any(Function(w) SearchEntry.Contains(w)) OrElse SearchEntry = "bin" Then
JavaSearchFolder(FolderInfo, Results, Source)
End If
Next
@@ -738,7 +744,7 @@ 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 = NetGetCodeByDownload(
+ 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)
@@ -750,7 +756,7 @@ Wait:
Dim Address As String = TargetEntry.Value("manifest")("url")
Log($"[Java] 准备下载 Java {TargetEntry.Value("version")("name")}({TargetEntry.Key}):{Address}")
'获取文件列表
- Dim ListFileStr As String = NetGetCodeByDownload({Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com"), Address}, IsJson:=True)
+ Dim ListFileStr As String = NetGetCodeByLoader({Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com"), Address}, 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")
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
index 26cd470..e9988bd 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
@@ -4,6 +4,7 @@ Public Module ModLaunch
#Region "开始"
+ Public CurrentLaunchOptions As McLaunchOptions = Nothing
Public Class McLaunchOptions
'''
''' 强制指定在启动后进入的服务器 IP。
@@ -20,32 +21,34 @@ Public Module ModLaunch
''' 默认值:Nothing。使用 McVersionCurrent。
'''
Public Version As McVersion = Nothing
+ '''
+ ''' 额外的启动参数。
+ '''
+ Public ExtraArgs As New List(Of String)
End Class
'''
''' 尝试启动 Minecraft。必须在 UI 线程调用。
''' 返回是否实际开始了启动(如果没有,则一定弹出了错误提示)。
'''
Public Function McLaunchStart(Optional Options As McLaunchOptions = Nothing) As Boolean
- Options = If(Options, New McLaunchOptions)
+ CurrentLaunchOptions = If(Options, New McLaunchOptions)
'预检查
- If Not RunInUi() Then
- Throw New Exception("McLaunchStart 必须在 UI 线程调用!")
- End If
+ If Not RunInUi() Then Throw New Exception("McLaunchStart 必须在 UI 线程调用!")
If McLaunchLoader.State = LoadState.Loading Then
Hint("已有游戏正在启动中!", HintType.Critical)
Return False
End If
'强制切换需要启动的版本
- If Options.Version IsNot Nothing AndAlso McVersionCurrent <> Options.Version Then
- McLaunchLog("在启动前切换到版本 " & Options.Version.Name)
+ If CurrentLaunchOptions.Version IsNot Nothing AndAlso McVersionCurrent <> CurrentLaunchOptions.Version Then
+ McLaunchLog("在启动前切换到版本 " & CurrentLaunchOptions.Version.Name)
'检查版本
- Options.Version.Load()
- If Options.Version.State = McVersionState.Error Then
- Hint("无法启动 Minecraft:" & Options.Version.Info, HintType.Critical)
+ CurrentLaunchOptions.Version.Load()
+ If CurrentLaunchOptions.Version.State = McVersionState.Error Then
+ Hint("无法启动 Minecraft:" & CurrentLaunchOptions.Version.Info, HintType.Critical)
Return False
End If
'切换版本
- McVersionCurrent = Options.Version
+ McVersionCurrent = CurrentLaunchOptions.Version
Setup.Set("LaunchVersionSelect", McVersionCurrent.Name)
FrmLaunchLeft.RefreshButtonsUI()
FrmLaunchLeft.RefreshPage(False, False)
@@ -95,7 +98,7 @@ Public Module ModLaunch
McLaunchPrecheck()
McLaunchLog("预检测已通过")
Catch ex As Exception
- Hint(ex.Message, HintType.Critical)
+ If Not ex.Message.StartsWithF("$$") Then Hint(ex.Message, HintType.Critical)
Throw
End Try
'正式加载
@@ -103,8 +106,8 @@ Public Module ModLaunch
'构造主加载器
Dim Loaders As New List(Of LoaderBase) From {
New LoaderTask(Of Integer, Integer)("获取 Java", AddressOf McLaunchJava) With {.ProgressWeight = 4, .Block = False},
- McLoginLoader,
- New LoaderCombo(Of String)("补全文件", DlClientFix(McVersionCurrent, False, AssetsIndexExistsBehaviour.DownloadInBackground, True)) With {.ProgressWeight = 15, .Show = False},
+ McLoginLoader, '.ProgressWeight = 15, .Block = False
+ New LoaderCombo(Of String)("补全文件", DlClientFix(McVersionCurrent, False, AssetsIndexExistsBehaviour.DownloadInBackground)) With {.ProgressWeight = 15, .Show = False},
New LoaderTask(Of String, List(Of McLibToken))("获取启动参数", AddressOf McLaunchArgumentMain) With {.ProgressWeight = 2},
New LoaderTask(Of List(Of McLibToken), Integer)("解压文件", AddressOf McLaunchNatives) With {.ProgressWeight = 2},
New LoaderTask(Of Integer, Integer)("预启动处理", AddressOf McLaunchPrerun) With {.ProgressWeight = 1},
@@ -144,7 +147,7 @@ Public Module ModLaunch
Hint(McVersionCurrent.Name & " 启动成功!", HintType.Finish)
Case LoadState.Aborted
If AbortHint Is Nothing Then
- Hint(If(Loader.Input.SaveBatch Is Nothing, "已取消启动!", "已取消导出启动脚本!"), HintType.Info)
+ Hint(If(CurrentLaunchOptions?.SaveBatch Is Nothing, "已取消启动!", "已取消导出启动脚本!"), HintType.Info)
Else
Hint(AbortHint, HintType.Finish)
End If
@@ -159,7 +162,7 @@ NextInner:
If CurrentEx.Message.StartsWithF("$") Then
'若有以 $ 开头的错误信息,则以此为准显示提示
'若错误信息为 $$,则不提示
- If Not CurrentEx.Message = "$$" Then MyMsgBox(CurrentEx.Message.TrimStart("$"), If(Loader.Input.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败"))
+ If Not CurrentEx.Message = "$$" Then MyMsgBox(CurrentEx.Message.TrimStart("$"), If(CurrentLaunchOptions?.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败"))
Throw
ElseIf CurrentEx.InnerException IsNot Nothing Then
'检查下一级错误
@@ -168,7 +171,9 @@ NextInner:
Else
'没有特殊处理过的错误信息
McLaunchLog("错误:" & GetExceptionDetail(ex))
- Log(ex, If(Loader.Input.SaveBatch Is Nothing, "Minecraft 启动失败", "导出启动脚本失败"), LogLevel.Msgbox, If(Loader.Input.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败"))
+ Log(ex,
+ If(CurrentLaunchOptions?.SaveBatch Is Nothing, "Minecraft 启动失败", "导出启动脚本失败"), LogLevel.Msgbox,
+ If(CurrentLaunchOptions?.SaveBatch Is Nothing, "启动失败", "导出启动脚本失败"))
Throw
End If
End Try
@@ -211,36 +216,51 @@ NextInner:
If McVersionCurrent.State = McVersionState.Error Then Throw New Exception("Minecraft 存在问题:" & McVersionCurrent.Info)
'检查输入信息
Dim CheckResult As String = ""
- RunInUiWait(Sub()
- Dim LoginInput As McLoginData = McLoginInput()
- CheckResult = McLoginAble(LoginInput)
- End Sub)
+ RunInUiWait(Sub() CheckResult = McLoginAble(McLoginInput()))
If CheckResult <> "" Then Throw New ArgumentException(CheckResult)
#If BETA Then
'求赞助
- RunInNewThread(Sub()
- Select Case Setup.Get("SystemLaunchCount")
- Case 20, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1700, 1900, 2100, 2300, 2500
- If MyMsgBox("PCL 已经为你启动了 " & Setup.Get("SystemLaunchCount") & " 次游戏啦!" & vbCrLf &
- "如果觉得 PCL 还算好用的话,也可以考虑赞助一下作者……一点心意也行……" & vbCrLf &
- "毕竟一个人开发也不容易(悲)……",
- "求赞助啦……", "这就赞助!", "但是我拒绝") = 1 Then
- OpenWebsite("https://afdian.com/a/LTCat")
- End If
- End Select
- End Sub, "Donate")
+ If CurrentLaunchOptions?.SaveBatch Is Nothing Then '保存脚本时不提示
+ RunInNewThread(
+ Sub()
+ Select Case Setup.Get("SystemLaunchCount")
+ Case 10, 20, 40, 60, 80, 100, 120, 150, 200, 250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000
+ If MyMsgBox("PCL 已经为你启动了 " & Setup.Get("SystemLaunchCount") & " 次游戏啦!" & vbCrLf &
+ "如果 PCL 还算好用的话,能不能考虑赞助一下 PCL……" & vbCrLf &
+ "如果没有大家的支持,PCL 很难在免费、无任何广告的情况下维持数年的更新(磕头)……!",
+ Setup.Get("SystemLaunchCount") & " 次启动!", "支持 PCL!", "但是我拒绝") = 1 Then
+ OpenWebsite("https://afdian.com/a/LTCat")
+ End If
+ End Select
+ End Sub, "Donate")
+ End If
#End If
'正版购买提示
- If Not Setup.Get("HintBuy") AndAlso Setup.Get("LoginType") <> McLoginType.Ms Then
- Select Case Setup.Get("SystemLaunchCount")
- Case 10, 35, 75, 125, 175, 225, 275, 325, 375, 425, 475, 550, 650, 750, 850, 950, 1050, 1150, 1250, 1350, 1450, 1600, 1800, 2000, 2200, 2400
- If MyMsgBox("你已经启动了 " & Setup.Get("SystemLaunchCount") & " 次 Minecraft 啦!" & vbCrLf &
- "如果觉得 Minecraft 还不错,也可以考虑购买正版支持一下,毕竟开发游戏也很不容易……" & vbCrLf &
- "在你登录一次正版账号后,就不会再出现这个提示了。",
- "考虑一下正版?", "购买正版游戏", "下次一定") = 1 Then
- OpenWebsite("https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj")
- End If
- End Select
+ If CurrentLaunchOptions?.SaveBatch Is Nothing AndAlso '保存脚本时不提示
+ Not Setup.Get("HintBuy") AndAlso Setup.Get("LoginType") <> McLoginType.Ms Then
+ If IsSystemLanguageChinese() Then
+ RunInNewThread(
+ Sub()
+ Select Case Setup.Get("SystemLaunchCount")
+ Case 3, 8, 15, 30, 50, 70, 90, 110, 130, 180, 220, 280, 330, 380, 450, 550, 660, 750, 880, 950, 1100, 1300, 1500, 1700, 1900
+ If MyMsgBox("你已经启动了 " & Setup.Get("SystemLaunchCount") & " 次 Minecraft 啦!" & vbCrLf &
+ "如果觉得 Minecraft 还不错,可以购买正版支持一下,毕竟开发游戏也真的很不容易……不要一直白嫖啦。" & vbCrLf & vbCrLf &
+ "在登录一次正版账号后,就不会再出现这个提示了!",
+ "考虑一下正版?", "支持正版游戏!", "下次一定") = 1 Then
+ OpenWebsite("https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj")
+ End If
+ End Select
+ End Sub, "Buy Minecraft")
+ ElseIf Setup.Get("LoginType") = McLoginType.Legacy Then
+ Select Case MyMsgBox("你必须先登录正版账号,才能进行离线登录!", "正版验证", "购买正版", "试玩", "返回",
+ Button1Action:=Sub() OpenWebsite("https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj"))
+ Case 2
+ Hint("游戏将以试玩模式启动!", HintType.Critical)
+ CurrentLaunchOptions.ExtraArgs.Add("--demo")
+ Case 3
+ Throw New Exception("$$")
+ End Select
+ End If
End If
End Sub
@@ -364,19 +384,19 @@ NextInner:
'根据当前登录方式优先返回
Select Case Setup.Get("LoginType")
Case McLoginType.Ms
- If Setup.Get("CacheMsName") <> "" Then Return Setup.Get("CacheMsName")
+ If Setup.Get("CacheMsV2Name") <> "" Then Return Setup.Get("CacheMsV2Name")
Case McLoginType.Legacy
- If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.Before("¨")
+ If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.BeforeFirst("¨")
Case McLoginType.Nide
If Setup.Get("CacheNideName") <> "" Then Return Setup.Get("CacheNideName")
Case McLoginType.Auth
If Setup.Get("CacheAuthName") <> "" Then Return Setup.Get("CacheAuthName")
End Select
'查找所有可能的项
- If Setup.Get("CacheMsName") <> "" Then Return Setup.Get("CacheMsName")
+ If Setup.Get("CacheMsV2Name") <> "" Then Return Setup.Get("CacheMsV2Name")
If Setup.Get("CacheNideName") <> "" Then Return Setup.Get("CacheNideName")
If Setup.Get("CacheAuthName") <> "" Then Return Setup.Get("CacheAuthName")
- If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.Before("¨")
+ If Setup.Get("LoginLegacyName") <> "" Then Return Setup.Get("LoginLegacyName").ToString.BeforeFirst("¨")
Return Nothing
End Function
'''
@@ -385,7 +405,7 @@ NextInner:
Public Function McLoginAble() As String
Select Case Setup.Get("LoginType")
Case McLoginType.Ms
- If Setup.Get("CacheMsOAuthRefresh") = "" Then
+ If Setup.Get("CacheMsV2OAuthRefresh") = "" Then
Return FrmLoginMs.IsVaild()
Else
Return ""
@@ -436,7 +456,7 @@ NextInner:
Case McLoginType.Legacy
LoginData = PageLoginLegacy.GetLoginData()
Case McLoginType.Ms
- If Setup.Get("CacheMsOAuthRefresh") = "" Then
+ If Setup.Get("CacheMsV2OAuthRefresh") = "" Then
LoginData = PageLoginMs.GetLoginData()
Else
LoginData = PageLoginMsSkin.GetLoginData()
@@ -487,79 +507,77 @@ NextInner:
#Region "分方式登录模块"
'各个登录方式的主对象与输入构造
- Public McLoginMsLoader As New LoaderTask(Of McLoginMs, McLoginResult)("Loader Login Ms", AddressOf McLoginMsStart) With {.ReloadTimeout = 300000}
+ Public McLoginMsLoader As New LoaderTask(Of McLoginMs, McLoginResult)("Loader Login Ms", AddressOf McLoginMsStart) With {.ReloadTimeout = 1}
Public McLoginLegacyLoader As New LoaderTask(Of McLoginLegacy, McLoginResult)("Loader Login Legacy", AddressOf McLoginLegacyStart)
- Public McLoginNideLoader As New LoaderTask(Of McLoginServer, McLoginResult)("Loader Login Nide", AddressOf McLoginServerStart) With {.ReloadTimeout = 60000}
- Public McLoginAuthLoader As New LoaderTask(Of McLoginServer, McLoginResult)("Loader Login Auth", AddressOf McLoginServerStart) With {.ReloadTimeout = 60000}
+ Public McLoginNideLoader As New LoaderTask(Of McLoginServer, McLoginResult)("Loader Login Nide", AddressOf McLoginServerStart) With {.ReloadTimeout = 1000 * 60 * 10}
+ Public McLoginAuthLoader As New LoaderTask(Of McLoginServer, McLoginResult)("Loader Login Auth", AddressOf McLoginServerStart) With {.ReloadTimeout = 1000 * 60 * 10}
'主加载函数,返回所有需要的登录信息
+ Private McLoginMsRefreshTime As Long = 0 '上次刷新登录的时间
Private Sub McLoginMsStart(Data As LoaderTask(Of McLoginMs, McLoginResult))
Dim Input As McLoginMs = Data.Input
Dim LogUsername As String = Input.UserName
McLaunchLog("登录方式:正版(" & If(LogUsername = "", "尚未登录", LogUsername) & ")")
Data.Progress = 0.05
'检查是否已经登录完成
- If Input.AccessToken <> "" AndAlso Not Data.IsForceRestarting Then
+ If Not Data.IsForceRestarting AndAlso '不要求强行重启
+ Input.AccessToken <> "" AndAlso '已经登录过了
+ (McLoginMsRefreshTime > 0 AndAlso GetTimeTick() - McLoginMsRefreshTime < 1000 * 60 * 10) Then '完成时间在 10 分钟内
Data.Output = New McLoginResult With
{.AccessToken = Input.AccessToken, .Name = Input.UserName, .Uuid = Input.Uuid, .Type = "Microsoft", .ClientToken = Input.Uuid, .ProfileJson = Input.ProfileJson}
GoTo SkipLogin
End If
'尝试登录
Dim OAuthTokens As String()
- Dim ClientId As String = ""
If Input.OAuthRefreshToken = "" Then
'无 RefreshToken
Relogin:
- Dim OAuthCode As String = MsLoginStep1(Data)
- If OAuthCode = "Cancel" Then Exit Sub
- If Data.IsAborted Then Throw New ThreadInterruptedException
- Data.Progress = 0.2
- OAuthTokens = MsLoginStep2(OAuthCode, False, ClientId)
+ OAuthTokens = MsLoginStep1New(Data)
Else
'有 RefreshToken
- OAuthTokens = MsLoginStep2(Input.OAuthRefreshToken, True, ClientId)
+ OAuthTokens = MsLoginStep1Refresh(Input.OAuthRefreshToken)
+ If OAuthTokens(0) = "Relogin" Then GoTo Relogin '要求重新打开登录网页认证
End If
- '要求重新打开登录网页认证
- If OAuthTokens(0) = "Cancel" Then Exit Sub
- If OAuthTokens(0) = "Relogin" Then GoTo Relogin
- Data.Progress = 0.35
+ If Data.IsAborted Then Throw New ThreadInterruptedException
+ Data.Progress = 0.25
If Data.IsAborted Then Throw New ThreadInterruptedException
Dim OAuthAccessToken As String = OAuthTokens(0)
Dim OAuthRefreshToken As String = OAuthTokens(1)
- Dim XBLToken As String = MsLoginStep3(OAuthAccessToken)
- Data.Progress = 0.5
+ Dim XBLToken As String = MsLoginStep2(OAuthAccessToken)
+ Data.Progress = 0.4
If Data.IsAborted Then Throw New ThreadInterruptedException
- Dim Tokens = MsLoginStep4(XBLToken)
- Data.Progress = 0.65
+ Dim Tokens = MsLoginStep3(XBLToken)
+ Data.Progress = 0.55
If Data.IsAborted Then Throw New ThreadInterruptedException
- Dim AccessToken As String = MsLoginStep5(Tokens)
- Data.Progress = 0.8
+ Dim AccessToken As String = MsLoginStep4(Tokens)
+ Data.Progress = 0.7
If Data.IsAborted Then Throw New ThreadInterruptedException
- MsLoginStep6(AccessToken)
- Data.Progress = 0.9
+ MsLoginStep5(AccessToken)
+ Data.Progress = 0.85
If Data.IsAborted Then Throw New ThreadInterruptedException
- Dim Result = MsLoginStep7(AccessToken)
+ Dim Result = MsLoginStep6(AccessToken)
+ Data.Progress = 0.98
'输出登录结果
- Setup.Set("CacheMsOAuthRefresh", OAuthRefreshToken)
- Setup.Set("CacheMsAccess", AccessToken)
- Setup.Set("CacheMsUuid", Result(0))
- Setup.Set("CacheMsName", Result(1))
- Setup.Set("CacheMsProfileJson", Result(2))
+ Setup.Set("CacheMsV2OAuthRefresh", OAuthRefreshToken)
+ Setup.Set("CacheMsV2Access", AccessToken)
+ Setup.Set("CacheMsV2Uuid", Result(0))
+ Setup.Set("CacheMsV2Name", Result(1))
+ Setup.Set("CacheMsV2ProfileJson", Result(2))
Dim MsJson As JObject = GetJson(Setup.Get("LoginMsJson"))
MsJson.Remove(Input.UserName) '如果更改了玩家名……
MsJson(Result(1)) = OAuthRefreshToken
Setup.Set("LoginMsJson", MsJson.ToString(Newtonsoft.Json.Formatting.None))
Data.Output = New McLoginResult With {.AccessToken = AccessToken, .Name = Result(1), .Uuid = Result(0), .Type = "Microsoft", .ClientToken = Result(0), .ProfileJson = Result(2)}
- '解锁主题
-SkipLogin:
+ '结束
+ McLoginMsRefreshTime = GetTimeTick()
McLaunchLog("微软登录完成")
+SkipLogin:
Setup.Set("HintBuy", True) '关闭正版购买提示
- Data.Progress = 0.98
If ThemeUnlock(10, False) Then MyMsgBox("感谢你对正版游戏的支持!" & vbCrLf & "隐藏主题 跳票红 已解锁!", "提示")
End Sub
Private Sub McLoginServerStart(Data As LoaderTask(Of McLoginServer, McLoginResult))
Dim Input As McLoginServer = Data.Input
- Dim NeedRefresh As Boolean = False
+ 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)
@@ -580,7 +598,7 @@ SkipLogin:
McLoginRequestValidate(Data)
GoTo LoginFinish
Catch ex As Exception
- Dim AllMessage = GetExceptionSummary(ex)
+ Dim AllMessage = GetExceptionDetail(ex)
McLaunchLog("验证登录失败:" & AllMessage)
If (AllMessage.Contains("超时") OrElse AllMessage.Contains("imeout")) AndAlso Not AllMessage.Contains("403") Then
McLaunchLog("已触发超时登录失败")
@@ -595,7 +613,8 @@ Refresh:
McLoginRequestRefresh(Data, NeedRefresh)
GoTo LoginFinish
Catch ex As Exception
- McLaunchLog("刷新登录失败:" & GetExceptionSummary(ex))
+ McLaunchLog("刷新登录失败:" & GetExceptionDetail(ex))
+ If WasRefreshed Then Throw New Exception("二轮刷新登录失败", ex)
End Try
Data.Progress = If(NeedRefresh, 0.85, 0.45)
End If
@@ -604,10 +623,12 @@ Refresh:
If Data.IsAborted Then Throw New ThreadInterruptedException
NeedRefresh = McLoginRequestLogin(Data)
Catch ex As Exception
- McLaunchLog("登录失败:" & GetExceptionSummary(ex))
+ McLaunchLog("登录失败:" & GetExceptionDetail(ex))
Throw
End Try
If NeedRefresh Then
+ McLaunchLog("重新进行刷新登录")
+ WasRefreshed = True
Data.Progress = 0.65
GoTo Refresh
End If
@@ -671,7 +692,7 @@ LoginFinish:
Url:=Data.Input.BaseUrl & "/validate",
Method:="POST",
Data:=RequestData.ToString(0),
- Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh_CN"}},
+ Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}},
ContentType:="application/json; charset=utf-8") '没有返回值的
'将登录结果输出
Data.Output.AccessToken = AccessToken
@@ -687,15 +708,15 @@ LoginFinish:
Dim LoginJson As JObject = GetJson(NetRequestRetry(
Url:=Data.Input.BaseUrl & "/refresh",
Method:="POST",
- Data:="{" &
- If(RequestUser, "
- ""requestUser"": true,
- ""selectedProfile"": {
- ""id"":""" & Setup.Get("Cache" & Data.Input.Token & "Uuid") & """,
- ""name"":""" & Setup.Get("Cache" & Data.Input.Token & "Name") & """},", "") & "
- ""accessToken"":""" & Setup.Get("Cache" & Data.Input.Token & "Access") & """,
- ""clientToken"":""" & Setup.Get("Cache" & Data.Input.Token & "Client") & """}",
- Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh_CN"}},
+ Data:=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"))
+ )),
+ 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"))
'将登录结果输出
If LoginJson("selectedProfile") Is Nothing Then Throw New Exception("选择的角色 " & Setup.Get("Cache" & Data.Input.Token & "Name") & " 无效!")
@@ -726,7 +747,7 @@ LoginFinish:
Url:=Data.Input.BaseUrl & "/authenticate",
Method:="POST",
Data:=RequestData.ToString(0),
- Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh_CN"}},
+ Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}},
ContentType:="application/json; charset=utf-8"))
'检查登录结果
If LoginJson("availableProfiles").Count = 0 Then
@@ -751,17 +772,18 @@ LoginFinish:
'缓存无效,要求玩家选择
If SelectedName Is Nothing Then
McLaunchLog("要求玩家选择角色")
- RunInUiWait(Sub()
- Dim SelectionControl As New List(Of IMyRadio)
- Dim SelectionJson As New List(Of JToken)
- For Each Profile In LoginJson("availableProfiles")
- SelectionControl.Add(New MyRadioBox With {.Text = Profile("name").ToString})
- SelectionJson.Add(Profile)
- Next
- Dim SelectedIndex As Integer = MyMsgBoxSelect(SelectionControl, "选择使用的角色")
- SelectedName = SelectionJson(SelectedIndex)("name").ToString
- SelectedId = SelectionJson(SelectedIndex)("id").ToString
- End Sub)
+ RunInUiWait(
+ Sub()
+ Dim SelectionControl As New List(Of IMyRadio)
+ Dim SelectionJson As New List(Of JToken)
+ For Each Profile In LoginJson("availableProfiles")
+ SelectionControl.Add(New MyRadioBox With {.Text = Profile("name").ToString})
+ SelectionJson.Add(Profile)
+ Next
+ Dim SelectedIndex As Integer = MyMsgBoxSelect(SelectionControl, "选择使用的角色")
+ SelectedName = SelectionJson(SelectedIndex)("name").ToString
+ SelectedId = SelectionJson(SelectedIndex)("id").ToString
+ End Sub)
McLaunchLog("玩家选择的角色:" & SelectedName)
End If
Else
@@ -793,7 +815,19 @@ LoginFinish:
ErrorMessage = GetJson(DirectCast(ex, ResponsedWebException).Response)("errorMessage")
Catch
End Try
- If Not String.IsNullOrWhiteSpace(ErrorMessage) Then Throw New Exception("$登录失败:" & ErrorMessage)
+ If Not String.IsNullOrWhiteSpace(ErrorMessage) Then
+ If ErrorMessage.Contains("密码错误") OrElse ErrorMessage.ContainsF("Incorrect username or password", True) Then
+ '密码错误,退出登录 (#5090)
+ McLaunchLog("密码错误,退出登录")
+ Select Case Data.Input.Type
+ Case McLoginType.Auth
+ RunInUi(AddressOf PageLoginAuthSkin.ExitLogin)
+ Case McLoginType.Nide
+ RunInUi(AddressOf PageLoginNideSkin.ExitLogin)
+ End Select
+ End If
+ Throw New Exception("$登录失败:" & ErrorMessage)
+ End If
End If
'通用关键字检测
If AllMessage.Contains("403") Then
@@ -821,192 +855,82 @@ LoginFinish:
End Try
End Function
- '微软登录步骤 1:打开网页认证,获取 OAuth Code
- Private Function MsLoginStep1(Data As LoaderTask(Of McLoginMs, McLoginResult)) As String
- McLaunchLog("开始微软登录步骤 1")
- OpenWebsite(FormLoginOAuth.LoginUrl1)
- Dim Result As String =
- MyMsgBoxInput("等待网页登录",
- "登录完成后,网页会变得完全空白。把那个空白网页的网址复制到下面的框中就行!" & vbCrLf &
- "如果网络环境不佳,它可能一直加载不出来,那就只能试试用 VPN 或加速器了。",
- ValidateRules:=New ObjectModel.Collection(Of Validate) From {New ValidateRegex("(?<=code\=)[^&]+", "返回网址应以 https://login.live.com/oauth20_desktop.srf?code= 开头")},
- HintText:="https://login.live.com/oauth20_desktop.srf?code=XXXXXX")
- If Result Is Nothing Then
- McLaunchLog("微软登录已在步骤 1 被取消")
- Throw New ThreadInterruptedException("$$")
- Else
- Return RegexSeek(Result, "(?<=code\=)[^&]+")
- End If
- End Function
+ '微软登录步骤 1,原始登录:获取 DeviceCode 并开启登录网页
+ Private Function MsLoginStep1New(Data As LoaderTask(Of McLoginMs, McLoginResult)) As String()
+ '参考:https://learn.microsoft.com/zh-cn/entra/identity-platform/v2-oauth2-device-code
- '微软登录步骤 2:从 OAuth Code 或 OAuth RefreshToken 获取 {OAuth AccessToken, OAuth RefreshToken}
- Private Function MsLoginStep2(Code As String, IsRefresh As Boolean, ClientId As String, Optional ExpiresIn As String = "900") As String()
- McLaunchLog("开始微软登录步骤 2(" & If(IsRefresh, "", "非") & "刷新登录)")
+ '初始请求
+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"))
+ McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString)
- Dim Request As String
- If IsRefresh Then
- Request = "grant_type=refresh_token" & "&" &
- "client_id=" & ClientId & "&" &
- "device_code=" & Code & "&" &
- "refresh_token=" & Uri.EscapeDataString(Code) & "&" &
- "scope=XboxLive.signin%20offline_access"
- Else
- Request = "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" &
- "client_id=" & ClientId & "&" &
- "device_code=" & Code & "&" &
- "scope=XboxLive.signin%20offline_access"
- End If
- Dim Result As String
- Dim stopwatch As Stopwatch = Stopwatch.StartNew()
-
- While stopwatch.Elapsed < TimeSpan.FromSeconds(ExpiresIn)
- Try
- Result = NetRequestMulty("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", "POST", Request, "application/x-www-form-urlencoded", 2)
- Catch ex As Exception
- If ex.Message.Contains("must sign in again") OrElse ex.Message.Contains("invalid_grant") Then '#269
- Return {"Relogin", ""}
- ElseIf ex.Message.Contains("authorization_declined") Then
- Hint("你拒绝了 PCL2 的访问权限申请,验证过程被中断!")
- Return {"Cancel", ""}
- ElseIf ex.Message.Contains("expired_token") Then
- Hint("Token 已过期,请尝试重新验证!")
- Exit While
- ElseIf ex.Message.Contains("AADSTS70016") Then
- Continue While
- Else
- Throw
- End If
- End Try
-
- If Result IsNot Nothing Then
- Dim ResultJson As JObject = GetJson(Result)
- Dim AccessToken As String = ResultJson("access_token").ToString
- Dim RefreshToken As String = ResultJson("refresh_token").ToString
- Return {AccessToken, RefreshToken}
- End If
-
- Thread.Sleep(1000)
+ '弹窗
+ Dim Converter As New MyMsgBoxConverter With {.Content = PrepareJson, .ForceWait = True, .Type = MyMsgBoxType.Login}
+ WaitingMyMsgBox.Add(Converter)
+ While Converter.Result Is Nothing
+ Thread.Sleep(100)
End While
-
- Hint("验证超时,请尝试重新验证!")
- Return {"Cancel", ""}
-
+ If TypeOf Converter.Result Is RestartException Then
+ If MyMsgBox($"请在登录时选择 {vbLQ}其他登录方法{vbRQ},然后选择 {vbLQ}使用我的密码{vbRQ}。{vbCrLf}如果没有该选项,请选择 {vbLQ}设置密码{vbRQ},设置完毕后再登录。",
+ "需要使用密码登录", "重新登录", "设置密码", "取消",
+ Button2Action:=Sub() OpenWebsite("https://account.live.com/password/Change")) = 1 Then
+ GoTo Retry
+ Else
+ Throw New Exception("$$")
+ End If
+ ElseIf TypeOf Converter.Result Is Exception Then
+ Throw CType(Converter.Result, Exception)
+ Else
+ Return Converter.Result
+ End If
End Function
+ '微软登录步骤 1,刷新登录:从 OAuth Code 或 OAuth RefreshToken 获取 {OAuth AccessToken, OAuth RefreshToken}
+ Private Function MsLoginStep1Refresh(Code As String) As String()
+ McLaunchLog("开始微软登录步骤 1/6(刷新登录)")
- ''微软登录步骤 1:获取 DeviceCode 并显示验证提示
- ''https://learn.microsoft.com/zh-cn/entra/identity-platform/v2-oauth2-device-code
- 'Private Function MsLoginStep1(Data As LoaderTask(Of McLoginMs, McLoginResult)) As String
- ' McLaunchLog("开始微软登录步骤 1:获取验证信息")
- ' Dim PrepareJson As JObject = GetJson(NetRequestMulty("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", "POST",
- ' $"client_id={OAuthClientId}&scope=XboxLive.signin%20offline_access", "application/x-www-form-urlencoded", 2))
+ 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
+ Return {"Relogin", ""}
+ Else
+ Throw
+ End If
+ End Try
- ' '从这里开始直到步骤 2 结束都没有 Review
- ' '参考:https://learn.microsoft.com/zh-cn/entra/identity-platform/v2-oauth2-device-code
- ' If MyMsgBox("请在打开的网页里输入 PCL2 提供的设备代码(会自动复制到剪贴板)。" & vbCrLf &
- ' "设备代码有时效性,若操作时间过长需要重新进行登录流程。", "正版验证确认", "继续", "取消") <> 1 Then
- ' Return "Cancel"
- ' End If
-
- ' Dim DeviceCode As String = PrepareJson("device_code")
- ' Dim UserCode As String = PrepareJson("user_code")
- ' Dim VerifyUri As String = PrepareJson("verification_uri")
- ' 'ResultJson("expires_in")
- ' ClipboardSet(UserCode)
- ' OpenWebsite(VerifyUri)
-
- ' Dim MsgBoxValue As String = ""
- ' While MsgBoxValue IsNot "1"
- ' MsgBoxValue = MyMsgBox("本次验证的设备代码为:" & UserCode & vbCrLf & vbCrLf & "你也可以在任意设备上打开下列网址进行验证:" & vbCrLf & VerifyUri & vbCrLf & vbCrLf & "在验证完成后,你可以直接关闭这个弹窗,PCL2 会自动完成接下来的流程。", "正版验证", "关闭", "复制网址", "复制设备代码").ToString
- ' If MsgBoxValue = "2" Then
- ' ClipboardSet(VerifyUri)
- ' Continue While
- ' ElseIf MsgBoxValue = "3" Then
- ' ClipboardSet(UserCode)
- ' Continue While
- ' ElseIf MsgBoxValue = "1" Then
- ' Exit While
- ' End If
- ' End While
-
- ' Return DeviceCode
- 'End Function
-
- ''微软登录步骤 2:从 OAuth Code 或 OAuth RefreshToken 获取 {OAuth AccessToken, OAuth RefreshToken}
- 'Private Function MsLoginStep2(Code As String, IsRefresh As Boolean, Optional ExpiresIn As String = "900") As String()
- ' McLaunchLog("开始微软登录步骤 2(" & If(IsRefresh, "", "非") & "刷新登录)")
-
- ' Dim Request As String
- ' If IsRefresh Then
- ' Request = "grant_type=refresh_token" & "&" &
- ' "client_id=" & OAuthClientId & "&" &
- ' "device_code=" & Code & "&" &
- ' "refresh_token=" & Uri.EscapeDataString(Code) & "&" &
- ' "scope=XboxLive.signin%20offline_access"
- ' Else
- ' Request = "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" &
- ' "client_id=" & OAuthClientId & "&" &
- ' "device_code=" & Code & "&" &
- ' "scope=XboxLive.signin%20offline_access"
- ' End If
- ' Dim Result As String
- ' Dim stopwatch As Stopwatch = Stopwatch.StartNew()
-
- ' While stopwatch.Elapsed < TimeSpan.FromSeconds(ExpiresIn)
- ' Try
- ' Result = NetRequestMulty("https://login.microsoftonline.com/consumers/oauth2/v2.0/token", "POST", Request, "application/x-www-form-urlencoded", 2)
- ' Catch ex As Exception
- ' If ex.Message.Contains("must sign in again") OrElse ex.Message.Contains("invalid_grant") Then '#269
- ' Return {"Relogin", ""}
- ' ElseIf ex.Message.Contains("authorization_declined") Then
- ' Hint("你拒绝了 PCL2 的访问权限申请,验证过程被中断!")
- ' Return {"Cancel", ""}
- ' ElseIf ex.Message.Contains("expired_token") Then
- ' Hint("Token 已过期,请尝试重新验证!")
- ' Exit While
- ' ElseIf ex.Message.Contains("AADSTS70016") Then
- ' Continue While
- ' Else
- ' Throw
- ' End If
- ' End Try
-
- ' If Result IsNot Nothing Then
- ' Dim ResultJson As JObject = GetJson(Result)
- ' Dim AccessToken As String = ResultJson("access_token").ToString
- ' Dim RefreshToken As String = ResultJson("refresh_token").ToString
- ' Return {AccessToken, RefreshToken}
- ' End If
-
- ' Thread.Sleep(1000)
- ' End While
-
- ' Hint("验证超时,请尝试重新验证!")
- ' Return {"Cancel", ""}
-
- 'End Function
-
- '微软登录步骤 3:从 OAuth AccessToken 获取 XBLToken
- Private Function MsLoginStep3(AccessToken As String) As String
- McLaunchLog("开始微软登录步骤 3")
+ Dim ResultJson As JObject = GetJson(Result)
+ Dim AccessToken As String = ResultJson("access_token").ToString
+ Dim RefreshToken As String = ResultJson("refresh_token").ToString
+ Return {AccessToken, RefreshToken}
+ End Function
+ '微软登录步骤 2:从 OAuth AccessToken 获取 XBLToken
+ Private Function MsLoginStep2(AccessToken As String) As String
+ McLaunchLog("开始微软登录步骤 2/6")
Dim Request As String = "{
""Properties"": {
""AuthMethod"": ""RPS"",
""SiteName"": ""user.auth.xboxlive.com"",
- ""RpsTicket"": """ & AccessToken & """
+ ""RpsTicket"": """ & If(AccessToken.StartsWithF("d="), "", "d=") & AccessToken & """
},
""RelyingParty"": ""http://auth.xboxlive.com"",
""TokenType"": ""JWT""
- }" 'TODO: 新版登录改为 ""RpsTicket"": ""d=" & AccessToken & """
- Dim Result As String = NetRequestMulty("https://user.auth.xboxlive.com/user/authenticate", "POST", Request, "application/json", 3)
+ }"
+ Dim Result As String = NetRequestMultiple("https://user.auth.xboxlive.com/user/authenticate", "POST", Request, "application/json", 3)
Dim ResultJson As JObject = GetJson(Result)
Dim XBLToken As String = ResultJson("Token").ToString
Return XBLToken
End Function
- '微软登录步骤 4:从 XBLToken 获取 {XSTSToken, UHS}
- Private Function MsLoginStep4(XBLToken As String) As String()
- McLaunchLog("开始微软登录步骤 4")
+ '微软登录步骤 3:从 XBLToken 获取 {XSTSToken, UHS}
+ Private Function MsLoginStep3(XBLToken As String) As String()
+ McLaunchLog("开始微软登录步骤 3/6")
Dim Request As String = "{
""Properties"": {
@@ -1020,7 +944,7 @@ LoginFinish:
}"
Dim Result As String
Try
- Result = NetRequestMulty("https://xsts.auth.xboxlive.com/xsts/authorize", "POST", Request, "application/json", 3)
+ Result = NetRequestMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", "POST", Request, "application/json", 3)
Catch ex As Net.WebException
'参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js
If ex.Message.Contains("2148916227") Then
@@ -1056,14 +980,14 @@ LoginFinish:
Dim UHS As String = ResultJson("DisplayClaims")("xui")(0)("uhs").ToString
Return {XSTSToken, UHS}
End Function
- '微软登录步骤 5:从 {XSTSToken, UHS} 获取 Minecraft AccessToken
- Private Function MsLoginStep5(Tokens As String()) As String
- McLaunchLog("开始微软登录步骤 5")
+ '微软登录步骤 4:从 {XSTSToken, UHS} 获取 Minecraft AccessToken
+ Private Function MsLoginStep4(Tokens As String()) As String
+ McLaunchLog("开始微软登录步骤 4/6")
Dim Request As String = New JObject(New JProperty("identityToken", $"XBL3.0 x={Tokens(1)};{Tokens(0)}")).ToString(0)
Dim Result As String
Try
- Result = NetRequestMulty("https://api.minecraftservices.com/authentication/login_with_xbox", "POST", Request, "application/json", 2)
+ Result = NetRequestRetry("https://api.minecraftservices.com/authentication/login_with_xbox", "POST", Request, "application/json")
Catch ex As Net.WebException
Dim Message As String = GetExceptionSummary(ex)
If Message.Contains("(429)") Then
@@ -1081,11 +1005,11 @@ LoginFinish:
Dim AccessToken As String = ResultJson("access_token").ToString
Return AccessToken
End Function
- '微软登录步骤 6:验证微软账号是否持有 MC,这也会刷新 XGP
- Private Sub MsLoginStep6(AccessToken As String)
- McLaunchLog("开始微软登录步骤 6")
+ '微软登录步骤 5:验证微软账号是否持有 MC,这也会刷新 XGP
+ Private Sub MsLoginStep5(AccessToken As String)
+ McLaunchLog("开始微软登录步骤 5/6")
- Dim Result As String = NetRequestMulty("https://api.minecraftservices.com/entitlements/mcstore", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
+ Dim Result As String = NetRequestMultiple("https://api.minecraftservices.com/entitlements/mcstore", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
Try
Dim ResultJson As JObject = GetJson(Result)
If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any) Then
@@ -1100,13 +1024,13 @@ LoginFinish:
Throw
End Try
End Sub
- '微软登录步骤 7:从 Minecraft AccessToken 获取 {UUID, UserName, ProfileJson}
- Private Function MsLoginStep7(AccessToken As String) As String()
- McLaunchLog("开始微软登录步骤 7")
+ '微软登录步骤 6:从 Minecraft AccessToken 获取 {UUID, UserName, ProfileJson}
+ Private Function MsLoginStep6(AccessToken As String) As String()
+ McLaunchLog("开始微软登录步骤 6/6")
Dim Result As String
Try
- Result = NetRequestMulty("https://api.minecraftservices.com/minecraft/profile", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
+ 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)
If Message.Contains("(429)") Then
@@ -1154,7 +1078,8 @@ LoginFinish:
Case 3
'使用正版用户名
Try
- If SkinName <> "" AndAlso McVersionCurrent.Version.McCodeMain < 20 Then '1.20+ 或快照版不能使用该项(#3746)
+ If SkinName <> "" AndAlso
+ McVersionCurrent IsNot Nothing AndAlso McVersionCurrent.Version.McCodeMain < 20 Then '1.20+ 或快照版不能使用该项(#3746)
Log("[Skin] 由于离线皮肤设置,使用正版 UUID:" & SkinName)
Uuid = McLoginMojangUuid(SkinName, False)
End If
@@ -1229,6 +1154,11 @@ LoginFinish:
'1.5.2-:最高 Java 12
MaxVer = New Version(1, 12, 999, 999)
End If
+ If McVersionCurrent.JsonVersion?("java_version") IsNot Nothing Then
+ Dim RecommendedJava As Integer = McVersionCurrent.JsonVersion("java_version").ToObject(Of Integer)
+ McLaunchLog("Mojang 推荐使用 Java " & RecommendedJava)
+ If RecommendedJava >= 22 Then MinVer = New Version(1, RecommendedJava, 0, 0) '潜在的向后兼容
+ End If
'OptiFine 检测
If McVersionCurrent.Version.HasOptiFine Then
@@ -1302,7 +1232,10 @@ LoginFinish:
If Task.IsAborted Then Exit Sub '中断加载会导致 JavaSelect 异常地返回空值,误判找不到 Java
McLaunchLog("无合适的 Java,需要确认是否自动下载")
Dim JavaCode As String
- If MinVer >= New Version(1, 21, 0, 0) Then
+ If MinVer >= New Version(1, 22, 0, 0) Then '潜在的向后兼容
+ JavaCode = MinVer.Minor
+ If Not JavaDownloadConfirm("Java " & JavaCode) Then Throw New Exception("$$")
+ ElseIf MinVer >= New Version(1, 21, 0, 0) Then
JavaCode = 21
If Not JavaDownloadConfirm("Java 21") Then Throw New Exception("$$")
ElseIf MinVer >= New Version(1, 9, 0, 0) Then
@@ -1361,8 +1294,7 @@ LoginFinish:
''' 释放 Java Wrapper 并返回完整文件路径。
'''
Public Function ExtractJavaWrapper() As String
- Dim BaseDir As String = GetJavaWrapperDir()
- Dim WrapperPath As String = BaseDir & "\JavaWrapper.jar"
+ Dim WrapperPath As String = PathPure & "JavaWrapper.jar"
Log("[Java] 选定的 Java Wrapper 路径:" & WrapperPath)
SyncLock ExtractJavaWrapperLock '避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突
Try
@@ -1376,7 +1308,7 @@ LoginFinish:
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex2 As Exception
Log(ex2, "Java Wrapper 文件重新释放失败,将尝试更换文件名重新生成", LogLevel.Developer)
- WrapperPath = BaseDir & "\JavaWrapper2.jar"
+ WrapperPath = PathPure & "JavaWrapper2.jar"
Try
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex3 As Exception
@@ -1392,21 +1324,6 @@ LoginFinish:
End Function
Private ExtractJavaWrapperLock As New Object
- '''
- ''' 获取 Java Wrapper 所在的文件夹,不以 \ 结尾。
- '''
- Public Function GetJavaWrapperDir() As String
- If (Path & "PCL").IsASCII() Then
- Return Path & "PCL"
- ElseIf PathAppdata.IsASCII() Then
- Return PathAppdata.TrimEnd("\")
- ElseIf PathTemp.IsASCII() Then
- Return PathTemp.TrimEnd("\")
- Else
- Return OsDrive & "ProgramData\PCL"
- End If
- End Function
-
'主方法,合并 Jvm、Game、Replace 三部分的参数数据
Private Sub McLaunchArgumentMain(Loader As LoaderTask(Of String, List(Of McLibToken)))
McLaunchLog("开始获取 Minecraft 启动参数")
@@ -1433,6 +1350,12 @@ LoginFinish:
Arguments += " " & McLaunchArgumentsGameNew(McVersionCurrent)
McLaunchLog("新版 Game 参数获取成功")
End If
+ '编码参数(#5818、#5892)
+ 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"
+ End If
'替换参数
Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader)
If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
@@ -1447,8 +1370,12 @@ LoginFinish:
Arguments = Arguments.Replace(" -Dos.name=Windows 10", " -Dos.name=""Windows 10""")
'全屏
If Setup.Get("LaunchArgumentWindowType") = 0 Then Arguments += " --fullscreen"
+ '由 Option 传入的额外参数
+ For Each Arg In CurrentLaunchOptions.ExtraArgs
+ Arguments += " " & Arg.Trim
+ Next
'进服
- Dim Server As String = If(String.IsNullOrEmpty(McLaunchLoader.Input.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), McLaunchLoader.Input.ServerIp)
+ 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
@@ -1499,10 +1426,10 @@ LoginFinish:
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))
+ Setup.Get("VersionServerAuthServer", McVersionCurrent))
Try
Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
- DataList.Insert(0, "-javaagent:""" & PathAppdata & "authlib-injector.jar""=" & Server &
+ DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
" -Dauthlibinjector.side=client" &
" -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
Catch ex As Exception
@@ -1511,9 +1438,11 @@ LoginFinish:
End If
'添加 Java Wrapper 作为主 Jar
- If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
- DataList.Add("-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
- DataList.Add("-jar """ & ExtractJavaWrapper() & """")
+ If Not Setup.Get("LaunchAdvanceDisableJLW") AndAlso Not Setup.Get("VersionAdvanceDisableJLW", McVersionCurrent) Then
+ If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
+ DataList.Add("-Doolloo.jlw.tmpdir=""" & PathPure.TrimEnd("\") & """")
+ DataList.Add("-jar """ & ExtractJavaWrapper() & """")
+ End If
'添加 MainClass
If Version.JsonObject("mainClass") Is Nothing Then
@@ -1569,7 +1498,7 @@ NextVersion:
Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
Try
Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
- DataList.Insert(0, "-javaagent:""" & PathAppdata & "authlib-injector.jar""=" & Server &
+ DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
" -Dauthlibinjector.side=client" &
" -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
Catch ex As Exception
@@ -1578,9 +1507,11 @@ NextVersion:
End If
'添加 Java Wrapper 作为主 Jar
- If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
- DataList.Add("-Doolloo.jlw.tmpdir=""" & GetJavaWrapperDir() & """")
- DataList.Add("-jar """ & ExtractJavaWrapper() & """")
+ If Not Setup.Get("LaunchAdvanceDisableJLW") AndAlso Not Setup.Get("VersionAdvanceDisableJLW", McVersionCurrent) Then
+ If McLaunchJavaSelected.VersionCode >= 9 Then DataList.Add("--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED")
+ DataList.Add("-Doolloo.jlw.tmpdir=""" & PathPure.TrimEnd("\") & """")
+ DataList.Add("-jar """ & ExtractJavaWrapper() & """")
+ End If
'将 "-XXX" 与后面 "XXX" 合并到一起
'如果不合并,会导致 Forge 1.17 启动无效,它有两个 --add-exports,进一步导致其中一个在后面被去重
@@ -1723,16 +1654,16 @@ NextVersion:
'基础参数
GameArguments.Add("${classpath_separator}", ";")
- GameArguments.Add("${natives_directory}", GetNativesFolder())
- GameArguments.Add("${library_directory}", PathMcFolder & "libraries")
- GameArguments.Add("${libraries_directory}", PathMcFolder & "libraries")
+ GameArguments.Add("${natives_directory}", ShortenPath(GetNativesFolder()))
+ GameArguments.Add("${library_directory}", ShortenPath(PathMcFolder & "libraries"))
+ GameArguments.Add("${libraries_directory}", ShortenPath(PathMcFolder & "libraries"))
GameArguments.Add("${launcher_name}", "PCL")
GameArguments.Add("${launcher_version}", VersionCode)
GameArguments.Add("${version_name}", Version.Name)
Dim ArgumentInfo As String = Setup.Get("VersionArgumentInfo", Version:=McVersionCurrent)
GameArguments.Add("${version_type}", If(ArgumentInfo = "", Setup.Get("LaunchArgumentInfo"), ArgumentInfo))
- GameArguments.Add("${game_directory}", Left(McVersionCurrent.PathIndie, McVersionCurrent.PathIndie.Count - 1))
- GameArguments.Add("${assets_root}", PathMcFolder & "assets")
+ GameArguments.Add("${game_directory}", ShortenPath(Left(McVersionCurrent.PathIndie, McVersionCurrent.PathIndie.Count - 1)))
+ GameArguments.Add("${assets_root}", ShortenPath(PathMcFolder & "assets"))
GameArguments.Add("${user_properties}", "{}")
GameArguments.Add("${auth_player_name}", McLoginLoader.Output.Name)
GameArguments.Add("${auth_uuid}", McLoginLoader.Output.Uuid)
@@ -1754,8 +1685,11 @@ NextVersion:
GameSize = New Size(875 - 2, 540 - 2)
End Select
GameSize.Height -= 29.5 * DPI / 96 '标题栏高度
- If McVersionCurrent.Version.McCodeMain <= 12 AndAlso McLaunchJavaSelected.VersionCode <= 8 AndAlso
- Not McVersionCurrent.Version.HasOptiFine AndAlso Not McVersionCurrent.Version.HasForge Then '修复 #3463:1.12.2-,JRE 8 下窗口大小为设置大小的 DPI% 倍
+ 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
+ '修复 #3463:1.12.2-,JRE 8u200~321 下窗口大小为设置大小的 DPI% 倍
+ McLaunchLog($"已应用窗口大小过大修复({McLaunchJavaSelected.Version.Revision})")
GameSize.Width /= DPI / 96
GameSize.Height /= DPI / 96
End If
@@ -1763,7 +1697,7 @@ NextVersion:
GameArguments.Add("${resolution_height}", Math.Round(GameSize.Height))
'Assets 相关参数
- GameArguments.Add("${game_assets}", PathMcFolder & "assets\virtual\legacy") '1.5.2 的 pre-1.6 资源索引应与 legacy 合并
+ GameArguments.Add("${game_assets}", ShortenPath(PathMcFolder & "assets\virtual\legacy")) '1.5.2 的 pre-1.6 资源索引应与 legacy 合并
GameArguments.Add("${assets_index_name}", McAssetsGetIndexName(Version))
'支持库参数
@@ -1780,7 +1714,7 @@ NextVersion:
End If
Next
If OptiFineCp IsNot Nothing Then CpStrings.Insert(CpStrings.Count - 2, OptiFineCp)
- GameArguments.Add("${classpath}", Join(CpStrings, ";"))
+ GameArguments.Add("${classpath}", Join(CpStrings.Select(Function(c) ShortenPath(c)), ";"))
Return GameArguments
End Function
@@ -1868,6 +1802,29 @@ NextVersion:
Private Sub McLaunchPrerun()
+ '要求 Java 使用高性能显卡
+ If Setup.Get("LaunchAdvanceGraphicCard") Then
+ Try
+ SetGPUPreference(McLaunchJavaSelected.PathJavaw)
+ SetGPUPreference(PathWithName)
+ Catch ex As Exception
+ If IsAdmin() Then
+ Log(ex, "直接调整显卡设置失败")
+ Else
+ Log(ex, "直接调整显卡设置失败,将以管理员权限重启 PCL 再次尝试")
+ Try
+ If RunAsAdmin($"--gpu ""{McLaunchJavaSelected.PathJavaw}""") = ProcessReturnValues.TaskDone Then
+ McLaunchLog("以管理员权限重启 PCL 并调整显卡设置成功")
+ Else
+ Throw New Exception("调整过程中出现异常")
+ End If
+ Catch exx As Exception
+ Log(exx, "调整显卡设置失败,Minecraft 可能会使用默认显卡运行", LogLevel.Hint)
+ End Try
+ End If
+ End Try
+ End If
+
'更新 launcher_profiles.json
Try
'确保可用
@@ -2138,28 +2095,29 @@ IgnoreCustomSkin:
'输出 bat
Try
Dim CmdString As String =
+ $"{If(McLaunchJavaSelected.VersionCode > 8, "chcp 65001>nul" & vbCrLf, "")}" &
"@echo off" & vbCrLf &
- "title 启动 - " & McVersionCurrent.Name & vbCrLf &
+ $"title 启动 - {McVersionCurrent.Name}" & vbCrLf &
"echo 游戏正在启动,请稍候。" & vbCrLf &
- "set APPDATA=""" & PathMcFolder & """" & vbCrLf &
- "cd /D """ & PathMcFolder & """" & vbCrLf &
+ $"set APPDATA=""{ShortenPath(McVersionCurrent.PathIndie)}""" & vbCrLf &
+ $"cd /D ""{ShortenPath(McVersionCurrent.PathIndie)}""" & vbCrLf &
CustomCommandGlobal & vbCrLf &
CustomCommandVersion & vbCrLf &
- """" & McLaunchJavaSelected.PathJava & """ " & McLaunchArgument & vbCrLf &
+ $"""{McLaunchJavaSelected.PathJava}"" {McLaunchArgument}" & vbCrLf &
"echo 游戏已退出。" & vbCrLf &
"pause"
- WriteFile(If(McLaunchLoader.Input.SaveBatch, Path & "PCL\LatestLaunch.bat"), SecretFilter(CmdString, "F"),
+ WriteFile(If(CurrentLaunchOptions.SaveBatch, Path & "PCL\LatestLaunch.bat"), SecretFilter(CmdString, "F"),
Encoding:=If(Encoding.Default.Equals(Encoding.UTF8), Encoding.UTF8, Encoding.GetEncoding("GB18030")))
- If McLaunchLoader.Input.SaveBatch IsNot Nothing Then
+ If CurrentLaunchOptions.SaveBatch IsNot Nothing Then
McLaunchLog("导出启动脚本完成,强制结束启动过程")
AbortHint = "导出启动脚本成功!"
- OpenExplorer("/select,""" & McLaunchLoader.Input.SaveBatch & """")
+ OpenExplorer(CurrentLaunchOptions.SaveBatch)
Loader.Parent.Abort()
Exit Sub '导出脚本完成
End If
Catch ex As Exception
Log(ex, "输出启动脚本失败")
- If McLaunchLoader.Input.SaveBatch IsNot Nothing Then Throw ex '直接触发启动失败
+ If CurrentLaunchOptions.SaveBatch IsNot Nothing Then Throw ex '直接触发启动失败
End Try
'执行自定义命令
@@ -2169,7 +2127,7 @@ IgnoreCustomSkin:
Try
CustomProcess.StartInfo.FileName = "cmd.exe"
CustomProcess.StartInfo.Arguments = "/c """ & CustomCommandGlobal & """"
- CustomProcess.StartInfo.WorkingDirectory = PathMcFolder
+ CustomProcess.StartInfo.WorkingDirectory = ShortenPath(PathMcFolder)
CustomProcess.StartInfo.UseShellExecute = False
CustomProcess.StartInfo.CreateNoWindow = True
CustomProcess.Start()
@@ -2193,7 +2151,7 @@ IgnoreCustomSkin:
Try
CustomProcess.StartInfo.FileName = "cmd.exe"
CustomProcess.StartInfo.Arguments = "/c """ & CustomCommandVersion & """"
- CustomProcess.StartInfo.WorkingDirectory = PathMcFolder
+ CustomProcess.StartInfo.WorkingDirectory = ShortenPath(PathMcFolder)
CustomProcess.StartInfo.UseShellExecute = False
CustomProcess.StartInfo.CreateNoWindow = True
CustomProcess.Start()
@@ -2220,19 +2178,15 @@ IgnoreCustomSkin:
Dim StartInfo As New ProcessStartInfo(McLaunchJavaSelected.PathJavaw)
'设置环境变量
- If StartInfo.EnvironmentVariables.ContainsKey("appdata") Then
- StartInfo.EnvironmentVariables("appdata") = PathMcFolder
- Else
- StartInfo.EnvironmentVariables.Add("appdata", PathMcFolder)
- End If
Dim Paths As New List(Of String)(StartInfo.EnvironmentVariables("Path").Split(";"))
- Paths.Add(McLaunchJavaSelected.PathFolder)
+ Paths.Add(ShortenPath(McLaunchJavaSelected.PathFolder))
StartInfo.EnvironmentVariables("Path") = Join(Paths.Distinct.ToList, ";")
+ StartInfo.EnvironmentVariables("appdata") = ShortenPath(PathMcFolder)
'设置其他参数
- StartInfo.StandardErrorEncoding = Encoding.UTF8
- StartInfo.StandardOutputEncoding = Encoding.UTF8
- StartInfo.WorkingDirectory = McVersionCurrent.PathIndie
+ StartInfo.StandardErrorEncoding = If(McLaunchJavaSelected.VersionCode > 8, Encoding.UTF8, Nothing)
+ StartInfo.StandardOutputEncoding = If(McLaunchJavaSelected.VersionCode > 8, Encoding.UTF8, Nothing)
+ StartInfo.WorkingDirectory = ShortenPath(McVersionCurrent.PathIndie)
StartInfo.UseShellExecute = False
StartInfo.RedirectStandardOutput = True
StartInfo.RedirectStandardError = True
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
index abc4a6b..6dc7c0e 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
@@ -73,7 +73,8 @@ Public Module ModMinecraft
End If
Dim Name As String = Folder.Split(">")(0)
Dim Path As String = Folder.Split(">")(1)
- If CheckPermission(Path) Then
+ Try
+ CheckPermissionWithException(Path)
'若已有该文件夹,则直接重命名;没有则添加
Dim Renamed As Boolean = False
For Each OriginalFolder As McFolder In CacheMcFolderList
@@ -84,9 +85,10 @@ Public Module ModMinecraft
End If
Next
If Not Renamed Then CacheMcFolderList.Add(New McFolder With {.Name = Name, .Path = Path, .Type = McFolderType.Custom})
- Else
- Hint("无效的 Minecraft 文件夹:" & Path, HintType.Critical)
- End If
+ Catch ex As Exception
+ MyMsgBox("失效的 Minecraft 文件夹:" & vbCrLf & Path & vbCrLf & vbCrLf & GetExceptionSummary(ex), "Minecraft 文件夹失效", IsWarn:=True)
+ Log(ex, $"无法访问 Minecraft 文件夹 {Path}")
+ End Try
Next
'将自定义文件夹情况同步到设置
@@ -205,53 +207,46 @@ Public Module ModMinecraft
'''
Public ReadOnly Property PathIndie As String
Get
- Return GetPathIndie(Modable)
+ InitPathIndie(Modable)
+ Return If(Setup.Get("VersionArgumentIndieV2", Version:=Me), Path, PathMcFolder)
End Get
End Property
'''
- ''' 在不加载版本的情况下获取版本隔离目录。
+ ''' 决定该版本是否应该被隔离。
'''
- Public Function GetPathIndie(Modable As Boolean) As String
- Dim IndieType As Integer = Setup.Get("LaunchArgumentIndie")
- Select Case Setup.Get("VersionArgumentIndie", Version:=Me)
- Case -1
- '尚未判断
- 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
- '自动开启
- Setup.Set("VersionArgumentIndie", 1, Version:=Me)
- Log("[Setup] 已自动开启单版本隔离:" & Name)
- IndieType = 4
- Else
- '使用全局设置
- Setup.Set("VersionArgumentIndie", 0, Version:=Me)
- Log("[Setup] 版本隔离使用全局设置:" & Name)
- End If
- Case 0
- '使用全局设置
- Case 1
- '开启
- IndieType = 4
- Case 2
- '关闭
- IndieType = 0
- End Select
- Select Case IndieType
- Case 0 '关闭
- Case 1 '仅隔离可安装 Mod 的版本
- If Modable Then Return Path
- Case 2 '仅隔离非正式版
- If State = McVersionState.Fool OrElse State = McVersionState.Old OrElse State = McVersionState.Snapshot Then Return Path
- Case 3 '隔离非正式版与可安装 Mod 的版本
- If Modable Then Return Path
- If State = McVersionState.Fool OrElse State = McVersionState.Old OrElse State = McVersionState.Snapshot Then Return Path
- Case 4 '隔离所有版本
- Return Path
- End Select
- Return PathMcFolder
- End Function
+ 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
'''
''' 该版本的版本文件夹名称。
@@ -394,27 +389,13 @@ Public Module ModMinecraft
GoTo VersionSearchFinish
End If
'从 jar 文件的 version.json 中获取版本号
- 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)
- Dim VersionJsonObj As JObject = GetJson(VersionJsonStream.ReadToEnd)
- If VersionJsonObj("id") IsNot Nothing Then
- Dim VersionId As String = VersionJsonObj("id").ToString
- If VersionId.Length < 32 Then '因为 wiki 说这玩意儿可能是个 hash,虽然我没发现
- _Version.McName = VersionId
- Log("[Minecraft] 从版本 jar 中的 version.json 获取到版本号:" & VersionId)
- GoTo VersionSearchFinish
- End If
- End If
- End Using
- End If
- End Using
- Catch ex As Exception
- Log(ex, "从版本 jar 中的 version.json 获取版本号失败")
- End Try
+ If JsonVersion?("name") IsNot Nothing Then
+ Dim JsonVerName As String = JsonVersion("name").ToString
+ If JsonVerName.Length < 32 Then '因为 wiki 说这玩意儿可能是个 hash,虽然我没发现
+ _Version.McName = JsonVerName
+ Log("[Minecraft] 从版本 jar 中的 version.json 获取到版本号:" & JsonVerName)
+ GoTo VersionSearchFinish
+ End If
End If
'非准确的版本判断警告
Log("[Minecraft] 无法完全确认 MC 版本号的版本:" & Name)
@@ -475,22 +456,37 @@ VersionSearchFinish:
'''
Public Property JsonText As String
Get
+ '快速检查 JSON 是否以 { 开头、} 结尾;忽略空白字符
+ Dim FastJsonCheck =
+ Function(Json As String) As Boolean
+ Dim TrimedJson As String = Json.Trim()
+ Return TrimedJson.StartsWithF("{") AndAlso TrimedJson.EndsWithF("}")
+ End Function
If _JsonText Is Nothing Then
- If Not File.Exists(Path & Name & ".json") Then Throw New Exception("未找到版本 json 文件:" & Path & Name & ".json")
- _JsonText = ReadFile(Path & Name & ".json")
- '如果 ReadFile 失败会返回空字符串;这可能是由于文件被临时占用,故延时后重试
- If _JsonText.Length = 0 Then
- If RunInUi() Then
- Log("[Minecraft] 版本 json 文件为空或读取失败,由于代码在主线程运行,将不再进行重试", LogLevel.Debug)
- Throw New Exception("版本 Json 文件为空或读取失败")
+ 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
- Log("[Minecraft] 版本 json 文件为空或读取失败,将在 2s 后重试读取(" & Path & Name & ".json)", LogLevel.Debug)
+ Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json")
+ End If
+ End If
+ _JsonText = ReadFile(JsonPath)
+ '如果 ReadFile 失败会返回空字符串;这可能是由于文件被临时占用,故延时后重试
+ If Not FastJsonCheck(_JsonText) Then
+ If RunInUi() Then
+ Log("[Minecraft] 版本 JSON 文件为空或有误,由于代码在主线程运行,将不再进行重试", LogLevel.Debug)
+ GetJson(_JsonText) '触发异常
+ Else
+ Log($"[Minecraft] 版本 JSON 文件为空或有误,将在 2s 后重试读取({JsonPath})", LogLevel.Debug)
Thread.Sleep(2000)
- _JsonText = ReadFile(Path & Name & ".json")
- If _JsonText.Length = 0 Then Throw New Exception("版本 json 文件为空或读取失败")
+ _JsonText = ReadFile(JsonPath)
+ If Not FastJsonCheck(_JsonText) Then GetJson(_JsonText) '触发异常
End If
End If
- If _JsonText.Length < 100 Then Throw New Exception("版本 json 文件有误,内容为:" & _JsonText)
End If
Return _JsonText
End Get
@@ -519,9 +515,8 @@ VersionSearchFinish:
For Each Subjson As JObject In _JsonObject("patches")
SubjsonList.Add(Subjson)
Next
- SubjsonList = Sort(SubjsonList, Function(Left As JObject, Right As JObject) As Boolean
- Return Val(If(Left("priority"), "0").ToString) < Val(If(Right("priority"), "0").ToString)
- End Function)
+ SubjsonList = SubjsonList.Sort(
+ Function(Left, Right) Val(If(Left("priority"), "0").ToString) < Val(If(Right("priority"), "0").ToString))
For Each Subjson As JObject In SubjsonList
Dim Id As String = Subjson("id")
If Id IsNot Nothing Then
@@ -562,11 +557,10 @@ Recheck:
GoTo Recheck
End If
Catch ex As Exception
- Log(ex, "合并版本依赖项 json 失败(" & If(InheritVersion, "null").ToString & ")")
+ Log(ex, "合并版本依赖项 JSON 失败(" & If(InheritVersion, "null").ToString & ")")
End Try
Catch ex As Exception
- Log($"[Minecraft] 传入的版本 json 文件内容(共 {Text.Length} 字符,最多输出前 5000 字符):{vbCrLf}{Text.Substring(0, 5000)}")
- Throw New Exception("版本 json 不规范(" & If(Name, "null") & ")", ex)
+ Throw New Exception("初始化版本 JSON 时失败(" & If(Name, "null") & ")", ex)
End Try
Try
'处理 JumpLoader
@@ -612,6 +606,35 @@ Recheck:
'''
Public Property HasJumpLoader As Boolean = False
+ '''
+ ''' 版本 jar 中的 version.json 文件对象。
+ ''' 若没有则返回 Nothing。
+ '''
+ Public ReadOnly Property JsonVersion As JObject
+ 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
+ End Using
+ Catch ex As Exception
+ Log(ex, "从版本 jar 中读取 version.json 失败")
+ End Try
+ End If
+ End If
+ Return _JsonVersion
+ End Get
+ End Property
+ Private JsonVersionInited As Boolean = False
+ Private _JsonVersion As JObject = Nothing
+
'''
''' 该版本的依赖版本。若无依赖版本则为空字符串。
'''
@@ -775,37 +798,7 @@ ExitDataLoad:
End If
'确定版本描述
Dim CustomInfo As String = ReadIni(Path & "PCL\Setup.ini", "CustomInfo")
- If CustomInfo = "" Then
- Select Case State
- Case McVersionState.Snapshot
- If Version.McName.ContainsF("pre", True) Then
- Info = "预发布版 " & Version.McName
- ElseIf Version.McName.ContainsF("rc", True) Then
- Info = "发布候选 " & Version.McName
- ElseIf Version.McName.Contains("experimental") OrElse Version.McName = "pending" Then
- Info = "实验性快照"
- Else
- Info = "快照 " & Version.McName
- End If
- Case McVersionState.Old
- Info = "远古版本"
- Case McVersionState.Original, McVersionState.Forge, McVersionState.NeoForge, McVersionState.Fabric, McVersionState.OptiFine, McVersionState.LiteLoader
- Info = Version.ToString
- Case McVersionState.Fool
- Info = GetMcFoolName(Version.McName)
- Case McVersionState.Error
- '已有错误信息
- Case Else
- 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
- Else
- Info = CustomInfo
- End If
+ Info = If(CustomInfo <> "", CustomInfo, GetDefaultDescription())
'确定版本收藏状态
IsStar = ReadIni(Path & "PCL\Setup.ini", "IsStar", False)
'确定版本显示种类
@@ -838,6 +831,41 @@ ExitDataLoad:
End Try
Return Me
End Function
+ '''
+ ''' 获取版本的默认描述。
+ '''
+ Public Function GetDefaultDescription() As String
+ Dim Info As String = ""
+ Select Case State
+ Case McVersionState.Snapshot
+ If Version.McName.ContainsF("pre", True) Then
+ Info = "预发布版 " & Version.McName
+ ElseIf Version.McName.ContainsF("rc", True) Then
+ Info = "发布候选 " & Version.McName
+ ElseIf Version.McName.Contains("experimental") OrElse Version.McName = "pending" Then
+ Info = "实验性快照"
+ Else
+ Info = "快照 " & Version.McName
+ End If
+ Case McVersionState.Old
+ Info = "远古版本"
+ Case McVersionState.Original, McVersionState.Forge, McVersionState.NeoForge, McVersionState.Fabric, McVersionState.OptiFine, McVersionState.LiteLoader
+ Info = Version.ToString
+ Case McVersionState.Fool
+ Info = GetMcFoolName(Version.McName)
+ Case McVersionState.Error
+ Return Me.Info '已有错误信息
+ Case Else
+ 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
+ Return Info
+ End Function
+
Public IsLoaded As Boolean = False
Public Overrides Function Equals(obj As Object) As Boolean
@@ -1030,21 +1058,23 @@ ExitDataLoad:
Public Function GetMcFoolName(Name As String) As String
Name = Name.ToLower
If Name.StartsWithF("2.0") Then
- Return "这个秘密计划了两年的更新将游戏推向了一个新高度!"
- ElseIf Name.StartsWithF("20w14inf") OrElse Name = "20w14∞" Then
- Return "我们加入了 20 亿个新的维度,让无限的想象变成了现实!"
+ Return "2013 | 这个秘密计划了两年的更新将游戏推向了一个新高度!"
ElseIf Name = "15w14a" Then
- Return "作为一款全年龄向的游戏,我们需要和平,需要爱与拥抱。"
+ Return "2015 | 作为一款全年龄向的游戏,我们需要和平,需要爱与拥抱。"
ElseIf Name = "1.rv-pre1" Then
- Return "是时候将现代科技带入 Minecraft 了!"
+ Return "2016 | 是时候将现代科技带入 Minecraft 了!"
ElseIf Name = "3d shareware v1.34" Then
- Return "我们从地下室的废墟里找到了这个开发于 1994 年的杰作!"
+ Return "2019 | 我们从地下室的废墟里找到了这个开发于 1994 年的杰作!"
+ ElseIf Name.StartsWithF("20w14inf") OrElse Name = "20w14∞" Then
+ Return "2020 | 我们加入了 20 亿个新的维度,让无限的想象变成了现实!"
ElseIf Name = "22w13oneblockatatime" Then
- Return "一次一个方块更新!迎接全新的挖掘、合成与骑乘玩法吧!"
+ Return "2022 | 一次一个方块更新!迎接全新的挖掘、合成与骑乘玩法吧!"
ElseIf Name = "23w13a_or_b" Then
- Return "研究表明:玩家喜欢作出选择——越多越好!"
+ Return "2023 | 研究表明:玩家喜欢作出选择——越多越好!"
ElseIf Name = "24w14potato" Then
- Return "毒马铃薯一直都被大家忽视和低估,于是我们超级加强了它!"
+ Return "2024 | 毒马铃薯一直都被大家忽视和低估,于是我们超级加强了它!"
+ ElseIf Name = "25w14craftmine" Then
+ Return "2025 | 你可以合成任何东西——包括合成你的世界!"
Else
Return ""
End If
@@ -1095,7 +1125,7 @@ ExitDataLoad:
Throw New Exception("无法读取版本文件夹,可能是由于没有权限(" & Path & "versions)", ex)
End Try
End If
- '没有可用版本
+ '不可用
If Not FolderList.Any() Then
WriteIni(Path & "PCL.ini", "VersionCache", "") '清空缓存
GoTo OnLoaded
@@ -1119,10 +1149,10 @@ Reload:
McVersionList = McVersionListLoadNoCache(Path)
End If
IsFirstMcVersionListLoad = False
- If Loader.IsAborted Then Exit Sub
'改变当前选择的版本
OnLoaded:
+ If Loader.IsAborted Then Exit Sub
If McVersionList.Any(Function(v) v.Key <> McVersionCardType.Error) Then
'尝试读取已储存的选择
Dim SavedSelection As String = ReadIni(Path & "PCL.ini", "Version")
@@ -1252,9 +1282,8 @@ OnLoaded:
Dim VersionList As New List(Of McVersion)
#Region "循环加载每个版本的信息"
- Dim Dirs As DirectoryInfo() = New DirectoryInfo(Path & "versions").GetDirectories
- For Each Folder As DirectoryInfo In Dirs
- If Not Folder.EnumerateFiles.Any Then
+ For Each Folder As DirectoryInfo In New DirectoryInfo(Path & "versions").GetDirectories
+ If Not Folder.Exists OrElse Not Folder.EnumerateFiles.Any Then
Log("[Minecraft] 跳过空文件夹:" & Folder.FullName)
Continue For
End If
@@ -1288,8 +1317,11 @@ OnLoaded:
'单独列出收藏的版本
Dim StaredVersions As New List(Of McVersion)
- For Each Version As McVersion In VersionList
- If Version.IsStar AndAlso Not Version.DisplayType = McVersionCardType.Hidden Then StaredVersions.Add(Version)
+ For Each Version As McVersion In VersionList.ToList
+ If Version.IsStar AndAlso Not Version.DisplayType = McVersionCardType.Hidden Then
+ StaredVersions.Add(Version)
+ VersionList.Remove(Version)
+ End If
Next
If StaredVersions.Any Then VersionListOriginal.Add(McVersionCardType.Star, StaredVersions)
@@ -1404,7 +1436,7 @@ OnLoaded:
Next
If Not IsNothing(Snapshot) Then OldList.Remove(Snapshot)
'按版本号排序
- Dim NewList As List(Of McVersion) = Sort(OldList, Function(Left, Right) Left.Version.McCodeMain > Right.Version.McCodeMain)
+ Dim NewList As List(Of McVersion) = OldList.OrderByDescending(Function(v) v.Version.McCodeMain).ToList
'回设
If Not IsNothing(Snapshot) Then NewList.Insert(0, Snapshot)
ResultVersionList(McVersionCardType.OriginalLike) = NewList
@@ -1412,7 +1444,7 @@ OnLoaded:
'不常用版本:按发布时间新旧排序,如果不可用则按名称排序
If ResultVersionList.ContainsKey(McVersionCardType.Rubbish) Then
- ResultVersionList(McVersionCardType.Rubbish) = Sort(ResultVersionList(McVersionCardType.Rubbish),
+ ResultVersionList(McVersionCardType.Rubbish) = ResultVersionList(McVersionCardType.Rubbish).Sort(
Function(Left As McVersion, Right As McVersion)
Dim LeftYear As Integer = Left.ReleaseTime.Year '+ If(Left.State = McVersionState.Original OrElse Left.Version.HasOptiFine, 100, 0)
Dim RightYear As Integer = Right.ReleaseTime.Year '+ If(Right.State = McVersionState.Original OrElse Left.Version.HasOptiFine, 100, 0)
@@ -1434,7 +1466,7 @@ OnLoaded:
'API 版本:优先按版本排序,此后【先放 Fabric,再放 Neo/Forge(按版本号从高到低排序),最后放 LiteLoader(按名称排序)】
If ResultVersionList.ContainsKey(McVersionCardType.API) Then
- ResultVersionList(McVersionCardType.API) = Sort(ResultVersionList(McVersionCardType.API),
+ ResultVersionList(McVersionCardType.API) = ResultVersionList(McVersionCardType.API).Sort(
Function(Left As McVersion, Right As McVersion)
Dim Basic = VersionSortInteger(Left.Version.McName, Right.Version.McName)
If Basic <> 0 Then
@@ -1524,7 +1556,7 @@ OnLoaded:
''' 要求玩家选择一个皮肤文件,并进行相关校验。
'''
Public Function McSkinSelect() As McSkinInfo
- Dim FileName As String = SelectFile("皮肤文件(*.png)|*.png", "选择皮肤文件")
+ Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.webp)|*.png;*.jpg;*.webp", "选择皮肤文件")
'验证有效性
If FileName = "" Then Return New McSkinInfo With {.IsVaild = False}
@@ -1613,7 +1645,7 @@ OnLoaded:
Dim FileAddress As String = PathTemp & "Cache\Skin\" & GetHash(Address) & ".png"
SyncLock McSkinDownloadLock
If Not File.Exists(FileAddress) Then
- NetDownload(Address, FileAddress & NetDownloadEnd)
+ NetDownloadByClient(Address, FileAddress & NetDownloadEnd)
File.Delete(FileAddress)
FileSystem.Rename(FileAddress & NetDownloadEnd, FileAddress)
Log("[Minecraft] 皮肤下载成功:" & FileAddress)
@@ -1931,7 +1963,7 @@ OnLoaded:
'''
''' 获取版本缺失的支持库文件所对应的 NetTaskFile。
'''
- Public Function McLibFix(Version As McVersion, Optional CoreJarOnly As Boolean = False) As List(Of NetFile)
+ Public Function McLibFix(Version As McVersion) As List(Of NetFile)
If Not Version.IsLoaded Then Version.Load() '确保例如 JumpLoader 等项被合并入 Json
Dim Result As New List(Of NetFile)
@@ -1944,88 +1976,99 @@ OnLoaded:
Catch ex As Exception
Log(ex, "版本缺失主 jar 文件所必须的信息", LogLevel.Developer)
End Try
- If CoreJarOnly Then Return Result
-
- '是否跳过校验
- Dim IsSetupSkip As Boolean = ShouldIgnoreFileCheck(Version)
'Library 文件
- Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False), JumpLoaderFolder:=Version.PathIndie & ".jumploader\", AllowUnsameFile:=IsSetupSkip))
+ Result.AddRange(McLibFixFromLibToken(McLibListGet(Version, False), JumpLoaderFolder:=Version.PathIndie & ".jumploader\"))
'统一通行证文件
If Setup.Get("VersionServerLogin", Version:=Version) = 3 Then
Dim TargetFile = PathAppdata & "nide8auth.jar"
- If Not (IsSetupSkip AndAlso File.Exists(TargetFile)) Then
- Dim DownloadInfo As JObject = Nothing
- '获取下载信息
- Try
- Log("[Minecraft] 开始获取统一通行证下载信息")
- '测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/
- DownloadInfo = GetJson(NetGetCodeByDownload({
+ Dim DownloadInfo As JObject = Nothing
+ '获取下载信息
+ 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))
- Catch ex As Exception
- Log(ex, "获取统一通行证下载信息失败")
- End Try
- '校验文件
- If DownloadInfo IsNot Nothing Then
- Dim Checker As New FileChecker(Hash:=DownloadInfo("jarHash").ToString)
- If (IsSetupSkip AndAlso File.Exists(TargetFile)) OrElse Checker.Check(TargetFile) IsNot Nothing Then
- '开始下载
- Log("[Minecraft] 统一通行证需要更新:Hash - " & Checker.Hash, LogLevel.Developer)
- Result.Add(New NetFile({"https://login.mc-user.com:233/index/jar"}, TargetFile, Checker))
- End If
+ Catch ex As Exception
+ Log(ex, "获取统一通行证下载信息失败")
+ End Try
+ '校验文件
+ If DownloadInfo IsNot Nothing Then
+ Dim Checker As New FileChecker(Hash:=DownloadInfo("jarHash").ToString)
+ If Checker.Check(TargetFile) IsNot Nothing Then
+ '开始下载
+ Log("[Minecraft] 统一通行证需要更新:Hash - " & Checker.Hash, LogLevel.Developer)
+ Result.Add(New NetFile({"https://login.mc-user.com:233/index/jar"}, TargetFile, Checker))
End If
End If
End If
'Authlib-Injector 文件
- If Setup.Get("VersionServerLogin", Version:=Version) = 4 OrElse
- (PageLinkHiper.HiperState = LoadState.Finished AndAlso Setup.Get("LoginType") = McLoginType.Legacy) Then 'HiPer 登录转接
- Dim TargetFile = PathAppdata & "authlib-injector.jar"
- If Not (IsSetupSkip AndAlso File.Exists(TargetFile)) Then
- Dim DownloadInfo As JObject = Nothing
- '获取下载信息
- Try
- Log("[Minecraft] 开始获取 Authlib-Injector 下载信息")
- DownloadInfo = GetJson(NetGetCodeByDownload({"https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"}, IsJson:=True))
- Catch ex As Exception
- Log(ex, "获取 Authlib-Injector 下载信息失败")
- End Try
- '校验文件
- If DownloadInfo IsNot Nothing Then
- Dim Checker As New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString)
- If (IsSetupSkip AndAlso File.Exists(TargetFile)) OrElse Checker.Check(TargetFile) IsNot Nothing Then
- '开始下载
- Dim DownloadAddress As String = DownloadInfo("download_url")
- Log("[Minecraft] Authlib-Injector 需要更新:" & DownloadAddress, LogLevel.Developer)
- Result.Add(New NetFile({DownloadAddress}, TargetFile, New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString)))
- End If
+ If Setup.Get("VersionServerLogin", Version:=Version) = 4 Then
+ Dim TargetFile = PathPure & "\authlib-injector.jar"
+ Dim DownloadInfo As JObject = Nothing
+ '获取下载信息
+ Try
+ Log("[Minecraft] 开始获取 Authlib-Injector 下载信息")
+ DownloadInfo = GetJson(NetGetCodeByLoader({
+ "https://authlib-injector.yushi.moe/artifact/latest.json",
+ "https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"
+ }, IsJson:=True))
+ Catch ex As Exception
+ Log(ex, "获取 Authlib-Injector 下载信息失败")
+ End Try
+ '校验文件
+ If DownloadInfo IsNot Nothing Then
+ Dim Checker As New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString)
+ If Checker.Check(TargetFile) IsNot Nothing Then
+ '开始下载
+ Dim DownloadAddress As String = DownloadInfo("download_url").ToString.
+ Replace("bmclapi2.bangbang93.com/mirrors/authlib-injector", "authlib-injector.yushi.moe")
+ Log("[Minecraft] Authlib-Injector 需要更新:" & DownloadAddress, LogLevel.Developer)
+ Result.Add(New NetFile({
+ DownloadAddress,
+ DownloadAddress.Replace("authlib-injector.yushi.moe", "bmclapi2.bangbang93.com/mirrors/authlib-injector")
+ }, TargetFile, New FileChecker(Hash:=DownloadInfo("checksums")("sha256").ToString)))
End If
End If
End If
+ '跳过校验
+ If ShouldIgnoreFileCheck(Version) Then
+ Log("[Minecraft] 用户要求尽量忽略文件检查,这可能会保留有误的文件")
+ Result = Result.Where(
+ Function(f)
+ If File.Exists(f.LocalPath) Then
+ Log("[Minecraft] 跳过下载的支持库文件:" & f.LocalPath, LogLevel.Debug)
+ Return False
+ Else
+ Return True
+ End If
+ End Function).ToList
+ End If
+
Return Result
End Function
'''
''' 将 McLibToken 列表转换为 NetFile。无需下载的文件会被自动过滤。
'''
- Public Function McLibFixFromLibToken(Libs As List(Of McLibToken), Optional CustomMcFolder As String = Nothing, Optional JumpLoaderFolder As String = Nothing, Optional AllowUnsameFile As Boolean = False) As List(Of NetFile)
+ Public Function McLibFixFromLibToken(Libs As List(Of McLibToken), Optional CustomMcFolder As String = Nothing, Optional JumpLoaderFolder 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 FileChecker
- If AllowUnsameFile Then '只要文件存在则通过检查,用于放宽完整性校验的情况
- Checker = New FileChecker(MinSize:=1)
- Else
- Checker = New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1)
- End If
+ Dim Checker As New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1)
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("\", "/")
+ End If
If Token.Url IsNot Nothing Then
- '获取 Url 的真实地址
+ '获取 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
@@ -2067,10 +2110,14 @@ OnLoaded:
McLibGet = If(WithHead, CustomMcFolder & "libraries\", "") &
Splited(0).Replace(".", "\") & "\" & Splited(1) & "\" & Splited(2) & "\" & Splited(1) & "-" & Splited(2) & ".jar"
'判断 OptiFine 是否应该使用 installer
- If McLibGet.Contains("optifine\OptiFine\1.12") AndAlso '仅在 1.12 OptiFine 可重现
- File.Exists(CustomMcFolder & "libraries\" & Splited(0).Replace(".", "\") & "\" & Splited(1) & "\" & Splited(2) & "\" & Splited(1) & "-" & Splited(2) & "-installer.jar") Then
- Log("[Launch] 已将 " & Original & " 特判替换为对应的 Installer 文件", LogLevel.Debug)
- McLibGet = McLibGet.Replace(".jar", "-installer.jar")
+ If McLibGet.Contains("optifine\OptiFine\1.") AndAlso Splited(2).Split(".").Count > 1 Then
+ Dim MajorVersion As Integer = Val(Splited(2).Split(".")(1).BeforeFirst("_"))
+ Dim MinorVersion As Integer = If(Splited(2).Split(".").Count > 2, Val(Splited(2).Split(".")(2).BeforeFirst("_")), 0)
+ If (MajorVersion = 12 OrElse (MajorVersion = 20 AndAlso MinorVersion >= 4) OrElse MajorVersion >= 21) AndAlso '仅在 1.12 (无法追溯) 和 1.20.4+ (#5376) 遇到此问题
+ File.Exists($"{CustomMcFolder}libraries\{Splited(0).Replace(".", "\")}\{Splited(1)}\{Splited(2)}\{Splited(1)}-{Splited(2)}-installer.jar") Then
+ McLaunchLog("已将 " & Original & " 替换为对应的 Installer 文件")
+ McLibGet = McLibGet.Replace(".jar", "-installer.jar")
+ End If
End If
End Function
@@ -2078,7 +2125,7 @@ OnLoaded:
''' 检查设置,是否应当忽略文件检查?
'''
Public Function ShouldIgnoreFileCheck(Version As McVersion)
- Return Setup.Get("LaunchAdvanceAssets") OrElse Setup.Get("VersionAdvanceAssetsV2", Version:=Version) OrElse (Setup.Get("VersionAdvanceAssets", Version:=Version) = 2)
+ Return Setup.Get("VersionAdvanceAssetsV2", Version:=Version) OrElse (Setup.Get("VersionAdvanceAssets", Version:=Version) = 2)
End Function
#End Region
@@ -2159,77 +2206,50 @@ OnLoaded:
'''
Public Size As Long
'''
- ''' 是否为 Virtual 资源文件。
- '''
- Public IsVirtual As Boolean
- '''
''' 文件的 Hash 校验码。
'''
Public Hash As String
Public Overrides Function ToString() As String
- Return If(IsVirtual, "[Virtual] ", "") & GetString(Size) & " | " & LocalPath
+ Return GetString(Size) & " | " & LocalPath
End Function
End Structure
'''
''' 获取 Minecraft 的资源文件列表。失败会抛出异常。
'''
- ''' 版本的资源名称,如“1.13.1”。
- Private Function McAssetsListGet(Name As String) As List(Of McAssetsToken)
+ Private Function McAssetsListGet(Version As McVersion) As List(Of McAssetsToken)
+ Dim IndexName = McAssetsGetIndexName(Version)
Try
'初始化
- If Not File.Exists(PathMcFolder & "assets\indexes\" & Name & ".json") Then Throw New FileNotFoundException("Assets 索引文件未找到", PathMcFolder & "assets\indexes\" & Name & ".json")
- McAssetsListGet = New List(Of McAssetsToken)
- Dim Json = GetJson(ReadFile(PathMcFolder & "assets\indexes\" & Name & ".json"))
+ If Not File.Exists($"{PathMcFolder}assets\indexes\{IndexName}.json") Then Throw New FileNotFoundException("未找到 Asset Index", PathMcFolder & "assets\indexes\" & IndexName & ".json")
+ Dim Result As New List(Of McAssetsToken)
+ Dim Json As JObject = GetJson(ReadFile($"{PathMcFolder}assets\indexes\{IndexName}.json"))
- '确认 Virtual 与 Map 状态
- Dim IsVirtual As Boolean = False
- If Json("virtual") IsNot Nothing AndAlso Json("virtual").ToString Then IsVirtual = True
- If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToString Then
- IsVirtual = True
- '刷新 resources 文件夹符号链接(#2182)
- 'Dim Info As FileSystemInfo = New FileInfo(PathMcFolder & "resources\pack.mcmeta")
- 'If Info.Attributes.HasFlag(FileAttributes.ReparsePoint) Then
- If Not File.Exists(PathMcFolder & "resources\pack.mcmeta") Then
- Log("[Minecraft] 尝试刷新 resources 文件夹符号链接", LogLevel.Debug)
- Try
- DeleteDirectory(PathMcFolder & "resources\", True)
- Directory.CreateDirectory(PathMcFolder & "assets\virtual\legacy\")
- Dim Result = ShellAndGetOutput("cmd", $"/C mklink /D /J ""{PathMcFolder}resources"" ""{PathMcFolder}assets\virtual\legacy""")
- Log($"[Minecraft] 符号链接创建结果:{Result}")
- If Not Result.Contains("<<===>>") Then Throw New Exception($"非预期的结果({Result})")
- Catch ex As Exception
- Log(ex, "创建资源文件夹链接失败,游戏可能会没有声音", LogLevel.Msgbox)
- End Try
+ '读取列表
+ For Each File As JProperty In Json("objects").Children
+ 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("/", "\")
+ ElseIf Json("virtual") IsNot Nothing AndAlso Json("virtual").ToObject(Of Boolean) Then
+ 'Virtual
+ LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\")
+ Else
+ '正常
+ LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString
End If
- End If
-
- '加载列表
- If IsVirtual Then
- For Each File As JProperty In Json("objects").Children
- McAssetsListGet.Add(New McAssetsToken With {
- .IsVirtual = True,
- .LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\"),
- .SourcePath = File.Name,
- .Hash = File.Value("hash").ToString,
- .Size = File.Value("size").ToString
- })
- Next
- Else
- For Each File As JProperty In Json("objects").Children
- McAssetsListGet.Add(New McAssetsToken With {
- .IsVirtual = False,
- .LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString,
- .SourcePath = File.Name,
- .Hash = File.Value("hash").ToString,
- .Size = File.Value("size").ToString
- })
- Next
- End If
+ Result.Add(New McAssetsToken With {
+ .LocalPath = LocalPath,
+ .SourcePath = File.Name,
+ .Hash = File.Value("hash").ToString,
+ .Size = File.Value("size").ToString
+ })
+ Next
+ Return Result
Catch ex As Exception
- Log(ex, "获取资源文件列表失败:" & Name)
+ Log(ex, "获取资源文件列表失败:" & IndexName)
Throw
End Try
End Function
@@ -2238,12 +2258,12 @@ OnLoaded:
'''
''' 获取版本缺失的资源文件所对应的 NetTaskFile。
'''
- Public Function McAssetsFixList(IndexAddress As String, CheckHash As Boolean, Optional ByRef ProgressFeed As LoaderBase = Nothing) As List(Of NetFile)
+ Public Function McAssetsFixList(Version As McVersion, CheckHash As Boolean, Optional ByRef ProgressFeed As LoaderBase = Nothing) As List(Of NetFile)
Dim Result As New List(Of NetFile)
Dim AssetsList As List(Of McAssetsToken)
Try
- AssetsList = McAssetsListGet(IndexAddress)
+ AssetsList = McAssetsListGet(Version)
Dim Token As McAssetsToken
If ProgressFeed IsNot Nothing Then ProgressFeed.Progress = 0.04
For i = 0 To AssetsList.Count - 1
@@ -2322,8 +2342,8 @@ OnLoaded:
End If
Left = Left.ToLowerInvariant
Right = Right.ToLowerInvariant
- Dim Lefts = RegexSearch(Left.Replace("快照", "snapshot"), "[a-z]+|[0-9]+")
- Dim Rights = RegexSearch(Right.Replace("快照", "snapshot"), "[a-z]+|[0-9]+")
+ Dim Lefts = RegexSearch(Left.Replace("快照", "snapshot").Replace("预览版", "pre"), "[a-z]+|[0-9]+")
+ Dim Rights = RegexSearch(Right.Replace("快照", "snapshot").Replace("预览版", "pre"), "[a-z]+|[0-9]+")
Dim i As Integer = 0
While True
'两边均缺失,感觉是一个东西
@@ -2372,15 +2392,11 @@ NextEntry:
'''
''' 比较两个版本名的排序器。
'''
- Public Class VersionSorter
+ Public Class VersionComparer
Implements IComparer(Of String)
- Public IsDecreased As Boolean = True
Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare
- Return VersionSortInteger(x, y) * If(IsDecreased, -1, 1)
+ Return VersionSortInteger(x, y)
End Function
- Public Sub New(Optional IsDecreased As Boolean = True)
- Me.IsDecreased = IsDecreased
- End Sub
End Class
'''
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
index 0c3ea34..f20e352 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
@@ -1,6 +1,7 @@
Imports System.IO.Compression
Public Module ModMod
+ Private Const LocalModCacheVersion As Integer = 7
Public Class McMod
@@ -47,7 +48,7 @@ Public Module ModMod
Get
Load()
If Not IsFileAvailable Then
- Return McModState.Unavaliable
+ Return McModState.Unavailable
ElseIf Path.EndsWithF(".disabled", True) OrElse Path.EndsWithF(".old", True) Then
Return McModState.Disabled
Else
@@ -58,7 +59,7 @@ Public Module ModMod
Public Enum McModState As Integer
Fine = 0
Disabled = 1
- Unavaliable = 2
+ Unavailable = 2
End Enum
#End Region
@@ -687,7 +688,7 @@ Finished:
'''
Public ReadOnly Property CanUpdate As Boolean
Get
- Return ChangelogUrls.Any()
+ Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso ChangelogUrls.Any()
End Get
End Property
@@ -701,7 +702,7 @@ Finished:
Dim Info As New FileInfo(Path)
Dim CacheKey As String = GetHash($"{RawPath}-{Info.LastWriteTime.ToLongTimeString}-{Info.Length}-C")
Dim Cached As String = ReadIni(PathTemp & "Cache\ModHash.ini", CacheKey)
- If Cached <> "" Then
+ If Cached <> "" AndAlso RegexCheck(Cached, "^\d+$") Then '#5062
_CurseForgeHash = Cached
Return _CurseForgeHash
End If
@@ -814,7 +815,7 @@ Finished:
'等待 Mod 更新完成
If PageVersionMod.UpdatingVersions.Contains(Loader.Input) Then
- Log($"[Mod] 等待 Mod 更新完成后才能继续加载 Mod 列表")
+ Log($"[Mod] 等待 Mod 更新完成后才能继续加载 Mod 列表:" & Loader.Input)
Try
RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.Text = "正在更新 Mod")
Do Until Not PageVersionMod.UpdatingVersions.Contains(Loader.Input)
@@ -824,6 +825,7 @@ Finished:
Finally
RunInUiWait(Sub() If FrmVersionMod IsNot Nothing Then FrmVersionMod.Load.Text = "正在加载 Mod 列表")
End Try
+ FrmVersionMod.LoaderRun(LoaderFolderRunType.UpdateOnly)
End If
'获取 Mod 文件夹下的可用文件列表
@@ -852,7 +854,7 @@ Finished:
Dim Cache As New JObject
Try
Dim CacheContent As String = ReadFile(CachePath)
- If CacheContent <> "" Then
+ If Not String.IsNullOrWhiteSpace(CacheContent) Then
Cache = GetJson(CacheContent)
If Not Cache.ContainsKey("version") OrElse Cache("version").ToObject(Of Integer) <> LocalModCacheVersion Then
Log($"[Mod] 本地 Mod 信息缓存版本已过期,将弃用这些缓存信息", LogLevel.Debug)
@@ -861,6 +863,7 @@ Finished:
End If
Catch ex As Exception
Log(ex, "读取本地 Mod 信息缓存失败,已重置")
+ Cache = New JObject
End Try
Cache("version") = LocalModCacheVersion
@@ -886,7 +889,7 @@ Finished:
End If
ModList.Add(ModEntry)
'读取 Comp 缓存
- If ModEntry.State = McMod.McModState.Unavaliable Then Continue For
+ If ModEntry.State = McMod.McModState.Unavailable Then Continue For
Dim CacheKey = ModEntry.ModrinthHash & PageVersionLeft.Version.Version.McName & GetTargetModLoaders().Join("")
If Cache.ContainsKey(CacheKey) Then
ModEntry.FromJson(Cache(CacheKey))
@@ -899,10 +902,10 @@ Finished:
Log($"[Mod] 共有 {ModList.Count} 个 Mod,其中 {ModUpdateList.Where(Function(m) m.Comp Is Nothing).Count} 个需要联网获取信息,{ModUpdateList.Where(Function(m) m.Comp IsNot Nothing).Count} 个需要更新信息")
'排序
- ModList = Sort(ModList,
+ ModList = ModList.Sort(
Function(Left As McMod, Right As McMod) As Boolean
- If (Left.State = McMod.McModState.Unavaliable) <> (Right.State = McMod.McModState.Unavaliable) Then
- Return Left.State = McMod.McModState.Unavaliable
+ If (Left.State = McMod.McModState.Unavailable) <> (Right.State = McMod.McModState.Unavailable) Then
+ Return Left.State = McMod.McModState.Unavailable
Else
Return Not Right.FileName.CompareTo(Left.FileName)
End If
@@ -924,27 +927,18 @@ Finished:
End Try
End Sub
'联网加载 Mod 详情
- Private Const LocalModCacheVersion As Integer = 6
Public McModDetailLoader As New LoaderTask(Of KeyValuePair(Of List(Of McMod), JObject), Integer)("Mod List Detail Loader", AddressOf McModDetailLoad)
Private Sub McModDetailLoad(Loader As LoaderTask(Of KeyValuePair(Of List(Of McMod), JObject), Integer))
Dim Mods As List(Of McMod) = Loader.Input.Key
Dim Cache As JObject = Loader.Input.Value
'获取作为检查目标的加载器和版本
+ '此处不应向下扩展检查的 MC 小版本,例如 Mod 在更新 1.16.5 后,对早期的 1.16.2 版本发布了修补补丁,这会导致 PCL 将 1.16.5 版本的 Mod 降级到 1.16.2
Dim TargetMcVersion As McVersionInfo = PageVersionLeft.Version.Version
Dim ModLoaders = GetTargetModLoaders()
Dim McVersion = TargetMcVersion.McName
- '暂不向下扩展检查的 MC 小版本
- '例如:Mod 在更新 1.16.5 后,对早期的 1.16.2 版本发布了修补补丁,这会导致 PCL 将 1.16.5 版本的 Mod 降级到 1.16.2
- 'If TargetMcVersion.McCodeMain > 0 AndAlso TargetMcVersion.McCodeMain < 99 Then
- ' McVersions.Add($"1.{TargetMcVersion.McCodeMain}")
- ' For i = 1 To TargetMcVersion.McCodeSub
- ' McVersions.Add($"1.{TargetMcVersion.McCodeMain}.{i}")
- ' Next
- 'End If
- 'McVersions = McVersions.Distinct().ToList()
'开始网络获取
Log($"[Mod] 目标加载器:{ModLoaders.Join("/")},版本:{McVersion}")
- Dim EndedThreadCount As Integer = 0, SucceedThreadCount As Integer = 0
+ Dim EndedThreadCount As Integer = 0, IsFailed As Boolean = False
Dim MainThread As Thread = Thread.CurrentThread
'从 Modrinth 获取信息
RunInNewThread(
@@ -979,7 +973,6 @@ Finished:
For Each ProjectJson In ModrinthProject
Dim Project As New CompProject(ProjectJson)
For Each Entry In ModrinthMapping(Project.Id)
- If Entry.Comp IsNot Nothing AndAlso Entry.Comp.FromCurseForge Then Project.LogoUrl = Entry.Comp.LogoUrl 'Modrinth 的部分 Logo 不是图片,如果可能,使用 CurseForge 的 Logo
Entry.Comp = Project
Next
Next
@@ -1005,9 +998,9 @@ Finished:
End If
Next
Log($"[Mod] 从 Modrinth 获取本地 Mod 信息结束")
- SucceedThreadCount += 1
Catch ex As Exception
Log(ex, "从 Modrinth 获取本地 Mod 信息失败")
+ IsFailed = True
Finally
EndedThreadCount += 1
End Try
@@ -1023,7 +1016,7 @@ Finished:
If Loader.IsAbortedWithThread(MainThread) Then Exit Sub
Next
Dim CurseForgeRaw = CType(CType(GetJson(DlModRequest("https://api.curseforge.com/v1/fingerprints/432", "POST",
- $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
+ $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息")
'步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
If Not CurseForgeRaw.Any() Then Exit Sub
@@ -1046,7 +1039,7 @@ Finished:
'步骤 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")
+ $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("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
@@ -1055,21 +1048,19 @@ Finished:
Dim Project As New CompProject(ProjectJson)
For Each Entry In CurseForgeMapping(Project.Id) '倒查防止 CurseForge 返回的内容有漏
If Entry.Comp IsNot Nothing AndAlso Not Entry.Comp.FromCurseForge Then
- Entry.Comp.LogoUrl = Project.LogoUrl 'Modrinth 部分 Logo 加载不出来
Entry.Comp = Entry.Comp '再次触发修改事件
Continue For
End If
Entry.Comp = Project
Next
'查找或许版本更新的文件列表
- If ModLoaders.Count = 1 Then 'TODO: 结果有多个 ModLoader 时提示无法检测更新
+ If ModLoaders.Count = 1 Then
Dim NewestVersion As String = Nothing
Dim NewestFileIds As New List(Of Integer)
For Each IndexEntry In ProjectJson("latestFilesIndexes")
- If IndexEntry("modLoader") Is Nothing OrElse Not IndexEntry("modLoader").HasValues OrElse '镜像源会返回一个值为 null 的键
- ModLoaders.Single <> IndexEntry("modLoader").ToObject(Of Integer) Then Continue For 'ModLoader 唯一且匹配
+ If IndexEntry("modLoader") Is Nothing OrElse ModLoaders.Single <> IndexEntry("modLoader").ToObject(Of Integer) Then Continue For 'ModLoader 唯一且匹配
Dim IndexVersion As String = IndexEntry("gameVersion")
- If McVersion <> IndexVersion Then Continue For 'MC 版本匹配
+ If IndexVersion <> McVersion Then Continue For 'MC 版本匹配
'由于 latestFilesIndexes 是按时间从新到老排序的,所以只需取第一个;如果需要检查多个 releaseType 下的文件,将 > -1 改为 = 1,但这应当并不会获取到更新的文件
If NewestVersion IsNot Nothing AndAlso VersionSortInteger(NewestVersion, IndexVersion) > -1 Then Continue For '只保留最新 MC 版本
If NewestVersion <> IndexVersion Then
@@ -1114,9 +1105,9 @@ Finished:
End If
Next
Log($"[Mod] 从 CurseForge 获取 Mod 更新信息结束")
- SucceedThreadCount += 1
Catch ex As Exception
Log(ex, "从 CurseForge 获取本地 Mod 信息失败")
+ IsFailed = True
Finally
EndedThreadCount += 1
End Try
@@ -1131,14 +1122,14 @@ Finished:
Log($"[Mod] 联网获取本地 Mod 信息完成,为 {Mods.Count} 个 Mod 更新缓存")
If Not Mods.Any() Then Exit Sub
For Each Entry In Mods
- Entry.CompLoaded = SucceedThreadCount = 2
+ 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())
End Sub
- Private Function GetTargetModLoaders() As List(Of CompModLoaderType)
+ Public Function GetTargetModLoaders() As List(Of CompModLoaderType)
Dim ModLoaders As New List(Of CompModLoaderType)
If PageVersionLeft.Version.Version.HasForge Then ModLoaders.Add(CompModLoaderType.Forge)
If PageVersionLeft.Version.Version.HasNeoForge Then ModLoaders.Add(CompModLoaderType.NeoForge)
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
index 4b82a94..dc867e3 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
@@ -9,21 +9,33 @@ Public Module ModModpack
Public Sub ModpackInstall()
Dim File As String = SelectFile("整合包文件(*.rar;*.zip;*.mrpack)|*.rar;*.zip;*.mrpack", "选择整合包压缩文件") '选择整合包文件
If String.IsNullOrEmpty(File) Then Exit Sub
- RunInThread(Sub() ModpackInstall(File))
+ RunInThread(
+ Sub()
+ Try
+ ModpackInstall(File)
+ Catch ex As CancelledException
+ Catch ex As Exception
+ Log(ex, "手动安装整合包失败", LogLevel.Msgbox)
+ End Try
+ End Sub)
End Sub
'''
- ''' 安装一个给定的整合包文件,返回启动的安装加载器,如果未成功启动则返回 Nothing。
+ ''' 构建并启动安装给定的整合包文件的加载器,并返回该加载器。若失败则抛出异常。
''' 必须在工作线程执行。
'''
- Public Function ModpackInstall(File As String, Optional VersionName As String = Nothing, Optional ShowHint As Boolean = True, Optional Logo As String = Nothing) As LoaderCombo(Of String)
+ '''
+ Public Function ModpackInstall(File As String, Optional VersionName As String = Nothing, Optional Logo As String = Nothing) As LoaderCombo(Of String)
Log("[ModPack] 整合包安装请求:" & If(File, "null"))
Dim Archive As ZipArchive = Nothing
Dim ArchiveBaseFolder As String = ""
Try
+ '字符校验
+ Dim TargetFolder As String = $"{PathMcFolder}versions\{VersionName}\"
+ If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("游戏路径中不能含有感叹号或分号:" & TargetFolder, HintType.Critical) : Throw New CancelledException
'获取整合包种类与关键 Json
Dim PackType As Integer = -1
Try
- Archive = New ZipArchive(New FileStream(File, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ Archive = New ZipArchive(New FileStream(File, FileMode.Open, FileAccess.Read, FileShare.Read))
'从根目录判断整合包类型
If Archive.GetEntry("mcbbs.packmeta") IsNot Nothing Then PackType = 3 : Exit Try 'MCBBS 整合包(优先于 manifest.json 判断)
If Archive.GetEntry("mmc-pack.json") IsNot Nothing Then PackType = 2 : Exit Try 'MMC 整合包(优先于 manifest.json 判断,#4194)
@@ -37,11 +49,11 @@ Public Module ModModpack
End If
End If
If Archive.GetEntry("modpack.json") IsNot Nothing Then PackType = 1 : Exit Try 'HMCL 整合包
+ If Archive.GetEntry("modpack.zip") IsNot Nothing OrElse Archive.GetEntry("modpack.mrpack") IsNot Nothing Then PackType = 9 : Exit Try '带启动器的压缩包
'从一级目录判断整合包类型
For Each Entry In Archive.Entries
Dim FullNames As String() = Entry.FullName.Split("/")
ArchiveBaseFolder = FullNames(0) & "/"
- If Entry.FullName.EndsWithF("/versions/") AndAlso FullNames.Count = 3 Then PackType = 9 : Exit Try '压缩包
'确定为一级目录下
If FullNames.Count <> 2 Then Continue For
'判断是否为关键文件
@@ -57,17 +69,15 @@ Public Module ModModpack
End If
End If
If FullNames(1) = "modpack.json" Then PackType = 1 : Exit Try 'HMCL 整合包
+ 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
- Log(ex, "打开整合包文件失败", If(ShowHint, LogLevel.Hint, LogLevel.Normal))
- Return Nothing
+ Throw New Exception("打开整合包文件失败", ex)
ElseIf File.EndsWithF(".rar", True) Then
- Log(ex, "PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", If(ShowHint, LogLevel.Hint, LogLevel.Normal))
- Return Nothing
+ Throw New Exception("PCL 无法处理 rar 格式的压缩包,请在解压后重新压缩为 zip 格式再试", ex)
Else
- Log(ex, "打开整合包文件失败,文件可能损坏或为不支持的压缩包格式", If(ShowHint, LogLevel.Hint, LogLevel.Normal))
- Return Nothing
+ Throw New Exception("打开整合包文件失败,文件可能损坏或为不支持的压缩包格式", ex)
End If
End Try
'执行对应的安装方法
@@ -88,57 +98,28 @@ Public Module ModModpack
Log("[ModPack] 整合包种类:Modrinth")
Return InstallPackModrinth(File, Archive, ArchiveBaseFolder, VersionName, Logo)
Case 9
- Log("[ModPack] 整合包种类:压缩包")
- Archive.Dispose()
- Archive = Nothing
- Return InstallPackCompress(File, ArchiveBaseFolder)
+ Log("[ModPack] 整合包种类:带启动器的压缩包")
+ Return InstallPackLauncherPack(File, Archive, ArchiveBaseFolder)
Case Else
- If ShowHint Then
- Hint("未能识别该整合包的种类,无法安装!", HintType.Critical)
- Else
- Log("[ModPack] 未能识别该整合包的种类,无法安装!")
- End If
- Return Nothing
+ Log("[ModPack] 整合包种类:未能识别,假定为压缩包")
+ Return InstallPackCompress(File, Archive)
End Select
- Catch ex As Exception
- Log(ex, "准备安装整合包失败", LogLevel.Feedback)
- Return Nothing
Finally
If Archive IsNot Nothing Then Archive.Dispose()
End Try
End Function
- '整合包缓存清理
- Private IsInstallCacheCleared As Boolean = False
- Private IsInstallCacheClearing As Boolean = False
- Private Sub UnpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase)
- '清理缓存文件夹
- If Not IsInstallCacheCleared Then
- IsInstallCacheCleared = True
- IsInstallCacheClearing = True
- Try
- Log("[ModPack] 开始清理整合包安装缓存")
- DeleteDirectory(PathTemp & "PackInstall\")
- Log("[ModPack] 已清理整合包安装缓存")
- Catch ex As Exception
- Log(ex, "清理整合包安装缓存失败")
- Finally
- IsInstallCacheClearing = False
- End Try
- ElseIf IsInstallCacheClearing Then
- '等待另一个整合包安装的清理步骤完成
- Do While IsInstallCacheClearing
- Thread.Sleep(1)
- Loop
- End If
+ Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase, LoaderProgressDelta 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)
+ ExtractFile(FileAddress, InstallTemp, Encode, ProgressIncrementHandler:=Sub(Delta) Loader.Progress += Delta * LoaderProgressDelta)
Catch ex As Exception
Log(ex, "第 " & RetryCount & " 次解压尝试失败")
If TypeOf ex Is ArgumentException Then
@@ -151,7 +132,7 @@ Retry:
RetryCount += 1
GoTo Retry
Else
- Throw
+ Throw New Exception("解压整合包文件失败", ex)
End If
End Try
End Sub
@@ -166,20 +147,18 @@ Retry:
Dim Json As JObject
Try
Json = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "manifest.json").Open))
- If Json("minecraft") Is Nothing OrElse Json("minecraft")("version") Is Nothing Then Throw New Exception("整合包未提供 Minecraft 版本信息")
Catch ex As Exception
- Log(ex, "CurseForge 整合包安装信息存在问题", LogLevel.Hint)
- Return Nothing
+ Throw New Exception("CurseForge 整合包安装信息存在问题", ex)
End Try
+ If Json("minecraft") Is Nothing OrElse Json("minecraft")("version") Is Nothing Then Throw New Exception("CurseForge 整合包未提供 Minecraft 版本信息")
'获取版本名
- Dim ShowRibble As Boolean = VersionName Is Nothing
If VersionName Is Nothing Then
VersionName = If(Json("name"), "")
Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
If Validate.Validate(VersionName) <> "" Then VersionName = ""
If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
- If String.IsNullOrEmpty(VersionName) Then Return Nothing
+ If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
End If
'获取 Mod API 版本信息
@@ -190,57 +169,44 @@ Retry:
Dim Id As String = If(Entry("id"), "").ToString.ToLower
If Id.StartsWithF("forge-") Then
'Forge 指定
- If Id.Contains("recommended") Then
- Log("[ModPack] 该整合包版本过老,已不支持进行安装!", LogLevel.Hint)
- Return Nothing
- End If
- Try
- Log("[ModPack] 整合包 Forge 版本:" & Id)
- ForgeVersion = Id.Replace("forge-", "")
- Exit For
- Catch ex As Exception
- Log(ex, "读取整合包 Forge 版本失败:" & Id)
- End Try
+ If Id.Contains("recommended") Then Throw New Exception("该整合包版本过老,已不支持进行安装!")
+ Log("[ModPack] 整合包 Forge 版本:" & Id)
+ ForgeVersion = Id.Replace("forge-", "")
ElseIf Id.StartsWithF("neoforge-") Then
'NeoForge 指定
- Try
- Log("[ModPack] 整合包 NeoForge 版本:" & Id)
- NeoForgeVersion = Id.Replace("neoforge-", "")
- Exit For
- Catch ex As Exception
- Log(ex, "读取整合包 NeoForge 版本失败:" & Id)
- End Try
+ Log("[ModPack] 整合包 NeoForge 版本:" & Id)
+ NeoForgeVersion = Id.Replace("neoforge-", "")
ElseIf Id.StartsWithF("fabric-") Then
'Fabric 指定
- Try
- Log("[ModPack] 整合包 Fabric 版本:" & Id)
- FabricVersion = Id.Replace("fabric-", "")
- Exit For
- Catch ex As Exception
- Log(ex, "读取整合包 Fabric 版本失败:" & Id)
- End Try
+ Log("[ModPack] 整合包 Fabric 版本:" & Id)
+ FabricVersion = Id.Replace("fabric-", "")
+ Else
+ Log("[ModPack] 未知 Mod 加载器:" & Id)
End If
Next
'解压与配置文件
- Dim InstallTemp As String = PathTemp & "PackInstall\" & RandomInteger(0, 100000) & "\"
+ Dim InstallTemp As String = RequestTaskTempFolder()
Dim InstallLoaders As New List(Of LoaderBase)
Dim OverrideHome As String = If(Json("overrides"), "")
If OverrideHome <> "" Then
InstallLoaders.Add(New LoaderTask(Of String, Integer)("解压整合包文件",
Sub(Task As LoaderTask(Of String, Integer))
- UnpackFiles(InstallTemp, FileAddress, Task)
- Task.Progress = 0.5
- Dim OverridePath As String = InstallTemp & ArchiveBaseFolder & OverrideHome
+ ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
+ Task.Progress = 0.6
'复制结果
- If Directory.Exists(OverridePath) Then
- CopyDirectory(OverridePath, PathMcFolder & "versions\" & VersionName)
- Log($"[ModPack] 整合包 override 复制:{OverridePath} -> {PathMcFolder & "versions\" & VersionName}")
+ 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 文件夹:{OverridePath}")
+ Log($"[ModPack] 整合包中未找到 override 文件夹:{OverrideRoot}")
End If
- Task.Progress = 0.9
+ Task.Progress = 0.95
'开启版本隔离
- WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
+ 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
End If
@@ -262,7 +228,7 @@ Retry:
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")
'如果文件已被删除,则 API 会跳过那一项
- If ModList.Count > Task.Output.Count Then Throw New Exception("整合包所需要的部分 Mod 版本已被 Mod 作者删除,因此无法完成整合包安装,请联系整合包作者更新整合包中的 Mod 版本")
+ If ModList.Count > Task.Output.Count Then Throw New Exception("整合包中的部分 Mod 版本已被 Mod 作者删除,所以没法继续安装了,请向整合包作者反馈该问题")
End Sub) With {.ProgressWeight = ModList.Count / 10}) '每 10 Mod 需要 1s
'构造 NetFile
ModDownloadLoaders.Add(New LoaderTask(Of JArray, List(Of NetFile))("构造 Mod 下载信息",
@@ -320,7 +286,6 @@ Retry:
.FabricVersion = FabricVersion
}
Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
- If MergeLoaders Is Nothing Then Return Nothing
'构造 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})
@@ -333,7 +298,7 @@ Retry:
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")
@@ -342,18 +307,22 @@ Retry:
End If
'删除原始整合包文件
For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
- If Not Setup.Get("ToolDownloadKeepModpack") AndAlso File.Exists(Target) Then
- Log("[ModPack] 根据设置要求删除原始整合包文件:" & Target)
+ If File.Exists(Target) Then
+ Log("[ModPack] 删除原始整合包文件:" & Target)
File.Delete(Target)
End If
Next
+ If File.Exists(FileAddress) AndAlso GetFileNameWithoutExtentionFromPath(FileAddress) = "modpack" Then
+ Log("[ModPack] 删除安装整合包文件:" & FileAddress)
+ File.Delete(FileAddress)
+ End If
End Sub) With {.ProgressWeight = 0.1, .Show = False})
'重复任务检查
Dim LoaderName As String = "CurseForge 整合包安装:" & VersionName & " "
If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
Hint("该整合包正在安装中!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
End If
'启动
@@ -361,7 +330,7 @@ Retry:
Loader.Start(Request.TargetVersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
- If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
+ RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
Return Loader
End Function
@@ -372,11 +341,10 @@ Retry:
Dim Json As JObject
Try
Json = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "modrinth.index.json").Open))
- If Json("dependencies") Is Nothing OrElse Json("dependencies")("minecraft") Is Nothing Then Throw New Exception("整合包未提供 Minecraft 版本信息")
Catch ex As Exception
- Log(ex, "整合包安装信息存在问题", LogLevel.Hint)
- Return Nothing
+ Throw New Exception("Modrinth 整合包安装信息存在问题", ex)
End Try
+ If Json("dependencies") Is Nothing OrElse Json("dependencies")("minecraft") Is Nothing Then Throw New Exception("Modrinth 整合包未提供 Minecraft 版本信息")
'获取 Mod API 版本信息
Dim MinecraftVersion As String = Nothing
Dim ForgeVersion As String = Nothing
@@ -397,41 +365,43 @@ Retry:
Log("[ModPack] 整合包 Fabric 版本:" & FabricVersion)
Case "quilt-loader" 'eg. 1.0.0
Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
Case Else
Hint($"无法安装整合包,其中出现了未知的 Mod 加载器 {Entry.Value}!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
End Select
Next
'获取版本名
- Dim ShowRibble As Boolean = VersionName Is Nothing
If VersionName Is Nothing Then
VersionName = If(Json("name"), "")
Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
If Validate.Validate(VersionName) <> "" Then VersionName = ""
If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
- If String.IsNullOrEmpty(VersionName) Then Return Nothing
+ If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
End If
'解压和配置文件
- Dim InstallTemp As String = PathTemp & "PackInstall\" & RandomInteger(0, 100000) & "\"
+ 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))
- UnpackFiles(InstallTemp, FileAddress, Task)
- Task.Progress = 0.5
+ 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)
+ CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName,
+ Sub(Delta) Task.Progress += Delta * 0.25)
Else
Log("[ModPack] 整合包中未找到 override 目录,已跳过")
End If
- Task.Progress = 0.8
+ Task.Progress = 0.85
If Directory.Exists(InstallTemp & ArchiveBaseFolder & "client-overrides") Then
- CopyDirectory(InstallTemp & ArchiveBaseFolder & "client-overrides", PathMcFolder & "versions\" & VersionName)
+ CopyDirectory(InstallTemp & ArchiveBaseFolder & "client-overrides", PathMcFolder & "versions\" & VersionName,
+ Sub(Delta) Task.Progress += Delta * 0.1)
End If
- Task.Progress = 0.9
+ Task.Progress = 0.95
'开启版本隔离
- WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
+ 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
'获取下载文件列表
Dim FileList As New List(Of NetFile)
@@ -440,7 +410,7 @@ Retry:
If File("env") IsNot Nothing Then
Select Case File("env")("client").ToString
Case "optional"
- If MyMsgBox("是否要下载整合包中的可选文件 " & GetFileNameFromPath(File("path").ToString) & "?",
+ If MyMsgBox("是否要下载可选文件 " & GetFileNameFromPath(File("path").ToString) & "?",
"下载可选文件", "是", "否") = 2 Then
Continue For
End If
@@ -469,7 +439,6 @@ Retry:
.FabricVersion = FabricVersion
}
Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
- If MergeLoaders Is Nothing Then Return Nothing
'构造 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})
@@ -491,18 +460,22 @@ Retry:
End If
'删除原始整合包文件
For Each Target As String In {VersionFolder & "原始整合包.zip", VersionFolder & "原始整合包.mrpack"}
- If Not Setup.Get("ToolDownloadKeepModpack") AndAlso File.Exists(Target) Then
- Log("[ModPack] 根据设置要求删除原始整合包文件:" & Target)
+ If File.Exists(Target) Then
+ Log("[ModPack] 删除原始整合包文件:" & Target)
File.Delete(Target)
End If
Next
+ If File.Exists(FileAddress) AndAlso GetFileNameWithoutExtentionFromPath(FileAddress) = "modpack" Then
+ Log("[ModPack] 删除安装整合包文件:" & FileAddress)
+ File.Delete(FileAddress)
+ End If
End Sub) With {.ProgressWeight = 0.1, .Show = False})
'重复任务检查
- Dim LoaderName As String = "Modrinth 整合包安装:" & VersionName & " "
+ Dim LoaderName As String = $"Modrinth 整合包安装:{VersionName} "
If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
Hint("该整合包正在安装中!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
End If
'启动
@@ -510,9 +483,8 @@ Retry:
Loader.Start(Request.TargetVersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
- If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
+ RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
Return Loader
-
End Function
'HMCL
@@ -522,44 +494,40 @@ Retry:
Try
Json = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "modpack.json").Open, Encoding.UTF8))
Catch ex As Exception
- Log(ex, "整合包安装信息存在问题", LogLevel.Hint)
- Return Nothing
+ Throw New Exception("HMCL 整合包安装信息存在问题", ex)
End Try
'获取版本名
Dim VersionName As String = If(Json("name"), "")
Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
If Validate.Validate(VersionName) <> "" Then VersionName = ""
If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
- If String.IsNullOrEmpty(VersionName) Then Return Nothing
+ If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
'解压与配置文件
- Dim InstallTemp As String = PathTemp & "PackInstall\" & RandomInteger(0, 100000) & "\"
+ 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))
- UnpackFiles(InstallTemp, FileAddress, Task)
- Task.Progress = 0.5
+ ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
+ Task.Progress = 0.6
'复制结果
If Directory.Exists(InstallTemp & ArchiveBaseFolder & "minecraft") Then
- CopyDirectory(InstallTemp & ArchiveBaseFolder & "minecraft", PathMcFolder & "versions\" & VersionName)
+ CopyDirectory(InstallTemp & ArchiveBaseFolder & "minecraft", PathMcFolder & "versions\" & VersionName, Sub(Delta) Task.Progress += Delta * 0.35)
Else
Log("[ModPack] 整合包中未找到 minecraft override 目录,已跳过")
End If
- Task.Progress = 0.9
+ Task.Progress = 0.95
'开启版本隔离
- WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
+ 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("gameVersion") Is Nothing Then
- Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical)
- Return Nothing
- End If
+ 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)
- If MergeLoaders Is Nothing Then Return Nothing
'构造 Libraries 加载器(为了使得 Mods 下载结束后再构造,这样才会下载 JumpLoader 文件)
Dim LoadersLib As New List(Of LoaderBase)
LoadersLib.Add(New LoaderTask(Of String, String)("重命名版本 Json(副加载器)",
@@ -589,7 +557,7 @@ Retry:
Dim LoaderName As String = "HMCL 整合包安装:" & VersionName & " "
If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
Hint("该整合包正在安装中!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
End If
'启动
@@ -598,7 +566,7 @@ Retry:
Loader.Start(Request.TargetVersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
- FrmMain.BtnExtraDownload.Ribble()
+ RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
Return Loader
End Function
@@ -610,38 +578,44 @@ Retry:
PackJson = GetJson(ReadFile(Archive.GetEntry(ArchiveBaseFolder & "mmc-pack.json").Open, Encoding.UTF8))
PackInstance = ReadFile(Archive.GetEntry(ArchiveBaseFolder & "instance.cfg").Open, Encoding.UTF8)
Catch ex As Exception
- Log(ex, "整合包安装信息存在问题", LogLevel.Hint)
- Return Nothing
+ Throw New Exception("MMC 整合包安装信息存在问题", ex)
End Try
'获取版本名
Dim VersionName As String = If(RegexSeek(PackInstance, "(?<=\nname\=)[^\n]+"), "")
Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
If Validate.Validate(VersionName) <> "" Then VersionName = ""
If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
- If String.IsNullOrEmpty(VersionName) Then Return Nothing
+ If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
'解压、配置设置文件
- Dim InstallTemp As String = $"{PathTemp}PackInstall\{RandomInteger(0, 100000)}\"
+ 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))
- UnpackFiles(InstallTemp, FileAddress, Task)
- Task.Progress = 0.5
+ ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
+ Task.Progress = 0.6
'复制结果
If Directory.Exists(InstallTemp & ArchiveBaseFolder & ".minecraft") Then
- CopyDirectory(InstallTemp & ArchiveBaseFolder & ".minecraft", PathMcFolder & "versions\" & VersionName)
+ CopyDirectory(InstallTemp & ArchiveBaseFolder & ".minecraft", PathMcFolder & "versions\" & VersionName, Sub(Delta) Task.Progress += Delta * 0.35)
Else
Log("[ModPack] 整合包中未找到 override .minecraft 目录,已跳过")
End If
- Task.Progress = 0.9
+ Task.Progress = 0.95
'开启版本隔离
WriteIni(SetupFile, "VersionArgumentIndie", 1)
+ WriteIni(SetupFile, "VersionArgumentIndieV2", True)
'读取 MMC 设置文件(#2655)
Try
Dim MMCSetupFile As String = InstallTemp & ArchiveBaseFolder & "instance.cfg"
If File.Exists(MMCSetupFile) Then
'将其中的等号替换为冒号,以符合 ini 文件格式
- WriteFile(MMCSetupFile, ReadFile(MMCSetupFile).Replace("=", ":"))
+ Dim Lines As New List(Of String)
+ For Each Line In ReadFile(MMCSetupFile).Split({vbCr, vbLf}, StringSplitOptions.RemoveEmptyEntries)
+ If Not Line.Contains("=") Then Continue For
+ Lines.Add(Line.BeforeFirst("=") & ":" & Line.AfterFirst("="))
+ Next
+ WriteFile(MMCSetupFile, Join(Lines, vbCrLf))
+ '读取文件
If ReadIni(MMCSetupFile, "OverrideCommands", False) Then
Dim PreLaunchCommand As String = ReadIni(MMCSetupFile, "PreLaunchCommand")
If PreLaunchCommand <> "" Then
@@ -670,16 +644,25 @@ Retry:
CopyFile($"{InstallTemp}{ArchiveBaseFolder}{Logo}.png", $"{PathMcFolder}versions\{VersionName}\PCL\Logo.png")
Log($"[ModPack] 迁移 MultiMC 版本独立设置:版本图标({Logo}.png)")
End If
+ 'JVM 参数
+ Dim JvmArgs As String = ReadIni(MMCSetupFile, "JvmArgs", "")
+ If JvmArgs <> "" Then
+ If ReadIni(MMCSetupFile, "OverrideJavaArgs", False) Then
+ WriteIni(SetupFile, "VersionAdvanceJvm", JvmArgs)
+ Log("[ModPack] 迁移 MultiMC 版本独立设置:JVM 参数(覆盖):" & JvmArgs)
+ Else
+ JvmArgs += " " & Setup.Get("LaunchAdvanceJvm")
+ WriteIni(SetupFile, "VersionAdvanceJvm", JvmArgs)
+ Log("[ModPack] 迁移 MultiMC 版本独立设置:JVM 参数(追加):" & JvmArgs)
+ End If
+ End If
End If
Catch ex As Exception
Log(ex, $"读取 MMC 配置文件失败({InstallTemp}{ArchiveBaseFolder}instance.cfg)")
End Try
End Sub) With {.ProgressWeight = New FileInfo(FileAddress).Length / 1024 / 1024 / 6, .Block = False}) '每 6M 需要 1s
'构造版本安装请求
- If PackJson("components") Is Nothing Then
- Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical)
- Return Nothing
- End If
+ 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
@@ -695,12 +678,11 @@ Retry:
Request.FabricVersion = Component("version")
Case "org.quiltmc.quilt-loader" 'eg. 1.0.0
Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
End Select
Next
'构造加载器
Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
- If MergeLoaders Is Nothing Then Return Nothing
'构造 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})
@@ -715,7 +697,7 @@ Retry:
Dim LoaderName As String = "MMC 整合包安装:" & VersionName & " "
If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
Hint("该整合包正在安装中!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
End If
'启动
@@ -723,7 +705,7 @@ Retry:
Loader.Start(Request.TargetVersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
- FrmMain.BtnExtraDownload.Ribble()
+ RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
Return Loader
End Function
@@ -736,51 +718,45 @@ Retry:
Dim Entry = If(Archive.GetEntry(ArchiveBaseFolder & "mcbbs.packmeta"), Archive.GetEntry(ArchiveBaseFolder & "manifest.json"))
Json = GetJson(ReadFile(Entry.Open, Encoding.UTF8))
Catch ex As Exception
- Log(ex, "整合包安装信息存在问题", LogLevel.Hint)
- Return Nothing
+ Throw New Exception("MCBBS 整合包安装信息存在问题", ex)
End Try
'获取版本名
- Dim ShowRibble As Boolean = VersionName Is Nothing
If VersionName Is Nothing Then
VersionName = If(Json("name"), "")
Dim Validate As New ValidateFolderName(PathMcFolder & "versions")
If Validate.Validate(VersionName) <> "" Then VersionName = ""
If VersionName = "" Then VersionName = MyMsgBoxInput("输入版本名称", "", "", New ObjectModel.Collection(Of Validate) From {Validate})
- If String.IsNullOrEmpty(VersionName) Then Return Nothing
+ If String.IsNullOrEmpty(VersionName) Then Throw New CancelledException
End If
'解压与配置文件
- Dim InstallTemp As String = PathTemp & "PackInstall\" & RandomInteger(0, 100000) & "\"
+ 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))
- UnpackFiles(InstallTemp, FileAddress, Task)
- Task.Progress = 0.5
+ ExtractModpackFiles(InstallTemp, FileAddress, Task, 0.6)
+ Task.Progress = 0.6
'复制结果
If Directory.Exists(InstallTemp & ArchiveBaseFolder & "overrides") Then
- CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName)
+ CopyDirectory(InstallTemp & ArchiveBaseFolder & "overrides", PathMcFolder & "versions\" & VersionName,
+ Sub(Delta) Task.Progress += 0.35 * Delta)
Else
Log("[ModPack] 整合包中未找到 overrides 目录,已跳过")
End If
- Task.Progress = 0.9
+ Task.Progress = 0.95
'开启版本隔离
- WriteIni(PathMcFolder & "versions\" & VersionName & "\PCL\Setup.ini", "VersionArgumentIndie", 1)
+ 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
- Hint("该整合包未提供游戏版本附加信息,无法安装!", HintType.Critical)
- Return Nothing
- End If
+ If Json("addons") Is Nothing Then Throw New Exception("该 MCBBS 整合包未提供游戏版本附加信息,无法安装!")
Dim Addons As New Dictionary(Of String, String)
For Each Entry In Json("addons")
Addons.Add(Entry("id"), Entry("version"))
Next
- If Not Addons.ContainsKey("game") Then
- Hint("该整合包未提供游戏版本信息,无法安装!", HintType.Critical)
- Return Nothing
- End If
+ If Not Addons.ContainsKey("game") Then Throw New Exception("该 MCBBS 整合包未提供游戏版本信息,无法安装!")
If Addons.ContainsKey("quilt") Then
Hint("PCL 暂不支持安装需要 Quilt 的整合包!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
End If
Dim Request As New McInstallRequest With {
.TargetVersionName = VersionName,
@@ -792,7 +768,6 @@ Retry:
.FabricVersion = If(Addons.ContainsKey("fabric"), Addons("fabric"), Nothing)
}
Dim MergeLoaders As List(Of LoaderBase) = McInstallLoader(Request, True)
- If MergeLoaders Is Nothing Then Return Nothing
'构造 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})
@@ -807,7 +782,7 @@ Retry:
Dim LoaderName As String = "MCBBS 整合包安装:" & VersionName & " "
If LoaderTaskbar.Any(Function(l) l.Name = LoaderName) Then
Hint("该整合包正在安装中!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
End If
'启动
@@ -816,31 +791,111 @@ Retry:
Loader.Start(Request.TargetVersionFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
- If ShowRibble Then FrmMain.BtnExtraDownload.Ribble()
+ RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
+ Return Loader
+ End Function
+
+ '带启动器的压缩包
+ Private Function InstallPackLauncherPack(FileAddress As String, Archive As Compression.ZipArchive, ArchiveBaseFolder As String) As LoaderCombo(Of String)
+ '获取解压路径
+ MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
+ Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)")
+ If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException
+ If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Critical) : Throw New CancelledException
+ '解压
+ Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
+ New LoaderTask(Of String, Integer)("解压压缩包",
+ Sub(Task As LoaderTask(Of String, Integer))
+ ExtractModpackFiles(TargetFolder, FileAddress, Task, 0.9)
+ Thread.Sleep(400) '避免文件争用
+ '查找解压后的 exe 文件
+ Dim Launcher As String = Nothing
+ For Each ExeFile In Directory.GetFiles(TargetFolder, "*.exe", SearchOption.TopDirectoryOnly)
+ Dim Info = FileVersionInfo.GetVersionInfo(ExeFile)
+ Log($"[Modpack] 文件 {ExeFile} 的产品名标识为 {Info.ProductName}")
+ If Info.ProductName = "Plain Craft Launcher" Then
+ Launcher = ExeFile
+ Log($"[Modpack] 发现整合包附带的 PCL 启动器:{ExeFile}")
+ ElseIf (Info.ProductName.ContainsF("Launcher", True) OrElse Info.ProductName.ContainsF("启动", True)) AndAlso
+ Not Info.ProductName = "Plain Craft Launcher Admin Manager" Then
+ If Launcher Is Nothing Then
+ Launcher = ExeFile
+ Log($"[Modpack] 发现整合包附带的疑似第三方启动器:{ExeFile}")
+ End If
+ End If
+ Next
+ Task.Progress = 0.95
+ '尝试使用附带的启动器打开
+ If Launcher IsNot Nothing Then
+ Log("[Modpack] 找到压缩包中附带的启动器:" & Launcher)
+ If MyMsgBox($"整合包里似乎自带了启动器,是否换用它继续安装?{vbCrLf}即将打开:{Launcher}", "换用整合包启动器?", "换用", "不换用") = 1 Then
+ OpenExplorer(TargetFolder)
+ Try
+ ShellOnly(Launcher, "--wait") '要求等待已有的 PCL 退出
+ Log("[Modpack] 为换用整合包中的启动器启动,强制结束程序")
+ FrmMain.EndProgram(False)
+ Catch ex As Exception
+ Log(ex, "打开文件失败", LogLevel.Msgbox)
+ End Try
+ Return
+ End If
+ Else
+ Log("[Modpack] 未找到压缩包中附带的启动器")
+ End If
+ OpenExplorer(TargetFolder)
+ '加入文件夹列表
+ Dim VersionName As String = GetFolderNameFromPath(TargetFolder)
+ Directory.CreateDirectory(TargetFolder & ".minecraft\")
+ PageSelectLeft.AddFolder(
+ TargetFolder & ".minecraft\" & ArchiveBaseFolder.Replace("/", "\").TrimStart("\"), '格式例如:包裹文件夹\.minecraft\(最短为空字符串)
+ VersionName, False)
+ '调用 modpack 文件进行安装
+ Dim ModpackFile = Directory.GetFiles(TargetFolder, "modpack.*", SearchOption.AllDirectories).First
+ Log("[Modpack] 调用 modpack 文件继续安装:" & ModpackFile)
+ ModpackInstall(ModpackFile)
+ End Sub)
+ })
+ Loader.Start(TargetFolder)
+ LoaderTaskbarAdd(Loader)
+ FrmMain.BtnExtraDownload.ShowRefresh()
+ FrmMain.BtnExtraDownload.Ribble()
Return Loader
End Function
'普通压缩包
- Private Function InstallPackCompress(FileAddress As String, ArchiveBaseFolder As String) As LoaderCombo(Of String)
- MyMsgBox("请在接下来打开的窗口中选择安装目标文件夹,它必须是一个空文件夹。", "安装提示", "继续", ForceWait:=True)
+ Private Function InstallPackCompress(FileAddress As String, Archive As Compression.ZipArchive) As LoaderCombo(Of String)
+ '尝试定位 .minecraft 文件夹:寻找形如 “/versions/XXX/XXX.json” 的路径
+ Dim Match As RegularExpressions.Match = Nothing
+ Dim Regex As New RegularExpressions.Regex("^.*\/(?=versions\/(?[^\/]+)\/(\k)\.json$)", RegularExpressions.RegexOptions.IgnoreCase)
+ For Each Entry In Archive.Entries
+ Dim EntryMatch = Regex.Match("/" & Entry.FullName)
+ If EntryMatch.Success Then
+ Match = EntryMatch
+ Exit For
+ End If
+ Next
+ If Match Is Nothing Then Throw New Exception("未能找到适合的文件结构,这可能不是一个 MC 压缩包") '没有匹配
+ Dim ArchiveBaseFolder As String = Match.Value.Replace("/", "\").TrimStart("\") '格式例如:包裹文件夹\.minecraft\(最短为空字符串)
+ Dim VersionName As String = Match.Groups(1).Value
+ Log("[ModPack] 检测到压缩包的 .minecraft 根目录:" & ArchiveBaseFolder & ",命中的版本名:" & VersionName)
'获取解压路径
+ MyMsgBox("接下来请选择一个空文件夹,它会被安装到这个文件夹里。", "安装", "继续", ForceWait:=True)
Dim TargetFolder As String = SelectFolder("选择安装目标(必须是一个空文件夹)")
- If String.IsNullOrEmpty(TargetFolder) Then Return Nothing
- If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Return Nothing
- If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Critical) : Return Nothing
- '要求显示名称
- Dim NewName As String = MyMsgBoxInput("输入显示名称", "输入该文件夹在左边栏列表中显示的名称。", GetFolderNameFromPath(TargetFolder),
- New ObjectModel.Collection(Of Validate) From {New ValidateNullOrWhiteSpace, New ValidateLength(1, 30), New ValidateExcept({">", "|"})})
- If String.IsNullOrWhiteSpace(NewName) Then Return Nothing
+ If String.IsNullOrEmpty(TargetFolder) Then Throw New CancelledException
+ If TargetFolder.Contains("!") OrElse TargetFolder.Contains(";") Then Hint("Minecraft 文件夹路径中不能含有感叹号或分号!", HintType.Critical) : Throw New CancelledException
+ If Directory.GetFileSystemEntries(TargetFolder).Length > 0 Then Hint("请选择一个空文件夹作为安装目标!", HintType.Critical) : Throw New CancelledException
'解压
- Dim Loader As New LoaderCombo(Of String)("安装压缩包", {
- New LoaderTask(Of String, Integer)("安装压缩包",
- Sub()
- UnpackFiles(TargetFolder, FileAddress, Nothing)
- PageSelectLeft.AddFolder(TargetFolder, NewName, False) '加入文件夹列表
- End Sub)
- })
- Loader.Start()
+ Dim Loader As New LoaderCombo(Of String)("解压压缩包", {
+ New LoaderTask(Of String, Integer)("解压压缩包",
+ Sub(Task As LoaderTask(Of String, Integer))
+ ExtractModpackFiles(TargetFolder, FileAddress, Task, 0.95)
+ '加入文件夹列表
+ PageSelectLeft.AddFolder(TargetFolder & ArchiveBaseFolder, GetFolderNameFromPath(TargetFolder), False)
+ Thread.Sleep(400) '避免文件争用
+ RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.VersionSelect))
+ End Sub)
+ }) With {.OnStateChanged = AddressOf McInstallState}
+ Loader.Start(TargetFolder)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
FrmMain.BtnExtraDownload.Ribble()
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb
index 9d16fd2..ce900ea 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModWatcher.vb
@@ -88,26 +88,27 @@
AddHandler GameProcess.ErrorDataReceived, AddressOf LogReceived
'初始化时钟
- RunInNewThread(Sub()
- Try
- Do Until State = MinecraftState.Ended OrElse State = MinecraftState.Crashed OrElse State = MinecraftState.Canceled OrElse Loader.State = LoadState.Aborted
- TimerWindow()
- TimerLog()
- '设置窗口标题
- For i = 1 To 3
- If IsWindowFinished AndAlso IsWindowAppeared AndAlso WindowTitle <> "" AndAlso State = MinecraftState.Running AndAlso Not GameProcess.HasExited Then
- Dim RealTitle As String = WindowTitle.Replace("{date}", Date.Now.ToString("yyyy/M/d")).Replace("{time}", Date.Now.ToString("HH:mm:ss"))
- SetWindowText(WindowHandle, RealTitle.ToCharArray)
- End If
- Thread.Sleep(64)
- Next
- Loop
- WatcherLog("Minecraft 日志监控已退出")
- Catch ex As Exception
- Log(ex, "Minecraft 日志监控主循环出错", LogLevel.Feedback)
- State = MinecraftState.Ended
- End Try
- End Sub, "Minecraft Watcher PID " & PID)
+ RunInNewThread(
+ Sub()
+ Try
+ Do Until State = MinecraftState.Ended OrElse State = MinecraftState.Crashed OrElse State = MinecraftState.Canceled OrElse Loader.State = LoadState.Aborted
+ TimerWindow()
+ TimerLog()
+ '设置窗口标题
+ For i = 1 To 3
+ If IsWindowFinished AndAlso IsWindowAppeared AndAlso WindowTitle <> "" AndAlso State = MinecraftState.Running AndAlso Not GameProcess.HasExited Then
+ Dim RealTitle As String = WindowTitle.Replace("{date}", Date.Now.ToString("yyyy/M/d")).Replace("{time}", Date.Now.ToString("HH:mm:ss"))
+ SetWindowText(WindowHandle, RealTitle.ToCharArray)
+ End If
+ Thread.Sleep(64)
+ Next
+ Loop
+ WatcherLog("Minecraft 日志监控已退出")
+ Catch ex As Exception
+ Log(ex, "Minecraft 日志监控主循环出错", LogLevel.Feedback)
+ State = MinecraftState.Ended
+ End Try
+ End Sub, "Minecraft Watcher PID " & PID)
End Sub
'状态
@@ -349,22 +350,21 @@
WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析")
Hint("检测到 Minecraft 出现错误,错误分析已开始……")
FeedbackInfo()
- RunInNewThread(Sub()
- Try
- Thread.Sleep(2000)
- WatcherLog("崩溃分析开始")
- Dim Analyzer As New CrashAnalyzer(PID)
- Analyzer.Collect(Version.PathIndie, LatestLog.ToList)
- Analyzer.Prepare()
- Analyzer.Analyze(Version)
- Analyzer.Output(False, New List(Of String) From
- {Version.Path & Version.Name & ".json",
- Path & "PCL\Log1.txt",
- Path & "PCL\LatestLaunch.bat"})
- Catch ex As Exception
- Log(ex, "崩溃分析失败", LogLevel.Feedback)
- End Try
- End Sub, "Crash Analyzer")
+ RunInNewThread(
+ Sub()
+ Try
+ Thread.Sleep(2000)
+ WatcherLog("崩溃分析开始")
+ Dim Analyzer As New CrashAnalyzer(PID)
+ Analyzer.Collect(Version.PathIndie, LatestLog.ToList)
+ Analyzer.Prepare()
+ Analyzer.Analyze(Version)
+ Analyzer.Output(False, New List(Of String) From
+ {Version.Path & Version.Name & ".json", Path & "PCL\Log1.txt", Path & "PCL\LatestLaunch.bat"})
+ Catch ex As Exception
+ Log(ex, "崩溃分析失败", LogLevel.Feedback)
+ End Try
+ End Sub, "Crash Analyzer")
End Sub
'强制关闭
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml b/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml
index 8fd7bae..7524a36 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml
+++ b/Plain Craft Launcher 2/Modules/Minecraft/MyCompItem.xaml
@@ -1,6 +1,7 @@
@@ -23,7 +24,7 @@
-
+
$"{PathTemp}CompLogo\{GetHash(_Logo)}.png" Then Exit Sub
- '在完成正常加载后才保存缓存图片
- PathLogo.Source = New MyBitmap(LocalFileAddress & DownloadEnd)
- Catch ex As Exception
- Log(ex, $"读取资源工程图标失败({LocalFileAddress})")
- File.Delete(LocalFileAddress & DownloadEnd)
- LoadError = ex
- End Try
- End Sub)
- If LoadError IsNot Nothing Then Throw LoadError
- If File.Exists(LocalFileAddress) Then
- File.Delete(LocalFileAddress & DownloadEnd)
- Else
- FileIO.FileSystem.MoveFile(LocalFileAddress & DownloadEnd, LocalFileAddress)
- End If
- Catch ex As Exception
- If Not Retried Then
- Retried = True
- GoTo RetryStart
- Else
- Log(ex, $"下载资源工程图标失败({_Logo})")
- RunInUi(Sub() PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png"))
- End If
- End Try
- End Sub
'标题
Public Property Title As String
@@ -175,7 +96,7 @@ RetryStart:
'记录当前展开的卡片标题(#2712)
Dim Titles As New List(Of String)
If FrmMain.PageCurrent.Page = FormMain.PageType.CompDetail Then
- For Each Card As MyCard In FrmDownloadCompDetail.PanMain.Children
+ For Each Card As MyCard In FrmDownloadCompDetail.PanResults.Children
If Card.Title <> "" AndAlso Not Card.IsSwaped Then Titles.Add(Card.Title)
Next
Log("[Comp] 记录当前已展开的卡片:" & String.Join("、", Titles))
@@ -253,7 +174,7 @@ RetryStart:
'''
''' 是否允许交互。目前仅用于 PageDownloadCompDetail 的顶部栏展示:若关闭碰撞检测,则无法展开 Tooltip。
'''
- Public CanInteraction As Boolean = True
+ 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
'判断当前颜色
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml
index a64f249..445f44b 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml
+++ b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml
@@ -24,30 +24,27 @@
-
+
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb
index a623f04..a7bc54b 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/MyLocalModItem.xaml.vb
@@ -6,93 +6,14 @@ Public Class MyLocalModItem
Public Uuid As Integer = GetUuid()
'Logo
- Private _Logo As String = ""
Public Property Logo As String
Get
- Return _Logo
+ Return PathLogo.Source
End Get
Set(value As String)
- If _Logo = value OrElse value Is Nothing Then Exit Property
- _Logo = value
- Dim FileAddress = PathTemp & "CompLogo\" & GetHash(_Logo) & ".png"
- Try
- If _Logo.StartsWithF("http", True) Then
- '网络图片
- If File.Exists(FileAddress) Then
- PathLogo.Source = New MyBitmap(FileAddress)
- Else
- PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png")
- RunInNewThread(Sub() LogoLoader(FileAddress), "Comp Logo Loader " & Uuid & "#", ThreadPriority.BelowNormal)
- End If
- Else
- '位图
- PathLogo.Source = New MyBitmap(_Logo)
- End If
- Catch ex As IOException
- Log(ex, "加载本地 Mod 图标时读取失败(" & FileAddress & ")")
- Catch ex As ArgumentException
- '考虑缓存的图片本身可能有误
- Log(ex, "可视化本地 Mod 图标失败(" & FileAddress & ")")
- Try
- File.Delete(FileAddress)
- Log("[LocalModItem] 已清理损坏的本地 Mod 图标:" & FileAddress)
- Catch exx As Exception
- Log(exx, "清理损坏的本地 Mod 图标缓存失败(" & FileAddress & ")", LogLevel.Hint)
- End Try
- Catch ex As Exception
- Log(ex, "加载本地 Mod 图标失败(" & value & ")")
- End Try
+ PathLogo.Source = value
End Set
End Property
- '后台加载 Logo
- Private Sub LogoLoader(LocalFileAddress As String)
- Dim Retried As Boolean = False
- Dim DownloadEnd As String = GetUuid()
-RetryStart:
- Try
- 'CurseForge 图片使用缩略图
- Dim Url As String = _Logo
- If Url.Contains("/256/256/") AndAlso GetPixelSize(1) <= 1.25 AndAlso Not Retried Then
- Url = Url.Replace("/256/256/", "/64/64/") '#3075:部分 Mod 不存在 64x64 图标,所以重试时不再缩小
- End If
- '下载图片
- NetDownload(Url, LocalFileAddress & DownloadEnd, True)
- If Url.EndsWithF("webp") Then
- Log($"[Comp] Webp 格式转换:{LocalFileAddress}")
- Dim dec = New Imazen.WebP.SimpleDecoder()
- Dim picFile = File.ReadAllBytes(LocalFileAddress & DownloadEnd)
- dec.DecodeFromBytes(picFile, picFile.Length).Save(LocalFileAddress & DownloadEnd)
- End If
- Dim LoadError As Exception = Nothing
- RunInUiWait(
- Sub()
- Try
- '在地址更换时取消加载
- If LocalFileAddress <> $"{PathTemp}CompLogo\{GetHash(_Logo)}.png" Then Exit Sub
- '在完成正常加载后才保存缓存图片
- PathLogo.Source = New MyBitmap(LocalFileAddress & DownloadEnd)
- Catch ex As Exception
- Log(ex, "读取本地 Mod 图标失败(" & LocalFileAddress & ")")
- File.Delete(LocalFileAddress & DownloadEnd)
- LoadError = ex
- End Try
- End Sub)
- If LoadError IsNot Nothing Then Throw LoadError
- If File.Exists(LocalFileAddress) Then
- File.Delete(LocalFileAddress & DownloadEnd)
- Else
- FileIO.FileSystem.MoveFile(LocalFileAddress & DownloadEnd, LocalFileAddress)
- End If
- Catch ex As Exception
- If Not Retried Then
- Retried = True
- GoTo RetryStart
- Else
- Log(ex, $"下载本地 Mod 图标失败({_Logo})")
- RunInUi(Sub() PathLogo.Source = New MyBitmap(PathImage & "Icons/NoIcon.png"))
- End If
- End Try
- End Sub
'标题
Private _Title As String
@@ -107,7 +28,7 @@ RetryStart:
LabTitle.TextDecorations = Nothing
Case McMod.McModState.Disabled
LabTitle.TextDecorations = TextDecorations.Strikethrough
- Case McMod.McModState.Unavaliable
+ Case McMod.McModState.Unavailable
LabTitle.TextDecorations = TextDecorations.Strikethrough
value &= " [错误]"
End Select
@@ -120,12 +41,12 @@ RetryStart:
'副标题
Public Property SubTitle As String
Get
- Return If(LabTitleRaw?.Text, "")
+ Return If(LabSubtitle?.Text, "")
End Get
Set(value As String)
- If LabTitleRaw.Text = value Then Exit Property
- LabTitleRaw.Text = value
- LabTitleRaw.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
+ If LabSubtitle.Text = value Then Exit Property
+ LabSubtitle.Text = value
+ LabSubtitle.Visibility = If(value = "", Visibility.Collapsed, Visibility.Visible)
End Set
End Property
@@ -148,9 +69,9 @@ RetryStart:
For Each TagText In value
Dim NewTag = GetObjectFromXML(
"
-
+
")
PanTags.Children.Add(NewTag)
Next
@@ -216,13 +137,14 @@ RetryStart:
Exit Sub
End If
'计算滑动范围
- Dim Index = CType(Parent, StackPanel).Children.IndexOf(Me)
- SwipeStart = Math.Min(SwipeStart, Index)
- SwipeEnd = Math.Max(SwipeEnd, Index)
+ Dim Elements = CType(Parent, StackPanel).Children
+ Dim Index As Integer = Elements.IndexOf(Me)
+ SwipeStart = MathClamp(Math.Min(SwipeStart, Index), 0, Elements.Count - 1)
+ SwipeEnd = MathClamp(Math.Max(SwipeEnd, Index), 0, Elements.Count - 1)
'勾选所有范围中的项
If SwipeStart = SwipeEnd Then Exit Sub
For i = SwipeStart To SwipeEnd
- Dim Item As MyLocalModItem = CType(Parent, StackPanel).Children(i)
+ Dim Item As MyLocalModItem = Elements(i)
Item.InitLate(Item, e)
Item.Checked = SwipToState
Next
@@ -382,57 +304,75 @@ RetryStart:
#End Region
- Public Sub Refresh()
+ Private Function GetUpdateCompareDescription() As String
+ Dim CurrentName = Entry.CompFile.FileName.Replace(".jar", "")
+ Dim NewestName = Entry.UpdateFile.FileName.Replace(".jar", "")
+ '简化名称对比
+ Dim CurrentSegs = CurrentName.Split("-"c).ToList()
+ Dim NewestSegs = NewestName.Split("-"c).ToList()
+ Dim Shortened As Boolean = False
+ For Each Seg In CurrentSegs.ToList()
+ If Not NewestSegs.Contains(Seg) Then Continue For
+ CurrentSegs.Remove(Seg)
+ NewestSegs.Remove(Seg)
+ Shortened = True
+ Next
+ If Shortened AndAlso CurrentSegs.Any() AndAlso NewestSegs.Any() Then
+ CurrentName = Join(CurrentSegs, "-")
+ NewestName = Join(NewestSegs, "-")
+ Entry._Version = CurrentName '使用网络信息作为显示的版本号
+ End If
+ Return $"当前版本:{CurrentName}({GetTimeSpanString(Entry.CompFile.ReleaseDate - Date.Now, False)}){vbCrLf}最新版本:{NewestName}({GetTimeSpanString(Entry.UpdateFile.ReleaseDate - Date.Now, False)})"
+ End Function
+ Public Sub Refresh() Handles Me.Loaded
RunInUi(
Sub()
'更新
If Entry.CanUpdate Then
BtnUpdate.Visibility = Visibility.Visible
- Dim CurrentName = Entry.CompFile.FileName.Replace(".jar", "")
- Dim NewestName = Entry.UpdateFile.FileName.Replace(".jar", "")
- '简化名称对比
- Dim CurrentSegs = CurrentName.Split("-"c).ToList()
- Dim NewestSegs = NewestName.Split("-"c).ToList()
- Dim Shortened As Boolean = False
- For Each Seg In CurrentSegs.ToList()
- If Not NewestSegs.Contains(Seg) Then Continue For
- CurrentSegs.Remove(Seg)
- NewestSegs.Remove(Seg)
- Shortened = True
- Next
- If Shortened AndAlso CurrentSegs.Any() AndAlso NewestSegs.Any() Then
- CurrentName = Join(CurrentSegs, "-")
- NewestName = Join(NewestSegs, "-")
- Entry._Version = CurrentName '使用网络信息作为显示的版本号
- End If
- BtnUpdate.ToolTip = $"当前版本:{CurrentName} ({Entry.CompFile.ReleaseDate:yyyy/MM/dd HH:mm:ss}){vbCrLf}最新版本:{NewestName} ({Entry.UpdateFile.ReleaseDate:yyyy/MM/dd HH:mm:ss}){vbCrLf}点击以更新,右键查看更新日志。"
+ BtnUpdate.ToolTip = $"{GetUpdateCompareDescription()}{vbCrLf}点击以更新,右键查看更新日志。"
Else
BtnUpdate.Visibility = Visibility.Collapsed
End If
- '标题
- If Entry.Comp Is Nothing Then
- Title = Entry.Name
- SubTitle = If(Entry.Version Is Nothing, "", " | " & Entry.Version)
- Else
- Dim Titles = Entry.Comp.GetControlTitle(False)
- Title = Titles.Key
- SubTitle = Titles.Value & If(Entry.Version Is Nothing, "", " | " & Entry.Version)
- End If
- If Checked Then
- LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush2", "ColorBrush5"))
- Else
- LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush1", "ColorBrushGray4"))
- End If
- '描述
- Dim NewDescription As String
+ '标题与描述
+ Dim DescFileName As String
Select Case Entry.State
Case McMod.McModState.Fine
- NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path)
+ DescFileName = GetFileNameWithoutExtentionFromPath(Entry.Path)
Case McMod.McModState.Disabled
- NewDescription = GetFileNameWithoutExtentionFromPath(Entry.Path.Replace(".disabled", "").Replace(".old", ""))
- Case Else 'McMod.McModState.Unavaliable
- NewDescription = GetFileNameFromPath(Entry.Path)
+ DescFileName = GetFileNameWithoutExtentionFromPath(Entry.Path.Replace(".disabled", "").Replace(".old", ""))
+ Case Else 'McMod.McModState.Unavailable
+ DescFileName = GetFileNameFromPath(Entry.Path)
End Select
+ Dim NewDescription As String
+ If Setup.Get("ToolModLocalNameStyle") = 1 Then
+ '标题显示文件名,详情显示译名
+ '标题
+ Title = DescFileName
+ SubTitle = ""
+ '描述
+ If Entry.Comp Is Nothing Then
+ NewDescription = Entry.Name
+ Else
+ Dim Titles = Entry.Comp.GetControlTitle(False)
+ NewDescription = Titles.Key & Titles.Value
+ End If
+ NewDescription = NewDescription.Replace(" | ", " / ")
+ If Entry.Version IsNot Nothing Then NewDescription &= $" ({Entry.Version})"
+ Else
+ '标题显示译名,详情显示文件名
+ '标题
+ If Entry.Comp Is Nothing Then
+ Title = Entry.Name
+ SubTitle = If(Entry.Version Is Nothing, "", " | " & Entry.Version)
+ Else
+ Dim Titles = Entry.Comp.GetControlTitle(False)
+ Title = Titles.Key
+ SubTitle = Titles.Value & If(Entry.Version Is Nothing, "", " | " & Entry.Version)
+ End If
+ '描述
+ NewDescription = DescFileName
+ End If
If Entry.Comp IsNot Nothing Then
NewDescription += ": " & Entry.Comp.Description.Replace(vbCr, "").Replace(vbLf, "")
ElseIf Entry.Description IsNot Nothing Then
@@ -441,6 +381,11 @@ RetryStart:
NewDescription += ": " & "存在错误,无法获取信息"
End If
Description = NewDescription
+ If Checked Then
+ LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush2", "ColorBrush5"))
+ Else
+ LabTitle.SetResourceReference(TextBlock.ForegroundProperty, If(Entry.State = McMod.McModState.Fine, "ColorBrush1", "ColorBrushGray4"))
+ End If
'主 Logo
Logo = If(Entry.Comp Is Nothing, PathImage & "Icons/NoIcon.png", Entry.Comp.GetControlLogo())
'图标右下角的 Logo
@@ -461,7 +406,7 @@ RetryStart:
'
+ ' Source="/Images/Icons/Unavailable.png" />
End If
ImgState.Source = New MyBitmap(PathImage & $"Icons/{Entry.State}.png")
End If
@@ -517,8 +462,11 @@ RetryStart:
End Sub
'显示更新日志
- Private Sub ShowUpdateLog(sender As Object, e As MouseButtonEventArgs) Handles BtnUpdate.PreviewMouseRightButtonUp
+ Private Sub BtnUpdate_PreviewMouseRightButtonUp(sender As Object, e As MouseButtonEventArgs) Handles BtnUpdate.PreviewMouseRightButtonUp
e.Handled = True
+ ShowUpdateLog()
+ End Sub
+ Private Sub ShowUpdateLog()
Dim CurseForgeUrl As String = Entry.ChangelogUrls.FirstOrDefault(Function(x) x.Contains("curseforge.com"))
Dim ModrinthUrl As String = Entry.ChangelogUrls.FirstOrDefault(Function(x) x.Contains("modrinth.com"))
If CurseForgeUrl Is Nothing OrElse ModrinthUrl Is Nothing Then
@@ -535,7 +483,62 @@ RetryStart:
'触发更新
Private Sub BtnUpdate_Click(sender As Object, e As EventArgs) Handles BtnUpdate.Click
- FrmVersionMod.UpdateMods({Entry})
+ Select Case MyMsgBox($"是否要更新 {Entry.Name}?{vbCrLf}{vbCrLf}{GetUpdateCompareDescription()}", "Mod 更新确认", "更新", "查看更新日志", "取消")
+ Case 1 '更新
+ FrmVersionMod.UpdateMods({Entry})
+ Case 2 '查看更新日志
+ ShowUpdateLog()
+ Case 3 '取消
+ End Select
+ End Sub
+
+ '自适应(#4465)
+ Private Sub PanTitle_SizeChanged() Handles PanTitle.SizeChanged
+ '0:全部舒展:Auto - Auto - (Auto) - 1*
+ '1:压缩 Subtitle:Auto - 1* - (Auto) - 0
+ '2:继续压缩 Title:1* - 0 - (Auto) - 0
+ Dim CurrentCompressLevel As Integer =
+ If(ColumnExtend.Width.IsStar, 0, If(ColumnTitle.Width.IsStar, 2, 1)) 'Subtitle 可能是 Collapsed
+ Dim NewCompressLevel As Integer
+ Select Case CurrentCompressLevel
+ Case 0
+ If ColumnExtend.ActualWidth < 0.5 Then
+ NewCompressLevel = If(LabSubtitle.Visibility = Visibility.Collapsed, 2, 1)
+ Else
+ Return
+ End If
+ Case 1
+ If ColumnSubtitle.ActualWidth < 0.5 Then
+ NewCompressLevel = 2
+ ElseIf Not LabSubtitle.IsTextTrimmed Then
+ NewCompressLevel = 0
+ Else
+ Return
+ End If
+ Case 2
+ If Not LabTitle.IsTextTrimmed Then
+ NewCompressLevel = If(LabSubtitle.Visibility = Visibility.Collapsed, 0, 1)
+ Else
+ Return
+ End If
+ End Select
+ Select Case NewCompressLevel
+ Case 0
+ '全部舒展:Auto - Auto - (Auto) - 1*
+ ColumnTitle.Width = GridLength.Auto
+ ColumnSubtitle.Width = GridLength.Auto
+ ColumnExtend.Width = New GridLength(1, GridUnitType.Star)
+ Case 1
+ '压缩 Subtitle:Auto - 1* - (Auto) - 0
+ ColumnTitle.Width = GridLength.Auto
+ ColumnSubtitle.Width = New GridLength(1, GridUnitType.Star)
+ ColumnExtend.Width = New GridLength(0, GridUnitType.Pixel)
+ Case 2
+ '继续压缩 Title:1* - 0 - (Auto) - 0
+ ColumnTitle.Width = New GridLength(1, GridUnitType.Star)
+ ColumnSubtitle.Width = New GridLength(0, GridUnitType.Pixel)
+ ColumnExtend.Width = New GridLength(0, GridUnitType.Pixel)
+ End Select
End Sub
End Class
diff --git a/Plain Craft Launcher 2/Modules/ModDevelop.vb b/Plain Craft Launcher 2/Modules/ModDevelop.vb
index 8f630e5..b1eb6f0 100644
--- a/Plain Craft Launcher 2/Modules/ModDevelop.vb
+++ b/Plain Craft Launcher 2/Modules/ModDevelop.vb
@@ -1,3 +1,8 @@
Public Module ModDevelop
+#If DEBUG Then
+ Public Sub Start()
+ End Sub
+#End If
+
End Module
diff --git a/Plain Craft Launcher 2/Modules/ModEvent.vb b/Plain Craft Launcher 2/Modules/ModEvent.vb
index e5282a9..6f7b5e3 100644
--- a/Plain Craft Launcher 2/Modules/ModEvent.vb
+++ b/Plain Craft Launcher 2/Modules/ModEvent.vb
@@ -20,7 +20,7 @@
Hint("正在开启中,请稍候……")
OpenWebsite(Data(0))
- Case "打开文件", "打开帮助"
+ Case "打开文件", "打开帮助", "执行命令"
RunInThread(
Sub()
Try
@@ -29,15 +29,25 @@
Dim Location = ActualPaths(0), WorkingDir = ActualPaths(1)
Log($"[Control] 打开类自定义事件实际路径:{Location},工作目录:{WorkingDir}")
'执行
- If Type = "打开文件" Then
+ If Type = "打开帮助" Then
+ PageOtherHelp.EnterHelpPage(Location)
+ Else
+ If Not Setup.Get("HintCustomCommand") Then
+ Select Case MyMsgBox(
+ "即将执行:" & Location & If(Data.Length >= 2, " " & Data(1), "") & vbCrLf &
+ "请在确认该操作没有安全隐患后继续。", "执行确认", "继续", "继续且今后不再要求确认", "取消")
+ Case 2
+ Setup.Set("HintCustomCommand", True)
+ Case 3
+ Exit Sub
+ End Select
+ End If
Dim Info As New ProcessStartInfo With {
.Arguments = If(Data.Length >= 2, Data(1), ""),
.FileName = Location,
- .WorkingDirectory = WorkingDir
+ .WorkingDirectory = ShortenPath(WorkingDir)
}
Process.Start(Info)
- Else '打开帮助
- PageOtherHelp.EnterHelpPage(Location)
End If
Catch ex As Exception
Log(ex, "执行打开类自定义事件失败", LogLevel.Msgbox)
@@ -92,7 +102,18 @@
MyMsgBox("EventData 必须为以 http:// 或 https:// 开头的网址。" & vbCrLf & "PCL 不支持其他乱七八糟的下载协议。", "事件执行失败")
Exit Sub
End If
- PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0)))
+ Try
+ Select Case Data.Length
+ Case 1
+ PageOtherTest.StartCustomDownload(Data(0), GetFileNameFromPath(Data(0)))
+ Case 2
+ PageOtherTest.StartCustomDownload(Data(0), Data(1))
+ Case Else
+ PageOtherTest.StartCustomDownload(Data(0), Data(1), Data(2))
+ End Select
+ Catch
+ PageOtherTest.StartCustomDownload(Data(0), "未知")
+ End Try
Case Else
MyMsgBox("未知的事件类型:" & Type & vbCrLf & "请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败")
@@ -125,13 +146,11 @@
" - https://www.baidu.com/test.xaml(同时也需要包含这个文件)", ex)
End Try
'下载文件
- Dim LocalTemp1 As String = PathTemp & "CustomEvent\" & RawFileName
- Dim LocalTemp2 As String = PathTemp & "CustomEvent\" & RawFileName.Replace(".json", ".xaml")
- Log("[Event] 转换网络资源:" & RelativeUrl & " -> " & LocalTemp1)
- Hint("正在获取资源,请稍候……")
+ Dim LocalTemp As String = RequestTaskTempFolder() & RawFileName
+ Log("[Event] 转换网络资源:" & RelativeUrl & " -> " & LocalTemp)
Try
- NetDownload(RelativeUrl, LocalTemp1)
- NetDownload(RelativeUrl.Replace(".json", ".xaml"), LocalTemp1.Replace(".json", ".xaml"))
+ NetDownloadByClient(RelativeUrl, LocalTemp)
+ NetDownloadByClient(RelativeUrl.Replace(".json", ".xaml"), LocalTemp.Replace(".json", ".xaml"))
Catch ex As Exception
Throw New Exception("下载指定的文件失败!" & vbCrLf &
"注意,联网帮助页面须指向一个帮助 JSON 文件,并在同路径下包含相应 XAML 文件!" & vbCrLf &
@@ -139,7 +158,7 @@
" - https://www.baidu.com/test.json(填写这个路径)" & vbCrLf &
" - https://www.baidu.com/test.xaml(同时也需要包含这个文件)", ex)
End Try
- RelativeUrl = LocalTemp1
+ RelativeUrl = LocalTemp
End If
RelativeUrl = RelativeUrl.Replace("/", "\").ToLower.TrimStart("\")
@@ -164,7 +183,7 @@
Location = PathTemp & "Help\" & RelativeUrl
WorkingDir = PathTemp & "Help\"
Log("[Control] 自定义事件中由相对 PCL 自带帮助文件夹的路径" & EventType & ":" & Location)
- ElseIf EventType = "打开文件" Then
+ ElseIf EventType = "打开文件" OrElse EventType = "执行命令" Then
'直接使用原有路径启动程序
Location = RelativeUrl
Log("[Control] 自定义事件中直接" & EventType & ":" & Location)
diff --git a/Plain Craft Launcher 2/Modules/ModMain.vb b/Plain Craft Launcher 2/Modules/ModMain.vb
index 2b4aa24..697190e 100644
--- a/Plain Craft Launcher 2/Modules/ModMain.vb
+++ b/Plain Craft Launcher 2/Modules/ModMain.vb
@@ -69,14 +69,14 @@ Public Module ModMain
Dim Percent As Double = 0.3
Select Case CurrentHint.Type
Case HintType.Info
- TargetColor0 = New MyColor(37, 155, 252)
- TargetColor1 = New MyColor(10, 142, 252)
+ TargetColor0 = New MyColor(215, 37, 155, 252)
+ TargetColor1 = New MyColor(215, 10, 142, 252)
Case HintType.Finish
- TargetColor0 = New MyColor(33, 177, 33)
- TargetColor1 = New MyColor(29, 160, 29)
+ TargetColor0 = New MyColor(215, 33, 177, 33)
+ TargetColor1 = New MyColor(215, 29, 160, 29)
Case Else 'HintType.Critical
- TargetColor0 = New MyColor(255, 53, 11)
- TargetColor1 = New MyColor(255, 43, 0)
+ TargetColor0 = New MyColor(215, 255, 53, 11)
+ TargetColor1 = New MyColor(215, 255, 43, 0)
End Select
If Not IsNothing(DoubleStack) Then
'有重复提示,且该提示的进入动画已播放
@@ -176,15 +176,17 @@ EndHint:
Public Title As String
Public Text As String
'''
- ''' 输入框模式:文本框的文本;选择模式:需要放进去的 List(Of MyListItem)。
+ ''' 输入模式:文本框的文本。
+ ''' 选择模式:需要放进去的 List(Of MyListItem)。
+ ''' 登录模式:登录步骤 1 中返回的 JSON。
'''
Public Content As Object
'''
- ''' 仅输入框模式:输入验证规则。
+ ''' 输入模式:输入验证规则。
'''
Public ValidateRules As ObjectModel.Collection(Of Validate)
'''
- ''' 仅输入框模式:提示文本。
+ ''' 输入模式:提示文本。
'''
Public HintText As String = ""
'''
@@ -214,7 +216,9 @@ EndHint:
'''
Public IsExited As Boolean = False
'''
- ''' 点击的按钮编号或输入的文本。若输入弹窗点击了非第一个按钮,则为 Nothing。
+ ''' 输入模式:输入的文本。若点击了 非 第一个按钮,则为 Nothing。
+ ''' 选择模式:点击的按钮编号,从 1 开始。
+ ''' 登录模式:字符串数组 {AccessToken, RefreshToken} 或一个 Exception。
'''
Public Result As Object
End Class
@@ -222,6 +226,7 @@ EndHint:
Text
[Select]
Input
+ Login
End Enum
'''
@@ -352,6 +357,8 @@ EndHint:
FrmMain.PanMsg.Children.Add(New MyMsgSelect(WaitingMyMsgBox(0)))
Case MyMsgBoxType.Text
FrmMain.PanMsg.Children.Add(New MyMsgText(WaitingMyMsgBox(0)))
+ Case MyMsgBoxType.Login
+ FrmMain.PanMsg.Children.Add(New MyMsgLogin(WaitingMyMsgBox(0)))
End Select
WaitingMyMsgBox.RemoveAt(0)
Else
@@ -399,6 +406,7 @@ EndHint:
Public FrmDownloadMod As PageDownloadMod
Public FrmDownloadPack As PageDownloadPack
Public FrmDownloadShader As PageDownloadShader
+ Public FrmDownloadResourcePack As PageDownloadResourcePack
'设置页面声明
Public FrmSetupLeft As PageSetupLeft
@@ -428,6 +436,7 @@ EndHint:
Public FrmVersionMod As PageVersionMod
Public FrmVersionModDisabled As PageVersionModDisabled
Public FrmVersionSetup As PageVersionSetup
+ Public FrmVersionExport As PageVersionExport
'资源信息分页声明
Public FrmDownloadCompDetail As PageDownloadCompDetail
@@ -437,6 +446,10 @@ EndHint:
#Region "帮助"
Public Class HelpEntry
+ '''
+ ''' 原始信息路径。用于刷新。
+ '''
+ Public RawPath As String
'基础
@@ -495,6 +508,7 @@ EndHint:
''' 从文件初始化 HelpEntry 对象,失败会抛出异常。
'''
Public Sub New(FilePath As String)
+ RawPath = FilePath
Dim JsonData As JObject = GetJson(HelpArgumentReplace(ReadFile(FilePath)))
If JsonData Is Nothing Then Throw New FileNotFoundException("未找到帮助文件:" & FilePath, FilePath)
'加载常规信息
@@ -592,7 +606,7 @@ EndHint:
'加载忽略列表
Log("[Help] 发现 .helpignore 文件:" & File.FullName)
For Each Line In ReadFile(File.FullName).Split(vbCrLf.ToCharArray)
- Dim RealString As String = Line.Before("#").Trim
+ Dim RealString As String = Line.BeforeFirst("#").Trim
If String.IsNullOrWhiteSpace(RealString) Then Continue For
IgnoreList.Add(RealString)
If ModeDebug Then Log("[Help] > " & RealString)
@@ -665,8 +679,8 @@ NextFile:
'''
Public Function HelpArgumentReplace(Xaml As String) As String
Dim Result = Xaml.Replace("{path}", EscapeXML(Path))
- Result = RegexReplaceEach(Result, Function() EscapeXML(PageOtherTest.GetRandomHint()), "\{hint\}")
- Result = RegexReplaceEach(Result, Function() EscapeXML(PageOtherTest.GetRandomCave()), "\{cave\}")
+ Result = Result.RegexReplaceEach("\{hint\}", Function() EscapeXML(PageOtherTest.GetRandomHint()))
+ Result = Result.RegexReplaceEach("\{cave\}", Function() EscapeXML(PageOtherTest.GetRandomCave()))
Return Result
End Function
@@ -799,7 +813,89 @@ NextFile:
Public Declare Function SetForegroundWindow Lib "user32" (hWnd As IntPtr) As Integer
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (hWnd As IntPtr, msg As UInteger, wParam As Long, lParam As Long) As Boolean
- Public Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean
+ '''
+ ''' 将特定程序设置为使用高性能显卡启动。
+ ''' 如果失败,则抛出异常。
+ '''
+ Public Sub SetGPUPreference(Executeable As String)
+ Const REG_KEY As String = "Software\Microsoft\DirectX\UserGpuPreferences"
+ Const REG_VALUE As String = "GpuPreference=2;"
+ '查看现有设置
+ Using ReadOnlyKey = My.Computer.Registry.CurrentUser.OpenSubKey(REG_KEY, False)
+ If ReadOnlyKey IsNot Nothing Then
+ Dim CurrentValue = ReadOnlyKey.GetValue(Executeable)
+ If REG_VALUE = CurrentValue?.ToString() Then
+ Log($"[System] 无需调整显卡设置:{Executeable}")
+ Return
+ End If
+ Else
+ '创建父级键
+ Log($"[System] 需要创建显卡设置的父级键")
+ My.Computer.Registry.CurrentUser.CreateSubKey(REG_KEY)
+ End If
+ End Using
+ '写入新设置
+ Using WriteKey = My.Computer.Registry.CurrentUser.OpenSubKey(REG_KEY, True)
+ WriteKey.SetValue(Executeable, REG_VALUE)
+ Log($"[System] 已调整显卡设置:{Executeable}")
+ End Using
+ End Sub
+
+#End Region
+
+#Region "任务缓存"
+
+ Private IsTaskTempCleared As Boolean = False
+ Private IsTaskTempClearing As Boolean = False
+
+ '''
+ ''' 尝试清理任务缓存文件夹。
+ ''' 在整次运行中只会实际清理一次。
+ '''
+ Public Sub TryClearTaskTemp()
+ If Not IsTaskTempCleared Then
+ IsTaskTempCleared = True
+ IsTaskTempClearing = True
+ Try
+ Log("[System] 开始清理任务缓存文件夹")
+ DeleteDirectory($"{OsDrive}ProgramData\PCL\TaskTemp\")
+ DeleteDirectory($"{PathTemp}TaskTemp\")
+ Log("[System] 已清理任务缓存文件夹")
+ Catch ex As Exception
+ Log(ex, "清理任务缓存文件夹失败")
+ Finally
+ IsTaskTempClearing = False
+ End Try
+ ElseIf IsTaskTempClearing Then
+ '等待另一个清理步骤完成
+ Do While IsTaskTempClearing
+ Thread.Sleep(1)
+ Loop
+ End If
+ End Sub
+
+ '''
+ ''' 申请一个可用于任务缓存的临时文件夹,以 \ 结尾。这些文件夹无需进行后续清理。
+ ''' 若所有缓存位置均没有权限,会抛出异常。
+ '''
+ ''' 是否要求路径不包含空格。
+ Public Function RequestTaskTempFolder(Optional RequireNonSpace As Boolean = False) As String
+ TryClearTaskTemp()
+ Dim ResultFolder As String
+ Try
+ ResultFolder = $"{PathTemp}TaskTemp\{GetUuid()}-{RandomInteger(0, 1000000)}\"
+ If RequireNonSpace AndAlso ResultFolder.Contains(" ") Then Exit Try '带空格
+ Directory.CreateDirectory(ResultFolder)
+ CheckPermissionWithException(ResultFolder)
+ Return ResultFolder
+ Catch
+ End Try
+ '使用备用路径
+ ResultFolder = $"{OsDrive}ProgramData\PCL\TaskTemp\{GetUuid()}-{RandomInteger(0, 1000000)}\"
+ Directory.CreateDirectory(ResultFolder)
+ CheckPermission(ResultFolder)
+ Return ResultFolder
+ End Function
#End Region
diff --git a/Plain Craft Launcher 2/Modules/ModMusic.vb b/Plain Craft Launcher 2/Modules/ModMusic.vb
index cc1ace6..060091a 100644
--- a/Plain Craft Launcher 2/Modules/ModMusic.vb
+++ b/Plain Craft Launcher 2/Modules/ModMusic.vb
@@ -14,8 +14,8 @@
''' 初始化音乐播放列表。
'''
''' 强制全部重新载入列表。
- ''' 在重载列表时避免让某项成为第一项。
- Private Sub MusicListInit(ForceReload As Boolean, Optional IgnoreFirst As String = "")
+ ''' 在重载列表时避免让某项成为第一项。
+ Private Sub MusicListInit(ForceReload As Boolean, Optional PreventFirst As String = Nothing)
If ForceReload Then MusicAllList = Nothing
Try
'初始化全部可用音乐列表
@@ -31,10 +31,10 @@
End If
'打乱顺序播放
MusicWaitingList = If(Setup.Get("UiMusicRandom"), Shuffle(New List(Of String)(MusicAllList)), New List(Of String)(MusicAllList))
- If Not IgnoreFirst = "" AndAlso Not Not MusicWaitingList.Any() AndAlso MusicWaitingList(0) = IgnoreFirst Then
+ If PreventFirst IsNot Nothing AndAlso MusicWaitingList.FirstOrDefault = PreventFirst Then
'若需要避免成为第一项的为第一项,则将它放在最后
MusicWaitingList.RemoveAt(0)
- MusicWaitingList.Add(IgnoreFirst)
+ MusicWaitingList.Add(PreventFirst)
End If
Catch ex As Exception
Log(ex, "初始化音乐列表失败", LogLevel.Feedback)
@@ -42,13 +42,18 @@
End Sub
'''
''' 获取下一首播放的音乐路径并将其从列表中移除。
+ ''' 如果没有,可能会返回 Nothing。
'''
Private Function DequeueNextMusicAddress() As String
'初始化,确保存在音乐
If MusicAllList Is Nothing OrElse Not MusicAllList.Any() OrElse Not MusicWaitingList.Any() Then MusicListInit(False)
'出列下一个音乐,如果出列结束则生成新列表
- DequeueNextMusicAddress = MusicWaitingList(0)
- MusicWaitingList.RemoveAt(0)
+ If MusicWaitingList.Any() Then
+ DequeueNextMusicAddress = MusicWaitingList(0)
+ MusicWaitingList.RemoveAt(0)
+ Else
+ DequeueNextMusicAddress = Nothing
+ End If
If Not MusicWaitingList.Any() Then MusicListInit(False, DequeueNextMusicAddress)
End Function
@@ -60,44 +65,45 @@
''' 刷新背景音乐按钮 UI 与设置页 UI。
'''
Private Sub MusicRefreshUI()
- RunInUi(Sub()
- Try
+ RunInUi(
+ Sub()
+ Try
- If Not MusicAllList.Any() Then
- '无背景音乐
- FrmMain.BtnExtraMusic.Show = False
+ If Not MusicAllList.Any() Then
+ '无背景音乐
+ FrmMain.BtnExtraMusic.Show = False
+ Else
+ '有背景音乐
+ FrmMain.BtnExtraMusic.Show = True
+ Dim ToolTipText As String
+ If MusicState = MusicStates.Pause Then
+ FrmMain.BtnExtraMusic.Logo = Logo.IconPlay
+ FrmMain.BtnExtraMusic.LogoScale = 0.8
+ ToolTipText = "已暂停:" & GetFileNameWithoutExtentionFromPath(MusicCurrent)
+ If MusicAllList.Count > 1 Then
+ ToolTipText += vbCrLf & "左键恢复播放,右键播放下一曲。"
Else
- '有背景音乐
- FrmMain.BtnExtraMusic.Show = True
- Dim ToolTipText As String
- If MusicState = MusicStates.Pause Then
- FrmMain.BtnExtraMusic.Logo = Logo.IconPlay
- FrmMain.BtnExtraMusic.LogoScale = 0.8
- ToolTipText = "已暂停:" & GetFileNameWithoutExtentionFromPath(MusicCurrent)
- If MusicAllList.Count > 1 Then
- ToolTipText += vbCrLf & "左键恢复播放,右键播放下一曲。"
- Else
- ToolTipText += vbCrLf & "左键恢复播放,右键重新从头播放。"
- End If
- Else
- FrmMain.BtnExtraMusic.Logo = Logo.IconMusic
- FrmMain.BtnExtraMusic.LogoScale = 1
- ToolTipText = "正在播放:" & GetFileNameWithoutExtentionFromPath(MusicCurrent)
- If MusicAllList.Count > 1 Then
- ToolTipText += vbCrLf & "左键暂停,右键播放下一曲。"
- Else
- ToolTipText += vbCrLf & "左键暂停,右键重新从头播放。"
- End If
- End If
- FrmMain.BtnExtraMusic.ToolTip = ToolTipText
- ToolTipService.SetVerticalOffset(FrmMain.BtnExtraMusic, If(ToolTipText.Contains(vbLf), 10, 16))
+ ToolTipText += vbCrLf & "左键恢复播放,右键重新从头播放。"
End If
- If FrmSetupUI IsNot Nothing Then FrmSetupUI.MusicRefreshUI()
+ Else
+ FrmMain.BtnExtraMusic.Logo = Logo.IconMusic
+ FrmMain.BtnExtraMusic.LogoScale = 1
+ ToolTipText = "正在播放:" & GetFileNameWithoutExtentionFromPath(MusicCurrent)
+ If MusicAllList.Count > 1 Then
+ ToolTipText += vbCrLf & "左键暂停,右键播放下一曲。"
+ Else
+ ToolTipText += vbCrLf & "左键暂停,右键重新从头播放。"
+ End If
+ End If
+ FrmMain.BtnExtraMusic.ToolTip = ToolTipText
+ ToolTipService.SetVerticalOffset(FrmMain.BtnExtraMusic, If(ToolTipText.Contains(vbLf), 10, 16))
+ End If
+ If FrmSetupUI IsNot Nothing Then FrmSetupUI.MusicRefreshUI()
- Catch ex As Exception
- Log(ex, "刷新背景音乐 UI 失败", LogLevel.Feedback)
- End Try
- End Sub)
+ Catch ex As Exception
+ Log(ex, "刷新背景音乐 UI 失败", LogLevel.Feedback)
+ End Try
+ End Sub)
End Sub
'''
@@ -128,8 +134,12 @@
Hint("重新播放:" & GetFileNameFromPath(MusicCurrent), HintType.Finish)
Else
Dim Address As String = DequeueNextMusicAddress()
- MusicStartPlay(Address)
- Hint("正在播放:" & GetFileNameFromPath(Address), HintType.Finish)
+ If Address Is Nothing Then
+ Hint("没有可以播放的音乐!", HintType.Critical)
+ Else
+ MusicStartPlay(Address)
+ Hint("正在播放:" & GetFileNameFromPath(Address), HintType.Finish)
+ End If
End If
MusicRefreshUI()
End Sub
@@ -179,11 +189,15 @@
End If
Else
Dim Address As String = DequeueNextMusicAddress()
- Try
- MusicStartPlay(Address, IsFirstLoad)
- If ShowHint Then Hint("背景音乐已刷新:" & GetFileNameFromPath(Address), HintType.Finish, False)
- Catch
- End Try
+ If Address Is Nothing Then
+ If ShowHint Then Hint("没有可以播放的音乐!", HintType.Critical)
+ Else
+ Try
+ MusicStartPlay(Address, IsFirstLoad)
+ If ShowHint Then Hint("背景音乐已刷新:" & GetFileNameFromPath(Address), HintType.Finish, False)
+ Catch
+ End Try
+ End If
End If
MusicRefreshUI()
@@ -195,6 +209,7 @@
''' 开始播放音乐。
'''
Private Sub MusicStartPlay(Address As String, Optional IsFirstLoad As Boolean = False)
+ If Address Is Nothing Then Return
Log("[Music] 播放开始:" & Address)
MusicCurrent = Address
RunInNewThread(Sub() MusicLoop(IsFirstLoad), "Music", ThreadPriority.BelowNormal)
@@ -230,7 +245,12 @@
RunInThread(
Sub()
Log("[Music] 已恢复播放")
- MusicNAudio?.Play()
+ Try
+ MusicNAudio?.Play()
+ Catch 'https://github.com/Hex-Dragon/PCL2/pull/5415#issuecomment-2751135223
+ MusicNAudio?.Stop()
+ MusicNAudio?.Play()
+ End Try
MusicRefreshUI()
End Sub)
Return True
@@ -240,7 +260,7 @@
#End Region
'''
- ''' 当前正在播放的 NAudio.Wave.WaveOut。
+ ''' 当前正在播放的 NAudio.Wave.WaveOutEvent。
'''
Public MusicNAudio = Nothing
'''
@@ -252,12 +272,13 @@
''' 在 MusicUuid 不变的前提下,持续播放某地址的音乐,且在播放结束后随机播放下一曲。
'''
Private Sub MusicLoop(Optional IsFirstLoad As Boolean = False)
- Dim CurrentWave As NAudio.Wave.WaveOut = Nothing
+ Dim CurrentWave As NAudio.Wave.WaveOutEvent = Nothing
Dim Reader As NAudio.Wave.WaveStream = Nothing
Try
'开始播放
- CurrentWave = New NAudio.Wave.WaveOut()
+ CurrentWave = New NAudio.Wave.WaveOutEvent()
MusicNAudio = CurrentWave
+ CurrentWave.DeviceNumber = -1
Reader = New NAudio.Wave.AudioFileReader(MusicCurrent)
CurrentWave.Init(Reader)
CurrentWave.Play()
@@ -280,22 +301,32 @@
'当前音乐已播放结束,继续下一曲
If CurrentWave.PlaybackState = NAudio.Wave.PlaybackState.Stopped AndAlso MusicAllList.Any Then MusicStartPlay(DequeueNextMusicAddress)
Catch ex As Exception
- Log("[Music] 播放音乐失败的文件完整路径:" & MusicCurrent)
+ Log(ex, "播放音乐出现内部错误(" & MusicCurrent & ")", LogLevel.Developer)
+ If TypeOf ex Is NAudio.MmException AndAlso ex.Message.Contains("AlreadyAllocated") Then
+ Hint("你的音频设备正被其他程序占用。请在关闭占用的程序后重启 PCL,才能恢复音乐播放功能!", HintType.Critical)
+ Thread.Sleep(1000000000)
+ End If
If TypeOf ex Is NAudio.MmException AndAlso (ex.Message.Contains("NoDriver") OrElse ex.Message.Contains("BadDeviceId")) Then
Hint("由于音频设备变更,音乐播放功能在重启 PCL 后才能恢复!", HintType.Critical)
Thread.Sleep(1000000000)
End If
If ex.Message.Contains("Got a frame at sample rate") OrElse ex.Message.Contains("does not support changes to") Then
Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 不支持播放音频属性在中途发生变化的音乐", HintType.Critical)
- ElseIf Not (MusicCurrent.EndsWithF(".wav", True) OrElse MusicCurrent.EndsWithF(".mp3", True) OrElse MusicCurrent.EndsWithF(".flac", True)) Then
+ ElseIf Not (MusicCurrent.EndsWithF(".wav", True) OrElse MusicCurrent.EndsWithF(".mp3", True) OrElse MusicCurrent.EndsWithF(".flac", True)) OrElse
+ ex.Message.Contains("0xC00D36C4") Then '#5096:不支持给定的 URL 的字节流类型。 (异常来自 HRESULT:0xC00D36C4)
Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 可能不支持此音乐格式,请将格式转换为 .wav、.mp3 或 .flac 后再试", HintType.Critical)
Else
Log(ex, "播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & ")", LogLevel.Hint)
End If
- Thread.Sleep(1000)
+ '将播放错误的音乐从列表中移除
+ MusicAllList.Remove(MusicCurrent)
+ MusicWaitingList.Remove(MusicCurrent)
+ MusicRefreshUI()
+ '等待 2 秒后继续播放
+ Thread.Sleep(2000)
If TypeOf ex Is FileNotFoundException Then
MusicRefreshPlay(True, IsFirstLoad)
- ElseIf MusicAllList.Count > 1 Then
+ Else
MusicStartPlay(DequeueNextMusicAddress(), IsFirstLoad)
End If
Finally
diff --git a/Plain Craft Launcher 2/Modules/ModSecret.vb b/Plain Craft Launcher 2/Modules/ModSecret.vb
index 00b26c0..3c38897 100644
--- a/Plain Craft Launcher 2/Modules/ModSecret.vb
+++ b/Plain Craft Launcher 2/Modules/ModSecret.vb
@@ -1,17 +1,17 @@
'由于包含加解密等安全信息,本文件中的部分代码已被删除
-Imports System.Net
-Imports System.Reflection
Imports System.Security.Cryptography
Friend Module ModSecret
#Region "杂项"
- '在开源内容的注册表键与普通内容的注册表键隔离
+ '在开源版的注册表与常规版的注册表隔离,以防数据冲突
Public Const RegFolder As String = "PCLDebug"
'用于微软登录的 ClientId
Public Const OAuthClientId As String = ""
+ 'CurseForge API Key
+ Public Const CurseForgeAPIKey As String = ""
Friend Sub SecretOnApplicationStart()
'提升 UI 线程优先级
@@ -32,7 +32,7 @@ Friend Module ModSecret
"2. 删除当前目录中的 PCL 文件夹,然后再试。" & vbCrLf &
"3. 右键 PCL 选择属性,打开 兼容性 中的 以管理员身份运行此程序。",
MsgBoxStyle.Critical, "运行环境错误")
- Environment.[Exit](Result.Cancel)
+ Environment.[Exit](ProcessReturnValues.Cancel)
End Try
If Not CheckPermission(Path & "PCL") Then
MsgBox("PCL 没有对当前文件夹的写入权限,请尝试:" & vbCrLf &
@@ -40,11 +40,11 @@ Friend Module ModSecret
"2. 删除当前目录中的 PCL 文件夹,然后再试。" & vbCrLf &
"3. 右键 PCL 选择属性,打开 兼容性 中的 以管理员身份运行此程序。",
MsgBoxStyle.Critical, "运行环境错误")
- Environment.[Exit](Result.Cancel)
+ Environment.[Exit](ProcessReturnValues.Cancel)
End If
'开源版本提示
MyMsgBox($"该版本中无法使用以下特性:
-- CurseForge API 调用:需要你自行申请 API Key,然后添加到 SecretHeadersSign 方法中
+- CurseForge API 调用:需要你自行申请 API Key,然后添加到 ModSecret.vb 的开头
- 正版登录:需要你自行申请 Client ID,然后添加到 ModSecret.vb 的开头
- 更新与联网通知:避免滥用隐患
- 主题切换:这是需要赞助解锁的纪念性质的功能,别让赞助者太伤心啦……
@@ -99,29 +99,29 @@ Friend Module ModSecret
''' 设置 Headers 的 UA、Referer。
'''
Friend Sub SecretHeadersSign(Url As String, ByRef Client As WebClient, Optional UseBrowserUserAgent As Boolean = False)
- If Url.Contains("modrinth.com") Then '根据 #4334,不添加 PCL 的 UA 反而能正常访问
- Client.Headers("User-Agent") = "Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
+ 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
End If
- Client.Headers("Referer") = "http://" & VersionCode & ".pcl2.open.server/"
- '如果你有 CurseForge API Key,请添加到 Headers 中,以恢复对 CurseForge 的访问
+ 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("modrinth.com") Then '根据 #4334,不添加 PCL 的 UA 反而能正常访问
- Request.UserAgent = "Mozilla/5.0 AppleWebKit/537.36 Chrome/63.0.3239.132 Safari/537.36"
+ 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 & ".pcl2.open.server/"
- '如果你有 CurseForge API Key,请添加到 Headers 中,以恢复对 CurseForge 的访问
+ Request.Referer = "http://" & VersionCode & ".open.pcl2.server/"
+ If Url.Contains("api.curseforge.com") Then Request.Headers("x-api-key") = CurseForgeAPIKey
End Sub
#End Region
@@ -244,18 +244,18 @@ Friend Module ModSecret
FrmMain.PanForm.Background.Freeze()
End Sub)
End Sub
- Public Sub ThemeCheckAll(EffectSetup As Boolean)
+ Friend Sub ThemeCheckAll(EffectSetup As Boolean)
End Sub
- Public Function ThemeCheckOne(Id As Integer) As Boolean
+ Friend Function ThemeCheckOne(Id As Integer) As Boolean
Return True
End Function
Friend Function ThemeUnlock(Id As Integer, Optional ShowDoubleHint As Boolean = True, Optional UnlockHint As String = Nothing) As Boolean
Return False
End Function
- Public Function ThemeCheckGold(Optional Code As String = Nothing) As Boolean
+ Friend Function ThemeCheckGold(Optional Code As String = Nothing) As Boolean
Return False
End Function
- Public Function DonateCodeInput() As Boolean?
+ Friend Function DonateCodeInput() As Boolean?
Return Nothing
End Function
@@ -274,6 +274,13 @@ Friend Module ModSecret
End Sub
Public Sub UpdateReplace(ProcessId As Integer, OldFileName As String, NewFileName As String, TriggerRestart As Boolean)
End Sub
+ '''
+ ''' 确保 PathTemp 下的 Latest.exe 是最新正式版的 PCL,它会被用于整合包打包。
+ ''' 如果不是,则下载一个。
+ '''
+ Friend Sub DownloadLatestPCL(Optional LoaderToSyncProgress As LoaderBase = Nothing)
+ '注意:如果要自行实现这个功能,请换用另一个文件路径,以免与官方版本冲突
+ End Sub
#End Region
diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb
index 205f3ba..17bee37 100644
--- a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb
+++ b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb
@@ -1,4 +1,4 @@
-Imports System.Reflection
+Imports System.Reflection
Imports System.Resources
Imports System.Runtime.InteropServices
@@ -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 990f9b4..81e998d 100644
--- a/Plain Craft Launcher 2/My Project/Resources.Designer.vb
+++ b/Plain Craft Launcher 2/My Project/Resources.Designer.vb
@@ -100,6 +100,16 @@ Namespace My.Resources
End Get
End Property
+ '''
+ ''' 查找 System.Byte[] 类型的本地化资源。
+ '''
+ Friend ReadOnly Property Imazen_WebP() As Byte()
+ Get
+ Dim obj As Object = ResourceManager.GetObject("Imazen_WebP", resourceCulture)
+ Return CType(obj,Byte())
+ End Get
+ End Property
+
'''
''' 查找 System.Byte[] 类型的本地化资源。
'''
@@ -120,16 +130,6 @@ Namespace My.Resources
End Get
End Property
- '''
- ''' 查找 System.Byte[] 类型的本地化资源。
- '''
- Friend ReadOnly Property libwebp32() As Byte()
- Get
- Dim obj As Object = ResourceManager.GetObject("libwebp32", resourceCulture)
- Return CType(obj,Byte())
- End Get
- End Property
-
'''
''' 查找 System.Byte[] 类型的本地化资源。
'''
@@ -169,15 +169,5 @@ Namespace My.Resources
Return CType(obj,Byte())
End Get
End Property
-
- '''
- ''' 查找 System.Byte[] 类型的本地化资源。
- '''
- Friend ReadOnly Property WebP() As Byte()
- Get
- Dim obj As Object = ResourceManager.GetObject("WebP", resourceCulture)
- Return CType(obj,Byte())
- End Get
- End Property
End Module
End Namespace
diff --git a/Plain Craft Launcher 2/My Project/Resources.resx b/Plain Craft Launcher 2/My Project/Resources.resx
index d15494b..8f1da65 100644
--- a/Plain Craft Launcher 2/My Project/Resources.resx
+++ b/Plain Craft Launcher 2/My Project/Resources.resx
@@ -130,15 +130,15 @@
..\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\libwebp32.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
@@ -151,7 +151,4 @@
..\Resources\transformer.jar;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
-
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
index 1cd46b9..a748106 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
@@ -1,4 +1,5 @@
Imports System.IO.Compression
+Imports System.Net.Http
Public Module ModDownloadLib
@@ -28,7 +29,7 @@ Public Module ModDownloadLib
Dim VersionFolder As String = PathMcFolder & "versions\" & Id & "\"
'重复任务检查
- For Each OngoingLoader In LoaderTaskbar
+ For Each OngoingLoader In LoaderTaskbar.ToList()
If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
If Behaviour = NetPreDownloadBehaviour.ExitWhileExistsOrDownloading Then Return OngoingLoader
Hint("该版本正在下载中!", HintType.Critical)
@@ -59,47 +60,6 @@ Public Module ModDownloadLib
Return Nothing
End Try
End Function
- '''
- ''' 保存某个 Minecraft 版本的核心文件(仅 Json 与核心 Jar)。
- '''
- ''' 所下载的 Minecraft 的版本名。
- ''' Json 文件的 Mojang 官方地址。
- Public Sub McDownloadClientCore(Id As String, JsonUrl As String, Behaviour As NetPreDownloadBehaviour)
- Try
- Dim VersionFolder As String = SelectFolder()
- If Not VersionFolder.Contains("\") Then Exit Sub
- VersionFolder = VersionFolder & Id & "\"
-
- '重复任务检查
- For Each OngoingLoader In LoaderTaskbar
- If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
- If Behaviour = NetPreDownloadBehaviour.ExitWhileExistsOrDownloading Then Exit Sub
- Hint("该版本正在下载中!", HintType.Critical)
- Exit Sub
- Next
-
- Dim Loaders As New List(Of LoaderBase)
- '下载版本 Json 文件
- Loaders.Add(New LoaderDownload("下载版本 json 文件", New List(Of NetFile) From {
- New NetFile(DlSourceLauncherOrMetaGet(JsonUrl), VersionFolder & Id & ".json", New FileChecker(CanUseExistsFile:=False, IsJson:=True))
- }) With {.ProgressWeight = 2})
- '获取支持库文件地址
- Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析核心 jar 文件下载地址",
- Sub(Task As LoaderTask(Of String, List(Of NetFile))) Task.Output = McLibFix(New McVersion(VersionFolder), True)) With {.ProgressWeight = 0.5, .Show = False})
- '下载支持库文件
- Loaders.Add(New LoaderDownload("下载核心 jar 文件", New List(Of NetFile)) With {.ProgressWeight = 5})
-
- '启动
- Dim Loader As New LoaderCombo(Of String)("Minecraft " & Id & " 下载", Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
- Loader.Start(Id)
- LoaderTaskbarAdd(Loader)
- FrmMain.BtnExtraDownload.ShowRefresh()
- FrmMain.BtnExtraDownload.Ribble()
-
- Catch ex As Exception
- Log(ex, "开始 Minecraft 下载失败", LogLevel.Feedback)
- End Try
- End Sub
'''
''' 获取下载某个 Minecraft 版本的加载器列表。
@@ -127,6 +87,7 @@ Public Module ModDownloadLib
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)))
+ Thread.Sleep(50) '等待 JSON 文件实际写入硬盘(#3710)
Log("[Download] 开始分析原版支持库文件:" & VersionFolder)
Task.Output = McLibFix(New McVersion(VersionFolder))
End Sub) With {.ProgressWeight = 1, .Show = False})
@@ -155,7 +116,7 @@ Public Module ModDownloadLib
LoadersAssets.Add(New LoaderDownload("下载资源文件索引(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 3, .Show = False})
LoadersAssets.Add(New LoaderTask(Of String, List(Of NetFile))("分析所需资源文件(副加载器)",
Sub(Task As LoaderTask(Of String, List(Of NetFile)))
- Task.Output = McAssetsFixList(McAssetsGetIndexName(New McVersion(VersionFolder)), True, Task)
+ Task.Output = McAssetsFixList(New McVersion(VersionFolder), True, Task)
End Sub) With {.ProgressWeight = 3, .Show = False})
LoadersAssets.Add(New LoaderDownload("下载资源文件(副加载器)", New List(Of NetFile)) With {.ProgressWeight = 14, .Show = False})
Loaders.Add(New LoaderCombo(Of String)("下载原版资源文件", LoadersAssets) With {.Block = False, .ProgressWeight = 21})
@@ -207,7 +168,12 @@ Public Module ModDownloadLib
ToolTipService.SetVerticalOffset(BtnInfo, 30)
ToolTipService.SetHorizontalOffset(BtnInfo, 2)
AddHandler BtnInfo.Click, AddressOf McDownloadMenuLog
- sender.Buttons = {BtnInfo}
+ Dim BtnServer As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonServer, .ToolTip = "下载服务端"}
+ ToolTipService.SetPlacement(BtnServer, Primitives.PlacementMode.Center)
+ ToolTipService.SetVerticalOffset(BtnServer, 30)
+ ToolTipService.SetHorizontalOffset(BtnServer, 2)
+ AddHandler BtnServer.Click, AddressOf McDownloadMenuSaveServer
+ sender.Buttons = {BtnServer, BtnInfo}
End Sub
Private Sub McDownloadMenuBuild(sender As Object, e As EventArgs)
Dim BtnSave As New MyIconButton With {.Logo = Logo.IconButtonSave, .ToolTip = "另存为"}
@@ -220,7 +186,12 @@ Public Module ModDownloadLib
ToolTipService.SetVerticalOffset(BtnInfo, 30)
ToolTipService.SetHorizontalOffset(BtnInfo, 2)
AddHandler BtnInfo.Click, AddressOf McDownloadMenuLog
- sender.Buttons = {BtnSave, BtnInfo}
+ Dim BtnServer As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonServer, .ToolTip = "下载服务端"}
+ ToolTipService.SetPlacement(BtnServer, Primitives.PlacementMode.Center)
+ ToolTipService.SetVerticalOffset(BtnServer, 30)
+ ToolTipService.SetHorizontalOffset(BtnServer, 2)
+ AddHandler BtnServer.Click, AddressOf McDownloadMenuSaveServer
+ sender.Buttons = {BtnSave, BtnInfo, BtnServer}
End Sub
Private Sub McDownloadMenuLog(sender As Object, e As RoutedEventArgs)
Dim Version As JToken
@@ -233,6 +204,83 @@ Public Module ModDownloadLib
End If
McUpdateLogShow(Version)
End Sub
+ Private Sub McDownloadMenuSaveServer(sender As Object, e As RoutedEventArgs)
+ Dim Version As MyListItem
+ If TypeOf sender Is MyListItem Then
+ Version = sender
+ ElseIf TypeOf sender.Parent Is MyListItem Then
+ Version = sender.Parent
+ Else
+ Version = sender.Parent.Parent
+ End If
+ Try
+ Dim Id = Version.Title
+ Dim JsonUrl = Version.Tag("url").ToString
+ Dim VersionFolder As String = SelectFolder()
+ If Not VersionFolder.Contains("\") Then Exit Sub
+ VersionFolder = VersionFolder & Id & "\"
+
+ '重复任务检查
+ For Each OngoingLoader In LoaderTaskbar.ToList()
+ If OngoingLoader.Name <> $"Minecraft {Id} 服务端下载" Then Continue For
+ Hint("该服务端正在下载中!", HintType.Critical)
+ Exit Sub
+ Next
+
+ Dim Loaders As New List(Of LoaderBase)
+ '下载版本 JSON 文件
+ Loaders.Add(New LoaderDownload("下载版本 JSON 文件", New List(Of NetFile) From {
+ New NetFile(DlSourceLauncherOrMetaGet(JsonUrl), VersionFolder & Id & ".json", New FileChecker(CanUseExistsFile:=False, IsJson:=True))
+ }) With {.ProgressWeight = 2})
+ '构建服务端
+ Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("构建服务端",
+ Sub(Task As LoaderTask(Of String, List(Of NetFile)))
+ '分析服务端 JAR 文件下载地址
+ Dim McVersion As New McVersion(VersionFolder)
+ If McVersion.JsonObject("downloads") Is Nothing OrElse McVersion.JsonObject("downloads")("server") Is Nothing OrElse McVersion.JsonObject("downloads")("server")("url") Is Nothing Then
+ File.Delete(VersionFolder & Id & ".json")
+ If Not New DirectoryInfo(VersionFolder).GetFileSystemInfos.Any() Then Directory.Delete(VersionFolder)
+ Task.Output = New List(Of NetFile)
+ Hint($"Mojang 没有给 Minecraft {Id} 提供官方服务端下载,没法下,撤退!", HintType.Critical)
+ Thread.Sleep(2000) '等玩家把上一个提示看完
+ Task.Abort()
+ Exit Sub
+ 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"))
+ Task.Output = New List(Of NetFile) From {New NetFile(DlSourceLauncherOrMetaGet(JarUrl), VersionFolder & Id & "-server.jar", Checker)}
+ '添加启动脚本
+ Dim Bat As String =
+$"@echo off
+title {Id} 原版服务端
+echo 如果服务端立即停止,请右键编辑该脚本,将下一行开头的 java 替换为适合该 Minecraft 版本的完整 java.exe 的路径。
+echo 你可以在 PCL 的 [设置 → 启动选项] 中查看已安装的 java,所需的 java.exe 一般在其中的 bin 文件夹下。
+echo ------------------------------
+echo 如果提示 ""You need to agree to the EULA in order to run the server"",请打开 eula.txt,按说明阅读并同意 Minecraft EULA 后,将该文件最后一行中的 eula=false 改为 eula=true。
+echo ------------------------------
+""java"" -server -XX:+UseG1GC -Xmx4096M -Xms1024M -XX:+UseCompressedOops -jar {Id}-server.jar nogui
+echo ----------------------
+echo 服务端已停止。
+pause"
+ WriteFile(VersionFolder & "Launch Server.bat", Bat,
+ Encoding:=If(Encoding.Default.Equals(Encoding.UTF8), Encoding.UTF8, Encoding.GetEncoding("GB18030")))
+ '删除版本 JSON
+ File.Delete(VersionFolder & Id & ".json")
+ End Sub
+ ) With {.ProgressWeight = 0.5, .Show = False})
+ '下载服务端文件
+ Loaders.Add(New LoaderDownload("下载服务端文件", New List(Of NetFile)) With {.ProgressWeight = 5})
+
+ '启动
+ Dim Loader As New LoaderCombo(Of String)("Minecraft " & Id & " 服务端下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
+ Loader.Start(Id)
+ LoaderTaskbarAdd(Loader)
+ FrmMain.BtnExtraDownload.ShowRefresh()
+ FrmMain.BtnExtraDownload.Ribble()
+ Catch ex As Exception
+ Log(ex, "开始 Minecraft 服务端下载失败", LogLevel.Feedback)
+ End Try
+ End Sub
Public Sub McDownloadMenuSave(sender As Object, e As RoutedEventArgs)
Dim Version As MyListItem
If TypeOf sender Is MyListItem Then
@@ -242,7 +290,41 @@ Public Module ModDownloadLib
Else
Version = sender.Parent.Parent
End If
- McDownloadClientCore(Version.Title, Version.Tag("url").ToString, NetPreDownloadBehaviour.HintWhileExists)
+ Try
+ Dim Id = Version.Title
+ Dim JsonUrl = Version.Tag("url").ToString
+ Dim VersionFolder As String = SelectFolder()
+ If Not VersionFolder.Contains("\") Then Exit Sub
+ VersionFolder = VersionFolder & Id & "\"
+
+ '重复任务检查
+ For Each OngoingLoader In LoaderTaskbar.ToList()
+ If OngoingLoader.Name <> $"Minecraft {Id} 下载" Then Continue For
+ Hint("该版本正在下载中!", HintType.Critical)
+ Exit Sub
+ Next
+
+ Dim Loaders As New List(Of LoaderBase)
+ '下载版本 JSON 文件
+ Loaders.Add(New LoaderDownload("下载版本 JSON 文件", New List(Of NetFile) From {
+ New NetFile(DlSourceLauncherOrMetaGet(JsonUrl), VersionFolder & Id & ".json", New FileChecker(CanUseExistsFile:=False, IsJson:=True))
+ }) With {.ProgressWeight = 2})
+ '获取支持库文件地址
+ Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("分析核心 JAR 文件下载地址",
+ Sub(Task) Task.Output = New List(Of NetFile) From {DlClientJarGet(New McVersion(VersionFolder), False)}
+ ) With {.ProgressWeight = 0.5, .Show = False})
+ '下载支持库文件
+ Loaders.Add(New LoaderDownload("下载核心 JAR 文件", New List(Of NetFile)) With {.ProgressWeight = 5})
+
+ '启动
+ Dim Loader As New LoaderCombo(Of String)("Minecraft " & Id & " 下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
+ Loader.Start(Id)
+ LoaderTaskbarAdd(Loader)
+ FrmMain.BtnExtraDownload.ShowRefresh()
+ FrmMain.BtnExtraDownload.Ribble()
+ Catch ex As Exception
+ Log(ex, "开始 Minecraft 下载失败", LogLevel.Feedback)
+ End Try
End Sub
'''
''' 显示某 Minecraft 版本的更新日志。
@@ -313,17 +395,17 @@ Public Module ModDownloadLib
Private Sub McDownloadOptiFineSave(DownloadInfo As DlOptiFineListEntry)
Try
Dim Id As String = DownloadInfo.NameVersion
- Dim Target As String = SelectAs("选择保存位置", DownloadInfo.NameFile, "OptiFine Jar (*.jar)|*.jar")
+ Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.NameFile, "OptiFine Jar (*.jar)|*.jar")
If Not Target.Contains("\") Then Exit Sub
'重复任务检查
- For Each OngoingLoader In LoaderTaskbar
+ For Each OngoingLoader In LoaderTaskbar.ToList()
If OngoingLoader.Name <> $"OptiFine {DownloadInfo.NameDisplay} 下载" Then Continue For
Hint("该版本正在下载中!", HintType.Critical)
Exit Sub
Next
- Dim Loader As New LoaderCombo(Of DlOptiFineListEntry)("OptiFine " & DownloadInfo.NameDisplay & " 下载", McDownloadOptiFineSaveLoader(DownloadInfo, Target)) With {.OnStateChanged = AddressOf DownloadStateSave}
+ Dim Loader As New LoaderCombo(Of DlOptiFineListEntry)("OptiFine " & DownloadInfo.NameDisplay & " 下载", McDownloadOptiFineSaveLoader(DownloadInfo, Target)) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
Loader.Start(DownloadInfo)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
@@ -358,10 +440,10 @@ Public Module ModDownloadLib
End SyncLock
'添加 Java Wrapper 作为主 Jar
Dim Arguments As String
- If UseJavaWrapper Then
- Arguments = $"-Doolloo.jlw.tmpdir=""{GetJavaWrapperDir()}"" -Duser.home=""{BaseMcFolderHome}"" -cp ""{Target}"" -jar ""{ExtractJavaWrapper()}"" optifine.Installer"
+ If UseJavaWrapper AndAlso Not Setup.Get("LaunchAdvanceDisableJLW") Then
+ Arguments = $"-Doolloo.jlw.tmpdir=""{PathPure.TrimEnd("\")}"" -Duser.home=""{BaseMcFolderHome.TrimEnd("\")}"" -cp ""{Target}"" -jar ""{ExtractJavaWrapper()}"" optifine.Installer"
Else
- Arguments = $"-Duser.home=""{BaseMcFolderHome}"" -cp ""{Target}"" optifine.Installer"
+ Arguments = $"-Duser.home=""{BaseMcFolderHome.TrimEnd("\")}"" -cp ""{Target}"" optifine.Installer"
End If
If Java.VersionCode >= 9 Then Arguments = "--add-exports cpw.mods.bootstraplauncher/cpw.mods.bootstraplauncher=ALL-UNNAMED " & Arguments
'开始启动
@@ -373,7 +455,7 @@ Public Module ModDownloadLib
.CreateNoWindow = True,
.RedirectStandardError = True,
.RedirectStandardOutput = True,
- .WorkingDirectory = BaseMcFolderHome
+ .WorkingDirectory = ShortenPath(BaseMcFolderHome)
}
If Info.EnvironmentVariables.ContainsKey("appdata") Then
Info.EnvironmentVariables("appdata") = BaseMcFolderHome
@@ -463,76 +545,76 @@ Public Module ModDownloadLib
Dim VersionFolder As String = McFolder & "versions\" & Id & "\"
Dim IsNewVersion As Boolean = DownloadInfo.Inherit.Contains("w") OrElse Val(DownloadInfo.Inherit.Split(".")(1)) >= 14
Dim Target As String = If(IsNewVersion,
- PathTemp & "Cache\Code\" & DownloadInfo.NameVersion & "_" & GetUuid(),
- McFolder & "libraries\optifine\OptiFine\" & DownloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "") & "\" & DownloadInfo.NameFile.Replace("OptiFine_", "OptiFine-").Replace("preview_", ""))
+ $"{RequestTaskTempFolder()}OptiFine.jar",
+ $"{McFolder}libraries\optifine\OptiFine\{DownloadInfo.NameFile.Replace("OptiFine_", "").Replace(".jar", "").Replace("preview_", "")}\{DownloadInfo.NameFile.Replace("OptiFine_", "OptiFine-").Replace("preview_", "")}")
Dim Loaders As New List(Of LoaderBase)
'获取下载地址
Loaders.Add(New LoaderTask(Of String, List(Of NetFile))("获取 OptiFine 主文件下载地址",
- Sub(Task As LoaderTask(Of String, List(Of NetFile)))
- '启动依赖版本的下载
- If ClientDownloadLoader Is Nothing Then
- If IsCustomFolder Then Throw New Exception("如果没有指定原版下载器,则不能指定 MC 安装文件夹")
- ClientDownloadLoader = McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, DownloadInfo.Inherit)
- End If
- Task.Progress = 0.1
- Dim Sources As New List(Of String)
- 'BMCLAPI 源
- If DownloadInfo.IsPreview Then
- Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
- Else
- Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
- End If
- '官方源
- Dim PageData As String
- Try
- PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True)
- Task.Progress = 0.8
- Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
- Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources(0))
- Catch ex As Exception
- Log(ex, "获取 OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址失败")
- End Try
- 'OptiFine 中文镜像源
- Sources.Add("https://optifine.cn/download/" & DownloadInfo.NameFile)
- '构造文件请求
- Task.Output = New List(Of NetFile) From {New NetFile(Sources.ToArray, Target, New FileChecker(MinSize:=300 * 1024))}
- End Sub) With {.ProgressWeight = 8})
+ Sub(Task As LoaderTask(Of String, List(Of NetFile)))
+ '启动依赖版本的下载
+ If ClientDownloadLoader Is Nothing Then
+ If IsCustomFolder Then Throw New Exception("如果没有指定原版下载器,则不能指定 MC 安装文件夹")
+ ClientDownloadLoader = McDownloadClient(NetPreDownloadBehaviour.ExitWhileExistsOrDownloading, DownloadInfo.Inherit)
+ End If
+ Task.Progress = 0.1
+ Dim Sources As New List(Of String)
+ 'BMCLAPI 源
+ Dim BmclapiInherit As String = DownloadInfo.Inherit
+ If BmclapiInherit = "1.8" OrElse BmclapiInherit = "1.9" Then BmclapiInherit &= ".0" '#4281
+ If DownloadInfo.IsPreview Then
+ Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
+ Else
+ Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
+ End If
+ '官方源
+ Dim PageData As String
+ Try
+ PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True)
+ Task.Progress = 0.8
+ Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
+ Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
+ Catch ex As Exception
+ Log(ex, "获取 OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址失败")
+ End Try
+ '构造文件请求
+ Task.Output = New List(Of NetFile) From {New NetFile(Sources.ToArray, Target, New FileChecker(MinSize:=300 * 1024))}
+ End Sub) With {.ProgressWeight = 8})
Loaders.Add(New LoaderDownload("下载 OptiFine 主文件", New List(Of NetFile)) With {.ProgressWeight = 8})
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
- 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
- If TargetLoaders.Any Then Log("[Download] OptiFine 安装正在等待原版文件下载完成")
- Do While TargetLoaders.Any AndAlso Not Task.IsAborted
- TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
- Thread.Sleep(50)
- Loop
- If Task.IsAborted Then Exit Sub
- '拷贝原版文件
- If Not IsCustomFolder Then Exit Sub
- SyncLock VanillaSyncLock
- Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
- Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit)
- If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json") Then
- CopyFile($"{ClientFolder}{ClientName}.json", $"{McFolder}versions\{DownloadInfo.Inherit}\{DownloadInfo.Inherit}.json")
- End If
- If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar") Then
- CopyFile($"{ClientFolder}{ClientName}.jar", $"{McFolder}versions\{DownloadInfo.Inherit}\{DownloadInfo.Inherit}.jar")
- End If
- End SyncLock
- End Sub) With {.ProgressWeight = 0.1, .Show = False})
+ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
+ '等待原版文件下载完成
+ If ClientDownloadLoader Is Nothing Then Exit Sub
+ 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
+ If TargetLoaders.Any Then Log("[Download] OptiFine 安装正在等待原版文件下载完成")
+ Do While TargetLoaders.Any AndAlso Not Task.IsAborted
+ TargetLoaders = TargetLoaders.Where(Function(l) l.State <> LoadState.Finished).ToList
+ Thread.Sleep(50)
+ Loop
+ If Task.IsAborted Then Exit Sub
+ '拷贝原版文件
+ If Not IsCustomFolder Then Exit Sub
+ SyncLock VanillaSyncLock
+ Dim ClientName As String = GetFolderNameFromPath(ClientFolder)
+ Directory.CreateDirectory(McFolder & "versions\" & DownloadInfo.Inherit)
+ If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".json") Then
+ CopyFile($"{ClientFolder}{ClientName}.json", $"{McFolder}versions\{DownloadInfo.Inherit}\{DownloadInfo.Inherit}.json")
+ End If
+ If Not File.Exists(McFolder & "versions\" & DownloadInfo.Inherit & "\" & DownloadInfo.Inherit & ".jar") Then
+ CopyFile($"{ClientFolder}{ClientName}.jar", $"{McFolder}versions\{DownloadInfo.Inherit}\{DownloadInfo.Inherit}.jar")
+ End If
+ End SyncLock
+ End Sub) With {.ProgressWeight = 0.1, .Show = False})
'安装(新旧方式均需要原版 Jar 和 Json)
If IsNewVersion Then
Log("[Download] 检测为新版 OptiFine:" & DownloadInfo.Inherit)
Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)("安装 OptiFine(方式 A)",
Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
- Dim BaseMcFolderHome As String = PathTemp & "InstallOptiFine" & RandomInteger(0, 100000)
- Dim BaseMcFolder As String = BaseMcFolderHome & "\.minecraft\"
+ Dim BaseMcFolderHome As String = RequestTaskTempFolder()
+ Dim BaseMcFolder As String = BaseMcFolderHome & ".minecraft\"
Try
'准备安装环境
If Directory.Exists(BaseMcFolder & "versions\" & DownloadInfo.Inherit) Then
@@ -641,10 +723,12 @@ Retry:
Sub(Task As LoaderTask(Of DlOptiFineListEntry, List(Of NetFile)))
Dim Sources As New List(Of String)
'BMCLAPI 源
+ Dim BmclapiInherit As String = DownloadInfo.Inherit
+ If BmclapiInherit = "1.8" OrElse BmclapiInherit = "1.9" Then BmclapiInherit &= ".0" '#4281
If DownloadInfo.IsPreview Then
- Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
+ Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U_" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", "").Replace(" ", "/"))
Else
- Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & DownloadInfo.Inherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
+ Sources.Add("https://bmclapi2.bangbang93.com/optifine/" & BmclapiInherit & "/HD_U/" & DownloadInfo.NameDisplay.Replace(DownloadInfo.Inherit & " ", ""))
End If
'官方源
Dim PageData As String
@@ -652,7 +736,7 @@ Retry:
PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True)
Task.Progress = 0.8
Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
- Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources(0))
+ Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
Catch ex As Exception
Log(ex, "获取 OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址失败")
End Try
@@ -675,7 +759,7 @@ Retry:
.Title = Entry.NameDisplay, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
.Info = If(Entry.IsPreview, "测试版", "正式版") &
If(Entry.ReleaseTime = "", "", ",发布于 " & Entry.ReleaseTime) &
- If(Entry.RequiredForgeVersion Is Nothing, ",不兼容 Forge", If(Entry.RequiredForgeVersion = "", "", ",推荐 Forge 版本:" & Entry.RequiredForgeVersion)),
+ If(Entry.RequiredForgeVersion Is Nothing, ",不兼容 Forge", If(Entry.RequiredForgeVersion = "", "", ",兼容 Forge " & Entry.RequiredForgeVersion)),
.Logo = PathImage & "Blocks/GrassPath.png"
}
AddHandler NewItem.Click, OnClick
@@ -744,7 +828,7 @@ Retry:
Dim VersionFolder As String = PathMcFolder & "versions\" & VersionName & "\"
'重复任务检查
- For Each OngoingLoader In LoaderTaskbar
+ For Each OngoingLoader In LoaderTaskbar.ToList()
If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
Hint("该版本正在下载中!", HintType.Critical)
Exit Sub
@@ -774,11 +858,11 @@ Retry:
Private Sub McDownloadLiteLoaderSave(DownloadInfo As DlLiteLoaderListEntry)
Try
Dim Id As String = DownloadInfo.Inherit
- Dim Target As String = SelectAs("选择保存位置", DownloadInfo.FileName.Replace("-SNAPSHOT", ""), "LiteLoader 安装器 (*.jar)|*.jar")
+ Dim Target As String = SelectSaveFile("选择保存位置", DownloadInfo.FileName.Replace("-SNAPSHOT", ""), "LiteLoader 安装器 (*.jar)|*.jar")
If Not Target.Contains("\") Then Exit Sub
'重复任务检查
- For Each OngoingLoader In LoaderTaskbar
+ For Each OngoingLoader In LoaderTaskbar.ToList()
If OngoingLoader.Name <> $"LiteLoader {Id} 下载" Then Continue For
Hint("该版本正在下载中!", HintType.Critical)
Exit Sub
@@ -810,7 +894,7 @@ Retry:
End If
Loaders.Add(New LoaderDownload("下载主文件", New List(Of NetFile) From {New NetFile(Address.ToArray, Target, New FileChecker(MinSize:=1024 * 1024))}) With {.ProgressWeight = 15})
'启动
- Dim Loader As New LoaderCombo(Of DlLiteLoaderListEntry)("LiteLoader " & Id & " 安装器下载", Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
+ Dim Loader As New LoaderCombo(Of DlLiteLoaderListEntry)("LiteLoader " & Id & " 安装器下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
Loader.Start(DownloadInfo)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
@@ -954,13 +1038,13 @@ Retry:
Public Sub McDownloadForgelikeSave(Info As DlForgelikeEntry)
Try
- Dim Target As String = SelectAs("选择保存位置", $"{Info.LoaderName}-{Info.Inherit}-{Info.VersionName}.{Info.FileExtension}",
+ 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
'重复任务检查
- For Each OngoingLoader In LoaderTaskbar
+ For Each OngoingLoader In LoaderTaskbar.ToList()
If OngoingLoader.Name <> $"{DisplayName} 下载" Then Continue For
Hint("该版本正在下载中!", HintType.Critical)
Exit Sub
@@ -989,7 +1073,7 @@ Retry:
Loaders.Add(New LoaderDownload("下载主文件", Files) With {.ProgressWeight = 6})
'启动
- Dim Loader = New LoaderCombo(Of DlForgelikeEntry)(DisplayName & " 下载", Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
+ Dim Loader = New LoaderCombo(Of DlForgelikeEntry)(DisplayName & " 下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
Loader.Start(Info)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
@@ -1025,8 +1109,8 @@ Retry:
End SyncLock
'添加 Java Wrapper 作为主 Jar
Dim Arguments As String
- If UseJavaWrapper Then
- Arguments = $"-Doolloo.jlw.tmpdir=""{GetJavaWrapperDir()}"" -cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" -jar ""{ExtractJavaWrapper()}"" com.bangbang93.ForgeInstaller ""{McFolder}"
+ If UseJavaWrapper AndAlso Not Setup.Get("LaunchAdvanceDisableJLW") Then
+ Arguments = $"-Doolloo.jlw.tmpdir=""{PathPure.TrimEnd("\")}"" -cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" -jar ""{ExtractJavaWrapper()}"" com.bangbang93.ForgeInstaller ""{McFolder}"
Else
Arguments = $"-cp ""{PathTemp}Cache\forge_installer.jar;{Target}"" com.bangbang93.ForgeInstaller ""{McFolder}"
End If
@@ -1175,12 +1259,12 @@ Retry:
End If
If Not IsNeoForge AndAlso LoaderVersion.StartsWithF("1.") AndAlso LoaderVersion.Contains("-") Then
'类似 1.19.3-41.2.8 格式,优先使用 Version 中要求的版本而非 Inherit(例如 1.19.3 却使用了 1.19 的 Forge)
- Inherit = LoaderVersion.Before("-")
- LoaderVersion = LoaderVersion.After("-")
+ Inherit = LoaderVersion.BeforeFirst("-")
+ LoaderVersion = LoaderVersion.AfterLast("-")
End If
Dim LoaderName As String = If(IsNeoForge, "NeoForge", "Forge")
Dim IsCustomFolder As Boolean = McFolder <> PathMcFolder
- Dim InstallerAddress As String = $"{PathTemp}Cache\Code\{LoaderName}Install-{LoaderVersion}_{RandomInteger(0, 100000)}"
+ Dim InstallerAddress As String = RequestTaskTempFolder() & "forge_installer.jar"
Dim VersionFolder As String = $"{McFolder}versions\{TargetVersion}\"
Dim DisplayName As String = $"{LoaderName} {Inherit} - {LoaderVersion}"
Dim Loaders As New List(Of LoaderBase)
@@ -1236,7 +1320,7 @@ Retry:
Loaders.Add(New LoaderDownload($"下载 {LoaderName} 主文件", New List(Of NetFile)) With {.ProgressWeight = 9})
'安装(仅在新版安装时需要原版 Jar)
- If IsNeoForge OrElse LoaderVersion.Before(".") >= 20 Then
+ If IsNeoForge OrElse LoaderVersion.BeforeFirst(".") >= 20 Then
Log($"[Download] 检测为{If(IsNeoForge, " Neo", "新版 ")}Forge:" & LoaderVersion)
Dim Libs As List(Of McLibToken) = Nothing
Loaders.Add(New LoaderTask(Of String, List(Of NetFile))($"分析 {LoaderName} 支持库文件",
@@ -1256,9 +1340,9 @@ Retry:
If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then
'下载原版 Json 文件
Task.Progress = 0.4
- Dim RawJson As JObject = GetJson(NetGetCodeByDownload(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True))
+ Dim RawJson As JObject = GetJson(NetGetCodeByLoader(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()).Before("@")
+ 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 {
@@ -1270,7 +1354,7 @@ Retry:
'去除其中的原始 Forgelike 项
For i = 0 To Libs.Count - 1
If Libs(i).LocalPath.EndsWithF($"{LoaderName.ToLower}-{Inherit}-{LoaderVersion}.jar") OrElse
-Libs(i).LocalPath.EndsWithF($"{LoaderName.ToLower}-{Inherit}-{LoaderVersion}-client.jar") Then
+ Libs(i).LocalPath.EndsWithF($"{LoaderName.ToLower}-{Inherit}-{LoaderVersion}-client.jar") Then
Log($"[Download] 已从待下载 {LoaderName} 支持库中移除:" & Libs(i).LocalPath, LogLevel.Debug)
Libs.RemoveAt(i)
Exit For
@@ -1381,8 +1465,6 @@ Retry:
'没有新增文件夹
Log("[Download] 未找到新增的版本文件夹")
End If
- '新建 mods 文件夹
- Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\")
Catch ex As Exception
Throw New Exception($"安装新 {LoaderName} 版本失败", ex)
Finally
@@ -1398,54 +1480,52 @@ Retry:
Else
Log("[Download] 检测为非新版 Forge:" & LoaderVersion)
Loaders.Add(New LoaderTask(Of List(Of NetFile), Boolean)($"安装 {LoaderName}(方式 B)",
-Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
- Dim Installer As ZipArchive = Nothing
- Try
- '解压并获取信息
- Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open))
- Task.Progress = 0.2
- Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open))
- Task.Progress = 0.4
- '新建版本文件夹
- Directory.CreateDirectory(VersionFolder)
- Task.Progress = 0.5
- If Json("install") Is Nothing Then
- '中版:Legacy 方式 1
- Log("[Download] 开始进行 Forge 安装,Legacy 方式 1:" & InstallerAddress)
- '建立 Json 文件
- Dim JsonVersion As JObject = GetJson(ReadFile(Installer.GetEntry(Json("json").ToString.TrimStart("/")).Open))
- JsonVersion("id") = TargetVersion
- WriteFile(VersionFolder & TargetVersion & ".json", JsonVersion.ToString)
- Task.Progress = 0.6
- '解压支持库文件
- Installer.Dispose()
- ExtractFile(InstallerAddress, InstallerAddress & "_unrar\")
- CopyDirectory(InstallerAddress & "_unrar\maven\", McFolder & "libraries\")
- DeleteDirectory(InstallerAddress & "_unrar\")
- Else
- '旧版:Legacy 方式 2
- Log("[Download] 开始进行 Forge 安装,Legacy 方式 2:" & InstallerAddress)
- '解压 Jar 文件
- Dim JarAddress As String = McLibGet(Json("install")("path"), CustomMcFolder:=McFolder)
- If File.Exists(JarAddress) Then File.Delete(JarAddress)
- WriteFile(JarAddress, Installer.GetEntry(Json("install")("filePath")).Open)
- Task.Progress = 0.9
- '建立 Json 文件
- Json("versionInfo")("id") = TargetVersion
- If Json("versionInfo")("inheritsFrom") Is Nothing Then CType(Json("versionInfo"), JObject).Add("inheritsFrom", Inherit)
- WriteFile(VersionFolder & TargetVersion & ".json", Json("versionInfo").ToString)
- End If
- '新建 mods 文件夹
- Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\")
- Catch ex As Exception
- Throw New Exception("非新版方式安装 Forge 失败", ex)
- Finally
- Try
- '清理文件
- If Installer IsNot Nothing Then Installer.Dispose()
- If File.Exists(InstallerAddress) Then File.Delete(InstallerAddress)
- If Directory.Exists(InstallerAddress & "_unrar\") Then DeleteDirectory(InstallerAddress & "_unrar\")
- Catch ex As Exception
+ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
+ Dim Installer As ZipArchive = Nothing
+ Try
+ '解压并获取信息
+ Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open))
+ Task.Progress = 0.2
+ Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open))
+ Task.Progress = 0.4
+ '新建版本文件夹
+ Directory.CreateDirectory(VersionFolder)
+ Task.Progress = 0.5
+ If Json("install") Is Nothing Then
+ '中版:Legacy 方式 1
+ Log("[Download] 开始进行 Forge 安装,Legacy 方式 1:" & InstallerAddress)
+ '建立 Json 文件
+ Dim JsonVersion As JObject = GetJson(ReadFile(Installer.GetEntry(Json("json").ToString.TrimStart("/")).Open))
+ JsonVersion("id") = TargetVersion
+ WriteFile(VersionFolder & TargetVersion & ".json", JsonVersion.ToString)
+ Task.Progress = 0.6
+ '解压支持库文件
+ Installer.Dispose()
+ ExtractFile(InstallerAddress, InstallerAddress & "_unrar\")
+ CopyDirectory(InstallerAddress & "_unrar\maven\", McFolder & "libraries\")
+ DeleteDirectory(InstallerAddress & "_unrar\")
+ Else
+ '旧版:Legacy 方式 2
+ Log("[Download] 开始进行 Forge 安装,Legacy 方式 2:" & InstallerAddress)
+ '解压 Jar 文件
+ Dim JarAddress As String = McLibGet(Json("install")("path"), CustomMcFolder:=McFolder)
+ If File.Exists(JarAddress) Then File.Delete(JarAddress)
+ WriteFile(JarAddress, Installer.GetEntry(Json("install")("filePath")).Open)
+ Task.Progress = 0.9
+ '建立 Json 文件
+ Json("versionInfo")("id") = TargetVersion
+ If Json("versionInfo")("inheritsFrom") Is Nothing Then CType(Json("versionInfo"), JObject).Add("inheritsFrom", Inherit)
+ WriteFile(VersionFolder & TargetVersion & ".json", Json("versionInfo").ToString)
+ End If
+ Catch ex As Exception
+ Throw New Exception("非新版方式安装 Forge 失败", ex)
+ Finally
+ Try
+ '清理文件
+ If Installer IsNot Nothing Then Installer.Dispose()
+ If File.Exists(InstallerAddress) Then File.Delete(InstallerAddress)
+ If Directory.Exists(InstallerAddress & "_unrar\") Then DeleteDirectory(InstallerAddress & "_unrar\")
+ Catch ex As Exception
Log(ex, "非新版方式安装 Forge 清理文件时出错")
End Try
End Try
@@ -1460,6 +1540,8 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
#Region "Forge 下载菜单"
Public Sub ForgeDownloadListItemPreload(Stack As StackPanel, Entries As List(Of DlForgeVersionEntry), OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean)
+ '如果只有一个版本,则不特别列出
+ If Entries.Count = 1 Then Return
'获取推荐版本与最新版本
Dim FreshVersion As DlForgeVersionEntry = Nothing
If Entries.Any Then
@@ -1562,7 +1644,7 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
RunInNewThread(Sub()
Try
Log("[Download] 刷新 Forge 推荐版本缓存开始")
- Dim Result As String = NetGetCodeByDownload("https://bmclapi2.bangbang93.com/forge/promos")
+ 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)
'获取所有推荐版本列表
@@ -1611,6 +1693,8 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
#Region "NeoForge 下载菜单"
Public Sub NeoForgeDownloadListItemPreload(Stack As StackPanel, Entries As List(Of DlNeoForgeListEntry), OnClick As MyListItem.ClickEventHandler, IsSaveOnly As Boolean)
+ '如果只有一个版本,则不特别列出
+ If Entries.Count = 1 Then Return
'获取最新稳定版和测试版
Dim FreshStableVersion As DlNeoForgeListEntry = Nothing
Dim FreshBetaVersion As DlNeoForgeListEntry = Nothing
@@ -1710,11 +1794,11 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
Dim Url As String = DownloadInfo("url").ToString
Dim FileName As String = GetFileNameFromPath(Url)
Dim Version As String = GetFileNameFromPath(DownloadInfo("version").ToString)
- Dim Target As String = SelectAs("选择保存位置", FileName, "Fabric 安装器 (*.jar)|*.jar")
+ Dim Target As String = SelectSaveFile("选择保存位置", FileName, "Fabric 安装器 (*.jar)|*.jar")
If Not Target.Contains("\") Then Exit Sub
'重复任务检查
- For Each OngoingLoader In LoaderTaskbar
+ For Each OngoingLoader In LoaderTaskbar.ToList()
If OngoingLoader.Name <> $"Fabric {Version} 安装器下载" Then Continue For
Hint("该版本正在下载中!", HintType.Critical)
Exit Sub
@@ -1728,7 +1812,7 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
Address.Add(Url)
Loaders.Add(New LoaderDownload("下载主文件", New List(Of NetFile) From {New NetFile(Address.ToArray, Target, New FileChecker(MinSize:=1024 * 64))}) With {.ProgressWeight = 15})
'启动
- Dim Loader As New LoaderCombo(Of JObject)("Fabric " & Version & " 安装器下载", Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
+ Dim Loader As New LoaderCombo(Of JObject)("Fabric " & Version & " 安装器下载", Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
Loader.Start(DownloadInfo)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
@@ -1765,8 +1849,6 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
"https://bmclapi2.bangbang93.com/fabric-meta/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json",
"https://meta.fabricmc.net/v2/versions/loader/" & MinecraftName & "/" & FabricVersion & "/profile/json"
}, VersionFolder & Id & ".json", New FileChecker(IsJson:=True))}
- '新建 mods 文件夹
- Directory.CreateDirectory(New McVersion(VersionFolder).GetPathIndie(True) & "mods\")
End Sub) With {.ProgressWeight = 0.5})
Loaders.Add(New LoaderDownload("下载 Fabric 主文件", New List(Of NetFile)) With {.ProgressWeight = 2.5})
@@ -1798,7 +1880,7 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
Public Function FabricApiDownloadListItem(Entry As CompFile, OnClick As MyListItem.ClickEventHandler) As MyListItem
'建立控件
Dim NewItem As New MyListItem With {
- .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").Before("+").Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
+ .Title = Entry.DisplayName.Split("]")(1).Replace("Fabric API ", "").Replace(" build ", ".").BeforeFirst("+").Trim, .SnapsToDevicePixels = True, .Height = 42, .Type = MyListItem.CheckType.Clickable, .Tag = Entry,
.Info = Entry.StatusDescription & ",发布于 " & Entry.ReleaseDate.ToString("yyyy'/'MM'/'dd HH':'mm"),
.Logo = PathImage & "Blocks/Fabric.png"
}
@@ -1897,6 +1979,20 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
End Class
+ '''
+ ''' 在加载器状态改变后显示一条提示。
+ ''' 不会进行任何其他操作。
+ '''
+ Public Sub LoaderStateChangedHintOnly(Loader)
+ Select Case Loader.State
+ Case LoadState.Finished
+ Hint(Loader.Name & "成功!", HintType.Finish)
+ Case LoadState.Failed
+ Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
+ Case LoadState.Aborted
+ Hint(Loader.Name & "已取消!", HintType.Info)
+ End Select
+ End Sub
'''
''' 安装加载器状态改变后进行提示和重载文件夹列表的方法。
'''
@@ -1915,21 +2011,6 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
McInstallFailedClearFolder(Loader)
LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
End Sub
- '''
- ''' 安装加载器状态改变后进行提示的方法。它不会在失败时删除文件夹,也不会刷新 MC 文件夹。
- '''
- Public Sub DownloadStateSave(Loader)
- Select Case Loader.State
- Case LoadState.Finished
- Hint(Loader.Name & "成功!", HintType.Finish)
- Case LoadState.Failed
- Hint(Loader.Name & "失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
- Case LoadState.Aborted
- Hint(Loader.Name & "已取消!", HintType.Info)
- Case LoadState.Loading
- Exit Sub '不重新加载版本列表
- End Select
- End Sub
Public Sub McInstallFailedClearFolder(Loader)
Try
Thread.Sleep(1000) '防止存在尚未完全释放的文件,导致清理失败(例如整合包安装)
@@ -1963,6 +2044,8 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
FrmMain.BtnExtraDownload.Ribble()
Return True
+ Catch ex As CancelledException
+ Return False
Catch ex As Exception
Log(ex, "开始合并安装失败", LogLevel.Feedback)
Return False
@@ -1970,35 +2053,18 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
End Function
'''
''' 获取合并安装加载器列表,并进行前期的缓存清理与 Java 检查工作。
- ''' 如果出现已知问题且已提示用户,则返回 Nothing。出现异常则直接抛出。
'''
+ '''
Public Function McInstallLoader(Request As McInstallRequest, Optional DontFixLibraries As Boolean = False) As List(Of LoaderBase)
- '获取缓存目录
- Dim PathInstallTemp As String
- If PathTemp.Contains(" ") AndAlso (Request.OptiFineEntry IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing) Then
- PathInstallTemp = OsDrive & "ProgramData\PCL\Install\"
- Else
- PathInstallTemp = PathTemp & "Install\"
- End If
-
- '清理缓存
- Try
- If Not IsInstallTempCleared Then
- IsInstallTempCleared = True
- DeleteDirectory(PathInstallTemp, True)
- Log("[Download] 已清理合并安装缓存")
- End If
- Catch ex As Exception
- Log(ex, "清理合并安装缓存失败")
- End Try
+ '获取缓存目录(安装 Mod 加载器的文件夹不能包含空格)
+ Dim TempMcFolder As String = RequestTaskTempFolder(Request.OptiFineEntry IsNot Nothing OrElse Request.ForgeEntry IsNot Nothing OrElse Request.NeoForgeEntry IsNot Nothing)
'获取参数
- Dim OutputFolder As String = PathMcFolder & "versions\" & Request.TargetVersionName & "\"
- Dim TempMcFolder As String = PathInstallTemp & Request.TargetVersionName.GetHashCode() & "\"
+ Dim VersionFolder As String = PathMcFolder & "versions\" & Request.TargetVersionName & "\"
If Directory.Exists(TempMcFolder) Then DeleteDirectory(TempMcFolder)
Dim OptiFineFolder As String = Nothing
If Request.OptiFineVersion IsNot Nothing Then
- If Request.OptiFineVersion.Contains("_HD_U_") Then Request.OptiFineVersion = "HD_U_" & Request.OptiFineVersion.After("_HD_U_") '#735
+ If Request.OptiFineVersion.Contains("_HD_U_") Then Request.OptiFineVersion = "HD_U_" & Request.OptiFineVersion.AfterLast("_HD_U_") '#735
Request.OptiFineEntry = New DlOptiFineListEntry With {
.NameDisplay = Request.MinecraftName & " " & Request.OptiFineVersion.Replace("HD_U_", "").Replace("_", "").Replace("pre", " pre"),
.Inherit = Request.MinecraftName,
@@ -2020,14 +2086,17 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
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 MinecraftCode As Integer = If(Request.MinecraftName.Contains("."), Val(Request.MinecraftName.Split(".")(1)), 0)
- Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso '1. 选择了 OptiFine
- (Request.FabricVersion IsNot Nothing OrElse '2. 选择了 Fabric...
- (Request.ForgeEntry IsNot Nothing AndAlso MinecraftCode >= 14 AndAlso MinecraftCode <= 15)) '...或者 Forge 1.14~15(#4134)
+ Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso Modable '选择了 OptiFine 与任意 Mod 加载器
If OptiFineAsMod Then
Log("[Download] OptiFine 将作为 Mod 进行下载")
- OptiFineFolder = New McVersion(OutputFolder).GetPathIndie(True) & "mods\"
+ OptiFineFolder = ModsFolder
End If
'记录日志
@@ -2039,21 +2108,21 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
Log("[Download] 对应的原版版本:" & Request.MinecraftName)
'重复版本检查
- If File.Exists(OutputFolder & Request.TargetVersionName & ".json") Then
+ If File.Exists($"{VersionFolder}{Request.TargetVersionName}.json") Then
Hint("版本 " & Request.TargetVersionName & " 已经存在!", HintType.Critical)
- Return Nothing
+ Throw New CancelledException
End If
Dim LoaderList As New List(Of LoaderBase)
'添加忽略标识
- LoaderList.Add(New LoaderTask(Of Integer, Integer)("添加忽略标识", Sub() WriteFile(OutputFolder & ".pclignore", "用于临时地在 PCL 的版本列表中屏蔽此版本。")) With {.Show = False, .Block = False})
+ 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(New McVersion(OutputFolder).GetPathIndie(True) & "mods\")}) With {.ProgressWeight = 3, .Block = False})
+ LoaderList.Add(New LoaderDownload("下载 Fabric API", New List(Of NetFile) From {Request.FabricApi.ToNetFile(ModsFolder)}) 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(New McVersion(OutputFolder).GetPathIndie(True) & "mods\")}) With {.ProgressWeight = 3, .Block = False})
+ LoaderList.Add(New LoaderDownload("下载 OptiFabric", New List(Of NetFile) From {Request.OptiFabric.ToNetFile(ModsFolder)}) 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}
@@ -2085,25 +2154,29 @@ Sub(Task As LoaderTask(Of List(Of NetFile), Boolean))
'合并安装
LoaderList.Add(New LoaderTask(Of String, String)("安装游戏",
Sub(Task As LoaderTask(Of String, String))
- InstallMerge(OutputFolder, OutputFolder, OptiFineFolder, OptiFineAsMod, ForgeFolder, Request.ForgeVersion, NeoForgeFolder, Request.NeoForgeVersion, FabricFolder, LiteLoaderFolder)
+ 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", PathMcFolder & "mods")
+ 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})
'补全文件
If Not DontFixLibraries AndAlso
- (Request.OptiFineEntry IsNot Nothing OrElse (Request.ForgeVersion IsNot Nothing AndAlso Request.ForgeVersion.Before(".") >= 20) OrElse Request.NeoForgeVersion IsNot Nothing OrElse Request.FabricVersion IsNot Nothing OrElse Request.LiteLoaderEntry IsNot Nothing) Then
+ (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(OutputFolder))) With {.ProgressWeight = 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(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
'删除忽略标识
- LoaderList.Add(New LoaderTask(Of Integer, Integer)("删除忽略标识", Sub() File.Delete(OutputFolder & ".pclignore")) With {.Show = False})
+ LoaderList.Add(New LoaderTask(Of Integer, Integer)("删除忽略标识", Sub() File.Delete(VersionFolder & ".pclignore")) With {.Show = False})
'总加载器
Return LoaderList
End Function
- Private IsInstallTempCleared As Boolean = False
'''
''' 将多个版本 Json 进行合并,如果目标已存在则直接覆盖。失败会抛出异常。
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb
index 5c041d1..2af82b7 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadClient.xaml.vb
@@ -39,7 +39,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")))
@@ -61,8 +61,7 @@
Next
'排序
For i = 0 To Dict.Keys.Count - 1
- Dict(Dict.Keys(i)) = Sort(Dict.Values(i),
- Function(a, b) a("releaseTime").Value(Of Date) > b("releaseTime").Value(Of Date))
+ Dict(Dict.Keys(i)) = Dict.Values(i).OrderByDescending(Function(v) v("releaseTime").Value(Of Date)).ToList
Next
'清空当前
PanMain.Children.Clear()
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml
index 7333268..3a52922 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml
@@ -9,7 +9,6 @@
-
@@ -20,7 +19,12 @@
-
+
+
+
+
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb
index 3df5ed1..2a1dab3 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadCompDetail.xaml.vb
@@ -34,41 +34,120 @@
End Select
End Sub
'结果 UI 化
- Private Class VersionSorterWithSelect
+ Private Class CardSorter
Implements IComparer(Of String)
- Public Top As String = ""
+ Public Topmost As String = ""
Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare
+ '相同
If x = y Then Return 0
- If x = Top Then Return -1
- If y = Top Then Return 1
+ '置顶
+ If x = Topmost Then Return -1
+ If y = Topmost Then Return 1
+ '特殊版本
+ Dim IsXSpecial As Boolean = x.EndsWithF("版本")
+ Dim IsYSpecial As Boolean = y.EndsWithF("版本")
+ If IsXSpecial AndAlso IsYSpecial Then Return x.CompareTo(y)
+ If IsXSpecial Then Return 1
+ If IsYSpecial Then Return -1
+ '比较版本号
+ Dim VersionCodeSort = -VersionSortInteger(x.Replace(x.BeforeFirst(" ") & " ", ""), y.Replace(y.BeforeFirst(" ") & " ", ""))
+ If VersionCodeSort <> 0 Then Return VersionCodeSort
+ '比较全部
Return -VersionSortInteger(x, y)
End Function
- Public Sub New(Optional Top As String = "")
- Me.Top = If(Top, "")
+ Public Sub New(Optional Topmost As String = "")
+ Me.Topmost = If(Topmost, "")
End Sub
End Class
+
+ Private VersionFilter As String
+ Private IsMajorVersionFilter As Boolean '是否按大版本号筛选(1.21 / 1.20 / 1.19 / ...)而非小版本号(1.21.1 / 1.21 / 1.20.4 / ...)
Private Sub Load_OnFinish()
+ '初始化筛选器
+ Dim VersionFilters As List(Of String)
+
+ '按小版本号筛选?
+ IsMajorVersionFilter = False
+ VersionFilters = CompFileLoader.Output.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)).
+ Distinct.OrderByDescending(Function(s) s, New VersionComparer).ToList
+ End If
+
+ 'UI 化筛选器
+ PanFilter.Children.Clear()
+ If VersionFilters.Count < 2 Then
+ CardFilter.Visibility = Visibility.Collapsed
+ VersionFilter = Nothing
+ Else
+ CardFilter.Visibility = Visibility.Visible
+ VersionFilters.Insert(0, "全部")
+ '转化为按钮
+ For Each Version As String In VersionFilters
+ Dim NewButton As New MyRadioButton With {
+ .Text = Version, .Margin = New Thickness(2, 0, 2, 0), .ColorType = MyRadioButton.ColorState.Highlight}
+ NewButton.LabText.Margin = New Thickness(-2, 0, 8, 0)
+ AddHandler NewButton.Check,
+ Sub(sender As MyRadioButton, raiseByMouse As Boolean)
+ VersionFilter = If(sender.Text = "全部", Nothing, sender.Text)
+ UpdateFilterResult()
+ End Sub
+ PanFilter.Children.Add(NewButton)
+ Next
+ '自动选择
+ Dim ToCheck As MyRadioButton = Nothing
+ If TargetVersion <> "" Then
+ Dim TargetFile = CompFileLoader.Output.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
+ If Button.Text <> TargetGroup Then Continue For
+ ToCheck = Button
+ Exit For
+ Next
+ End If
+ End If
+ If ToCheck Is Nothing Then ToCheck = PanFilter.Children(0)
+ ToCheck.Checked = True
+ End If
+
+ '更新筛选结果(文件列表 UI 化)
+ UpdateFilterResult()
+ End Sub
+ Private Sub UpdateFilterResult()
Dim TargetCardName As String = If(TargetVersion <> "" OrElse TargetLoader <> CompModLoaderType.Any,
- $"所选版本:{TargetVersion} {If(TargetLoader <> CompModLoaderType.Any, TargetLoader, "")}", "")
- '初始化字典
- Dim Dict As New SortedDictionary(Of String, List(Of CompFile))(New VersionSorterWithSelect(TargetCardName))
- Dict.Add("未知版本", New List(Of CompFile))
+ $"所选版本:{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 GameVersion In Version.GameVersions
- '决定添加到哪个版本
- Dim TargetCard As String
- If GameVersion Is Nothing Then
- TargetCard = "未知版本"
- ElseIf GameVersion.Contains("w") OrElse GameVersion.Contains("pre") OrElse GameVersion.Contains("rc") Then
- TargetCard = "快照版本"
- ElseIf GameVersion.StartsWith("1.0") Then
- TargetCard = "远古版本"
- Else
- TargetCard = GameVersion
+ '检查是否符合版本筛选器
+ If VersionFilter IsNot Nothing AndAlso
+ GetGroupedVersionName(GameVersion, IsMajorVersionFilter, True) <> VersionFilter Then Continue For
+ '决定添加到哪个卡片
+ Dim Ver 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 '不是 “快照版本” 之类的
+ 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 & " ")
+ Next
End If
- '实际进行添加
- If Not Dict.ContainsKey(TargetCard) Then Dict.Add(TargetCard, New List(Of CompFile))
- If Not Dict(TargetCard).Contains(Version) Then Dict(TargetCard).Add(Version)
+ If Not Loaders.Any() Then Loaders.Add("") '保底加一个空的,确保它在一张卡片里
+ '实际添加
+ For Each Loader In Loaders
+ Dim TargetCard As String = Loader & Ver
+ 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
'添加筛选的版本的卡片
@@ -77,16 +156,16 @@
For Each Version As CompFile In CompFileLoader.Output
If Version.GameVersions.Contains(TargetVersion) AndAlso
(TargetLoader = CompModLoaderType.Any OrElse Version.ModLoaders.Contains(TargetLoader)) Then
+ '检查是否符合版本筛选器
+ If VersionFilter IsNot Nothing AndAlso
+ Not Version.GameVersions.Any(Function(v) GetGroupedVersionName(v, IsMajorVersionFilter, True) = VersionFilter) Then Continue For
If Not Dict(TargetCardName).Contains(Version) Then Dict(TargetCardName).Add(Version)
End If
Next
End If
-
-#Region "转化为 UI"
+ '转化为 UI
Try
- '清空当前
- PanMain.Children.Clear()
- '转化为 UI
+ PanResults.Children.Clear()
For Each Pair As KeyValuePair(Of String, List(Of CompFile)) In Dict
If Not Pair.Value.Any() Then Continue For
'增加卡片
@@ -94,7 +173,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
- PanMain.Children.Add(NewCard)
+ PanResults.Children.Add(NewCard)
'确定卡片是否展开
If Pair.Key = TargetCardName OrElse
(FrmMain.PageCurrent.Additional IsNot Nothing AndAlso '#2761
@@ -104,20 +183,29 @@
NewCard.IsSwaped = True
End If
'增加提示
- If Pair.Key = "未知版本" Then
- NewStack.Children.Add(New MyHint With {.Text = "由于 API 的版本信息更新缓慢,可能无法识别刚更新不久的 MC 版本,只需等待几天即可自动恢复正常。", .IsWarn = False, .Margin = New Thickness(0, 0, 0, 7)})
+ If Pair.Key = "其他版本" Then
+ NewStack.Children.Add(New MyHint With {.Text = "由于版本信息更新缓慢,可能无法识别刚更新的 MC 版本,只需等待几天即可自动恢复正常。", .IsWarn = False, .Margin = New Thickness(0, 0, 0, 7)})
End If
Next
'如果只有一张卡片,展开第一张卡片
- If PanMain.Children.Count = 1 Then
- CType(PanMain.Children(0), MyCard).IsSwaped = False
+ If PanResults.Children.Count = 1 Then
+ CType(PanResults.Children(0), MyCard).IsSwaped = False
End If
Catch ex As Exception
Log(ex, "可视化工程下载列表出错", LogLevel.Feedback)
End Try
-#End Region
-
End Sub
+ Private Function GetGroupedVersionName(Name As String, MajorOnly As Boolean, FoldOldRelease As Boolean) As String
+ If Name Is Nothing Then
+ Return "其他版本"
+ ElseIf Name.Contains("w") Then
+ Return "快照版本"
+ ElseIf Name.StartsWith("1.0") OrElse Not Name.StartsWith("1.") OrElse (FoldOldRelease AndAlso Val(Name.Split(".")(1)) < 10) Then
+ Return "远古版本"
+ Else
+ Return If(MajorOnly, "1." & Name.Split(".")(1).BeforeFirst(" "), Name)
+ End If
+ End Function
#End Region
@@ -167,14 +255,10 @@
'构造步骤加载器
Dim Loaders As New List(Of LoaderBase)
Dim Target As String = $"{PathMcFolder}versions\{VersionName}\原始整合包.{If(Project.FromCurseForge, "zip", "mrpack")}"
- Dim LogoFileAddress As String = PathTemp & "CompLogo\" & GetHash(CompItem.Logo) & ".png"
+ Dim LogoFileAddress As String = MyImage.GetTempPath(CompItem.Logo)
Loaders.Add(New LoaderDownload("下载整合包文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 10, .Block = True})
Loaders.Add(New LoaderTask(Of Integer, Integer)("准备安装整合包",
- Sub()
- If ModpackInstall(Target, VersionName, Logo:=If(IO.File.Exists(LogoFileAddress), LogoFileAddress, Nothing)) Is Nothing Then
- Throw New Exception("整合包安装出现异常!")
- End If
- End Sub) With {.ProgressWeight = 0.1})
+ Sub() ModpackInstall(Target, VersionName, If(IO.File.Exists(LogoFileAddress), LogoFileAddress, Nothing))) With {.ProgressWeight = 0.1})
'启动
Dim Loader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged =
@@ -235,7 +319,7 @@
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.Split(".")(1) = Version.Version.McCodeMain.ToString) Then Return False
+ 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
@@ -283,14 +367,16 @@
If Project.TranslatedName = Project.RawName Then
FileName = File.FileName
Else
- Dim ChineseName As String = Project.TranslatedName.Before(" (").Before(" - ").
+ Dim ChineseName As String = Project.TranslatedName.BeforeFirst(" (").BeforeFirst(" - ").
Replace("\", "\").Replace("/", "/").Replace("|", "|").Replace(":", ":").Replace("<", "<").Replace(">", ">").Replace("*", "*").Replace("?", "?").Replace("""", "").Replace(": ", ":")
- Select Case Setup.Get("ToolDownloadTranslate")
+ Select Case Setup.Get("ToolDownloadTranslateV2")
Case 0
- FileName = $"[{ChineseName}] {File.FileName}"
+ FileName = $"【{ChineseName}】{File.FileName}"
Case 1
- FileName = $"{ChineseName}-{File.FileName}"
+ FileName = $"[{ChineseName}] {File.FileName}"
Case 2
+ FileName = $"{ChineseName}-{File.FileName}"
+ Case 3
FileName = $"{File.FileName}-{ChineseName}"
Case Else
FileName = File.FileName
@@ -300,11 +386,11 @@
Sub()
'弹窗要求选择保存位置
Dim Target As String
- Target = SelectAs("选择保存位置", FileName,
- Desc & "文件|" &
- If(Project.Type = CompType.Mod,
- If(File.FileName.EndsWith(".litemod"), "*.litemod", "*.jar"),
- If(File.FileName.EndsWith(".mrpack"), "*.mrpack", "*.zip")), DefaultFolder)
+ Target = SelectSaveFile("选择保存位置", FileName,
+ Desc & "文件|" &
+ If(Project.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
'构造步骤加载器
Dim LoaderName As String = Desc & "下载:" & GetFileNameWithoutExtentionFromPath(Target) & " "
@@ -312,7 +398,7 @@
Dim Loaders As New List(Of LoaderBase)
Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True})
'启动
- Dim Loader As New LoaderCombo(Of Integer)(LoaderName, Loaders) With {.OnStateChanged = AddressOf DownloadStateSave}
+ Dim Loader As New LoaderCombo(Of Integer)(LoaderName, Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
Loader.Start(1)
LoaderTaskbarAdd(Loader)
FrmMain.BtnExtraDownload.ShowRefresh()
@@ -331,7 +417,7 @@
OpenWebsite("https://www.mcmod.cn/class/" & Project.WikiId & ".html")
End Sub
Private Sub BtnIntroCopy_Click(sender As Object, e As EventArgs) Handles BtnIntroCopy.Click
- ClipboardSet(CompItem.LabTitle.Text)
+ ClipboardSet(CompItem.LabTitle.Text & CompItem.LabTitleRaw.Text)
End Sub
End Class
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb
index 354fe86..38ba113 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadForge.xaml.vb
@@ -10,12 +10,10 @@
Private Sub Load_OnFinish()
'结果数据化
Try
- '排序
- Dim Versions = Sort(DlForgeListLoader.Output.Value, AddressOf VersionSortBoolean)
'清空当前
PanMain.Children.Clear()
'转化为 UI
- For Each Version As String In Versions
+ 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}
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
index 6c7a800..124a1d7 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
@@ -5,66 +5,31 @@
mc:Ignorable="d" x:Class="PageDownloadInstall"
PanScroll="{Binding ElementName=PanBack}">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -77,12 +42,12 @@
+ 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" />
-
+
-
+
@@ -95,12 +60,12 @@
+ 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" />
-
+
-
+
@@ -113,12 +78,12 @@
+ 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" />
-
+
-
+
@@ -131,14 +96,14 @@
+ 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" />
-
+
-
+
@@ -151,12 +116,12 @@
+ 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" />
-
+
-
+
@@ -169,12 +134,12 @@
+ 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" />
-
+
-
+
@@ -187,12 +152,16 @@
+ 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" />
-
-
-
+
+
+
+
+
@@ -205,3 +174,26 @@
+
+
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
index 2230ba6..8e9547a 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
@@ -1,7 +1,8 @@
Public Class PageDownloadInstall
Private Sub LoaderInit() Handles Me.Initialized
- PageLoaderInit(LoadMinecraft, PanLoad, PanBack, Nothing, DlClientListLoader, AddressOf LoadMinecraft_OnFinish)
+ DisabledPageAnimControls.Add(BtnStart)
+ PageLoaderInit(LoadMinecraft, PanLoad, PanAllBack, Nothing, DlClientListLoader, AddressOf LoadMinecraft_OnFinish)
End Sub
Private IsLoad As Boolean = False
@@ -40,6 +41,8 @@
If IsInSelectPage Then Exit Sub
IsInSelectPage = True
+ PanInner.Margin = New Thickness(25, 10, 25, 40)
+
AutoSelectedFabricApi = False
AutoSelectedOptiFabric = False
IsSelectNameEdited = False
@@ -49,7 +52,8 @@
PanBack.IsHitTestVisible = False
PanBack.ScrollToHome()
- CardMinecraft.IsSwaped = True
+ DisabledPageAnimControls.Remove(BtnStart)
+ BtnStart.Show = True
CardOptiFine.IsSwaped = True
CardLiteLoader.IsSwaped = True
CardForge.IsSwaped = True
@@ -64,9 +68,11 @@
End If
'如果在选择页面按了刷新键,选择页的东西可能会由于动画被隐藏,但不会由于加载结束而再次显示,因此这里需要手动恢复
- For Each Card In GetAllAnimControls(PanSelect)
- Card.Opacity = 1
- Card.RenderTransform = New TranslateTransform
+ For Each Control In GetAllAnimControls(PanSelect)
+ Control.Opacity = 1
+ If Control.RenderTransform Is Nothing OrElse TypeOf Control.RenderTransform Is TranslateTransform Then
+ Control.RenderTransform = New TranslateTransform
+ End If
Next
'启动 Forge 加载
@@ -81,8 +87,8 @@
DlOptiFabricLoader.Start()
AniStart({
- AaOpacity(PanMinecraft, -PanMinecraft.Opacity, 100, 10),
- AaTranslateX(PanMinecraft, -50 - CType(PanMinecraft.RenderTransform, TranslateTransform).X, 110, 10),
+ AaOpacity(PanMinecraft, -PanMinecraft.Opacity, 90, 10),
+ AaTranslateX(PanMinecraft, -50 - CType(PanMinecraft.RenderTransform, TranslateTransform).X, 100, 10),
AaCode(
Sub()
PanBack.ScrollToHome()
@@ -96,8 +102,8 @@
OptiFabric_Loaded()
SelectReload()
End Sub, After:=True),
- AaOpacity(PanSelect, 1 - PanSelect.Opacity, 250, 150),
- AaTranslateX(PanSelect, -CType(PanSelect.RenderTransform, TranslateTransform).X, 500, 150, Ease:=New AniEaseOutBack(AniEasePower.Weak)),
+ AaOpacity(PanSelect, 1 - PanSelect.Opacity, 90, 100),
+ AaTranslateX(PanSelect, -CType(PanSelect.RenderTransform, TranslateTransform).X, 200, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)),
AaCode(
Sub()
PanMinecraft.Visibility = Visibility.Collapsed
@@ -115,10 +121,14 @@
End Sub,, True)
}, "FrmDownloadInstall SelectPageSwitch", True)
End Sub
- Public Sub ExitSelectPage()
+ Public Sub ExitSelectPage() Handles BtnBack.Click
If Not IsInSelectPage Then Exit Sub
IsInSelectPage = False
+ PanInner.Margin = New Thickness(25, 10, 25, 25)
+
+ DisabledPageAnimControls.Add(BtnStart)
+ BtnStart.Show = False
SelectClear() '清除已选择项
PanMinecraft.Visibility = Visibility.Visible
PanSelect.IsHitTestVisible = False
@@ -130,26 +140,21 @@
AaOpacity(PanSelect, -PanSelect.Opacity, 90, 10),
AaTranslateX(PanSelect, 50 - CType(PanSelect.RenderTransform, TranslateTransform).X, 100, 10),
AaCode(Sub() PanBack.ScrollToHome(), After:=True),
- AaOpacity(PanMinecraft, 1 - PanMinecraft.Opacity, 150, 100),
- AaTranslateX(PanMinecraft, -CType(PanMinecraft.RenderTransform, TranslateTransform).X, 400, 100, Ease:=New AniEaseOutBack(AniEasePower.Weak)),
- AaCode(Sub()
- PanSelect.Visibility = Visibility.Collapsed
- PanBack.IsHitTestVisible = True
- End Sub,, True)
+ AaOpacity(PanMinecraft, 1 - PanMinecraft.Opacity, 90, 100),
+ AaTranslateX(PanMinecraft, -CType(PanMinecraft.RenderTransform, TranslateTransform).X, 200, 100, Ease:=New AniEaseOutFluent(AniEasePower.ExtraStrong)),
+ AaCode(
+ Sub()
+ PanSelect.Visibility = Visibility.Collapsed
+ PanBack.IsHitTestVisible = True
+ End Sub,, True)
}, "FrmDownloadInstall SelectPageSwitch")
End Sub
-
- '页面切换触发
Public Sub MinecraftSelected(sender As MyListItem, e As MouseButtonEventArgs)
SelectedMinecraftId = sender.Title
SelectedMinecraftJsonUrl = sender.Tag("url").ToString
SelectedMinecraftIcon = sender.Logo
EnterSelectPage()
End Sub
- Private Sub CardMinecraft_PreviewSwap(sender As Object, e As RouteEventArgs) Handles CardMinecraft.PreviewSwap
- ExitSelectPage()
- e.Handled = True
- End Sub
#End Region
@@ -309,12 +314,7 @@
IsReloading = True
'主预览
SelectNameUpdate()
- ItemSelect.Title = TextSelectName.Text
- ItemSelect.Info = GetSelectInfo()
- ItemSelect.Logo = GetSelectLogo()
- 'Minecraft
- LabMinecraft.Text = SelectedMinecraftId
- ImgMinecraft.Source = New MyBitmap(SelectedMinecraftIcon)
+ ImgLogo.Source = GetSelectLogo()
'OptiFine
Dim OptiFineError As String = LoadOptiFineGetError()
CardOptiFine.MainSwap.Visibility = If(OptiFineError Is Nothing, Visibility.Visible, Visibility.Collapsed)
@@ -323,7 +323,7 @@
If SelectedOptiFine Is Nothing Then
BtnOptiFineClear.Visibility = Visibility.Collapsed
ImgOptiFine.Visibility = Visibility.Collapsed
- LabOptiFine.Text = If(OptiFineError, "点击选择")
+ LabOptiFine.Text = If(OptiFineError, "可以添加")
LabOptiFine.Foreground = ColorGray4
Else
BtnOptiFineClear.Visibility = Visibility.Visible
@@ -343,7 +343,7 @@
If SelectedLiteLoader Is Nothing Then
BtnLiteLoaderClear.Visibility = Visibility.Collapsed
ImgLiteLoader.Visibility = Visibility.Collapsed
- LabLiteLoader.Text = If(LiteLoaderError, "点击选择")
+ LabLiteLoader.Text = If(LiteLoaderError, "可以添加")
LabLiteLoader.Foreground = ColorGray4
Else
BtnLiteLoaderClear.Visibility = Visibility.Visible
@@ -360,7 +360,7 @@
If SelectedForge Is Nothing Then
BtnForgeClear.Visibility = Visibility.Collapsed
ImgForge.Visibility = Visibility.Collapsed
- LabForge.Text = If(ForgeError, "点击选择")
+ LabForge.Text = If(ForgeError, "可以添加")
LabForge.Foreground = ColorGray4
Else
BtnForgeClear.Visibility = Visibility.Visible
@@ -380,7 +380,7 @@
If SelectedNeoForge Is Nothing Then
BtnNeoForgeClear.Visibility = Visibility.Collapsed
ImgNeoForge.Visibility = Visibility.Collapsed
- LabNeoForge.Text = If(NeoForgeError, "点击选择")
+ LabNeoForge.Text = If(NeoForgeError, "可以添加")
LabNeoForge.Foreground = ColorGray4
Else
BtnNeoForgeClear.Visibility = Visibility.Visible
@@ -401,7 +401,7 @@
If SelectedFabric Is Nothing Then
BtnFabricClear.Visibility = Visibility.Collapsed
ImgFabric.Visibility = Visibility.Collapsed
- LabFabric.Text = If(FabricError, "点击选择")
+ LabFabric.Text = If(FabricError, "可以添加")
LabFabric.Foreground = ColorGray4
Else
BtnFabricClear.Visibility = Visibility.Visible
@@ -422,7 +422,7 @@
If SelectedFabricApi Is Nothing Then
BtnFabricApiClear.Visibility = Visibility.Collapsed
ImgFabricApi.Visibility = Visibility.Collapsed
- LabFabricApi.Text = If(FabricApiError, "点击选择")
+ LabFabricApi.Text = If(FabricApiError, "可以添加")
LabFabricApi.Foreground = ColorGray4
Else
BtnFabricApiClear.Visibility = Visibility.Visible
@@ -443,7 +443,7 @@
If SelectedOptiFabric Is Nothing Then
BtnOptiFabricClear.Visibility = Visibility.Collapsed
ImgOptiFabric.Visibility = Visibility.Collapsed
- LabOptiFabric.Text = If(OptiFabricError, "点击选择")
+ LabOptiFabric.Text = If(OptiFabricError, "可以添加")
LabOptiFabric.Foreground = ColorGray4
Else
BtnOptiFabricClear.Visibility = Visibility.Visible
@@ -495,7 +495,26 @@
SelectedOptiFabric = Nothing
End Sub
- '显示信息获取
+ '''
+ ''' 获取版本图标。
+ '''
+ Private Function GetSelectLogo() As String
+ If SelectedFabric IsNot Nothing Then
+ Return "pack://application:,,,/images/Blocks/Fabric.png"
+ ElseIf SelectedForge IsNot Nothing Then
+ Return "pack://application:,,,/images/Blocks/Anvil.png"
+ ElseIf SelectedNeoForge IsNot Nothing Then
+ Return "pack://application:,,,/images/Blocks/NeoForge.png"
+ ElseIf SelectedLiteLoader IsNot Nothing Then
+ Return "pack://application:,,,/images/Blocks/Egg.png"
+ ElseIf SelectedOptiFine IsNot Nothing Then
+ Return "pack://application:,,,/images/Blocks/GrassPath.png"
+ Else
+ Return SelectedMinecraftIcon
+ End If
+ End Function
+
+ '版本名处理
'''
''' 获取默认版本名。
'''
@@ -518,49 +537,6 @@
End If
Return Name
End Function
- '''
- ''' 获取版本描述信息。
- '''
- Private Function GetSelectInfo() As String
- Dim Info As String = ""
- If SelectedFabric IsNot Nothing Then
- Info += ", Fabric " & SelectedFabric.Replace("+build", "")
- End If
- If SelectedForge IsNot Nothing Then
- Info += ", Forge " & SelectedForge.VersionName
- End If
- If SelectedNeoForge IsNot Nothing Then
- Info += ", NeoForge " & SelectedNeoForge.VersionName
- End If
- If SelectedLiteLoader IsNot Nothing Then
- Info += ", LiteLoader"
- End If
- If SelectedOptiFine IsNot Nothing Then
- Info += ", OptiFine " & SelectedOptiFine.NameDisplay.Replace(SelectedMinecraftId & " ", "")
- End If
- If Info = "" Then Info = ", 无附加安装"
- Return Info.TrimStart(", ".ToCharArray())
- End Function
- '''
- ''' 获取版本图标。
- '''
- Private Function GetSelectLogo() As String
- If SelectedFabric IsNot Nothing Then
- Return "pack://application:,,,/images/Blocks/Fabric.png"
- ElseIf SelectedForge IsNot Nothing Then
- Return "pack://application:,,,/images/Blocks/Anvil.png"
- ElseIf SelectedNeoForge IsNot Nothing Then
- Return "pack://application:,,,/images/Blocks/NeoForge.png"
- ElseIf SelectedLiteLoader IsNot Nothing Then
- Return "pack://application:,,,/images/Blocks/Egg.png"
- ElseIf SelectedOptiFine IsNot Nothing Then
- Return "pack://application:,,,/images/Blocks/GrassPath.png"
- Else
- Return SelectedMinecraftIcon
- End If
- End Function
-
- '版本名处理
Private IsSelectNameEdited As Boolean = False
Private IsSelectNameChanging As Boolean = False
Private Sub SelectNameUpdate()
@@ -575,7 +551,7 @@
SelectReload()
End Sub
Private Sub TextSelectName_ValidateChanged(sender As Object, e As EventArgs) Handles TextSelectName.ValidateChanged
- BtnSelectStart.IsEnabled = TextSelectName.ValidateResult = ""
+ BtnStart.IsEnabled = TextSelectName.IsValidated
End Sub
#End Region
@@ -614,7 +590,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")))
@@ -635,10 +611,8 @@
Dict(Type).Add(Version)
Next
'排序
- For i = 0 To Dict.Keys.Count - 1
- Dict(Dict.Keys(i)) = Sort(Dict.Values(i), Function(Left As JObject, Right As JObject) As Boolean
- Return Left("releaseTime").Value(Of Date) > Right("releaseTime").Value(Of Date)
- End Function)
+ For Each Pair In Dict.ToList
+ Dict(Pair.Key) = Pair.Value.OrderByDescending(Function(j) j("releaseTime").Value(Of Date)).ToList
Next
'清空当前
PanMinecraft.Children.Clear()
@@ -695,46 +669,41 @@
'''
Private Function LoadOptiFineGetError() As String
If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容"
- If LoadOptiFine Is Nothing OrElse LoadOptiFine.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
+ If LoadOptiFine Is Nothing OrElse LoadOptiFine.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……"
If LoadOptiFine.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadOptiFine.State, Object).Error.Message
'检查 Forge 1.13 - 1.14.3:全部不兼容
If SelectedForge IsNot Nothing AndAlso
VersionSortInteger(SelectedMinecraftId, "1.13") >= 0 AndAlso VersionSortInteger("1.14.3", SelectedMinecraftId) >= 0 Then
Return "与 Forge 不兼容"
End If
- '检查最低 Forge 版本
- Dim MinimalForgeVersion As String = "9999.9999"
- Dim NotSuitForForge As Boolean = False
+ '检查 Forge 版本
+ Dim HasAny As Boolean = False
+ Dim HasRequiredVersion As Boolean = False
For Each OptiFineVersion As DlOptiFineListEntry In DlOptiFineListLoader.Output.Value
If Not OptiFineVersion.NameDisplay.StartsWith(SelectedMinecraftId & " ") Then Continue For '不是同一个大版本
- If SelectedForge Is Nothing Then Return Nothing '该版本可用
- If IsOptiFineSuitForForge(OptiFineVersion, SelectedForge) Then
- Return Nothing '该版本可用
- Else
- NotSuitForForge = True
- If OptiFineVersion.RequiredForgeVersion IsNot Nothing Then
- '设置用于显示的最低允许的 Forge 版本
- MinimalForgeVersion = If(VersionSortBoolean(MinimalForgeVersion, OptiFineVersion.RequiredForgeVersion),
- OptiFineVersion.RequiredForgeVersion, MinimalForgeVersion)
- End If
- End If
+ HasAny = True
+ If SelectedForge Is Nothing Then Return Nothing '未选择 Forge
+ If IsOptiFineSuitForForge(OptiFineVersion, SelectedForge) Then Return Nothing '该版本可用
+ If OptiFineVersion.RequiredForgeVersion IsNot Nothing Then HasRequiredVersion = True
Next
- If MinimalForgeVersion = "9999.9999" Then
- Return If(NotSuitForForge, "与 Forge 不兼容", "没有可用版本")
+ If Not HasAny Then
+ Return "不可用"
+ ElseIf HasRequiredVersion Then
+ Return "仅兼容特定版本的 Forge"
Else
- Return "需要 Forge " & If(MinimalForgeVersion.Contains("."), "", "#") & MinimalForgeVersion & " 或更高版本"
+ Return "与 Forge 不兼容"
End If
End Function
- '检查某个 OptiFine 是否与某个 Forge 兼容(最低 Forge 版本是否达到需求)
+ '检查某个 OptiFine 是否与某个 Forge 兼容
Private Function IsOptiFineSuitForForge(OptiFine As DlOptiFineListEntry, Forge As DlForgeVersionEntry)
If Forge.Inherit <> OptiFine.Inherit Then Return False '不是同一个大版本
If OptiFine.RequiredForgeVersion Is Nothing Then Return False '不兼容 Forge
If String.IsNullOrWhiteSpace(OptiFine.RequiredForgeVersion) Then Return True '#4183
If OptiFine.RequiredForgeVersion.Contains(".") Then 'XX.X.XXX
- Return VersionSortInteger(Forge.Version.ToString, OptiFine.RequiredForgeVersion) >= 0
+ Return VersionSortInteger(Forge.Version.ToString, OptiFine.RequiredForgeVersion) = 0
Else 'XXXX
- Return Forge.Version.Revision >= OptiFine.RequiredForgeVersion
+ Return Forge.Version.Revision = OptiFine.RequiredForgeVersion
End If
End Function
@@ -758,7 +727,7 @@
Next
If Not Versions.Any() Then Exit Sub
'排序
- Versions = Sort(Versions,
+ Versions = Versions.Sort(
Function(Left As DlOptiFineListEntry, Right As DlOptiFineListEntry) As Boolean
If Not Left.IsPreview AndAlso Right.IsPreview Then Return True
If Left.IsPreview AndAlso Not Right.IsPreview Then Return False
@@ -787,6 +756,7 @@
Private Sub OptiFine_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnOptiFineClear.MouseLeftButtonUp
SelectedOptiFine = Nothing
SelectedOptiFabric = Nothing
+ AutoSelectedOptiFabric = False
CardOptiFine.IsSwaped = True
e.Handled = True
Forge_Loaded()
@@ -802,13 +772,13 @@
''' 获取 LiteLoader 的加载异常信息。若正常则返回 Nothing。
'''
Private Function LoadLiteLoaderGetError() As String
- If Not SelectedMinecraftId.Contains("1.") OrElse Val(SelectedMinecraftId.Split(".")(1)) > 12 Then Return "没有可用版本"
- If LoadLiteLoader Is Nothing OrElse LoadLiteLoader.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
+ If Not SelectedMinecraftId.Contains("1.") OrElse Val(SelectedMinecraftId.Split(".")(1)) > 12 Then Return "不可用"
+ If LoadLiteLoader Is Nothing OrElse LoadLiteLoader.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……"
If LoadLiteLoader.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadLiteLoader.State, Object).Error.Message
For Each Version As DlLiteLoaderListEntry In DlLiteLoaderListLoader.Output.Value
If Version.Inherit = SelectedMinecraftId Then Return Nothing
Next
- Return "没有可用版本"
+ Return "不可用"
End Function
'限制展开
@@ -859,15 +829,15 @@
''' 获取 Forge 的加载异常信息。若正常则返回 Nothing。
'''
Private Function LoadForgeGetError() As String
- If Not SelectedMinecraftId.StartsWith("1.") Then Return "没有可用版本"
- If Not LoadForge.State.IsLoader Then Return "正在获取版本列表……"
+ If Not SelectedMinecraftId.StartsWith("1.") Then Return "不可用"
+ If Not LoadForge.State.IsLoader Then Return "加载中……"
Dim Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)) = LoadForge.State
- If SelectedMinecraftId <> Loader.Input Then Return "正在获取版本列表……"
- If Loader.State = LoadState.Loading Then Return "正在获取版本列表……"
+ If SelectedMinecraftId <> Loader.Input Then Return "加载中……"
+ If Loader.State = LoadState.Loading Then Return "加载中……"
If Loader.State = LoadState.Failed Then
Dim ErrorMessage As String = Loader.Error.Message
- If ErrorMessage.Contains("没有可用版本") Then
- Return "没有可用版本"
+ If ErrorMessage.Contains("不可用") Then
+ Return "不可用"
Else
Return "获取版本列表失败:" & ErrorMessage
End If
@@ -909,12 +879,12 @@
Dim Versions = Loader.Output.ToList '复制数组,以免 Output 在实例化后变空
If Not Loader.Output.Any() Then Exit Sub
PanForge.Children.Clear()
- Versions = Sort(Versions, Function(a, b) a.Version > b.Version).Where(
+ Versions = Versions.Where(
Function(v)
If v.Category = "universal" OrElse v.Category = "client" Then Return False '跳过无法自动安装的版本
If SelectedOptiFine IsNot Nothing AndAlso Not IsOptiFineSuitForForge(SelectedOptiFine, v) Then Return False
Return True
- End Function).ToList()
+ End Function).OrderByDescending(Function(v) v.Version).ToList()
ForgeDownloadListItemPreload(PanForge, Versions, AddressOf Forge_Selected, False)
For Each Version In Versions
PanForge.Children.Add(ForgeDownloadListItem(Version, AddressOf Forge_Selected, False))
@@ -948,16 +918,16 @@
''' 获取 NeoForge 的加载异常信息。若正常则返回 Nothing。
'''
Private Function LoadNeoForgeGetError() As String
- If Not SelectedMinecraftId.StartsWith("1.") Then Return "没有可用版本"
+ If Not SelectedMinecraftId.StartsWith("1.") Then Return "不可用"
If SelectedOptiFine IsNot Nothing Then Return "与 OptiFine 不兼容"
If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容"
If SelectedFabric IsNot Nothing Then Return "与 Fabric 不兼容"
- If LoadNeoForge Is Nothing OrElse LoadNeoForge.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
+ If LoadNeoForge Is Nothing OrElse LoadNeoForge.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……"
If LoadNeoForge.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadNeoForge.State, Object).Error.Message
If DlNeoForgeListLoader.Output.Value.Any(Function(v) v.Inherit = SelectedMinecraftId) Then
Return Nothing
Else
- Return "没有可用版本"
+ Return "不可用"
End If
End Function
@@ -1009,7 +979,7 @@
''' 获取 Fabric 的加载异常信息。若正常则返回 Nothing。
'''
Private Function LoadFabricGetError() As String
- If LoadFabric Is Nothing OrElse LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
+ 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
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
@@ -1018,7 +988,7 @@
Return Nothing
End If
Next
- Return "没有可用版本"
+ Return "不可用"
End Function
'限制展开
@@ -1056,7 +1026,9 @@
Private Sub Fabric_Clear(sender As Object, e As MouseButtonEventArgs) Handles BtnFabricClear.MouseLeftButtonUp
SelectedFabric = Nothing
SelectedFabricApi = Nothing
+ AutoSelectedFabricApi = False
SelectedOptiFabric = Nothing
+ AutoSelectedOptiFabric = False
CardFabric.IsSwaped = True
e.Handled = True
SelectReload()
@@ -1076,12 +1048,12 @@
If DisplayName.StartsWith("[" & MinecraftVersion & "]") Then Return True
If Not DisplayName.Contains("/") OrElse Not DisplayName.Contains("]") Then Return False
'直接的判断(例如 1.18.1/22w03a)
- For Each Part As String In DisplayName.Before("]").TrimStart("[").Split("/")
+ For Each Part As String In DisplayName.BeforeFirst("]").TrimStart("[").Split("/")
If Part = MinecraftVersion Then Return True
Next
'将版本名分割语素(例如 1.16.4/5)
- Dim Lefts = RegexSearch(DisplayName.Before("]"), "[a-z/]+|[0-9/]+")
- Dim Rights = RegexSearch(MinecraftVersion.Before("]"), "[a-z/]+|[0-9/]+")
+ Dim Lefts = RegexSearch(DisplayName.BeforeFirst("]"), "[a-z/]+|[0-9/]+")
+ Dim Rights = RegexSearch(MinecraftVersion.BeforeFirst("]"), "[a-z/]+|[0-9/]+")
'对每段进行判断
Dim i As Integer = 0
While True
@@ -1109,18 +1081,18 @@
''' 获取 FabricApi 的加载异常信息。若正常则返回 Nothing。
'''
Private Function LoadFabricApiGetError() As String
- If LoadFabricApi Is Nothing OrElse LoadFabricApi.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
+ If LoadFabricApi Is Nothing OrElse LoadFabricApi.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……"
If LoadFabricApi.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabricApi.State, Object).Error.Message
If DlFabricApiLoader.Output Is Nothing Then
If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
- Return "正在获取版本列表……"
+ Return "加载中……"
End If
For Each Version In DlFabricApiLoader.Output
If Not IsSuitableFabricApi(Version.DisplayName, SelectedMinecraftId) Then Continue For
If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
Return Nothing
Next
- Return "没有可用版本"
+ Return "不可用"
End Function
'限制展开
@@ -1148,7 +1120,7 @@
End If
Next
If Not Versions.Any() Then Exit Sub
- Versions = Sort(Versions, Function(a, b) a.ReleaseDate > b.ReleaseDate)
+ Versions = Versions.OrderByDescending(Function(v) v.ReleaseDate).ToList
'可视化
PanFabricApi.Children.Clear()
For Each Version In Versions
@@ -1202,13 +1174,13 @@
'''
Private Function LoadOptiFabricGetError() As String
If SelectedMinecraftId.StartsWith("1.14") OrElse SelectedMinecraftId.StartsWith("1.15") Then Return "不兼容老版本 Fabric,请手动下载 OptiFabric Origins"
- If LoadOptiFabric Is Nothing OrElse LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "正在获取版本列表……"
+ If LoadOptiFabric Is Nothing OrElse LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……"
If LoadOptiFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadOptiFabric.State, Object).Error.Message
If DlOptiFabricLoader.Output Is Nothing Then
If SelectedFabric Is Nothing AndAlso SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine 与 Fabric"
If SelectedFabric Is Nothing Then Return "需要安装 Fabric"
If SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine"
- Return "正在获取版本列表……"
+ Return "加载中……"
End If
For Each Version In DlOptiFabricLoader.Output
If Not IsSuitableOptiFabric(Version, SelectedMinecraftId) Then Continue For '2135#
@@ -1217,7 +1189,7 @@
If SelectedOptiFine Is Nothing Then Return "需要安装 OptiFine"
Return Nothing '通过检查
Next
- Return "没有可用版本"
+ Return "不可用"
End Function
'限制展开
@@ -1239,7 +1211,7 @@
Next
If Not Versions.Any() Then Exit Sub
'排序
- Versions = Sort(Versions, Function(a, b) a.ReleaseDate > b.ReleaseDate)
+ Versions = Versions.OrderByDescending(Function(v) v.ReleaseDate).ToList
'可视化
PanOptiFabric.Children.Clear()
For Each Version In Versions
@@ -1276,22 +1248,23 @@
#Region "安装"
Private Sub TextSelectName_KeyDown(sender As Object, e As KeyEventArgs) Handles TextSelectName.KeyDown
- If e.Key = Key.Enter AndAlso BtnSelectStart.IsEnabled Then BtnSelectStart_Click()
+ If e.Key = Key.Enter AndAlso BtnStart.IsEnabled Then BtnStart_Click()
End Sub
- Private Sub BtnSelectStart_Click() Handles BtnSelectStart.Click
+ Private Sub BtnStart_Click() Handles BtnStart.Click
'确认版本隔离
If (SelectedForge IsNot Nothing OrElse SelectedNeoForge IsNot Nothing OrElse SelectedFabric IsNot Nothing) AndAlso
- (Setup.Get("LaunchArgumentIndie") = 0 OrElse Setup.Get("LaunchArgumentIndie") = 2) Then
- If MyMsgBox("你尚未开启版本隔离,这会导致多个 MC 共用同一个 Mod 文件夹。" & vbCrLf &
- "因此在切换 MC 版本时,MC 会因为读取到与当前版本不符的 Mod 而崩溃。" & vbCrLf &
- "PCL 推荐你在开始下载前,在 设置 → 版本隔离 中开启版本隔离选项!", "版本隔离提示", "取消下载", "继续") = 1 Then
+ (Setup.Get("LaunchArgumentIndieV2") = 0 OrElse Setup.Get("LaunchArgumentIndieV2") = 2) Then
+ If MyMsgBox("你尚未开启版本隔离,这会导致多个版本共用同一个 Mod 文件夹。" & vbCrLf &
+ "因此游戏可能会因为读取到与当前版本不符的 Mod 而崩溃。" & vbCrLf &
+ "推荐在开始下载前,在 设置 → 启动选项 → 版本隔离 中开启版本隔离!", "版本隔离提示", "取消下载", "继续") = 1 Then
Exit Sub
End If
End If
'提交安装申请
+ Dim VersionName As String = TextSelectName.Text
Dim Request As New McInstallRequest With {
- .TargetVersionName = TextSelectName.Text,
- .TargetVersionFolder = $"{PathMcFolder}versions\{TextSelectName.Text}\",
+ .TargetVersionName = VersionName,
+ .TargetVersionFolder = $"{PathMcFolder}versions\{VersionName}\",
.MinecraftJson = SelectedMinecraftJsonUrl,
.MinecraftName = SelectedMinecraftId,
.OptiFineEntry = SelectedOptiFine,
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml
index 1031d8b..8b547ad 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml
@@ -2,73 +2,74 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PCL" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}">
-
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- -->
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- -->
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- -->
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -84,6 +85,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -93,5 +118,6 @@
-
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb
index 91888f0..dc89486 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadLeft.xaml.vb
@@ -1,4 +1,5 @@
Public Class PageDownloadLeft
+ Implements IRefreshable
#Region "页面切换"
@@ -10,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, ItemShader.Check
+ 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, ItemShader.Check, ItemResourcePack.Check
'尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0
'若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False)
If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag))
@@ -49,6 +50,9 @@
Case FormMain.PageSubType.DownloadShader
If FrmDownloadShader Is Nothing Then FrmDownloadShader = New PageDownloadShader
Return FrmDownloadShader
+ Case FormMain.PageSubType.DownloadResourcePack
+ If FrmDownloadResourcePack Is Nothing Then FrmDownloadResourcePack = New PageDownloadResourcePack
+ Return FrmDownloadResourcePack
Case Else
Throw New Exception("未知的下载子页面种类:" & ID)
End Select
@@ -64,7 +68,7 @@
PageChangeRun(PageGet(ID))
PageID = ID
Catch ex As Exception
- Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+ Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
Finally
AniControlEnabled -= 1
End Try
@@ -94,7 +98,13 @@
'强制刷新
Public Sub Refresh(sender As Object, e As EventArgs) '由边栏按钮匿名调用
- Select Case Val(sender.Tag)
+ Refresh(Val(sender.Tag))
+ End Sub
+ Public Sub Refresh() Implements IRefreshable.Refresh
+ Refresh(FrmMain.PageCurrentSub)
+ End Sub
+ Public Sub Refresh(SubType As FormMain.PageSubType)
+ Select Case SubType
Case FormMain.PageSubType.DownloadInstall
DlClientListLoader.Start(IsForceRestart:=True)
DlOptiFineListLoader.Start(IsForceRestart:=True)
@@ -109,12 +119,14 @@
PageDownloadMod.Storage = New CompProjectStorage
PageDownloadMod.Page = 0
CompProjectCache.Clear()
+ CompFilesCache.Clear()
If FrmDownloadMod IsNot Nothing Then FrmDownloadMod.PageLoaderRestart()
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()
ItemPack.Checked = True
Case FormMain.PageSubType.DownloadShader
@@ -123,6 +135,12 @@
CompProjectCache.Clear()
If FrmDownloadShader IsNot Nothing Then FrmDownloadShader.PageLoaderRestart()
ItemShader.Checked = True
+ Case FormMain.PageSubType.DownloadResourcePack
+ PageDownloadResourcePack.Storage = New CompProjectStorage
+ PageDownloadResourcePack.Page = 0
+ CompProjectCache.Clear()
+ If FrmDownloadResourcePack IsNot Nothing Then FrmDownloadResourcePack.PageLoaderRestart()
+ ItemResourcePack.Checked = True
Case FormMain.PageSubType.DownloadClient
DlClientListLoader.Start(IsForceRestart:=True)
ItemClient.Checked = True
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml
index 978b37d..822a136 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml
@@ -33,20 +33,18 @@
+
+
-
-
-
-
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb
index db3f168..113ff25 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadMod.xaml.vb
@@ -1,24 +1,62 @@
Public Class PageDownloadMod
Public Const PageSize = 40
+ '''
+ ''' 在切换到该页面时自动设置的目标版本。
+ '''
+ Public Shared TargetVersion As McVersion = Nothing
'加载器信息
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 Sub PageDownloadMod_Inited(sender As Object, e As EventArgs) Handles Me.Initialized
+ Private IsLoaderInited As Boolean = False
+ Private Sub PageDownloadMod_Inited(sender As Object, e As EventArgs) Handles Me.Loaded
+ '不知道从 Initialized 改成 Loaded 会不会有问题,但用 Initialized 会导致初始的筛选器修改被覆盖回默认值
+ If TargetVersion IsNot Nothing Then
+ '设置目标
+ ResetFilter() '重置筛选器
+ TextSearchVersion.Text = TargetVersion.Version.McName
+ Dim GetTargetItemByName =
+ Function(Name As String) As MyComboBoxItem
+ For Each Item As MyComboBoxItem In ComboSearchLoader.Items
+ If Item.Content = Name Then Return Item
+ Next
+ Return ComboSearchLoader.Items(0)
+ End Function
+ If TargetVersion.Version.HasForge Then
+ ComboSearchLoader.SelectedItem = GetTargetItemByName("Forge")
+ ElseIf TargetVersion.Version.HasFabric Then
+ ComboSearchLoader.SelectedItem = GetTargetItemByName("Fabric")
+ ElseIf TargetVersion.Version.HasNeoForge Then
+ ComboSearchLoader.SelectedItem = GetTargetItemByName("NeoForge")
+ End If
+ TargetVersion = Nothing
+ '如果已经完成请求,则重新开始
+ If IsLoaderInited Then StartNewSearch()
+ PanScroll.ScrollToHome()
+ End If
+ '加载器初始化
+ If IsLoaderInited Then Return
+ IsLoaderInited = True
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 = If(FrmDownloadMod.TextSearchVersion.Text = "全部 (也可自行输入)", Nothing,
- If(FrmDownloadMod.TextSearchVersion.Text.Contains(".") OrElse FrmDownloadMod.TextSearchVersion.Text.Contains("w"), FrmDownloadMod.TextSearchVersion.Text, Nothing))
+ .GameVersion = GameVersion
.Tag = FrmDownloadMod.ComboSearchTag.SelectedItem.Tag
- .ModLoader = Val(FrmDownloadMod.ComboSearchLoader.SelectedItem.Tag)
+ .ModLoader = ModLoader
.Source = CType(Val(FrmDownloadMod.ComboSearchSource.SelectedItem.Tag), CompSourceType)
End With
End If
@@ -112,7 +150,7 @@
End Sub
'重置按钮
- Private Sub BtnSearchReset_Click(sender As Object, e As EventArgs) Handles BtnSearchReset.Click
+ Private Sub ResetFilter() Handles BtnSearchReset.Click
TextSearchName.Text = ""
TextSearchVersion.Text = "全部 (也可自行输入)"
TextSearchVersion.SelectedIndex = 0
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml
index 1ca5eb0..1a08b51 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml
@@ -8,14 +8,12 @@
-
+
-
-
-
+
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb
index 27c2fd4..f843529 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadOptiFine.xaml.vb
@@ -49,8 +49,5 @@
Private Sub BtnWeb_Click(sender As Object, e As EventArgs) Handles BtnWeb.Click
OpenWebsite("https://www.optifine.net/")
End Sub
- Private Sub BtnChina_Click(sender As Object, e As EventArgs) Handles BtnChina.Click
- OpenWebsite("https://optifine.cn/home")
- End Sub
End Class
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml
index dae0342..857776a 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadPack.xaml
@@ -32,20 +32,18 @@
+
+
-
-
-
-
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadResourcePack.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadResourcePack.xaml
new file mode 100644
index 0000000..6ce439f
--- /dev/null
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadResourcePack.xaml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadResourcePack.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadResourcePack.xaml.vb
new file mode 100644
index 0000000..41c5c7b
--- /dev/null
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadResourcePack.xaml.vb
@@ -0,0 +1,124 @@
+Public Class PageDownloadResourcePack
+
+ Public Const PageSize = 40
+
+ '加载器信息
+ Public Shared Loader As New LoaderTask(Of CompProjectRequest, Integer)("CompProject ResourcePack", AddressOf CompProjectsGet, AddressOf LoaderInput) With {.ReloadTimeout = 60 * 1000}
+ Public Shared Storage As New CompProjectStorage
+ Public Shared Page As Integer = 0
+ Private Sub PageDownloadResourcePack_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.ResourcePack, Storage, (Page + 1) * PageSize)
+ If FrmDownloadResourcePack IsNot Nothing Then
+ With Request
+ .SearchText = FrmDownloadResourcePack.TextSearchName.Text
+ .GameVersion = If(FrmDownloadResourcePack.TextSearchVersion.Text = "全部 (也可自行输入)", Nothing,
+ If(FrmDownloadResourcePack.TextSearchVersion.Text.Contains(".") OrElse FrmDownloadResourcePack.TextSearchVersion.Text.Contains("w"), FrmDownloadResourcePack.TextSearchVersion.Text, Nothing))
+ .Tag = FrmDownloadResourcePack.ComboSearchTag.SelectedItem.Tag
+ .Source = CType(Val(FrmDownloadResourcePack.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, False))
+ 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
+
+#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/FormLoginOAuth.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/FormLoginOAuth.xaml
deleted file mode 100644
index 10e9c84..0000000
--- a/Plain Craft Launcher 2/Pages/PageLaunch/FormLoginOAuth.xaml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/FormLoginOAuth.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/FormLoginOAuth.xaml.vb
deleted file mode 100644
index 35cd847..0000000
--- a/Plain Craft Launcher 2/Pages/PageLaunch/FormLoginOAuth.xaml.vb
+++ /dev/null
@@ -1,100 +0,0 @@
-Imports System.Windows.Navigation
-
-Public Class FormLoginOAuth
-
- Public Event OnLoginSuccess(code As String)
- Public Event OnLoginCanceled(IsSwitch As Boolean)
-
- '跳转事件
- Private IsLoginSuccessed As Boolean = False
- Private IsSwitch As Boolean = False
- Private Sub Browser_Navigating(sender As WebBrowser, e As NavigatingCancelEventArgs) Handles Browser1.Navigating, Browser2.Navigating, Browser3.Navigating
- Dim Url As String = e.Uri.AbsoluteUri
- If ModeDebug Then Log("[Login] 登录浏览器 " & sender.Tag & " 导向:" & Url)
- If Url.Contains("code=") Then
- Dim Code As String = RegexSeek(Url, "(?<=code\=)[^&]+")
- Log("[Login] 抓取到 OAuth 返回码:" & Code)
- IsLoginSuccessed = True
- RaiseEvent OnLoginSuccess(Code)
- ElseIf Url.Contains("github.") Then
- Hint("PCL 不支持使用 GitHub 登录微软账号!", HintType.Critical)
- Close()
- End If
- End Sub
-
- '已导航事件
- Private IsFirstLoaded As Boolean = False
- Private IsFirstLoadedLock As New Object
- Private Sub Browser_Navigated(sender As WebBrowser, e As NavigationEventArgs) Handles Browser1.Navigated, Browser2.Navigated, Browser3.Navigated
- SyncLock IsFirstLoadedLock
- If IsFirstLoaded Then Exit Sub
- IsFirstLoaded = True
- End SyncLock
- RunInThread(Sub()
- Thread.Sleep(1000)
- RunInUi(Sub()
- sender.Visibility = Visibility.Visible
- PanLoading.Visibility = Visibility.Collapsed
- Log("[Login] 已将登录窗口切换至浏览器")
- End Sub)
- End Sub)
- End Sub
-
- '窗体的进出事件
- Public Shared LoginUrl1 As String = "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?prompt=login&client_id=00000000402b5328&response_type=code&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL&redirect_uri=https:%2F%2Flogin.live.com%2Foauth20_desktop.srf"
- Public Shared LoginUrl2 As String = "https://login.live.com/oauth20_authorize.srf?prompt=login&client_id=00000000402b5328&response_type=code&scope=service%3A%3Auser.auth.xboxlive.com%3A%3AMBI_SSL&redirect_uri=https:%2F%2Flogin.live.com%2Foauth20_desktop.srf"
- Private Sub FrmLoginOAuth_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
- Browser1.Navigate(LoginUrl1)
- RunInThread(Sub()
- '估计是由于时间戳原因,同时导航会导致误报密码失败
- Thread.Sleep(2500)
- RunInUi(Sub()
- Try '防止在 Dispose 之后调用
- If Not IsFirstLoaded Then Browser2.Navigate(LoginUrl1)
- Catch
- End Try
- End Sub)
- Thread.Sleep(2500)
- RunInUi(Sub()
- Try
- If Not IsFirstLoaded Then Browser3.Navigate(LoginUrl2)
- Catch
- End Try
- End Sub)
- End Sub)
- End Sub
- Private Sub FormLoginOAuth_Closed() Handles Me.Closed
- RunInThread(Sub()
- Thread.Sleep(1000) '释放会卡一下,所以稍等一下……
- RunInUiWait(Sub()
- Try
- Browser1.Dispose()
- Catch ex As Exception
- Log(ex, "释放微软登录浏览器 1 失败")
- End Try
- End Sub)
- Thread.Sleep(200)
- RunInUiWait(Sub()
- Try
- Browser2.Dispose()
- Catch ex As Exception
- Log(ex, "释放微软登录浏览器 2 失败")
- End Try
- End Sub)
- Thread.Sleep(200)
- RunInUiWait(Sub()
- Try
- Browser3.Dispose()
- Catch ex As Exception
- Log(ex, "释放微软登录浏览器 3 失败")
- End Try
- End Sub)
- End Sub)
- If Not IsLoginSuccessed Then RaiseEvent OnLoginCanceled(IsSwitch)
- FrmMain.Focus()
- End Sub
- Private Sub HintSwitch_Click() Handles HintSwitch.Click
- IsSwitch = True
- Close()
- End Sub
-End Class
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml
new file mode 100644
index 0000000..1144f09
--- /dev/null
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
new file mode 100644
index 0000000..174f4be
--- /dev/null
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
@@ -0,0 +1,145 @@
+Public Class MyMsgLogin
+ Private Data As JObject
+ Private UserCode As String '需要用户在网页上输入的设备代码
+ Private DeviceCode As String '用于轮询的设备代码
+ Private Website As String '验证网页的网址
+
+#Region "弹窗"
+
+ Private ReadOnly MyConverter As MyMsgBoxConverter
+ Private ReadOnly Uuid As Integer = GetUuid()
+
+ Public Sub New(Converter As MyMsgBoxConverter)
+ Try
+ InitializeComponent()
+ Btn1.Name = Btn1.Name & GetUuid()
+ Btn2.Name = Btn2.Name & GetUuid()
+ Btn3.Name = Btn3.Name & GetUuid()
+ MyConverter = Converter
+ ShapeLine.StrokeThickness = GetWPFSize(1)
+ Data = Converter.Content
+ Init()
+ Catch ex As Exception
+ Log(ex, "登录弹窗初始化失败", LogLevel.Hint)
+ End Try
+ End Sub
+
+ Private Sub Load(sender As Object, e As EventArgs) Handles MyBase.Loaded
+ Try
+ '动画
+ Opacity = 0
+ AniStart(AaColor(FrmMain.PanMsg, Grid.BackgroundProperty, If(MyConverter.IsWarn, New MyColor(140, 80, 0, 0), New MyColor(90, 0, 0, 0)) - FrmMain.PanMsg.Background, 200), "PanMsg Background")
+ AniStart({
+ AaOpacity(Me, 1, 120, 60),
+ AaDouble(Sub(i) TransformPos.Y += i, -TransformPos.Y, 300, 60, New AniEaseOutBack(AniEasePower.Weak)),
+ AaDouble(Sub(i) TransformRotate.Angle += i, -TransformRotate.Angle, 300, 60, New AniEaseOutFluent(AniEasePower.Weak))
+ }, "MyMsgBox " & Uuid)
+ '记录日志
+ Log("[Control] 登录弹窗:" & LabTitle.Text & vbCrLf & LabCaption.Text)
+ Catch ex As Exception
+ Log(ex, "登录弹窗加载失败", LogLevel.Hint)
+ End Try
+ End Sub
+ Private Sub Close()
+ '动画
+ AniStart({
+ AaCode(
+ Sub()
+ If Not WaitingMyMsgBox.Any() Then
+ AniStart(AaColor(FrmMain.PanMsg, Grid.BackgroundProperty, New MyColor(0, 0, 0, 0) - FrmMain.PanMsg.Background, 200, Ease:=New AniEaseOutFluent(AniEasePower.Weak)))
+ End If
+ End Sub, 30),
+ AaOpacity(Me, -Opacity, 80, 20),
+ AaDouble(Sub(i) TransformPos.Y += i, 20 - TransformPos.Y, 150, 0, New AniEaseOutFluent),
+ AaDouble(Sub(i) TransformRotate.Angle += i, 6 - TransformRotate.Angle, 150, 0, New AniEaseInFluent(AniEasePower.Weak)),
+ AaCode(Sub() CType(Parent, Grid).Children.Remove(Me), , True)
+ }, "MyMsgBox " & Uuid)
+ End Sub
+
+ '实现回车和 Esc 的接口(#4857)
+ Public Sub Btn1_Click() Handles Btn1.Click
+ End Sub
+ Public Sub Btn3_Click() Handles Btn3.Click
+ Finished(New ThreadInterruptedException)
+ End Sub
+
+ Private Sub Drag(sender As Object, e As MouseButtonEventArgs) Handles PanBorder.MouseLeftButtonDown, LabTitle.MouseLeftButtonDown
+ On Error Resume Next
+ If e.GetPosition(ShapeLine).Y <= 2 Then FrmMain.DragMove()
+ End Sub
+
+#End Region
+
+ Private Sub Finished(Result As Object)
+ If MyConverter.IsExited Then Return
+ MyConverter.IsExited = True
+ MyConverter.Result = Result
+ RunInUi(AddressOf Close)
+ Thread.Sleep(200)
+ FrmMain.ShowWindowToTop()
+ End Sub
+
+ Private Sub Init()
+ UserCode = Data("user_code")
+ DeviceCode = Data("device_code")
+ Website = Data("verification_uri")
+ '设置 UI
+ LabTitle.Text = "登录 Minecraft"
+ LabCaption.Text =
+ $"登录网页将自动开启,请在网页中输入 {UserCode}(已自动复制)。" & vbCrLf & vbCrLf &
+ $"如果网络环境不佳,网页可能一直加载不出来,届时请使用 VPN 并重试。" & vbCrLf &
+ $"你也可以用其他设备打开 {Website} 并输入上述代码。"
+ Btn1.EventData = Website
+ Btn2.EventData = UserCode
+ '启动工作线程
+ RunInNewThread(AddressOf WorkThread, "MyMsgLogin")
+ End Sub
+
+ Private Sub WorkThread()
+ Thread.Sleep(3000)
+ If MyConverter.IsExited Then Exit Sub
+ OpenWebsite(Website)
+ ClipboardSet(UserCode)
+ Thread.Sleep((Data("interval").ToObject(Of Integer) - 1) * 1000)
+ '轮询
+ 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 ResultJson As JObject = GetJson(Result)
+ McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒")
+ Hint("网页登录成功!", HintType.Finish)
+ 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
+ UnknownFailureCount += 1
+ Log(ex, $"登录轮询第 {UnknownFailureCount} 次失败")
+ Thread.Sleep(2000)
+ Else
+ Finished(New Exception("登录轮询失败", ex))
+ Return
+ End If
+ End Try
+ Loop
+ End Sub
+
+End Class
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
index 068a40a..ebbbc96 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
@@ -52,7 +52,7 @@
Exit Sub
End If
Try
- Dim FileAddress As String = SelectAs("选取保存皮肤的位置", GetFileNameFromPath(Address), "皮肤图片文件(*.png)|*.png")
+ Dim FileAddress As String = SelectSaveFile("选取保存皮肤的位置", GetFileNameFromPath(Address), "皮肤图片文件(*.png)|*.png")
If FileAddress.Contains("\") Then
File.Delete(FileAddress)
If Address.StartsWith(PathImage) Then
@@ -177,8 +177,8 @@
Sub()
Try
'更新缓存
- WriteIni(PathTemp & "Cache\Skin\IndexMs.ini", Setup.Get("CacheMsUuid"), SkinAddress)
- Log(String.Format("[Skin] 已写入皮肤地址缓存 {0} -> {1}", Setup.Get("CacheMsUuid"), SkinAddress))
+ WriteIni(PathTemp & "Cache\Skin\IndexMs.ini", Setup.Get("CacheMsV2Uuid"), SkinAddress)
+ Log(String.Format("[Skin] 已写入皮肤地址缓存 {0} -> {1}", Setup.Get("CacheMsV2Uuid"), SkinAddress))
'刷新控件
For Each SkinLoader In {PageLaunchLeft.SkinMs, PageLaunchLeft.SkinLegacy}
SkinLoader.WaitForExit(IsForceRestart:=True)
@@ -242,7 +242,8 @@ Retry:
{"Vanilla", "原版披风"}, {"Minecon2011", "Minecon 2011 参与者披风"}, {"Minecon2012", "Minecon 2012 参与者披风"},
{"Minecon2013", "Minecon 2013 参与者披风"}, {"Minecon2015", "Minecon 2015 参与者披风"}, {"Minecon2016", "Minecon 2016 参与者披风"},
{"Cherry Blossom", "樱花披风"}, {"15th Anniversary", "15 周年纪念披风"}, {"Purple Heart", "紫色心形披风"},
- {"Follower's", "追随者披风"}, {"MCC 15th Year", "MCC 15 周年披风"}
+ {"Follower's", "追随者披风"}, {"MCC 15th Year", "MCC 15 周年披风"}, {"Minecraft Experience", "村民救援披风"},
+ {"Mojang Office", "Mojang 办公室披风"}, {"Home", "家园披风"}, {"Menace", "入侵披风"}, {"Yearn", "渴望披风"}
}
Dim SelectionControl As New List(Of IMyRadio) From {New MyRadioBox With {.Text = "无披风"}}
For Each Cape In SkinData("capes")
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb
index 206f96a..e74b323 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchLeft.xaml.vb
@@ -26,21 +26,15 @@
If File.Exists(Path & "modpack.zip") Then PackInstallPath = Path & "modpack.zip"
If File.Exists(Path & "modpack.mrpack") Then PackInstallPath = Path & "modpack.mrpack"
If PackInstallPath IsNot Nothing Then
- If MyMsgBox($"PCL 即将在当前文件夹下自动安装整合包。", "自动安装", "继续", "取消") = 1 Then
- '确认自动安装
- Log("[Launch] 需自动安装整合包:" & PackInstallPath, LogLevel.Debug)
- Setup.Set("LaunchFolderSelect", "$.minecraft\")
- If Not Directory.Exists(Path & ".minecraft\") Then
- Directory.CreateDirectory(Path & ".minecraft\")
- Directory.CreateDirectory(Path & ".minecraft\versions\")
- McFolderLauncherProfilesJsonCreate(Path & ".minecraft\")
- End If
- McFolderListLoader.WaitForExit(IsForceRestart:=True)
- Else
- '取消自动安装
- Log("[Launch] 取消自动安装整合包:" & PackInstallPath, LogLevel.Debug)
- PackInstallPath = Nothing
+ Log("[Launch] 需自动安装整合包:" & PackInstallPath, LogLevel.Debug)
+ Setup.Set("LaunchFolderSelect", "$.minecraft\")
+ If Not Directory.Exists(Path & ".minecraft\") Then
+ Directory.CreateDirectory(Path & ".minecraft\")
+ Directory.CreateDirectory(Path & ".minecraft\versions\")
+ McFolderLauncherProfilesJsonCreate(Path & ".minecraft\")
End If
+ PageSelectLeft.AddFolder(Path & ".minecraft\", GetFolderNameFromPath(Path), False)
+ McFolderListLoader.WaitForExit()
End If
'确认 Minecraft 文件夹存在
PathMcFolder = Setup.Get("LaunchFolderSelect").ToString.Replace("$", Path)
@@ -58,18 +52,19 @@
If Setup.Get("SystemDebugDelay") Then Thread.Sleep(RandomInteger(500, 3000))
'自动整合包安装
If PackInstallPath IsNot Nothing Then
- Dim InstallLoader = ModpackInstall(PackInstallPath)
- If InstallLoader Is Nothing Then
- Log("[Launch] 自动安装整合包失败:" & PackInstallPath)
- Else
- Log("[Launch] 自动安装整合包开始:" & PackInstallPath)
- RunInUi(Sub() FrmMain.PageChange(FormMain.PageType.DownloadManager))
+ Try
+ Dim InstallLoader = ModpackInstall(PackInstallPath)
+ Log("[Launch] 自动安装整合包已开始:" & PackInstallPath)
InstallLoader.WaitForExit()
If InstallLoader.State = LoadState.Finished Then
- Log("[Launch] 自动安装整合包成功,删除安装包:" & PackInstallPath)
- File.Delete(PackInstallPath)
+ Log("[Launch] 自动安装整合包成功,清理安装包:" & PackInstallPath)
+ If File.Exists(PackInstallPath) Then File.Delete(PackInstallPath)
End If
- End If
+ Catch ex As CancelledException
+ Log(ex, "自动安装整合包被用户取消:" & PackInstallPath)
+ Catch ex As Exception
+ Log(ex, "自动安装整合包失败:" & PackInstallPath, LogLevel.Msgbox)
+ End Try
End If
'确认 Minecraft 版本存在
Dim Selection As String = Setup.Get("LaunchVersionSelect")
@@ -130,7 +125,7 @@
'初始化页面
LabLaunchingName.Text = McVersionCurrent.Name
LabLaunchingStage.Text = "初始化"
- LabLaunchingTitle.Text = If(McLaunchLoader.Input Is Nothing OrElse McLaunchLoader.Input.SaveBatch Is Nothing, "正在启动游戏", "正在导出启动脚本")
+ LabLaunchingTitle.Text = If(CurrentLaunchOptions?.SaveBatch Is Nothing, "正在启动游戏", "正在导出启动脚本")
LabLaunchingProgress.Text = "0.00 %"
LabLaunchingProgress.Opacity = 1
LabLaunchingDownload.Visibility = Visibility.Visible
@@ -296,7 +291,7 @@
Case 0 '正版或离线
UnknownType:
If RadioLoginType5.Checked Then
- If Setup.Get("CacheMsAccess") = "" Then
+ If Setup.Get("CacheMsV2Access") = "" Then
Type = PageType.Ms
Else
Type = PageType.MsSkin
@@ -311,7 +306,7 @@ UnknownType:
RadioLoginType5.Visibility = Visibility.Visible
RadioLoginType0.Visibility = Visibility.Visible
Case 1 '仅正版
- If Setup.Get("CacheMsAccess") = "" Then
+ If Setup.Get("CacheMsV2Access") = "" Then
Type = PageType.Ms
Else
Type = PageType.MsSkin
@@ -375,7 +370,7 @@ UnknownType:
Public Shared SkinMs As New LoaderTask(Of EqualableList(Of String), String)("Loader Skin Ms", AddressOf SkinMsLoad, AddressOf SkinMsInput, ThreadPriority.AboveNormal)
Private Shared Function SkinMsInput() As EqualableList(Of String)
'获取名称
- Return New EqualableList(Of String) From {Setup.Get("CacheMsName"), Setup.Get("CacheMsUuid")}
+ Return New EqualableList(Of String) From {Setup.Get("CacheMsV2Name"), Setup.Get("CacheMsV2Uuid")}
End Function
Private Shared Sub SkinMsLoad(Data As LoaderTask(Of EqualableList(Of String), String))
'清空已有皮肤
@@ -431,7 +426,7 @@ Finish:
ElseIf Setup.Get("LoginLegacyName") = "" Then
Return New EqualableList(Of String) From {0, ""}
Else
- Return New EqualableList(Of String) From {0, If(Setup.Get("LoginLegacyName").ToString.Before("¨"), "")}
+ Return New EqualableList(Of String) From {0, If(Setup.Get("LoginLegacyName").ToString.BeforeFirst("¨"), "")}
End If
Case 3
Return New EqualableList(Of String) From {3, Setup.Get("LaunchSkinID")}
@@ -733,7 +728,7 @@ ExitRefresh:
If ActualProgress <= ShowProgress Then ShowProgress = ActualProgress '原来或处理后变得比实际进度高,直接回退
If IsLaunched Then ShowProgress = 1 '如果已经完成了,就不卖关子了
'文本
- LabLaunchingTitle.Text = If(IsLaunched, "已启动游戏", If(McLaunchLoader.Input.SaveBatch Is Nothing, "正在启动游戏", "正在导出启动脚本"))
+ LabLaunchingTitle.Text = If(IsLaunched, "已启动游戏", If(CurrentLaunchOptions.SaveBatch Is Nothing, "正在启动游戏", "正在导出启动脚本"))
LabLaunchingProgress.Text = StrFillNum(ShowProgress * 100, 2) & " %"
Dim HasLaunchDownloader As Boolean = False
Try
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
index 0994379..c5dfecc 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
@@ -1,4 +1,5 @@
Public Class PageLaunchRight
+ Implements IRefreshable
Private Sub Init() Handles Me.Loaded
PanBack.ScrollToHome()
@@ -92,7 +93,7 @@ Download:
GoTo Download
Case 3
Log("[Page] 主页预设:简单主页")
- Url = "https://gitee.com/mfn233/PCL-Mainpage/raw/main/Custom.xaml"
+ Url = "https://raw.gitcode.com/mfn233/PCL-Mainpage/raw/main/Custom.xaml"
GoTo Download
Case 4
Log("[Page] 主页预设:每日整合包推荐")
@@ -104,7 +105,27 @@ Download:
GoTo Download
Case 6
Log("[Page] 主页预设:OpenBMCLAPI 仪表盘 Lite")
- Url = "https://pcl-bmcl.milulu.xyz/"
+ 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
End Select
End Select
@@ -122,17 +143,17 @@ Download:
If Address.Contains(".xaml") Then
VersionAddress = Address.Replace(".xaml", ".xaml.ini")
Else
- VersionAddress = Address.Before("?")
+ VersionAddress = Address.BeforeFirst("?")
If Not VersionAddress.EndsWith("/") Then VersionAddress += "/"
VersionAddress += "version"
- If Address.Contains("?") Then VersionAddress += Address.After("?")
+ If Address.Contains("?") Then VersionAddress += Address.AfterLast("?")
End If
'校验版本
Dim Version As String = ""
Dim NeedDownload As Boolean = True
Try
Version = NetGetCodeByRequestOnce(VersionAddress, Timeout:=10000)
- If Version.Length > 100 Then Throw New Exception($"获取的自定义主页版本过长({Version.Length} 字符)")
+ 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}")
@@ -167,7 +188,7 @@ Download:
''' 立即强制刷新自定义主页。
''' 必须在 UI 线程调用。
'''
- Public Sub ForceRefresh()
+ Public Sub ForceRefresh() Implements IRefreshable.Refresh
Log("[Page] 要求强制刷新自定义主页")
ClearCache()
'实际的刷新
@@ -206,14 +227,20 @@ Download:
Log($"[Page] 实例化:清空自定义主页 UI,来源为空")
Return
End If
- Content = "" & Content & ""
- Content = HelpArgumentReplace(Content)
- Log($"[Page] 实例化:加载自定义主页 UI 开始,最终内容长度:{Content.Count}")
Try
+ Content = HelpArgumentReplace(Content)
+ If Content.Contains("xmlns") Then Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间
+ Content = "" & Content & ""
+ 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("[Page] 加载失败的自定义主页内容:" & vbCrLf & Content)
- If MyMsgBox($"自定义主页内容编写有误,请根据下列错误信息进行检查:{vbCrLf}{ex.Message}", "加载自定义主页失败", "重试", "取消") = 1 Then
+ Log(ex, "加载失败的自定义主页内容:" & vbCrLf & Content)
+ If MyMsgBox($"自定义主页内容编写有误,请根据下列错误信息进行检查:{vbCrLf}{GetExceptionSummary(ex)}", "加载自定义主页失败", "重试", "取消") = 1 Then
GoTo Refresh '防止 SyncLock 死锁
End If
End Try
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuth.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuth.xaml.vb
index 3476e70..e9ae125 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuth.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuth.xaml.vb
@@ -17,8 +17,8 @@
ComboName.ItemsSource = Nothing
Else
ComboName.ItemsSource = Setup.Get("LoginAuthEmail").ToString.Split("¨")
- ComboName.Text = Setup.Get("LoginAuthEmail").ToString.Before("¨")
- If Setup.Get("LoginRemember") Then TextPass.Password = Setup.Get("LoginAuthPass").ToString.Before("¨").Trim
+ ComboName.Text = Setup.Get("LoginAuthEmail").ToString.BeforeFirst("¨")
+ If Setup.Get("LoginRemember") Then TextPass.Password = Setup.Get("LoginAuthPass").ToString.BeforeFirst("¨").Trim
End If
End If
IsFirstLoad = False
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb
index ca21ffa..fec81e6 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginAuthSkin.xaml.vb
@@ -70,9 +70,11 @@
End Try
End Sub)
End Sub
- Private Sub BtnExit_Click() Handles BtnExit.Click
+ Public Shared Sub ExitLogin() Handles BtnExit.Click
Setup.Set("CacheAuthAccess", "")
Setup.Set("CacheAuthUuid", "")
+ Setup.Set("CacheAuthName", "")
+ McLoginAuthLoader.Input = Nothing '防止因为输入的用户名密码相同,直接使用了上次登录的加载器结果
FrmLaunchLeft.RefreshPage(False, True)
End Sub
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb
index 0c47882..dc1c043 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb
@@ -26,7 +26,7 @@ Public Class PageLoginLegacy
ComboName.ItemsSource = Nothing
Else
ComboName.ItemsSource = Setup.Get("LoginLegacyName").ToString.Split("¨")
- ComboName.Text = Setup.Get("LoginLegacyName").ToString.Before("¨").Trim
+ ComboName.Text = Setup.Get("LoginLegacyName").ToString.BeforeFirst("¨").Trim
End If
End If
IsReloaded = True
@@ -44,6 +44,11 @@ Public Class PageLoginLegacy
Public Shared Function IsVaild(LoginData As McLoginLegacy) As String
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
+ LoginData.UserName.Trim.Length > 16 Then
+ Return "自 1.20.3 起,玩家名至多只能包含 16 个字符!"
+ End If
Return ""
End Function
Public Function IsVaild() As String
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb
index e61fa4e..49c627e 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMs.xaml.vb
@@ -37,7 +37,7 @@
''' 获取当前页面的登录信息。
'''
Public Shared Function GetLoginData() As McLoginMs
- If FrmLoginMs Is Nothing Then Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName")}
+ If FrmLoginMs Is Nothing Then Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsV2OAuthRefresh"), .UserName = Setup.Get("CacheMsV2Name")}
Dim Result As McLoginMs = Nothing
RunInUiWait(
Sub()
@@ -92,7 +92,7 @@
ElseIf ex.Message.StartsWith("$") Then
Hint(ex.Message.TrimStart("$"), HintType.Critical)
ElseIf TypeOf ex Is Security.Authentication.AuthenticationException AndAlso ex.Message.ContainsF("SSL/TLS") Then
- Log(ex, "正版登录验证失败,请尝试在 [设置 → 启动器] 中关闭 [验证 SSL 证书] 然后再试。" & vbCrLf & vbCrLf & "原始错误信息:", LogLevel.Msgbox)
+ Log(ex, "正版登录验证失败,请考虑在 [设置 → 其他] 中关闭 [在正版登录时验证 SSL 证书],然后再试。" & vbCrLf & vbCrLf & "原始错误信息:", LogLevel.Msgbox)
Else
Log(ex, "正版登录尝试失败", LogLevel.Msgbox)
End If
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
index b3f47ae..29e865e 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
@@ -12,7 +12,7 @@
''' 刷新页面显示的所有信息。
'''
Public Sub Reload(KeepInput As Boolean)
- TextName.Text = Setup.Get("CacheMsName")
+ TextName.Text = Setup.Get("CacheMsV2Name")
'皮肤在 Loaded 加载
End Sub
'''
@@ -20,9 +20,9 @@
'''
Public Shared Function GetLoginData() As McLoginMs
If McLoginMsLoader.State = LoadState.Finished Then
- Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName"), .AccessToken = Setup.Get("CacheMsAccess"), .Uuid = Setup.Get("CacheMsUuid"), .ProfileJson = Setup.Get("CacheMsProfileJson")}
+ Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsV2OAuthRefresh"), .UserName = Setup.Get("CacheMsV2Name"), .AccessToken = Setup.Get("CacheMsV2Access"), .Uuid = Setup.Get("CacheMsV2Uuid"), .ProfileJson = Setup.Get("CacheMsV2ProfileJson")}
Else
- Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsOAuthRefresh"), .UserName = Setup.Get("CacheMsName")}
+ Return New McLoginMs With {.OAuthRefreshToken = Setup.Get("CacheMsV2OAuthRefresh"), .UserName = Setup.Get("CacheMsV2Name")}
End If
End Function
@@ -50,11 +50,11 @@
'退出登录
Private Sub BtnExit_Click() Handles BtnExit.Click
- Setup.Set("CacheMsOAuthRefresh", "")
- Setup.Set("CacheMsAccess", "")
- Setup.Set("CacheMsProfileJson", "")
- Setup.Set("CacheMsUuid", "")
- Setup.Set("CacheMsName", "")
+ Setup.Set("CacheMsV2OAuthRefresh", "")
+ Setup.Set("CacheMsV2Access", "")
+ Setup.Set("CacheMsV2ProfileJson", "")
+ Setup.Set("CacheMsV2Uuid", "")
+ Setup.Set("CacheMsV2Name", "")
McLoginMsLoader.Abort()
FrmLaunchLeft.RefreshPage(False, True)
End Sub
@@ -90,8 +90,8 @@
Try
Retry:
If McLoginMsLoader.State = LoadState.Loading Then McLoginMsLoader.WaitForExit() '等待登录结束
- Dim AccessToken As String = Setup.Get("CacheMsAccess")
- Dim Uuid As String = Setup.Get("CacheMsUuid")
+ 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)
@@ -113,6 +113,7 @@ Retry:
'获取新皮肤地址
Log("[Skin] 皮肤修改返回值:" & vbCrLf & Result)
Dim ResultJson As JObject = GetJson(Result)
+ If ResultJson.ContainsKey("errorMessage") Then Throw New Exception(ResultJson("errorMessage").ToString) '#5309
For Each Skin As JObject In ResultJson("skins")
If Skin("state").ToString = "ACTIVE" Then
MySkin.ReloadCache(Skin("url"))
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNide.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNide.xaml.vb
index 0e327d0..3b47833 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNide.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNide.xaml.vb
@@ -17,8 +17,8 @@
ComboName.ItemsSource = Nothing
Else
ComboName.ItemsSource = Setup.Get("LoginNideEmail").ToString.Split("¨")
- ComboName.Text = Setup.Get("LoginNideEmail").ToString.Before("¨")
- If Setup.Get("LoginRemember") Then TextPass.Password = Setup.Get("LoginNidePass").ToString.Before("¨").Trim
+ ComboName.Text = Setup.Get("LoginNideEmail").ToString.BeforeFirst("¨")
+ If Setup.Get("LoginRemember") Then TextPass.Password = Setup.Get("LoginNidePass").ToString.BeforeFirst("¨").Trim
End If
End If
IsFirstLoad = False
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNideSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNideSkin.xaml.vb
index bceb7c8..84fa773 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNideSkin.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginNideSkin.xaml.vb
@@ -47,8 +47,9 @@
Private Sub BtnEdit_Click(sender As Object, e As EventArgs) Handles BtnEdit.Click
OpenWebsite("https://login.mc-user.com:233/account/changepw")
End Sub
- Private Sub BtnExit_Click() Handles BtnExit.Click
+ Public Shared Sub ExitLogin() Handles BtnExit.Click
Setup.Set("CacheNideAccess", "")
+ McLoginNideLoader.Input = Nothing '防止因为输入的用户名密码相同,直接使用了上次登录的加载器结果
FrmLaunchLeft.RefreshPage(False, True)
End Sub
diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb
index 7f95d6f..e0639ae 100644
--- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkHiper.xaml.vb
@@ -17,7 +17,7 @@
Private IsLoad As Boolean = False
Private Sub OnLoaded() Handles Me.Loaded
- FormMain.EndProgramForce(Result.Aborted)
+ FormMain.EndProgramForce(ProcessReturnValues.Aborted)
If IsLoad Then Exit Sub
IsLoad = True
'启动监视线程
diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
index f0a318f..f1e6312 100644
--- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkIoi.xaml.vb
@@ -111,8 +111,6 @@ Public Class PageLinkIoi
End Sub
'复制联机码
Public Shared Sub BtnLeftCopy_Click() Handles BtnLeftCopy.Click
- ClipboardSet(IoiId.Substring(4) & SecretEncrypt(GetPlayerName), False)
- Hint("已复制联机码!", HintType.Finish)
End Sub
#End Region
@@ -419,75 +417,6 @@ Public Class PageLinkIoi
'获取数据包
Public Shared Sub ReceiveJson(JsonData As JObject, Optional NewSocket As Socket = Nothing)
- '获取数据
- Dim Id As String = JsonData("id"), Type As String = JsonData("type")
- Select Case Type
- Case "connect"
- Dim DisplayName As String = JsonData("name")
- Dim User As New LinkUserIoi(Id, DisplayName, NewSocket)
- '如果发生了双向连接
- If UserList.ContainsKey(Id) Then
- If Id > IoiId Then
- Log("[IOI] 双向连接,应当抛弃当前用户(" & DisplayName & ")")
- For Each Pair In UserList(User.Id).Ports.ToList
- User.Ports(Pair.Key) = Pair.Value
- Next
- UserList(Id).Dispose()
- Else
- '应当保留当前用户
- Log("[IOI] 双向连接,应当保留当前用户(" & DisplayName & ")")
- NewSocket.Dispose()
- Exit Sub
- End If
- End If
- '加入列表
- UserList.Add(Id, User)
- '返回请求
- User.RelativeThread = RunInNewThread(Sub()
- SendPortsubBack(User, JsonData("version").ToObject(Of Integer))
- End Sub, "Link Connect " & DisplayName)
- '更新时间
- User.LastReceive = Date.Now
- Case "update"
- If Not UserList.ContainsKey(Id) Then Throw New Exception("未在列表中的用户发送了更新请求:" & Id)
- Dim User = UserList(Id)
- '拉满进度(该请求也作为反向连接回应出现,用于向正向连接方传达连接已完成信号)
- User.Progress = 1
- '更新名称
- Dim DisplayName As String = JsonData("name")
- User.DisplayName = DisplayName
- '更新房间列表
- If JsonData("rooms") IsNot Nothing Then
- User.Rooms = New List(Of RoomEntry)
- For Each RoomObject In JsonData("rooms")
- User.Rooms.Add(New RoomEntry(RoomObject("port"), RoomObject("name"), User))
- Next
- End If
- '更新 Ping
- Dim Stage As Integer = JsonData("stage"), Unique As Long = JsonData("unique")
- If Stage > 1 Then
- User.PingRecord.Enqueue((Date.Now - User.PingPending(Unique)).TotalMilliseconds / 2)
- If User.PingRecord.Count > 5 Then User.PingRecord.Dequeue()
- User.PingPending.Remove(Unique)
- End If
- '返回请求
- If Stage > 0 AndAlso Stage < 3 Then RunInNewThread(Sub()
- Try
- SendUpdateRequest(User, Stage + 1, Unique)
- Catch ex As Exception
- Log(ex, "发送回程请求失败")
- End Try
- End Sub, "Link Update " & DisplayName)
- '更新时间
- User.LastReceive = Date.Now
- Case "disconnect"
- '断开连接
- If Not UserList.ContainsKey(Id) Then Exit Sub
- UserRemove(UserList(Id), ShowLeaveMessage:=JsonData("message") Is Nothing)
- If JsonData("message") IsNot Nothing Then Hint(JsonData("message").ToString, If(JsonData("isError").ToObject(Of Boolean), HintType.Critical, HintType.Info))
- Case Else
- Throw New Exception("未知的操作种类:" & Type)
- End Select
End Sub
'''
''' 从用户列表中移除一位用户。提示信息视作该用户主动离开。
diff --git a/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml.vb
index 7d62918..38fd50f 100644
--- a/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLink/PageLinkLeft.xaml.vb
@@ -63,7 +63,7 @@
PageChangeRun(PageGet(ID))
PageID = ID
Catch ex As Exception
- Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+ Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
Finally
AniControlEnabled -= 1
End Try
diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
index 0398d5e..27ef038 100644
--- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
+++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
@@ -1,275 +1,395 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb
index d8c0a68..46a8bf4 100644
--- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelp.xaml.vb
@@ -1,4 +1,5 @@
Public Class PageOtherHelp
+ Implements IRefreshable
#Region "初始化"
@@ -79,31 +80,35 @@
End Try
End Sub
Public Shared Sub EnterHelpPage(Location As String)
- RunInThread(Sub()
- If Not HelpLoader.State = LoadState.Finished Then HelpLoader.WaitForExit(GetUuid)
- Dim Entry As New HelpEntry(Location)
- RunInUi(Sub()
- Dim FrmHelpDetail As New PageOtherHelpDetail
- If FrmHelpDetail.Init(Entry) Then
- FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.HelpDetail, .Additional = {Entry, FrmHelpDetail}})
- Else
- Log("[Help] 已取消进入帮助项目,这一般是由于 xaml 初始化失败,且用户在弹窗中手动放弃", LogLevel.Debug)
- End If
- End Sub)
- End Sub)
+ RunInThread(
+ Sub()
+ If Not HelpLoader.State = LoadState.Finished Then HelpLoader.WaitForExit(GetUuid)
+ Dim Entry As New HelpEntry(Location)
+ RunInUi(
+ Sub()
+ Dim FrmHelpDetail As New PageOtherHelpDetail
+ If FrmHelpDetail.Init(Entry) Then
+ FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.HelpDetail, .Additional = {Entry, FrmHelpDetail}})
+ Else
+ Log("[Help] 已取消进入帮助项目,这一般是由于 xaml 初始化失败,且用户在弹窗中手动放弃", LogLevel.Debug)
+ End If
+ End Sub)
+ End Sub)
End Sub
Public Shared Sub EnterHelpPage(Entry As HelpEntry)
- RunInThread(Sub()
- If Not HelpLoader.State = LoadState.Finished Then HelpLoader.WaitForExit(GetUuid)
- RunInUi(Sub()
- Dim FrmHelpDetail As New PageOtherHelpDetail
- If FrmHelpDetail.Init(Entry) Then
- FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.HelpDetail, .Additional = {Entry, FrmHelpDetail}})
- Else
- Log("[Help] 已取消进入帮助项目,这一般是由于 xaml 初始化失败,且用户在弹窗中手动放弃", LogLevel.Debug)
- End If
- End Sub)
- End Sub)
+ RunInThread(
+ Sub()
+ If Not HelpLoader.State = LoadState.Finished Then HelpLoader.WaitForExit(GetUuid)
+ RunInUi(
+ Sub()
+ Dim FrmHelpDetail As New PageOtherHelpDetail
+ If FrmHelpDetail.Init(Entry) Then
+ FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.HelpDetail, .Additional = {Entry, FrmHelpDetail}})
+ Else
+ Log("[Help] 已取消进入帮助项目,这一般是由于 xaml 初始化失败,且用户在弹窗中手动放弃", LogLevel.Debug)
+ End If
+ End Sub)
+ End Sub)
End Sub
Public Shared Function GetHelpPage(Location As String) As PageOtherHelpDetail
If Not HelpLoader.State = LoadState.Finished Then HelpLoader.WaitForExit(GetUuid)
@@ -122,14 +127,15 @@
If String.IsNullOrWhiteSpace(SearchBox.Text) Then
'隐藏
AniStart({
- AaOpacity(PanSearch, -PanSearch.Opacity, 100),
- AaCode(Sub()
- PanSearch.Height = 0
- PanSearch.Visibility = Visibility.Collapsed
- PanList.Visibility = Visibility.Visible
- End Sub,, True),
- AaOpacity(PanList, 1 - PanList.Opacity, 150, 30)
- }, "FrmOtherHelp Search Switch")
+ AaOpacity(PanSearch, -PanSearch.Opacity, 100),
+ AaCode(
+ Sub()
+ PanSearch.Height = 0
+ PanSearch.Visibility = Visibility.Collapsed
+ PanList.Visibility = Visibility.Visible
+ End Sub,, True),
+ AaOpacity(PanList, 1 - PanList.Opacity, 150, 30)
+ }, "FrmOtherHelp Search Switch")
Else
'构造请求
Dim QueryList As New List(Of SearchEntry(Of HelpEntry))
@@ -163,15 +169,19 @@
End If
'显示
AniStart({
- AaOpacity(PanList, -PanList.Opacity, 100),
- AaCode(Sub()
- PanList.Visibility = Visibility.Collapsed
- PanSearch.Visibility = Visibility.Visible
- PanSearch.TriggerForceResize()
- End Sub,, True),
- AaOpacity(PanSearch, 1 - PanSearch.Opacity, 150, 30)
- }, "FrmOtherHelp Search Switch")
+ AaOpacity(PanList, -PanList.Opacity, 100),
+ AaCode(
+ Sub()
+ PanList.Visibility = Visibility.Collapsed
+ PanSearch.Visibility = Visibility.Visible
+ PanSearch.TriggerForceResize()
+ End Sub,, True),
+ AaOpacity(PanSearch, 1 - PanSearch.Opacity, 150, 30)
+ }, "FrmOtherHelp Search Switch")
End If
End Sub
+ Public Sub Refresh() Implements IRefreshable.Refresh
+ PageOtherLeft.RefreshHelp()
+ End Sub
End Class
diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb
index 79aecfd..fda05ce 100644
--- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherHelpDetail.xaml.vb
@@ -1,6 +1,11 @@
Public Class PageOtherHelpDetail
+ Implements IRefreshable
Public Entry As HelpEntry
+ Public Sub Refresh() Implements IRefreshable.Refresh
+ Init(New HelpEntry(Entry.RawPath))
+ End Sub
+
Private Sub PageOtherHelpDetail_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
PanBack.ScrollToTop()
End Sub
diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml
index 119a07c..0c62e42 100644
--- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml
+++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml
@@ -14,10 +14,10 @@
-
+
-
diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb
index 846e453..59df742 100644
--- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherLeft.xaml.vb
@@ -83,7 +83,7 @@
PageChangeRun(PageGet(ID))
PageID = ID
Catch ex As Exception
- Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+ Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
Finally
AniControlEnabled -= 1
End Try
@@ -125,19 +125,29 @@
End Sub
'打开网页
- Public Shared Sub TryFeedback() Handles ItemFeedback.Click
+ Private Sub TryFeedback(sender As Object, e As RouteEventArgs) Handles ItemFeedback.Changed
+ If Not ItemFeedback.Checked Then Exit Sub
+ TryFeedback()
+ e.Handled = True
+ End Sub
+ Public Shared Sub TryFeedback()
If Not CanFeedback(True) Then Exit Sub
- Select Case MyMsgBox("是否要打开反馈列表网页?" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。",
- "反馈提示", "提交新反馈", "查看反馈列表", "取消")
+ Select Case MyMsgBox("在提交新反馈前,建议先搜索反馈列表,以避免重复提交。" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。",
+ "反馈", "提交新反馈", "查看反馈列表", "取消")
Case 1
Feedback(True, False)
Case 2
OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/")
End Select
End Sub
- Public Shared Sub TryVote() Handles ItemVote.Click
+ Private Sub TryVote(sender As Object, e As RouteEventArgs) Handles ItemVote.Changed
+ If Not ItemVote.Checked Then Exit Sub
+ TryVote()
+ e.Handled = True
+ End Sub
+ Public Shared Sub TryVote()
If MyMsgBox("是否要打开新功能投票网页?" & vbCrLf & "如果无法打开该网页,请尝试使用加速器或 VPN。",
- "提醒", "打开", "取消") = 2 Then Exit Sub
+ "新功能投票", "打开", "取消") = 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")
End Sub
diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherTest.xaml.vb b/Plain Craft Launcher 2/Pages/PageOther/PageOtherTest.xaml.vb
index af35b45..041421a 100644
--- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherTest.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherTest.xaml.vb
@@ -12,7 +12,7 @@
Public Shared Sub MemoryOptimize(ShowHint As Boolean)
If ShowHint Then Hint("为便于维护,开源内容中不包含百宝箱功能……")
End Sub
- Public Shared Sub MemoryOptimizeInternal()
+ Public Shared Sub MemoryOptimizeInternal(ShowHint As Boolean)
End Sub
Public Shared Function GetRandomCave() As String
Return "为便于维护,开源内容中不包含百宝箱功能……"
diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml
index 97e9c54..2dfe903 100644
--- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml
+++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml
@@ -9,7 +9,7 @@
-
+
diff --git a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb
index 1ef40df..254d081 100644
--- a/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSelectLeft.xaml.vb
@@ -1,4 +1,5 @@
Public Class PageSelectLeft
+ Implements IRefreshable
Private Sub PageSelectLeft_Initialized(sender As Object, e As EventArgs) Handles Me.Initialized
AddHandler McFolderListLoader.PreviewFinish, Sub() If FrmSelectLeft IsNot Nothing Then RunInUiWait(AddressOf McFolderListUI)
@@ -170,6 +171,9 @@
Log(ex, "添加文件夹失败(" & NewFolder & ")", LogLevel.Feedback)
End Try
End Sub
+ '''
+ ''' 将指定文件夹添加到 Minecraft 文件夹列表,并选中它。
+ '''
Public Shared Sub AddFolder(FolderPath As String, DisplayName As String, ShowHint As Boolean)
RunInThread(
Sub()
@@ -223,7 +227,7 @@
'提示
If IsReplace Then Exit Sub
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
@@ -240,6 +244,7 @@
If ModIndieFolder.Exists AndAlso ModIndieFolder.EnumerateFiles.Any Then Exit Sub
'满足以上全部条件则视为根目录整合包
Setup.Set("VersionArgumentIndie", 2, Version:=Version)
+ Setup.Set("VersionArgumentIndieV2", False, Version:=Version)
Log("[Setup] 已自动关闭单版本隔离:" & Version.Name, LogLevel.Debug)
Next
Catch ex As Exception
@@ -295,7 +300,7 @@
For i = 0 To Folders.Count - 1
If Folders(i) = "" Then Exit For
If Folders(i).ToString.EndsWith(Folder.Path) Then
- Name = Folders(i).ToString.Before(">")
+ Name = Folders(i).ToString.BeforeFirst(">")
Folders.RemoveAt(i)
Exit For
End If
@@ -327,28 +332,35 @@
Next
Setup.Set("LaunchFolders", If(Not Folders.Any(), "", Join(Folders.ToArray, "|")))
End If
- RunInNewThread(Sub()
- '删除文件夹
- Try
- Hint("正在" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Info)
- DeleteDirectory(Folder.Path)
- If DeleteText = "清空" Then Directory.CreateDirectory(Folder.Path)
- Hint("已" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Finish)
- Catch ex As Exception
- Log(ex, DeleteText & "文件夹 " & Folder.Name & " 失败", LogLevel.Hint)
- Finally
- '刷新列表
- McFolderListLoader.Start(IsForceRestart:=True)
- End Try
- End Sub, "Folder Delete " & GetUuid(), ThreadPriority.BelowNormal)
+ RunInNewThread(
+ Sub()
+ '删除文件夹
+ Try
+ Hint("正在" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Info)
+ DeleteDirectory(Folder.Path)
+ If DeleteText = "清空" Then Directory.CreateDirectory(Folder.Path)
+ Hint("已" & DeleteText & "文件夹 " & Folder.Name & "!", HintType.Finish)
+ Catch ex As Exception
+ Log(ex, DeleteText & "文件夹 " & Folder.Name & " 失败", LogLevel.Hint)
+ Finally
+ '刷新列表
+ McFolderListLoader.Start(IsForceRestart:=True)
+ End Try
+ End Sub, "Folder Delete " & GetUuid(), ThreadPriority.BelowNormal)
End Sub
Public Sub Open_Click(sender As Object, e As RoutedEventArgs)
- OpenExplorer("""" & CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Info & """")
+ OpenExplorer(CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Info)
End Sub
Public Sub Refresh_Click(sender As Object, e As RoutedEventArgs)
Dim Data As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag
- WriteIni(Data.Path & "PCL.ini", "VersionCache", "") '删除缓存以强制要求下一次加载时更新列表
- If Data.Path = PathMcFolder Then LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
+ RefreshCurrent(Data.Path)
+ End Sub
+ Public Sub RefreshCurrent() Implements IRefreshable.Refresh
+ RefreshCurrent(PathMcFolder)
+ End Sub
+ Public Shared Sub RefreshCurrent(Folder As String)
+ WriteIni(Folder & "PCL.ini", "VersionCache", "") '删除缓存以强制要求下一次加载时更新列表
+ If Folder = PathMcFolder Then LoaderFolderRun(McVersionListLoader, PathMcFolder, LoaderFolderRunType.ForceRun, MaxDepth:=1, ExtraPath:="versions\")
End Sub
Public Sub Rename_Click(sender As Object, e As RoutedEventArgs)
Dim Folder As McFolder = CType(CType(CType(sender.Parent, ContextMenu).Parent, Primitives.Popup).PlacementTarget, MyListItem).Tag
diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
index 5b88927..5fa1311 100644
--- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
@@ -167,14 +167,16 @@
ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center)
ToolTipService.SetVerticalOffset(BtnCont, 30)
ToolTipService.SetHorizontalOffset(BtnCont, 2)
- AddHandler BtnCont.Click, Sub()
- PageVersionLeft.Version = Version
- FrmMain.PageChange(FormMain.PageType.VersionSetup, 0)
- End Sub
- AddHandler sender.MouseRightButtonUp, Sub()
- PageVersionLeft.Version = Version
- FrmMain.PageChange(FormMain.PageType.VersionSetup, 0)
- End Sub
+ AddHandler BtnCont.Click,
+ Sub()
+ PageVersionLeft.Version = Version
+ FrmMain.PageChange(FormMain.PageType.VersionSetup, 0)
+ End Sub
+ AddHandler sender.MouseRightButtonUp,
+ Sub()
+ PageVersionLeft.Version = Version
+ FrmMain.PageChange(FormMain.PageType.VersionSetup, 0)
+ End Sub
sender.Buttons = {BtnStar, BtnDel, BtnCont}
Else
Dim BtnCont As New MyIconButton With {.LogoScale = 1.15, .Logo = Logo.IconButtonOpen}
@@ -182,12 +184,8 @@
ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center)
ToolTipService.SetVerticalOffset(BtnCont, 30)
ToolTipService.SetHorizontalOffset(BtnCont, 2)
- AddHandler BtnCont.Click, Sub()
- PageVersionOverall.OpenVersionFolder(Version)
- End Sub
- AddHandler sender.MouseRightButtonUp, Sub()
- PageVersionOverall.OpenVersionFolder(Version)
- End Sub
+ AddHandler BtnCont.Click, Sub() PageVersionOverall.OpenVersionFolder(Version)
+ AddHandler sender.MouseRightButtonUp, Sub() PageVersionOverall.OpenVersionFolder(Version)
sender.Buttons = {BtnStar, BtnDel, BtnCont}
End If
End Sub
@@ -223,6 +221,7 @@
If(IsHintIndie, vbCrLf & "由于该版本开启了版本隔离,删除版本时该版本对应的存档、资源包、Mod 等文件也将被一并删除!", ""),
"版本删除确认", , "取消",, True)
Case 1
+ IniClearCache(Version.PathIndie & "options.txt")
IniClearCache(Version.Path & "PCL\Setup.ini")
If IsShiftPressed Then
DeleteDirectory(Version.Path)
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb
index 1e335a0..e521663 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb
+++ b/Plain Craft Launcher 2/Pages/PageSetup/ModSetup.vb
@@ -9,6 +9,8 @@
'''
Private ReadOnly SetupDict As New Dictionary(Of String, SetupEntry) From {
{"Identify", New SetupEntry("", Source:=SetupSource.Registry)},
+ {"WindowHeight", New SetupEntry(550)},
+ {"WindowWidth", New SetupEntry(900)},
{"HintDownloadThread", New SetupEntry(False, Source:=SetupSource.Registry)},
{"HintNotice", New SetupEntry(0, Source:=SetupSource.Registry)},
{"HintDownload", New SetupEntry(0, Source:=SetupSource.Registry)},
@@ -18,11 +20,16 @@
{"HintBuy", New SetupEntry(False, Source:=SetupSource.Registry)},
{"HintClearRubbish", New SetupEntry(0, Source:=SetupSource.Registry)},
{"HintUpdateMod", New SetupEntry(False, Source:=SetupSource.Registry)},
- {"TestSetupReader", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+ {"HintCustomCommand", New SetupEntry(False, Source:=SetupSource.Registry)},
+ {"HintCustomWarn", New SetupEntry(False, Source:=SetupSource.Registry)},
+ {"HintMoreAdvancedSetup", New SetupEntry(False, Source:=SetupSource.Registry)},
+ {"HintIndieSetup", New SetupEntry(False, Source:=SetupSource.Registry)},
+ {"HintExportConfig", New SetupEntry(False, Source:=SetupSource.Registry)},
{"SystemEula", New SetupEntry(False, Source:=SetupSource.Registry)},
{"SystemCount", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
{"SystemLaunchCount", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
{"SystemLastVersionReg", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
+ {"SystemHighestSavedBetaVersionReg", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
{"SystemHighestBetaVersionReg", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
{"SystemHighestAlphaVersionReg", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
{"SystemSetupVersionReg", New SetupEntry(VersionSetup, Source:=SetupSource.Registry)},
@@ -35,6 +42,7 @@
{"SystemSystemCache", New SetupEntry("", Source:=SetupSource.Registry)},
{"SystemSystemUpdate", New SetupEntry(0)},
{"SystemSystemActivity", New SetupEntry(0)},
+ {"CacheExportConfig", New SetupEntry("", Source:=SetupSource.Registry)},
{"CacheSavedPageUrl", New SetupEntry("", Source:=SetupSource.Registry)},
{"CacheSavedPageVersion", New SetupEntry("", Source:=SetupSource.Registry)},
{"CacheMsOAuthRefresh", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
@@ -42,6 +50,12 @@
{"CacheMsProfileJson", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
{"CacheMsUuid", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
{"CacheMsName", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+ {"CacheMsV2Migrated", New SetupEntry(False, Source:=SetupSource.Registry)},
+ {"CacheMsV2OAuthRefresh", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+ {"CacheMsV2Access", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+ {"CacheMsV2ProfileJson", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+ {"CacheMsV2Uuid", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
+ {"CacheMsV2Name", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
{"CacheNideAccess", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
{"CacheNideClient", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
{"CacheNideUuid", New SetupEntry("", Source:=SetupSource.Registry, Encoded:=True)},
@@ -80,18 +94,19 @@
{"LaunchArgumentJavaSelect", New SetupEntry("", Source:=SetupSource.Registry)},
{"LaunchArgumentJavaAll", New SetupEntry("[]", Source:=SetupSource.Registry)},
{"LaunchArgumentIndie", New SetupEntry(0)},
+ {"LaunchArgumentIndieV2", New SetupEntry(4)},
{"LaunchArgumentVisible", New SetupEntry(5, Source:=SetupSource.Registry)},
{"LaunchArgumentPriority", New SetupEntry(1, Source:=SetupSource.Registry)},
{"LaunchArgumentWindowWidth", New SetupEntry(854)},
{"LaunchArgumentWindowHeight", New SetupEntry(480)},
{"LaunchArgumentWindowType", New SetupEntry(1)},
{"LaunchArgumentRam", New SetupEntry(False, Source:=SetupSource.Registry)},
- {"LaunchAdvanceJvm", New SetupEntry("-XX:+UseG1GC -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Dfml.ignoreInvalidMinecraftCertificates=True -Dfml.ignorePatchDiscrepancies=True -Dlog4j2.formatMsgNoLookups=true")},
+ {"LaunchAdvanceJvm", New SetupEntry("-XX:+UseG1GC -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Djdk.lang.Process.allowAmbiguousCommands=true -Dfml.ignoreInvalidMinecraftCertificates=True -Dfml.ignorePatchDiscrepancies=True -Dlog4j2.formatMsgNoLookups=true")},
{"LaunchAdvanceGame", New SetupEntry("")},
{"LaunchAdvanceRun", New SetupEntry("")},
{"LaunchAdvanceRunWait", New SetupEntry(True)},
- {"LaunchAdvanceAssets", New SetupEntry(False)},
- {"LaunchAdvanceJava", New SetupEntry(False)},
+ {"LaunchAdvanceDisableJLW", New SetupEntry(False)},
+ {"LaunchAdvanceGraphicCard", New SetupEntry(True, Source:=SetupSource.Registry)},
{"LaunchRamType", New SetupEntry(0)},
{"LaunchRamCustom", New SetupEntry(15)},
{"LinkEula", New SetupEntry(False, Source:=SetupSource.Registry)},
@@ -105,10 +120,11 @@
{"ToolDownloadSpeed", New SetupEntry(42, Source:=SetupSource.Registry)},
{"ToolDownloadVersion", New SetupEntry(0, Source:=SetupSource.Registry)},
{"ToolDownloadTranslate", New SetupEntry(0, Source:=SetupSource.Registry)},
- {"ToolDownloadKeepModpack", New SetupEntry(False, 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)},
+ {"ToolModLocalNameStyle", New SetupEntry(0, Source:=SetupSource.Registry)},
{"ToolUpdateAlpha", New SetupEntry(0, Source:=SetupSource.Registry, Encoded:=True)},
{"ToolUpdateRelease", New SetupEntry(False, Source:=SetupSource.Registry)},
{"ToolUpdateSnapshot", New SetupEntry(False, Source:=SetupSource.Registry)},
@@ -145,6 +161,7 @@
{"UiHiddenPageSetup", New SetupEntry(False)},
{"UiHiddenPageOther", New SetupEntry(False)},
{"UiHiddenFunctionSelect", New SetupEntry(False)},
+ {"UiHiddenFunctionModUpdate", New SetupEntry(False)},
{"UiHiddenFunctionHidden", New SetupEntry(False)},
{"UiHiddenSetupLaunch", New SetupEntry(False)},
{"UiHiddenSetupUi", New SetupEntry(False)},
@@ -162,12 +179,14 @@
{"VersionAdvanceJava", New SetupEntry(False, Source:=SetupSource.Version)},
{"VersionAdvanceRun", New SetupEntry("", Source:=SetupSource.Version)},
{"VersionAdvanceRunWait", New SetupEntry(True, Source:=SetupSource.Version)},
+ {"VersionAdvanceDisableJLW", 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)},
{"VersionArgumentTitle", New SetupEntry("", Source:=SetupSource.Version)},
{"VersionArgumentInfo", New SetupEntry("", Source:=SetupSource.Version)},
{"VersionArgumentIndie", New SetupEntry(-1, Source:=SetupSource.Version)},
+ {"VersionArgumentIndieV2", New SetupEntry(False, Source:=SetupSource.Version)},
{"VersionArgumentJavaSelect", New SetupEntry("使用全局设置", Source:=SetupSource.Version)},
{"VersionServerEnter", New SetupEntry("", Source:=SetupSource.Version)},
{"VersionServerLogin", New SetupEntry(0, Source:=SetupSource.Version)},
@@ -192,17 +211,24 @@
Public Value
Public Source As SetupSource
- '加载状态:0/未读取 1/已读取未处理 2/已处理
+ '''
+ ''' 加载状态:0/未读取 1/已读取未处理 2/已处理
+ ''' 我也不知道当年写这坨的时候为啥没用 Enum……
+ '''
Public State As Byte = 0
- Public Type
+ Public Type As Type
Public Sub New(Value, Optional Source = SetupSource.Normal, Optional Encoded = False)
- Me.DefaultValue = Value
- Me.DefaultValueEncoded = If(Encoded, SecretEncrypt(Value, "PCL" & UniqueAddress), Value)
- Me.Encoded = Encoded
- Me.Value = Value
- Me.Source = Source
- Type = If(Value, New Object).GetType
+ Try
+ Me.DefaultValue = Value
+ Me.Encoded = Encoded
+ Me.Value = Value
+ Me.Source = Source
+ Me.Type = If(Value, New Object).GetType
+ Me.DefaultValueEncoded = If(Encoded, SecretEncrypt(Value, "PCL" & UniqueAddress), Value)
+ Catch ex As Exception
+ Log(ex, "初始化 SetupEntry 失败", LogLevel.Feedback) '#5095 的 fallback
+ End Try
End Sub
End Class
@@ -241,11 +267,8 @@
Case SetupSource.Registry
WriteReg(Key, Value)
Case SetupSource.Version
- If Version Is Nothing Then
- Throw New Exception("更改版本设置 " & Key & " 时未提供目标版本")
- Else
- WriteIni(Version.Path & "PCL\Setup.ini", Key, Value)
- End If
+ If Version Is Nothing Then Throw New Exception($"更改版本设置 {Key} 时未提供目标版本")
+ WriteIni(Version.Path & "PCL\Setup.ini", Key, Value)
End Select
'应用
'例如 VersionServerLogin 要求在设置之后再引发事件
@@ -303,18 +326,50 @@
Public Sub Reset(Key As String, Optional ForceReload As Boolean = False, Optional Version As McVersion = Nothing)
Dim E As SetupEntry = SetupDict(Key)
[Set](Key, E.DefaultValue, E, ForceReload, Version)
+ Select Case SetupDict(Key).Source
+ Case SetupSource.Normal
+ DeleteIniKey("Setup", Key)
+ Case SetupSource.Registry
+ DeleteReg(Key)
+ Case Else 'SetupSource.Version
+ If Version Is Nothing Then Throw New Exception($"重置版本设置 {Key} 时未提供目标版本")
+ DeleteIniKey(Version.Path & "PCL\Setup.ini", Key)
+ End Select
End Sub
+ '''
+ ''' 获取某个设置项的默认值。
+ '''
+ Public Function GetDefault(Key As String) As String
+ Return SetupDict(Key).DefaultValue
+ End Function
+ '''
+ ''' 某个设置项是否从未被设置过。
+ '''
+ Public Function IsUnset(Key As String, Optional Version As McVersion = Nothing) As Boolean
+ Select Case SetupDict(Key).Source
+ Case SetupSource.Normal
+ Return Not HasIniKey("Setup", Key)
+ Case SetupSource.Registry
+ Return Not HasReg(Key)
+ Case Else 'SetupSource.Version
+ If Version Is Nothing Then Throw New Exception($"判断版本设置 {Key} 是否存在时未提供目标版本")
+ Return Not HasIniKey(Version.Path & "PCL\Setup.ini", Key)
+ End Select
+ End Function
+ '''
+ ''' 读取设置。
+ '''
Private Sub Read(Key As String, ByRef E As SetupEntry, Version As McVersion)
Try
If Not E.State = 0 Then Exit Sub
- Dim SourceValue As String '先用 String 储存,避免类型转换
+ Dim SourceValue As String = Nothing '先用 String 储存,避免类型转换
Select Case E.Source
Case SetupSource.Normal
SourceValue = ReadIni("Setup", Key, E.DefaultValueEncoded)
Case SetupSource.Registry
SourceValue = ReadReg(Key, E.DefaultValueEncoded)
- Case Else 'SetupSource.Version
+ Case SetupSource.Version
If Version Is Nothing Then
Throw New Exception("读取版本设置 " & Key & " 时未提供目标版本")
Else
@@ -403,8 +458,20 @@
NetTaskThreadLimit = Value + 1
End Sub
Public Sub ToolDownloadCert(Value As Boolean)
- Net.ServicePointManager.ServerCertificateValidationCallback =
- If(Value, Nothing, New Net.Security.RemoteCertificateValidationCallback(Function() True))
+ ServicePointManager.ServerCertificateValidationCallback =
+ Function(Sender, Certificate, Chain, Failure)
+ Dim Request As HttpWebRequest = TryCast(Sender, HttpWebRequest)
+ If Failure = Net.Security.SslPolicyErrors.None Then Return True '已通过验证
+ '基于 #3018 和 #5879,只在访问正版登录 API 时跳过证书验证
+ Log($"[System] 未通过 SSL 证书验证({Failure}),提供的证书为 {Certificate?.Subject},URL:{Request?.Address}", LogLevel.Debug)
+ If Request Is Nothing Then
+ Return Not Value
+ ElseIf Request.Address.Host.Contains("xboxlive") OrElse Request.Address.Host.Contains("minecraftservices") Then
+ Return Not Value '根据设置决定是否忽略错误
+ Else
+ Return False
+ End If
+ End Function
End Sub
Public Sub ToolDownloadSpeed(Value As Integer)
If Value <= 14 Then
@@ -526,11 +593,13 @@
FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed
FrmSetupUI.PanCustomNet.Visibility = Visibility.Collapsed
FrmSetupUI.HintCustom.Visibility = Visibility.Collapsed
+ FrmSetupUI.HintCustomWarn.Visibility = Visibility.Collapsed
Case 1 '本地
FrmSetupUI.PanCustomPreset.Visibility = Visibility.Collapsed
FrmSetupUI.PanCustomLocal.Visibility = Visibility.Visible
FrmSetupUI.PanCustomNet.Visibility = Visibility.Collapsed
FrmSetupUI.HintCustom.Visibility = Visibility.Visible
+ FrmSetupUI.HintCustomWarn.Visibility = If(Setup.Get("HintCustomWarn"), Visibility.Collapsed, Visibility.Visible)
FrmSetupUI.HintCustom.Text = $"从 PCL 文件夹下的 Custom.xaml 读取主页内容。{vbCrLf}你可以手动编辑该文件,向主页添加文本、图片、常用网站、快捷启动等功能。"
FrmSetupUI.HintCustom.EventType = ""
FrmSetupUI.HintCustom.EventData = ""
@@ -539,6 +608,7 @@
FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed
FrmSetupUI.PanCustomNet.Visibility = Visibility.Visible
FrmSetupUI.HintCustom.Visibility = Visibility.Visible
+ 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"
@@ -547,6 +617,7 @@
FrmSetupUI.PanCustomLocal.Visibility = Visibility.Collapsed
FrmSetupUI.PanCustomNet.Visibility = Visibility.Collapsed
FrmSetupUI.HintCustom.Visibility = Visibility.Collapsed
+ FrmSetupUI.HintCustomWarn.Visibility = Visibility.Collapsed
End Select
FrmSetupUI.CardCustom.TriggerForceResize()
End Sub
@@ -592,7 +663,7 @@
FrmSetupUI.PanLogoChange.Visibility = Visibility.Visible
End If
Try
- FrmMain.ImageTitleLogo.Source = New MyBitmap(Path & "PCL\Logo.png")
+ FrmMain.ImageTitleLogo.Source = Path & "PCL\Logo.png"
Catch ex As Exception
FrmMain.ImageTitleLogo.Source = Nothing
Log(ex, "显示标题栏图片失败", LogLevel.Msgbox)
@@ -624,6 +695,9 @@
Public Sub UiHiddenFunctionSelect(Value As Boolean)
PageSetupUI.HiddenRefresh()
End Sub
+ Public Sub UiHiddenFunctionModUpdate(Value As Boolean)
+ PageSetupUI.HiddenRefresh()
+ End Sub
Public Sub UiHiddenFunctionHidden(Value As Boolean)
PageSetupUI.HiddenRefresh()
End Sub
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
index d34419c..ba28390 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
@@ -6,8 +6,172 @@
mc:Ignorable="d" x:Class="PageSetupLaunch"
PanScroll="{Binding ElementName=PanBack}">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ "
+ “
+ ”
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -54,176 +218,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- "
- “
- ”
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
@@ -232,24 +234,34 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb
index fe721e6..d11018b 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml.vb
@@ -7,6 +7,11 @@
'重复加载部分
PanBack.ScrollToHome()
RefreshRam(False)
+ If McVersionCurrent Is Nothing Then
+ BtnSwitch.Visibility = Visibility.Collapsed
+ Else
+ BtnSwitch.Visibility = Visibility.Visible
+ End If
'非重复加载部分
If IsLoad Then Exit Sub
@@ -32,7 +37,7 @@
'启动参数
TextArgumentTitle.Text = Setup.Get("LaunchArgumentTitle")
TextArgumentInfo.Text = Setup.Get("LaunchArgumentInfo")
- ComboArgumentIndie.SelectedIndex = Setup.Get("LaunchArgumentIndie")
+ ComboArgumentIndieV2.SelectedIndex = Setup.Get("LaunchArgumentIndieV2")
ComboArgumentVisibie.SelectedIndex = Setup.Get("LaunchArgumentVisible")
ComboArgumentPriority.SelectedIndex = Setup.Get("LaunchArgumentPriority")
ComboArgumentWindowType.SelectedIndex = Setup.Get("LaunchArgumentWindowType")
@@ -50,8 +55,8 @@
TextAdvanceGame.Text = Setup.Get("LaunchAdvanceGame")
TextAdvanceRun.Text = Setup.Get("LaunchAdvanceRun")
CheckAdvanceRunWait.Checked = Setup.Get("LaunchAdvanceRunWait")
- CheckAdvanceAssets.Checked = Setup.Get("LaunchAdvanceAssets")
- CheckAdvanceJava.Checked = Setup.Get("LaunchAdvanceJava")
+ CheckAdvanceDisableJLW.Checked = Setup.Get("LaunchAdvanceDisableJLW")
+ CheckAdvanceGraphicCard.Checked = Setup.Get("LaunchAdvanceGraphicCard")
Catch ex As NullReferenceException
Log(ex, "启动设置项存在异常,已被自动重置", LogLevel.Msgbox)
@@ -66,7 +71,7 @@
Try
Setup.Reset("LaunchArgumentTitle")
Setup.Reset("LaunchArgumentInfo")
- Setup.Reset("LaunchArgumentIndie")
+ Setup.Reset("LaunchArgumentIndieV2")
Setup.Reset("LaunchArgumentVisible")
Setup.Reset("LaunchArgumentWindowType")
Setup.Reset("LaunchArgumentWindowWidth")
@@ -79,11 +84,10 @@
Setup.Reset("LaunchSkinID")
Setup.Reset("LaunchAdvanceJvm")
Setup.Reset("LaunchAdvanceGame")
- Setup.Reset("LaunchAdvanceJava")
- Setup.Reset("LaunchAdvanceAssets")
Setup.Reset("LaunchAdvanceRun")
Setup.Reset("LaunchAdvanceRunWait")
-
+ Setup.Reset("LaunchAdvanceDisableJLW")
+ Setup.Reset("LaunchAdvanceGraphicCard")
Setup.Reset("LaunchArgumentJavaAll")
Setup.Reset("LaunchArgumentJavaSelect")
JavaSearchLoader.Start(IsForceRestart:=True)
@@ -107,10 +111,10 @@
Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderRamCustom.Change
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value)
End Sub
- Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndie.SelectionChanged, ComboArgumentVisibie.SelectionChanged, ComboArgumentWindowType.SelectionChanged, ComboArgumentPriority.SelectionChanged
+ Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndieV2.SelectionChanged, ComboArgumentVisibie.SelectionChanged, ComboArgumentWindowType.SelectionChanged, ComboArgumentPriority.SelectionChanged
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex)
End Sub
- Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceAssets.Change, CheckAdvanceJava.Change, CheckAdvanceRunWait.Change, CheckArgumentRam.Change
+ Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceRunWait.Change, CheckArgumentRam.Change, CheckAdvanceDisableJLW.Change, CheckAdvanceGraphicCard.Change
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked)
End Sub
@@ -165,7 +169,7 @@
Private Sub BtnSkinDelete_Click(sender As Object, e As EventArgs) Handles BtnSkinDelete.Click
Try
File.Delete(PathAppdata & "CustomSkin.png")
- RadioSkinType0.SetChecked(True, True, True)
+ RadioSkinType0.SetChecked(True, True)
Hint("离线皮肤已清空!", HintType.Finish)
Catch ex As Exception
Log(ex, "清空离线皮肤失败", LogLevel.Msgbox)
@@ -193,12 +197,12 @@
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
'获取内存情况
- Dim RamGame As Double = GetRam(McVersionCurrent, False)
- Dim RamTotal As Double = Math.Round(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024 / 1024 * 10) / 10
- Dim RamAvailable As Double = Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024 * 10) / 10
- Dim RamGameActual As Double = Math.Min(RamGame, RamAvailable)
- Dim RamUsed As Double = RamTotal - RamAvailable
- Dim RamEmpty As Double = Math.Round(MathClamp(RamTotal - RamUsed - RamGame, 0, 1000) * 10) / 10
+ Dim RamGame As Double = Math.Round(GetRam(McVersionCurrent, False), 5)
+ Dim RamTotal As Double = Math.Round(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024 / 1024, 1)
+ Dim RamAvailable As Double = Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024, 1)
+ Dim RamGameActual As Double = Math.Round(Math.Min(RamGame, RamAvailable), 5)
+ Dim RamUsed As Double = Math.Round(RamTotal - RamAvailable, 5)
+ Dim RamEmpty As Double = Math.Round(MathClamp(RamTotal - RamUsed - RamGame, 0, 1000), 1)
'设置最大可用内存
If RamTotal <= 1.5 Then
SliderRamCustom.MaxValue = Math.Max(Math.Floor((RamTotal - 0.3) / 0.1), 1)
@@ -207,14 +211,14 @@
ElseIf RamTotal <= 16 Then
SliderRamCustom.MaxValue = Math.Floor((RamTotal - 8) / 1) + 25
Else
- SliderRamCustom.MaxValue = Math.Min(Math.Floor((RamTotal - 16) / 2) + 33, 41)
+ SliderRamCustom.MaxValue = Math.Floor((RamTotal - 16) / 2) + 33
End If
'设置文本
LabRamGame.Text = If(RamGame = Math.Floor(RamGame), RamGame & ".0", RamGame) & " GB" &
If(RamGame <> RamGameActual, " (可用 " & If(RamGameActual = Math.Floor(RamGameActual), RamGameActual & ".0", RamGameActual) & " GB)", "")
LabRamUsed.Text = If(RamUsed = Math.Floor(RamUsed), RamUsed & ".0", RamUsed) & " GB"
LabRamTotal.Text = " / " & If(RamTotal = Math.Floor(RamTotal), RamTotal & ".0", RamTotal) & " GB"
- LabRamWarn.Visibility = If(RamGame = 1 AndAlso Not JavaIs64Bit() AndAlso Not Is32BitSystem, Visibility.Visible, Visibility.Collapsed)
+ LabRamWarn.Visibility = If(RamGame = 1 AndAlso Not JavaIs64Bit() AndAlso Not Is32BitSystem AndAlso JavaList.Any, Visibility.Visible, Visibility.Collapsed)
If ShowAnim Then
'宽度动画
AniStart({
@@ -321,6 +325,11 @@
''' 获取当前设置的 RAM 值。单位为 GB。
'''
Public Shared Function GetRam(Version As McVersion, UseVersionJavaSetup As Boolean, Optional Is32BitJava As Boolean? = Nothing) As Double
+
+ '------------------------------------------
+ ' 修改下方代码时需要一并修改 PageVersionSetup
+ '------------------------------------------
+
Dim RamGive As Double
If Setup.Get("LaunchRamType") = 0 Then
'自动配置
@@ -391,8 +400,7 @@ PreFin:
End If
'若使用 32 位 Java,则限制为 1G
If If(Is32BitJava, Not JavaIs64Bit(If(UseVersionJavaSetup, Version, Nothing))) Then RamGive = Math.Min(1, RamGive)
- '封顶 32G
- Return Math.Min(32, RamGive)
+ Return RamGive
End Function
#End Region
@@ -409,7 +417,7 @@ PreFin:
Dim SelectedItem As MyComboBoxItem = Nothing
Dim SelectedBySetup As String = Setup.Get("LaunchArgumentJavaSelect")
Try
- For Each Java In Sort(JavaList.Clone(), Function(l, r) l.VersionCode < r.VersionCode)
+ For Each Java In JavaList.Clone().OrderByDescending(Function(v) v.VersionCode)
Dim ListItem = New MyComboBoxItem With {.Content = Java.ToString, .ToolTip = Java.PathFolder, .Tag = Java}
ToolTipService.SetHorizontalOffset(ListItem, 400)
ComboArgumentJava.Items.Add(ListItem)
@@ -507,7 +515,7 @@ PreFin:
#End Region
-#Region "启动参数"
+#Region "其他选项"
Private Sub WindowTypeUIRefresh() Handles ComboArgumentWindowType.SelectionChanged
If ComboArgumentWindowType Is Nothing Then Exit Sub
@@ -545,6 +553,12 @@ PreFin:
End If
End Sub
+ '版本隔离提示
+ Private Sub ComboArgumentIndie_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboArgumentIndieV2.SelectionChanged
+ If AniControlEnabled <> 0 Then Exit Sub
+ MyMsgBox("默认策略只会对今后新安装的版本生效。" & vbCrLf & "已有版本的隔离策略需要在它的版本设置中调整。")
+ End Sub
+
#End Region
#Region "高级设置"
@@ -553,6 +567,22 @@ PreFin:
CheckAdvanceRunWait.Visibility = If(TextAdvanceRun.Text = "", Visibility.Collapsed, Visibility.Visible)
End Sub
+ 'JVM 参数重设
+ Private Sub TextAdvanceJvm_TextChanged() Handles TextAdvanceJvm.ValidatedTextChanged
+ BtnAdvanceJvmReset.Visibility = If(TextAdvanceJvm.Text = Setup.GetDefault("LaunchAdvanceJvm"), Visibility.Hidden, Visibility.Visible)
+ End Sub
+ Private Sub BtnAdvanceJvmReset_Click(sender As Object, e As EventArgs) Handles BtnAdvanceJvmReset.Click
+ Setup.Reset("LaunchAdvanceJvm")
+ Reload()
+ End Sub
+
#End Region
+ '切换到版本独立设置
+ Private Sub BtnSwitch_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnSwitch.Click
+ McVersionCurrent.Load()
+ PageVersionLeft.Version = McVersionCurrent
+ FrmMain.PageChange(FormMain.PageType.VersionSetup, FormMain.PageSubType.VersionSetup)
+ 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 795f41e..22a9a63 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.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" mc:Ignorable="d" x:Class="PageSetupLeft"
d:DesignWidth="152" AnimatedControl="{Binding ElementName=PanItem, Mode=OneWay}">
-
@@ -12,15 +12,16 @@
-
+
-
+
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml.vb
index 1787316..9c98d0b 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLeft.xaml.vb
@@ -115,7 +115,7 @@
PageID = ID
Catch ex As Exception
- Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+ Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
Finally
AniControlEnabled -= 1
End Try
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb
index 55e5b6a..73fa03e 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLink.xaml.vb
@@ -45,11 +45,6 @@
End Sub
Private Sub BtnHiperLog_Click(sender As Object, e As EventArgs) Handles BtnHiperLog.Click
- If File.Exists(PageLinkHiper.PathHiper & "logs\hiper.log") Then
- OpenExplorer("/select,""" & PageLinkHiper.PathHiper & "logs\hiper.log""")
- Else
- Hint("没有找到 HiPer 联机模块的日志!")
- End If
End Sub
End Class
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
index 20ac75c..c8ea952 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
@@ -7,9 +7,43 @@
PanScroll="{Binding ElementName=PanBack}">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -20,53 +54,31 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
+
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb
index 0b19b11..c348df0 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml.vb
@@ -1,33 +1,5 @@
Class PageSetupSystem
-#Region "语言"
- 'Private Sub PageSetupUI_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
- ' AniControlEnabled -= 1
-
- ' '读取设置
- ' Select Case Lang
- ' Case "zh_CN"
- ' ComboLang.SelectedIndex = 0
- ' Case "zh_HK"
- ' ComboLang.SelectedIndex = 1
- ' Case "en_US"
- ' ComboLang.SelectedIndex = 2
- ' End Select
- ' CheckDebug.Checked = ReadReg("SystemDebugMode", "False")
-
- ' AniControlEnabled += 1
- 'End Sub
-
- 'Private Sub RefreshLang() Handles ComboLang.SelectionChanged
- ' If IsLoaded Then
- ' If Not ComboLang.IsLoaded Then Exit Sub
- ' Lang = CType(ComboLang.SelectedItem, MyComboBoxItem).Tag
- ' Application.Current.Resources.MergedDictionaries(1) = New ResourceDictionary With {.Source = New Url("Languages\" & Lang & ".xaml", UrlKind.Relative)}
- ' WriteReg("Lang", Lang)
- ' End If
- 'End Sub
-#End Region
-
Private Shadows IsLoaded As Boolean = False
Private Sub PageSetupSystem_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
@@ -58,12 +30,14 @@
SliderDownloadThread.Value = Setup.Get("ToolDownloadThread")
SliderDownloadSpeed.Value = Setup.Get("ToolDownloadSpeed")
ComboDownloadVersion.SelectedIndex = Setup.Get("ToolDownloadVersion")
- ComboDownloadTranslate.SelectedIndex = Setup.Get("ToolDownloadTranslate")
- ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod")
- CheckDownloadKeepModpack.Checked = Setup.Get("ToolDownloadKeepModpack")
- CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt")
CheckDownloadCert.Checked = Setup.Get("ToolDownloadCert")
+ 'Mod 与整合包
+ ComboDownloadTranslateV2.SelectedIndex = Setup.Get("ToolDownloadTranslateV2")
+ 'ComboDownloadMod.SelectedIndex = Setup.Get("ToolDownloadMod")
+ ComboModLocalNameStyle.SelectedIndex = Setup.Get("ToolModLocalNameStyle")
+ CheckDownloadIgnoreQuilt.Checked = Setup.Get("ToolDownloadIgnoreQuilt")
+
'Minecraft 更新提示
CheckUpdateRelease.Checked = Setup.Get("ToolUpdateRelease")
CheckUpdateSnapshot.Checked = Setup.Get("ToolUpdateSnapshot")
@@ -90,11 +64,11 @@
Setup.Reset("ToolDownloadThread")
Setup.Reset("ToolDownloadSpeed")
Setup.Reset("ToolDownloadVersion")
- Setup.Reset("ToolDownloadTranslate")
- Setup.Reset("ToolDownloadKeepModpack")
+ Setup.Reset("ToolDownloadTranslateV2")
Setup.Reset("ToolDownloadIgnoreQuilt")
Setup.Reset("ToolDownloadCert")
Setup.Reset("ToolDownloadMod")
+ Setup.Reset("ToolModLocalNameStyle")
Setup.Reset("ToolUpdateRelease")
Setup.Reset("ToolUpdateSnapshot")
Setup.Reset("ToolHelpChinese")
@@ -116,13 +90,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, CheckDownloadKeepModpack.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
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, ComboDownloadTranslate.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.SelectionChanged, ComboDownloadMod.SelectionChanged
+ Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboDownloadVersion.SelectionChanged, ComboModLocalNameStyle.SelectionChanged, ComboDownloadTranslateV2.SelectionChanged, ComboSystemUpdate.SelectionChanged, ComboSystemActivity.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
@@ -170,26 +144,24 @@
End Sub
'自动更新
- Private Sub ComboSystemActivity_SizeChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboSystemActivity.SelectionChanged
+ Private Sub ComboSystemActivity_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboSystemActivity.SelectionChanged
If AniControlEnabled <> 0 Then Exit Sub
- If ComboSystemActivity.SelectedIndex = 2 Then
- If MyMsgBox("若选择此项,即使在将来出现严重问题时,你也无法获取相关通知。" & vbCrLf &
- "例如,如果发现某个版本游戏存在严重 Bug,你可能就会因为无法得到通知而导致无法预知的后果。" & vbCrLf & vbCrLf &
- "一般选择 仅在有重要通知时显示公告 就可以让你尽量不受打扰了。" & vbCrLf &
- "除非你在制作服务器整合包,或时常手动更新启动器,否则极度不推荐选择此项!", "警告", "我知道我在做什么", "取消", IsWarn:=True) = 2 Then
- ComboSystemActivity.SelectedItem = e.RemovedItems(0)
- End If
+ If ComboSystemActivity.SelectedIndex <> 2 Then Exit Sub
+ If MyMsgBox("若选择此项,即使在将来出现严重问题时,你也无法获取相关通知。" & vbCrLf &
+ "例如,如果发现某个版本游戏存在严重 Bug,你可能就会因为无法得到通知而导致无法预知的后果。" & vbCrLf & vbCrLf &
+ "一般选择 仅在有重要通知时显示公告 就可以让你尽量不受打扰了。" & vbCrLf &
+ "除非你在制作服务器整合包,或时常手动更新启动器,否则极度不推荐选择此项!", "警告", "我知道我在做什么", "取消", IsWarn:=True) = 2 Then
+ ComboSystemActivity.SelectedItem = e.RemovedItems(0)
End If
End Sub
- Private Sub ComboSystemUpdate_SizeChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboSystemUpdate.SelectionChanged
+ Private Sub ComboSystemUpdate_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboSystemUpdate.SelectionChanged
If AniControlEnabled <> 0 Then Exit Sub
- If ComboSystemUpdate.SelectedIndex = 3 Then
- If MyMsgBox("若选择此项,即使在启动器将来出现严重问题时,你也无法获取更新并获得修复。" & vbCrLf &
- "例如,如果官方修改了登录方式,从而导致现有启动器无法登录,你可能就会因为无法更新而无法开始游戏。" & vbCrLf & vbCrLf &
- "一般选择 仅在有重大漏洞更新时显示提示 就可以让你尽量不受打扰了。" & vbCrLf &
- "除非你在制作服务器整合包,或时常手动更新启动器,否则极度不推荐选择此项!", "警告", "我知道我在做什么", "取消", IsWarn:=True) = 2 Then
- ComboSystemUpdate.SelectedItem = e.RemovedItems(0)
- End If
+ If ComboSystemUpdate.SelectedIndex <> 3 Then Exit Sub
+ If MyMsgBox("若选择此项,即使在启动器将来出现严重问题时,你也无法获取更新并获得修复。" & vbCrLf &
+ "例如,如果官方修改了登录方式,从而导致现有启动器无法登录,你可能就会因为无法更新而无法开始游戏。" & vbCrLf & vbCrLf &
+ "一般选择 仅在有重大漏洞更新时显示提示 就可以让你尽量不受打扰了。" & vbCrLf &
+ "除非你在制作服务器整合包,或时常手动更新启动器,否则极度不推荐选择此项!", "警告", "我知道我在做什么", "取消", IsWarn:=True) = 2 Then
+ ComboSystemUpdate.SelectedItem = e.RemovedItems(0)
End If
End Sub
Private Sub BtnSystemUpdate_Click(sender As Object, e As EventArgs) Handles BtnSystemUpdate.Click
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml
index 4f6e7f7..da4f899 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml
@@ -72,10 +72,10 @@
-
+
-
-
+
+
@@ -179,7 +179,7 @@
-
+
@@ -206,6 +206,8 @@
+
@@ -225,6 +227,7 @@
+
@@ -243,6 +246,11 @@
+
+
+
+
+
@@ -257,7 +265,7 @@
-
+
@@ -276,7 +284,7 @@
-
+
@@ -286,8 +294,9 @@
-
-
+
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
index 57b6279..1f697f4 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
@@ -25,14 +25,15 @@
Next
End If
+ AniControlEnabled += 1
+ Reload() '#4826,在每次进入页面时都刷新一下
+ AniControlEnabled -= 1
+
'非重复加载部分
If IsLoaded Then Exit Sub
IsLoaded = True
- AniControlEnabled += 1
SliderLoad()
- Reload()
- AniControlEnabled -= 1
#If BETA Then
PanLauncherHide.Visibility = Visibility.Visible
@@ -41,8 +42,8 @@
'设置解锁
If Not RadioLauncherTheme8.IsEnabled Then LabLauncherTheme8Copy.ToolTip = "累积赞助达到 ¥23.33 后,在爱发电私信发送【解锁码】以解锁。" & vbCrLf & "右键打开赞助页面,如果觉得 PCL 做得还不错就支持一下吧 =w=!"
RadioLauncherTheme8.ToolTip = "累积赞助达到 ¥23.33 后,在爱发电私信发送【解锁码】以解锁"
- If Not RadioLauncherTheme9.IsEnabled Then LabLauncherTheme9Copy.ToolTip = "· 反馈一个 Bug,在标记为 [完成] 后回复识别码要求解锁(右键打开反馈页面)" & vbCrLf & "· 提交一个 Pull Request,在合并后回复识别码要求解锁"
- RadioLauncherTheme9.ToolTip = "· 反馈一个 Bug,在标记为 [完成] 后回复识别码要求解锁" & vbCrLf & "· 提交一个 Pull Request,在合并后回复识别码要求解锁"
+ If Not RadioLauncherTheme9.IsEnabled Then LabLauncherTheme9Copy.ToolTip = "· 反馈一个 Bug,在标记为 [完成] 后回复识别码要求解锁(右键打开反馈页面)" & vbCrLf & "· 提交一个 Pull Request 或主页预设,在标记为 [完成] 后回复识别码要求解锁"
+ RadioLauncherTheme9.ToolTip = "· 反馈一个 Bug,在标记为 [完成] 后回复识别码要求解锁" & vbCrLf & "· 提交一个 Pull Request 或主页预设,在标记为 [完成] 后回复识别码要求解锁"
'极客蓝的处理在 ThemeCheck 中
End Sub
@@ -97,6 +98,7 @@
CheckHiddenPageSetup.Checked = Setup.Get("UiHiddenPageSetup")
CheckHiddenPageOther.Checked = Setup.Get("UiHiddenPageOther")
CheckHiddenFunctionSelect.Checked = Setup.Get("UiHiddenFunctionSelect")
+ CheckHiddenFunctionModUpdate.Checked = Setup.Get("UiHiddenFunctionModUpdate")
CheckHiddenFunctionHidden.Checked = Setup.Get("UiHiddenFunctionHidden")
CheckHiddenSetupLaunch.Checked = Setup.Get("UiHiddenSetupLaunch")
CheckHiddenSetupUI.Checked = Setup.Get("UiHiddenSetupUi")
@@ -147,6 +149,7 @@
Setup.Reset("UiHiddenPageSetup")
Setup.Reset("UiHiddenPageOther")
Setup.Reset("UiHiddenFunctionSelect")
+ Setup.Reset("UiHiddenFunctionModUpdate")
Setup.Reset("UiHiddenFunctionHidden")
Setup.Reset("UiHiddenSetupLaunch")
Setup.Reset("UiHiddenSetupUi")
@@ -174,7 +177,7 @@
Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboBackgroundSuit.SelectionChanged, ComboCustomPreset.SelectionChanged
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex)
End Sub
- Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckMusicStop.Change, CheckMusicRandom.Change, CheckMusicAuto.Change, CheckBackgroundColorful.Change, CheckLogoLeft.Change, CheckLauncherLogo.Change, CheckHiddenFunctionHidden.Change, CheckHiddenFunctionSelect.Change, CheckHiddenPageDownload.Change, CheckHiddenPageLink.Change, CheckHiddenPageOther.Change, CheckHiddenPageSetup.Change, CheckHiddenSetupLaunch.Change, CheckHiddenSetupSystem.Change, CheckHiddenSetupLink.Change, CheckHiddenSetupUI.Change, CheckHiddenOtherAbout.Change, CheckHiddenOtherFeedback.Change, CheckHiddenOtherVote.Change, CheckHiddenOtherHelp.Change, CheckHiddenOtherTest.Change, CheckMusicStart.Change, CheckLauncherEmail.Change
+ Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckMusicStop.Change, CheckMusicRandom.Change, CheckMusicAuto.Change, CheckBackgroundColorful.Change, CheckLogoLeft.Change, CheckLauncherLogo.Change, CheckHiddenFunctionHidden.Change, CheckHiddenFunctionSelect.Change, CheckHiddenFunctionModUpdate.Change, CheckHiddenPageDownload.Change, CheckHiddenPageLink.Change, CheckHiddenPageOther.Change, CheckHiddenPageSetup.Change, CheckHiddenSetupLaunch.Change, CheckHiddenSetupSystem.Change, CheckHiddenSetupLink.Change, CheckHiddenSetupUI.Change, CheckHiddenOtherAbout.Change, CheckHiddenOtherFeedback.Change, CheckHiddenOtherVote.Change, CheckHiddenOtherHelp.Change, CheckHiddenOtherTest.Change, CheckMusicStart.Change, CheckLauncherEmail.Change
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked)
End Sub
Private Shared Sub TextBoxChange(sender As MyTextBox, e As Object) Handles TextLogoText.ValidatedTextChanged, TextCustomNet.ValidatedTextChanged
@@ -186,7 +189,7 @@
'背景图片
Private Sub BtnUIBgOpen_Click(sender As Object, e As EventArgs) Handles BtnBackgroundOpen.Click
- OpenExplorer("""" & Path & "PCL\Pictures""")
+ OpenExplorer(Path & "PCL\Pictures\")
End Sub
Private Sub BtnBackgroundRefresh_Click(sender As Object, e As EventArgs) Handles BtnBackgroundRefresh.Click
BackgroundRefresh(True, True)
@@ -269,17 +272,16 @@
End Try
End Sub
-
'顶部栏
Private Sub BtnLogoChange_Click(sender As Object, e As EventArgs) Handles BtnLogoChange.Click
- Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif", "选择图片")
+ Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片")
If FileName = "" Then Exit Sub
Try
'拷贝文件
File.Delete(Path & "PCL\Logo.png")
CopyFile(FileName, Path & "PCL\Logo.png")
'设置当前显示
- FrmMain.ImageTitleLogo.Source = New MyBitmap(Path & "PCL\Logo.png")
+ FrmMain.ImageTitleLogo.Source = Path & "PCL\Logo.png"
Catch ex As Exception
If ex.Message.Contains("参数无效") Then
Log("改变标题栏图片失败,该图片文件可能并非标准格式。" & vbCrLf &
@@ -296,7 +298,7 @@ Refresh:
'已有图片则不再选择
If File.Exists(Path & "PCL\Logo.png") Then
Try
- FrmMain.ImageTitleLogo.Source = New MyBitmap(Path & "PCL\Logo.png")
+ FrmMain.ImageTitleLogo.Source = Path & "PCL\Logo.png"
Catch ex As Exception
If ex.Message.Contains("参数无效") Then
Log("调整标题栏图片失败,该图片文件可能并非标准格式。" & vbCrLf &
@@ -315,7 +317,7 @@ Refresh:
Exit Sub
End If
'没有图片则要求选择
- Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif)|*.png;*.jpg;*.gif", "选择图片")
+ Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片")
If FileName = "" Then
FrmMain.ImageTitleLogo.Source = Nothing
e.Handled = True
@@ -333,7 +335,7 @@ Refresh:
Private Sub BtnLogoDelete_Click(sender As Object, e As EventArgs) Handles BtnLogoDelete.Click
Try
File.Delete(Path & "PCL\Logo.png")
- RadioLogoType1.SetChecked(True, True, True)
+ RadioLogoType1.SetChecked(True, True)
Hint("标题栏图片已清空!", HintType.Finish)
Catch ex As Exception
Log(ex, "清空标题栏图片失败", LogLevel.Msgbox)
@@ -342,7 +344,7 @@ Refresh:
'背景音乐
Private Sub BtnMusicOpen_Click(sender As Object, e As EventArgs) Handles BtnMusicOpen.Click
- OpenExplorer("""" & Path & "PCL\Musics""")
+ OpenExplorer(Path & "PCL\Musics\")
End Sub
Private Sub BtnMusicRefresh_Click(sender As Object, e As EventArgs) Handles BtnMusicRefresh.Click
MusicRefreshPlay(True)
@@ -405,7 +407,7 @@ Refresh:
End If
WriteFile(Path & "PCL\Custom.xaml", GetResources("Custom"))
Hint("教学文件已生成!", HintType.Finish)
- OpenExplorer("/select,""" & Path & "PCL\Custom.xaml""")
+ OpenExplorer(Path & "PCL\Custom.xaml")
Catch ex As Exception
Log(ex, "生成教学文件失败", LogLevel.Feedback)
End Try
@@ -428,8 +430,8 @@ Refresh:
RadioLauncherTheme5Gray.Opacity -= 0.23
RadioLauncherTheme5.Opacity += 0.23
AniStart({
- AaOpacity(RadioLauncherTheme5Gray, 1, 1000),
- AaOpacity(RadioLauncherTheme5, -1, 1000)
+ AaOpacity(RadioLauncherTheme5Gray, 1, 1000 * AniSpeed),
+ AaOpacity(RadioLauncherTheme5, -1, 1000 * AniSpeed)
}, "ThemeUnlock")
If RadioLauncherTheme5Gray.Opacity < 0.08 Then
ThemeUnlock(5, UnlockHint:="隐藏主题 玄素黑 已解锁!")
@@ -450,10 +452,10 @@ Refresh:
End If
End If
End Sub
- Private Sub LabLauncherTheme8Copy_MouseRightButtonUp() Handles LabLauncherTheme8Copy.MouseRightButtonUp
+ Private Sub LabLauncherTheme8Copy_MouseRightButtonUp() Handles LabLauncherTheme8Copy.MouseRightButtonUp, RadioLauncherTheme8.MouseRightButtonUp
OpenWebsite("https://afdian.com/a/LTCat")
End Sub
- Private Sub LabLauncherTheme9Copy_MouseRightButtonUp() Handles LabLauncherTheme9Copy.MouseRightButtonUp
+ Private Sub LabLauncherTheme9Copy_MouseRightButtonUp() Handles LabLauncherTheme9Copy.MouseRightButtonUp, RadioLauncherTheme9.MouseRightButtonUp
PageOtherLeft.TryFeedback()
End Sub
@@ -662,7 +664,7 @@ Refresh:
#End Region
- '公开预览版
+ '赞助
Private Sub BtnLauncherDonate_Click(sender As Object, e As EventArgs) Handles BtnLauncherDonate.Click
OpenWebsite("https://afdian.com/a/LTCat")
End Sub
@@ -673,24 +675,26 @@ Refresh:
SliderLauncherOpacity.GetHintText = Function(v) Math.Round(40 + v * 0.1) & "%"
SliderLauncherHue.GetHintText = Function(v) v & "°"
SliderLauncherSat.GetHintText = Function(v) v & "%"
- SliderLauncherDelta.GetHintText = Function(Value As Integer)
- If Value > 90 Then
- Return "+" & (Value - 90)
- ElseIf Value = 90 Then
- Return 0
- Else
- Return Value - 90
- End If
- End Function
- SliderLauncherLight.GetHintText = Function(Value As Integer)
- If Value > 20 Then
- Return "+" & (Value - 20)
- ElseIf Value = 20 Then
- Return 0
- Else
- Return Value - 20
- End If
- End Function
+ SliderLauncherDelta.GetHintText =
+ Function(Value As Integer)
+ If Value > 90 Then
+ Return "+" & (Value - 90)
+ ElseIf Value = 90 Then
+ Return 0
+ Else
+ Return Value - 90
+ End If
+ End Function
+ SliderLauncherLight.GetHintText =
+ Function(Value As Integer)
+ If Value > 20 Then
+ Return "+" & (Value - 20)
+ ElseIf Value = 20 Then
+ Return 0
+ Else
+ Return Value - 20
+ End If
+ End Function
SliderBackgroundOpacity.GetHintText = Function(v) Math.Round(v * 0.1) & "%"
SliderBackgroundBlur.GetHintText = Function(v) v & " 像素"
End Sub
diff --git a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb
index 665ff21..b8c81b0 100644
--- a/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSpeedLeft.xaml.vb
@@ -92,15 +92,12 @@ Public Class PageSpeedLeft
Card.Children.Add(GetObjectFromXML(""))
Dim Tb As TextBlock = GetObjectFromXML("")
Tb.Text = GetExceptionDetail(Loader.Error)
- AddHandler Tb.MouseLeftButtonDown, Sub(sender As TextBlock, e As EventArgs)
- ClipboardSet(sender.Text, False)
- Hint("已复制错误详情!", HintType.Finish)
- End Sub
+ AddHandler Tb.MouseLeftButtonDown,
+ Sub(sender As TextBlock, e As EventArgs)
+ ClipboardSet(sender.Text, False)
+ Hint("已复制错误详情!", HintType.Finish)
+ End Sub
Card.Children.Add(Tb)
- ''删除取消按钮
- 'For Each Element As FrameworkElement In CType(Card.Parent, Panel).Children
- ' If Element.Name = "BtnCancel" Then AniDispose(CType(Element, MyIconButton), True)
- 'Next
#End Region
Case LoadState.Finished
#Region "完成,销毁卡片并返回"
@@ -195,11 +192,10 @@ Public Class PageSpeedLeft
Sub(sender As MyIconButton, e As EventArgs)
AniDispose(sender, False)
AniDispose(Card, True, Sub() If FrmSpeedRight.PanMain.Children.Count = 0 AndAlso FrmMain.PageCurrent = FormMain.PageType.DownloadManager Then FrmMain.PageBack())
- Dim LoaderName = If(Loader Is Nothing, "null", Loader.Name)
- RightCards.Remove(LoaderName)
- LoaderTaskbar.TryTake(Loader)
- Log($"[Taskbar] 关闭下载管理卡片:{LoaderName},且移出任务列表")
- RunInThread(Sub() Loader?.Abort())
+ RightCards.Remove(Loader.Name)
+ LoaderTaskbar.Remove(Loader)
+ Log($"[Taskbar] 关闭下载管理卡片:{Loader.Name},且移出任务列表")
+ RunInThread(Sub() Loader.Abort())
End Sub
'如果已经失败,再刷新一次,修改成失败的控件
If Loader.State = LoadState.Failed Then
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml
new file mode 100644
index 0000000..46c1ab0
--- /dev/null
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb
new file mode 100644
index 0000000..7473e86
--- /dev/null
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb
@@ -0,0 +1,721 @@
+Imports System.IO.Compression
+
+Public Class ExportOption
+ Public Property Title As String
+ Public Property Description As String
+ Public Property Rules As String
+ '''
+ ''' 如果 Rules 为空,则根据 ShowRules 的内容判断是否应该显示这个复选框。
+ ''' 如果 ShowRules 也为空,则始终显示。
+ '''
+ Public Property ShowRules As String
+ Public Property DefaultChecked As Boolean
+ Public Property RequireModLoader As Boolean = False
+ Public Property RequireOptiFine As Boolean = False
+ Public Property RequireModLoaderOrOptiFine As Boolean = False
+End Class
+
+Public Class PageVersionExport
+ Implements IRefreshable
+
+ Private CurrentVersion As String = ""
+ Private Sub PageVersionExport_Loaded() Handles Me.Loaded
+ AniControlEnabled += 1
+ If CurrentVersion <> PageVersionLeft.Version.Path Then RefreshAll() '切换到了另一个版本,重置页面
+ BtnAdvancedHelp.EventData = If(VersionBranchName = "Release", "指南/整合包制作 - Public.json", "指南/整合包制作 - Snapshot.json")
+ AniControlEnabled -= 1
+ End Sub
+ Public Sub RefreshAll() Implements IRefreshable.Refresh
+ Log($"[Export] 刷新导出页面")
+ HintOptiFine.Visibility = If(PageVersionLeft.Version.Version.HasOptiFine, Visibility.Visible, Visibility.Collapsed)
+ CurrentVersion = PageVersionLeft.Version.Path
+ TextExportName.Text = ""
+ TextExportName.HintText = PageVersionLeft.Version.Name
+ TextExportVersion.Text = ""
+ TextExportVersion.HintText = "1.0.0"
+ CheckAdvancedInclude.Checked = False
+ CheckAdvancedModrinth.Checked = False
+ GetExportOption(CheckOptionsBasic).Description = PageVersionLeft.Version.GetDefaultDescription()
+ ResetConfigOverrides()
+ ReloadAllSubOptions()
+ RefreshAllOptionsUI()
+ PanBack.ScrollToHome()
+ End Sub
+
+#Region "子选项"
+ Private SubOptionBlackList As String() = {"Quark Programmer Art.zip", "+ EuphoriaPatches_"}
+
+ '''
+ ''' 动态生成子文件夹下的选项,例如资源包、存档等。
+ '''
+ Private Sub ReloadAllSubOptions()
+ ReloadSubOptions(PanOptionsResourcePacks, True, True, "resourcepacks", "texturepacks")
+ ReloadSubOptions(PanOptionsSaves, False, True, "saves")
+ ReloadSubOptions(PanOptionsShaderPacks, True, True, "shaderpacks")
+ End Sub
+
+ Private Sub ReloadSubOptions(Panel As StackPanel, AcceptCompressedFile As Boolean, AcceptFolder As Boolean, ParamArray Folders As String())
+ Panel.Children.Clear()
+ For Each Folder In Folders
+ Dim TargetFolder As New DirectoryInfo(PageVersionLeft.Version.PathIndie & Folder)
+ If Not TargetFolder.Exists() Then Continue For
+ '查找文件夹下的对应项
+ If AcceptCompressedFile Then
+ For Each File In TargetFolder.EnumerateFiles("*.zip").Concat(TargetFolder.EnumerateFiles("*.rar"))
+ If SubOptionBlackList.Any(Function(b) File.Name.ContainsF(b)) Then Continue For
+ Panel.Children.Add(New MyCheckBox With {. _
+ Tag = New ExportOption With {.Title = File.Name, .DefaultChecked = True, .Rules = EscapeLikePattern($"{Folder}/{File.Name}")}})
+ Next
+ End If
+ If AcceptFolder Then
+ For Each SubFolder In TargetFolder.EnumerateDirectories().OrderByDescending(Function(f) f.LastWriteTime)
+ If SubOptionBlackList.Any(Function(b) SubFolder.Name.ContainsF(b)) Then Continue For
+ If Not SubFolder.EnumerateFileSystemInfos().Any() Then Continue For
+ Dim NewCheckBox As New MyCheckBox With {. _
+ Tag = New ExportOption With {.Title = SubFolder.Name, .DefaultChecked = True, .Rules = EscapeLikePattern($"{Folder}/{SubFolder.Name}/")}}
+ If Panel Is PanOptionsSaves Then GetExportOption(NewCheckBox).Description = SubFolder.LastWriteTime.ToString("yyyy'/'MM'/'dd HH':'mm")
+ Panel.Children.Add(NewCheckBox)
+ Next
+ End If
+ Next
+ End Sub
+
+#End Region
+
+#Region "选项"
+
+ '''
+ ''' 重新确认是否应该显示每个选项,并将 ExportOption 同步到 UI。
+ '''
+ Private Sub RefreshAllOptionsUI()
+ '预先归纳所有至多二级的文件/文件夹
+ Dim AllEntries As New List(Of String)
+ Dim IsValidDirectory = '检查文件夹不为空
+ Function(Folder As DirectoryInfo) As Boolean
+ Try
+ Return Folder.Exists AndAlso
+ Folder.EnumerateFileSystemInfos().Any(Function(i) Not SubOptionBlackList.Any(Function(b) i.Name.ContainsF(b)))
+ Catch '一般是由于无法访问,或是一个指向已不存在的文件夹的链接(例如使用 mklink 创造的 resource 文件夹链接)
+ Return False
+ End Try
+ End Function
+ Dim PathInfo As New DirectoryInfo(PageVersionLeft.Version.PathIndie)
+ AllEntries.AddRange(PathInfo.EnumerateFiles().Select(Function(f) f.Name))
+ For Each SubFolder In PathInfo.EnumerateDirectories().Where(IsValidDirectory)
+ AllEntries.Add($"{SubFolder.Name}\")
+ AllEntries.AddRange(SubFolder.EnumerateFiles().Select(Function(f) $"{SubFolder.Name}\{f.Name}"))
+ AllEntries.AddRange(SubFolder.EnumerateDirectories().Where(IsValidDirectory).Select(Function(d) $"{SubFolder.Name}\{d.Name}\"))
+ Next
+ Log($"[Export] 共发现 {AllEntries.Count} 个可行的二级文件/文件夹")
+ '确认选项是否应该被显示
+ Dim IsVisible =
+ Function(TargetOption As ExportOption) As Boolean
+ '检查需要 OptiFine 或 Mod 加载器
+ If TargetOption.RequireOptiFine AndAlso Not PageVersionLeft.Version.Version.HasOptiFine Then Return False
+ If TargetOption.RequireModLoader AndAlso Not PageVersionLeft.Version.Modable Then Return False
+ If TargetOption.RequireModLoaderOrOptiFine AndAlso Not PageVersionLeft.Version.Version.HasOptiFine AndAlso Not PageVersionLeft.Version.Modable Then Return False
+ '粗略检查是否可能有符合规则的文件/文件夹
+ Return StandardizeLines(If(TargetOption.Rules, TargetOption.ShowRules).Split("|"c), True).Any(
+ Function(Rule As String)
+ If Rule.StartsWithF("!") Then Return False '只看正向规则
+ '检查前两级
+ Try
+ If AllEntries.Any(Function(Entry) Entry Like Rule) Then Return True
+ Catch ex As Exception
+ Log(ex, $"错误的规则:{Rule}", LogLevel.Hint)
+ Return False
+ End Try
+ '粗略检查所有级
+ Rule = Rule.Trim("*?".ToCharArray)
+ If Rule.Split({"\"c}, StringSplitOptions.RemoveEmptyEntries).Count >= 3 Then
+ If Rule.EndsWithF("\") Then
+ Return IsValidDirectory(New DirectoryInfo(PageVersionLeft.Version.PathIndie & Rule)) '文件夹有效
+ Else
+ Return File.Exists(PageVersionLeft.Version.PathIndie & Rule) '文件有效
+ End If
+ Else
+ Return False
+ End If
+ End Function)
+ End Function
+ '逐个检查选项
+ For Each CheckBox In GetAllOptions(True)
+ Dim TargetOption = GetExportOption(CheckBox)
+ '名称与简介
+ CheckBox.Inlines.Clear()
+ CheckBox.Inlines.Add(New Run(TargetOption.Title))
+ If Not String.IsNullOrEmpty(TargetOption.Description) Then
+ CheckBox.Inlines.Add(New Run(" " & TargetOption.Description) With {.Foreground = ColorGray5})
+ End If
+ '可见性、默认勾选
+ If String.IsNullOrEmpty(TargetOption.Rules) AndAlso String.IsNullOrEmpty(TargetOption.ShowRules) Then
+ CheckBox.Visibility = Visibility.Visible
+ CheckBox.Checked = TargetOption.DefaultChecked
+ Else
+ Dim Pass As Boolean = IsVisible(TargetOption)
+ CheckBox.Visibility = If(Pass, Visibility.Visible, Visibility.Collapsed)
+ CheckBox.Checked = TargetOption.DefaultChecked AndAlso Pass
+ End If
+ Next
+ End Sub
+
+ '''
+ ''' 对文本行进行标准化处理,以便使用 Like 进行匹配。
+ '''
+ Private Iterator Function StandardizeLines(Raw As IEnumerable(Of String), AddSuffixStarToFolderPath As Boolean) As IEnumerable(Of String)
+ For Each IgnoreLine In Raw
+ IgnoreLine = IgnoreLine.Trim
+ If IgnoreLine = "" OrElse IgnoreLine.StartsWithF("#") OrElse IgnoreLine.StartsWithF("=") Then Continue For
+ IgnoreLine = IgnoreLine.Replace("/", "\")
+ Yield IgnoreLine & If(IgnoreLine.EndsWithF("\") AndAlso AddSuffixStarToFolderPath, "*", "")
+ Next
+ End Function
+
+ '''
+ ''' 获取所有可作为选项的 CheckBox。
+ '''
+ Private Iterator Function GetAllOptions(IncludeHidden As Boolean) As IEnumerable(Of MyCheckBox)
+ For Each Element In PanOptions.Children
+ If Not IncludeHidden AndAlso Element.Visibility <> Visibility.Visible Then Continue For
+ If TypeOf Element Is MyCheckBox Then
+ Yield Element
+ ElseIf TypeOf Element Is StackPanel Then
+ For Each SubElement In DirectCast(Element, StackPanel).Children
+ If Not IncludeHidden AndAlso SubElement.Visibility <> Visibility.Visible Then Continue For
+ If TypeOf SubElement Is MyCheckBox Then Yield SubElement
+ Next
+ End If
+ Next
+ End Function
+
+ '''
+ ''' 获取该 CheckBox 对应的 ExportOption。
+ '''
+ Private Function GetExportOption(CheckBox As MyCheckBox) As ExportOption
+ Return CType(CheckBox.Tag, ExportOption)
+ End Function
+
+#End Region
+
+#Region "配置文件"
+ Private Const Sperator As String = "=============================================================="
+
+ ' ================ 导出内容段 ================
+
+ '''
+ ''' 从配置文件中读取的规则。
+ ''' 如果不为 Nothing,则会覆写当前勾选的规则并禁用对应 UI。
+ '''
+ Private Property RulesOverrides As List(Of String)
+ Get
+ Return _RulesOverrides
+ End Get
+ Set(value As List(Of String))
+ _RulesOverrides = value
+ If value Is Nothing Then
+ BtnOverrideCancel.Visibility = Visibility.Collapsed
+ PanOptions.Visibility = Visibility.Visible
+ CardOptions.Inlines.Clear()
+ CardOptions.Inlines.Add(New Run("导出内容列表") With {.FontWeight = FontWeights.Bold})
+ Else
+ BtnOverrideCancel.Visibility = Visibility.Visible
+ PanOptions.Visibility = Visibility.Collapsed
+ CardOptions.Inlines.Clear()
+ CardOptions.Inlines.Add(New Run("导出内容列表: ") With {.FontWeight = FontWeights.Bold})
+ CardOptions.Inlines.Add(New Run("从配置文件中读取") With {.FontWeight = FontWeights.Normal})
+ End If
+ End Set
+ End Property
+ Private _RulesOverrides As List(Of String) = Nothing
+ '''
+ ''' 获取当前实际生效的所有规则。
+ '''
+ Private Iterator Function GetAllRules() As IEnumerable(Of String)
+ If RulesOverrides IsNot Nothing Then
+ '返回覆盖的列表
+ For Each Rule In RulesOverrides
+ Yield Rule
+ Next
+ Else
+ '从当前勾选的所有选项中获取所有规则行
+ Yield ""
+ Yield "# 修改下方的规则以控制需要导出的内容。"
+ Yield "# 以 ! 开头以反选。可以使用 *、?、[] 通配符。靠后的行覆盖靠前的。"
+ Yield ""
+ For Each CheckBox In GetAllOptions(False)
+ If Not CheckBox.Checked Then Continue For
+ Dim TargetOption = GetExportOption(CheckBox)
+ If TargetOption.Rules Is Nothing Then Continue For
+ Yield $"# {TargetOption.Title}"
+ For Each Rule In TargetOption.Rules.Split("|"c)
+ Yield Rule
+ Next
+ Yield ""
+ Next
+ Yield "# 排除的文件"
+ Yield "!*.log"
+ Yield "!*.dat_old"
+ Yield "!*.BakaCoreInfo"
+ Yield ""
+ End If
+ End Function
+
+ ' ================ 追加内容段 ================
+
+ Private ExtraFiles As List(Of String) = Nothing
+ '''
+ ''' 获取当前实际生效的追加内容。
+ '''
+ Private Iterator Function GetExtraFileLines() As IEnumerable(Of String)
+ If ExtraFiles IsNot Nothing Then
+ '返回覆盖的列表
+ For Each File In ExtraFiles
+ Yield File
+ Next
+ Else
+ '从当前勾选的所有选项中获取所有规则行
+ Yield ""
+ Yield "# 如果想将额外的文件自动放到压缩包根目录中,可以将它们的路径写在下方。"
+ Yield "# 必须是完整路径。每行中,若以 \ 结尾则代表是文件夹,不以 \ 结尾则代表是文件。"
+ Yield ""
+ End If
+ End Function
+
+ ' ================ 重置 ================
+
+ '''
+ ''' 重置配置文件所带来的影响。
+ '''
+ Private Sub ResetConfigOverrides()
+ RulesOverrides = Nothing
+ ConfigPackPath = Nothing
+ ExtraFiles = Nothing
+ PanBack.ScrollToHome()
+ End Sub
+ Private Sub CardOptions_MouseLeftButtonDown() Handles CardOptions.MouseLeftButtonDown
+ If RulesOverrides Is Nothing Then Return
+ ResetConfigOverrides()
+ End Sub
+
+ ' ================ 保存 / 读取 ================
+
+ '保存配置文件
+ Private Sub ExportConfig() Handles BtnAdvancedExport.Click
+ Try
+ Dim ConfigPath As String = SelectSaveFile("选择文件位置", "export_config.txt", "整合包导出配置(*.txt)|*.txt", Setup.Get("CacheExportConfig"))
+ If String.IsNullOrEmpty(ConfigPath) Then Return
+ Setup.Set("CacheExportConfig", ConfigPath)
+ Dim ConfigLines As New List(Of String)
+ 'ini 段
+ ConfigLines.Add("Name:" & TextExportName.Text)
+ ConfigLines.Add("Version:" & TextExportVersion.Text)
+ ConfigLines.Add("")
+ ConfigLines.Add("# 是否打包正式版 PCL,以便没有启动器的玩家安装整合包。")
+ ConfigLines.Add("IncludeLauncher:" & CheckOptionsPcl.Checked)
+ ConfigLines.Add("")
+ ConfigLines.Add("# 是否打包 PCL 个性化内容,例如功能隐藏设置、主页、背景音乐和图片等。")
+ ConfigLines.Add("IncludeLauncherCustom:" & CheckOptionsPclCustom.Checked)
+ ConfigLines.Add("")
+ ConfigLines.Add("# 是否将 Mod、资源包、光影包的文件直接放入整合包中,这样在导入时就无需联网下载它们。")
+ ConfigLines.Add("# 建议仅在无法稳定连接 CurseForge 或 Modrinth 时才考虑启用。")
+ ConfigLines.Add("# 二次分发可能违反使用协议,请尽量不要公开发布包含资源文件的整合包!")
+ ConfigLines.Add("DontCheckHostedAssets:" & CheckAdvancedInclude.Checked)
+ ConfigLines.Add("")
+ ConfigLines.Add("# 如果你想要打包上传到 Modrinth,启用此项会生成完全符合 Modrinth 要求的整合包文件。")
+ ConfigLines.Add("# 由于 Modrinth 要求,只能从 CurseForge 下载的资源将无法联网下载,会被直接放入整合包中。")
+ ConfigLines.Add("# 此选项与 IncludeLauncher、IncludeLauncherCustom、DontCheckHostedAssets 冲突。")
+ ConfigLines.Add("ModrinthUploadMode:" & CheckAdvancedModrinth.Checked)
+ ConfigLines.Add("")
+ ConfigLines.Add("# 导出的文件的存放位置。")
+ ConfigLines.Add("# 若设置了此项,在导出时会直接将文件放到此路径,不会弹窗要求选择。")
+ ConfigLines.Add("# 若 IncludeLauncher 为 True,应以 .zip 结尾;若为 False,应以 .mrpack 结尾。")
+ ConfigLines.Add("PackPath:" & If(ConfigPackPath, ""))
+ ConfigLines.Add("")
+ '导出内容段
+ ConfigLines.Add(Sperator)
+ ConfigLines.AddRange(GetAllRules())
+ '追加内容段
+ ConfigLines.Add(Sperator)
+ ConfigLines.AddRange(GetExtraFileLines())
+ '结束
+ WriteFile(ConfigPath, ConfigLines.Join(vbCrLf))
+ Hint("已保存配置文件:" & ConfigPath, HintType.Finish)
+ OpenExplorer(ConfigPath)
+ Catch ex As Exception
+ Log(ex, "保存配置失败", LogLevel.Msgbox)
+ End Try
+ End Sub
+ '读取配置文件
+ Private Sub ImportConfig() Handles BtnAdvancedImport.Click
+ Try
+ Dim ConfigPath As String = SelectFile("整合包导出配置(*.txt)|*.txt", "选择配置文件", Setup.Get("CacheExportConfig"))
+ If String.IsNullOrEmpty(ConfigPath) Then Return
+ Setup.Set("CacheExportConfig", ConfigPath)
+ Dim Segments As String() = ReadFile(ConfigPath).Split(Sperator)
+ 'ini 段
+ Dim Ini As New Dictionary(Of String, String)
+ For Each Line In Segments(0).Split(vbCrLf.ToCharArray())
+ Line = Line.Trim
+ If Line = "" OrElse Line.StartsWithF("#") OrElse Line.StartsWithF("=") Then Continue For
+ Dim Index As Integer = Line.IndexOfF(":")
+ If Index > 0 Then Ini(Line.Substring(0, Index)) = Line.Substring(Index + 1)
+ Next
+ TextExportName.Text = Ini.GetOrDefault("Name", "")
+ TextExportVersion.Text = Ini.GetOrDefault("Version", "")
+ CheckOptionsPcl.Checked = Ini.GetOrDefault("IncludeLauncher", True)
+ CheckOptionsPclCustom.Checked = Ini.GetOrDefault("IncludeLauncherCustom", True)
+ CheckAdvancedModrinth.Checked = Ini.GetOrDefault("ModrinthUploadMode", False)
+ CheckAdvancedInclude.Checked = Ini.GetOrDefault("DontCheckHostedAssets", False)
+ ConfigPackPath = Ini.GetOrDefault("PackPath", Nothing)
+ '导出内容段
+ RulesOverrides = Segments(1).Replace(vbCr, vbLf).Replace(vbLf & vbLf, vbLf).Split(vbLf).ToList
+ '追加内容段
+ If Segments.Length > 2 Then
+ ExtraFiles = Segments(2).Replace(vbCr, vbLf).Replace(vbLf & vbLf, vbLf).Split(vbLf).ToList
+ Else
+ ExtraFiles = Nothing
+ End If
+ '结束
+ Hint("已读取配置文件:" & ConfigPath, HintType.Finish)
+ Catch ex As Exception
+ Log(ex, "读取配置失败", LogLevel.Msgbox)
+ End Try
+ End Sub
+
+#End Region
+
+#Region "导出"
+
+ '''
+ ''' 配置文件中指定的导出位置。
+ '''
+ Private ConfigPackPath As String = Nothing
+
+ '''
+ ''' 开始导出。
+ '''
+ Private Sub StartExport() Handles BtnExport.Click
+ Dim PackName As String = If(String.IsNullOrEmpty(TextExportName.Text), TextExportName.HintText, TextExportName.Text)
+ Dim PackVersion As String = If(String.IsNullOrEmpty(TextExportVersion.Text), "1.0.0", TextExportVersion.Text)
+
+ '重复任务检查
+ Dim LoaderName As String = $"导出整合包:" & PackName
+ For Each OngoingLoader In LoaderTaskbar
+ If OngoingLoader.Name <> LoaderName Then Continue For
+ FrmMain.PageChange(FormMain.PageType.DownloadManager)
+ Return
+ Next
+
+ '确认导出位置
+ Dim Extension As String = If(CheckOptionsPcl.Checked, ".zip", ".mrpack")
+ Dim PackPath As String = Nothing
+ If Not String.IsNullOrWhiteSpace(ConfigPackPath) AndAlso
+ (Not ConfigPackPath.EndsWithF("\") AndAlso Not ConfigPackPath.EndsWithF("/")) Then
+ Try
+ Directory.CreateDirectory(GetPathFromFullPath(ConfigPackPath))
+ PackPath = ConfigPackPath
+ Log($"[Export] 使用配置文件中指定的导出路径:{ConfigPackPath}")
+ Catch ex As Exception
+ Log(ex, $"无法使用配置文件中指定的导出路径({ConfigPackPath})", LogLevel.Debug)
+ If MyMsgBox($"指定的路径:{ConfigPackPath}{vbCrLf}{vbCrLf}{GetExceptionDetail(ex)}", "无法使用配置文件中指定的导出路径", "确定", "取消") = 2 Then Return
+ End Try
+ End If
+ If PackPath Is Nothing Then
+ PackPath = SelectSaveFile("选择导出位置",
+ PackName & If(String.IsNullOrEmpty(TextExportVersion.Text), "", " " & TextExportVersion.Text),
+ $"整合包文件(*{Extension})|*{Extension}")
+ Log($"[Export] 手动指定的导出路径:{PackPath}")
+ End If
+ If String.IsNullOrEmpty(PackPath) Then Return
+
+ '缓存所需参数
+ Dim CacheFolder = RequestTaskTempFolder()
+ Dim OverridesFolder = CacheFolder & "modpack\overrides\"
+ Dim McVersion = PageVersionLeft.Version
+ Dim PathIndie As String = McVersion.PathIndie
+ Dim CheckHostedAssets As Boolean = Not CheckAdvancedInclude.Checked
+ Dim ModrinthUploadMode As Boolean = CheckAdvancedModrinth.Checked
+ Dim IncludePCL As Boolean = CheckOptionsPcl.Checked
+ Dim IncludePCLCustom As Boolean = IncludePCL AndAlso CheckOptionsPclCustom.Checked
+ Dim AllRules = StandardizeLines(GetAllRules(), True).ToList()
+ Dim AllExtraFiles = StandardizeLines(GetExtraFileLines(), False).ToList()
+ Log($"[Export] 准备导出整合包,共有 {AllRules.Count} 条规则,{AllExtraFiles.Count} 条追加内容行")
+
+ '构造步骤加载器
+ Dim Loaders As New List(Of LoaderBase)
+
+#Region "准备 PCL 文件"
+
+#If Not BETA Then
+ If IncludePCL Then
+ Loaders.Add(New LoaderTask(Of Integer, Integer)("下载 PCL 正式版",
+ Sub(Loader As LoaderTask(Of Integer, Integer))
+ DownloadLatestPCL(Loader)
+ CopyFile(PathTemp & "Latest.exe", CacheFolder & "Plain Craft Launcher.exe")
+ End Sub) With {.ProgressWeight = 0.5, .Block = False})
+ End If
+#End If
+
+#End Region
+
+#Region "复制文件"
+
+ Loaders.Add(New LoaderTask(Of Integer, List(Of McMod))("复制导出内容",
+ Sub(Loader As LoaderTask(Of Integer, List(Of McMod)))
+ Loader.Output = New List(Of McMod)
+ '复制版本文件
+ Dim Progress As Integer = 0
+ Dim SearchFolder As Action(Of DirectoryInfo)
+ SearchFolder =
+ Sub(Folder As DirectoryInfo)
+ '文件夹:进一步搜索
+ For Each SubFolder In Folder.EnumerateDirectories("*", SearchOption.TopDirectoryOnly)
+ '跳过部分又没用文件又多的文件夹,加快搜索
+ If Folder.FullName = PathIndie AndAlso {"assets", "versions", "libraries"}.Contains(SubFolder.Name) Then Continue For
+ If {"structureCacheV1", ".fabric", ".git", "avatar-cache", "cosmetic-cache"}.Contains(SubFolder.Name) Then Continue For
+ SearchFolder(SubFolder)
+ Next
+ '文件:检查规则并复制
+ For Each Entry In Folder.EnumerateFiles("*", SearchOption.TopDirectoryOnly)
+ Dim RelativePath As String = Entry.FullName.AfterFirst(PathIndie)
+ '检查规则
+ Dim ShouldKeep As Boolean = False
+ For Each Rule In AllRules
+ Dim Revert = Rule.StartsWith("!")
+ If RelativePath Like Rule.TrimStart("!") Then ShouldKeep = Not Revert
+ Next
+ If Not ShouldKeep Then Continue For
+ Dim TargetPath As String = OverridesFolder & RelativePath
+ CopyFile(Entry.FullName, TargetPath)
+ '若为压缩包,考虑联网获取路径
+ If CheckHostedAssets AndAlso
+ {".zip", ".rar", ".jar", ".disabled", ".old"}.Contains(Entry.Extension.ToLower) AndAlso
+ {"mods", "packs", "openloader", "resource"}.Any(Function(s) RelativePath.Contains(s)) Then
+ Dim ModFile As New McMod(TargetPath)
+ Dim Unused = ModFile.ModrinthHash '提前计算 Hash
+ Unused = ModFile.CurseForgeHash
+ Loader.Output.Add(ModFile)
+ End If
+ '更新进度(进度并不准确,主要突出一个我还没似)
+ Progress += 1
+ If Progress = 25 Then
+ Loader.Progress += (0.94 - Loader.Progress) * 0.012
+ Progress = 0
+ End If
+ Next
+ End Sub
+ SearchFolder(New DirectoryInfo(PathIndie))
+ Log($"[Export] 复制 overrides 文件完成,有 {Loader.Output.Count} 个文件需要联网检查")
+ Loader.Progress = 0.95
+ '复制追加内容到根目录
+ Dim BaseFolder As String = If(IncludePCL, CacheFolder, CacheFolder & "modpack\")
+ For Each Line In AllExtraFiles
+ If Line.EndsWithF("\") OrElse Line.EndsWithF("/") Then
+ If Directory.Exists(Line) Then
+ CopyDirectory(Line, BaseFolder & GetFolderNameFromPath(Line) & "\")
+ Else
+ Hint($"未找到配置文件中指定的文件夹:{Line}", HintType.Critical)
+ End If
+ Else
+ If File.Exists(Line) Then
+ CopyFile(Line, BaseFolder & GetFileNameFromPath(Line))
+ Else
+ Hint($"未找到配置文件中指定的单个文件:{Line}", HintType.Critical)
+ End If
+ End If
+ Next
+ Loader.Progress = 0.97
+ '复制 PCL 版本设置
+ CopyDirectory(McVersion.Path & "PCL\", OverridesFolder & "PCL\")
+#If BETA Then
+ '复制 PCL 本体
+ If IncludePCL Then CopyFile(PathWithName, CacheFolder & "Plain Craft Launcher.exe")
+#End If
+ '复制 PCL 个性化内容
+ If IncludePCLCustom Then
+ If Directory.Exists(Path & "PCL\Pictures\") Then CopyDirectory(Path & "PCL\Pictures\", CacheFolder & "PCL\Pictures\")
+ If Directory.Exists(Path & "PCL\Musics\") Then CopyDirectory(Path & "PCL\Musics\", CacheFolder & "PCL\Musics\")
+ If Directory.Exists(Path & "PCL\Help\") Then CopyDirectory(Path & "PCL\Help\", CacheFolder & "PCL\Help\")
+ If File.Exists(Path & "PCL\Custom.xaml") Then CopyFile(Path & "PCL\Custom.xaml", CacheFolder & "PCL\Custom.xaml")
+ If File.Exists(Path & "PCL\Setup.ini") Then CopyFile(Path & "PCL\Setup.ini", CacheFolder & "PCL\Setup.ini")
+ If File.Exists(Path & "PCL\hints.txt") Then CopyFile(Path & "PCL\hints.txt", CacheFolder & "PCL\hints.txt")
+ If File.Exists(Path & "PCL\Logo.png") Then CopyFile(Path & "PCL\Logo.png", CacheFolder & "PCL\Logo.png")
+ End If
+ End Sub) With {.ProgressWeight = 5})
+
+#End Region
+
+#Region "联网检查"
+
+ Loaders.Add(New LoaderTask(Of List(Of McMod), Dictionary(Of McMod, List(Of String)))("联网获取文件信息",
+ Sub(Loader As LoaderTask(Of List(Of McMod), Dictionary(Of McMod, List(Of String))))
+ Loader.Output = New Dictionary(Of McMod, List(Of String))
+ If Not CheckHostedAssets Then Log($"[Export] 要求跳过联网获取步骤") : Return
+ If Not Loader.Input.Any Then Log($"[Export] 没有需要联网检查的文件,跳过联网获取步骤") : Return
+
+ '分平台获取下载地址
+ Dim EndedThreadCount As Integer = 0, FailedExceptions As New List(Of Exception)
+
+ '从 Modrinth 获取信息
+ RunInNewThread(
+ Sub()
+ Try
+ Dim ModrinthHashes = Loader.Input.Select(Function(m) m.ModrinthHash)
+ Dim ModrinthRaw = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files", "POST",
+ $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject)
+ For Each ModFile In Loader.Input
+ '查找对应的文件
+ If Not ModrinthRaw.ContainsKey(ModFile.ModrinthHash) Then Continue For
+ If ModrinthRaw(ModFile.ModrinthHash)?("files")?(0)("hashes")?("sha1") <> ModFile.ModrinthHash Then Continue For
+ '写入下载地址
+ Loader.Output.AddToList(ModFile, ModrinthRaw(ModFile.ModrinthHash)("files")(0)("url"))
+ Next
+ Log($"[Export] 从 Modrinth 获取到 {ModrinthRaw.Count} 个本地资源项的对应信息")
+ Catch ex As Exception
+ Log(ex, "从 Modrinth 获取本地 Mod 信息失败")
+ FailedExceptions.Add(ex)
+ Finally
+ EndedThreadCount += 1
+ Loader.Progress += 0.45
+ End Try
+ End Sub, "Modrinth - " & LoaderName)
+
+ '从 CurseForge 获取信息
+ RunInNewThread(
+ Sub()
+ Try
+ If ModrinthUploadMode Then Return 'Modrinth 上传模式下,不能从 CurseForge 获取信息
+ Dim CurseForgeHashes = Loader.Input.Select(Function(m) m.CurseForgeHash)
+ Dim CurseForgeRaw = CType(CType(GetJson(DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", "POST",
+ $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
+ For Each ResultJson As JObject In CurseForgeRaw
+ If Not ResultJson.ContainsKey("file") Then Continue For
+ Dim File As JObject = ResultJson("file")
+ If String.IsNullOrEmpty(File("downloadUrl")) Then Continue For
+ '查找对应的文件
+ Dim ModFile As McMod = Loader.Input.FirstOrDefault(Function(m) m.CurseForgeHash = File("fileFingerprint").ToObject(Of UInteger))
+ If ModFile Is Nothing Then Continue For
+ '写入下载地址
+ For Each Address In CompFile.HandleCurseForgeDownloadUrls(File("downloadUrl").ToString)
+ Loader.Output.AddToList(ModFile, Address)
+ Next
+ Next
+ Log($"[Export] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地资源项的对应信息")
+ Catch ex As Exception
+ Log(ex, "从 CurseForge 获取本地 Mod 信息失败")
+ FailedExceptions.Add(ex)
+ Finally
+ EndedThreadCount += 1
+ Loader.Progress += 0.45
+ End Try
+ End Sub, "CurseForge - " & LoaderName)
+
+ '等待线程结束
+ Do Until EndedThreadCount = 2
+ If Loader.IsAborted Then Exit Sub
+ Thread.Sleep(10)
+ Loop
+
+ '若失败,确认是否继续
+ If FailedExceptions.Count = 1 Then
+ If MyMsgBox("联网获取部分文件信息失败,是否继续导出?" & vbCrLf & vbCrLf &
+ "若继续,无法获取信息的文件将被直接打包。" & vbCrLf &
+ "由于二次分发可能违反使用协议,请尽量不要公开发布导出的整合包!",
+ "部分文件信息获取失败", "继续", "取消") = 2 Then Throw FailedExceptions.First
+ ElseIf FailedExceptions.Count > 1 Then
+ If MyMsgBox("联网获取文件信息失败,是否继续导出?" & vbCrLf & vbCrLf &
+ "若继续,所有文件都将被直接打包。" & vbCrLf &
+ "由于二次分发可能违反使用协议,请尽量不要公开发布导出的整合包!",
+ "文件信息获取失败", "继续", "取消") = 2 Then Throw FailedExceptions.First
+ End If
+ End Sub) With {.Show = CheckHostedAssets, .ProgressWeight = If(CheckHostedAssets, 2, 0.01)})
+
+#End Region
+
+#Region "生成压缩包"
+
+ Loaders.Add(New LoaderTask(Of Dictionary(Of McMod, List(Of String)), Integer)("生成压缩包",
+ Sub(Loader As LoaderTask(Of Dictionary(Of McMod, List(Of String)), Integer))
+ '整理文件列表
+ Dim Files As New JArray
+ For Each Pair In Loader.Input
+ Dim ModFile As McMod = Pair.Key
+ Files.Add(New JObject From {
+ {"path", ModFile.Path.AfterFirst(OverridesFolder).Replace("\", "/")},
+ {"hashes", New JObject From {{"sha1", ModFile.ModrinthHash}, {"sha512", GetFileSHA512(ModFile.Path)}}},
+ {"downloads", New JArray(Pair.Value.OrderByDescending(Function(u) u.Contains("modrinth.com")))},
+ {"fileSize", New FileInfo(ModFile.Path).Length}
+ })
+ File.Delete(ModFile.Path)
+ Next
+ Loader.Progress = 0.2
+ '导出最终 JSON 文件
+ Dim Dependencies As New JObject From {{"minecraft", McVersion.Version.McName}}
+ If McVersion.Version.HasForge Then Dependencies.Add("forge", McVersion.Version.ForgeVersion)
+ If McVersion.Version.HasFabric Then Dependencies.Add("fabric-loader", McVersion.Version.FabricVersion)
+ If McVersion.Version.HasNeoForge Then Dependencies.Add("neoforge", McVersion.Version.NeoForgeVersion)
+ Dim ResultJson As New JObject From {
+ {"game", "minecraft"},
+ {"formatVersion", 1},
+ {"versionId", PackVersion},
+ {"name", PackName},
+ {"summary", McVersion.Info},
+ {"files", Files},
+ {"dependencies", Dependencies}
+ }
+ File.WriteAllText(CacheFolder & "modpack\modrinth.index.json", ResultJson.ToString(Newtonsoft.Json.Formatting.Indented))
+ '打包
+ Directory.CreateDirectory(GetPathFromFullPath(PackPath))
+ If File.Exists(PackPath) Then File.Delete(PackPath)
+ If IncludePCL Then
+ '首次压缩整合包
+ ZipFile.CreateFromDirectory(CacheFolder & "modpack\", CacheFolder & "modpack.mrpack")
+ Loader.Progress = 0.5
+ Directory.Delete(CacheFolder & "modpack\", True)
+ Loader.Progress = 0.6
+ '二次压缩整合包
+ ZipFile.CreateFromDirectory(CacheFolder, PackPath)
+ Loader.Progress = 0.9
+ Else
+ '直接压缩整合包
+ ZipFile.CreateFromDirectory(CacheFolder & "modpack\", PackPath)
+ Loader.Progress = 0.8
+ End If
+ Directory.Delete(CacheFolder, True)
+ OpenExplorer(PackPath)
+ End Sub) With {.ProgressWeight = 6})
+
+#End Region
+
+ '启动
+ Dim MainLoader As New LoaderCombo(Of String)(LoaderName, Loaders) With {.OnStateChanged = AddressOf LoaderStateChangedHintOnly}
+ MainLoader.Start()
+ LoaderTaskbarAdd(MainLoader)
+ FrmMain.BtnExtraDownload.ShowRefresh()
+ FrmMain.BtnExtraDownload.Ribble()
+ FrmMain.PageChange(FormMain.PageType.DownloadManager)
+ End Sub
+
+#End Region
+
+ '自动填写整合包名称
+ Private Sub TextExportName_GotFocus() Handles TextExportName.GotFocus
+ If TextExportName.Text = "" Then
+ TextExportName.Text = TextExportName.HintText
+ TextExportName.SelectionStart = TextExportName.Text.Length
+ End If
+ End Sub
+
+ '勾选 Modrinth 上传模式时,禁止打包 PCL
+ Private Sub CheckAdvancedModrinth_Change(sender As Object, user As Boolean) Handles CheckAdvancedModrinth.Change
+ If CheckAdvancedModrinth.Checked Then CheckOptionsPcl.Checked = False
+ CheckOptionsPcl.IsEnabled = Not CheckAdvancedModrinth.Checked
+ End Sub
+
+ '勾选打包资源文件时,禁止开启 Modrinth 上传模式
+ Private Sub CheckAdvancedInclude_Change(sender As Object, user As Boolean) Handles CheckAdvancedInclude.Change
+ If CheckAdvancedInclude.Checked Then CheckAdvancedModrinth.Checked = False
+ CheckAdvancedModrinth.IsEnabled = Not CheckAdvancedInclude.Checked
+ End Sub
+
+End Class
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml
index 32d8fde..b145e25 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml
@@ -6,7 +6,7 @@
+ Logo="M1000 241c-2-5-4-11-6-17-0-6-4-12-8-17-7-9-18-15-31-15-6 0-13 1-19 5l-15 15-17 17-21 21L749 381c-14 14-38 14-52 0l-54-54c-14-14-14-38 0-52l132-132 14-14 20-20 16-16c3-5 5-11 5-19 0-12-5-24-16-32-5-4-11-6-18-7-5-2-10-4-16-5-36-12-76-19-117-19-7 0-14 0-22 0-188 11-337 168-337 360 0 16 0 32 3 48L56 663c-73 74-73 193 0 267l33 33c73 73 193 73 267 0l240-241c21 4 42 5 64 5 190 0 346-146 360-333 0-8 0-17 0-27 0-44-6-86-21-125zM658 639c-28 0-54-4-79-11-4 7-10 14-16 21l-258 258c-43 43-115 43-158 0l-29-29c-43-44-43-115 0-158l212-212 46-46c5-5 12-11 19-15-8-26-12-54-12-83v-1c0-152 123-275 276-275 18 0 37 1 55 5L597 207c-52 52-52 137 0 189l29 29c52 52 137 52 189 0L930 312c2 16 4 33 4 50 0 152-123 276-276 276z">
@@ -23,5 +23,13 @@
+
+
+
+
+
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb
index 99e211f..0383111 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionLeft.xaml.vb
@@ -1,4 +1,5 @@
Public Class PageVersionLeft
+ Implements IRefreshable
'''
''' 当前显示设置的 MC 版本。
@@ -25,7 +26,7 @@
'''
''' 勾选事件改变页面。
'''
- Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemOverall.Check, ItemMod.Check, ItemModDisabled.Check, ItemSetup.Check
+ Private Sub PageCheck(sender As MyListItem, e As RouteEventArgs) Handles ItemOverall.Check, ItemMod.Check, ItemModDisabled.Check, ItemSetup.Check, ItemExport.Check
'尚未初始化控件属性时,sender.Tag 为 Nothing,会导致切换到页面 0
'若使用 IsLoaded,则会导致模拟点击不被执行(模拟点击切换页面时,控件的 IsLoaded 为 False)
If sender.Tag IsNot Nothing Then PageChange(Val(sender.Tag))
@@ -46,6 +47,9 @@
Case FormMain.PageSubType.VersionSetup
If IsNothing(FrmVersionSetup) Then FrmVersionSetup = New PageVersionSetup
Return FrmVersionSetup
+ Case FormMain.PageSubType.VersionExport
+ If FrmVersionExport Is Nothing Then FrmVersionExport = New PageVersionExport
+ Return FrmVersionExport
Case Else
Throw New Exception("未知的版本设置子页面种类:" & ID)
End Select
@@ -61,7 +65,7 @@
PageChangeRun(PageGet(ID))
PageID = ID
Catch ex As Exception
- Log(ex, "切换设置分页面失败(ID " & ID & ")", LogLevel.Feedback)
+ Log(ex, "切换分页面失败(ID " & ID & ")", LogLevel.Feedback)
Finally
AniControlEnabled -= 1
End Try
@@ -88,17 +92,20 @@
#End Region
Public Sub Refresh(sender As Object, e As EventArgs) '由边栏按钮匿名调用
- '强制刷新
- Try
- CompProjectCache.Clear()
- File.Delete(PathTemp & "Cache\LocalMod.json")
- Log("[Mod] 由于点击刷新按钮,清理本地 Mod 信息缓存")
- Catch ex As Exception
- Log(ex, "强制刷新时清理本地 Mod 信息缓存失败")
- End Try
- If FrmVersionMod IsNot Nothing Then FrmVersionMod.ReloadModList(True) '无需 Else,还没加载刷个鬼的新
- ItemMod.Checked = True
- Hint("正在刷新……", Log:=False)
+ Refresh(Val(sender.Tag))
+ End Sub
+ Public Sub Refresh() Implements IRefreshable.Refresh
+ Refresh(FrmMain.PageCurrentSub)
+ End Sub
+ Public Sub Refresh(SubType As FormMain.PageSubType)
+ Select Case SubType
+ Case FormMain.PageSubType.VersionMod
+ PageVersionMod.Refresh()
+ ItemMod.Checked = True
+ Case FormMain.PageSubType.VersionExport
+ If FrmVersionExport IsNot Nothing Then FrmVersionExport.RefreshAll()
+ ItemExport.Checked = True
+ End Select
End Sub
Public Sub Reset(sender As Object, e As EventArgs)
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml
index 854458e..d00296d 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml
@@ -16,11 +16,13 @@
+
-
-
-
-
+
+
+
+
+
@@ -37,10 +39,19 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
index a7a81f4..3f1b77f 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
@@ -1,4 +1,5 @@
Public Class PageVersionMod
+ Implements IRefreshable
#Region "初始化"
@@ -30,22 +31,43 @@
''' 刷新 Mod 列表。
'''
Public Sub ReloadModList(Optional ForceReload As Boolean = False)
- If LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", If(ForceReload, LoaderFolderRunType.ForceRun, LoaderFolderRunType.RunOnUpdated)) Then
+ If LoaderRun(If(ForceReload, LoaderFolderRunType.ForceRun, LoaderFolderRunType.RunOnUpdated)) Then
Log("[System] 已刷新 Mod 列表")
Filter = FilterType.All
PanBack.ScrollToHome()
SearchBox.Text = ""
End If
End Sub
+ '强制刷新
+ Private Sub RefreshSelf() Implements IRefreshable.Refresh
+ Refresh()
+ End Sub
+ Public Shared Sub Refresh()
+ '强制刷新
+ Try
+ CompProjectCache.Clear()
+ CompFilesCache.Clear()
+ File.Delete(PathTemp & "Cache\LocalMod.json")
+ Log("[Mod] 由于点击刷新按钮,清理本地 Mod 信息缓存")
+ Catch ex As Exception
+ Log(ex, "强制刷新时清理本地 Mod 信息缓存失败")
+ End Try
+ If FrmVersionMod IsNot Nothing Then FrmVersionMod.ReloadModList(True) '无需 Else,还没加载刷个鬼的新
+ FrmVersionLeft.ItemMod.Checked = True
+ Hint("正在刷新……", Log:=False)
+ End Sub
Private Sub LoaderInit() Handles Me.Initialized
PageLoaderInit(Load, PanLoad, PanAllBack, Nothing, McModLoader, AddressOf LoadUIFromLoaderOutput, AutoRun:=False)
End Sub
Private Sub Load_Click(sender As Object, e As MouseButtonEventArgs) Handles Load.Click
If McModLoader.State = LoadState.Failed Then
- LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.ForceRun)
+ LoaderRun(LoaderFolderRunType.ForceRun)
End If
End Sub
+ Public Function LoaderRun(Type As LoaderFolderRunType) As Boolean
+ Return LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", Type)
+ End Function
#End Region
@@ -121,7 +143,7 @@
ToolTipService.SetVerticalOffset(BtnED, 30)
ToolTipService.SetHorizontalOffset(BtnED, 2)
AddHandler BtnED.Click, AddressOf ED_Click
- If sender.Entry.State = McMod.McModState.Unavaliable Then
+ If sender.Entry.State = McMod.McModState.Unavailable Then
sender.Buttons = {BtnCont, BtnOpen, BtnDelete}
Else
sender.Buttons = {BtnCont, BtnOpen, BtnED, BtnDelete}
@@ -133,13 +155,13 @@
'''
Public Sub RefreshUI()
If PanList Is Nothing Then Exit Sub
- Dim ShowMods = GetShowingMods(True).ToList()
+ Dim ShowingMods = If(IsSearching, SearchResult, If(McModLoader.Output, New List(Of McMod))).Where(Function(m) CanPassFilter(m)).ToList
'重新列出列表
AniControlEnabled += 1
- If ShowMods.Any() Then
+ If ShowingMods.Any() Then
PanList.Visibility = Visibility.Visible
PanList.Children.Clear()
- For Each TargetMod In ShowMods
+ For Each TargetMod In ShowingMods
Dim Item As MyLocalModItem = ModItems(TargetMod.RawFileName)
Item.Checked = SelectedMods.Contains(TargetMod.RawFileName) '更新选中状态
PanList.Children.Add(Item)
@@ -148,7 +170,7 @@
PanList.Visibility = Visibility.Collapsed
End If
AniControlEnabled -= 1
- SelectedMods = SelectedMods.Where(Function(m) ShowMods.Any(Function(s) s.RawFileName = m)).ToList '取消选中已经不显示的 Mod
+ SelectedMods = SelectedMods.Where(Function(m) ShowingMods.Any(Function(s) s.RawFileName = m)).ToList '取消选中已经不显示的 Mod
RefreshBars()
End Sub
@@ -166,27 +188,23 @@
Dim DisabledCount As Integer = 0
Dim UpdateCount As Integer = 0
Dim UnavalialeCount As Integer = 0
- For Each ModItem In GetShowingMods(False)
+ For Each ModItem In If(IsSearching, SearchResult, If(McModLoader.Output, New List(Of McMod)))
AnyCount += 1
If ModItem.CanUpdate Then UpdateCount += 1
If ModItem.State.Equals(McMod.McModState.Fine) Then EnabledCount += 1
If ModItem.State.Equals(McMod.McModState.Disabled) Then DisabledCount += 1
- If ModItem.State.Equals(McMod.McModState.Unavaliable) Then UnavalialeCount += 1
+ If ModItem.State.Equals(McMod.McModState.Unavailable) Then UnavalialeCount += 1
Next
'显示
BtnFilterAll.Text = If(IsSearching, "搜索结果", "全部") & $" ({AnyCount})"
BtnFilterCanUpdate.Text = $"可更新 ({UpdateCount})"
- BtnFilterCanUpdate.Visibility = If(Filter = FilterType.CanUpdate OrElse
- UpdateCount > 0, Visibility.Visible, Visibility.Collapsed)
+ BtnFilterCanUpdate.Visibility = If(Filter = FilterType.CanUpdate OrElse UpdateCount > 0, Visibility.Visible, Visibility.Collapsed)
BtnFilterEnabled.Text = $"启用 ({EnabledCount})"
- BtnFilterEnabled.Visibility = If(Filter = FilterType.Enabled OrElse Filter = FilterType.Disabled OrElse
- EnabledCount > 0 AndAlso EnabledCount <> AnyCount, Visibility.Visible, Visibility.Collapsed)
+ BtnFilterEnabled.Visibility = If(Filter = FilterType.Enabled OrElse (EnabledCount > 0 AndAlso EnabledCount < AnyCount), Visibility.Visible, Visibility.Collapsed)
BtnFilterDisabled.Text = $"禁用 ({DisabledCount})"
- BtnFilterDisabled.Visibility = If(Filter = FilterType.Enabled OrElse Filter = FilterType.Disabled OrElse
- DisabledCount > 0, Visibility.Visible, Visibility.Collapsed)
+ BtnFilterDisabled.Visibility = If(Filter = FilterType.Disabled OrElse DisabledCount > 0, Visibility.Visible, Visibility.Collapsed)
BtnFilterError.Text = $"错误 ({UnavalialeCount})"
- BtnFilterError.Visibility = If(Filter = FilterType.Unavaliable OrElse
- UnavalialeCount > 0, Visibility.Visible, Visibility.Collapsed)
+ BtnFilterError.Visibility = If(Filter = FilterType.Unavailable OrElse UnavalialeCount > 0, Visibility.Visible, Visibility.Collapsed)
'-----------------
' 底部栏
@@ -271,7 +289,7 @@
Private Sub BtnManageOpen_Click(sender As Object, e As EventArgs) Handles BtnManageOpen.Click, BtnHintOpen.Click
Try
Directory.CreateDirectory(PageVersionLeft.Version.PathIndie & "mods\")
- OpenExplorer("""" & PageVersionLeft.Version.PathIndie & "mods\""")
+ OpenExplorer(PageVersionLeft.Version.PathIndie & "mods\")
Catch ex As Exception
Log(ex, "打开 Mods 文件夹失败", LogLevel.Msgbox)
End Try
@@ -299,21 +317,78 @@
''' 全选。
'''
Private Sub BtnManageSelectAll_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageSelectAll.Click
- ChangeAllSelected(SelectedMods.Count < GetShowingMods(True).Count)
+ ChangeAllSelected(SelectedMods.Count < PanList.Children.Count)
End Sub
'''
''' 安装 Mod。
'''
- Private Sub BtnManageInstall_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageInstall.Click
- Hint("将 Mod 文件直接拖入 PCL 窗口即可安装!")
+ Private Sub BtnManageInstall_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageInstall.Click, BtnHintInstall.Click
+ Dim FileList = SelectFiles("Mod 文件(*.jar;*.litemod;*.disabled;*.old)|*.jar;*.litemod;*.disabled;*.old", "选择要安装的 Mod")
+ If Not FileList.Any Then Return
+ InstallMods(FileList)
+ End Sub
+ '''
+ ''' 尝试安装 Mod。
+ ''' 返回输入的文件是否为一个 Mod 文件,仅用于判断拖拽行为。
+ '''
+ Public Shared Function InstallMods(FilePathList As IEnumerable(Of String)) As Boolean
+ Dim Extension As String = FilePathList.First.AfterLast(".").ToLower
+ '检查文件扩展名
+ If Not {"jar", "litemod", "disabled", "old"}.Any(Function(t) t = Extension) Then Return False
+ Log("[System] 文件为 jar/litemod 格式,尝试作为 Mod 安装")
+ '检查回收站:回收站中的文件有错误的文件名
+ If FilePathList.First.Contains(":\$RECYCLE.BIN\") Then
+ Hint("请先将文件从回收站还原,再尝试安装!", HintType.Critical)
+ Return True
+ End If
+ '获取并检查目标版本
+ Dim TargetVersion As McVersion = McVersionCurrent
+ If FrmMain.PageCurrent = FormMain.PageType.VersionSetup Then TargetVersion = PageVersionLeft.Version
+ If FrmMain.PageCurrent = FormMain.PageType.VersionSelect OrElse TargetVersion Is Nothing OrElse Not TargetVersion.Modable Then
+ '正在选择版本,或当前版本不能安装 Mod
+ Hint("若要安装 Mod,请先选择一个可以安装 Mod 的版本!")
+ ElseIf Not (FrmMain.PageCurrent = FormMain.PageType.VersionSetup AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.VersionMod) Then
+ '未处于 Mod 管理页面
+ If MyMsgBox($"是否要将这{If(FilePathList.Count = 1, "个", "些")}文件作为 Mod 安装到 {TargetVersion.Name}?", "Mod 安装确认", "确定", "取消") = 1 Then GoTo Install
+ Else
+ '处于 Mod 管理页面
+Install:
+ Try
+ For Each ModFile In FilePathList
+ Dim NewFileName = GetFileNameFromPath(ModFile).Replace(".disabled", "").Replace(".old", "")
+ If Not NewFileName.Contains(".") Then NewFileName += ".jar" '#4227
+ CopyFile(ModFile, TargetVersion.PathIndie & "mods\" & NewFileName)
+ Next
+ If FilePathList.Count = 1 Then
+ Hint($"已安装 {GetFileNameFromPath(FilePathList.First).Replace(".disabled", "").Replace(".old", "")}!", HintType.Finish)
+ Else
+ Hint($"已安装 {FilePathList.Count} 个 Mod!", HintType.Finish)
+ End If
+ '刷新列表
+ If FrmMain.PageCurrent = FormMain.PageType.VersionSetup AndAlso FrmMain.PageCurrentSub = FormMain.PageSubType.VersionMod Then
+ LoaderFolderRun(McModLoader, TargetVersion.PathIndie & "mods\", LoaderFolderRunType.ForceRun)
+ End If
+ Catch ex As Exception
+ Log(ex, "复制 Mod 文件失败", LogLevel.Msgbox)
+ End Try
+ End If
+ Return True
+ End Function
+
+ '''
+ ''' 下载 Mod。
+ '''
+ Private Sub BtnManageDownload_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageDownload.Click, BtnHintDownload.Click
+ PageDownloadMod.TargetVersion = PageVersionLeft.Version '将当前版本设置为筛选器
+ FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadMod)
End Sub
#End Region
#Region "选择"
- '选择的 Mod 的路径(不含 .disabled)
+ '选择的 Mod 的路径(不含 .disabled 和 .old)
Public SelectedMods As New List(Of String)
'单项切换选择状态
@@ -333,9 +408,11 @@
Private Sub ChangeAllSelected(Value As Boolean)
AniControlEnabled += 1
SelectedMods.Clear()
- For Each Item As MyLocalModItem In GetShowingMods(True).Select(Function(m) ModItems(m.RawFileName))
- Item.Checked = Value
- If Value Then SelectedMods.Add(Item.Entry.RawFileName)
+ For Each Item As MyLocalModItem In ModItems.Values
+ '#4992,Mod 从过滤器看可能不应在列表中,但因为刚切换状态所以依然保留在列表中,所以应该从列表 UI 判断,而非从过滤器判断
+ Dim ShouldSelected As Boolean = Value AndAlso PanList.Children.Contains(Item)
+ Item.Checked = ShouldSelected
+ If ShouldSelected Then SelectedMods.Add(Item.Entry.RawFileName)
Next
AniControlEnabled -= 1
RefreshBars()
@@ -382,17 +459,9 @@
Enabled = 1
Disabled = 2
CanUpdate = 3
- Unavaliable = 4
+ Unavailable = 4
End Enum
- '''
- ''' 获取所有应该显示在 UI 中的 Mod。
- '''
- Private Function GetShowingMods(ApplyFilter As Boolean) As IEnumerable(Of McMod)
- If McModLoader.Output Is Nothing Then Return New List(Of McMod)
- Return If(IsSearching, SearchResult, McModLoader.Output).Where(Function(m) Not ApplyFilter OrElse CanPassFilter(m))
- End Function
-
'''
''' 检查该 Mod 项是否符合当前筛选的类别。
'''
@@ -406,8 +475,8 @@
Return CheckingMod.State = McMod.McModState.Disabled
Case FilterType.CanUpdate
Return CheckingMod.CanUpdate
- Case FilterType.Unavaliable
- Return CheckingMod.State = McMod.McModState.Unavaliable
+ Case FilterType.Unavailable
+ Return CheckingMod.State = McMod.McModState.Unavailable
Case Else
Return False
End Select
@@ -471,9 +540,16 @@
'更改 Loader 中的列表
Dim NewModEntity As New McMod(NewPath)
NewModEntity.FromJson(ModEntity.ToJson)
- Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity)
- McModLoader.Output.RemoveAt(IndexOfLoader)
- McModLoader.Output.Insert(IndexOfLoader, NewModEntity)
+ If McModLoader.Output.Contains(ModEntity) Then
+ Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity)
+ McModLoader.Output.RemoveAt(IndexOfLoader)
+ McModLoader.Output.Insert(IndexOfLoader, NewModEntity)
+ End If
+ If SearchResult IsNot Nothing AndAlso SearchResult.Contains(ModEntity) Then '#4862
+ Dim IndexOfResult As Integer = SearchResult.IndexOf(ModEntity)
+ SearchResult.Remove(ModEntity)
+ SearchResult.Insert(IndexOfResult, NewModEntity)
+ End If
'更改 UI 中的列表
Dim NewItem As MyLocalModItem = McModListItem(NewModEntity)
ModItems(ModEntity.RawFileName) = NewItem
@@ -488,11 +564,15 @@
Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
ReloadModList(True)
End If
+ LoaderRun(LoaderFolderRunType.UpdateOnly)
End Sub
'更新
Private Sub BtnSelectUpdate_Click() Handles BtnSelectUpdate.Click
- UpdateMods(McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName) AndAlso m.CanUpdate))
+ Dim UpdateList As List(Of McMod) = McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName) AndAlso m.CanUpdate).ToList()
+ If Not UpdateList.Any() Then Return
+ UpdateMods(UpdateList)
+ ChangeAllSelected(False)
End Sub
'''
''' 记录正在进行 Mod 更新的 mods 文件夹路径。
@@ -500,8 +580,8 @@
Public Shared UpdatingVersions As New List(Of String)
Public Sub UpdateMods(ModList As IEnumerable(Of McMod))
'更新前警告
- If Not Setup.Get("HintUpdateMod") Then
- If MyMsgBox($"新版本 Mod 可能不兼容老版本的存档或者其他 Mod,这可能导致游戏崩溃,甚至存档损坏!{vbCrLf}除非整合包作者要求你更新,否则不要私自更新整合包里的 Mod!{vbCrLf}在更新 Mod 前,请先备份存档,并检查它的更新日志!{vbCrLf}更新时,老版本的 Mod 会被移动到回收站,以防万一。{vbCrLf}{vbCrLf}请在认真阅读上述警告后再继续!", "Mod 更新警告", "我已了解上述风险,继续更新", "取消", IsWarn:=True) = 1 Then
+ If Not Setup.Get("HintUpdateMod") OrElse ModList.Count >= 15 Then
+ If MyMsgBox($"新版本 Mod 可能不兼容旧存档或者其他 Mod,这可能导致游戏崩溃,甚至永久损坏存档!{vbCrLf}如果你在游玩整合包,请千万不要自行更新 Mod!{vbCrLf}{vbCrLf}在更新前,请先备份存档,并检查 Mod 的更新日志。{vbCrLf}如果更新后出现问题,你也可以在回收站找回更新前的 Mod。", "Mod 更新警告", "我已了解风险,继续更新", "取消", IsWarn:=True) = 1 Then
Setup.Set("HintUpdateMod", True)
Else
Exit Sub
@@ -521,12 +601,20 @@
Dim CurrentSegs = CurrentReplaceName.Split("-"c).ToList()
Dim NewestSegs = NewestReplaceName.Split("-"c).ToList()
Dim Shortened As Boolean = False
- For Each Seg In CurrentSegs.ToList()
- If Not NewestSegs.Contains(Seg) Then Continue For
- CurrentSegs.Remove(Seg)
- NewestSegs.Remove(Seg)
+ Do While True '移除前导相同部分(不能移除所有相同项,这会导致例如 1.2-forge-2 和 1.3-forge-3 中间的 forge 被去掉,导致尝试替换 1.2-2)
+ If Not CurrentSegs.Any() OrElse Not NewestSegs.Any() Then Exit Do
+ If CurrentSegs.First <> NewestSegs.First Then Exit Do
+ CurrentSegs.RemoveAt(0)
+ NewestSegs.RemoveAt(0)
Shortened = True
- Next
+ Loop
+ Do While True '移除后导相同部分
+ If Not CurrentSegs.Any() OrElse Not NewestSegs.Any() Then Exit Do
+ If CurrentSegs.Last <> NewestSegs.Last Then Exit Do
+ CurrentSegs.RemoveAt(CurrentSegs.Count - 1)
+ NewestSegs.RemoveAt(NewestSegs.Count - 1)
+ Shortened = True
+ Loop
If Shortened AndAlso CurrentSegs.Any() AndAlso NewestSegs.Any() Then
CurrentReplaceName = Join(CurrentSegs, "-")
NewestReplaceName = Join(NewestSegs, "-")
@@ -575,7 +663,14 @@
'结果提示
Select Case Loader.State
Case LoadState.Finished
- Hint(If(FinishedFileNames.Count > 1, $"已成功更新 {FinishedFileNames.Count} 个 Mod!", $"已成功更新:{FinishedFileNames.Single}"), HintType.Finish)
+ Select Case FinishedFileNames.Count
+ Case 0 '一般是由于 Mod 文件被占用,然后玩家主动取消
+ Log($"[Mod] 没有 Mod 被成功更新")
+ Case 1
+ Hint($"已成功更新 {FinishedFileNames.Single}!", HintType.Finish)
+ Case Else
+ Hint($"已成功更新 {FinishedFileNames.Count} 个 Mod!", HintType.Finish)
+ End Select
Case LoadState.Failed
Hint("Mod 更新失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
Case LoadState.Aborted
@@ -583,6 +678,7 @@
Case Else
Exit Sub
End Select
+ Log($"[Mod] 已从正在进行 Mod 更新的文件夹列表移除:{PathMods}")
UpdatingVersions.Remove(PathMods)
'清理缓存
RunInNewThread(
@@ -597,7 +693,7 @@
End Sub, "Clean Mod Update Cache", ThreadPriority.BelowNormal)
End Sub
'启动加载器
- Log($"[Mod] 开始更新 {ModList.Count} 个 Mod")
+ Log($"[Mod] 开始更新 {ModList.Count} 个 Mod:{PathMods}")
UpdatingVersions.Add(PathMods)
Loader.Start()
LoaderTaskbarAdd(Loader)
@@ -612,6 +708,7 @@
'删除
Private Sub BtnSelectDelete_Click() Handles BtnSelectDelete.Click
DeleteMods(McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName)))
+ ChangeAllSelected(False)
End Sub
Private Sub DeleteMods(ModList As IEnumerable(Of McMod))
Try
@@ -683,6 +780,7 @@
Log(ex, "删除 Mod 出现未知错误", LogLevel.Feedback)
ReloadModList(True)
End Try
+ LoaderRun(LoaderFolderRunType.UpdateOnly)
End Sub
'取消选择
@@ -700,7 +798,7 @@
Dim ModEntry As McMod = CType(If(TypeOf sender Is MyIconButton, sender.Tag, sender), MyLocalModItem).Entry
'加载失败信息
- If ModEntry.State = McMod.McModState.Unavaliable Then
+ If ModEntry.State = McMod.McModState.Unavailable Then
MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntry.FileUnavailableReason), "Mod 读取失败")
Return
End If
@@ -766,10 +864,8 @@
'打开文件所在的位置
Public Sub Open_Click(sender As MyIconButton, e As EventArgs)
Try
-
Dim ListItem As MyLocalModItem = sender.Tag
- OpenExplorer("/select,""" & ListItem.Entry.Path & """")
-
+ OpenExplorer(ListItem.Entry.Path)
Catch ex As Exception
Log(ex, "打开 Mod 文件位置失败", LogLevel.Feedback)
End Try
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml
index d461c04..5882928 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml
@@ -30,7 +30,7 @@
-
+
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
index 783a111..2f03cf4 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
@@ -235,21 +235,21 @@
OpenVersionFolder(PageVersionLeft.Version)
End Sub
Public Shared Sub OpenVersionFolder(Version As McVersion)
- OpenExplorer("""" & Version.Path & """")
+ OpenExplorer(Version.Path)
End Sub
'存档文件夹
Private Sub BtnFolderSaves_Click() Handles BtnFolderSaves.Click
Dim FolderPath As String = PageVersionLeft.Version.PathIndie & "saves\"
Directory.CreateDirectory(FolderPath)
- OpenExplorer("""" & FolderPath & """")
+ OpenExplorer(FolderPath)
End Sub
'Mod 文件夹
Private Sub BtnFolderMods_Click() Handles BtnFolderMods.Click
Dim FolderPath As String = PageVersionLeft.Version.PathIndie & "mods\"
Directory.CreateDirectory(FolderPath)
- OpenExplorer("""" & FolderPath & """")
+ OpenExplorer(FolderPath)
End Sub
#End Region
@@ -260,7 +260,7 @@
Private Sub BtnManageScript_Click() Handles BtnManageScript.Click
Try
'弹窗要求指定脚本的保存位置
- Dim SavePath As String = SelectAs("选择脚本保存位置", "启动 " & PageVersionLeft.Version.Name & ".bat", "批处理文件(*.bat)|*.bat")
+ Dim SavePath As String = SelectSaveFile("选择脚本保存位置", "启动 " & PageVersionLeft.Version.Name & ".bat", "批处理文件(*.bat)|*.bat")
If SavePath = "" Then Exit Sub
'检查中断(等玩家选完弹窗指不定任务就结束了呢……)
If McLaunchLoader.State = LoadState.Loading Then
@@ -283,6 +283,11 @@
'补全文件
Private Sub BtnManageCheck_Click(sender As Object, e As EventArgs) Handles BtnManageCheck.Click
Try
+ '忽略文件检查提示
+ If ShouldIgnoreFileCheck(PageVersionLeft.Version) Then
+ Hint("请先关闭 [版本设置 → 设置 → 高级启动选项 → 关闭文件校验],然后再尝试补全文件!", HintType.Info)
+ Exit Sub
+ End If
'重复任务检查
For Each OngoingLoader In LoaderTaskbar
If OngoingLoader.Name <> PageVersionLeft.Version.Name & " 文件补全" Then Continue For
@@ -290,7 +295,7 @@
Exit Sub
Next
'启动
- Dim Loader As New LoaderCombo(Of String)(PageVersionLeft.Version.Name & " 文件补全", DlClientFix(PageVersionLeft.Version, True, AssetsIndexExistsBehaviour.AlwaysDownload, False))
+ Dim Loader As New LoaderCombo(Of String)(PageVersionLeft.Version.Name & " 文件补全", DlClientFix(PageVersionLeft.Version, True, AssetsIndexExistsBehaviour.AlwaysDownload))
Loader.OnStateChanged =
Sub()
Select Case Loader.State
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
index 6dfb1fa..f1513de 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
@@ -6,8 +6,9 @@
mc:Ignorable="d" x:Class="PageVersionSetup"
PanScroll="{Binding ElementName=PanBack}">
-
-
+
+
@@ -25,10 +26,17 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
@@ -39,14 +47,6 @@
-
-
-
-
-
-
@@ -70,7 +70,7 @@
-
+
@@ -88,7 +88,7 @@
-
+
@@ -176,6 +176,8 @@
"
“
”
+ http://
+ https://
@@ -191,7 +193,7 @@
-
+
@@ -206,24 +208,32 @@
-
-
-
-
+
+
+
+
+ ToolTip="该项不会覆盖全局设置:启动时会先执行全局设置的命令,再执行版本设置的命令。
在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。
可以使用以下替换标记实现相对路径(路径均以 \ 结尾):
· {path}:PCL 的 exe 文件所在的文件夹
· {minecraft}:.minecraft 文件夹
· {verpath}:版本文件夹(.minecraft\versions\版本名\)
· {verindie}:开启版本隔离时等同版本文件夹,未开启时等同 .minecraft 文件夹
· {java}:游戏运行时的 Java 文件夹
除此之外,也支持以下替换标记:
· {user}:玩家名字
· {login}:玩家的登录方式
· {uuid}:玩家的 UUID
· {name}:游戏版本名
· {date}、{time}:当前的系统时间
· {version}:游戏对应的原版版本号
例如:
· "{verpath}test.exe" :运行版本文件夹下的 test.exe 程序
· "{java}java.exe" -jar "{verpath}test.jar" :用 Java 运行版本文件夹下的 test.jar
· notepad "{verindie}options.txt" :使用记事本打开该版本的设置文件
涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。
执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb
index ae11fc8..f997b5f 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml.vb
@@ -29,23 +29,8 @@
'启动参数
TextArgumentTitle.Text = Setup.Get("VersionArgumentTitle", Version:=PageVersionLeft.Version)
TextArgumentInfo.Text = Setup.Get("VersionArgumentInfo", Version:=PageVersionLeft.Version)
- Dim Indie As Integer = Setup.Get("VersionArgumentIndie", Version:=PageVersionLeft.Version)
- If Indie = -1 Then
- Dim ModFolder As New DirectoryInfo(PageVersionLeft.Version.Path & "mods\")
- Dim SaveFolder As New DirectoryInfo(PageVersionLeft.Version.Path & "saves\")
- If (ModFolder.Exists AndAlso ModFolder.EnumerateFiles.Any) OrElse (SaveFolder.Exists AndAlso SaveFolder.EnumerateFiles.Any) Then
- '自动开启
- Setup.Set("VersionArgumentIndie", 1, Version:=PageVersionLeft.Version)
- Log("[Setup] 已自动开启单版本隔离:" & PageVersionLeft.Version.Name)
- Indie = 1
- Else
- '使用全局设置
- Setup.Set("VersionArgumentIndie", 0, Version:=PageVersionLeft.Version)
- Log("[Setup] 版本隔离使用全局设置:" & PageVersionLeft.Version.Name)
- Indie = 0
- End If
- End If
- ComboArgumentIndie.SelectedIndex = Indie
+ Dim _unused = PageVersionLeft.Version.PathIndie '触发自动判定
+ ComboArgumentIndieV2.SelectedIndex = If(Setup.Get("VersionArgumentIndieV2", Version:=PageVersionLeft.Version), 0, 1)
RefreshJavaComboBox()
'游戏内存
@@ -75,6 +60,7 @@
End If
CheckAdvanceAssetsV2.Checked = Setup.Get("VersionAdvanceAssetsV2", Version:=PageVersionLeft.Version)
CheckAdvanceJava.Checked = Setup.Get("VersionAdvanceJava", Version:=PageVersionLeft.Version)
+ CheckAdvanceDisableJLW.Checked = Setup.Get("VersionAdvanceDisableJLW", Version:=PageVersionLeft.Version)
Catch ex As Exception
Log(ex, "重载版本独立设置时出错", LogLevel.Feedback)
@@ -93,7 +79,7 @@
Setup.Reset("VersionServerAuthName", Version:=PageVersionLeft.Version)
Setup.Reset("VersionArgumentTitle", Version:=PageVersionLeft.Version)
Setup.Reset("VersionArgumentInfo", Version:=PageVersionLeft.Version)
- Setup.Set("VersionArgumentIndie", 0, Version:=PageVersionLeft.Version)
+ Setup.Reset("VersionArgumentIndieV2", Version:=PageVersionLeft.Version)
Setup.Reset("VersionRamType", Version:=PageVersionLeft.Version)
Setup.Reset("VersionRamCustom", Version:=PageVersionLeft.Version)
Setup.Reset("VersionRamOptimize", Version:=PageVersionLeft.Version)
@@ -104,6 +90,7 @@
Setup.Reset("VersionAdvanceJava", Version:=PageVersionLeft.Version)
Setup.Reset("VersionAdvanceRun", Version:=PageVersionLeft.Version)
Setup.Reset("VersionAdvanceRunWait", Version:=PageVersionLeft.Version)
+ Setup.Reset("VersionAdvanceDisableJLW", Version:=PageVersionLeft.Version)
Setup.Reset("VersionArgumentJavaSelect", Version:=PageVersionLeft.Version)
JavaSearchLoader.Start(IsForceRestart:=True)
@@ -132,10 +119,13 @@
Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderRamCustom.Change
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value, Version:=PageVersionLeft.Version)
End Sub
- Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndie.SelectionChanged, ComboRamOptimize.SelectionChanged
+ Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboRamOptimize.SelectionChanged
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex, Version:=PageVersionLeft.Version)
End Sub
- Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceRunWait.Change, CheckAdvanceAssetsV2.Change, CheckAdvanceJava.Change
+ Private Shared Sub CheckBoxLikeComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndieV2.SelectionChanged
+ If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex = 0, Version:=PageVersionLeft.Version)
+ End Sub
+ Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceRunWait.Change, CheckAdvanceAssetsV2.Change, CheckAdvanceJava.Change, CheckAdvanceDisableJLW.Change
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Checked, Version:=PageVersionLeft.Version)
End Sub
@@ -152,12 +142,12 @@
Public Sub RefreshRam(ShowAnim As Boolean)
If LabRamGame Is Nothing OrElse LabRamUsed Is Nothing OrElse FrmMain.PageCurrent <> FormMain.PageType.VersionSetup OrElse FrmVersionLeft.PageID <> FormMain.PageSubType.VersionSetup Then Exit Sub
'获取内存情况
- Dim RamGame As Double = GetRam(PageVersionLeft.Version)
- Dim RamTotal As Double = Math.Round(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024 / 1024 * 10) / 10
- Dim RamAvailable As Double = Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024 * 10) / 10
- Dim RamGameActual As Double = Math.Min(RamGame, RamAvailable)
- Dim RamUsed As Double = RamTotal - RamAvailable
- Dim RamEmpty As Double = Math.Round(MathClamp(RamTotal - RamUsed - RamGame, 0, 1000) * 10) / 10
+ Dim RamGame As Double = Math.Round(GetRam(PageVersionLeft.Version), 5)
+ Dim RamTotal As Double = Math.Round(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024 / 1024, 1)
+ Dim RamAvailable As Double = Math.Round(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024 / 1024, 1)
+ Dim RamGameActual As Double = Math.Round(Math.Min(RamGame, RamAvailable), 5)
+ Dim RamUsed As Double = Math.Round(RamTotal - RamAvailable, 5)
+ Dim RamEmpty As Double = Math.Round(MathClamp(RamTotal - RamUsed - RamGame, 0, 1000), 1)
'设置最大可用内存
If RamTotal <= 1.5 Then
SliderRamCustom.MaxValue = Math.Max(Math.Floor((RamTotal - 0.3) / 0.1), 1)
@@ -166,14 +156,14 @@
ElseIf RamTotal <= 16 Then
SliderRamCustom.MaxValue = Math.Floor((RamTotal - 8) / 1) + 25
Else
- SliderRamCustom.MaxValue = Math.Min(Math.Floor((RamTotal - 16) / 2) + 33, 41)
+ SliderRamCustom.MaxValue = Math.Floor((RamTotal - 16) / 2) + 33
End If
'设置文本
LabRamGame.Text = If(RamGame = Math.Floor(RamGame), RamGame & ".0", RamGame) & " GB" &
If(RamGame <> RamGameActual, " (可用 " & If(RamGameActual = Math.Floor(RamGameActual), RamGameActual & ".0", RamGameActual) & " GB)", "")
LabRamUsed.Text = If(RamUsed = Math.Floor(RamUsed), RamUsed & ".0", RamUsed) & " GB"
LabRamTotal.Text = " / " & If(RamTotal = Math.Floor(RamTotal), RamTotal & ".0", RamTotal) & " GB"
- LabRamWarn.Visibility = If(RamGame = 1 AndAlso Not JavaIs64Bit(PageVersionLeft.Version) AndAlso Not Is32BitSystem, Visibility.Visible, Visibility.Collapsed)
+ LabRamWarn.Visibility = If(RamGame = 1 AndAlso Not JavaIs64Bit(PageVersionLeft.Version) AndAlso Not Is32BitSystem AndAlso JavaList.Any, Visibility.Visible, Visibility.Collapsed)
If ShowAnim Then
'宽度动画
AniStart({
@@ -278,10 +268,15 @@
''' 获取当前设置的 RAM 值。单位为 GB。
'''
Public Shared Function GetRam(Version As McVersion, Optional Is32BitJava As Boolean? = Nothing) As Double
- '使用全局设置
+ '跟随全局设置
If Setup.Get("VersionRamType", Version:=Version) = 2 Then
Return PageSetupLaunch.GetRam(Version, True, Is32BitJava)
End If
+
+ '------------------------------------------
+ ' 修改下方代码时需要一并修改 PageSetupLaunch
+ '------------------------------------------
+
'使用当前版本的设置
Dim RamGive As Double
If Setup.Get("VersionRamType", Version:=Version) = 0 Then
@@ -297,19 +292,19 @@
'可安装 Mod 的版本
Dim ModDir As New DirectoryInfo(Version.PathIndie & "mods\")
Dim ModCount As Integer = If(ModDir.Exists, ModDir.GetFiles.Length, 0)
- RamMininum = 0.4 + ModCount / 150
- RamTarget1 = 1.5 + ModCount / 100
- RamTarget2 = 3 + ModCount / 60
- RamTarget3 = 6 + ModCount / 30
+ RamMininum = 0.5 + ModCount / 150
+ RamTarget1 = 1.5 + ModCount / 90
+ RamTarget2 = 2.7 + ModCount / 50
+ RamTarget3 = 4.5 + ModCount / 25
ElseIf Version IsNot Nothing AndAlso Version.Version.HasOptiFine Then
'OptiFine 版本
- RamMininum = 0.3
+ RamMininum = 0.5
RamTarget1 = 1.5
RamTarget2 = 3
- RamTarget3 = 6
+ RamTarget3 = 5
Else
'普通版本
- RamMininum = 0.3
+ RamMininum = 0.5
RamTarget1 = 1.5
RamTarget2 = 2.5
RamTarget3 = 4
@@ -317,27 +312,23 @@
Dim RamDelta As Double
'预分配内存,阶段一,0 ~ T1,100%
RamDelta = RamTarget1
- RamAvailable = Math.Max(0, RamAvailable - 0.1)
- RamGive += Math.Min(RamAvailable * 1, RamDelta)
- RamAvailable -= RamDelta / 1 + 0.1
+ RamGive += Math.Min(RamAvailable, RamDelta)
+ RamAvailable -= RamDelta
If RamAvailable < 0.1 Then GoTo PreFin
- '预分配内存,阶段二,T1 ~ T2,80%
+ '预分配内存,阶段二,T1 ~ T2,70%
RamDelta = RamTarget2 - RamTarget1
- RamAvailable = Math.Max(0, RamAvailable - 0.1)
- RamGive += Math.Min(RamAvailable * 0.8, RamDelta)
- RamAvailable -= RamDelta / 0.8 + 0.1
+ RamGive += Math.Min(RamAvailable * 0.7, RamDelta)
+ RamAvailable -= RamDelta / 0.7
If RamAvailable < 0.1 Then GoTo PreFin
- '预分配内存,阶段三,T2 ~ T3,60%
+ '预分配内存,阶段三,T2 ~ T3,40%
RamDelta = RamTarget3 - RamTarget2
- RamAvailable = Math.Max(0, RamAvailable - 0.2)
- RamGive += Math.Min(RamAvailable * 0.6, RamDelta)
- RamAvailable -= RamDelta / 0.6 + 0.2
- If RamAvailable < 0.1 Then GoTo PreFin
- '预分配内存,阶段四,T3 ~ T3 * 2,40%
- RamDelta = RamTarget3
- RamAvailable = Math.Max(0, RamAvailable - 0.3)
RamGive += Math.Min(RamAvailable * 0.4, RamDelta)
- RamAvailable -= RamDelta / 0.4 + 0.3
+ RamAvailable -= RamDelta / 0.4
+ If RamAvailable < 0.1 Then GoTo PreFin
+ '预分配内存,阶段四,T3 ~ T3 * 2,15%
+ RamDelta = RamTarget3
+ RamGive += Math.Min(RamAvailable * 0.15, RamDelta)
+ RamAvailable -= RamDelta / 0.15
If RamAvailable < 0.1 Then GoTo PreFin
PreFin:
'不低于最低值
@@ -357,8 +348,7 @@ PreFin:
End If
'若使用 32 位 Java,则限制为 1G
If If(Is32BitJava, Not JavaIs64Bit(PageVersionLeft.Version)) Then RamGive = Math.Min(1, RamGive)
- '封顶 32G
- Return Math.Min(32, RamGive)
+ Return RamGive
End Function
#End Region
@@ -371,8 +361,8 @@ PreFin:
If AniControlEnabled <> 0 Then Exit Sub
ServerLogin(ComboServerLogin.SelectedIndex)
'检查是否输入正确,正确才触发设置改变
- If ComboServerLogin.SelectedIndex = 3 AndAlso TextServerNide.ValidateResult <> "" Then Exit Sub
- If ComboServerLogin.SelectedIndex = 4 AndAlso TextServerAuthServer.ValidateResult <> "" Then Exit Sub
+ If ComboServerLogin.SelectedIndex = 3 AndAlso Not TextServerNide.IsValidated Then Exit Sub
+ If ComboServerLogin.SelectedIndex = 4 AndAlso Not TextServerAuthServer.IsValidated Then Exit Sub
'检查结果是否发生改变,未改变则不触发设置改变
If ComboServerLoginLast = ComboServerLogin.SelectedIndex Then Exit Sub
'触发
@@ -418,13 +408,13 @@ PreFin:
If ComboArgumentJava Is Nothing Then Exit Sub
'初始化列表
ComboArgumentJava.Items.Clear()
- ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "使用全局设置", .Tag = "使用全局设置"})
+ ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "跟随全局设置", .Tag = "使用全局设置"})
ComboArgumentJava.Items.Add(New MyComboBoxItem With {.Content = "自动选择合适的 Java", .Tag = "自动选择"})
'更新列表
Dim SelectedItem As MyComboBoxItem = Nothing
Dim SelectedBySetup As String = Setup.Get("VersionArgumentJavaSelect", Version:=PageVersionLeft.Version)
Try
- For Each Java In Sort(JavaList.Clone(), Function(l, r) l.VersionCode < r.VersionCode)
+ For Each Java In JavaList.Clone().OrderByDescending(Function(v) v.VersionCode)
Dim ListItem = New MyComboBoxItem With {.Content = Java.ToString, .ToolTip = Java.PathFolder, .Tag = Java}
ToolTipService.SetHorizontalOffset(ListItem, 400)
ComboArgumentJava.Items.Add(ListItem)
@@ -441,7 +431,7 @@ PreFin:
If SelectedBySetup = "" Then
SelectedItem = ComboArgumentJava.Items(1) '选中 “自动选择”
Else
- SelectedItem = ComboArgumentJava.Items(0) '选中 “使用全局设置”
+ SelectedItem = ComboArgumentJava.Items(0) '选中 “跟随全局设置”
End If
End If
ComboArgumentJava.SelectedItem = SelectedItem
@@ -469,7 +459,7 @@ PreFin:
If "使用全局设置".Equals(SelectedJava) Then
'选择 “自动”
Setup.Set("VersionArgumentJavaSelect", "使用全局设置", Version:=PageVersionLeft.Version)
- Log("[Java] 修改版本 Java 选择设置:使用全局设置")
+ Log("[Java] 修改版本 Java 选择设置:跟随全局设置")
ElseIf "自动选择".Equals(SelectedJava) Then
'选择 “自动”
Setup.Set("VersionArgumentJavaSelect", "", Version:=PageVersionLeft.Version)
@@ -484,6 +474,25 @@ PreFin:
#End Region
+#Region "其他设置"
+
+ '版本隔离警告
+ Private IsReverting As Boolean = False
+ Private Sub ComboArgumentIndieV2_SelectionChanged(sender As Object, e As SelectionChangedEventArgs) Handles ComboArgumentIndieV2.SelectionChanged
+ If AniControlEnabled <> 0 Then Exit Sub
+ If IsReverting Then Exit Sub
+ If MyMsgBox("调整版本隔离后,你可能得把游戏存档、Mod 等文件手动迁移到新的游戏文件夹中。" & vbCrLf &
+ "如果修改后发现存档消失,把这项设置改回来就能恢复。" & vbCrLf &
+ "如果你不会迁移存档,不建议修改这项设置!",
+ "警告", "我知道我在做什么", "取消", IsWarn:=True) = 2 Then
+ IsReverting = True
+ ComboArgumentIndieV2.SelectedItem = e.RemovedItems(0)
+ IsReverting = False
+ End If
+ End Sub
+
+#End Region
+
#Region "高级设置"
Private Sub TextAdvanceRun_TextChanged(sender As Object, e As TextChangedEventArgs) Handles TextAdvanceRun.TextChanged
@@ -492,4 +501,9 @@ PreFin:
#End Region
+ '切换到全局设置
+ Private Sub BtnSwitch_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnSwitch.Click
+ FrmMain.PageChange(FormMain.PageType.Setup, FormMain.PageSubType.SetupLaunch)
+ End Sub
+
End Class
diff --git a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
index 1bb206d..f2ea5d4 100644
--- a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
+++ b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
@@ -1,4 +1,4 @@
-
+
Debug
@@ -143,6 +143,7 @@
False
Resources\Imazen.WebP.dll
+ False
False
@@ -164,6 +165,7 @@
+
@@ -184,6 +186,10 @@
+
+ MyExtraTextButton.xaml
+
+
@@ -233,6 +239,9 @@
MyCompItem.xaml
+
+ PageDownloadResourcePack.xaml
+
PageDownloadMod.xaml
@@ -257,6 +266,9 @@
FormLoginOAuth.xaml
+
+ MyMsgLogin.xaml
+
PageLoginAuth.xaml
@@ -287,6 +299,9 @@
PageLinkLeft.xaml
+
+ PageVersionExport.xaml
+
PageVersionLeft.xaml
@@ -379,6 +394,10 @@
PageSpeedRight.xaml
+
+ MSBuild:Compile
+ Designer
+
Designer
MSBuild:Compile
@@ -484,6 +503,10 @@
MSBuild:Compile
Designer
+
+ MSBuild:Compile
+ Designer
+
Designer
MSBuild:Compile
@@ -513,8 +536,12 @@
Designer
- Designer
MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
MSBuild:Compile
@@ -561,6 +588,10 @@
MSBuild:Compile
Designer
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
@@ -689,6 +720,7 @@
+
@@ -888,26 +920,6 @@
-
-
- {1C565858-F302-471E-B409-F180AA4ABEC6}
- 1
- 0
- 0
- tlbimp
- False
- True
-
-
- {58FBCF7C-E7A9-467C-80B3-FC65E8FCCA08}
- 1
- 0
- 0
- tlbimp
- False
- True
-
-
@@ -928,15 +940,12 @@
-
+
-
-
-
@@ -944,5 +953,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Resources/Custom.xaml b/Plain Craft Launcher 2/Resources/Custom.xaml
index df177e3..8dbed55 100644
--- a/Plain Craft Launcher 2/Resources/Custom.xaml
+++ b/Plain Craft Launcher 2/Resources/Custom.xaml
@@ -4,311 +4,309 @@
你也可以使用 Ctrl + F 快速查找。例如,若需要改变文本颜色,则搜索 “颜色” 即可。 -->
-
+
-
+
-
-
-
+
+
-
+
-
+
-
-
+ Text="local:MyImage 代表图片,你需要在它的 Source 属性中填写一个网址或文件路径,它会从该处获取图片并显示。通常需要使用 Height 限制它的高度。" />
+
+
-
+
-
-
-
-
+
-
+
-
-
-
-
+
-
+
-
-
-
-
-
+
-
-
+
+
-
+ Text="将 EventType 设置为 执行命令(或 打开文件),EventData 设置为文件路径,即可点击打开特定文件或启动程序。" />
+
-
-
-
+
-
-
+
+
-
-
+
-
-
-
-
-
-
-
+
-
-
-
-
+
-
-
+
-
-
+
-
-
-
+
+
-
-
-
-
-
-
+
+
+
-
-
-
-
+
+
-
+
-
+
-
+ EventType="打开帮助" EventData="帮助/自定义帮助.json" Type="Clickable" />
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
-
+
-
+
-
-
+
+
-
-
+
-
+
-
-
+
-
-
+
-
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
+
+
-
-
-
+
+
-
-
-
-
-
-
-
-
+
-
-
-
+
+
-
-
+
diff --git a/Plain Craft Launcher 2/Resources/Help.zip b/Plain Craft Launcher 2/Resources/Help.zip
index a49d41f..d204879 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/ModData.txt b/Plain Craft Launcher 2/Resources/ModData.txt
index 89b8ba9..bda7ce3 100644
--- a/Plain Craft Launcher 2/Resources/ModData.txt
+++ b/Plain Craft Launcher 2/Resources/ModData.txt
@@ -17,9 +17,9 @@ powerconverters-portablejims-fork|能源转换 (PowerConverters)
modular-forcefield-system|模块化力场系统*
minechem-recompile|化学 (MineChem) - 非官方重编译版¨73373|化学 (MineChem) - 已失效
-iron-chests@cyberanner-ironchest|更多箱子*¨iron-chests-for-fabric|更多箱子 (Iron Chests)¨iron-chests-fabric@|更多箱子 (Iron Chests)
+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|高级机械 (AdvancedMachines)¨@advanced-machines-uuum|高级机械 (AdvancedMachines) - Immibis版
advanced-solar-panels|高级太阳能*
nuclear-control-2|核电控制2*
@@ -91,7 +91,7 @@ applied-energistics-1@ae1|应用能源*
Universal-Electricity|通用电力*
@better-than-wolves|比狼好 (Better Than Wolves)
-the-aether@aether|天境*
+aether@|天境 (The Aether)
assassincraft-assassins-creed-mod|刺客信条 (AssassinCraft)
@@ -118,7 +118,7 @@ atum|阿图姆:沙漠之旅/亚图姆:金沙之旅 (Atum 2: Return to the Sa
blockphysics_by_id_miner|方块物理学 (BlockPhysics)
-remain-in-motion|运动红石机构 (Redstone in motion)
+remain-in-motion|运动红石机构 (Remain in Motion)
modular-powersuits|模块化动力套装*
@@ -161,9 +161,9 @@ minechess|国际象棋 (MineChess)
-project-red-base|红石计划 (ProjectRed) - 基础¨project-red-illumination|红石计划:照明 (Project Red - Illumination)¨project-red-core|红石计划 (ProjectRed)
+project-red-base|红石计划 (ProjectRed) - 基础¨project-red-illumination|红石计划:照明 (Project Red - Illumination)¨project-red-core@|红石计划 (ProjectRed)
-dimensionaldoors|次元门 (Dimensional Doors)
+dimensionaldoors@|次元门 (Dimensional Doors)
chickenchunks@chicken-chunks|区块载入器 (ChickenChunks)¨chicken-chunks-1-8|区块载入器 (ChickenChunks)
dlt2|动态蓄水槽 (Dynamic Liquid Tanks)
@@ -175,8 +175,8 @@ extracells2|更多存储单元2 (Extra Cells 2)
billund-223258|乐高 (Billund)
-alchemyplusplus|炼金++ (Alchemy++)
-better-bows|更好弓箭系统 (Better Archery Mod)
+alchemyplusplus|炼金++ (AlchemyPlusPlus)
+better-bows|更好弓箭系统 (Better Archery)
mumblelink|Mumble语音链接*
ender-io@enderio|末影接口 (Ender IO)
@@ -225,7 +225,7 @@ openblocks|开放式方块 (OpenBlocks)
randomcraft-v0-1-0|随机工艺 (RandomCraft)
-
+dragon-mounts|龙坐骑/龙骑士*
animal-bikes|动物坐骑*
@@ -247,7 +247,7 @@ waila|高亮信息拓展 (What am I looking at)
chaoscraft-2|混沌工艺 (ChaosCraft)
scenter-mod|矿物追踪 (Scenter)
-ex-nihilo|无中生有*
+ex-nihilo|无中生有*¨ex-nihilo-2|无中生有 (Ex Nihilo)
reactorcraft|反应堆工艺 (ReactorCraft)
bctools|BC扩展:更多工具 (Buildcraft Tools)
additional-buildcraft-objects|更多管道*
@@ -275,10 +275,10 @@ pchan3-steamship-steamboat-pirates|飞艇与汽船 (SteamShip - SteamBoat and Pi
betterfarming|农业增强 (Better Farming)
-lots-of-food|更多食物*
+lots-of-food|更多食物 (Lots of Food)
-aquaculture|水产业2/水产品2 (Aquaculture 2)
+aquaculture@|水产业2/水产品2 (Aquaculture 2)
project-bench|高级工作台*
apples-plus|更多苹果 (Apples+)
@@ -295,7 +295,7 @@ lucky-block-official|幸运方块 (Lucky Block)
dynamic-lights|动态光源*
yarr-cute-mob-models-remake|怪物娘化 (Yarr Cute Mob Models - Remake)
monster-hunter-frontier-craft|怪物猎人边境工艺*
-zombie-awareness|僵尸意识*
+zombie-awareness@|僵尸意识*
remote-io|远程接口 (Remote IO)
electrical-age|电力时代*
@@ -312,7 +312,7 @@ mcicraft|MCI工艺 (MCI Craft)
engineers-toolbox|工程师工具箱 (Engineer's Toolbox)
e-tools-redstone-powered-tools|电子工具:红石供电工具 (E-Tools: Redstone Powered Tools)
-modular-systems|模块化熔炉2 (Modular Furnaces 2/Modular Systems)
+modular-systems|模块化系统*
traincraft|火车工业*
hydraulicraft|液压工艺*
@@ -349,7 +349,7 @@ ex-aliquo
steves-carts-2|史蒂夫矿车2 (Steve's Carts 2)
mariculture|海洋物语*
-evilcraft|邪恶工艺 (EvilCraft)
+evilcraft@|邪恶工艺 (EvilCraft)
projecte|等价交换重制版 (ProjectE)
falling-meteors-mod|陨石 (Falling Meteors)
@@ -376,7 +376,7 @@ pet-bats|宠物蝙蝠*
crafting-pillar-mod|合成柱*
ultratech|次元科技 (UltraTech)
-
+@advanced-repulsion-systems|高级排斥系统 (Advanced Repulsion Systems)
atomicstrykers-infernal-mobs|稀有精英怪 (AtomicStryker's Infernal Mobs)
applecore|苹果核 (AppleCore)
@@ -422,7 +422,7 @@ multi-page-chest|多页箱子 (Multi-Page Chest)
draconic-evolution@|龙之进化/龙之研究*
galacticraft-add-on-more-planets@more-planets|更多行星 (More Planets)
-thermal-expansion@thermal-foundation|热力基本 (Thermal Foundation)
+thermal-foundation@|热力基本*
ding@|界面进入提醒*
@@ -462,7 +462,7 @@ magneticraft|磁场工艺 (MagnetiCraft)
chromaticraft|缤纷纪元 (ChromatiCraft)
immersive-engineering@immersiveengineering|沉浸工程*
armourers-workshop@|时装工坊 (Armourer's Workshop)
-
+plants-to-ores|植物矿石*
slash|命令强化*
et-futurum|把未来带回现实*
cooking-for-blockheads@|傻瓜烹饪/懒人厨房 (Cooking for Blockheads)¨cooking-for-blockheads-fabric|傻瓜烹饪/懒人厨房 (Cooking for Blockheads)
@@ -471,12 +471,12 @@ psi@
good-nights-sleep|夜眠梦境/晚安的梦境 (Good Night's Sleep)
binnies-mods@|宾尼的模组合集 (Binnie's Mods)
baubles|饰品栏*
-more-swords-mod|更多剑 (MoreSwords)
+more-swords-mod@|更多剑 (MoreSwords)
thaumic-bases|神秘基础学*¨thaumic-bases-unofficial|神秘基础学 (Thaumic Bases)
essentialcraft-3|源质魔法3 (EssentialCraft 3)
-hardcore-questing-mode|困难任务*
-unidict|矿辞统一 (UniDict)
+hardcore-questing-mode@hqm|困难任务*
+unidict@|矿辞统一 (UniDict)
thermal-dynamics@|热动力学*
zoology|动物学*
@@ -522,7 +522,7 @@ inventory-pets|背包宠物*
levels|装备等级*
keeping-inventory
orespawn|矿石菌种 (OreSpawn)
-reliquary-v1-3|圣遗物 (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@|夸克*
@@ -535,7 +535,7 @@ better-storage|更好的储存*
davincis-vessels|达芬奇之舟/达芬奇的船*
bspkrscore|bspkrs核心 (bspkrsCore)¨bspkrscore-updated|bspkrs核心 (bspkrsCore)
techguns|科技枪*
-Iron-Backpacks|特殊背包*
+iron-backpacks|特殊背包*
openmodularturrets|开放式炮台 (OpenModularTurrets)
technomancy|量子魔法*
quantumflux|量子通量 (QuantumFlux)
@@ -566,8 +566,8 @@ aobd-singularities|AOBD的奇点 (AOBD Singularities)
universal-singularities|通用奇点*
academy-monster|超能力怪物*
sonar-core|声呐核心*
-armorstatushud-updated|耐久信息显示 (ArmorStatusHUD)¨armorstatushud|耐久信息显示 (ArmorStatusHUD)
-wanionlib
+armorstatushud|耐久信息显示 (ArmorStatusHUD)
+wanionlib@
llibrary
@@ -631,7 +631,7 @@ stimmedcow-nomorerecipeconflict|合成冲突消除 (NoMoreRecipeConflict)
tinker-i-o|工匠接口 (Tinker I/O)
woot|生物工厂*
-
+thermal-expansion|热力膨胀*
realistic-terrain-generation|真实地形生成*¨realistic-terrain-generation-plus|真实地形生成 (Realistic Terrain Generation) - RTG原作者继续开发的分支
mob-rebirth|怪物重生*
diseasecraft|疾病 (Disease Craft)
@@ -642,13 +642,13 @@ apocalypse-mod|启示录 (Apocalypse)
bookshelf-api-library
movingworld
-botania-garden-of-glass|水晶花园 (Garden of Glass)
+botania-garden-of-glass@gardenofglass|水晶花园 (Garden of Glass)
@tubestuff|RP增强 (Tube Stuff)
tiny-progressions|微型自动化*
baubles-stuff|更多首饰*
thaumaturgical-knowledge|神秘奥义*
-advanced-thaumaturgy-2|高等神秘学*
+advanced-thaumaturgy-2|高等神秘学 (Advanced Thaumaturgy)
INpureCore
atomicstrykers-battle-towers|战斗高塔 (AtomicStryker's Battle Towers)
reborncore|重生核心 (Reborn Core)
@@ -672,7 +672,7 @@ real-time-chat-translation-mod|实时聊天翻译*
practical-logistics-2|实用物流监控2*
energyadditions|高级能源 (EnergyAdditions)
-advanced-generators|高级发电机*
+advanced-generators@|高级发电机*
bdlib
pressure-pipes|压力管道*
@@ -685,8 +685,8 @@ mcjtylib@
mcmultipart
aroma1997s-dimensional-world|Aroma1997的维度世界 (Aroma1997's Dimensional World)
project-73926|超级合成框架 (Super Crafting Frame)
-akashic-tome|阿卡什宝典*
-morph-o-tool|变形工具 (Morph-o-Tool)
+akashic-tome@|阿卡什宝典*
+morph-o-tool@|变形工具 (Morph-o-Tool)
decocraft@|装饰工艺*
refined-storage@|精致存储*
deep-resonance@|深度共振*
@@ -777,19 +777,19 @@ wireless-crafting-grid|无线合成网络*
top-addons
torchmaster@|火炬大师*
-ZeroCore
+zerocore@
sleeping-bag|睡袋*
the-one-probe@|检测器*
simply-jetpacks-2|简易喷气背包2*
snad|子沙*
storage-drawers-extras
-solar-flux-reborn|日光通量:重制版*
+solar-flux-reborn@|日光通量:重制版*
simple-void-world|简单虚空世界*
shetiphiancore@¨shetiphiancore-fabric
shadowmc
scannable@|扫描器*
-rebornstorage|存储重置 (Reborn Storage)
+rebornstorage@|存储重置 (Reborn Storage)
quantumstorage|量子存储 (QuantumStorage)
p455w0rds-library
mtlib
@@ -805,7 +805,7 @@ flat-colored-blocks-forge|平滑色块 (Flat Colored Blocks)¨flat-colored-block
jei-bees
chisels-bits|雕凿工艺 (Chisels & Bits)¨chisels-bits-for-fabric|雕凿工艺 (Chisels & Bits)
architecturecraft|建筑师工艺 (Architecture Craft)
-chameleon|变色龙*
+chameleon@texel-chameleon|变色龙*
mrtjpcore
cyclops-core@
netherportalfix@|下界传送门修正 (NetherPortalFix)¨netherportalfix-fabric|下界传送门修正 (NetherPortalFix)
@@ -831,7 +831,7 @@ teletoro|远程传送 (TeleToro)
re-zero-kara-hajimeru-isekai-seikatsu|RE:从零开始的异世界 (Re: Zero kara hajimeru isekai seikatsu)
minewatch
thaumcraft-inventory-scanning|神秘时代物品栏扫描*
-cyclic|循环*
+cyclic@|循环*
redstoneplusplus|红石++ (Redstone++)
ctm|连接纹理 (ConnectedTexturesMod)
@@ -934,19 +934,19 @@ minenautica
improved-extraction|矿物获取改进*
ecology-mod|生态*
cakeisalie-mod|蛋糕是个谎言 (CakeIsALie)
-ex-nihilo-adscensio|无中生有:Adscensio*
+ex-nihilo-adscensio|无中生有:Adscensio*
bookshelf@bookshelf-lib
random-things|随意作品*
netherex|下界拓展 (NetherEX)
-engender-mod|召唤师 (Engender - The Age of Minecraft)
+engender-mod|召唤师 (The Engender Mod)
better-questing|更好的任务*
ct-mortar
ct-watercan
-totemic|图腾*
+totemic@fenns_totemic|图腾*
grimoire-of-alice|爱丽丝的魔法书 (Grimoire of Alice)
journey-to-gensokyo|幻想乡之旅 (Journey to Gensokyo)
danmakucore|弹幕核心 (DanmakuCore)
@@ -954,7 +954,7 @@ angel-of-vengeance|复仇天使 (Angel of Vengeance)
tammodized
ex-nihilo-creatio|无中生有:创世 (Ex Nihilo: Creatio)
thaumcraft|神秘时代*
-colossal-chests|巨型箱子*
+colossal-chests@|巨型箱子*
auto-pickup|自动拾取*
mc-mods-u-want|背包编辑器 (All-U-Want)
potion-core|药水核心*
@@ -962,7 +962,7 @@ solar-expansion
blue-power|蓝石力量 (Blue power)
redstone-armory|红石军械库*
qmunitylib
-
+quest-for-blue-name
the-last-smith|最后的太刀匠人*
mineamp|我的音乐 (MineAmp)
quiverbow|Quiver Bow - 官方原版¨quiverbow-restrung|Quiver Bow - 重制版
@@ -972,7 +972,7 @@ meecreeps@|使命必达先生 (MeeCreeps)
streams|溪流*
blockcraftery|方块工匠*
-bedrockores|基岩矿簇 (Bedrock Ores)
+bedrockores@|基岩矿簇 (Bedrock Ores)
chat-bubbles|聊天泡泡*
refined-storage-addons@|精致存储附属*
foamfix-optimization-mod@foamfix|泡沫修复 (FoamFix)
@@ -981,7 +981,7 @@ expanded-armory|扩展装备*
voxelmap|体素地图 (VoxelMap)
-mowzies-mobs|Mowzie的生物 (Mowzie's Mobs)
+mowzies-mobs@|Mowzie的生物 (Mowzie's Mobs)
thaumcraft-mob-aspects
magical-psi
starcraft-mod-scmc|星际争霸2 (StarCraft II in MineCraft)
@@ -1002,10 +1002,10 @@ storage-boats-mod|存储船 (Storage Boat Mod)
mo-withers|更多凋灵 (Mo' Withers)
jingames-naruto-c|火影忍者C (Naruto C)
the-autologin-mod-for-authme-server|自动登录 (AutoLogin)
-ROOST|鸡窝 (Roost)
+roost|鸡窝*
croparia@|矿石作物/魔种之咏*
simple-corn|简单玉米*
-thaumic-jei
+thaumic-jei@
tinkers-steelworks|匠魂炼钢厂 (Tinkers' Steelworks)
unicode-font-extension|Unicode 字体扩展*
placeable-items|可放置物品*
@@ -1042,7 +1042,7 @@ toxicrain|有毒的雨 (ToxicRain)
cannibalism|割肉小刀/同类相食*
quick-consume|快速消耗*
-planting-dirt-for-saplings|自动植树机 (Planting dirt for saplings)
+planting-dirt-for-saplings@auto-planter|自动植树机 (Auto Planter / Planting dirt for saplings)
custom-crosshair-mod@|自定义准心*
better-blink|更好的传送*
@@ -1079,7 +1079,7 @@ rough-tweaks|难度调整*
the-disenchanter-mod|祛魔台 (The Disenchanter)
carry-on@|搬运*
-better-than-llamas|更好看的羊驼*¨better-than-llamas-fabric|更好看的羊驼 (Better Than Llamas)
+better-than-llamas@|更好看的羊驼*¨better-than-llamas-fabric|更好看的羊驼 (Better Than Llamas)
dynamic-surroundings@dynamicsurroundingsfabric|动态环境/动态环绕*¨dynamic-surroundings-fabric-edition|动态环境/动态环绕 (Dynamic Surroundings)
alfheim@|亚尔夫海姆*
mob-battle-mod@|怪物大乱斗*¨mob-battle-mod-fabric|怪物大乱斗 (Mob Battle Mod)
@@ -1108,7 +1108,7 @@ misty-world@|朦胧世界*
beta-kathairis|卡塞瑞斯 (Kathairis / Kether)¨kathairis-alive|卡塞瑞斯 (Kathairis / Kether)
-manametal|魔法金属 (ManaMetalMod)
+manametal@|魔法金属 (ManaMetalMod)
avaritiaddons|无尽收容 / 更多空间箱子*
base
immersive-cables|沉浸线缆*
@@ -1119,7 +1119,7 @@ lambda-craft|半条命 (LambdaCraft)
vivecraft@¨vivecraft-forge-extensions
super-sound-muffler|超级消音器*
armorplus@|装备扩充 (ArmorPlus)
-inspirations|灵感*
+inspirations@|灵感*
tree-growing-simulator|跳舞树成长*
personal-cars
@@ -1155,12 +1155,12 @@ reactioncraft-3-rebirth|世界反应3:重置 (Reactioncraft 3: Rebirth)
pewter|魔法匠魂*
redstone-repository-revolved|红石军械库重制版*
spawncommands-spawn-commands-teleport|传送指令 (Spawn Commands Teleport)
-server-properties-for-lan@|简单联机 (Server.Properties for LAN)
+server-properties-for-lan|简单联机 (Server.Properties for LAN)¨@server-properties-for-lan|简单联机 (Server.Properties for LAN) - 已失效
jaopcaadditions|JAOPCA附属 (JAOPCAAdditions)
cloche-call
-mouse-tweaks@|鼠标手势*
-jaopcaagriculture|JAOPCA神秘农业附属 (Just A Resource Generation Compatibility Attempt)
+mouse-tweaks|鼠标手势*¨@mouse-tweaks|鼠标手势 (Mouse Tweaks) - 仅部分
+jargca|JAOPCA神秘农业附属 (Just A Resource Generation Compatibility Attempt)
sky-resources|空岛资源2 (Sky Resources2)
hexxit-gear|Hexxit 装备*¨hexxit-gear-restored|Hexxit 装备 (Hexxit Gear)
@@ -1169,7 +1169,7 @@ inventory-crafting-grid|背包工作台*
kleeslabs@|更好地破坏半砖 (KleeSlabs)¨kleeslabs-fabric|更好地破坏半砖 (KleeSlabs)
gregtechce|格雷科技社区版 (GregTech Community Edition)
-blur@blur-fabric|界面背景模糊*¨blur-fabric|界面背景模糊 (Blur)
+blur@blur-plus|界面背景模糊*¨blur-fabric|界面背景模糊 (Blur)
fast-leaf-decay|树叶快速腐烂*
enable-cheats|开启作弊模式*
findit|找到它 (Findit!)
@@ -1179,7 +1179,7 @@ wearable-blocks|可穿戴的方块*
swissarmyknife|瑞士军刀 (SwissArmyKnife)
ender-io-addons|末影接口拓展 (Ender IO Addons)
historicized-medicine|历史化医学*
-infinity-item-editor|无尽物品编辑器*
+infinity-item-editor@|无尽物品编辑器*
extra-rails|更多功能铁轨*
gregtania
origin
@@ -1205,7 +1205,7 @@ stellar-sky|群星璀璨*
stellar-api|恒星API (Stellar API)
nei-addons|NEI扩充 (NEI Addons)
cucumber@
-hunting-dimension|狩猎维度*
+hunting-dimension@|狩猎维度*
runes-of-wizardry|符文魔法 (Runes-of-Wizardry)
coffee-workshop|咖啡工坊*
@@ -1213,7 +1213,7 @@ in-game-account-switcher@|游戏内账号切换 (In-Game Account Switcher)
consecration@|圣化*
ignite-hud|燃点HUD (Ignite HUD)
mrcrayfishs-gun-mod|MrCrayfish的枪 (MrCrayfish's Gun Mod)
-bloodmoon|血月 (bloodmoon)
+bloodmoon|血月*
obfuscate
runic-dungeons|符文地牢 (Runic Dungeon)
poop|屎 (Poop Mod)
@@ -1222,7 +1222,7 @@ tough-expansion|意志坚定:热力附属*
mystical-agradditions@|神秘农业扩展*
vanillafix@|原版修复 (VanillaFix)
just-enough-harvestcraft|更多潘马斯配方查询 (Just Enough HarvestCraft)
-charcoal-pit@charcoal_pit
+charcoal-pit-4@charcoal_pit¨charcoal-pit-3¨charcoal-pit
uncomplication|IC2exp还原*
the-wings-of-alfheim-1-7-10|亚尔夫海姆之翼 (The Wings of Alfheim)
@@ -1243,7 +1243,7 @@ the-mists-of-riov|神秘世界 (The Mists of RioV Mod)
nsr-no-respawn-screen|无重生屏幕 (No Respawn Screen)
more-bees|蜜蜂拓展*
evilnotchlib
-tpalette|调色板 (TPalette)
+tpalette-forge|调色板 (TPalette)¨tpalette|调色板 (TPalette) - 文件覆盖
career-bees|职业蜜蜂*
@@ -1252,7 +1252,7 @@ level-up-legacy|升级!前版 (Level Up!Legacy)
moar-boats|模块化船*
numina|模块化动力装甲前置*
tinkers-complement@|匠魂补充 (Tinkers' Complement)
-everlastingabilities@everlasting-abilities|永恒能力 (EverlastingAbilities)
+everlastingabilities@everlasting-abilities|永恒能力 (Everlasting Abilities)
abyssalcraft-heads|深渊国度头颅 (AbyssalCraft Heads)
overlord@|不死者之王*
multifarm-crops|多功能农场兼容*
@@ -1271,8 +1271,8 @@ toroquest|托罗探索 (Toro Quest)
rikmulds-core-mod
just-enough-pattern-banners
-primitive-mobs|远古生物/原始生物*
-realistic-cobwebs
+primitive-mobs|远古生物 / 原始生物*
+realistic-cobwebs@
simpleharvest|简单收获 (Simple Harvest)
mrcrayfishs-device-mod|MrCrayfish的设备 (MrCrayfish's Device Mod)
block-armor|方块盔甲*
@@ -1390,9 +1390,9 @@ jeid
gases-framework
ranged-pumps@|范围泵*
bagginses|背包*
-ic2-classic|工业时代2经典版 (IC2 Classic)
-opencubicchunks
-cubicworldgen
+ic2-classic@ic2classic|工业时代2经典版 (IC2 Classic)
+opencubicchunks@cubicchunks
+cubicworldgen@cwg
rally-health
open-terrain-generator|开放式地形生成器*
@@ -1403,7 +1403,7 @@ furnace-overhaul|熔炉改革*
architecturecraft-tridev@architecture-craft|建筑师工艺重置版 (ArchitectureCraft - TridentMC Version)
ore-farm|矿石农场*
-sound-physics-fabric|物理声效 (Sound Physics)
+
plethora-peripherals@
simple-flight|简单的飞行*
factory-tech|工厂科技 (FactoryTech)
@@ -1415,12 +1415,12 @@ crissaegrim
traverse-reforged@traverse-legacy|遍历 (Traverse)¨traverse@|遍历*
winter-wonder-land|霏雪寄语之地*
-road-stuff@|道路2 (Road Stuff2)
+road-stuff@|道路*
ae2wtlib
real-filing-cabinet|仿真档案柜*
hycrafthds-wtf-ic2-addon|WTF 工业拓展 (WTF Ic2 Addon)
u-team-core@
-ftb-quests|FTB任务 (FTB Quests)¨ftb-quests-fabric|FTB任务 (FTB Quests)
+ftb-quests-forge|FTB任务 (FTB Quests)¨ftb-quests-fabric|FTB任务 (FTB Quests)
texfix@|材质修复 (TexFix)
mapmakers-gadgets|地图作者小帮手 (Mapmaker's Gadgets)
travellers-gear|旅者之器 (Traveller's Gear)
@@ -1436,7 +1436,7 @@ new-tardis-mod
advanced-chimneys|高级烟囱*
regeneration|神秘博士 - 再生 (Doctor Who - Regeneration)
-more-planets-gc-addon
+stevekungs-lib
make-zoom-zoom|更快的加载 (MakeZoomZoom)
minecraft-virtual-machines|Minecraft虚拟机*
cavern2|洞穴 II (Cavern II)
@@ -1449,10 +1449,10 @@ cxlibrary
integrated-tunnels@|集成管道*
unmending|经验不修补*
-the-creeping-nether|蔓延的下界*
+the-creeping-nether|蔓延的下界* - 已失效¨@creeping-nether|蔓延的下界 (The Creeping Nether)
random-psideas|Psi随想 (Random PSIdeas)
expetrum|真实世界 (ExPetrum)
-equivalent-energistics|等价能源学*¨equivalent-energistics-patched|等价能源学 (Equivalent Energistics) - patch版
+equivalent-energistics|等价能源学 (Equivalent Energistics / Equivalent Energistics (LTS))¨equivalent-energistics-patched|等价能源学 (Equivalent Energistics / Equivalent Energistics (LTS)) - (LTS)patch 版
thedragonlib@
repose|自动上坡*
botanic-additions|植物学拓展*
@@ -1488,7 +1488,7 @@ unbreaken|工具保护 (UnbreakEn)
bitcoin|比特币*
hungering-darkness
roots-classic|根源魔法经典版*
-embers-rekindled|余烬复刻版*
+embers-rekindled@embers|余烬复刻版*
vending-machines-revamped|自动贩卖机 (VendingMachinesRevamped)
thaumic-alchemy|神秘炼金学*
@@ -1561,7 +1561,7 @@ ptrlib
creeper-awareness|爬行者意识*
what-fluid-fix
blue-skies@|蔚蓝浩空*
-project-vibrant-journeys|活力之旅 (Project: Vibrant Journeys)
+project-vibrant-journeys@|活力之旅 (Project: Vibrant Journeys)
bed-bugs|修复卡床*
biometweaker|生物群系修改器 (BiomeTweaker)
actuallycomputers|实用电脑 (ActuallyComputers)
@@ -1571,7 +1571,7 @@ exotic-birds|珍奇鸟类*
charset
biometweakercore|生物群系修改器扩展 (BiomeTweakerCore)
trashcans-reborn
-compactdrawers
+CompactDrawers
blockdispenser|发射器放置方块 (BlockDispenser)
redstone-paste|爬墙红石/粘性红石*
dynamic-trees-traverse-compat|动态的树:遍历附属 (Dynamic Trees - Traverse Compat)
@@ -1692,13 +1692,13 @@ custom-selection-box-port|自定义选择框 (Custom Selection Box)¨customselec
librikka-api
not-enough-wands@|更多魔杖*
deep-mob-learning-blood-magic-addon|深度怪物知识-血魔法插件 (Deep Mob Learning - Blood Magic Addon)
-informed-load-fabric|更多加载信息 (Informed Load)
+informed-load|更多加载信息*
huajiage-infinite-galaxy|滑稽纪元II:无尽星河 (Huaji Age II:Infinite Galaxy)
-cqrepoured|寻找巧克力重铸版 (Chocolate Quest Repoured)
+cqrepoured@chocolate-quest-repoured|寻找巧克力重铸版 (Chocolate Quest Repoured)
fabricproxy@|Fabric 群组支持 (FabricProxy)
ex-sartagine|锅具*
-xaeros-minimap@|Xaero的小地图 (Xaero's Minimap) - 完整版¨xaeros-minimap-fair-play-edition@xaeros-minimap-fair|Xaero的小地图 (Xaero's Minimap) - 公平竞争版
+xaeros-minimap|Xaero的小地图 (Xaero's Minimap) - 默认版(功能完整)¨xaeros-minimap-fair-play-edition|Xaero的小地图 (Xaero's Minimap) - 公平竞争版(功能部分缺失)¨@xaeros-minimap|Xaero的小地图 (Xaero's Minimap) - 完整版¨@xaeros-minimap-fair|Xaero的小地图 (Xaero's Minimap) - 公平竞争版
everlastingabilities-potioncore|永恒能力-药水核心 (EverlastingAbilities-PotionCore)
optifabric
the-emerald-haven|翡翠天堂*
@@ -1712,7 +1712,7 @@ climatic-biomes
howling-moon|狼人*
hostile-worlds-invasions|敌对世界-入侵 (Hostile Worlds - Invasions)
simple-storage-network|简单存储*
-
+2973537|虚无世界1 (Eternal Isles)
scaling-health
minecoprocessors|微处理器*
@@ -1759,7 +1759,7 @@ toast-control|消息框控制*
cocoa-magic|可可工艺*
betterportals|更好的传送门 (Better Portals)
-classic-combat|经典战斗*
+classic-combat@|经典战斗*
less-lag|较少的滞后*
goodbye-grass|没有草丛 (Grassta la Vista)
@@ -1781,7 +1781,7 @@ extra-foam-for-liteloader@|Liteloader 修复 (Extra Foam For LiteLoader)
embersified|余烬能量转换*
thaumic-grid|神秘网络*
-back-tools|工具后置/后背工具展示*
+back-tools|工具后置 / 后背工具展示*
mapwriter-2|地图作者2*
dartcraft-reloaded|达特工艺重置版*
@@ -1907,7 +1907,7 @@ goprone|匍匐前进 (GoProne)¨goprone-fabric|匍匐前进 (GoProne)
ceiling-torch@|倒置火把*
air-hop@|多段跳*¨air-hop-fabric|多段跳 (Air Hop)
better-combat-mod|更好的战斗*
-ftb-money|FTB交易 (FTB Money)
+ftb-money|FTB 交易 (FTB Money)
restricted-portals|传送门限制*
vampirelib@
ingameconfigmanager|游戏内配置器 (InGameConfigManager)
@@ -1959,7 +1959,7 @@ iron-chest-minecarts|更多箱子矿车*
nether-chest|下界箱子*
mob-kill-messages|击杀信息通知*
-betterdispenser|能喂食的发射器 (Better Dispenser)
+betterdispenser|能喂食的发射器 (BetterDispenser)
shulkerboxdisplay|潜影盒显示器 (ShulkerBoxDisplay)
mycommands|我的命令 (MyCommands)
@@ -2022,7 +2022,7 @@ create@|机械动力*¨create-fabric@|机械动力 (Create)
energeticsheep|脉冲羊 (EnergeticSheep)
-ensorcellation|万象*
+ensorcellation@|万象*
inventory-sorter|物品分拣*
speedster-heroes|闪电侠*
@@ -2037,7 +2037,7 @@ modular-magic|模块化魔法*
magic-kingdoms-mod|魔法王国 (Magic Kingdoms Mod/Schools of Magic Mod)
zollern-galaxy|卓伦星系*
-macaws-bridges|Macaw的桥梁 (Macaw's Bridges)
+macaws-bridges@|Macaw的桥梁 (Macaw's Bridges)
trample-stopper|防止踩踏*¨trample-stopper-fabric|防止踩踏 (Trample Stopper)
macaws-roofs@|Macaw的屋顶 (Macaw's Roofs)
@@ -2085,12 +2085,12 @@ zetta-industries|泽它重工*
kekztech
by-the-gods|神迹*
bee-angry-est|愤怒的蜜蜂 (Bee Angry-est)
-titanium|钛*
+titanium@|钛*
chemlib¨chemlib-fabric
carrots-lib|胡萝卜库 (Carrots Library)
no-hostiles-around-campfire@|围火无怪*¨no-hostiles-around-campfire-fabric|围火无怪 (No Hostiles Around Campfire)
healing-campfire@|治愈营火*¨healing-campfire-fabric|治愈营火 (Healing Campfire)
-231455-minetunes
+minetunes
jetif|物品丢入流体 (Just Enough Throwing In Fluids)
rftools-base@|RF工具:基础 (RFTools Base)
flopper|流体漏斗*
@@ -2124,7 +2124,7 @@ tektopia|桃花源记/特克托皮亚 (TekTopia)
fabric-language-kotlin@
-ftb-backups|FTB备份 (FTB Backups)
+ftb-backups-forge|FTB备份 (FTB Backups)
cyclopstek|独眼巨人 (CyclopsTek)
champions@|冠军/强敌*
@@ -2135,7 +2135,7 @@ arcanum|奥秘*
random-enchants|随机附魔*
much-more-spiders-v2|更多种类的蜘蛛 (Much More Spiders)
compressed-items
-no-tree-punching|无树可撸*
+no-tree-punching@|无树可撸*
harvest-festival-legacy|丰收物语移植版*
friendlymobs|友好的怪物 (FriendlyMobs)
simplecore-api@|简单前置 (SimpleCore API)
@@ -2154,7 +2154,7 @@ usefultnt|更有用的TNT (UsefulTNT)
crow-flight|乌鸦坐飞机*
findme@|东寻西觅 (FindMe)
-minecraft-transit-railway|我的世界铁路* - 较为落后¨@minecraft-transit-railway|我的世界铁路 (Minecraft Transit Railway)
+minecraft-transit-railway|我的世界铁路* - 现已更新4.0.0 Beta9及以上¨@minecraft-transit-railway|我的世界铁路 (Minecraft Transit Railway)
random-loot-mod|随机战利品*
tinkers-aether|天境工匠*
@@ -2191,7 +2191,7 @@ forceasciifont|强制ASCII字体 (ForceASCIIFont)
skewers|烤串*
get-in-the-bucket-mod|进入水桶*¨get-in-the-bucket-mod-fabric|进入水桶 (Get In The Bucket Mod)
fertilization|施肥*¨fertilization-fabric|施肥 (Fertilization)
-additional-banners|更多旗帜*
+additional-banners@|更多旗帜*
dropt
the-elven-forest|精灵森林 (The Elven Forest)
the-beneath|深渊世界*
@@ -2283,7 +2283,7 @@ mahou-tsukai@|魔法使*
moar-tinkers
baubley-heart-canisters@|心之容器*
-jaopcasingularities|JAOPCA的奇点 (JAOPCA Singularities)
+jaopcasingularities@|JAOPCA奇点 (JAOPCA Singularities)
labymod
@@ -2302,7 +2302,7 @@ amun-ra
mysterious-mountain-lib|妖怪之山通用库 (MysteriousMountainLib)
loottweaker
-rediscovered|特性重现*
+rediscovered@|特性重现*
baubles-reborn|饰品栏重置版*
@@ -2320,7 +2320,7 @@ lightning-bolt-mod|雷电攻击*
spartan-and-fire|斯巴达之冰与火 (Spartan and Fire)
beacons-for-all@¨beacons-for-all-fabric
-
+mxts-tutu-mod|混沌凋灵 (Chaos Wither)
more-flowers|更多功能花*
buzzier-bees@
@@ -2346,7 +2346,7 @@ fishs-undead-rising|Fish的不死崛起/Fish的亡灵崛起 (Fish's Undead Risin
cloth-config@
just-map
too-realistic-inventory|真实背包 (Realistic Inventory)
-mob-farm|生物农场 (Mob farm)
+mob-farm|生物农场*
acceleratorcraft|加速器工艺 (AcceleratorCraft)
minecraft-spongebob-squarepants-mod-for-the-1-7-10
utility-worlds|实用世界*
@@ -2388,7 +2388,7 @@ tube-transport-system|管道传输系统*
transmutation-alchemy¨transmutation-alchemy-revival
extra-spells-electroblobs-wizardry|额外咒语 (Extra Spells)
ancient-spellcraft|古代咒法*
-@moving-elevators
+moving-elevators@
interstellar-exoplanets@|星际:系外行星 (Interstellar: Exoplanets)
infini-tic
extra-disks@|更多磁盘*
@@ -2434,10 +2434,10 @@ tooltweaks|禁用原版工具 (ToolTweaks)
atop|超多生物群系护甲 (Armor for Biomes O' Plenty)
tinkers-forging|工匠锻造 (Tinker's Forging)
gravity-gun|重力枪*
-mekanism-additions@|通用机械附加 (Mekanism Addition)
+mekanism-additions@|通用机械附加*
signpicture|告示牌图片 (Sign Picture)
vampirism-integrations@|吸血鬼联动*
-gottschcore
+gottschcore@
not-enough-crashes@notenoughcrashes|崩溃优化*¨not-enough-crashes-forge|崩溃优化 (Not Enough Crashes)
crash-to-main-menu|崩溃返回主菜单*
tinkers-jei
@@ -2445,7 +2445,7 @@ tinkers-jei
better-questing-quest-book|更好的任务-任务书 (Better Questing - Quest Book)
engineers-doors|工程师的门 (Engineer's Doors)
tps-generator|TPS发电机 (TPS Generator)
-better-drowning|更好的溺水*
+better-drowning@|更好的溺水*
bibliotheca
kubejs@¨kubejs-fabric
tickcentral
@@ -2526,7 +2526,7 @@ the-lord-of-the-rings-mod-renewed|魔戒:复兴 (The Lord of the Rings Mod: Re
modelloader
animal-crops|动物作物*
terrafirmacraftneiplugin
-furenikusroads|Fureniku的路 (Fureniku's Roads)
+furenikusroads@furenikus-roads|Fureniku的路 (Fureniku's Roads)
patchouli-quests|帕秋莉任务手册*
extraacademy|学园附属 (ExtraAcademy)
iitemrenderer-reborn
@@ -2562,7 +2562,7 @@ qmd|量子块动力学 (Quantum Minecraft Dynamics)
environmental-rads
fission-based-neutron-collector|裂变能中子素收集器*
trinity|三位一体*
-macaws-windows|Macaw 的窗户 (Macaw's Windows)
+macaws-windows@|Macaw 的窗户 (Macaw's Windows)
music-player|音乐播放器*
csokicraftutil
large-enchants
@@ -2570,8 +2570,8 @@ angrysun|炙热太阳 (AngrySun)
its-not-long-enough|与现实相同的昼夜交替 (It's Not Long Enough)
sunscreen-gel|防晒霜*
muya1-7-10|姆亚核心 (MuyaCore)
-macaws-furniture|Macaw的家具 (Macaw's Furniture)
-macaws-doors|Macaw的门 (Macaw's Doors)
+macaws-furniture@|Macaw的家具 (Macaw's Furniture)
+macaws-doors@|Macaw 的门 (Macaw's Doors)
blueprint@
geyser-mod
not-enough-pets@|更多宠物*
@@ -2593,7 +2593,7 @@ gulliver-reborn
rationcraft
default-options@¨default-options-fabric
-ender-mail|末影邮递*
+ender-mail@|末影邮递*
more-overlays
stefinus-3d-guns-mod|3D枪械 (New Stefinus Guns)¨stefinus-3d-guns-mod-s3dgm|3D枪械 (New Stefinus Guns)
@@ -2602,13 +2602,13 @@ lang_magic|言灵 (lang_magic)
noexpensive@|不昂贵 (NoExpensive)
rough-mobs-revamped|粗暴的怪物重置版*
fullbright|充满光明*
-backpacks|背包! (backpacks!)
+backpacks|背包! (Backpacks!)
living-enchantment|生命附魔*
karat-garden|克拉的花园*
bard-mania
-shulkerboxtooltip@|潜影盒工具提示 (Shulker Box Tooltip)
+shulkerboxtooltip@|潜影盒提示框 (Shulker Box Tooltip)
crafting-dead-official|行尸走肉 (Crafting Dead)¨crafting-dead-legacy|行尸走肉 (Crafting Dead)
lord-craft
@@ -2669,7 +2669,7 @@ more-discs-mod|更多唱片 (More discs mod)
edible-honeycombs
emerald-and-ruby
craftable-nametag|可合成的命名牌*
-dyeable-flower-pots|染色花盆*
+dyeable-flower-pots|染色花盆 (More Flower Pots / Dyeable Flower Pots)¨more-flower-pots|染色花盆 (More Flower Pots / Dyeable Flower Pots)
graffiti|涂鸦*
bunny-boots
backported-flora
@@ -2678,7 +2678,7 @@ wool-plates|羊毛压力板*
more-berry-uses|更多浆果用途*
elemental-blades|元素之刃 (Elemental Blades / Elementium)
beyond-earth@|飞越地球 (Beyond Earth/Space-BossTools)¨beyond-earth-fabric|飞越地球 (Beyond Earth/Space-BossTools)
-simple-planes|简单飞机*¨simple-planes-fabric|简单飞机 (Simple Planes)
+simple-planes@|简单飞机*¨simple-planes-fabric|简单飞机 (Simple Planes)
world-tooltips|掉落物信息显示*
extra-arcane-knowledge-electroblobs-wizardry|额外巫术知识 (Extra Arcane Knowledge)
bacteriums|细菌重置版 (Bacterium)¨bacterium|细菌重置版*
@@ -2734,7 +2734,7 @@ screenshot-to-clipboard@|截图到剪贴板 (Screenshot to Clipboard)¨screensho
hide-armor|隐藏盔甲*
hunterillager@hunters-return|邪猎者 (HunterIllager)
-pocket-apocalypse-mod|便携式天灾 (Pocket Apocalypse mod)
+pocket-apocalypse-mod|便携式天灾*
compass|指南针 (compass)
tinkers-evolution@|匠魂进化 (Tinkers' Evolution)
tinkers-exporter|匠魂数据导出器*
@@ -2751,7 +2751,7 @@ mobs-attempt-parkour@
identity|化身*¨identity-on-forge|化身 (Identity)
industrial-revolution@|工业革命*
art-of-alchemy|炼金艺术 (Art of Alchemy)
-lan-server-properties|自定义局域网联机*
+lan-server-properties@|自定义局域网联机*
immersive-intelligence@|沉浸智能*
analyzeio
@@ -2808,7 +2808,7 @@ tfctech-unofficial|群峦工业非官方版 (TFCTech Unofficial)
@ee1|等价交换 (Equivalent Exchange)
invmove@|边拿边走 (InvMove)¨invmove-fabric|边拿边走 (InvMove)¨invmove-forge|边拿边走 (InvMove)
autofish-for-forge|自动钓鱼Forge版 (AutoFish for Forge)
-industrial-upgrade|工业升级*
+industrial-upgrade@industrialupgrade|工业升级*
rpgstats@
datapackrecipemaker|数据包配方助手 (Datapack Recipe Maker)
never-needed-or-wanted
@@ -2820,10 +2820,10 @@ eldritch-mobs@
farmers-delight@|农夫乐事 (Farmer's Delight)¨farmers-delight-fabric@|农夫乐事 (Farmer's Delight)
artifacts@|奇异饰品*¨artifacts-fabric|奇异饰品 (Artifacts)
-dynamictreestfc|动态的树:群峦传说附属 (Dynamic Trees-TFC)
+dynamictreestfc@|动态的树:群峦传说附属 (Dynamic Trees TFC)
dynamic-trees-the-twilight-forest|动态的树:暮色森林附属 (Dynamic Trees-The Twilight Forest)
-drp-global-datapack@globalpacks|全局数据包 (Global Data & Resourcepacks)
+drp-global-datapack@globalpacks|全局数据包 (Global Packs/Global Data & Resourcepacks)
mob-farm-nerfer
blood-in-the-water
bio-technik
@@ -2835,7 +2835,7 @@ rustic-thaumaturgy|神秘乡村*
thallium|铊*
haema@
evil-ores
-unsuspicious-stew|炖菜解谜/炖菜效果显示*
+unsuspicious-stew@|炖菜解谜/炖菜效果显示*
tfc-seasonal
imblockerfabric@imblocker|输入法冲突修复 (IMBlockerFabric)
saomclib
@@ -2859,12 +2859,12 @@ project-red-integration|红石计划:集成 (Project Red - Integration)
project-red-world|红石计划:探索 (Project Red - World)
project-red-compat|红石计划:兼容性 (Project Red - Compat)
-canvas-renderer@canvas|Canvas渲染器*
+canvas-renderer@canvas|Canvas 渲染器*
coolfood|冷饮 (CoolFood)
infinitevillagertrading@|无限村民交易 (InfiniteVillagerTrading)
infinite-trading@|无限交易*¨infinite-trading-fabric|无限交易 (Infinite Trading)
-better-loading-screen|自定义加载画面 (Custom Loading Screen)
+better-loading-screen@custom-loading-screen|自定义加载画面 (Custom Loading Screen)
extended-lights-mod|灯具拓展 (Extended Lights)
more-swords-legacy@|更多剑:传承*
the-undergarden@|深暗之园*
@@ -2897,7 +2897,7 @@ scalable-cats-force@
otyacraft-engine
omega-craft-mod
custom-ports|自定义局域网端口 (Custom LAN Ports)
-decorative-blocks|装饰方块*
+decorative-blocks@|装饰方块*
music-layer
colored-lux|彩色照明*
corsair-mccue@
@@ -2912,10 +2912,10 @@ base-metals|基础金属*
mmdlib
just-poop-mod|屎! (Poop Mod!)
costumes|服装*
-eyemod-a-phone-mod
+eyemod-phone
upgrade-aquatic@|碧海新生*
tfc-ambiental
-macaws-trapdoors|Macaw的活板门 (Macaw's Trapdoors)
+macaws-trapdoors@|Macaw的活板门 (Macaw's Trapdoors)
zenutil
better-beacon-effect|更好的信标效果*
@@ -2930,7 +2930,7 @@ heaven-earth-ring|乾坤戒指*
pams-harvestcraft-2-food-extended@|潘马斯农场2 - 食物拓展 (Pam's HarvestCraft 2 - Food Extended)
pams-harvestcraft-2-trees@|潘马斯农场2 - 果树 (Pam's HarvestCraft 2 - Trees)
-minecart-direction-follow¨better-minecart-rotation-forge
+better-minecart-rotation-forge¨minecart-direction-follow
modern-metals|现代金属*
blockrenderer¨block-renderer
blockus@
@@ -2951,7 +2951,7 @@ better-ping-display@|更好的延迟显示*¨better-ping-display-fabric@|更好
turned-the-changed-mod
survival-tweaks|生存微调*
melon-slabs@|西瓜台阶*
-lambdynamiclights@|Lambd 的动态光源 (LambDynamicLights)
+lambdynamiclights@|Lambd的动态光源 (LambDynamicLights)
realistic-torches@|真实火把*
durability-viewer|耐久显示器*
@@ -2967,14 +2967,14 @@ time-to-live
dual-hotbar-1-12-2|双重快捷栏1.12.2 (Dual Hotbars 1.12.2)
cha-s|可合成的马铠和鞍 (Craftable Horse Armour & Saddle)
dual-hotbar|双重快捷栏 (Dual Hotbars)
-travellers-boots
+travellers-boots@
cofh-vanilla-satchels
betteranimalsplus|更多动物 (Better Animals Plus)
packagedauto@|封包合成 (PackagedAuto)
packagedexcrafting@|封包合成拓展 (PackagedExCrafting)
-compactstorage|紧凑存储 (compactstorage)¨@compact_storage|紧凑存储 (compactstorage) - 已失效
+compactstorage@compact-storage|紧凑存储 (compactstorage)
useful-backpacks@|实用背包*
conquest-reforged@|征服者*
steelseries-gamesense
@@ -3031,7 +3031,7 @@ treasure2|寻宝记2 (Treasure2!)
@autoreconnector-fabric|自动断线重连 (AutoReconnector-Fabric)
structure-gel-api@
-the-conjurer|魔术师*
+the-conjurer@|魔术师*
ganys-nether|Gany的下界 (Gany's Nether)
ganys-end|Gany的末地 (Gany's End)
epic-fight-mod@epic-fight|史诗战斗 (Epic Fight)
@@ -3071,7 +3071,7 @@ refined-pipes|精致管道*
singularities|奇点*
better-spawner-control@|更好的刷怪笼控制*¨better-spawner-control-fabric|更好的刷怪笼控制 (Better Spawner Control)
crying-ghasts@|哭泣的恶魂*¨crying-ghasts-fabric|哭泣的恶魂 (Crying Ghasts)
-dynamic-fps@|动态FPS (Dynamic FPS)
+dynamic-fps@|动态 FPS (Dynamic FPS)
compact-ores|致密矿石*
thaumic-additions@|神秘领域 (Thaumcraft Addition)
fabric-disable-custom-worlds-advice@dcwa|禁用自定义世界警告 (Disable Custom Worlds Advice)
@@ -3091,7 +3091,7 @@ better-questing-triggerer
Broken-Wings
compacter|压缩者*
-simple-cc|简单清除聊天 (Simple Clear Chat)
+
not-enough-potions
framed-compacting-drawers|镶框压缩抽屉*
discordsuite
@@ -3114,10 +3114,10 @@ feed-a-friend
the-legend-of-herobrine@
-extra-origins|起源扩展*
+extra-origins@|起源扩展*
caves-and-cliffs
miner-golems|矿工傀儡*
-
+bakadanmaku@|直播弹幕模组 (BakaDanmaku)
online-card|网络卡片*
felling|连锁砍树*
extra-player-render|额外玩家渲染 (Extra Player Renderer)
@@ -3133,7 +3133,7 @@ toomanydanyores
glass-pipe|玻璃管道*
origins-classes@|起源:职业 (Origins: Classes)
plonk@|世界内物品放置*
-baritone-1-18-2
+baritone-simply¨baritone-1-18-2|Baritone - 已失效
potionsmaster|魔药大师 (Potions Master)
tinkered-hegemony@|匠器主导*
better-impaling|更好的穿刺附魔*
@@ -3159,7 +3159,7 @@ chlorine|氯*
bedrock-miner@|基岩矿工*
reasonable-realism|合理的现实主义*
-passable-foliage|叶间穿行 🌳 (Passable Foliage 🌳)¨passable-foliage-fabric|叶间穿行 🌳 (Passable Foliage 🌳)
+passable-foliage@|叶间穿行 🌳 (Passable Foliage 🌳)¨passable-foliage-fabric|叶间穿行 🌳 (Passable Foliage 🌳)
betterend@|更好的末地 (BetterEnd)
armor-chroma|盔甲颜色显示*
interactions|自定义交互事件*
@@ -3210,7 +3210,7 @@ monospace-font|等宽字体 (Monospace font)
mana-liquidizer|魔力液化器*
neapolitan@|那不勒斯风味*
-clear-despawn-fabric
+clear-despawn-fabric@cleardespawn
tinkers-modifier-modifier|匠魂强化修改 (Tinkers' Modifier Modifier)
randomtweaks|随意微调 (RandomTweaks)
zettai-magic
@@ -3234,7 +3234,7 @@ memory-cleaner-mod|内存自动清理*
ore-flowers|矿物花卉*
aqua-creepers|水下苦力怕 (Aqua Creepers!)
extrastorage|更多存储 (ExtraStorage)
-thermal-locomotion|热力运输*
+thermal-locomotion@|热力运输*
external-tweaker
morebiomesxl
notes@|笔记*
@@ -3243,7 +3243,7 @@ vm-computers|虚拟电脑 (VM Computers)
inventory-free|无物品栏 (Inventory-Free)
randompatches-integration
-bubble-column-elevator-backport|1.13水域机制 (1.13 Water Mechanics)
+bubble-column-elevator-backport|1.13水域机制 (1.13 Bubble Columns for 1.12)
basic-nether-ores@|基础下界矿石*
nuclearcraft-reactor-builder|核电工艺反应堆建造机 (NuclearCraft Reactor Builder)
pams-harvestcraft-2-food-core@|潘马斯农场2 - 食物核心 (Pam's HarvestCraft 2 - Food Core)
@@ -3268,7 +3268,7 @@ cardboard@
enchanting-with-thaumcraft|神秘附魔学*
crimson-revelations|血腥启示 (Crimson Revelation)
environmental-core@
-bigger-crafting-tables
+bigger-crafting-tables@biggercraftingtables
covers-everywhere|万用覆盖板*
mo-villages|更多村庄 (Mo' Villages)
inventoryneko
@@ -3282,7 +3282,7 @@ twitch-chat-integration|Twitch信息显示*
avaritia-lite|无尽贪婪精简版*
packmenu
-console-hud|原主机版界面特性 (Console Experience)
+console-experience|原主机版界面特性*
mite-reborn|MITE:重生 (MITE:Reborn)
@@ -3369,7 +3369,7 @@ variant16x
endremastered@endrem|末地:创世 (End Remastered)¨endremastered-fabric|末地:创世 (End Remastered)
unique-crops|独特作物*
-yungs-api@¨yungs-api-fabric
+yungs-api@¨yungs-api-fabric¨yungs-api-neoforge
dawn
assembly-line-machines|装配线*
netherending-ores
@@ -3490,7 +3490,7 @@ clear-despawn
unionlib@
botany-trees@|植树盆栽*
better-third-person@
-survive|生存要素*
+survive@|生存要素*
wallpapercraft-a-fresh-roll-stairs|墙纸创造:楼梯 (Wallpapercraft - A Fresh Roll (Stairs!))
wallpapercraft-a-fresh-roll-slabs|墙纸创造:台阶 (Wallpapercraft - A Fresh Roll (Slabs!))
wallpapercraft-a-fresh-roll-walls|墙纸创造:围墙 (Wallpapercraft - A Fresh Roll (Walls!))
@@ -3501,14 +3501,14 @@ callable-horses|召之马来*
ultimate-solar-panels
slight-gui-modifications@|界面微调 ('Slight' Gui Modifications)
-elemental-craft|元素工艺*
+elemental-craft@|元素工艺*
just-another-rotten-flesh-to-leather-mod@jrftl|腐肉换皮革 (Just Another Rotten Flesh to Leather Mod)
spatial-harvesters-forge@spatial-harvesters-fabric¨spatial-harvesters-fabric
emotecraft@|表情工艺*¨emotecraft-forge|表情工艺 (Emotecraft)
pattysmorestuff@
hats|帽子*
smoother-bedrock|平滑基岩层*
-c2me-fabric@|C^2M 引擎 (C^2M-Engine/Concurrent Chunk Management Engine)
+c2me-fabric@|C^2M 引擎 (C^2M-Engine/Concurrent Chunk Management Engine)¨@c2me-neoforge|C^2M 引擎 (C^2M-Engine/Concurrent Chunk Management Engine)
datapack-anvil
nuclear-science@|核科学*
glaretorch|强光火把 (Glare Torch)
@@ -3542,7 +3542,7 @@ dynamic-sound-filters@|动态声音滤波器*
binniepatcher
subterranean-wilderness
-construction-wand|建筑手杖*
+construction-wand@|建筑手杖*
squeedometer
forgery@fabrication¨fabrication
@@ -3578,9 +3578,9 @@ simple-drawers|简单的抽屉*
charmonium¨charmonium-reforged@charmonium
xnicex@
krate
-chickensshed|鸡棚重置版 (ChickensShed)
+chickensshed@|鸡棚重置版 (ChickensShed)
chickenshed
-expanded-storage@|扩展存储*¨expanded-storage-fabric|扩展存储 (Expanded Storage)¨expanded-storage-forge|扩展存储 (Expanded Storage)
+expanded-storage|扩展存储* - 已失效¨expanded-storage-fabric@expanded-storage|扩展存储 (Expanded Storage)¨expanded-storage-forge|扩展存储 (Expanded Storage)
project-mmo@
abnormals-delight@
jojos-bizarre-survival
@@ -3598,7 +3598,7 @@ simply-tea|简单的茶! (Simply Tea!)
buttermilk
diet@|均衡饮食*
brazilian-fields|巴西风情*
-ae2-extras|AE2附加 (AE2 Extras)
+ae2-extras|AE2 附加 (AE2 Extras)
animalium|动物*
mob-origins@moborigins|生物起源*
better-default-biomes|更好的默认生物群系*
@@ -3612,7 +3612,7 @@ mc-modsurement@allurement!
dwarf-miner-totem|矮人矿工*
yungs-better-portals|YUNG 的传送门机制重构 (YUNG's Better Portals)
german-road-signs-mod|德国交通标志*
-iguana-in-a-blanket|真实调整 (Iguana in a Blanket)
+iguana-in-a-blanket@iguana|真实调整 (Iguana in a Blanket)
anthill-inside|蚂蚁物流*
nei-integration
uncrafting-grinder|分解磨床*
@@ -3642,7 +3642,7 @@ abundance@
just-enough-calculation
mythicbotany@|神话植物学 (MythicBotany)
-improved-mobs|怪物增强*¨improved-mobs-fabric|怪物增强 (Improved Mobs)
+improved-mobs@|怪物增强*¨improved-mobs-fabric|怪物增强 (Improved Mobs)
limitless|无上限附魔*
vpn-blocker|禁用VPN (VPN Blocker)
premium-wood@
@@ -3659,11 +3659,11 @@ farmers-delight-integration
magical-psi-redux
anvil-fix|铁砧修复*
no-increasing-repair-cost
-wool-temples|羊毛神殿*
+wool-temples@|羊毛神殿*
exp-bottling|经验装瓶 (EXP Bottling)
buildersaddition@
gt-remastered|格雷科技重制版 (GT: Remastered)
-tips|提示*
+tips@|提示*
Interactio
advancement-book|进度之书*
drawers-tooltip|抽屉提示*
@@ -3692,7 +3692,7 @@ tipline
ultimate-plane-mod@|终极飞机*
simple-voice-chat@|简单的语音聊天*
not-enough-milk
-ewewukeks-musket-mod|ewewukek 的火枪模组 (ewewukek's Musket mod)
+ewewukeks-musket-mod@musket-mod|ewewukek 的火枪模组 (ewewukek's Musket mod)
irisshaders@iris
@dimthread|世界多线程化 (DimensionalThreading)
@@ -3702,7 +3702,7 @@ sodium-extra@|钠 · 扩展*
grind-enchantments@|砂轮增强*
voice-chat|语音聊天*
-xnet-gases
+xnet-gases@
filing-cabinets-refurbished
dropz@
@@ -3736,13 +3736,13 @@ engineers-workshop-reborn
crimson-xp-charm|经验护符 (Crimson XP)
locatebiome
enderman-evolution
-sophisticated-backpacks|精妙背包*
+sophisticated-backpacks@|精妙背包*
age-of-exile
whoosh
gregtech-intergalactical|泛银河系格雷科技 (GregTech Intergalactical)
catalogue|模组目录*¨catalogue-fabric|模组目录 (Catalogue)
libblockattributes
-balance-of-exile
+
dungeons-of-exile
lots-more-food-forge@lots-more-food¨lots-more-food-fabric
@@ -3762,7 +3762,7 @@ astromine-core|天体矿工:核心 (Astromine: Core)
dungeondq
dynamic-trees-integrated-dynamics|动态的树:动态联合/集成动力附属 (Dynamic Trees - Integrated Dynamics)
skyblock-builder@|空岛建立者*
-antiportals|反传送门 (AntiPortals)
+antiportals@|反传送门 (AntiPortals)
trash-cans@|垃圾桶*
mattdahepic-core
mob-farm-helpers-forge|刷怪塔小帮手 (Mob Farm Helpers)¨mob-farm-helpers-fabric|刷怪塔小帮手 (Mob Farm Helpers)
@@ -3821,7 +3821,7 @@ cat-jammies
rhino@|犀牛*
fastsuite|配方性能优化 (FastSuite)
twerkitmeal|跳舞生长一切 (Twerk To Grow All The Things)
-thermal-integration|热力集成*
+thermal-integration@|热力集成*
piggybanks|存钱罐*
in-control@
mystical-agriculture-refabricated|神秘农业:Fabric版 (Mystical Agriculture: Refabricated)
@@ -3878,16 +3878,16 @@ winged@
@slideshow-fabric|幻灯片 (Slide Show)
mob-stages|生物阶段*
refooled@
-
+@better-nether-beta|更好的下界:Beta版 (BetterNether Beta)
fabric-seasons@
dehydration@|脱水*
easy-villagers@|村民物品化/简单村民*
largebar
-tis-3d
+tis-3d@tis3d
entity-nan-health-fix|假死修复 (Entity NaN Health Fix)
ferritecore@ferrite-core|铁氧体磁芯 (FerriteCore)¨ferritecore-fabric|铁氧体磁芯 (FerriteCore)
thaumcraft-research-patcher
-masterful-machinery
+masterful-machinery@
minecraft-earth-mod|Derec 的地球生物 (Derec's Earth Mobs)
scotts-essential
drawerfps|储物抽屉渲染配置 (DrawerFPS)
@@ -3920,13 +3920,13 @@ the-elemelon-mod|元素西瓜*
dcsp|钻石压缩 (Diamonds Compressor SP)
corpse-complex@
modularvoicechat|模块化语音聊天 (ModularVoiceChat)
-curious-elytra@elytra-slot|鞘翅插槽 (Curious Elytra / Elytra Trinket / Elytra Slot)¨curious-elytra-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)
incubation@
-
+abyssmana|奈落魔法学 (AbyssMana)
create-goggles@
@@ -3954,7 +3954,7 @@ xenoclus-one
satako-library¨satako-fabric
unique-enchantments-utils|独特的附魔:效用扩展 (Unique Enchantments - Utils)
forge-ae2-additional-opportunity@ae2ao¨ae2ao¨legacy-ae2-additional-opportunity
-iconexporter
+iconexporter@icon-exporter
curious-shulker-boxes@shulker-box-slot|潜影盒插槽 (Shulker Box Slot)¨curious-shulker-boxes-fabric|潜影盒插槽 (Shulker Box Slot)
xenoclus-2
darker-depths|深岩之下*
@@ -3963,7 +3963,7 @@ solar-cooker@|太阳灶*
create-addon
boat-contraptions
-ender-rift
+ender-rift|末影裂缝*
splashanimation
continue-button@|继续按钮*
@@ -4000,7 +4000,7 @@ modularwarfare|模块化战争/模块化武装 (ModularWarfare)
mputils-basic-tools
the-agartha-mod
falling-with-style|花式降落*
-valhelsia-core
+valhelsia-core@
alwayseat|吃不停 (AlwaysEat)
@@ -4011,7 +4011,7 @@ mixin-booter@mixinbooter
jrds-moneymod
skeletdimension|骷髅维度 (SkeletDimension)
-sushigocrafting|巧制寿司 (Sushi Go Crafting)
+sushigocrafting@|巧制寿司 (Sushi Go Crafting)
shrines-structures@shrines
valhelsia-tweaks
lazierae2@|懒人AE2 1.16+ (Lazier AE2)
@@ -4031,7 +4031,7 @@ sonicraft@|索尼克工艺 (SoniCraft)
islands
origins-forge@|起源非官方Forge版 (Origins (Forge))
magic-feather
-alet
+alet@a-little-extra-tiles
netheriteplus
the-survivalists-mod
campfire-overhaul
@@ -4041,7 +4041,7 @@ rats-mischief@
dream-rng
wandering-trader-improvements
-wasteland-reborn|不毛之地:重生 (Wastland Reborn)
+wasteland-reborn|不毛之地:重生*
skds-core
endertech|末影科技 (Ender Tech)
enderpig-mod|末影猪*
@@ -4052,18 +4052,18 @@ se-sign-shop|简单经济:牌子商店 (SE: Sign Shop)
fancymenu@¨fancymenu-fabric
halogen|卤素*
-zetamod@zetaforged|泽塔 (ZetaForged/Zeta's Mod)
+zetamod@zetaforged|泽塔 (ZetaForged / Zeta's Mod)
wholetreeaxe
fabrishot@|高清截图Fabric*
-materialisation|物质化*
+materialisation|物质化*¨materialisation3|物质化 (Materialisation)
recipebuffers|配方缓冲 (RecipeBuffers)
portal-gun-fabric|传送枪 Fabric*
incendium@
@new-frontier-craft
-cooperative-advancements|合作进度*¨cooperative-advancements-fabric|合作进度 (Cooperative Advancements)
+cooperative-advancements@|合作进度*¨cooperative-advancements-fabric|合作进度 (Cooperative Advancements)
lightstones
openbackup
fat-experience-orbs@|肥胖经验球 ([Fabric] Fat Experience Orbs)
@@ -4091,7 +4091,7 @@ afkpeace@
smooth-chunks|平滑区块加载*¨@smooth-chunks|平滑区块加载 (Smooth Chunks) - 已失效
-saturationoverflow|饱和度溢出 (Saturation Over flow)
+saturationoverflow|饱和度溢出 (SaturationOverflow)
phantom-blood@phantomblood|幻影之血*
@@ -4146,11 +4146,11 @@ water-physics-overhaul
netherless
auto-ore-dictionary-converter@aodc
elytraboosters@elytra-boosters
-cobblegenrandomizer
+cobblegenrandomizer@
unified-resources
wooden-buckets|木桶*
no-nether-portals|没有下界传送门*
-beehivetooltips|蜂巢工具提示 (BeehiveTooltips)
+beehivetooltips|蜂巢提示框 (BeehiveTooltips)
buddycards@|巴迪卡牌*
extended-caves@expanded-caves¨expanded-caves
@@ -4172,7 +4172,7 @@ tool-progression|工具进阶*
a-paimon|派蒙*
-
+@bh-creative
music-maker-mod
penguin-lib
flywheel@|飞轮*
@@ -4200,11 +4200,11 @@ PickupWidely
lilliputian
sampler
custom-splash-screen@|自定义加载界面*
-masa-gadget@
+masa-gadget@¨@masa-gadget-dev|Masa Gadget - 测试版
meet-your-fight@|迎战*
hot-or-not-plus
macaws-furniture-tfc
-tfc-florae
+tfc-florae@
tfc-elementia
infinity-water-bucket|无限水桶*
tfc-caffeine-addon
@@ -4218,7 +4218,7 @@ tfc-medicinal
water-flasks
structure-world
-macaws-lights-and-lamps|Macaw的灯 (Macaw's Lights and Lamps)
+macaws-lights-and-lamps@|Macaw的灯 (Macaw's Lights and Lamps)
terrarian-slimes@
create-gears|机械动力:齿轮附加*
simple-big-backpack
@@ -4257,18 +4257,18 @@ untitled-duck-mod-forge@untitled-duck-mod|无题鸭 (Untitled Duck)¨untitled-du
v-tweaks@
fairenchanting|公平附魔 (FairEnchanting)
bedspreads@¨bedspreads-fabric
-piglib
+piglib@
nether-portal-spread@¨nether-portal-spread-fabric
-x-hp
+x-hp@
mr-pineapples-toy-guns
magickcore@|魔法核心 (MagickCore)
-feywild
+feywild|仙灵荒野*
The-final-sword|最终之剑重置版 (The final sword)
gog-skybox
-lemon-lib
+lemon-lib@lemonlib
custom-entity-models-kaimyentity-reborn|自定义玩家和实体模型 / KAI我的实体:重生 (Custom Player & Entity Models/KAIMyEntity-Reborn)
miniutilities|迷你实用设备 (Mini Utilities)
stumble-upon-campsites|邂逅:营地 (Stumble Upon: Campsites)
@@ -4288,7 +4288,7 @@ reroll|刷新附魔*
tax-free-levels@|免税附魔*
portable-dimensions|便携式传送门*
stone-age-by-yanny
-terra-world-generator@terra
+terra-world-generator|Terra - 已经失效¨@terra
omnis|灾厄村民拓展*
omnis-backpacks
@@ -4296,7 +4296,7 @@ superhotmc
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|沉浸烹饪*
+immersive-cooking@|沉浸烹饪*
structured-crafting
random-shulker-colours@|随机潜影贝颜色*¨random-shulker-colours-fabric|随机潜影贝颜色 (Random Shulker Colours)
create-automated
@@ -4329,8 +4329,8 @@ inventory-tabs
bottle-your-xp@|装瓶你的经验*¨bottle-your-xp-fabric|装瓶你的经验 (Bottle Your Xp)
gravestone-mod-extended|墓碑坟墓 - 拓展 (Gravestone mod - Extended)
better-enchanted-books@|更好的附魔书*
-easy-steel-forge
-fabric-waystones@fwaystones|Fabric版传送石碑*
+easy-steel-forge|简单的钢 (Easy Steel & More)
+fabric-waystones@fwaystones|Fabric 版传送石碑 (Wraith Waystones / Fabric Waystones)
dark-enchanting@|暗黑附魔*
@@ -4346,7 +4346,7 @@ blockshifter¨blockshifter-forge
vanilla-builders-extension
big-beacons|大信标*
-wooden-hopper|木漏斗*
+wooden-hopper@|木漏斗*
ages-api
fancyhud¨spiffy-hud-fabric
@@ -4378,12 +4378,12 @@ farsight-spyglasses
not-enough-animations@|更多动画*
better-beacon|更好的信标与潮涌核心 (Better Beacon / Conduit)
-dungeon-now-loading
+dungeon-now-loading@|地牢进行时*
simple-emerald-tools-fabric¨easy-emerald-tools
cold-sweat@|冷汗*
witchery-rewitched|巫术:重生 (Witchery: Rewitched)
majou-densetsu-3d|魔城传说3D (Majou Denetsu 3D)
-icarus-wings|伊卡洛斯之翼*
+icarus-wings@|伊卡洛斯之翼*
keyboard-wizard|按键精灵*
piscary
@@ -4468,7 +4468,7 @@ plumed-belt
better-with-minecolonies
calemis-utilities|Calemi的实用设备 (Calemi's Utilities)
botanical-cowardice
-travelers-titles@¨travelers-titles-fabric¨travelers-titles-neoforge
+travelers-titles@|旅人标题 (Traveler's Titles)¨travelers-titles-fabric|旅人标题 (Traveler's Titles)¨travelers-titles-neoforge|旅人标题 (Traveler's Titles)
expand-chinese-sword|中式剑拓展 (Arsenal Integration)
littleframes@littlepictureframes|小画 (LittlePictureFrames)
@@ -4494,7 +4494,7 @@ essential-commands@
caves-and-cliffs-backport
that-future-mod-glow-squid-axolotl-moobloom-etc
diamond-buildcraft|钻石装饰*
-apocalypse-rebooted|启示录重制* - 已失效¨@apocalypse-rebooted|启示录重制 (Apocalypse Rebooted)
+@apocalypse-rebooted|启示录重制 (Apocalypse Rebooted)
mcwifipnp@|更高级联机设置 (LAN World Plug-n-Play)
requious-frakto
@@ -4548,7 +4548,7 @@ better-furnaces-reforged@|更好的熔炉重铸版 (BetterFurnaces Reforged)
schools-of-magic-second-semester|魔法学校:第二学期 (Schools of Magic: Second Semester)
dark-loading-screen@|深色加载界面*
-hals-exploration-mod
+hals-exploration-mod@hals%20exploration%20mod
farmers-delight-cookbook-addon|农夫乐事:烹饪书拓展 (Farmer's Delight Cookbook Addon)
heads-down-display@head-down-display
mrcrayfish-more-furniture-mod|MrCrayfish 的更多家具 (MrCrayfish's More Furniture Mod)
@@ -4587,11 +4587,11 @@ emerald-tools-armour
hidden-recipe-book@|隐藏配方书*
kibe@|羊肉面饼实用设备 (Kibe Utilities)
poisoncraft|毒药工艺 (PoisonCraft)
-detail-armor-bar@|细节盔甲*¨detail-armor-bar-forge|细节盔甲 (Detail Armor Bar)
+detail-armor-bar-forge@detail-armor-bar|细节盔甲 (Detail Armor Bar)¨detail-armor-bar|细节盔甲*
wings-horns-hooves-the-ultimate-unicorn-mod
project-rankine@rankine|兰金计划*
tinkers-mechworks@
-qdc-quantum-dis-assembly-craft|量子拆解工艺 (Quantum Dis-Assembly Craft)
+qdc-core-4-0@qdc-core-4.0|量子拆解工艺 (Quantum Dis-Assembly Craft)¨qdc-quantum-dis-assembly-craft|量子拆解工艺 (Quantum Dis-Assembly Craft) - 旧
william-wythers-overhauled-overworld@wwoo¨expanded-ecosphere@
infinite-music
cobbler|可靠的潜影贝工厂 (Shulker's Faithful Factories/Cobbler)
@@ -4602,7 +4602,7 @@ buzzy-drones
morevanillaarmor@|更多原版盔甲 (MoreVanillaArmor)
eternal-tales@|永恒传说*
-oh-the-biomes-youll-go-fixes|“你将去的生物群系”修复模组 (Oh The Biomes You'll Go Fixes)
+oh-the-biomes-youll-go-fixes|你将去的生物群系修复版 (Oh The Biomes You'll Go Fixes)
wild-world
miskatonic-mysteries¨miskatonic-mysteries-legacy
@@ -4649,7 +4649,7 @@ maidens-marvelous-materials
nekoration@|猫咪装饰*
tomb-many-graves-2
fins-and-tails|鳍和尾巴 (Fins and Tails)
-smarter-farmers-farmers-replant@|更聪明的农夫 (Smarter Farmers/Farmers Replant)
+smarter-farmers-farmers-replant@|更聪明的农夫 (Smarter Farmers / Farmers Replant)
peculiars|独特风味*
draggable-resource-packs@draggable-lists|可拖动列表/可拖动资源包 (Draggable Lists/Draggable Resource Packs)
fastchest|快速箱子渲染 (FastChest) - 已不再更新¨@fastchest|快速箱子渲染 (FastChest) - 在此处更新
@@ -4657,9 +4657,9 @@ yuushya-townscape@|方块小镇*
architects-palette-fabric@|建筑师的调色板 (Architect's Palette)¨architects-palette@architects-palette#all-versions|建筑师的调色板 (Architect's Palette)
whisperwoods|低语之森*
dimensional-dungeons@|维度地牢*
-bosses-of-mass-destruction@
+bosses-of-mass-destruction@|祸乱鬼魅 (Bosses of Mass Destruction)
neoelfeos-medieval-pub-decoration
-dustrial-decor
+dustrial-decor@dustrial-decor-forge
feders-scarecrows|Feder的稻草人 (Feder's Scarecrows)
zetter@zetter-painting-mod
mystical-pumpkins|神秘南瓜*
@@ -4668,7 +4668,7 @@ scarecrows@|稻草人*
seasonals@|季节风味*
mimi-mod@mimi
blocktuner@|音符盒调音助手 (BlockTuner)
-caged-mobs|笼中生物*
+caged-mobs@|笼中生物*
advanced-rifles
tome-of-blood|血之宝典 (Tome of Blood)
blockfront-world-war-ii@blockfront|方块前线 (BlockFront)
@@ -4687,7 +4687,7 @@ increasemobs
scarecrows-territory@|稻草人领域 (Scarecrows' Territory)
dungeons-mod¨dungeons-mod-lite
-loot-beams|战利品光束*
+loot-beams@|战利品光束 (Loot Beams: Relooted!)
theurgy@
better-badlands|恶地改善*
@@ -4709,8 +4709,8 @@ botany-pots-tiers@|更好的植物盆栽*
compatchedstorage|紧凑存储修复 (ComPatchedStorage)
iseedragons
psionic-peripherals
-malum|灵灾*
-mc-modsomancy|镕金术 (Allomancy)
+malum@|灵灾*
+mc-modsomancy@allomancy|镕金术 (Allomancy)
steamcraft2
boilerplate
meldexuns-crystalic-void
@@ -4719,7 +4719,7 @@ llama-steeds-forge@llama-steeds|骑羊驼 (Llama Steeds)¨llama-steeds|骑羊驼
levellib@657400
enigmatic-graves
boat-item-view-forge@boat-item-view|边划边看 (Boat Item View)¨boat-item-view|边划边看*
-forge-creeper-heal-unofficial|Forge苦力怕坑修复[非官方版] (Forge Creeper Heal [Unofficial])
+forge-creeper-heal-unofficial|Forge 苦力怕坑修复[非官方版] (Forge Creeper Heal [Unofficial])
desolation-forge@desolation|荒芜 (Desolation)¨desolation|荒芜*
ma-astral
zaraklib
@@ -4752,8 +4752,8 @@ crafting-automat
ingotter
presence-footsteps@|脚步声*
walk-the-air|凌空微步 (Walk the air)
-castle-dungeons
-mobs-properties-randomness
+castle-dungeons@castle
+mobs-properties-randomness¨@mobs-properties-randomness|Mobs Properties Randomness - 未及时更新,不全
iguana-tweaks|蜥蜴微调*
enhanced-ai
vulcanite|软碲铜 (vulcanite)¨vulcanite-fabric|软碲铜 (vulcanite)
@@ -4789,9 +4789,9 @@ better-survival-mod|更好的生存 (Better Survival mod)
tschipplib
the-deep-dark-ocean-dimension
-online-picture-frame@modern-online-picture-frames|在线相框显示2 (Modern Online Picture Frame)
+online-picture-frame|在线相框显示2 (Modern Online Picture Frame)¨@modern-online-picture-frames|在线相框显示2 (Modern Online Picture Frame) - 已失效
pyromancer|炽焰术师*
-mekanism-fission-recipe
+mekanism-fission-recipe@
macaws-fences-and-walls@|Macaw的栅栏与墙 (Macaw's Fences and Walls)
lunae-silva
@@ -4812,15 +4812,15 @@ enderbeetles
customport
more-sound-config
undead-expansion
-hourglass|沙漏*
+hourglass@|沙漏*
relics-mod@|遗物 (Relics)
comfortable-nether|舒适下界*
arcanecraft-ii|奥法工艺2 (ArcaneCraft II)
-presence-footsteps-forge|脚步声Forge版 (Presence Footsteps (Forge))
+presence-footsteps-forge@resence-footsteps-forge|脚步声Forge版 (Presence Footsteps (Forge))
aardvarks-weird-wonderful-wild
-
+little-delicacy-cafes
ee3-helper
save-your-pets|拯救你的宠物*
main-menu-scale
@@ -4848,7 +4848,7 @@ dark-caverns|暗黑深穴*
-milk-all-the-mobs¨milk-all-the-mobs-fabric
+milk-all-the-mobs@¨milk-all-the-mobs-fabric
roadrunner
pokecube-aoi
grim-statues|Grim的雕像*
@@ -4876,9 +4876,9 @@ rotten-piglins-mod
ex-nihilo-random|无中生有:随想 (Ex Nihilo: Random)
-fabrictailor|离线皮肤切换 (Fabric Tailor)
+fabrictailor@|离线皮肤切换 (Fabric Tailor)
ambientworld
-
+@custom-raid|自定义袭击 (Custom Raid)
apple-trees-revived|苹果树:复生*
potatos-lucky-block|马铃薯幸运方块 (Potato's Lucky block)
even-more-tnt
@@ -4900,7 +4900,7 @@ phosphor-reforged|硫 (Sulfuric/Phosphor Reforged)
hydrogen-reforged|氦 (HeIium)
xl-food-mod-plus|超多食物+ (XL Food Mod+)
-botconnect@mcbot|群服互联🔗 (McBot🔗)
+mcbot@|群服互联🔗 (McBot🔗)
ice-and-fire-dragonseeker
reeses-sodium-options@|Reese的钠视频界面 (Reese's Sodium Options)
continuity@
@@ -4994,15 +4994,15 @@ decorative-blocks-modded-compat|装饰方块模组兼容 (Decorative Blocks modd
camp-chair
size-matters
chalk@chalk-mod|粉笔*
-chalk-fabric
+chalk-fabric@chalk
@modmanager|模组管理器 (Mod Manager)
-yarcf|合成冲突消除 (Yet Another Recipe Conflict Fixer)
+yarcf@|合成冲突消除 (Yet Another Recipe Conflict Fixer)
drones
ticbc
meetle
tarot|塔罗*
-forcecraft|力量工艺 (ForceCraft)
+forcecraft@|力量工艺 (ForceCraft)
right-click-harvest@|右击收割 (Right-Click-Harvest)
custom-content-packs
cake-chomps@¨cake-chomps-fabric
@@ -5018,19 +5018,19 @@ npc-variety-port@npc-variety|NPC变体 (NPC Variety)
just-enough-effects
incorporeal-2-rhododendrite|幻想多媒体2 (Incorporeal 2 + Rhododendrite)
-einsteins-library
+einsteins-library@
worldshape
rslargepatterns|精致存储大样板 (Refined Storage Large Patterns)
gendustry-jei-addon
statement@
-mutant-more@|更多突变生物* - 旧¨new-mutant-more@|更多突变生物 (Mutant More) - 新
+mutant-more@|更多突变生物*¨new-mutant-more@|更多突变生物 (Mutant More)
just-enough-keg
paleolithic-revolution
better-shields|更好的盾牌*
yungs-bridges@|YUNG 的桥 (YUNG's Bridges)¨yungs-bridges-fabric|YUNG 的桥 (YUNG's Bridges)¨yungs-bridges-neoforge|YUNG 的桥 (YUNG's Bridges)
project-red-transmission|红石计划:传导 (ProjectRed - Transmission)
-day-dream
+day-dream@
savage-ender-dragon|更凶猛的末影龙*
age-of-weapons@age-of-weapons-reforged|武器时代 (Age of Weapons)
@@ -5051,12 +5051,12 @@ mentalillness|精神病 (Mental illness)
better-magnesium-video-settings-button|更好的镁视频设置按钮*
adventurez@
mobz
-villagerquests|村民任务 (VillagerQuests)
+villagerquests@|村民任务 (VillagerQuests)
environmentz@
homing-ender-eye@|回归之眼*
magical-crops-regrowth-1-16-5|魔法作物:重生 (Magical Crops: Regrowth)
-voidz
+voidz@
mining-master@
victus@
@@ -5082,7 +5082,7 @@ global-xp@
universal-graves@
more-bows-restrung@|更多弓:重振旗鼓! (More Bows: Restrung!)
-nimble-fabric|灵活 (Nimble)¨nimble|灵活*
+nimble-fabric@nimble|灵活 (Nimble)¨nimble|灵活*
metal-barrels|金属储物桶*
@@ -5141,7 +5141,7 @@ refined-relocation-2|简单分类2*
ftb-sluice
sky-villages-forge@sky-villages|天空村庄 (Sky Villages)¨sky-villages-fabric|天空村庄 (Sky Villages)
gui-clock@¨gui-clock-fabric-version
-hand-over-your-items¨hand-over-your-items-fabric
+hand-over-your-items@¨hand-over-your-items-fabric
koopas-critters
krypton-reforged|氪Forge版*
item-counter|物品计数器*
@@ -5196,14 +5196,14 @@ more-respawn-anchors|更多重生锚*¨more-respawn-anchors-forge|更多重生
werewolves-become-a-beast@werewolves|狼人 (Werewolves)
omnipotent-card@|万用卡牌*
-does-it-tick
+does-it-tick@
public-gui-announcement|公开GUI显示*
wet-lava-sponge
ripples-of-the-past¨@ripples-of-the-past|Ripples of the Past - 最新版(包括公开测试版)
cursor-mod@cursormod|自定义光标*
amplified-nether@|放大化下界*
-oreberries-replanted|矿莓重制版*
+oreberries-replanted@|矿莓重制版*
eye-contact-prohibited|禁止眼神交流! (Eye contact prohibited)
@@ -5233,7 +5233,7 @@ fluffys-farming
aquatic-odyssey-mod
vanilla-cookbook@vanillacookbook|原版烹饪书*
edible-nether-wart|可食用下界疣*
-plasmo-voice@|Plasmo语音*
+plasmo-voice@|Plasmo 语音*
armor-sound-tweak|盔甲音效调整*
hexerei@|魔法巫师*
morerefinedstorage
@@ -5252,7 +5252,7 @@ stupid-horse-stand-still
ceramic-bucket@|陶瓷桶*
durability101@
ceramic-shears|陶瓷剪刀*
-torch-bandolier|火把弹药袋*
+torch-bandolier@|火把弹药袋*
rapid-leaf-decay@
netherite-horse-armor-mod|下界合金马铠*
ambient-environment@
@@ -5260,7 +5260,7 @@ glass-cutter
dicemc-tiered-armors
tetranomicon
more-observers|更多侦测器*
-villagers-respawn|村民重生*
+villagers-respawn@|村民重生*
hot-swappable-armor|热切换护甲 (Hot-Swappable Armor)
advancement-plaques@|进度牌匾*¨advancement-plaques-fabric|进度牌匾 (Advancement Plaques)
@@ -5275,15 +5275,15 @@ 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
+player-tracking-compass@
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-ocean-forge¨awesome-dungeon-edition-ocean-fabric@awesome-dungeon-ocean-fabric
+awesome-dungeon-edition-ocean-forge@awesome-dungeon-edition-ocean¨awesome-dungeon-edition-ocean-fabric
pistorder
-escape-rope-pokemon-item-forge¨escape-rope-pokemon-item-fabric
+escape-rope-pokemon-item-forge¨awesome-dungeon-neoforge
awesome-flooring-forge@¨awesome-flooring-fabric@
medievial-entity-forge@medieval-entity-forge|中世纪生物 (Medieval Entity)¨medievial-entity-fabric@medieval-entity-fabric|中世纪生物 (Medieval Entity)
upgraded-shulkers|更多潜影盒*
@@ -5298,8 +5298,8 @@ mythicmetals@|神话金属 (Mythic Metals)
creeper-firework@|烟花苦力怕*
second-chance@second-chance-forge¨second-chance-forge
promenade@
-london-underground@the-tube|伦敦地铁 (The London Underground)
-dynamiclights-reforged|Embeddium/铷:动态光源 (Embeddium/Rubidium Dynamic Lights)
+london-underground@the-tube|伦敦地铁 (MTR London Underground Addon)
+dynamiclights-reforged@sodium-dynamic-lights|钠/Embeddium:动态光源 (Sodium/Embeddium Dynamic Lights)
the-wild-mod@|荒野更新*
tuff-depth|凝灰岩深度*
minefantasy-reforged|我的幻想重铸版 (MineFantasy Reforged)
@@ -5308,8 +5308,8 @@ framework¨framework-fabric
anointed-items
-golden-age-combat|黄金时代的战斗*
-magnesium-extras|镁/铷:附属 (Magnesium/Rubidium Extras)
+golden-age-combat@|黄金时代的战斗*
+magnesium-extras|钠/Embeddium:附属 (Sodium/Embeddium Extras) - 全部¨@sodium-extras|钠/Embeddium:附属 (Sodium/Embeddium Extras)
chest-tracker@|箱子追踪*
advancementinfo@|进度信息展示 (AdvancementInfo)
animatica@
@@ -5322,9 +5322,9 @@ not-enough-gamerules@¨not-enough-gamerules-fabric
@jsonem|Json格式化实体模型 (Json Entity Models)
speedrunigt@
archmagus
-slimyboyos
+slimyboyos@
fireplace-lib@¨fireplace-lib-forge
-immersive-geology|沉浸地质学*
+immersive-geology@|沉浸地质学*
wool-tweaks@|羊毛调整*¨wool-tweaks-fabric|羊毛调整 (Wool Tweaks)
tools-complement@
adventtic
@@ -5345,17 +5345,17 @@ eggtab-fabric|独立刷怪蛋列表 (Egg Tab)¨eggtab-forge|独立刷怪蛋列
always-a-wither-skull@|必得凋灵骷髅头颅 (Always a Wither Skull)¨always-a-wither-skull-fabric|必得凋灵骷髅头颅 (Always a Wither Skull)
anvil-restoration@|铁砧修复*¨anvil-restoration-fabric|铁砧修复 (Anvil Restoration)
hide-hands-fabric@hide-hands|隐藏主副手 (Hide Hands)¨hide-hands|隐藏主副手*
-better-beacon-placement-fabric@better-beacon-placement|更好的信标放置 (Better Beacon Placement)¨better-beacon-placement|更好的信标放置*
+better-beacon-placement@|更好的信标放置*¨better-beacon-placement-fabric|更好的信标放置 (Better Beacon Placement)
dead-totems
functional-storage@|功能性存储*
the-wild-update|荒野更新 1.19 (The Wild Update 1.19)
archeology@|考古学 (CapsLock Archeology)
-trinkets-and-baubles|饰品与小玩意 (Trinkets and Baubles)
+trinkets-and-baubles@|饰品与小玩意 (Trinkets and Baubles)
rope-ladders|绳梯*
skewers-updated|烤串更新版*
-keep-my-soil-tilled-fabric|保持肥沃 (Keep My Soil Tilled)¨keep-my-soil-tilled|保持肥沃*
+keep-my-soil-tilled@|保持肥沃*¨keep-my-soil-tilled-fabric|保持肥沃 (Keep My Soil Tilled)
double-doors@|双开门*¨double-doors-fabric|双开门 (Double Doors)
expanded-armor-enchanting@|盔甲附魔扩展*
expanded-weapon-enchanting@|武器附魔扩展*
@@ -5365,13 +5365,13 @@ urns
pylons
on-soul-fire@
wizard-staff
-kambrik
+kambrik@
additional-bars@additionalbars|更多的栏杆*¨additional-bars-fabric|更多的栏杆 (Additional Bars)
leap@
cameraoverhaul@
enchantment-id-extender@eide
creeper-overhaul@|苦力怕改革/苦力怕革新*
-cool-elytra-roll
+cool-elytra-roll@
camera-overhaul-forge
blame¨blame-fabric
patboxs-banhammer@banhammer
@@ -5384,7 +5384,7 @@ fabric-shield-lib@fabricshieldlib
frys-things-guns-and-more
quickbind|功能集成 (Quick Bind/Command GUI buttons)
ompd|开放式模块化被动防御 (Open Modular Passive Defense)
-basic-shields-fabric
+basic-shields-fabric@
borderless-mining@
ledger@
pet-owner@petowner
@@ -5413,10 +5413,10 @@ glassware|玻璃制品*
milk-plus@milk_plus
modifiers|重铸*
customnpc-plus@|自定义NPC+ (CustomNPC+)
-ingredient-extension-api
+ingredient-extension-api@
wireless-networks
-compass-manhunt
+compass-manhunt@
exlines-furniture@
tinkers-archery|工匠箭术 (Tinkers' Archery)
designer-modeling|设计师建模*
@@ -5435,7 +5435,7 @@ immersive-industry|沉浸工业*
lightmans-currency@¨lightmans-currency-fabric
hellfire-fireball-launcher|地狱火:火焰弹发射器 (Hellfire: Fireball Launcher)¨hellfire-fireball-launcher-fabric|地狱火:火焰弹发射器 (Hellfire: Fireball Launcher)
guild
-endless-deep-space|无尽深空*
+endless-deep-space|无尽深空* - 已弃用¨@endless-deep-space|无尽深空 (Endless Deep Space)
dynamic-trees-for-sakura|动态的树:樱附属 (Dynamic Trees for Sakura)
advanced-fishing
@@ -5461,7 +5461,7 @@ just-enough-effect-descriptions-jeed@|JEI药水效果 (Just Enough Effect Descri
hostile-neural-networks
mo-cakes|更多蛋糕 (Mo' Cake)
fishing-made-better|更好的钓鱼*
-neon-craft-2-mod|霓虹灯艺2*
+neon-craft-ultimate|霓虹灯艺2 (Neon Craft Ultimate/Neon Craft 2 Mod)
architects-dream|建筑师之梦 (Architect's Dream)
ignition-enderbags
correlated@
@@ -5495,7 +5495,7 @@ mcdw@|我的世界:地下城武器 (MC Dungeons Weapons)
beta-creepers
-macaws-paintings|Macaw的画 (Macaw's Paintings)
+macaws-paintings@|Macaw的画 (Macaw's Paintings)
dyenamics
multi-piston
blood-magic@|血魔法*
@@ -5506,7 +5506,7 @@ wdl|世界下载器 (World Downloader)
domum-ornamentum
methodicalmilking
-save-and-load-inventories-fabric¨save-and-load-inventories
+save-and-load-inventories-fabric@save-and-load-inventories¨save-and-load-inventories
forge-config-api-port-fabric@forge-config-api-port
pams-cookables
end-metals
@@ -5538,7 +5538,7 @@ betweenlands-redstone
more-peripherals@
ageing-spawners
loot-extra|战利品表拓展 (LootExtra)
-cloud-boots-mod@cloud-boots|云靴 (Cloud Boots Mod!)
+cloud-boots-mod@cloud-boots|云靴 (Cloud Boots)
better-compatibility-checker
fastfurnace-minus-replacement
harvest
@@ -5546,7 +5546,7 @@ craftpresence@
stonecutter-recipe-tags@stonecutter_recipe_tags
customnpcsreborn|自定义NPC:重生 (Custom NPCs Reborn)
-brassamberbattletowers
+brassamberbattletowers|战斗高塔 (BrassAmber BattleTowers)
jags
sedna
fog-tweaker
@@ -5586,7 +5586,7 @@ no-null-processors
ma-enchants|Ma 附魔*
bits-and-chisels@
-trading-post|交易站*¨trading-post-fabric|交易站 (Trading Post)
+trading-post@|交易站*¨trading-post-fabric|交易站 (Trading Post)
phantasmic¨@phantasmic|Nourished Nether/Phantasmic/Netherific - 已失效
furnus
permanent-light-generator@plg|恒久光发电机*¨permanent-light-generator-fabric|恒久光发电机 (Permanent Light Generator)
@@ -5604,7 +5604,7 @@ botany-pots-ore-planting@|植物盆栽矿石种植*
satin-api@
easy-magic@|简易附魔台*
-enlightend¨@enlightened-end|Enlightend/Enlightened End/Nourished End - 已失效
+enlightend@|末地启明 (Enlightend/Enlightened End/Nourished End)
rubidium@|铷*
cultural-delights@|多元乐事*
@@ -5634,7 +5634,7 @@ atom-economy|基石-经济 (Atom-Economy)
just-enough-piglin-bartering|JEI猪灵以物易物*
@aftersans53228-fabric-road|暗访谈的Fabric道路 (aftersans53228's fabric roads)
-primal-magick|原初魔法*
+primal-magick@|原初魔法*
ore-creeper|矿石苦力怕*
panthalassa|泛古洋*
stardew-armory|星露谷武器*¨stardew-armory-fabric|星露谷武器 (Stardew Armory)
@@ -5682,7 +5682,7 @@ power-utilities-remastered|能源转换重制*
create-gear-addon
gun-mod-tl
-energymeter
+energymeter@
@headindex|头颅菜单 (Head Index)
quantum-generators|量子发电机*
simply-quarries|简易采石场*
@@ -5720,7 +5720,7 @@ platypuses
hostile-villages|敌对村庄*
throwability|投掷*
biomeinfo@|生物群系信息 (BiomeInfo)¨biomeinfo-fabric|生物群系信息 (BiomeInfo)
-clickable-advancements|可点击的进度 (Clickable advancements)
+clickable-advancements|可点击的进度*
factory0-resources
epic-knights-armor-and-weapons@epic-knights-shields-armor-and-weapons|史诗骑士:盾牌,盔甲和武器 (Epic Knights: Shields, Armor and Weapons)
youre-an-expert-harry|你是个专家,哈利! (You're an Expert, Harry!)
@@ -5775,7 +5775,7 @@ creatures-of-the-snow@creatures-from-the-snow-fabric|雪地里的生物! (Crea
collisions-lib@
wooled-boots@|羊毛靴子*
zawa-evolved|野生动物园:进化 (Zoo and Wild Animals: Evolved)
-bongo@
+bongo@|宾果游戏*
impersonate@
calemi-core@ccore
@@ -5803,7 +5803,7 @@ ladylucs-critters-mod
@modmenu-beta
better-signs
-
+@macula
@howmanyitems
@howmanyitems-fabric
@@ -5823,13 +5823,13 @@ zettai-ars
cant-sleep-clowns-will-eat-me@|夜不能寐 (Can't Sleep Clowns Will Eat Me)
colony-cards|殖民地卡牌*
autoharvest|自动收获 (AutoHarvest)
-realistic-block-physics
+realistic-block-physics@
domestication-innovation@|驯养革新*
-snuffles|魄罗*
+snuffles@|魄罗*
flan-forge@flan¨flan
awesome-dungeon-the-end-forge@¨awesome-dungeon-the-end-fabric@
tehnuts-torcherino
-library-ferret-forge@¨library-ferret-fabric@
+library-ferret-forge@library-ferret¨library-ferret-fabric
awesome-dungeon-nether-forge@¨awesome-dungeon-nether-fabric@
ae2-extended-life|应用能源非官方延续版 (AE2 Unofficial Extended Life)
@@ -5846,12 +5846,12 @@ howling-moon-rebooted-forge¨howling-moon-rebooted-fabric
@dustrial-decor
furnish@furnish-furniture
-iammusicplayer
+iammusicplayer@iam-music-player
pirates|海盗*
comics-bubbles-chat@
fabric-extended-armor-bars-forked
-eating-animation-forge@eating-animation|进食动画 (Eating Animation)¨eating-animation-fabric@eating-animations|进食动画 (Eating Animation)
+eating-animation-forge@eating-animations|进食动画 (Eating Animation)¨eating-animation-fabric@eating-animation|进食动画 (Eating Animation)
waterdripsound@¨dripsounds-fabric@
better-horse-hud|更好的骑马 HUD*
thermopolium|汤饮铺*
@@ -5869,16 +5869,16 @@ betterloadingscreen|加载画面改良 (Better Loading Screen)
fabric-title-changer
window-title-changer
+@betterlight
-
-
+@bushyleaves
robins-ruby-mod
just-needed@justneeded¨just-needed-fabric
nodami-remake|伤害免疫机制移除重制版 (NoDamIRemake)
-had-enough-items|HEI物品管理器*
+had-enough-items@|HEI物品管理器*
worldinfo
@bang
block-helper@
@@ -5889,7 +5889,7 @@ gregicprobe
travelers-index
dimension-data-fix|维度数据修复*
-gtce-bees
+gtce-bees|GTCE 蜜蜂 (GTCE Bees)
armory-expansion-bits-and-pieces
@@ -5946,8 +5946,8 @@ slender-man-mod
cookielicious|曲奇佳味*
the-forbidden|禁忌者*
memory-usage-screen@
-evergreenhud@
-fabricskyboxes@
+evergreenhud|EvergreenHUD - 未及时更新,请从MR¨@evergreenhud
+nuit@
@level-border
@fvt
blue_skies_tcon
@@ -5977,7 +5977,7 @@ nether-hexed-kingdom
talking-ben-mod
in-backpack-eat|背包内食用 (In-backpack Eat)¨@inbackpackeat|背包内食用 (In-backpack Eat) - 已失效
with-thursday|相约星期四 (WithThursday!)
-smooth-swapping@|平滑转移*¨smooth-swapping-forge|平滑转移 (Smooth Swapping)
+smooth-swapping@|平滑转移*¨smooth-swapping-forge@smooth-swapping-forge-updated|平滑转移 (Smooth Swapping)
tinkering-with-blood-magic
tinker-toolcasts
thermal-tinkering
@@ -6028,14 +6028,14 @@ cheese-dragon
boatjump@
torched
-nyfs-quivers|Nyf的箭袋 (Nyf's Quivers)
+nyfs-quivers@|Nyf的箭袋 (Nyf's Quivers)
party-parrots@¨party-parrots-fabric
patina-pipeworks|古铜管道*
brewevolution|酿造进化🍺 (Brewevolution 🍺)
devil-fruits|恶魔果实*
trail-mix
-multiplayer-server-pause-fabric|多人服务器暂停 (Multiplayer Server Pause)¨multiplayer-server-pause-forge|多人服务器暂停 (Multiplayer Server Pause)
+multiplayer-server-pause-forge|多人服务器暂停 (Multiplayer Server Pause)¨multiplayer-server-pause-fabric|多人服务器暂停 (Multiplayer Server Pause)
noshelter-reborn|无庇护所:重生 (NoShelter Reborn)
initial-spawn-dimension
the-one-probe-fabric
@@ -6062,7 +6062,7 @@ glymworld-lush-caves
colored-crafting-stations
harvest-scythes
croptosis
-weeping-angels-mod¨the-lonely-assassins
+weeping-angels-mod@weeping-angels¨the-lonely-assassins
hardcore-item-stages
game-stages-viewer
mpm-lotr|更多玩家模型:魔戒兼容 (MPM LOTR)
@@ -6135,7 +6135,7 @@ the-binding-of-isaac-tears-mod
humbling-bundle@|慈善捆绑包*
grindstone-sharper-tools@¨grindstone-sharper-tools-fabric
nutritious-milk@¨nutritious-milk-fabric|Nutritious Milk - 文件已失效
-extract-poison¨extract-poison-fabric
+extract-poison@¨extract-poison-fabric
zombie-villagers-from-spawner@¨zombie-villagers-from-spawner-fabric
vine-climber
healing-soup@¨healing-soup-fabric
@@ -6159,7 +6159,7 @@ orcz
sandwichable@
@shelve-refabricated
mod-erate-loading-screen@moderate-loading-screen
-shrink_
+shrink_@shrink
cable-tiers
jumbo-furnace@
no-villager-death-messages@
@@ -6176,7 +6176,7 @@ spectrum@|光谱世界*
kitteh6660s-morecraft
angel-ring|天使指环*
debugify@
-nomowanderer
+nomowanderer@
mc-modsthecompressed
cave-enhancements
creative-apiary|创造蜂场*
@@ -6192,7 +6192,7 @@ multiblocked
arcanus@
armor-weight
mc-mods-the-tweaks
-runelic
+runelic@
alexs-cloud-storage@|云存储 (Cloud Storage)
visuality@|可视性*
ecologics@|丰富的生态*
@@ -6217,7 +6217,7 @@ atm-additions
extra-tags|额外标签*
advancedchatlog@AdvancedChatLog
crafting-station|合成站*
-sons-of-sins@
+sons-of-sins@|七罪之子*
advancedchathud@AdvancedChatHUD
fuzes-relics
chirpys-wildlife@wildlife-fabric
@@ -6249,7 +6249,7 @@ kaliastyle|卡利亚式附魔 (CarianStyle)
wmitaf@
depth-meter|深度计*
omni-hopper@omnihopper
-jumpy-boats
+jumpy-boats@
carpetclient@
@@ -6271,7 +6271,7 @@ the-stormlight-archive-mod|飓光志*
extrae
projecte-integration|等价兼容 (ProjectE Integration)
mekajs
-antique-dragons
+antique-dragons@
badpackets@
undead-army
@@ -6318,13 +6318,13 @@ resident-evil-8-mod|生化危机8 (Resident Evil 8)
solar-forge
colored-allays|染色悦灵*
-phantasm
+phantasm@ends-phantasm
emerald-craft-mod@emerald-craft|绿宝石工艺*
roughly-enough-professions-rep@|REI工作方块 (Roughly Enough Professions)
-sophisticated-core|精妙核心*
-edibles-fabric¨edibles
+sophisticated-core@|精妙核心*
+edibles@¨edibles-fabric
reach-fix
-lootjs-forge
+lootjs-forge@lootjs
chat-sounds@chatsounds
advancement-screenshot@¨advancement-screenshot-fabric
mc-mods-loot-drops@all-loot-drops
@@ -6347,19 +6347,19 @@ illager-plushies¨illager-plushies-fabric
pillageplunder
extended-illagers-mod
midnightcontrols@
-wonderful-enchantments
+wonderful-enchantments@majruszs-enchantments
a-lot-of-useless-stuff
lankaster-origins
priority-target|优先目标*
-advanced-botany
+advanced-botany|高级植物学*
charred
incorporeal-3@|幻想多媒体3*
oxygen|氧*
-better-combat-revamped|沉浸式战斗 (Immersive Combat)
+immersive-combat|沉浸式战斗*
@essentialclient
mystical-index@
combat-commons
@@ -6389,9 +6389,9 @@ dense-ores-reborn|致密矿石:重生*
tfctreatedwood|群峦·防腐木 (TFC Treated Wood)
retro64¨retro64-fabric
-double-hotbar|双重快捷栏*
+double-hotbar@|双重快捷栏*
lavatech|熔岩科技 (Lava Tech)
-@with-pineapple-psychic|凤梨通灵术 (Pineapple Psychic)
+pineapple-psychic@|凤梨通灵术*
fragile-torches
@time-changer
come-closer
@@ -6456,7 +6456,7 @@ fantasys-furniture@fantasy-furniture|Fantasy的家具 (Fantasy's Furniture)
touhou-spell-card-additional|东方符卡附加包 (Touhou Spell Card Additional Pack)
registrator
apexcore@
-magiclib@|魔法类库 (MagicLib)
+magiclib@|魔法类库 (MagicLib)¨magiclib-dev@|魔法类库 (MagicLib)
cull-less-leaves@|更好的树叶渲染优化*
@ex-stellae|繁星之凝 (Ex Stellae)
with-craft|手工艺 (With Crafts)
@@ -6523,17 +6523,17 @@ aquatic-torches@
better-sponges
easyexcavate
wooden-shears@|木剪刀*
-brick-furnace|砖熔炉*
+brick-furnace@|砖熔炉*
brick-hopper|砖漏斗*
human-companions
odysseyhud
colorblindness@
aether-addon
-moonstone@|月之石 (MoonStone)¨@moonfabric|月之石 (MoonStone)
+moonstonemod@moonstone|月之石 (MoonStone)¨@moonfabric|月之石 (MoonStone)
server-redirect
city-blocks
-useful-hats
+useful-hats@
modern-splash@|高版本加载界面*
babel
@@ -6566,19 +6566,19 @@ ae2-fluid-crafting-rework@ae2-fluid-crafting|AE2流体合成套件重置 (AE2 Fl
moreculling@
-better-village-forge@better-villages-forge|更好的村庄 (Better Village)¨better-village-fabric@better-villages-fabric|更好的村庄 (Better Village)
+better-village-forge@better-village|更好的村庄 (Better Villages)¨better-village-fabric|更好的村庄 (Better Villages)¨better-village-neoforge|更好的村庄 (Better Villages)
trinkets-curios-theme@
banilla-claws-forge@banilla-claws¨banilla-claws@banilla-claws-forge
transparent-cosmetics|Transparent Cosmetics - 已失效¨@transparent-cosmetics
more-geodes-reforged@¨emerald-geodes@more-geodes
right-click-get-crops@
-oh-my-gourd
+oh-my-gourd@
better-block-sounds@bbs
v0ids-smart-backpacks|V0id的智能背包 (V0id's Smart Backpacks)
-winterly@
+winterly@|银装素裹*
forgetmechunk@¨forgetmechunk-forge
@moremusic
@@ -6625,16 +6625,16 @@ zoomify@
golemania@
vulkanmod@
sledgehammer
-quilt-loading-screen|Quilt加载画面* - 不再更新¨@quilt-loading-screen|Quilt加载画面 (Quilt Loading Screen)
+quilt-loading-screen|Quilt 加载画面* - 不再更新¨@quilt-loading-screen|Quilt 加载画面 (Quilt Loading Screen)
amethyst-imbuement@|紫晶魔艺*
emi@
nuclear-contraptions
fire-crafting@firecrafting|烈焰配方 (FireCrafting)
-tooltiptweaks@
+tooltiptweaks@|提示框微调 (Tooltip Tweaks)
more-babies
@save-my-bed
-@data-reload
+data-reload@
winver@
armor-visibility@
@@ -6642,7 +6642,7 @@ the-chicken-came-first|先有鸡*
hardcore-dungeons
block-relocation-forge¨block-relocation
-norecipebook-fabric@|没有配方书Fabric版 (NoRecipeBook (Fabric))
+norecipebook-fabric@norecipebook|没有配方书Fabric版 (NoRecipeBook (Fabric))
@sort
fpsdisplay@
@where-items-owo
@@ -6661,13 +6661,13 @@ modupdater
fivehead
attained-drops|可种植掉落物*
-attained-drops-2|可种植掉落物2*
+attained-drops-2|可种植掉落物 2*
auth-me@
refinedcreativeinventory@|创造模式物品栏重制 (RefinedCreativeInventory)
runesword
-@jcm
+@jcm|常磐装饰 (Joban Client Mod)
autopackager
dynamx
mob-cards
@@ -6679,7 +6679,7 @@ keybinds@
hat-list@hatlist
nmuk@
raven-brews-core
-raven-coffee|Raven咖啡*
+raven-coffee@raven-coffee-mod|Raven咖啡*
cqc
geode
realistic-spiders|真实的蜘蛛*
@@ -6707,8 +6707,8 @@ no-telemetry@
create-chromatic-return|机械动力:异彩化合物 (Create: Chromatic Return)
coffee-spawner
-timber-mod@timber-mod-by-mycf
-sophisticated-storage|精妙存储*
+timber-mod@timber-mod-by-mycf¨@timber-by-mycf-(server-side)|Timber Mod - 服务端版
+sophisticated-storage@|精妙存储*
advanced-lootable-weapons
what-the-bucket
quickhomes
@@ -6811,7 +6811,7 @@ time-reverse
nether-tears|下界遗泪 (NetherTears)
-africraft
+safaricraft
applied-botanics-addon@applied-botanics|应用能源:植物魔法附属*
overloaded-armor-bar-updated
delightful@
@@ -6826,7 +6826,7 @@ arte-magicum
ocean-blender@|海洋搅拌机 (OceanBlender)
buildcraft-fluxified
-brewin-and-chewin|饮酒作乐 (Brewin' And Chewin')
+brewin-and-chewin@|饮酒作乐 (Brewin' And Chewin')
infinite-source-of-water
dramatic-doors@
recipe-gui
@@ -6851,9 +6851,9 @@ umu-backpack|UMU Backpack - 已失效¨@umu-backpack
short-stacks
smooth-boot-reloaded@|流畅加载重置版*
stone-crafting-table
-lodestonelib
+lodestonelib@
witherturnsskeletons@
-twilight-forest-the-lost-blocks
+twilight-forest-the-lost-blocks@
the-nexus-deep-dark-dimension
jepp|JEI绘画预览 (Just Enough Painting Previews)
@@ -6883,7 +6883,7 @@ forgotten-treasures
player-health-indicators@playerhealthindicators
displaycase@|陈列柜 (Display Case)
auto-look
-grimoire-of-gaia|盖亚魔典 (Grimoire of Gaia)
+grimoire-of-gaia@|盖亚魔典 (Grimoire of Gaia)
ftb-jar-mod-forge
dimensional-paintings|维度绘画*
item-recycler
@@ -6962,7 +6962,7 @@ cammies-wearable-backpacks@
parrying
back-in-slime-slime-blocks-for-1-7
-create-alloyed|机械动力:合金 (Create: Alloyed)
+create-alloyed@|机械动力:合金 (Create: Alloyed)
verdure@|郁郁葱葱*
@@ -7040,7 +7040,7 @@ greenery|绿色植物🌿 (Greenery🌿)
butchers-delight@|屠夫乐事 (Butcher's Delight)
large-meals-an-add-on-for-farmers-delight
explorers-delight@
-the-irregular-chef
+the-irregular-chef@irregular-chef
compat-o-plenty@compatoplenty
snow-pig@snowpig|雪猪*¨snow-pig-fabric@snowpig-fabric|雪猪 (Snow Pig)
wandering-collector@
@@ -7050,7 +7050,7 @@ wandering-collector@
skull-craft@|头颅工艺*
re-chiseled-compat
auto-leveling
-packed-inventory
+packed-inventory@
crafting-on-a-stick
endlessids@
@@ -7058,10 +7058,10 @@ endlessids@
@cpm-fabric|压缩空间 Fabric (Compact Machines - Fabric)
cycle-paintings@¨cycle-paintings-fabric
-fire-spread-tweaks¨fire-spread-tweaks-fabric
+fire-spread-tweaks@¨fire-spread-tweaks-fabric
anger-management
easy-elytra-takeoff@¨easy-elytra-takeoff-fabric
-stack-refill¨stack-refill-fabric
+stack-refill@¨stack-refill-fabric
light-meals@|轻食物语*
lost-and-fished@lost-fished|失而钓得 (Lost and Fished)
custom-backgrounds
@@ -7090,7 +7090,7 @@ twigs@
-@fplib
+fplib@
spongemixins¨@spongemixin1710
nock-enough-arrows
every-compat@|泛用兼容:木材 (Every Compat: Wood Good)
@@ -7116,7 +7116,7 @@ lapis-stays-in-the-enchanting-table
goodall
freecam-by-zergatul
-macaws-bridges-oh-the-biomes-youll-go@|Macaw的桥梁:你将去的生物群系附属 (Macaw's Bridges - Oh The Biomes You'll Go)
+macaws-bridges-oh-the-biomes-youll-go|Macaw 的桥梁:你将去的生物群系附属 (Macaw's Bridges - Oh The Biomes You'll Go) - 已失效¨macaws-byg-bwg@|Macaw 的桥梁:你将去的生物群系附属 (Macaw's Bridges - Oh The Biomes You'll Go)
macaws-fences-oh-the-biomes-youll-go@|Macaw的栅栏与墙:你将去的生物群系附属 (Macaw's Fences - Oh The Biomes You'll Go)
alternate-current@
no-unused-chunks@
@@ -7172,7 +7172,7 @@ extrabotany2|额外植物学重置版 (ExtraBotany2)
terramine@
aeinfinitybooster
endertech-infinity|Endertech Infinity - 已失效¨@endertech-infinity
-more-frogs-fabric|更多青蛙 (More Frogs)
+more-frogs-fabric@morefrogs|更多青蛙 (More Frogs)
create-big-cannons@|机械动力:火炮*
@@ -7202,7 +7202,7 @@ another-furniture@|别样家具*
rayon@
-ponder
+ponder@
ender-logistics|末影物流*
@@ -7211,9 +7211,9 @@ megaplanets
cosmic-horizons-gc-addon
ultris-boss-expansion
bedfix
-mo-glass
+mo-glass@
flans-mod-global-firestorm
-turtlemancy
+turtlemancy@
@@ -7260,7 +7260,7 @@ dog-yeeter
mc122477fix@
armors-hud-revived-liteloader-only
living-things@|自然生物*
-beautify-decorate@beautify|美化! (Beautify !)
+beautify-decorate@beautify|美化! (Beautify!)
Voodoo@voodoo
notenoughluck|更多幸运 (NotEnoughLuck)
@@ -7281,7 +7281,7 @@ the-squid-game-mod
datapack-portals@datapackportals|数据包传送门*
more-beautiful-walls@
iris-flywheel-compat@iris-flw-compat
-prism-lib¨prism-fabric
+prism-lib@¨prism-fabric
map-atlases-forge@map-atlases|地图册 (Map Atlases)¨map-atlases|地图册*
@eprr|额外玩家渲染Rift版 (ExtraPlayerRenderer-Rift)
@@ -7346,7 +7346,7 @@ solar-flux|日光通量*
custom-background-color@cbc|自定义背景颜色*
simple-shulker-preview|Simple Shulker Preview - 已弃用¨@simple-shulker-preview
-bygone-nether¨bygone-nether-fabric
+bygone-nether|逝去的下界*¨bygone-nether-fabric|逝去的下界 (Bygone Nether)
@clickopener
kokoalinux-backport
mechanical-clothesline|机械晾衣绳*
@@ -7386,7 +7386,7 @@ f5-begone@
smartvision@
-no-more-purple
+no-more-purple@
content-creator-integration¨content-creator-integration-fabric
defaultworldtype@
@@ -7415,12 +7415,12 @@ visible-toggle-sprint@
let-me-despawn@lmd
glsl-panorama@
feyfriends@
-mccalc
+mccalc@
nekeys
crafted-forge¨crafted
cursery
mirage-orb
-galacticborn-origin
+galacticborn-origin@
extra-recipe|更多配方*
@@ -7451,17 +7451,17 @@ way-much-faster-oxidize@|更快铜锈蚀*
emi-trades-villager-trading-emi-plugin@emitrades|EMI村民交易 (EMI Trades)
sim-u-kraft-reloaded|模拟城市重置版 (Sim-U-Kraft Reloaded)
-create-factory
+create-factory@
horseinboat@
upgraded-tools@|下界合金增强:工具*
tools-for-caves-and-cliffs
-@better-end-sky
+betterendsky@better-end-sky
infinity-with-mending@|无限与经验修补*
infinity-craft
hookshot@
custom-spawns
-create-misc-and-things|机械动力:实用物件 (Create : Misc & Things)
+create-misc-and-things@|机械动力:实用物件 (Create : Misc & Things)
low-fire@
infinity-buttons@infinitybuttons|无限按钮*
i-know-what-im-doing-fabric
@@ -7485,7 +7485,7 @@ shiny-horses@
nyx-rotn-edition
playeranimator@
-apathy-mod
+apathy-mod@apathy
auudio-forge@auudio¨auudio-fabric
easy-shulker-boxes@
@@ -7519,7 +7519,7 @@ bedrockcraft@
better-mobgriefing-gamerule
frozen-fiend
-gateways-to-eterrnity|永恒之门 (Gateways to Eternity)
+gateways-to-eternity|永恒之门 (Gateways to Eternity)
gate-of-babylon-additions@|王之财宝附加 (Gate of Babylon Additions)
hephaestus-fabric@hephaestus|匠神 (Hephaestus)
crafting-bench@
@@ -7532,7 +7532,7 @@ create-infinidrill
@scrollable-tooltips
attribute-tooltip-fix@
-
+no-slow-down|不减速*
crumbs@¨crumbs-fabric
italian-delights-add-on-for-farmers-delight
immersivemc@|沉浸式MC (ImmersiveMC)
@@ -7568,14 +7568,14 @@ led@|发光二极管 (Light Emitting Diode)
i-dont-need-stone|再见 圆石 (I DON'T Need Stone)
@libgui-forge
colorize@
-no-magic-milk
+no-magic-milk@
powered-food
spirit@
elytra-bounce@
no-report-button@nrb
mana-wizardry
-aileron
+aileron@
@devworld
immersive-weapons-compatibility-bridge
@@ -7608,7 +7608,7 @@ stop-hiding
simple-world-timer
recursive-resources@recursiveresources
inventory-actions
-lavafishing@|岩浆钓鱼 (Lava Fishing)
+lavafishing@|熔岩钓鱼 (Lava Fishing)
haunted-harvest@
dungeonsenhanced
create-crystal-clear@|机械动力:晶莹剔透 (Create: Crystal Clear)
@@ -7628,10 +7628,10 @@ beaming-drops@loot-beams-backport|掉落物光束 (Beaming drops)
protocharset@protocharset-rift
the-hordes@
-@inventory-management
+inventory-management@
soul-fired@soul-fire-d
faithfulbackrooms
-ex-machinis@
+ex-machinis@¨ex-machinis-divitiae-deorum@
goety@|诡厄巫法*
torch-hit
@@ -7655,7 +7655,7 @@ model-gap-fix@modelfix
posture
little-ants|小蚂蚁*
-hardcore-torches|硬核火把*¨hardcore-torches-forge|硬核火把 (Hardcore Torches)
+hardcore-torches@|硬核火把*¨hardcore-torches-forge|硬核火把 (Hardcore Torches)
auto-torch
nightlights@|小夜灯 (Night Lights)
angry-mobs
@@ -7666,13 +7666,13 @@ unlit-campfire@
xl-packets-renewed
nameplate@
rpgdifficulty@
-tooltiprareness@
+tooltiprareness@|稀有度提示框 (TooltipRareness)
@minimotd-reforged
combat-roll@|战斗翻滚*
project-brazier
over-enchanted@overenchanted
creeper-overhaul-firework|烟花苦力怕改革*
-spartan-weaponry-bewitchment|斯巴达的武器:巫师之路 (Spartan Weaponry - Bewitchment)
+spartan-weaponry-bewitchment@|斯巴达的武器:巫师之路 (Spartan Weaponry - Bewitchment)
no-water-spread
shoppy
cathedral
@@ -7763,7 +7763,7 @@ create-sifting@|机械动力:筛子*
player-name-tweaks|角色名调整 (PlayerNameTweaks)
dat-modding-api
rangefinder-hud@rangefinderhud
-prelaws-blocky-siege
+prelaws-blocky-siege@luckys-blocky-siege
modget@
@babies-forever
no-leaf-decay@
@@ -7790,22 +7790,22 @@ glacidus
compat-makeover
-
+@deferred-registries
@nametagping
map-in-slot@
hungertweaker
knob-control
daedalus-labyrinth
tip-the-gui|界面尺寸设置优化 (GUI Scale)
-copper-overhaul
+copper-overhaul@
epic-fight-nbt-integration
login-protection|登录保护*
-swem@
+swem@|星虫马术 (Star Worm Equestrian Mod)
metal-barrels-legacy|金属储物桶:传承 (Metal Barrels: Legacy)
inventoryfullalert@
lead-beyond|引向超越*
@dyeallthethings
-swampier-swamps¨swampier-swamps-fabric
+swampier-swamps@¨swampier-swamps-fabric
weather-settings|更好的天气控制 (Weather settings)
shutdowner
shield-mechanics|盾牌机制优化 (Shield mechanics)
@@ -7843,7 +7843,7 @@ couplings@
mambience@
elytra-aeronautics@¨elytra-aeronautics-forge
-infinitory
+infinitory@
loud-leads|拴绳音效*
music-duration-reducer
telepistons@
@@ -7889,7 +7889,7 @@ mob-vote-2022-forge¨mob-vote-2022
curious-lands
cpmsvcc@|自定义玩家模型的简单的语音聊天兼容 (Customizable Player Models Simple Voice Chat compat)
pettable@
-create-enchantment-industry@|机械动力:附魔工业*¨create-enchantment-industry-fabric@|机械动力:附魔工业 (Create Enchantment Industry)
+create-enchantment-industry@|机械动力:附魔工业 (Create: Enchantment Industry)¨create-enchantment-industry-fabric@|机械动力:附魔工业 (Create: Enchantment Industry)
enchanted-witchcraft@
wyml
@@ -7927,7 +7927,7 @@ camera-utils@
roughly-enough-items-server-component@
daves-building-extended¨daves-building-extended-fabric
@rechiseled-bookshelves
-dimensional-pockets-ii|四次元口袋2 (Dimensional Pockets II)
+dimensional-pockets-ii@|四次元口袋2 (Dimensional Pockets II)
@calculatorrinf-uwu
black-hole-storage|黑洞存储*
@@ -7936,7 +7936,7 @@ mod-button
opt-carpet-addition@
trees-do-not-float@
cosmos-library
-groovyscript
+groovyscript@
potion-ring-reforged@|药剂环重铸 (Potion Rings - REFORGED)¨potion-ring-reforged-fabric|药剂环重铸 (Potion Rings - REFORGED)
scp-overtime@
@@ -7953,7 +7953,7 @@ changed-minecraft-mod@
hello-crosshair@
bendy-lib@
-enchantment-transfer-forge
+enchantment-transfer-forge@enchantment-transfer
golden-crops
lil-wings@
@@ -7974,7 +7974,7 @@ earth-mobs@earthmobs|地球生物*
simpleores-tin
just-enough-keys¨just-enough-keys-fabric
why-am-i-on-fire@
-bouncing-balls-api
+bouncing-balls-api@
bouncing-balls@|弹跳球*
teletubbies-mod|天线宝宝*
almost-unified@
@@ -8075,7 +8075,7 @@ custom-shop|自定义商店 (CustomShop)
another-liquid-milk@
dark-mode-everywhere-fabric@|夜间模式无处不在Fabric版 (Dark Mode Everywhere - Fabric)
-bartering-station@
+bartering-station@|易物站*
dark-paintings@
filtpick@|拾取筛 (FiltPick)
@@ -8104,7 +8104,7 @@ toomanyorigins@
evil-regeneration
food-treasure-box|食物宝盒*
revolution
-apugli@
+apugli|Apugli - 在此处更新¨@apugli|Apugli - 已停更
harvest-season
@ic2fix
@extended-bookshelves
@@ -8120,7 +8120,7 @@ mystical-nature|神秘自然*
@formula-addition-fabric|配方追加 (Formula addition)
randomlib
@kinventory
-pullup@|飞行警报
+pullup@|飞行警报*
id-primeval@primeval
colorfulazaleas@colorful-azaleas
pigeon-post@
@@ -8160,7 +8160,7 @@ aoa-end-realmstone
adaptive-tooltips@
variant-barrels-fabric¨@variant-barrels|Variant Barrels - 已失效
-variant-crafting-tables-fabric¨@variant-craftingtables|Variant Crafting Tables - 已失效
+variant-crafting-tables-fabric@variant-crafting-tables
immersive-structures@
avaritia-port-fabric
explorify
@@ -8240,11 +8240,11 @@ qfapi-and-qsl-biomefix-1-18-2@qsl-biomefix
honey-expansion-add-on-for-farmers-delight|蜂蜜拓展 (Honey Expansion)
moguns@mo-guns
dayz-minecraft
-infusion-table@
+infusion-table|灌注祛魔台* - 已失效¨@infusion-table|灌注祛魔台 (Infusion Table)
backroomtastic
xps-things|经验之谈 (XP's Things)
gravityapi@gravity-api
-@super-secret-revival
+fabric-super-secret-settings@super-secret-revival
item-alchemy-fabric@item-alchemy
bat-elytra@
super-secret-settings@
@@ -8268,7 +8268,7 @@ mined-rider-kuuga
inventory-essentials@¨inventory-essentials-fabric
astral-additions|星辉附加*
simple-copper-pipes@¨simple-copper-pipes-forge
-advanced-nbt-tooltips@|高级NBT提示工具 (Advanced NBT Tooltips)
+advanced-nbt-tooltips@|高级NBT提示框 (Advanced NBT Tooltips)
follow-me
stabx-modern-guns
justhammers@just-hammers
@@ -8280,10 +8280,10 @@ sebastrnlib
what-music-is-this
elemental-combat|元素战斗*
-roasted|烘烤*
+roasted@|烘烤*
lotr-attack-indicator-addon@lotr-attack-indicator|魔戒攻击指示器 (LOTR Attack Indicator Addon)
steels-delight
-lotr-pouch-viewer-addon@lotr-pouch-viewer|魔戒口袋内容展示 (LOTR Pouch Viewer Addon)
+lotr-pouch-viewer-addon|魔戒口袋内容展示 (LOTR Pouch Viewer Addon)¨@lotr-pouch-viewer|魔戒口袋内容展示 (LOTR Pouch Viewer Addon) - 已失效
fastfood-delight|快餐乐事 (FastFood Delight)
festive-delight@|节日乐事*
project-arsenal
@@ -8327,7 +8327,7 @@ mo-colors@
peek@
fancy-dyes@
-@extra-mod-integrations
+extra-mod-integrations@
mod-settings@
dont-disappear-on-me@dont-dissapear
fastquit@
@@ -8352,9 +8352,9 @@ masonry-blocks
mssql-jdbc
packages@|自定义包裹*
giant-spawn@¨giant-spawn-fabric
-@mtr-nte|蓝星社交通扩展 (Nemo's Transit Expansion)
-serene-shrubbery
-gardens-of-the-dead@
+@mtr-nte|纸板箱的交通扩展 (Nemo's Transit Expansion)
+serene-shrubbery@
+gardens-of-the-dead@|死亡花园 (Gardens of the Dead)
pirate-hats
decor4fabric
mob-skills
@@ -8373,7 +8373,7 @@ mineterrariabosses
biomes-in-jars-forge@biomes-in-jars|群系罐子 (Biomes in Jars)¨biomes-in-jars-fabric|群系罐子 (Biomes in Jars)
@bocchitheblock
craftable-potion-combinations@craftablepotioncombinations
-polylib
+polylib@
@creeper-fire-charge-fix|Creeper火焰弹修复 (Creeper Fire Charge Fix)
tags-binder@
fastcaps
@@ -8387,7 +8387,7 @@ atom-sweep|基石-清理 (Atom-Sweep)
the-gold-rush
optifabric-origins@
lng-deco
-darkrpg-reborn|黑暗RPG:重生 (DarkRPG: REBORN)
+darkrpg-reborn|黑暗 RPG:重生 (DarkRPG: REBORN)
modpack-manager@|模组管理器*
rocket-connector
hot-and-cold
@@ -8420,7 +8420,7 @@ mob-crossing
just-enough-vehicles
max-health-fix@
-@synlib
+synlib@
tell-me-my-items@
pollen
mavapi@
@@ -8442,7 +8442,7 @@ re-ftbqkeys@|FTB任务本地化键:重置 (Re-FTBQLocalizationKeys)
enchantment-sort|附魔排序*
wrapfix|换行修复 (WrapFix)
gem-crabs|宝石蟹*
-create-rainbow-compound|机械动力:彩虹化合物 (Create: Rainbow Compound)
+create-rainbow-compound@rainbowcompound|机械动力:彩虹化合物 (Create: Rainbow Compound)
inmis-back-pack-mod-retextured
thonkutil
xp-storage@
@@ -8467,7 +8467,7 @@ treasure2-twilight-forest-loot-pack
sanity-and-insanity
crops-love-rain@
till-zombies-tear-us-apart
-difficult-raids
+difficult-raids@
gag
bleach-awaken-by-deephantom15
@@ -8506,7 +8506,7 @@ simple-flashlight-port@flashlight
alwaysopenwater@|始终开放水域 (AlwaysOpenWater)
quest-plaques@|任务牌匾*
-video-player|视频播放器*
+video-player@|视频播放器*
stendhal@
dynamic-surroundings-remastered-fabric-edition@dynamicsurroundings_remasteredfabric
terraplusplus
@@ -8549,7 +8549,7 @@ codechickenlib-unofficial@
codechickencore-unofficial@|鸡块核心非官方版 (CodeChickenCore Unofficial)
permafrost-biome
-ambient-additions
+ambient-additions@ambientadditions
sweep|扫雷 (Minesweeper)
sekclib
@@ -8574,7 +8574,7 @@ tales-of-villagers-the-infiltrators|村民之书:渗透者 (Tales of Villagers
@morecullingextra
carpet-fixes@
er-core|ER 核心 (ER Core)
-rings-of-ascension@¨fabric-rings-of-ascension
+rings-of-ascension@|提升戒指 (Rings of Ascension)¨fabric-rings-of-ascension|提升戒指 (Rings of Ascension)
@@ -8583,7 +8583,7 @@ convenient-curios-container@convenient_curios_container
moogs-voyager-structures@
extra-aoa-info|额外虚无世界信息 (Extra AoA Info)
-opulence
+opulence@
lonely-biome|孤独的生物群系*
lamps
@@ -8599,42 +8599,42 @@ uncrafter@
pangea-ultima
@fpspluslf|FPS优化Legacy Fabric版 (FPS Plus Legacy Fabric)
-table-top-craft¨table-top-craft-fabric
+table-top-craft@¨table-top-craft-fabric
ddyc@
@stonecutter-damage
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
@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-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@
macaws-bridges-modding-legacy|Macaw 的桥梁:Modding Legacy 附属 (Macaw's Bridges - Modding Legacy)
-macaws-bridges-aurora@|Macaw的桥梁:Aurora 附属 (Macaw's Bridges - Aurora)
+macaws-bridges-aurora@macaws-aurora|Macaw 的桥梁:Aurora 附属 (Macaw's Bridges - Aurora)
betterlands
koremods@
-heart-of-ender
-heart-of-spartan
-macaws-fences-biomes-o-plenty|Macaw的栅栏与墙:超多生物群系附属 (Macaw's Fences - Biomes O' Plenty)
+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) - 原,已失效
endemic@
lucys-sloths
@gdtweaker
-drop-the-rock-no-tree-punching-compat
-macaws-bridges-abnormals|Macaw的桥梁:Abnormals 附属 (Macaw's Bridges - Abnormals)
-macaws-bridges-shroomed|Macaw的桥梁:Shroomed 附属 (Macaw's Bridges - Shroomed)
-macaws-bridge-betterlands|Macaw的桥梁:Betterlands 附属 (Macaw's Bridge - Betterlands)
+drop-the-rock-no-tree-punching-compat@
+macaws-bridges-abnormals@macaws-abnormals|Macaw 的桥梁:Abnormals 附属 (Macaw's Bridges - Abnormals)
+macaws-bridges-shroomed|Macaw 的桥梁:Shroomed 附属 (Macaw's Bridges - Shroomed)
+
shroomed
-rlcraft-structures-not-official
+rlcraft-structures-not-official@rlcraft-structures-%28not-official%29
moving-sunflowers
lunar-mana-generator|月光魔能制造器*
ost-overhaul@
@@ -8650,7 +8650,7 @@ the-monk-mod
expanded-enderchest@
text-utilities@
-memorysweep|内存清扫 (Memory Sweep)
+memorysweep@|内存清扫 (Memory Sweep)
@explosive-enhancement|爆炸动画增强 (Explosive Enhancement)
autostereogram@
bounced|Bounced! - 不活跃¨@bounced
@@ -8684,7 +8684,7 @@ emcstage@
@alwayzsteve|永远史蒂夫 (Alwayzsteve)
vanilla-vistas
menulogue
-archaicfix@
+archaicfix¨@archaicfix
ekac@|糕蛋 (ekaC)
byg-plus|你将去的生物群系+ (Oh The Biomes You'll Go Plus)
farmers-compats
@@ -8697,8 +8697,8 @@ vanity-dungeons-pack
divinerpg-processing-compat
weapon-master@|武器大师*
your-ideas|你的想法*
-vanity-ancient-tool-variants
-motion-capture-mod-mocap|动作捕捉 (Motion Capture)
+vanity-ancient-tool-variants@
+motion-capture-mod-mocap@motion-capture|动作捕捉 (Motion Capture)
wormhole-portals@wormhole|虫洞 (Wormhole)
dragon-magic-and-relics@|神龙魔法与遗物*
yummy-foods-forked
@@ -8707,10 +8707,10 @@ fullblock-energistics@
youmatter@
vanity_battle_axes
markdownmanual@|Markdown 手册(Markdown Manual)
-powershot
+powershot@
@crockpot-tweaker
automatic-tool-swap@
-knight-quest
+knight-quest@¨knight-quest-fabric¨knight-quest-reforged
modernfix@|现代化修复 (ModernFix)
blades-plus
homeostatic@|稳态*
@@ -8738,7 +8738,7 @@ harvest-day|丰收之日*
hungteens-lib@pangteens-lib
more-tool|更多工具*
@ex-hud
-
+@hoarding|囤积 (Hoarding)
optifine-crash-fix
@@ -8760,7 +8760,7 @@ fxs-rail-optimization@rail-optimization
morejs@
@moresnowballls|更多雪球 (MoreSnowballs)
-valkyrien-skies-control
+valkyrien-skies-control@
the-end-expanded-mod-ice-and-fire-addon
vault-patcher@|保险库补丁*
projectile-damage-attribute@
@@ -8776,7 +8776,7 @@ containersearcher@csearcher
screencapper@
-valkyrien-skies-world
+valkyrien-skies-world@
lucky-tnt-lib@
waffles-placeable-foods@
otg-the-void
@@ -8789,9 +8789,9 @@ levelhearts@
bring-back-luck@bringbackluck¨forge-bring-back-luck
teleport-master@|传送命令*
@mufog|锻造 (Mufog)
-betterparry|更好的格挡 (BetterParry)
+betterparry@|更好的格挡 (BetterParry)
rltweaker2
-thaumcraft-aspect-creator|神秘时代要素创建器*
+thaumcraft-aspect-creator@|神秘时代要素创建器*
vanity_katanas
pixels-character-models@|Pixel 的角色模型 (Pixel's Character Models)
l2-complements@|莱特兰-扩充*
@@ -8835,14 +8835,14 @@ ex-nihilonatureminerals|无中生有:传承 - Nature's Minerals扩展 (Ex Nihi
memory-settings|内存设置*
-tiny-item-animations@
+tiny-item-animations|Tiny Item Animations - 已失效¨@tiny-item-animations
more-plates-revamped@|更多金属板重置版*
extended-slabs-plus
brewin-and-chewin-fabric@|饮酒作乐(Fabric版) (Brewin' And Chewin' [Fabric])
particular
longer-login-times
broglis-plants
-rlmixins
+rlmixins@
simplehats@simple-hats|简单帽子 (Simple Hats)
timeoutout-fabric@timeoutout
fabric-furnaces
@@ -8878,13 +8878,13 @@ even-more-creatures-mod
eerie-entities
better-weather-revived
-rlcombat|更好的战斗-RLCraft版 (RLCombat)
+rlcombat@|更好的战斗-RLCraft版 (RLCombat)
botanical-addons
@scoreboards-backup|计分板备份 (Scoreboards Backup)
hodgepodge@
fast-projectile-fix
-edivadlib
-systeams
+edivadlib@
+systeams|热力系列:蒸汽锅炉 (Thermal Systeams)
waymaker@
dmod
@@ -8894,7 +8894,7 @@ wizards@|魔法师*
basketcase
primal-lib
biomes-youll-go-1-12-2-fixed|你将去的生物群系修复版 (Biomes You'll Go Fixed)
-rune-crafting|符文 (Runes)
+rune-crafting@runes|符文 (Runes)
controlled-random
leos-bosses|Leo的Boss (Leo's Bosses)
leos-illagers
@@ -8910,7 +8910,7 @@ spell-power@
morecrashinfo|更多崩溃报告信息 (MoreCrashInfo)
epicfight-saopack@epicacg|史诗战斗:ACG附属包 (EpicFight ACG AddonPack)
screenshot-viewer@|截图查看器*
-gml
+gml@
@futurepack-additions|飞向未来附加 (Futurepack Additions)
bee-fix|蜜蜂修复*
spawner-fix|刷怪笼漏洞修复 (Spawner Bug Fix)
@@ -8942,7 +8942,7 @@ ob-pandorica
no-weather-effects@
ob-tooltips
hekate-lib
-loot-journal
+loot-journal@¨loot-journal-neoforge¨loot-journal-fabric
@@ -8964,7 +8964,7 @@ structure-floater-fabric
applecore-unofficial@|苹果核非官方版*
drilling-veins@
server-chat-history@|服务端聊天历史*
-scriptor-magicae|字符魔法师*
+scriptor-magicae@|字符魔法师*
json-entity-models-jems@|JSON实体模型 (JSON Entity Models)
@@ -8978,7 +8978,7 @@ monoblocks
tinkers-things-json@tinkers-things|匠魂杂物 (Tinkers' Things)
free-cam¨@freecam|Freecam - 无作弊版
workings@
-@mod-loading-screen
+@mod-loading-screen|模组加载屏幕 (Mod Loading Screen)
lucky-cases
atmosfera
feruchemy-allomancy|藏金术 (Feruchemy)
@@ -9016,11 +9016,11 @@ boss-rifts
random-teleport-altar
syncpotioneffects
aobd-ore-chunks
-evasive-items
+evasive-items@
spagheters-enchants
factory-api@
tempad@|任意门*
-structure-void-toggle|结构空位切换*
+structure-void-toggle@|结构空位切换*
@cactusfix
morezombies|更多僵尸 (More Zombies)
mushroom-quest
@@ -9054,7 +9054,7 @@ superfancyclouds-refabricated@|超级华丽云:重筑版 (SuperFancyClouds: Re
pet-tracker
@splashfox|狐狸加载界面 (SplashFox)
-@tap-tab
+tap-tab@
mekanism-ce-tools|通用机械工具:社区版 (Mekanism Community Edition: TOOLS)
mekanism-ce-generators|通用机械发电机:社区版 (Mekanism Community Edition: GENERATORS)
@@ -9076,7 +9076,7 @@ golden-delicacies
hightiercrops
commandspy
slimyfloor
-daily-dad@|每日冷笑话*¨daily-dad-server@|每日冷笑话 (Daily Dad) - Daily Dad - Server
+daily-dad@|每日冷笑话* - 客户端版¨daily-dad-server@|每日冷笑话 (Daily Dad) - 服务端版
good-ending@
creeper-nuggets
@@ -9100,7 +9100,7 @@ welcome-to-the-jungle|丛林传说 (Welcome to the Jungle)
transparent@
hopperplus-instant-hoppers
sbm-merpig
-lwjgl3ify
+lwjgl3ify@
creates-delight@|机械动力乐事 (Create's Delight)
@@ -9135,7 +9135,7 @@ betweencards
google-chat@
-@resclone
+resclone@
tame-them-all
little-contraptions
@@ -9148,7 +9148,7 @@ sculked|Sculked - 已失效¨@sculked
better-mob-spawner|更好的刷怪笼*
planeteer-dimensions|飞越群星:维度 (Planeteer - Dimensions)
planeteer-machines|飞越群星:机器 (Planeteer - Machines)
-create-central-kitchen@|机械动力:中央厨房*
+create-central-kitchen@|机械动力:中央厨房 (Create: Central Kitchen)
jamd
ninezeros-gun-mod-addon
donthurtthatboat@
@@ -9197,12 +9197,12 @@ tis-the-season
@baubles-expanded
cobblehaters
-
+mob-captains|怪物队长*
@sound-track
snowballing
winter-essentials
-embeddium-extras|镁/铷附属:土豆版 (Magnesium/Rubidium Extra: Potato Editon*)
+embeddium-extras@|镁/铷附属:土豆版 (Magnesium/Rubidium Extra: Potato Editon*)
@realistic-cruelty|真实残忍 (Realistic Cruelty)
trapper-pelts@
workshops-of-doom@
@@ -9214,7 +9214,7 @@ tnt-breaks-bedrock@
from-the-north@
spruce-willis-the-xmas-tree@
sweet-potato-core-spc|地瓜核心 (Sweet Potato Core)
-merge-enchantments
+merge-enchantments@
turkey|火鸡*
snowy-weaponry@
@@ -9302,7 +9302,7 @@ mysterious-biomes
head-in-the-clouds@
fdmc
-rpg-style-more-weapons
+rpg-style-more-weapons@rpg_style_more_weapons
easy-experience
@@ -9319,7 +9319,7 @@ darkmenu-darkrpg-legacy-menu@darkrpg-menu
rl-artifacts|奇异饰品-RLCraft版 (RLArtifacts)
azurelib@
-harder-monster-boats-finer-boat-control
+harder-monster-boats-finer-boat-control@harder-monster-boats
spyglass-improvements@|望远镜改进*
vanilla-zoom@
time-in-a-bottled
@@ -9343,7 +9343,7 @@ amethyst-tools-mod
@grate-blocks
lush-forests
-retraining
+retraining@
ender-ore@enderore
ars-instrumentum
easier-sleeping@
@@ -9374,7 +9374,7 @@ item-info
florist
omni
frostrealm
-nameless-trinkets
+nameless-trinkets@
unsafe-spawns
bubbles@pop
@@ -9384,8 +9384,8 @@ 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)
squidless
-enders-delight|末影乐事 (Ender's Delight)
-decostorage
+enders-delight@|末影乐事 (Ender's Delight)
+decostorage@decorative-storage
mahogany
aspen
unmending-potato-edition|经验不修补:土豆版*
@@ -9441,7 +9441,7 @@ metaworlds-unofficial@metaworldsunofficial|元世界:非官方版*
tslateffectslib@
tslatentitystatus-tes@tslatentitystatus
@invisible-frames-mod
-elytra-capes
+elytra-capes@elytracapes
config-swapper
viafabricplus@
menu-companions
@@ -9455,7 +9455,7 @@ footprintparticle@|脚印粒子 (FootprintParticle)
tabbychat
@leafculling
unimixins@
-nei-lotr-fa|魔戒:第一纪元NEI插件 (NEI LOTR FA)
+nei-lotr-fa|魔戒:第一纪元 NEI 插件 (NEI LOTR FA)
nogravity-mod@nogravity|零重力 (NoGravity)
milk-of-magnesium
@slumber
@@ -9530,7 +9530,7 @@ integrated-stronghold@
grass-overhaul@
@purpurclient
alkimicraft-part-i-project-oasis
-
+wild-caves-3|洞穴装饰3 (WildCaves 3)
modest-mining
vanilla-compats
vending-block|自动售货机 (Vending block)¨vending-block-1-5|自动售货机 (Vending block)
@@ -9542,11 +9542,11 @@ opotato|哦,土豆!*
contingameime@|游戏内输入法续更版 / 游戏内输入法 Forge 重置版 (ContingameIME / IngameIME-Forge / IngameIME-Forged)
ccastroadds
aerial-hell@
-swashbucklers
+swashbucklers@
style
eatable-livestock
-hunger-strike|绝食*
-extrabuttons|更多按钮 (ExtraButtons)
+hunger-strike@|绝食*
+extrabuttons@extra-buttons|更多按钮 (ExtraButtons)
big-doors
blockshot
jump-over-fences-forge@jump-over-fences
@@ -9575,15 +9575,15 @@ geore-nouveau
crimsonsteves-more-mobs
the-ulterlands
mt-lotr
-asjcore
+asjcore@
bane-tooltips-mod|克星信息显示*
lava-crystal
literal-sky-block
@literal-sky-block-fabric
aniskin
-mob-stacker
-mob-stacker-integration
-mob-stacker-farming
+mob-stacker@
+mob-stacker-integration@
+mob-stacker-farming@
lightspeedmod@lightspeed
coffee-delight@|咖啡乐事*
simple-sculk-dimension-deeper-dark
@@ -9611,14 +9611,14 @@ durts-nether-dungeons@nether-dungeons
health-and-hunger-tweaks
@create-track-map
improved-hoes
-orehider
+orehider@
full-grown-crop-marker@
luggage|行李箱*
slime-breeder-advanced
-ahznbs-naruto-mod
+ahznbs-naruto-mod@ahznbs-shinobicraft
delightful-mining
@beancoin
@@ -9650,7 +9650,7 @@ bundled-delight@
@ijo-pona-poki
sk1llful@|身怀绝技 (Skillful)
-salt
+salt@
monobank
sooty-chimneys
areas-undefined
@@ -9717,13 +9717,13 @@ itemupgrader-core
itemupgrader
screenfx@
dense-ores-abundant@dense-ores
-tinkers-thinking@|匠心独运 (Tinkers' Thinking)
+tinkers-thinking@|工匠巧思 (Tinkers' Thinking)
simple-musket@
cash-ores@
stacked-dimensions
@unlimited-maxhealth
floral-enchantment¨floral-enchantment-fabric
-newworld
+newworld@new_world
@more-stats
notifmod@
wallfence
@@ -9797,12 +9797,12 @@ compressor@
hexal@|咒法拓展*
-create-encased@
+create-encased@|机械动力:封装工艺*
@treeplacer
death-knell@
moreiotas@
numismatic-overhaul@¨numismatic-overhaul-reforged
-@hexbound|咒法组构学 (Hexbound)
+hexbound-fork@|咒法组构学 (Hexbound)¨@hexbound|咒法组构学 (Hexbound) - 原-已失效
vanity-druid-pack
vanity-farcrs-colossal-weapons
debris-chunk-raw-netherite-scrap@raw-netherite-scrap|残骸碎块 (Debris Chunk)
@@ -9812,7 +9812,7 @@ biome-music|生物群系音乐*
agers-earthquake-generator
vanity-black-gold
vanity-bone-pack
-
+what-that-wolfhouse|那是什么?仓库管理员! (What That? Wolfhouse!)
click-for-hot-chicks@
vanity-molten-pack
vanity-spartan-pack
@@ -9841,7 +9841,7 @@ variant-crafting-tables
endless-biomes@
chatimg|图片聊天 (chatImg)
-throwable-explosives
+throwable-explosives@
@skillmmo
archeon@
chattoggle@
@@ -9850,7 +9850,7 @@ enchancement@
useful-spyglass@
-befriendmobs-days-with-monster-girls|友好化生物:与怪物娘的日常 (BefriendMobs: Days with Monster Girls)
+nff-girls
launchgui
villager-armor-layer@|村民盔甲显示*
wasabi@
@@ -9875,7 +9875,7 @@ l2weaponry@|莱特兰-武器 (L2 Weaponry)
@rhodium|铑 (Rhodium)
zoom-o-matic@
@broken-tool
-@fishing-ruler
+fishing-ruler@
command-relay@
beasts-bosses
@@ -9887,17 +9887,17 @@ floating
@blast-travel
skygrid@
disable-new-world-creation-screen-forge@disable-new-create-world-menu-forge¨disable-new-world-creation-screen-fabric@disable-new-create-world-menu-fabric
-packagedmekemicals@|封包通用机械 (PackagedMekemicals)
+packagedmekemicals@|封包通用化学品 (PackagedMekemicals)
packagedastral@|封包星辉魔法 (PackagedAstral)
-mekanism-moresolarpanels@|通用机械-更多太阳能板 (Mekanism-MoreSolarPanels)
+mekanism-advancedgenerators@mekanism-moresolarpanels|通用机械-更多太阳能板 (Mekanism Advanced Generators / Mekanism-MoreSolarPanels)
paperandquill
-sneaky-curses-forge¨sneaky-curses-fabric
+sneaky-curses-forge@sneaky-curses¨sneaky-curses-fabric
magnum-torch-forge@magnum-torch|卓越火炬 (Magnum Torch)¨magnum-torch-fabric|卓越火炬 (Magnum Torch)
universal-bone-meal-forge@universal-bone-meal|通用骨粉 (Universal Bone Meal)¨universal-bone-meal-fabric|通用骨粉 (Universal Bone Meal)
@bottle-of-gender
-leave-my-bars-alone@
+leave-my-bars-alone@|改进的骑乘HUD*
mc-mods-enchantments-for-trade
i-need-block
spiky-spikes@
@@ -9922,20 +9922,20 @@ locksmith
mystical-machinery|神秘机械*
life-steal-enchantment
more-wandering-trades|更多流浪商人交易*
-mineralas@
+mineralas|Mineralas - 已失效¨@mineralas
ecospherical-expansion@
ecospherical-depths
arrhys-simple-currency
-better-wandering-trader
+better-wandering-trader¨@better-wandering-traders|Prelaw's Better Wandering Trader - 已失效
creative-wireless-transmitter|创造无线发送器*
-cozy-home
+cozy-home@
inventory-crafter
maxs-armory|Max 的武器库 (Max's Armory)
nefs-farm-decoration
create-armory@
dangerous-wasps
-createfabricreibugfix@|机械动力Fabric版REI配方显示修复 (Create&REIBugFix/CreateFabric&REIBugFix)
+createfabricreibugfix@|机械动力 Fabric 版 REI 配方显示修复 (Create&REIBugFix / CreateFabric&REIBugFix)
seasontweaks
@@ -9971,10 +9971,10 @@ floating-block@floating-cube|浮空立方体 (Floating Cube)
curious-lanterns
river-redux@
restrictions
-create-modern-train-parts
+create-modern-train-parts@modern-train-parts
targetato
-
+messagetweaker
tskimi-seirans-fruit-wine@tskimiseirans-fruit-wine|月见清兰的果酒 (Tskimi Seiran's Fruit Wine)
@@ -9982,12 +9982,12 @@ graduated-cylinders@|刻度量筒*
fps-monitor
athena@athena-ctm
-calm-mornings
+calm-mornings@
fog-looks-good-now@
guarding@
cats-expanded@
modular-bosses
-experience-obelisk
+experience-obelisk@
here-be-no-dragons
gnumus-settlement
fading-night-vision
@@ -10014,7 +10014,7 @@ boundless-flux
@axolotlclient
energy-nodes
-fog-looks-good-now-fabric@fog
+fog@
thirst-was-taken@|口渴*
unlimited-fluidity
@@ -10030,9 +10030,9 @@ origins-vampire|起源:吸血鬼 (Origins: Vampire)
bad-to-the-bone¨bad-to-the-bone-fabric
workers@villager-workers
-sculk-horde|幽匿部落*
+sculk-horde@|幽匿部落*
stratospherical-expansion
-candlelight-lets-do-a-candlelight-dinner@lets-do-candlelight|烛火晚宴 ([Let's Do] Candlelight)
+lets-do-candlelight-farm-charm-compat@lets-do-candlelight-farmcharm-compat|烛火晚宴 ([Let's Do] Candlelight) - [作者推荐]2.0.0+¨candlelight-lets-do-a-candlelight-dinner@lets-do-candlelight|烛火晚宴 ([Let's Do] Candlelight) - Legacy版,作者已停止维护
let-me-play-iris@
dimensional-depths
create-molten-vents@
@@ -10042,7 +10042,7 @@ world-border-expander
htmltech
extra-tan@
diamond-economy@|钻石经济*
-texels-paintings
+texels-paintings@
tinkers-expansion
revelationary@
fullstack-watchdog@
@@ -10061,7 +10061,7 @@ actions-of-stamina
disenchanting-book
stackdeobf@
-portfolio@
+portfolio|Portfolio - 仅版¨@portfolio
@nvidium
merequester@
invade|入侵*
@@ -10072,7 +10072,7 @@ arcane-world
tiled-cauldron
manyideas-christmas@
overflowing-bars@
-
+kokoalinux
medievalweapons@
completionists-index@
stoneworks@
@@ -10096,7 +10096,7 @@ name-tag-tweaks@|命名牌调整*
funky-yoyo@funkyyoyo
pierce-arrow
tool-sharpening
-
+@ktfruaddon|群峦救援-非官方版扩展 (kTFRUAddon)
pigsteel@pigsteel-fabric¨pigsteel-forge@
@@ -10108,7 +10108,7 @@ hopo-better-mineshaft
reforgium@
spoorn-weapon-attributes@
-fabrc-legacy-fixes
+fabrc-legacy-fixes@legacy-fabric-fixes
@@ -10117,7 +10117,7 @@ moa-decor-bath
stealing-the-villagers-forge@¨stealing-the-villagers-fabric@
spoorn-armorattributes@spoorn-armor-attributes
connectedness@
-epic-samurais@|史诗武士 (Epic Samurai's)¨epic-samurais-fabric@epic-samruais-fabric|史诗武士 (Epic Samurai's)
+epic-samurais@|史诗武士 (Epic Samurai's / Samurai Dynasty)¨epic-samurais-fabric@epic-samruais-fabric|史诗武士 (Epic Samurai's / Samurai Dynasty)
sandwiches-n-more
enchantments-plus-fabric@enchantments-plus
purpeille@
@@ -10132,7 +10132,7 @@ forgotten-delight
enhancedlootbags
recipe-reshaper@
hellish-blahaj@|布罗艾 Hellish 版 (Hellish Blåhaj)
-amber-jade|琥珀 (Amber)
+amber-jade@|琥珀 (Amber)
mafglib@
@fast-minecart
ae2_tools
@@ -10141,7 +10141,7 @@ dont-drop-it@
nicemod-new-blocks@nicemod
forgematica@|全息蓝图 (Forgematica / Litematica-Forge)
framland
-@blahaj-legacy|布罗艾:旧版 (Blåhaj:Legacy)
+blahaj-legacy|布罗艾:旧版 (Blåhaj:Legacy) - 无法访问¨@blahaj-legacy|布罗艾:旧版 (Blåhaj:Legacy)
deep-aether@|深入天境*
the-forgotten-dimensions@
@the-sink|水槽 (The Sink)
@@ -10172,11 +10172,11 @@ blue-archive-halos
@mana-visualizer
mhcraft
-irons-spells-n-spellbooks@
+irons-spells-n-spellbooks@|Iron的法术与魔法书 (Iron's Spells 'n Spellbooks)
infernal-enchantments
reimagined-world-menu@
origins-assassin
-peculia@
+peculia¨@peculia|Peculia - 已失效
koratio
promans-origins
mermaid-mythical-origins
@@ -10252,8 +10252,8 @@ jer-ore-integration
adorkable-dogs-pocky|DK狗的Pocky (Adorkable dog's Pocky)
realistic-explosion-physics@
-nitwitification
-items-to-mobs
+nitwitification@
+mutated-items@
homespun@
primitive-start@
@@ -10272,7 +10272,7 @@ seven-days-to-fix-parasitus
dtm-integrations
@hdskins
bushier-flowers@
-ender-space
+create-gravity
cataclysm-boss-bar@|灾变Boss血条显示*
unusual-gifts
building-blocks-overhaul-forge
@@ -10292,7 +10292,7 @@ tool-upgrades@|工具升级*
visiblebarriers@
aether-gravitation@|天境之引*
@sublanguage|首选语言 (Sublanguage)
-befriendmobs-api|友好化生物框架 (BefriendMobs Framework)
+nff-services
better-potion-visuals@
modern-warfare-cubed@|现代战争方块化*
extraordinary-extra-totems
@@ -10326,7 +10326,7 @@ lands-of-icaria
wta@
@gtnh-no-nerf
pickable-villagers@
-pickable-piglins
+pickable-piglins@
book-of-blocks-fabric@mc-book-of-blocks
create-blue-skies-compat
dawncraft-mobs
@@ -10336,7 +10336,7 @@ netherracked
the-toolkits@
immersive-structures-ii-nether-edition
-mossier-deepslate
+mossier-deepslate@
celestial@
quest-giver
@@ -10351,7 +10351,7 @@ barrels-2012
tfc-sample-drill
tfc-rosia
aether-additions
-custom-portal-api-forge@customportalapi
+custom-portal-api-forge@customportalapi|自定义传送门 API Forge 版 (Custom Portal API [Forge])
jaopca-extras
enclosure@
@@ -10380,23 +10380,23 @@ tinkering-with-embers
mob-despawn-timers
tfc-towerheat
tfc-toohotaroundhere
-flower-patch
-quickcure¨quickcure-fabric
+flower-patch@
+quickcure@quick-cure¨quickcure-fabric@quick-cure-fabric
spoiled@
-@entity-specific-damage-invulnerability
+entity-specific-damage-invulnerability@
hungryanimals
opolis-utilities
better-breeding
frostiful@|霜冻*
camo-creepers@
-@coupons
+coupons@
simple-bracers
baby-delight
strategic-enchanting
forged-in-fire
lumy-skin-patch
@le0s-spoons
-redstone-clock@
+redstone-clock|Redstone Clock - 已失效¨@redstone-clock
anti-mob-griefing
nbtedit-reborn@|游戏内NBT编辑器重制版 (In-game NBTEdit Reborn)
@@ -10415,8 +10415,8 @@ openblocks-trophies
lootbag-patches
-stargate-journey@sgjourney
-xaero-arrow-fix@xaeroarrowfix
+stargate-journey@sgjourney|星门之旅*
+xaero-arrow-fix|Xaero Arrow Fix - 无法¨@xaeroarrowfix
ornamental
xaero-zoom-out@xaero-zoomout
minecard
@@ -10472,7 +10472,7 @@ continuumation
ai-art-deco-1|AI装饰一号 (AI Art Deco 1)
@occlusion-culling-fix-sodium
easy-dungeons|简易地牢*
-
+the-morbid-harvester-reborn-reborn|变态的凋灵收割者 (The Morbid Reborn Reborn)
ars-armiger
bettergolem
@end-poem-extension|终末之诗扩展补丁 (End Poem Extension)
@@ -10499,10 +10499,10 @@ arboria
mekanism-evolution
chiseledadditions
-@tinkers_ingenuity|工匠匠心 (Tinkers Ingenuity)
+
gregtech-4|格雷科技4移植版 (GregTech 4 Rewritten)
lacrimis
-productive-villagers
+productive-villagers¨@productive-villagers|Productive Villagers - 已失效
canned-goods@
itemchat-fabric@itemchat
@@ -10515,24 +10515,24 @@ observed@
ex-sartagine-requiem@
giant-pacman-1-16-5
item-banning-fabric
-erroring-entity-remover
+erroring-entity-remover@
mclo-gs
pink-expansion
polluted-earth-reborn
kitsus-forgecraft
stat-check
-hit-indication@
+hit-indication|Hit Indication - 已失效¨@hit-indication
multipart-machines-farming
tru-first-person-mod
reduced-music-delay
agers-giant-junkyard-dimension
-jsmacros
-great-big-world@
+jsmacros@
+great-big-world@¨@great-big-world-old
armored-mobs
astrological@
-end-expansion-the-lamented-islands
+end-expansion-the-lamented-islands@
@@ -10562,7 +10562,7 @@ gemspark@
otakomod
wall-jumping
new-life
-reeves-furniture@reevess-furniture
+reeves-furniture@furnitury
@undying|不朽 (Undying)
creatania
boat-rocked
@@ -10586,7 +10586,7 @@ toroquest-revamped
tis-create
tektopia-trader
-@worldtime
+worldtime@
beasts
alphabet-speedrun@|自定义速通挑战 (αβspeedrun)
@symlink-check|符号链接拦截 (Symlink Check)
@@ -10595,7 +10595,7 @@ tis-stringify
dismount-entity@¨dismount-entity-fabric
dyeable-compasses@|染色指南针*
supercompression|超级压缩 (Super Compression)
-better-hungteen-s-plants-vs-zombies|更好的HTPVZ (Better HungTeen's Plants vs. Zombies)
+better-hungteen-s-plants-vs-zombies|更好的 HTPVZ (Better HungTeen's Plants vs. Zombies)
piglinsafety|猪灵:多喝岩浆 (PiglinSafety)
gatewayer
bocchud@|孤独HUD (BoccHUD / MiniHUD-Forge)
@@ -10628,7 +10628,7 @@ netherite-items
aim-plus
reckless-drilling
tick-stasis@
-@azure-paxels
+azure-paxels@
no-angled-brackets
alien-apocalypse
@@ -10643,7 +10643,7 @@ passive-endermen@|被动型末影人*
elemental-aspects
dune
-lithereal
+lithereal@
mobs-genus
ore-extension
extradelight@|额外乐事 (ExtraDelight)
@@ -10685,8 +10685,8 @@ archeology-plus@
mobification
trimseffects@
tough-glass@
-celestisynth|天神剑技*
-golems-plus-plus
+celestisynth@|天神剑技*
+golems-plus-plus@
more-axolotls@more-axolotl
perfect-accuracy@
@perfect-accuracy-forge
@@ -10752,7 +10752,7 @@ bagus-lib@
@better-doors
server-pack-unlocker-forge@server-pack-unlocker¨server-pack-unlocker
-x-enchant
+x-enchant@x-enchantment
@blahaj-remake
pamps-simple-edition
@@ -10782,10 +10782,10 @@ hellions-sniffer@hellions-sniffer+
hybrid-flowers|杂交花*
-methane@
+methane|Methane - 已失效¨@methane
mushroom-villagers
@litematica-server-paster
-s33r-butterfly-paintings
+s33r-butterfly-paintings@
@more-statistics
villager-see-villager-do@
industrybase|工业基石 (IndustryBase)
@@ -10808,7 +10808,7 @@ haste-enchantment
workshop-for-handsome-adventurer
desert-upgrade
ocean-recovery
-more-armor-trims
+more-armor-trims|更多盔甲纹饰*
mc-mods-the-trims@allthetrims
@bamboo-sign-editor|告示牌编辑器 (Bamboo Sign Editor)
revival-orb@
@@ -10916,10 +10916,10 @@ modlist
panorama-screens@
campfire-spawn-and-tweaks@
ghostz@
-better-totem-of-undying@better-totem-of-undying-forge¨better-totem-of-undying-fabric
-modonomicon@
+better-totem-of-undying@¨better-totem-of-undying-fabric
+modonomicon@|模组秘典*
moreyoyos
-effortless-fabric
+
onetwenty-1-20-backport@trailstales-backport
xtra-arrows@
secrets-of-forging-revelations-a-tetra-addon
@@ -10930,7 +10930,7 @@ artemis-flower-pots
sculky-bits
minecolonies-for-computercraft
flame-sweeping-potato-edition|烈焰横扫:土豆版*
-skinned-carts
+skinned-carts@
quick-effect@quickeffect
block-log
@@ -10943,11 +10943,11 @@ ghostjump|死者为大 (GhostJump)
@notsoessential
worldsalads-opulence
-@wyrms
-pearfection
+wyrms-of-nyrus@wyrms
+pearfection@
enderchests@¨enderchests-fabric
dragon-rider
-create-utilities-fabric@create-utilities|机械动力:实用物品 (Create Utilities)
+createutilities@create-utilities|机械动力:实用物品 (Create Utilities)
creeperhost-presents-chickens
inet@
@@ -10959,7 +10959,7 @@ fruit-delight@|果品乐事*
@idletweaks
tagmod
passive-skill-tree|被动技能树*
-villagertradefix
+villagertradefix@
bullet-chat|弹幕聊天*
@@ -10982,7 +10982,7 @@ lively-forests
dynamic-fullbright@
@modularwarfare-shining|模块化武装:烨熠 (ModularWarfare-Shining)
-@dcch
+dont-clear-chat-history@dcch
difficulty-locker
virus-disease-mod
this-boat-is-mine@|这是我的船! (This boat is MINE!)
@@ -10994,7 +10994,7 @@ bobby-forge
matter-transporter
tall-flower-pots@
-too-fast
+too-fast@
fast-paintings@
personality-rp@personality
@@ -11027,7 +11027,7 @@ renewable-spore-blossoms
-mobs-info@mobsinfo
+mobs-info@mobsinfo|生物信息*
ngrok-api@
@@ -11039,7 +11039,7 @@ dynamic-fps-reforged@
utility-belt@
cultivatecraft
rpgloot-patched
-fluidlogged
+fluidlogged@
create-foundry
cobblemon-fight-or-flight@
@@ -11048,7 +11048,7 @@ dead-guys-hallucination-nightmare
simple-hot-air-balloons
-@toomuchrain
+toomuchrain@
thirst-was-remade|口渴-重制*¨@thirst-was-remade|口渴-重制 (Thirst Was Remade) - 已失效
ies-tweaks-fabric@
brown-mooshrooms
@@ -11070,7 +11070,7 @@ incapacitated@
@mcopper|梧桐物语 (MCopper)
no-ranged-knockback
industrial-agriculture
-macaws-bridges-sajevius|Macaw的桥梁:Sajevius 附属 (Macaw's Bridges - Sajevius)
+macaws-bridges-sajevius|Macaw 的桥梁:Sajevius 附属 (Macaw's Bridges - Sajevius)
@getwebbed
@water-resistance-potion
the-predators
@@ -11098,7 +11098,7 @@ big-fish@
nulltal@
caffeinefilled@
pet-transfer-trade-your-pets@pet-pass-trade-your-pets!
-lets-do-bakery@|馥郁烘焙 (Let's Do: Bakery)
+lets-do-bakery-farm-charm-compat@lets-do-bakery-farmcharm-compat|馥郁烘焙 (Let's Do: Bakery) - [作者推荐]2.0.0+¨lets-do-bakery@|馥郁烘焙 (Let's Do: Bakery) - Legacy版,作者已停止维护
searchables@
dixtas-armory@
ingamestats@
@@ -11146,7 +11146,7 @@ curios-compat
@serverpingerfixer
@world-days
such-a-delight
-create-compressed
+create-compressed@
emoji-type@
soul-bound-enchantment@soulbound-enchantment|灵魂绑定 (SoulBound)
tower-defense-units
@@ -11165,7 +11165,7 @@ noraidtotems@
bagus-mob@
chat-twix@
shulker-meat
-accurate-block-placement-reborn
+accurate-block-placement-reborn@
favorite-items
@smart-villagers-follow-emeralds|见钱眼开 (Smart Villagers Follow Emeralds)
@@ -11198,7 +11198,7 @@ armor-status-hud-renewed|耐久信息显示:重生 (Armor Status HUD Renewed)
boblib
placeable-mobs@placeble-mobs
potion-blender@
-t6-auto-attack-mod@t6-auto-attack
+t6-auto-attack-mod¨@t6-auto-attack|T6's Auto Attack Mod - 已失效
forceasciifont-backport
vmtranslationupdate@vmupdate|VM汉化更新 (VM Translation Update)
trample-no-more@
@@ -11207,8 +11207,8 @@ jeremyseq-damage-indicator@jeremyseqs-damage-indicator
digs-dnd-origins
superior-flat
-fungal-infection-spore@fungal-infectionspore
-@easy-tweak
+fungal-infection-spore@fungal-infectionspore|真菌感染:孢子 (Fungal Infection:Spore)
+easy-tweak@
netherless-quartz
@maple-api
@@ -11235,7 +11235,7 @@ themangonewadvent
@worthy-food
-better-nether-continuation|更好的下界延续 (BetterNether Continuing)
+better-nether-continuation@betternether-continuation|更好的下界延续 (BetterNether Continuing)
stygian-end-continuation|末地:生物群系扩展延续*
skill-slots@|技能槽*¨skill-slots-fabric|技能槽 (Skill Slots)
novam-terram-continuation
@@ -11274,7 +11274,7 @@ recipe-book-delight@|配方书乐事*
chest-hoppers|箱子漏斗*
@gravy-delight
ubes-delight@|香芋乐事 (Ube's Delight)
-torcherino-unofficial
+torcherino-unofficial|加速火把非官方版 (Torcherino(Unofficial))
more-bows-and-arrows@
xp-shop
acclaimed-origins@
@@ -11284,15 +11284,15 @@ simple-achievements
achievement-books|成就书*
villager-enchanter|村民附魔师 (VillagerEnchanter)
invocore-utility-mod
-saljus-quill
+saljus-quill@
derelict
@charged-charms
custom-villagers|自定义村民*
-turtlematic
+turtlematic@
remove-base-origins
@cobblemon-delights|方块宝可梦乐事 (Cobblemon Delights)
-redirectionor@redirector|异引定址 (Redirector)¨redirectionor-retro|异引定址 (Redirector)
+redirector@|异引定址*¨redirectionor-retro|异引定址 (Redirector)
villager-mantle-fix|村民帽子修复*
rechiseled-create@
xaeroplus@|Xaero的地图增强 (XaeroPlus)
@@ -11301,10 +11301,10 @@ thirdlife-rts@
real-peaceful-mode@|真正的和平模式*
niftyblocks
elfs-dark-dimension
-xaeros-minimap-world-map-waystones-compability
+xaeros-minimap-world-map-waystones-compability@xaeros-minimap-world-map-waystones-compatibility-forge
jagms-kiwis@
@masaadditions
-@improved-trident-enchantments
+improved-trident@
mystic-bows
forgiving-world
@skillcloaks
@@ -11335,7 +11335,7 @@ tac-craft-tactical-armor-pack
@flexible-arms|灵巧双手 (Flexible Arms)
boat-tweaks|船只调整*
new-effect-descriptions@effect-descriptions
-@diagonal-windows
+diagonal-windows@
illager-invasion@|灾厄侵扰*
@@ -11349,10 +11349,10 @@ minium-stone@
@fastrtp
tech-decorators@|科技装饰商*
-dynamic-trees-ars-nouveau|动态的树:元素魔艺附属 (Dynamic Trees - Ars Nouveau)
+dynamic-trees-ars-nouveau|动态的树:新生魔艺附属 (Dynamic Trees - Ars Nouveau)
@kazzmons-magecraft
-@metal-bundles
+metal-bundles@
tidal-towns|潮汐小镇*
@@ -11388,11 +11388,11 @@ permanent-sponges@
hang-glider@
@auto-twerk
block-beams@
-adorabuild-structures
+adorabuild-structures@
moogs-end-structures@mes-moogs-end-structures
@brew-guide
-@oneclickcrafting
+one-click-crafting-fabric@oneclickcrafting
@genshin-ui
fenomena-structures@phenomena-structures
first-join-message@
@@ -11416,7 +11416,7 @@ crabbers-delight@|蟹农乐事 (Crabber's Delight)
slot-cycler
resource-pack-overrides@
respawning-animals@|动物重生*
-arcane-lanterns
+arcane-lanterns@
crashma@
@@ -11445,7 +11445,7 @@ dual-swords@
organizable-play-screens@
labelling-containers@
inventory-pause-forge@
-ready-player-fun
+ready-player-fun@
condensed-creative@
leaves-us-in-peace@
yellow-snow@
@@ -11462,7 +11462,7 @@ more-sheep-wools
maturi-delight@|祭典乐事*
forgified-fabric-api@
-artisanat
+artisanat@
devmode
better-trims@bettertrims
@doggo-mod-overhauled
@@ -11500,7 +11500,7 @@ silence-mobs@
utilitix@
tiny-coal@
-
+replaymod-for-forge-reborn
flib@
copper-revisioned@
@@ -11509,7 +11509,7 @@ fine-tuned-calibration@finetunedcalibration
third-person-maps@
carpet-minitweaks@minitweaks
-time-in-a-bottle-forge
+time-in-a-bottle-forge@time-in-a-bottle-universal
seared-ladder-backport@seared-ladder-(backport)
wall-jumped@
red-core@
@@ -11547,12 +11547,12 @@ zombiegame|僵尸游戏 (Zombie Game)
effect-overhaul
casualness-delight@|随意乐事*
-eidolon-rebrewed
+eidolon-rebrewed@
fruity-random-teleport|紫颂果味随机传送*
pipe-master-2000|管道专家 2000 (Pipe Master 2000 / Pipe Googles)
command-macros@|命令宏*
@shwetro|上海地铁装饰组件 (The Shanghai Wetro Mod)
-yuushya-modelling|方块建模*
+yuushya-modelling@|方块建模*
tactical-aid@|战术医疗*
@clear-enchanting
fishing-bobber-detector
@@ -11597,7 +11597,7 @@ cozy-foods-milk-tea
opolis-miners@
netherite-but-cheaper
@fabric-hammers
-items-displayed-fabric@items-displayed¨items-displayed-forge@
+items-displayed-fabric@items-displayed|物品陈列 (Items Displayed)¨items-displayed-forge@|物品陈列 (Items Displayed)
wood-converter
hammer-mod
@@ -11612,9 +11612,9 @@ niftycarts@
wither-dimension-whitelist
@hexlink
-usefulfood-reborn@
+usefulfood-reborn@|有用的食物 重生 (UsefulFood Reborn)¨usefulfood-community|有用的食物 重生 (UsefulFood Reborn)
ancient-warfare-2-tweaked@
-cuffed@
+cuffed@|监禁*
rockn-roller@rocknroller
bodies-bodies@
potionparticlepack@|药水粒子包 (Potion Particle Pack)
@@ -11636,7 +11636,7 @@ cultivationcraft|异道修仙 (CultivationCraft)
@carpet-las-addition
@whoami|我是谁? (Who am i?)
-@verticality
+verticality-hotbar@verticality
the-slender-man
automodpack@
bella-quilt@bella
@@ -11660,14 +11660,14 @@ roots-classic-fabric
justenoughbreeding@
simple-drills
peculiarpieces@
-loot-stages|战利品阶段*
+loot-stages@|战利品阶段*
player-stages|玩家阶段*
generation-stages|生成阶段*
ars-oscura@|黑暗魔艺*
bobby-reforged
shut-up-gl-error@
no-spider@
-terracart-reloaded
+terracart-reloaded@
dme
fruit-stack@|果栈丰萦*
gregtech-drawers
@@ -11683,9 +11683,9 @@ angel-block-renewed@
da-bomb@
quickstack
@bhs|黑洞储存 (Black Hole Storage)
-collectors-album
+collectors-album@
head-name-fix@headfix
-hostile-delight|宿敌乐事 (Unusual Delight*)
+hostile-delight@unusualdelight|异趣乐事/宿敌乐事 (Unusual Delight*)
vivecraft-compat@|ViveCraft兼容 (ViveCraft Compat)
wildlife-expanded-skunk@
keepheadnames@
@@ -11710,7 +11710,7 @@ trenzalore
-creo-lib¨@creo-lib|Creo Lib - 已失效
+creo-lib@creo-api¨@creo-lib|Creo Lib - 已失效
highlight@
lost-libraries
@plant-pearl
@@ -11724,7 +11724,7 @@ invisible-frames
ultimatebackportmod
-
+true-ending
@sorted-enchants|附魔排序 (Sorted Enchants)
constructs-armory-1-18-2-port@construct-armory-updated
@@ -11736,7 +11736,7 @@ well-behaved-mobs
ancient-realms
-
+@remove-terralith-intro-message
gambling-style
animated-recipe-book
@@ -11754,7 +11754,7 @@ dangerous-oceans
@featherfallflying
@lidar
anvil-never-too-expensive
-@crossbow
+@crossbow|弩 (Crossbow)
@place-item
controlify@
@@ -11783,8 +11783,8 @@ totally-enough-pain@
custom-village|村民调教*
animaticareforged
shear-cows
-better-fps
-@afkfish
+fps-boost¨better-fps|FPS Boost - 已失效
+afkfish@
@rend
more-trails-more-tales@
@@ -11794,7 +11794,7 @@ botany-ores@
@cauldron-fix|炼药锅修复 (Cauldron Fix)
foods-plants-cooking-with-mindthemoods
@customgui|自定义 GUI (Custom GUI)
-keebszs-battletowers-fabric
+keebszs-battletowers-fabric@keebszs-battletowers
as-a-creeper|×的和你爆了 (As a creeper)
smarterwanderers
@@ -11826,7 +11826,7 @@ the-microworld
vionta
hammer-animations
-blueflame
+blueflame@
nocubes-craftable-music-discs
enderlicious
quarry-reborn
@@ -11839,19 +11839,19 @@ weaponized-baseball
epic-paragliders-but-sekiro|史诗狼之伞*
visualores@
d-furnace
-flickerfix|闪烁修复 (FlickerFix)
+flickerfix@|闪烁修复 (FlickerFix)
@spongesucc
@phosphor-legacy
random-crits
simple-enchantments
-simple-weapons-for-better-combat
+simple-weapons-for-better-combat@
wither-drops-netherite-templates
@spineless
immersive-melodies@
theobsidianboat
dragon-survival-compatibility@|龙之生存:兼容*
bettercookedaxolotls
-stateobserver
+stateobserver@
mighty-mail¨mighty-mail-fabric
nature-architect-plants-and-decorations
better-berries-redux
@@ -11878,7 +11878,7 @@ on1chest@|唯一箱子 (Only Need One Chest)
landmines@|地雷*
packed-up-backpacks@
-valhelsia-furniture
+valhelsia-furniture@
dimension-remote
bring-decay@
@@ -11904,7 +11904,7 @@ more-creepers|更多苦力怕*
cryptic-registry@
level-text-fix
binnies-mods-patched@
-cyanide-forge|氰化物 (Cyanide)¨cyanide-fabric|氰化物 (Cyanide)
+cyanide@|氰化物*¨cyanide-fabric|氰化物 (Cyanide)
@particlesenhanced
@atum
@worldpreview
@@ -11934,16 +11934,16 @@ prehistoric-nature-cambrian-dimension
@mushroom-mod
better-end-sky¨better-end-sky-fabric
valkyrie@
-soaring-structures-2
+soaring-structures-2|Soaring Structures 2 - 完整版¨soaring-structures-2-clean-version-fabric|Soaring Structures 2 - 轻量版
prehistoric-nature-ordovician-dimension
fadeless@
-create-tweaked-controllers
+create-tweaked-controllers@
@auto-elytra
villager-names@villager-names-serilum
bedrockwaters@
@antishulkerdupe
-hourai-elixir
+hourai-elixir@
convivium|会饮*
prehistoric-nature-silurian-dimension
grass-seeds@
@@ -11957,17 +11957,17 @@ lunar@
cosmetics@cosmetica
@abi
-interiors@
+interiors@|机械动力:内饰 (Create: Interiors)
souls
real-seasons
-eternal-winter
+eternal-winter@
move-boats@
omnom@
forest-x-reborn@
exlines-bark-carpets@bark-carpets
exlines-more-carpets
ne|猫艺:附魔 (NekoEnchantment)
-unearthed-journey|出土之旅*
+unearthed-journey@|出土之旅*
prehistoric-nature-devonian-dimension
mwtis-stone-expansion@stone-expansion
smooth-entity-light
@@ -12006,7 +12006,7 @@ the-legend-of-the-mudman
mutagenesis
nether-update-backport
l2hostility@|莱特兰-恶意 (L2Hostility)
-packagedthaumic@|封包神秘 (PackagedThaumic)
+packagedthaumic@|封包神秘时代 (PackagedThaumic)
cat-eyes-night-vision-toggle-mod
vtdownloader@
smithing-mod
@@ -12017,7 +12017,7 @@ tin-ore@tinore
dark-elves-of-underdark
titan-biomes
depth-striders@
-endercage@
+endercage@|末影笼*
mutationcraft@
aftershock@
limitless-concert|永无止境: 音乐会*
@@ -12049,12 +12049,12 @@ styled-chat@
infiniteinvo
playerex-reimagine
startup-timer
-cc-mb|纸带八音盒
+cc-mb@|纸带八音盒
oryls-adventures
elden-craft
ruok@|钌 (RuOK)
deep-dark-regrowth
-textrues-embeddium-options@|TexTrue的Embeddium视频界面 (TexTrue's Embeddium Options)
+textrues-embeddium-options@|TexTrue 的 Embeddium 视频界面 (TexTrue's Embeddium Options)
silent-mine
headhunter-mod@shadows-formidable-foes-the-headhunter
yungs-better-jungle-temples@|YUNG的丛林神庙优化 (YUNG's Better Jungle Temples)¨yungs-better-jungle-temples-fabric|YUNG的丛林神庙优化 (YUNG's Better Jungle Temples)¨yungs-better-jungle-temples-neoforge|YUNG的丛林神庙优化 (YUNG's Better Jungle Temples)
@@ -12083,10 +12083,10 @@ better-impaling-fabric
vanadium-mod@vanadium|钒 (Vanadium)
loginar-storage@
@jewelry
-@elytra-drag
+elytra-drag@
night-vision-flash-be-gone
-@ancient-return
+ancient-return@
mc-modsayed
honkai-star-rail-12|星铁12*
torchflower-relit@
@@ -12096,7 +12096,7 @@ the-digimod-beta|方块数码宝贝 (The Digimod)
ex-deorum@|无中生有:神赐*
entity-detectors@
@horse-armor-stand
-decorative-blocks-fork@decorative-blocks|装饰方块(分支版) (Decorative Blocks (Fork))
+
above-and-below@
freeze-it-and-heat-it@|冷汗:食物冷冻与加热 (Freeze It, and Heat It!)
@@ -12120,7 +12120,7 @@ history-of-middle-earth-legacy|中洲历史:传承 (History of Middle-earth: L
hostile-humans@
classic-and-simple-status-bars@cssb|经典且简易的状态栏 (Classic and simple status bars)
@turtle-charging-station
-your-reputation@¨your-reputation-jade@¨your-reputation-wthit@
+your-reputation@¨your-reputation-wthit@¨your-reputation-jade@
futurediary|未来日记 (FutureDiary)
infiniverse@
use-item-on-block-event@
@@ -12136,7 +12136,7 @@ coreextensions@|核心拓展 (CoreExtensions)
@fast-items
toad-terror
@modern-industrialization-sound-addon
-achievements-optimizer
+achievements-optimizer@
automessage@auto-message
ftb-quests-optimizer@
gfarm@
@@ -12148,7 +12148,7 @@ more-red-computercraft-integration
no-invincibility-frames
moderncreator
-better-questing-unofficial
+better-questing-unofficial|更好的任务非官方版*
p455w0rds-ae2-wireless-terminals-patch
spoiledz@
boss-music-mod
@@ -12156,7 +12156,7 @@ yippee
friends-forever|永恒的伙伴*
de-extinction-mod
custom-world-generation@|自定义世界生成*
-
+cataclysm-tweaks
euphoria-patches@
poke-ball|精灵球 (pokeballhv)
aquamirae-mod-music@aquamirae-mod-extra-music
@@ -12169,7 +12169,7 @@ fusion@
machines@
tenms-discs
netherrocks@
-
+invasion-code-red
efw-forgotten-place
@vulkanite-mod
yungs-better-end-island@|YUNG的末地岛屿优化 (YUNG's Better End Island)¨yungs-better-end-island-fabric|YUNG的末地岛屿优化 (YUNG's Better End Island)¨yungs-better-end-island-neoforge|YUNG的末地岛屿优化 (YUNG's Better End Island)
@@ -12190,13 +12190,13 @@ modification-of-critical-hit@|暴击改革*
improved-damage-framework@idf
@shulker+
@biome-tag-villagers
-combat-bash
+combat-bash@
@simpleprivatechest
marbleds-villager-hats@
connector-extras@
akkamaddis-simple-tungsten|Simple Tungsten - 新
-just-enough-beacons-reforged
+just-enough-beacons-reforged@
akkamaddis-additions@akkamaddis-simple-tungsten¨simple-cobalt|Simple Cobalt - 新
akkamaddis-ashenwheat@
@@ -12221,7 +12221,7 @@ akkamaddis-hadite-coal|Hadite Coal - 新
akkamaddis-arsenic-and-lace|Arsenic and Lace - 新
-xiyus-exchanted-book|饩雨的附魔书 (xiyu's Exchanted book)
+xiyus-exchanted-book@xiyus-enchanted-book|饩雨的附魔书 (xiyu's Exchanted book)
la-abuelita
rotted-forge@rotted
rustic-furniture-compat
@@ -12240,7 +12240,7 @@ random-structure-land
random-mobs-land
clean-swing-through-grass
-shrug-it-off-stb-edition
+shrug-it-off-stb-edition@
enchantsmith
snifferent
jei-recipe-history@
@@ -12292,11 +12292,11 @@ super-better-grass
configurable-boss-bars@
nuclearcraft-neoteric@
beastly-beacons
-creeplets@
+creeplets|Creeplets - 已失效¨@creeplets
thermoo@
restapi@rest-api
@advanced-ui-scale
-@cfb
+@umines
health-disease|健康&疾病 (health&disease)
@@ -12308,7 +12308,7 @@ aesthetics@
chinese-festivals@|中国节*
-
+ultramarine@|群青*
@cargo
@@ -12321,7 +12321,7 @@ terraria-calamity-mod|泰拉瑞亚灾厄 (Terraria Calamity)
darkrtp-rtp-random-teleport@darkrtp
cute-bears
additional-fruits@
-bigger-better-end-cities@
+bigger-better-end-cities@|更好的末地城*
@some-peripherals
reden@
sparse-structures@sparsestructures
@@ -12366,7 +12366,7 @@ mobile-beacon-1-18-forge
durabuilt
cavemans
balloon-box
-campfire-resting
+campfire-resting@
fizzle
grimoire-api
@@ -12377,7 +12377,7 @@ creative-experience
more-than-50
underground-jungle@
forge-soul-like-armors-reworked
-@meds-and-herbs
+meds-and-herbs@
soul-like-monsters-epic-fight-addon
souls-like-parry
tcdcommons@
@@ -12425,7 +12425,7 @@ formations-overworld@
formations-nether@
halloween-spirit
glitches-halloween
-@rei-collapsible-entries
+rei-collapsible-entries@
kubevs@
@@ -12477,7 +12477,7 @@ structure-essentials-forge-fabric
tiny-pals@
@amethyst-craft|紫水晶工艺 (Amethyst Craft)
-weapons-of-miracles-epicfight@weapons-of-miracles
+weapons-of-miracles-epicfight@weapons-of-miracles|奇迹武器 (Weapons of Miracles)
@hsrpaths
@@ -12508,7 +12508,7 @@ oculus-particle-fix@
urkaz-moon-tools@
the-midnight-lurker@
realm-rpg-imps-demons
-multiverse
+multiverse@multiverse_mod
strangecreatures
@@ -12528,8 +12528,8 @@ uniquedungeons
@cobbledex
cobblemon-info@
enderman-overhaul@|末影人革新*
-cloak-dagger
-oworld2create|别创建新世界! (0World2Create)
+cloak-dagger@cloak-and-dagger
+oworld2create|别创建新世界! (0World2Create)¨oworld2create-refoxed|别创建新世界! (0World2Create)
dungeons-in-dimensions
infernal-bugz
@cobblemon-gts
@@ -12545,7 +12545,7 @@ beavery@
vemerioraptor
technique-to-kill-thrasher|毙鲨技 (technique to kill thrasher)
-@the-open-sauce-toast-killer
+the-open-sauce-toast-killer@
@chicken-nerf-reforged
regex-search@
better-wake-up-position@|更好的起床位置 (Better Wake-up Position)
@@ -12553,7 +12553,7 @@ railroad-blocks-forge@railroad-blocks¨railroad-blocks
fossilmon@
opack2reload|资源包惰性重载/抑验怠载 (0Pack2Reload)
-@wits
+wits@
@motleys-music-discs
husks-drop-sand@
@@ -12621,7 +12621,7 @@ omniwand@|全能魔杖*
-key-binding-patch|按键绑定补丁*
+key-binding-patch@|按键绑定补丁*
packet-fixer@
new-xenchant@
more-brewable-potions@
@@ -12640,9 +12640,9 @@ eager-beavers@
laendli-transport
witchs-artifacts
the-pumpkin-challenge@
-loot-crates-plus
+loot-crates-plus@loot+
-@fuji|藤 (Fuji)
+fuji@|藤*
legendary-origins@
flow@
lit-it-up@
@@ -12689,8 +12689,8 @@ autofire-bow-enchant
rideableravagers@
passive-searchbar|被动搜索栏 (Passive SearchBar)
-flower-mimics
-vegan-delight|纯素乐事*
+flower-mimics@
+vegan-delight@|纯素乐事*
bloodmooncmsfix-bmcmsfix
evoodooers
giant-overhaul
@@ -12732,7 +12732,7 @@ modular-item-api@¨truly-modular-arsenal@¨truly-modular-archery@¨truly-modular
@actionhunger
trafficcraft@
banilla-hammers@
-everyx-hotpot|一起火锅 (EveryXHotpot)
+everyx-hotpot|一起火锅 (EveryXHotpot) - 他人代传¨@every-x-hotpot|一起火锅 (EveryXHotpot)
@hira-hira
tierability@
soul-collector@
@@ -12748,7 +12748,7 @@ charge-thrower
-jade-addons-ic2classic
+jade-addons-ic2classic@ic2cjadeaddon
sit1@sit!
monnef-core
robs-floating-islands
@@ -12762,7 +12762,7 @@ archers@
inventorylogger-backups
rideablepolarbears@
cobblemon-lucky-blocks
-egg-delight@
+egg-delight@|鸡蛋乐事*
portable-nether¨portable-nether-fabric
@resourceful-block-sounds
@@ -12817,9 +12817,9 @@ medieval-buildings@
@you-thief-remastered-edition
more-slabs-stairs-and-walls@
-
+caves-canyons
dragon-mounts-legacy-continued@|龙骑士重制版延续*
-embeddium-plus@embeddiumplus
+chloride-mod@chloride
aggro-indicator
haveasoltime@
tothestars@
@@ -12828,7 +12828,7 @@ localized-chat@
prominent-create
bad-lightning-no-cookie
equipment-standard@|装备标准 (EquipmentStandard)
-snowly@
+terra-armory@
advanced-enchantments@|进阶附魔*
oaks-nature@
geophilic-reforged
@@ -12853,7 +12853,7 @@ instant-village-building
venus@
nbt@
-separated-leaves@
+separated-leaves@separatedleaves
pangteens-immortal-adventure@|PangTeen的平凡冒险 (PangTeen's Immortal Adventure)
simple-shops@simpleshops|简单商店*
forgiving-border|仁慈的世界边界 (ForgivingBorder)
@@ -12864,7 +12864,7 @@ airdrop-supply|空投供应*
refined-proxy
pickable-pets@
-
+snow-mercy-forge
bouncestyles@
@@ -12876,7 +12876,7 @@ dancing-hoppers@|舞动的漏斗*
anvil-crafting@|铁砧配方*
ender-chested@
@skull-vision
-prefabricated-structures
+prefabricated-structures@
rebirth@rebirth-paintings
amethyst-tools-2|紫水晶工具2 (Amethyst Tools Mod 1.2x)
mob-pets|驯兽师*
@@ -12896,13 +12896,13 @@ diamond-and-emerald-farming-mod
blood-and-gore
nonetherportal@no-nether-portal
necronomicon@
-@showcase-item#all-versions
+showcase-item@
eldritch-end@
quit-or-not
spelled-mobs
let-fish-love
-exposure@
+exposure@|拾光定影*
@fast-portals
pick-up-me-please@pickup-me
bouncy-creepers
@@ -12923,7 +12923,7 @@ destruction-difficulty@destructiond|破坏困难*
villagers-follow-emeralds
dredge@
-create-creating-space
+create-creating-space@creating-space
asmc-performance-patch
mrcrayfishs-gun-mod-unofficial|MrCrayfish的枪:非官方版 (MrCrayfish's Gun Mod: Unofficial)
@fish-tanks
@@ -12933,7 +12933,7 @@ zombies-reworked-forge@zombies-reworked
legendary-monsters@
catontoasts-mobs
combatify@
-go-fish-updated|一起钓鱼吧!(updated) (Go Fish (updated))
+go-fish-updated|一起钓鱼吧!(更新版) (Go Fish (updated))
what-are-they-up-to@
@fumo
@@ -12952,7 +12952,7 @@ grassnotfloating|浮的古原草送别 (GrassNotFloating)
-@thermite
+thermite@
restriction
@collision-fix
@@ -12960,7 +12960,7 @@ dynamic-name-tags@
@profundis
ctlib@ctlibmod
-dependencies
+building-packs
styled-sidebars@
cut-and-colored@
the-fence-unleashed@fency
@@ -13017,7 +13017,7 @@ hammers-and-smithing
tetra-booster-pack@withering-boon-for-tetra
everlasting-skins
-
+decorated-pot-minecart|饰纹陶罐矿车*
legacy-console-sounds@
eye-spy@
sonicraft-demons@
@@ -13055,7 +13055,7 @@ wool-collection@|羊毛装饰方块 (The Wool Collection)
random-mob-sizes@|随机生物模型大小*
unexperienced
-balanced-crates
+balanced-crates@
ducky-periphs@
@norefreshscroll
@@ -13069,12 +13069,12 @@ geotagged-screenshots@
@legacy-optifabric
oneironaut@
-celestial-core|星月核心 (celestial_core)
+celestial-core|星月核心*
colorablefox|可染色狐狸/色绘你狐革 (Colorable Fox)
-
+archeology-api
rpg-parties@
-astikor-carts-tfc
+astikor-carts-tfc@tfc-astikor-carts
nanexs-mobs
no-feather-trample@
embers-extended-life|余烬非官方延续版 (Embers Unofficial Extended Life)
@@ -13084,7 +13084,7 @@ jujutsu-animation
craftable-elytra
francium@francium-optimization|钫*
dragonwingselytra
-universal-grid|通用终端*
+universal-grid@|通用终端*
antique-atlas-reborn
ars-artifice@
@@ -13092,7 +13092,7 @@ draconic-energy
hamsters-plus@hamsters
@you-bandit!
eugenes-lance@
-thorium-reactors@
+thorium-reactors@|钍反应堆*
eugenes-guan-dao@
eugenes-wealthy-plains-village
@@ -13120,7 +13120,7 @@ dontusecommand@|请别用指令 (DontUseCommand)
beans-backpacks@
randomjs@
-@piston-push
+piston-push@
erinium-automation
angelica@
explosiont
@@ -13136,7 +13136,7 @@ provihealth@provis-health-bars
metabolism@|新陈代谢*
adv-js|Advancement JS - 更新较慢¨@advancementjs
teleportcraft@
-frights-delight@|惊吓乐事 (Fright's Delight)
+frights-delight@|惊悚乐事 (Fright's Delight)
travelers-compass@
@artemis-thin-logs
configjs@
@@ -13175,15 +13175,15 @@ pottery-additional-pots@pottery
better-dogs-x-doggy-talents-next
@villagertimetable
towntalk
-cumulus
+cumulus@
epherolib
icterine@
notrample
-industrial-foregoing-souls
+industrial-foregoing-souls@
fuelgoeshere@
initial-dimension
-youkais-homecoming|妖怪们的归家 (Youkai's Homecoming)
-utilitarian
+youkais-homecoming@youkaishomecoming|妖怪们的归家 (Youkai's Homecoming)
+utilitarian@
classic-sword-blocking|剑之格挡*
@flutterandflounder
@@ -13203,7 +13203,7 @@ quickbackupmulti@|多槽位备份 (QuickBackupMulti-Fabric)
slashblade-japanese-addon-pack-reforked|拔刀剑日系附属重置版 (SlashBlade Japanese Addon Pack Reforked)
@create-fabric-fixes
-crate-delight-forge@crate-delight¨crate-delight
+crate-delight-forge@crate-delight|装箱乐事 (Crate Delight)¨crate-delight-fabric|装箱乐事 (Crate Delight)
cultural-creators-fabric-create-and-cultural
shuffle@
probejs-legacy
@@ -13229,9 +13229,9 @@ item-despawn-timer@
@is-this-mod-legit
aylyth@
no-shade@
-dimensional-threads@
+dimensional-threads@dimthreads
jonns-trophies@
-@it-follows!
+it-follows-exclamation@it-follows!
@loggerhead-luminancies
asteorbar@|Asteor的状态栏和血量显示 (AsteorBar)
better-f1-reborn@|更好的F1重制版*
@@ -13241,13 +13241,13 @@ ars-expanded-combat@ec-ars-compat
@fluiwid
revamped-phantoms@revamped_phantoms
defaultserverlist@
-green-delights|绿里乐事*
+green-delights|翠绿乐事*
@modmenu-ornithe
lost-city
digestive
-@inventory-binds
+inventory-binds@
octo-lib@
farmers-plus¨farmers-utils|Farmer's Plus - 旧
@@ -13281,7 +13281,7 @@ tanukidecor
cit-reforged
ultreonlib@
-undergarden-delight
+undergarden-delight@|深暗之园乐事*
medieval-deco@
@@ -13305,12 +13305,12 @@ mxtune
customui|自定义UI (CustomUI)
deadly-monsters-patched
glodium@
-aether-delight
+aether-delight|天境乐事*
dungeonz@
cazfps-axolotls
easy-breeding-tfc
-paperfixes
+paperfixes@
fishing-bot
progression-tweaks
@@ -13323,15 +13323,15 @@ tree-lib
real-camera@
unofficial-mod-name-tooltip-backport|模组名称显示:非官方版 (Mod Name Tooltip Unofficial)
custom-health-bar@
-lotrimprovements
+lotrimprovements|魔戒改进 (LOTR Improvements)
mousekeyinventoryfix@
visible-offhand@|可见副手*
phosphorcrashfix
sound-physics-remixin|籁*
hextended-staves@
-aquaculture-delight
+aquaculture-delight@|水产乐事*
vics-point-blank@
-occultism-kubejs
+occultism-kubejs@
tetra-extras@
anilink
overworld-netherite-ore@
@@ -13342,7 +13342,7 @@ flesh-mod
create-bitterballen@
-glowing-illagers
+glowing-illagers@
heads@
epicdmcbossdante
step@
@@ -13383,15 +13383,15 @@ better-quest-pop-up@
kiln-fabric@kiln
tetra-niche-materials
the-accurate-backrooms
-create-trains-on-trains
+create-trains-on-trains@
@campfire-torches-plus
@armor-rack
flux-networks-fabric@flux-network-fabric
@surface
poof-sounds@
-@crash-pipe
+crash-pipe@
gradeus-epic-fight-addon-soul-like-boss
-zombie-more
+zombie-more@zombies-more
gunscraft|枪械工艺 (Guns Craft)
audino@
tabbychat-unofficial@
@@ -13413,11 +13413,11 @@ create-oxidized@create_oxidized
green-soul
everyxdance@|一起跳舞! (EveryXDance)
-applied-flux
+applied-flux|应用通量*
-@tinkers-delight|匠魂乐事 (Tinkers' Delight)
+tics-delight@tinkers-delight|匠魂乐事 (Tinkers' Delight)
unnamed-desert-structures@
-bloomingnature@lets-do-bloomingnature
+bloomingnature@lets-do-bloomingnature|绽芳自然 (Let's Do: BloomingNature)
waila-inhibitors@
thingamabobs-and-doohickeys
waila-events@
@@ -13425,12 +13425,12 @@ march-of-the-ents|恩特的进军*
rndtp
in-world-buoyancy
@simply-starminer
-macos-input-fixes@
+macos-input-fixes@|MacOS 输入修复 (MacOS Input Fixes)
@maxhealthfixfixfix
@astronomical
-
-ned|猫艺:无尽难度 (Neko Endless Difficulty)
+@millet-delight|小米乐事 (Millet Delight)
+ned@|猫艺:终焉 (Neko End)
magical-torches@
dynamical-compass@
better-questing-lord-of-the-rings-addon|更好的任务-魔戒扩展 (Better Questing - Lord of the Rings addon)
@@ -13449,10 +13449,10 @@ aurorasdecorations@
choups-drakvyrn-mod
abyssal-decor
-reddens-stone-lanterns|Redden的石灯笼 (Redden's Stone Lanterns)
+reddens-stone-lanterns@|Redden的石灯笼 (Redden's Stone Lanterns)
heaven-burns-red|红烧天堂*
-apotheosis-modern-ragnarok|神化:现代诸神黄昏*
+apotheosis-modern-ragnarok|神化:现代诸神黄昏 (Apotheosis Modern Ragnarok: Zero)
@bedgoboom
block-properties|方块属性*
custom-chest-loot|自定义战利品箱*
@@ -13467,7 +13467,7 @@ login-shield
blockinteraction|禁止右键方块 (BlockInteractionMod)
linkart-fabric
leftist-potions@
-more-delight-fabric@more-delight¨more-delight-forge
+more-delight-fabric@more-delight|多趣乐事 (More Delight)¨more-delight-forge|多趣乐事 (More Delight)
mceconomy3
@antique-atlas-4
pollution|污染*
@@ -13486,7 +13486,7 @@ sawmill@universal-sawmill|锯木机*
@savemod
@dudewhatsmygpu
create-wizardry@
-@netherite-extras
+netherite-extras@
wfs-cave-overhaul@
@@ -13494,7 +13494,7 @@ cit-resewn-jsonlagpatch@
geckoanimfix@
mekanismecoenergistics@mekanism-eco-energistics
-waila-blacklist|Waila黑名单 (WAILA Blacklist)
+waila-blacklist|Waila 黑名单 (WAILA Blacklist)
item-blacklist|物品黑名单*
continue@
the-dawn-era@|黎明纪元*
@@ -13505,7 +13505,7 @@ create-hand-crafting@
@softer-pastels
@rainbethunder
create-easy-structures@
-sound-overhaul@sound
+sound@
skinshuffle@
reboot-lovelyrobot@|萌化机器人:重启 (Reboot LovelyRobot)
@@ -13523,7 +13523,7 @@ museum-curator@|琳琅满目*
pixel-hud@
@immibis-macroblocks
@heater
-@natures-melody|自然旋律 (Nature's Melody)
+natures-melody@|自然旋律 (Nature's Melody)
astemirlib@
create-unbreakable-tools@create_unbreakable
timeless-heroes@superhero
@@ -13537,7 +13537,7 @@ tac-interact-key|TAC交互优化 (TAC Interact Key)
@cement
@igfileeditor
-tide|潮汐*
+tide@|潮汐*
taiga-replant-replant|匠魂合金附加二次重栽版 (TAIGA Replant Replant)
salmons-genesis-reincarnation
loot-bundles@
@@ -13571,11 +13571,11 @@ epic-knightsnmages-fabric@epic-knightsnmages
hotai|绷带*
zeta@
taxtowncitizen
-killer-rabbit-transformation|转化杀手兔*
+killer-rabbit-transformation@killer-bunny-tweaks|转化杀手兔 (Killer Bunny Tweaks)
bronze-viking
astikor-carts-redux@astikorcarts-redux
-third-person-shooting@third-person-shooting-zero|第三人称射击*
+third-person-shooting@third-person-shooting-zero|第三人称射击:零 (Third Person Shooting: Zero)
sight|视域*
@the-first-city
threetag-palladium@|钯 (Palladium)
@@ -13592,7 +13592,7 @@ fusionutil
@compact-machines-infinite|压缩空间:无限 (Compact Machines: Infinite)
thaumic-sanity
-tfc-thermal-deposits
+tfc-thermal-deposits@tfc-volcanoes
animals-trap-for-tfc
zzzzz-custom-configs
@@ -13614,7 +13614,7 @@ combat-nouveau@
udlede@|港口*
hammer-time
hammerz
-@pickup|物品框互交优化 (Pickup)
+@pickup|物品框交互优化 (Pickup)
livingadventuretrails
suggestions-api@
colorful-subtitles@
@@ -13630,20 +13630,20 @@ more-variants-and-biomes
badoptimizations@
genshinimpactmod
-diagonal-walls
+diagonal-walls@
gravestones-die-classy-updated
osog-city-cars
immersive-vehicles-iav-parts-pack
tfmg-virtual-oil-deposit|工业长路:虚拟油田 (TFMG: Virtual Oil Deposit)
@alinlib
-croparia-if@|矿石作物/魔种之咏 IF版 (Croparia-IF)
+croparia-if@|矿石魔种 (矿石作物/魔种之咏 IF版) (Croparia-IF)
@actionlib
simple-close-button@
godzilla-rising
-create-netherless
+create-netherless@
noguievolution@no-gui-evolution|沉浸进化 (NoGuiEvolution)
baseball-bats
tfc-weather
@@ -13677,7 +13677,7 @@ drippedout@dripped-out
easter-rabbit-life@
sticker-frames@
tnt-slimes@
-armored-redstone
+armored-redstone@
bismuth-colors@bismuth-2|铋 (Bismuth)
killbind@
universal-clock-hud@
@@ -13703,11 +13703,11 @@ better-babies
morecreativetabs-forge-fabric@morecreativetabs
@shove!
-sunflower-delight
+sunflower-delight@|向日葵乐事*
mystias-izakaya@mystiasizakaya|夜雀乐事*
carpet-everything
-lets-do-brewery|盛节精酿 (Let's Do Brewery)
+lets-do-brewery-farm-charm-compat@lets-do-brewery-farmcharm-compat|盛节精酿 (Let's Do Brewery) - [作者推荐]2.0.0+¨lets-do-brewery@|盛节精酿 (Let's Do Brewery) - Legacy,作者已停止维护
hephaestusplus@
re-dimension-stages|维度阶段重制版 (Re-Dimension Stages)
lemoned@lemoned-(farmers-delight)
@@ -13720,7 +13720,7 @@ everymole@
mutant-iceologer@mutant-iceologer-mod
@days-gone
@extra-loot|额外战利品 (Extra Loot)
-relics-of-gaming-continued@
+relics-of-gaming-continued@|游戏遗物:延续*
endermen-plus@
tiptapshow@
villager-gunners
@@ -13730,7 +13730,7 @@ prodigy-mechanics
just-an-end-anchor@
memory-usage-title@|标题显示内存*
-
+string-utilities@|字符串工具*
sweeper-maid@|清洁女仆*
more-enchantments_|更多附魔 (More Enchantments)
@@ -13738,13 +13738,13 @@ bow-scope
fpv20
molten-metal
-selling-bin
+selling-bin@
tomtarus-cobblemon-farmers-delight-tweaks
cobblemon-o-plenty
tomtarus-cobblemon-immersive-engineering-tweaks
abuseless-hardcore@
@wizstacks32
-tconjei
+tconjei@
new-xp-ore
@not-responding
nocubes-wilderness
@@ -13763,14 +13763,14 @@ dragon-craft-quest
sword-of-gods
horse-stonks@
-pillarger
-ars-additions
+pillarger@
+ars-additions@
visualsound
@cyclic-crash-fixer
lavender-api@lavender
cheatutils-by-zergatul
-jump-sounds@
+jump-sounds¨@jump-sounds|Jump Sounds - 已失效
@kirin
llamapalooza
instant-drown
@@ -13784,7 +13784,7 @@ heterorustichromia
video-extension-for-fancymenu-forge¨video-extension-for-fancymenu-fabric
easy-npc@
-the-dawn-era-delight@
+the-dawn-era-delight@|黎明纪元乐事*
dynamic-craft
startinginventory
wireless-speakers|无线扬声器*
@@ -13809,7 +13809,7 @@ dirty-lens@
styled-player-list@styledplayerlist
create-love-war@
-chainmailed
+chainmailed@
serious-player-animations@
abridged@
hexvr@
@@ -13817,16 +13817,16 @@ hearths
prey-effect
venturer@
wheres-my-wither-storm
-alexs-mobs-interaction@
+alexs-mobs-interaction@|Alex 的生物:交互*
myworldgen
-medieval-buildings-end-edition
+medieval-buildings-end-edition@
orpheus@
-@create-sweets-and-treats
+create-sweets-and-treats@
klsts-beyond-netherite
refined-polymorphism@
@@ -13837,11 +13837,11 @@ thermals-delight
better-farmers-combat
simple-butchery¨@simple-butchery|Simple Butchery - 已失效
-butchery
+butchery@
enchantment-reveal@
-copycats@
+copycats@|机械动力:伪装方块+ (Create: Copycats+)
the-great-alchemical-compendium@
desperate-measures@
block-slabs-mod
@@ -13857,7 +13857,7 @@ super-water-block
@stop-repeating-command-block|停!循环型命令方块 (Stop Repeating Command Block)
pressure|压强*
-amendments
+amendments@
piglin-army
decorative-ladders@
more-foods
@@ -13888,10 +13888,10 @@ ad-astra-extra-additions@
infinite-fluid-bucket@|无限流体桶*
@voxy
-vmod@
+vmod|VMod - 已失效¨@vmod
minestuck
-star-wars-planets-ad-astra@
+star-wars-planets-ad-astra@star-wars-planets
amethyst-equipment@
the-whisperer
@@ -13911,7 +13911,7 @@ integrated-scripting@
@nyc-mtr
@mtr-utility
creativetabsearch@|创造物品栏检索 (CreativeTabSearch)
-
+@moonbetween|在月亮之间 (Moon Between)
tetrachord-lib@|四弦琴*
screenshot-sharing|屏幕截图分享/移影睹展 (ScreenshotSharing)
leaf-pile@|落叶堆*
@@ -13934,13 +13934,13 @@ vocalized
@advanced-framework|高级框架 (Advanced Framework)
-kubejs-irons-spells
+kubejs-irons-spells@isskjs
revelationaryjs@
arche
caverns-and-chasms@
-mixinbooster
+mixinbooster@
block-crafting@|方块合成*
zombplayer@
knighys-mystical-creatures
@@ -13985,7 +13985,7 @@ trajectory-estimation@|弹道估计*
-iron-repair-kits
+iron-repair-kits@
@susinsert
critters-n-crawlers@
@@ -14021,7 +14021,7 @@ deeper-caves@deeper_caves
-arborfirmacraft
+arborfirmacraft@arborfirmacraft-(afc)|林海传说 (ArborFirmaCraft)
enchanting-with-tfc
tfc-vessel-tooltip@
nether-gold-ore
@@ -14032,7 +14032,7 @@ natural-nether-portals
hotbaaaar@|忄夬扌疌木兰 (HotBaaaar)
ui@ui-lib
@random-items
-lucky-clover
+lucky-clover|Lucky Clover - 已失效¨@lucky-clover
@better-portals|更好的传送门 (Better Portals)
social-distance@|社交距离*
gt@gtnn
@@ -14040,7 +14040,7 @@ hunger-plus@|饥饿+*
melee-ex-machina-meexma@melee-ex-machina
arc@
-amlib
+cores@
@citymod|我的城市 (City Mod)
too-hard-to-dig@|隐岩砥凿 (Too Hard to Dig)
@@ -14059,14 +14059,14 @@ horseshoes@|马掌*
questlines@
endofherobrine@
thirstcanteen@thirst-canteen|口渴-水壶 (ThirstCanteen)
-thaumic-energistics-extended-life-fork
+thaumic-energistics-extended-life-fork|神秘能源延续版分支*
prehistoric-nature-decorations
tinyfoes
pyradium
anvilcraft@|铁砧工艺:重力科技 (AnvilCraft)
-tiny-mob-farm-remastered@
+tiny-mob-farm-remastered@|迷你刷怪场:重制*
create-aquatic-ambitions@
ffl-trinkets-compat
kaleidios-guns
@@ -14100,7 +14100,7 @@ buildify@
@big-globe
dragon-finder
respackopts@
-fortuitous-feasts
+fortuitous-feasts@
monazite@|独居石*
music-control|音乐控制* - 不再更新¨@music-control|音乐控制 (Music Control)
stellarcore|星核 (StellarCore)
@@ -14120,18 +14120,18 @@ eu-converter@
royal-variations
temporal-api@
-
+@underworld-magic-craftsmanship|幽法工艺 (Underworld Magic Craftsmanship)
callable-horse-unofficial-fabric-port|召之马来 Fabric版 (CallableHorse Unofficial Fabric port)
-
-shiny-horses-forge-enchantable-horse-armor
+@better-combat-experience|更好的战斗体验 (Better Combat Experience)
+shiny-horses-forge-enchantable-horse-armor@shiny-horses-forge
spartan-weaponry-simpleores|斯巴达的武器:简单矿石 (Spartan Weaponry: SimpleOres)
dye-depot@
@autoharvest|自动收获-重制 (AutoHarvest-RE)
@nyan-cat-loading
-@homeward-bound
+homeward-bound@
sword-blocking-mechanics@
electric-mace@
mace-backport
@@ -14143,7 +14143,7 @@ ae2-import-export-card@
@repairable-disc-11
-
+@fabric-permissions-api
@khinkali-mod
configured-defaults@
bens-sharks@
@@ -14171,7 +14171,7 @@ macaws-for-terrafirmacraft
@blahaj-of-undying
corals-tfc@
tfc-aged-alcohol
-tfc-improved-badlands
+tfc-improved-badlands@
@russian-metro-addon
winter-massacre
ghostlys|幽灵 (Ghostly)
@@ -14196,7 +14196,7 @@ toggledpickup|拾取控制 (Toggled Pickup)
farmers-delight-refabricated@|农夫乐事:重织 (Farmer's Delight Refabricated)
village-nullifier-terralith-mod
village-nullifier-mod
-structure-credits-fabric-version@structure-credits
+structure-credits@¨structure-credits-fabric-version
i-wanna-travel
simple-unbreakable-tools
cat-loaf@
@@ -14213,7 +14213,7 @@ tragicmc|悲惨世界1 (TragicMC)
auditory-continued@
create-more-copycats@
-farlands-reborn@modern-farlands-mod
+farlands-reborn@
momento@
@@ -14227,7 +14227,7 @@ aquatweaks|水下微调 (AquaTweaks)
aquatweaks-compat|水下微调:兼容 (AquaTweaks Compat)
@potions-re-glint
simpletextoverlay@
-bountiful-fares|丰饶食记* - 已失效¨@bountiful-fares|丰饶食记 (Bountiful Fares)
+bountiful-fares|丰饶食记* - 2.0.0 及以上¨@bountiful-fares|丰饶食记 (Bountiful Fares)
@ancient-debris-tweak|远古残骸微调 (Ancient Debris Tweak)
@@ -14338,10 +14338,10 @@ pet-collecting@
totallybalancedfallflying
giacomos-experience-seedling
@server-notify
-config-api
+config-api¨@config-api|Config API - 缺少部分
nethers-delight-refabricated@|下界乐事:重织*
@pumpkin-pie-delight
-tipsylib@
+tipsylib@runiclib¨@tipsylib|TipsyLib - 已失效
dragon-mounts-more-dragons@|龙骑士附属:更多龙 (Dragon Mounts: More Dragons)
@@ -14372,7 +14372,7 @@ disguiselib@
@mtr-resource-protector|MTR 资源包保护工具 (MTR Resource Protector)
station-architecture
-enchantmentrequirements
+enchantmentrequirements@enchantment-requirements
enchantment-enhancements@
mo-enchantments
soul-like-enchantment
@@ -14383,9 +14383,9 @@ btfixes
vmfixes
pillar-patched
novalogin|新星登录 (Nova Login)
-tfc-desire-paths
+tfc-desire-paths@
tfc-ore-washing
-tfc-hot-or-not
+
tfc-canes
lhmob-difficulty-increase|变强的怪物 (LH Mob Difficulty Increase)
@zombies-are-real-reforged
@@ -14405,8 +14405,8 @@ tinyinv@¨tinyinv-fabric
animationjs@
raids-backport
crossbows-backport
-tfc-caelum
-epic-compat-parcool
+tfc-caelum@
+epic-compat-parcool@
loqui@
real-worlds
@@ -14421,14 +14421,14 @@ alexs-caves-torpedoes@alexscaves-torpedoes|Alex 的洞穴:物品 & 鱼雷 (Ale
@slavic-cuisine
-tfc-tumbleweed
-tfc-barrens
+tfc-tumbleweed@
+tfc-barrens@
chiselse-bits-for-tfc@chisels-bits-for-tfc
-tfc-fresh-water-everywhere
+tfc-fresh-water-everywhere@
tfc-more-swords
-galleria@
+galleria¨@galleria|Galleria - 已失效
constructs-casting@
-@tinkersinnovation|工匠创新 (Tinkers' Innovation)
+tinkers-innovation@tinkersinnovation|工匠创新 (Tinkers' Innovation)
delicious-delights
@vietnamsdelight
@@ -14454,7 +14454,7 @@ create-connected-fabric-experimental@create-connected-fabric
wastify@
starter-structure@|初始建筑*
-mobblocker@
+mobblocker¨@mobblocker|MobBlocker - 已失效
giacomos-foundry
@blocky-bass
@toms-mobs
@@ -14504,7 +14504,7 @@ barely-enough-enchantments
tool-kit@
@filament
lijms-armory@
-tinker-delight
+tinker-delight@tinkers-construct-delight|匠魂乐事 (Tinkers Delight/Tinkers Construct Delight)
barbeques-delight@|烧烤乐事 (Barbeque's Delight)
coffeecraft-by-blockbrothers@coffeecraft
hearty-meals@
@@ -14516,7 +14516,7 @@ enderfake
@awakenedsilverfish|蠹虫觉醒了 (AwakenedSilverfish)
rawore-on-1-12-2@rawore_on_1.12.2|1.12.2的粗矿 (rawOre_on_1.12.2)
entityjs@
-@poem-of-star-wars
+poem-of-star-wars@
@hex-ways
vanity-aesthetic-armory@
@muffins-thais-delight
@@ -14530,7 +14530,7 @@ exquisite-packing@|精致包装*
grieflogger@
the-quest-of-entity-303
-
+pineapple-mending|经验修补:凤梨版*
@quick-chat|快捷聊天 (Quick Chat)
magic-vibe-decorations@
valentines-blessing@valentines-blessing-lilypads-roses
@@ -14555,7 +14555,7 @@ icarae-origin@
@consoles
-fsit@
+fsit|FSit - 已停更¨@fsit
carbon-config@
productivetrees@
@tsa-concrete
@@ -14591,17 +14591,17 @@ the-social
goety-mythical-addon
huge-structure-blocks@
-
+calamity-sn
bodyhygiene
cafes-birding@
kafs-valentine-special@
-create-deepfried@
+create-deepfried@|机械动力:油炸 (Create: Deepfried)
sleepable-create
create-railways-navigator@|机械动力:铁路导航*
speed-drifter@speed_drifters|MC飞车 (MCSpeed)
rearmour
-lionfish-api
+lionfish-api@
avaritia-universal
slash-blade-tweaker
@@ -14611,18 +14611,18 @@ built@
diyotweaks
extra-spell-attributes@extra-rpg-attributes
@create-contraption-creatures
-immersive-vehicles-iv-mts-texel-arsenal-pack@mts-ivv
+immersive-vehicles-iv-mts-texel-arsenal-pack
creature-whisperer
create-molten-metals@
-create-optical
+create-optical@|机械动力:光学*
@farmers-delight-plus
mob-battle-music@|战斗音乐*
-@concerto|协奏曲/一起听 (Concerto)
+concerto@|协奏曲/一起听*
@invisible-frames-for-forge
factions-and-curiosities@|派系与奇遇*
@@ -14634,7 +14634,7 @@ westeroscraft-worldeditcui
durability-display@
hardrock-samples-tfc
-
+@invhud_configurable|物品栏HUD+ 药水显示支持 (InvHUD_Configurable)
treps-cars@
@@ -14645,7 +14645,7 @@ back-up-beds@
-mountain-sea-and-continent-two|山海大陆2 (Mountain Sea and Continent Two)
+mountain-sea-and-continent-two|山海大陆 (Mountain Sea and Continent)
vinery-tfc
tkbs-campfires
@@ -14654,7 +14654,7 @@ tkbs-campfires
whispers-of-the-wendigo@
quartz-elevator@
elytra-utilities@¨elytra-utilities-fabric
-
+davids-furniture|大卫的家具 (David's Furniture)
kubejs-natures-aura@
fancymenu-system-interactions-addon@
control-engineering@
@@ -14664,7 +14664,7 @@ telepass@
offhandbanning|禁手令 (Off Hand Banning)
fantasy-craft|幻想工艺 (FantasyCraft)
breeding-potion@|繁殖药水*
-
+@servuxforged
loading-tips|加载提示 (Loading Tips / Pineapple Tips)
@highernether|更高的下界 (Higher Nether)
better-one-block|更好的单方块*
@@ -14675,21 +14675,21 @@ customemoji|自定义表情 (CustomEmoji)
sword-soaring|史诗战斗:御剑飞行 (Epic Fight - Sword Soaring)
bigger-ae2@
scalar
-trials-towers@|试炼高塔 (Trials Chambers)
+trials-towers@trials-chambers-backport¨@trials-towers|Trials Chambers - 已失效
blus-sign-pack-mts-iv
alexs-mediumcore@
sherdsapi@
@kontraption
musketeer-illager
-
+daker-acg|Daker ACG工艺 (DakerACG)
bananarangs@
remorphed@
particle-core@
fermiumbooter@
ytech
@bbrb
-@mcterra
+mcterra@
siren-head-the-arrival
classic-musical-discs@
music-notification@
@@ -14720,23 +14720,23 @@ agricraft-continuation|农业工艺:延续*
create-nuke@
egg-particle-fix|鸡蛋粒子修复*
chicken-chunk-patcher
-achievement-detection-fix|成就检测修复*
+achievement-detection-fix@|成就检测修复*
@auto-night-vision|自动夜视 (Auto Night Vision)
@chais-inventory-sorter
-@mysteriumlibunofficial
-
+@myceliumlib|菌丝 (Mycelium Lib)
+cataclysmic-illagers
witcherypatch@|巫术补丁 (WitcheryPatch)
pin
simple-pings@
great-scrollable-tooltips@
waterframes@
eugenes-whistle-spur|友止殷的马哨和马刺 (Eugene's Whistle & Spur)
-quickquark|畅启速始 (QuickQuark)
+quickquark|畅启速始/夸克疾速启动 (QuickQuark)
minecraft-mineralogy
-seed-delight|种子乐事*
+seed-delight@|种子乐事*
transmog@|幻化*
nuclear-guitars
cubic-racers
@@ -14747,13 +14747,13 @@ resiever@
cartload@
stewing-baking@|炖与烤 (Stewing & Baking)
am2playground@
-@astrocraft
+astrocraft-mod@astrocraft
neat-shaders-fix@|极简血量显示 - 光影修复版 (Neat - Shaders-Fix)
worldtooltips-shadersfix@|掉落物信息显示 - 光影修复版 (WorldTooltips - ShadersFix)
options-enforcer@
-macaws-bridges-biome-o-plenty@|Macaw的桥梁:超多生物群系附属 (Macaw's Bridges - Biomes O' Plenty)
+macaws-bridges-biome-o-plenty|Macaw 的桥梁:超多生物群系附属 (Macaw's Bridges - Biomes O' Plenty) - 已被更新版顶替¨@macaws-bridges-biome-o-plenty|Macaw 的桥梁:超多生物群系附属 (Macaw's Bridges - Biomes O' Plenty)
renhider@
itemframeremover@|物品展示框移除器 (ItemFrameRemover)
@legacy-debug-pause
@@ -14808,7 +14808,7 @@ flintandsteel-improve@
forge-zombie-plague-mod-2-wip@zombie-plague-2-hardcore-zombie-apocalypse|僵尸瘟疫2 (Zombie Plague 2)
colored-grasses
more-default-options
-@livelydanmaku|缤纷弹幕
+@livelydanmaku|缤纷弹幕 (LivelyDanmaku)
treecapitator-updated|砍树更新版*
@offhandlights|副手光源 (Offhand Lights)
@@ -14822,7 +14822,7 @@ polytone@
delightful-burgers@
cinderloe-project
lotweakr|魔戒微调 (LOTweakR)
-lotr-renewed-extended|魔戒:复兴-拓展 (LOTR: Renewed Extended)
+lotr-renewed-extended@|魔戒:复兴-拓展 (LOTR: Renewed Extended)
lotr-companions-renewed-addon|魔戒:同伴 (LOTR: Companions)
dvs|自定义魔戒小建筑 (DVS)
mffs
@@ -14831,8 +14831,8 @@ bitcrafting
fart-shit-pee
statuseffecthud-updated|状态信息显示更新版 (StatusEffectHUD Updated)
capejs@
-advanced-machines-patcher
-@immersive-tooltip|沉浸式的工具提示 (Immersive:Tooltip)
+advanced-machines-patcher|高级机械补丁*
+@immersive-tooltip|沉浸式提示框 (Immersive:Tooltip)
sound-controller|Sound Controller - 已弃用¨@sound-controller
cagerium
@@ -14876,7 +14876,7 @@ life-is-precious|生命是宝贵的 (Life is precious)
advanced-skills@|进阶技能*
nuts-delight
-@vegetables-delight
+vegetables-delight@
enderpack@
ingame-mod-configs|游戏内模组配置 (InGame Mod Configs)
playertag|玩家标签 (PlayerTag)
@@ -14900,7 +14900,7 @@ slashblade-resharped@|拔刀剑:重锋 (SlashBlade: Resharped)
spawn-mod@
@smooth-swapping-1.20.4-port
@create-tab-fix
-@srp-origins
+scape-and-run-origins@srp-origins
origins-extra-keybinds@origins-extra-keybinds-fabric
epic-knights-addon
@@ -14931,7 +14931,7 @@ totally-lit@
mo-shiz-mod
aetheric-tetranomicon
-arda-addon|阿尔达拓展*¨lotr-arda|阿尔达拓展 (Arda Addon) - Snow的阿尔达拓展
+arda-addon|阿尔达拓展*
@potionwarning
a-good-place@
aether-protect-your-moa@
@@ -14943,7 +14943,7 @@ coal-piece
@mechmonstrosity|机械巨兽 (MechMonstrosity)
slash-illager@
-@create-spawnerboxer
+@create-spawnerboxer|机械动力:拳击刷怪笼 (Create: SpawnerBoxer)
skeletalband|骷髅乐队乐谱 (SkeletalBand)
alexs-caves-entropy|Alex 的洞穴:熵 (Alex's Caves:Entropy)
mekanism-explosives|通用机械:炸药*
@@ -14966,7 +14966,7 @@ extra-addition-s@extra-additions
@herbal-delight
-@compatlib
+@compatlib|旧版本兼容工具 (LegacyCompatibilityUtils / CompatLib)
warriors-of-past-epoch
the-ghast-cow@
@alternate-current-unofficial
@@ -14997,14 +14997,14 @@ war-of-the-ring-mc|魔戒大战 (The War of the Ring MC)
redstone-clock-mod@|红石钟 (Redstone Clock)
create-cobblestone@
@another-pickaxe
-@selfexpression
+selfexpression@
electricalcraft
@elecfurn
what-is-stone-colorful-caves@what-is-stone-(colorful-caves)
vtones
heavy-inventories|物品负重*
vs-addition@
-gravity-torch
+gravity-torch@
lifesteal-forge-fabric@
mores-by-its-infinite
dragonite-gear
@@ -15042,7 +15042,7 @@ battlemusic@
@embyui
engineered-golems
axifier@
-renderjs
+renderjs@
@enchantment-compat
dehand@
jadecolonies
@@ -15054,7 +15054,7 @@ create-metallurgy@|机械动力:冶金学 (Create: Metallurgy)
the-shy-guy
@tea-time
@cobbreeding
-@cobblemon-myths-and-legends-addon
+@cobblemon-myths-and-legends-sidemod
@rctmod
create-klinks-n-klangs@klinksnklangs
mo-than-enough-guns@
@@ -15070,7 +15070,7 @@ yosby
@zundamod
common-network@
addurdisc@|自定义唱片 (AddurDisc/Add your disc)
-enigmatic-addons|神秘遗物扩展*
+enigmatic-addons@|神秘遗物扩展*
@bloodscythe
dungeons-delight@
@@ -15102,7 +15102,7 @@ realm-rpg-quests-rewards
unlucky-tnt@
fastboot@
-tardis-refined@
+tardis-refined@|改良的TARDIS (TARDIS Refined)
ocean-villager-trader@
@precise-manufacturing|机械动力:精制工艺 (Create: Precise Manufacturing)
@create-new-age-alexs-caves-integration
@@ -15117,22 +15117,22 @@ cubic-villager
@morecursors|更多光标 (MoreCursors)
true-infinity
nukateams-gun-lib@ntgl
-fantastic-biomes-cave@
+fantastic-biomes-cave
nukacraft@thenukacraft
enchantment-limiter
emi-ores@
@bread-skin-mite|面包皮 (Bread Skin)
extra-gore
chassiscore@
-@spit-splat-fabric
-@moderation
+@spit-splat-fabric|唾溅兽 Fabric 版 (Spit Splat [Fabric])
+linweiyun-mod@moderation
normalcore@
appliede@
waystones-teleport-pets@
@sneaky-link
create-mortar@
forgelin-continuous@
-@ripples-of-the-past-killer-queen-addon
+@ripples-of-the-past-killer-queen-addon|RotP:杀手皇后附属 (Ripples of the Past: Killer Queen Addon)
ninjin-knockback-dbc-by-hedaox
wings-3
dstathud@
@@ -15145,7 +15145,7 @@ ribbits@
raws-visual-keybinder
-@scoreboardtweaks
+scoreboard-tweaks@scoreboardtweaks
rsinfinitewireless
rustic-engineer@
artsandcrafts@
@@ -15164,7 +15164,7 @@ alternacraft
sheep-squeak@
fabric-simple-animated-guns
a-v-a-in-minecraft@a.v.a-alliance-of-valiant-arms-guns
-coordinates-display@
+coordinates-display@|坐标显示*
@acrylicraft|亚克力窗口 (AcryliCraft)
towelette@
shc-pvz-zengarden@
@@ -15198,7 +15198,7 @@ dralards-rock-candy-mod
@explosion-breaks-no-block|爆炸不破坏方块 (Explosion Breaks No Block)
via-romana@
-lithic-coins|石质硬币*
+lithic-coins
dynamic-trees-for-natures-spirit@|动态的树:自然之灵附属 (Dynamic Trees for Nature's Spirit)
gore-edition
christmas-wish-in-a-bottle
@@ -15225,8 +15225,8 @@ leadable-bats
canes-wonderful-spiders
-@scribble
-@text-formatting-everywhere
+scribble@
+text-formatting-everywhere@
extended-item-information
mod-sound-volume-options@mod-volume-options
@@ -15239,12 +15239,12 @@ wither-totem@wither-skeleton-totems
@player-bones
more-apples-hyj|更多苹果 (More Apples)
-fancy-enchantments|奇异附魔*
+fancy-enchantments@|奇异附魔*
ash-of-sin-adventure-dimension|罪业余烬:冒险维度 (Ash Of Sin: Adventure Dimension)
push-forward@|猪突猛进*
ymn@|你现在是我的了! (You're Mine Now!)
thermal-endergy@
-mekanism-turrets-fences
+mekanism-turrets-fences@mekanism-turrets
nuka-cola-classic
draconic-machinery
@voxelprints-halloween
@@ -15257,7 +15257,7 @@ iap-omega-craft
iap-mekanism
industrial-agriculture-plugin-silents-mechanisms
-roads-and-roofs-tfc
+roads-and-roofs-tfc@
taxvillagearchitect
taxoceanarchitect
nethers-flora
@@ -15285,9 +15285,9 @@ monochromatic-blocks@
-@inventiveinventory
+@inventive-inventory
boxlib@
-mokels-bossfight-saphyra
+the-shattered-goddess-mokels-bossfight-saphyra
resource-explorer@resource_explorer
@patcher
autoreconnectrf@
@@ -15309,7 +15309,7 @@ lexiconfig@
@advancementsfullscreen
luckys-wardrobe@
@forkkit
-@more-affordable-than-ever
+more-affordable-than-ever-never-too-expensive@more-affordable-than-ever
what-am-i
@camoverlay
@@ -15328,12 +15328,12 @@ autochefs-delight@
bindcommands@
short-grass@
stairs-but-chairs@
-@yumo-compactmachines-por|压缩空间:产量推演 (yumo compactmachines por)
+yumo-compactmachines-por@|压缩空间:产量推演 (yumo compactmachines por)
dynamic-trees-for-fruit-trees|动态的树:妙趣果园 🍊附属 (Dynamic Trees for Fruitful Fun 🍊)
dynamic-trees-for-better-end|动态的树:更好的末地附属 (Dynamic Trees for BetterEnd)
dynamic-trees-autumnity|动态的树:秋原附属 (Dynamic Trees - Autumnity)
dynamic-trees-for-bloomingnature|动态的树:BloomingNature附属 (Dynamic Trees for BloomingNature)
-dynamic-trees-terralith|动态的树:Terralith附属 (Dynamic Trees - Terralith)
+dynamic-trees-terralith@|动态的树:Terralith附属 (Dynamic Trees - Terralith)
dynamic-trees-for-meadow|动态的树:青青草甸附属 (Dynamic Trees for Meadow)
dynamic-trees-for-beachparty|动态的树:沙滩派对附属 (Dynamic Trees for Beachparty)
dynamic-trees-for-vinery|动态的树:葡园酒香附属 (Dynamic Trees for Vinery)
@@ -15380,7 +15380,7 @@ compatdatapacks@
the-luminous-mod@
-@burst-door-open|破门而入 (Burst Door Open)
+burst-door-open@|破门而入*
chain-suspension-forge@chain-suspension¨chain-suspension
@@ -15402,14 +15402,14 @@ jcraft-eyes-of-ender
@reset-keys-confirmation-screen
idle-boost@
-fabricbettergrass@|更好的草地 (FabricBetterGrass)
+bettergrassify@|更好的草地 (BetterGrassify)
@lightweight-inventory-sorting
@stonecutter-gui-remastered
@loom-gui-remastered
-@unseaworthy
+unseaworthy@
@chromaanvils
thermal-and-space@
-create-curios-jetpack
+create-curios-jetpack@
epic-knights-antique-legacy
viking-samurai-knight-items
barely-simple-staffs@barely-simple-staff
@@ -15431,7 +15431,7 @@ biome-modifier-for-fabric@|Fabric版群系编辑器 (Biome Modifier for Fabric)
chunkloader-simple
apollyon
worldweaver@
-netherite-compass@
+netherite-compass@|下界合金指南针*
the-darkness-will-find-you@
silent-screenshots@
cr3stal-updatet@crestal
@@ -15443,7 +15443,7 @@ ok-boomer-zoom@ok-boomer
rendi@|重生:伤害免疫机制移除 (ReNDI)
create-questing@
@create-train-perspective
-elainalike@|魔女之绘 重生:重生 (ElainaLike Reborn:Reborn)
+elainalike@|魔女之绘 重生:重生 (ElainaLike Reborn: Reborn)
take-back-your-sword@|收回你的剑!*
@@ -15458,34 +15458,34 @@ lush-cave-trader@
oceans-enhancements@
xp-jellies
aromatic
-onekeyminerforge|一键连锁Forge版 (OneKeyMinerForge)
+onekeyminer-nf@onekeyminer_nf|一键连锁(Forge/NeoForge) (OneKeyMiner(Forge/NeoForge))
retargetato@|约谊敌众 (ReTargetato)
-iceandfire-fabric@|冰火传说Fabric版 (IceAndFire Fabric)
+iceandfire-ce@|冰火传说社区版 (IceAndFire Community Edition)
tomanos-vehicle-mod
ancient-sands@
hs-bosses
chubby-mobs
repair-gem|修复宝石*
-better-weaponry-compatible-with-better-combat
+better-weaponry-compatible-with-better-combat@better-weaponry
disable-front-perspective@
thaumcraft-6-aspects-for-jei@
@up-and-away
-@ripples-of-the-past-hermit-purple-addon
+@ripples-of-the-past-hermit-purple-addon|RotP:紫色隐者附属 (Ripples of the Past: Hermit Purple Addon)
nc-steam-additions@nuclearcraft-steam-additions
aura-cascade|气场流注*
rac-compat@
spartan-weaponry-addon-toolkit@
-
+smsn@
sneaky-tree-growing-forge@sneaky-tree-growing¨sneaky-tree-growing-fabric
hungry-zombies
@gravity-gourds
-usa-delight
+usa-delight|USA Delight - 已失效¨@usa-delight
experience-farm
-@ad_extendra-continuation
+ad-extendra-dandi2k8-version@ad_extendra-continuation
nebulus-juice-trader@
cthulhu-fishing@
large-super-potato|大型超级马铃薯*
@@ -15495,7 +15495,7 @@ made-in-abyss-shroombears
squinkers
mokels-bossfight-kinora
@create-high-pressure
-create-framed@
+create-framed@|机械动力:多彩边框 (Create: Framed)
trackwork@
oritech@
the-impossible-library@
@@ -15516,9 +15516,9 @@ villager-in-a-bucket@
nebulus-trader-for-the-desert
nebulus-jungle-trader@nebulus-trader-for-the-jungle
mushroom-villager-trader@
-nebulus-better-portals@
+nebulus-better-portals¨@nebulus-better-portals|Nebulus Better Portals - 已失效
resource-ghouls
-
+avaritianeo@|无尽贪婪Neo (AvaritiaNeo)
create-legacy@
@create-bronze-age
wind-bottle@|风瓶 (wind bottle)
@@ -15528,9 +15528,9 @@ text-placeholder-api@placeholder-api|文本占位符 API (Text Placeholder API)
@gravity-api-fork
@tt20
-
-
-
+kasugalib@
+@c2h6s-tinker|乙硫基匠魂 (C2H6S Tinker)
+encounter-mob-ai-tweaker|遭遇/长驱速弑 (Encounter)
kubejs-mekanism-unofficial
@mediaplayer|媒体播放器 (MediaPlayer)
bakery-villager-trader
@@ -15538,7 +15538,7 @@ nebulus-trader-for-the-plains
mokels-more-spiders
exposure-catalog@
-skinchanger
+skinchanger@
@halflifemenu
sitting@sitting+
@@ -15560,7 +15560,7 @@ treasuredistance
affinity@
chaos-moon
@healthindicator
-
+@small-chisel|小凿子 (Small chisel)
@auto-sprint-fix
lets-do-emi-compat@
health-indicator@health-indicators
@@ -15581,8 +15581,8 @@ travelerz@
the-rise-of-minerals|矿石崛起*
gregtech-steam-additions
gregtech-nomifactory-edition
-create-ethium
-create-contraption-terminals@
+create-ethium@ethium
+create-contraption-terminals@|机械动力:动态终端*
strata-recipes
strayed-fates-forsaken
@@ -15602,7 +15602,7 @@ third-person-crosshair-fabric
@hitrange
black-moon@
dynamic-fire-overlay@
-firorize@improved-fire-overlay
+firorize@
smoke-extender
@nohotbarlooping
@higher-chat
@@ -15627,7 +15627,7 @@ tamago@
@clickthrough2.0
cat-jammies-fabric@cat-jammies
open-computer-resurrected
-@advancements-reloaded
+advancements-reloaded@
oc2r
eu-p2p-tunnel@eup2p|EU能源P2P通道 (EU P2P Tunnel)
@hold-onto-everything
@@ -15659,7 +15659,7 @@ esf-entity-sound-features@esf|实体声音特性 (Entity Sound Features)
nomoreglowingpots@
smooth-particles@
-5544292@unloaded-activity
+unloaded-activity@
@improperui
neverenoughanimation
reds-stagger-parry
@@ -15694,7 +15694,7 @@ pet-the-frog@frog-petting
ex-nihilo-extras@
cycle-title-screen-splash@
hide-invisible-armor@
-inventory-tweaks-refoxed
+inventory-tweaks-refoxed@
bigshot@
more-immersive-aircraft
legendary-survival-overhaul|传说生存*
@@ -15709,7 +15709,7 @@ phantom-flames
darktitlebar@
cubes-without-borders@
block-parry-dodge
-fargos-enchantments
+fargos-talismans|Fargo的护身符 (Fargo's Talismans)
toostrongmonster
simplystronger
tacz-durability
@@ -15733,7 +15733,7 @@ sandfilter@|砂滤器 (Sand Filter)
resaplingslayer@|雁雅得枝 (ReSaplingslayer)
cerberu@|狱牙抵战 (Cerberus)
remoreloyaltrident@|远亦得珍 (ReMoreloyaltrident)
-potionlevelfix@|药引对注 (PotionLevelFix)
+potionlevelfix@|药水等级修复/药引对注 (PotionLevelFix)
reunmending@|以验垫砧 (ReUnmending)
reghostjump@|引殃髑至 (ReGhostJump)
rebroken-blade@|隐影刀珍 (Mowzie's Mobs: ReBroken Blade)
@@ -15752,8 +15752,8 @@ kevs-library@
remove-purpleprison-ads
timeoutfixes
forgeskyboxes@
-
-
+customfishing|随液钓趣 (CustomFishing)
+metr0polis@metropolis
crust@
sc-structurecraft@structurecraft
@@ -15769,12 +15769,12 @@ miner-villager
villager-venture
alekiships@
alekishipsbop@
-antique-trading-ship
+antique-trading-ship@
small-horse-stable
shrek-the-ogre@shrek
the-anomaly
-desert-behemoths-sandworms
+desert-behemoths-sandworms@desert-behemoths-sandworms!
vermintide-rising
armor-of-the-ages@|时代铠甲 (Armor of the Ages)
@@ -15786,7 +15786,7 @@ ftb-quest-localizer|FTB Quest 本地化 (FTB Quest Localizer)
modpack-update-checker@
minimotd-fabric
effecttimerplus@
-thaumic-concilium@
+thaumic-concilium@|神秘议会*
equivalentexchange
coral-up@
@@ -15808,7 +15808,7 @@ ender-dragon-loot
oh-the-trees-youll-grow@
oh-the-biomes-weve-gone@|我们走过的生物群系 (Oh The Biomes We've Gone)
-@dblockbusterdisplays
+dblockbusterdisplays@
@waygl
@@ -15832,13 +15832,13 @@ dis-enchanting-table@
gt-top-addition|格雷信息显示拓展 (GT TOP Addition)
gt-steam-tech|格雷蒸汽科技 (GT Steam Tech)
-simpleanimator¨@simpleanimator
+simpleanimator@
tianjin-metro@|天津地铁*
modular-flower-pots|模组化花盆*
repurposed-structures-floralis-compat-mod|结构变体:Floralis 兼容模组 (Repurposed Structures - Floralis Compat Mod)
balanced-shield@
-@more-potion-effects|更多药水效果 (More Potion effects)
+more-potion-effects@|更多药水效果*
jungle-villages
truecherry
@revamped-trades
@@ -15857,30 +15857,30 @@ gt-wireless|格雷无线科技 (GT Wireless)
forcejava21@|强制 Java21 (ForceJava21)
blessings@|祝福*
crafted-core@
-call-from-the-depths@|深渊召唤*
+call-from-the-depths|深渊召唤*¨@call-from-the-depths|深渊召唤 (Call From The Depths) - 落后 更新不及时
@clover-fabric|四叶草Fabric版 (Clover Fabric)
-champions-unofficial|冠军/强敌再续 (Champions-Unofficial)
+champions-unofficial@|冠军/强敌再续 (Champions-Unofficial)
aetherworks-refracted@
eidolon-repraised@eidolonrepraised
create-northstar@
-create-pattern-schematics@
+create-pattern-schematics@|机械动力:模式蓝图 (Create: Pattern Schematics)
magic-merchant
aether-villages@|天境村庄*
tricky-trials@tricky-trials-1.21
-abilities-the-boys|黑袍纠察队 (abilities-the boys)
+the-boys-minecraft|黑袍:新世界 (The Boys : New world)
better-creative-tabs-forge-neoforge|更好的创造标签页 (Better Creative Tabs)
-
+more-upgrade|更多锻造模板 (MoreUpgrade)
avaritia-unofficial
-stellaris@
+stellaris@|群星*
command-o-matic
-realistic-physics
+realistic-physics@
cia@|自定义物品属性 (Custom Item Attributes)
item-wheel@
data-attributes-directors-cut@
@@ -15897,8 +15897,8 @@ coldsweat-tfc
deeper-nether-biomes
adventurers-beyond@
mss-moogs-soaring-structures@
-goblins-tyranny@
-no-32768-limit|没有32768限制*
+goblins-tyranny@|哥布林的统治*
+no-32768-limit@|没有32768限制*
@@ -15907,7 +15907,7 @@ reairdrop-supply@|空投供应:重生 (ReAirdrop Supply)
@minequery|MC宝 (MineQuery)¨@minequery-legacy|MC宝 (MineQuery)
@simply-create-model-mod|简单动力 (Simply Create Model)¨@simply-create-model|简单动力 (Simply Create Model) - 资源包
-mountains-poem
+mountains-poem@|峰峦辞 (Mountain's Poem)
@playtime-reborn|游戏时长计数器:重生 (Playtime Reborn)
moon-between-medicine|在月亮之间:药学拓展 (Moon Between : medicine)
@@ -15919,7 +15919,7 @@ extended-industrialization@
small-tetra-additions
lets-do-furniture@
tesseract-api-neoforge@tesseract-api
-
+tickrate-api
spectrelib
tfc-artisanal
@@ -15927,7 +15927,7 @@ tfc-artisanal
@the-real-time|现实时间
dragonoptifix|DragonBlockC 与 OptiFine 的兼容修复 (DragonOptiFix)
-@effortless
+effortless@
@barriers-dont-block-rain-1.21
@nochathide
@@ -15937,7 +15937,7 @@ re-translator@
yank-it-out@
vintage-kubejs@
emi-professions-emip@emi-professions-(emip)
-grindstone-enchantments
+improved-grindstone
@starlight-legacy
themetip@
woodwalkers@
@@ -15947,8 +15947,8 @@ horde-moon@
laser-mod-2
mekanism-lasers
-wizards-reborn
-
+wizards-reborn@
+tinkervillager|工匠村民 (Tinker Villager)
customnpc-contentback
debug-stick
useful-bat
@@ -15957,7 +15957,7 @@ bamboo-stuff@
@moai-update
junk-drawers@
playerex-directors-cut@
-lets-do-camping@
+lets-do-camping@|摇曳露营 ([Let's Do] Camping)
@startuptime-unoffical|启动时间非官方版 (StartupTime Unoffical)
serverconfig-updater
@epiczoomer
@@ -15967,22 +15967,22 @@ agenta@
more-beehive-variants@
persistent-creative-inventory@
-lets-do-wildernature@
+lets-do-wildernature@|野性自然 ([Let's Do] WilderNature)
@quitconfirm
@ressourcepackcommand
neodymium-unofficial@nd1710|钕:非官方版*
@carbon-performance|碳 (Carbon)
coloredglowlib@
-@better-snow-coverage|更好的雪覆盖
+@better-snow-coverage|更好的雪覆盖 (Better Snow Coverage)
trade-enchantment-display@|交易附魔显示*
eternal-starlight@|永恒星光*
astral-patch|星辉补丁*
fast-scrolling-fabric@fast-scrolling¨fast-scrolling
galoblender-updated
-more-tetra-materials
-ageing-mobs
+more-tetra-materials@
+ageing-mobs@
warm-ice-block
smoke-signals@
wunderreich@
@@ -15993,25 +15993,25 @@ vulkanmod-android-libs
@black-bar-concealer
building-shift@
mob-ai-tweaks
-the-masquerade-illager-boss
+the-masquerade-illager-boss@the-masquerade-(illager-boss)
better-armory-x
sweetys-horse-armor-tweaks@
fired-pots@
-under-the-moon
+under-the-moon@|驻月夜下 (Under the Moon)
coffeegs-colorful-world
jeb_-wool@|jeb_羊毛 (jeb_ Wool)
-illage-and-spillage-finally-ported
+illage-and-spillage-finally-ported@illage-and-spillage-respillaged
more-runes-more-magic-series@|更多符文 (More Runes)
-battlemages-more-magic-series
+battlemages-more-magic-series@
eidolon-rebrewed-data-recipes
-elemental-metals-magical-metallurgy-series
-dragonsteel-more-magic-series
-dragon-scale
+elemental-metals-magical-metallurgy-series@fallen-paladins-and-priests
+dragonsteel-more-magic-series@
+dragon-scale@
scholar@
@elytraswimfix
tacz-bullet-proof-enchant-add-on@¨tacz-bullet-proof-enchant-fabric-add-on
no-screen-bobbing@viewbobbingmod
-more-spell-attributes-more-magic-series
+more-spell-attributes-more-magic-series@
minos-particle-presets
somewhat-hardcore-fabric-forge@you-die-you-lose-a-heart
@blurred-window
@@ -16025,7 +16025,7 @@ unique-magic
epic-projectile-reflection|史诗弹射物反射
zombie-island
-@stratagems
+stratagems@
solar-hardcore@
i-hate-flesh-reborn-ihfr@i-hate-flesh-reborn-(ihfr)
@@ -16034,7 +16034,7 @@ aaa-particles@
@anno
compass3d@
@clockin
-diggus-maximus-reborn
+diggus-maximus-reborn@|连锁挖掘重制版*
satisfying-buttons@
item-on-chest-fabric@item-on-chest
industrialreborn@
@@ -16049,13 +16049,13 @@ simple-quiver@
too-many-paintings-forge-fabric@too-many-paintings
building-but-better@
exchange-fuels@
-more-craftings-of-runes
+more-craftings-of-runes@
mega-evolve
pixeltweaks@
-hrbs-drills
+hrbs-drills@
index-librorum-prohibitorum|禁书目录 (Index-Librorum-Prohibitorum)
bft
-heartbond
+heartbond@
breakerplacer@
iron-ladders@|更多梯子*
crumbs-deepslate-button|Crumbs 的深板岩按钮 (Crumbs' Deepslate Button)
@@ -16066,9 +16066,9 @@ unique-weaponry
boss-keys
mace-variants
-shortswords
+shortswords@new-short-swords
bbs-mod@
-
+monocle@monocle-iris
move-ui@
terrafly
achievements-mod-200@mysticrift-achievements
@@ -16077,8 +16077,3134 @@ saturation-plus
mod-and-resource-pack-checker-abras-additions
createstuffadditions-fix
warden-slayer-enchantment
-mods-optimizer
-glowy-players-renewed@|玩家高亮:重置版
+mods-optimizer@
+glowy-players-renewed@|玩家高亮:重置版*
epic-fight-kenjis-combat-forms
-mekanism-unleashed@
\ No newline at end of file
+mekanism-unleashed@
+
+easy-cake
+weave-craft
+morefarming@|更多农耕 (MoreFarming)
+iron-bushes
+forestry-community-edition@|林业:社区版 (Forestry: Community Edition)
+
+specified-spawning
+kubejs-create-for-fabric-1-20-1
+portable-spawners
+bettertime@better_time|长期熟时/求闻时记/更好的时间 (BetterTime)
+
+
+
+
+lets-do-farm-charm@|沉浸农艺 ([Let's Do] Farm & Charm)
+
+hunters-moon@
+miners-moon@
+ender-moon@
+extreme-horde-moon@
+spider-moon@
+@breed-parrots-with-seeds
+persistent-parrots@
+@ultimate-autototem
+anti-mob-cheese
+bad-horse-fix@
+vanilla-nei-fix
+
+@fconfiglib
+satin-free-wakes@
+blue-archive-paintings|Sensei的珍藏*
+
+lunar-nether
+yeehaw-towns@
+@copperrails
+
+mana-unification@
+immersive-energistics@
+specialised-cells@
+random-chunks@
+unique-commands
+
+
+flood@
+block-prints
+
+
+justleveling-x-ironspell@|法有所归 (Just Leveling x IronSpell)
+
+hugme@|抱抱我! (HugMe!)
+
+coins-ll@coins!
+purediscstrickytrials@
+tinkers-in-the-sky
+create-tank-defenses@|机械动力:坦克防御*
+potato-update@potato-update-backport
+twilight-forest-fixes@
+dietary-statistics
+day-zombies-rebooted
+viewboboptions@
+ender-pearl-anti-disappear@ender-pearl-ad
+
+create-dynamic-lights@
+decorative-tin
+blended-compat@
+hold-easy-place@|保持简单放置*
+
+
+
+endlesscompat|无尽贪婪·伪附属 (EndlessCompat)
+spartan-and-fire-lightning-edition|斯巴达之冰与火-雷电版 (Spartan and Fire - Lightning Edition)
+the-twilight-forest-deforestation-edition|暮色森林 - Deforestation 版 (The Twilight Forest - Deforestation Edition)
+
+minieffects-legacy-extended-life
+call-to-battle-ww2-vehicles-addon
+thaumic_funnel@
+ynet-an-xnet-fork
+rosatech
+nutrition-gtceu@
+nutrition-unofficial-extended-life@|营养学非官方延续版*
+@manametalmod-tweaks|魔金微调 (ManaMetalMod Tweaks)
+ae2-crafting-tree|合成树 (AE2: Crafting Tree)
+client-automatic-doors@client_automatic_doors|客户端自动门*
+
+@chais-build-tools
+forge-relocation
+forge-relocation-fmp-plugin
+@fish-lib
+forgedapi-fork@
+prioninfection
+dark-shadow-monster@|暗影怪物*
+the-mi-alliance-invasion
+fancy-health-bar@
+@no-join-leave-messages
+data-trades@
+
+draconic-alchemy
+fancymenu-animation-maker-tool
+jmx@imx
+@elytra-rebalance
+another-dyeable-shulkers@|可染色潜影贝*
+
+@thatskyinteractions
+
+chisatos-more-redstone@|Chisato的更多红石 (Chisato's More Redstone)
+assetmover
+duclib@
+
+orecrop|矿石作物 (OreCrop)
+@vinurl
+nutritionz@
+xxl-packets@
+uncharted
+mcfetch@
+reignited-hud@
+more-crafter-variants@
+sharingan-addon-ahznbs-naruto-mod
+create-tacz-automation@
+tacz-plus-timeless-and-classics-feature-addon@tacz-plus
+fibers-guns-pack-for-tacz
+@splinecart
+beams@
+discarnate@
+@fishmc
+mob-armor-trims@
+bubbles-a-baubles-fork
+arst-motion-blur@
+@mpalladium|钯 (Palladium)
+firehud@
+@elytra-autopilot
+
+personal-worlds@
+pearltickets
+cobweb@
+progression
+stop-repeating-command-block-reborn@|停!循环型命令方块:重生*
+parry-us@parry!|予以刀振/格挡! (Parry)
+
+frycooks-delight@|油炸乐事 (Frycook's Delight)
+advancedae@|高级AE (AdvancedAE)
+
+pretty-pipes-fluids|更好的管道:流体 (Pretty Pipes: Fluids)
+webstone@
+rotatable-blocks
+treasure-seas@|宝藏海*
+ambidextrous
+@always-sprint
+tea-the-story-recount|茶风·纪事:追叙 (Tea the Story : Recount)
+xp-challenge@|经验挑战 (XP Challenge)
+@styled-renaming
+@simple-block-overlay
+@helpfull-action-bar
+repeatable-trial-vaults@
+fast-async-world-save-forge-fabric
+@chat-magic
+
+unlimitedelytra@|无限鞘翅 (UnlimitedElytra)
+recrafted-creatures
+reciperesearch|合成配方研究 (RecipeResearch)
+thaumon@|神秘再饰*
+create-firearms-patch|机械动力火器补丁 (Create Firearms Patch(kinetic pixel))
+create-eureka
+forsaken-corpses@
+cat-variant-eggs@
+more-colorful@morecolorful|更加多彩的世界*
+void-island-control-2-0
+fishing-rod-fix@
+@additionalentityattributes
+@additionalentityattributes-forge
+kubejs-sanity
+
+journeymap-web-map@|旅行地图:网络地图 (JourneyMap Web Map)
+rain-should-extinguish-campfires@
+@mob-death-sounds
+gauntlets@
+
+
+thrown@|扔!*
+tacz-npcs-also-vpb@tacz-npcs
+@scape-and-run-parasites-addon-immalleable-generator|逃逸:无适应性发生器 (Scape and Run Parasites Addon: Immalleable Generator)
+
+
+
+hotdog-delight
+lets-do-addon-corn-expansion@|玉食俱馩 ([Let's Do Addon] Corn Expansion)
+
+@client-maps
+dragon-gear-renewed
+@archers-plushies
+@hephaestus-expansion
+create-structures-arise@
+
+gregtech-extended-chemistry-extended
+ulv-covers-modern
+farmers-structures
+
+@minecartspeeddisplay|矿车速度显示 (MinecartSpeedDisplay)
+crossstitch@
+
+free-camera-api@|自由相机API (FreeCameraAPI)
+@flashback
+bloodmagic-meteors-jei-support|血魔法陨星显示 (BloodMagic Meteors JEI Support)
+limited-damage-indicator-particle@|伤害粒子限制*
+netherite-extension@netheriteextension|下界合金拓展续作*
+immersive-farming@|沉浸农业*
+@vpb-too-many-guns
+death-angels|死亡天使*
+the-pale-hound
+monsters-girls@
+monsters-girls-secrets-of-mermaids
+dios-tiny-ore@
+defence-craft
+
+copper-equipment@exlines-copper-equipment
+terramity@
+
+
+
+neptune-lib@neptune
+sign-edit-lite@signeditlite|告示牌编辑轻量版*
+@book-formatting
+@morewaterlogging
+
+@customspyglassmagnificationoverlay|自定义望远镜覆盖层 (CustomSpyglassMagnificationOverlay)
+@chatting
+@crosshair
+
+respawning-structures-forge-fabric
+@sticky-honey
+horseman@
+@easyplacefix|轻松放置修复 (EasyPlaceFix)
+screenshot-flash@flash|一闪 (Screenshot Flash / Flash)
+warp-zone@
+faded-widgets@|淡出界面*
+knowledges@|知识库*
+panda-genetics-tweak
+colourful-llamas
+@chens-japanese-decoration
+
+create-halitosis-create-halitosis@create-halitosis
+create-unify@
+create-more-sand-papers@create-more-sandpapers
+sawmill-house
+kaboom-moon
+hardcore-revive-mod@
+
+
+petrols-parts@|Petrol 的机件 (Petrol's Parts)
+slavic-delight@
+aquamirae-delight@|海灵乐事*
+dumplings-delight-rewrapped@
+my-nethers-delight-refabricated@
+@polyweather
+shadow-x-axolotl-buckets-plus
+daily-shop@
+
+no-tree-to-destory@|你不准破坏木头!*
+
+
+growable-goodies
+petrolpark-library@petrolpark|Petrolpark 的库 (Petrolpark's Library)
+
+ancient-reforging@
+stackmob-fabric@
+itemindicator@
+kenshiro-mod|北斗神拳*
+roamers
+@legacycaneditsign
+title-screen-mobs@
+
+antiblocksrechiseled@
+
+end-ores
+tnt-expanded
+glimmering-tales|微光纪事*
+fluid-interaction-tweaker|自定义流体交互*
+kubejs-extended-crafting
+kubejs-addon-blood-magic
+custom-portal-builder@
+biome-difficulty
+@viaaprilfools
+tfc-weld-button@
+tfc-regrowing-forests@
+ccdbridge@
+welcome-message@
+auto-attack-rebirth
+lotas@
+tasmod@
+
+betterfly@|飞起来! (BetterFly)
+@leashedplayer|可拴玩家 (LeashedPlayer)
+melodymagic@|旋律魔法 (MelodyMagic)
+scaling-mob-difficulty-fork|知时修倍*
+trajectoryprojections@trajectory|弹道预测 (Trajectory)
+accessories@
+onewholibs
+nucleus@nucleus_pauljoda
+@equator
+accessories-cc-layer@
+accessories-tc-layer@
+@confetti-lib
+
+
+material-replication@
+rsj@
+irikana|伊利康纳*
+mts-iv-aq-studios-himars
+immersive-vehicles-iv-mts-lindqvist@lav
+immersive-vehicles-iv-mts-zroskyan-ordinary
+petroleums-wigs
+revenge-mark|遗怨待战/复仇标记*
+
+
+
+
+@create-coggle
+create-enchantable-machinery@¨create-enchantable-machinery-fabric
+bad-eyes|近视眼*
+bad-dreams@|噩梦*
+turtlebois-rpg-classes
+through-the-lily-pads-gently@
+decofirmacraft@
+@dusty
+@beeg-smol
+scaling-mobs
+multiline-mastery@
+@confetti-stuff
+pirates-treasures
+ars-nouveau-delight@
+crossbow-enchants@
+extendeddrawersaddon@
+minecolonies-letsdo
+earlystage@
+smitherz@
+additionz@
+bjornlib@
+
+
+skewers-reflavored|串起酥食/烤串:再调味*
+linear-xp-forge|验依定值/线性经验 (Linear Xp Curve)
+
+
+dumplings-delight-reload@|饺子乐事:重启*
+auto-switch-elytra@autoswitchelytra|自动切换鞘翅*
+gregtech-placeable-emitters@
+
+
+
+
+zakuro@|榴*
+dropoff
+upgraded-totems
+@player-chains
+anti-creative-drift@
+damage-incorporated@
+
+
+
+
+exmodifier@|极墨重锻-革故鼎新 (ExModifier)
+
+@creepersburn-reborn
+lazy-dfu-forge|DFU载入优化 [FORGE] (Lazy DataFixerUpper(LazyDFU) [FORGE])
+
+creaturechat@
+helldivers-spread-democracy@helldivers-2-spread-democracy
+
+variant-tools-and-weaponry-more-weapons@
+mgrr@
+arcanemysteries@arcane-mysteries
+botanic-pledge@botanicpledge
+rediculous-ore-generation@
+witchy-wearables
+simple-delights-recooked|重启爽食/简单乐事:再烹饪*
+braziliandelight@|巴西乐事 (Brazilian Delight)
+applied-energistics-delight|Applied Energistics Delight - 已失效¨@applied-energistics-delight
+extra-meat
+peculiar-primordials@
+time-wind-forge@time-wind¨time-wind
+@mcbrowser
+additional-trims
+upgraded-trims
+water-balloon@
+cataclysm-spellbooks@
+call-of-drowner@|溺亡者之嚎 (Call of Drowner)
+
+
+
+fastfood-delight-unofficial|重启速食/快餐乐事非官方版 (FastFood Delight Unofficial)
+faster-fly-block-breaking|飞行速速破*
+upgraded-additional-trims
+create-let-the-adventure-begin@
+bountiful-critters
+flashlight-mcowners
+smartpot@|智能锅 (SmartPot)
+
+
+merciful-void|渊佑堕者/慈悲的虚空*
+gsic2|星空工业 (GSIC2)
+open-energistics
+@the-friends-mod
+@construction-wand-fabric
+osmiumspacetiers
+bodyguard@
+@quick-menu|快捷菜单 (Quick Menu)
+lazrs-lib@
+cute-words-chat-replacer|雅言订正/聊天内容替换 (Cute Words)
+
+
+hotbath|热水澡 (Hot Bath)
+@no-double-sneak
+tfc-hammer-time@
+tfc-better-stone-age
+battery-status-info@batterystatusinfo
+mineteam@mine-team
+@nocreativemode|禁用创造模式 (NoCreativeMode)
+@darktitlebar-forge
+croaklib@
+
+nbt-copy
+
+more-totem-effects
+bakeyourbread@bake-your-bread
+modern-debugify
+recipe-essentials-forge-fabric
+zombie-villager-control|催起死尸/僵尸村民控制*
+@1.8-pvp-mod
+@blackscreenquiet|黑屏安静 (BlackScreenQuiet)
+
+@too-many-shortcuts
+better-horse-movement
+tct-core@
+obscure-tooltips-fix
+
+@notica
+
+zhonglv-12et@|钟吕十二律 (ZhongLv-12ET)
+
+
+just-another-random-spawn|遥缘调址/随机出生点*
+@kaimyentity-c|KAI我的实体-C (KAIMyEntity-C)
+realistic-mobs@
+fullthrottle-alchemist
+fullthrottle-nei
+creature-compendium@
+desert-update
+ancient-elements
+left-behind@
+corrupted-witherskeleton-boss-mod
+
+mca-api
+
+sodium-leaf-culling@sodiumleafculling|钠:树叶剔除*
+homing-xp-orb|引验导追/归航经验球*
+multiblocked2
+armorstatushud-updated|耐久信息显示更新版 (ArmorStatusHUD Updated)
+tfc-better-blast-furnace
+tfc-textile@
+tfc-woodworking@
+tfc-auroras@auroras
+greg-paintings@
+gtmthings
+create-trading-floor@|机械动力:交易站点 (Create: Trading Floor)
+@fishy-create-stuffs|Yu的机械动力附属 (Fishy Create Stuffs)
+biolith@
+hungrier@
+
+furnace-pickaxe-mod
+camouflage-doors-and-trapdoor@
+dense-metals
+actually-subtractions@
+mechanical-botania
+puddles
+yoyousukes-kabutoaddon-for-ahzs-shinobicraft
+illager-world-war
+guns-craft-adventurers-arsenal|枪械工艺:冒险者的武器库 (Guns Craft Adventurer's Arsenal)
+@seven-deadly-sins-origins
+difficult-raids-stronghold-survival-edition
+subnautica-flow
+cardinal-sins
+unwrecked-ships-forge¨unwrecked-ships-fabric
+revamped-shipwrecks-mod
+christmas-culinary-desires-decorations@christmas-culinary-desires-(-decorations)
+@florum-sporum
+unofficial-bosses-of-mass-destruction-dungeon
+
+
+@more-things|更多物品 (MoreThings)
+
+explosionvisualizer@|爆炸信息显示
+@more-vanilla-foods|更多原版食物 (More Vanilla Foods)
+let-me-feed-you|吃取送食/玩家喂食 (Let Me Feed You!)
+
+drg-laser-pointer@|深岩银河激光指示器 (DRG Laser Pointer)
+spawner-head
+medieval-core-mod
+darkmining@
+
+@visualkeys
+sodium-extra-information@
+@bulk-villager-trading
+omniscience
+illusion-onslaught
+rar-compat@
+wards
+moremcmeta-emissive-forge@moremcmeta-emissive¨moremcmeta-emissive-fabric
+@fantazia
+falling-attack
+lesraisins-armor@
+goety-spillage@
+waifucraft
+sign-to-chat|阅言得知/聊天栏读取告示牌 (Sign to Chat)
+rainimatormod@
+realistic-airdrop|真实空投*
+keepers-of-the-stones-ii@keepers-of-the-stones-2
+woodencog|Wooden Cog - 最新(已由Manwe_b接手)¨wooden-cog|Wooden Cog - 已不再更新¨@woodencog
+shadow-x-blocks-plus
+pet-essence@
+
+watson@
+
+emerald-matcha-craft-v3|翠茶工藝 (Emerald Matcha Craft)
+cctv-camera
+trash-compactor@
+rustic-delight@|乡村乐事*
+total-assault¨@total-assault|Total Assault - 已弃用
+sky-breaker|天穹突破*
+@scala3std
+
+@hoofprint
+better-controls@
+@optibye
+kubejei
+actually-harvest@
+city-craft@rexs-city-craft
+
+eggs-better-vanilla-food@eggs-better-food
+shelf-mod@
+nebulus-cherryblossom-tree@nebulus-cherry-blossom-tree
+ftb-rickety-water-wheel
+cobblegen-galore
+@ringles-gun-turret
+create-sky-village
+create-crushing
+cushy-pillows|舒适枕头*
+ice-and-fire-patcher@iaf-patcher|冰火传说随意修复*
+goetyrevelation|诡厄巫法:启示录 (Goety: Revelation) - 已失效¨@goety-revelation|诡厄巫法:启示录 (Goety: Revelation)
+the-mandela-catalogue-alternates@
+the-craftela-catalogue
+the-ceilands@|垂顶之地*
+@sharowings-better-dungeons
+snowbound@snow_bound
+opencomputers-icbm-classic-addon¨icbm-opencomputers|Opencomputers ICBM-Classic Addon - 教程文档
+@ad-astra-pandoras-frontier
+cosmic-horizons@cosmic-horizons-cosmos
+shadow-x-netherite-scrap-from-piglin-brutes|猪灵蛮兵掉落下界合金碎片 (Netherite Scrap From Piglin Brutes)
+
+better-modlist-neoforge@better-modlist
+sky-aesthetics@
+headshot-bonus|已无可惧/简单爆头*
+enchantlevelbrek@enchantmentlevelbreak|附魔等级上限突破2 (EnchantmentLevelBreak)
+global-gamerule-fabric@|全局游戏规则Fabric版 (Global GameRule [Fabric])
+biome-borders-revived-liteloader-only
+chunkborders|区块边界 (ChunkBorders)
+burnt@
+goatman@
+
+armor-damage-limit|甲哇!/盔甲耐久损失限制*
+knight-bodyguard-forge|骑士保镖 (Knight Bodyguard)
+deeper-depths
+unseens-nether-backport
+
+shadow-x-adventures-plus
+adventures-in-time@ait
+elementalist
+@craft-slime-fabric|制作黏液球 (Craft Slime Fabric)
+shadow-x-unactivated-totems
+cagebox
+
+rechiseled-ae2@
+archeological@archeologicalmod
+clarity
+backpacked-world-of-color@
+nofov
+cobalt-custom-chunk-renderer|钴 (Cobalt)¨@cobaltmod|钴 (Cobalt) - 已失效
+spartacus-army-uniform
+soviet-army-uniform@
+german-uniforms-ww2
+
+
+
+tameable@
+slashblade-gamerelic|拔刀剑重锋附属·游戏扩增 (srelic)
+theovergrowth
+jadens-nether-expansion@
+
+configurable@
+
+@chatpositiondisplay|聊天框坐标显示 (ChatPositionDisplay)
+
+
+@forge-early-loading-screen-fabric|Forge 加载屏幕移植 ((Neo)Forge Early Loading Screen for Fabric)
+@ebe-forge
+
+patpat
+modern-life-patch@modernlifepatch|摩登生活补丁*
+@ivr
+rusticdelight-refabricated@|乡村乐事:重织 (RusticDelight: Refabricated)
+scape-and-run-meshi@|逃逸:寄生饭 (Scape and Run: Meshi)
+@bad-apple-world-preset
+gastronomy-works@
+@create-air-fabric
+
+colorfulwater
+yungs-cave-biomes@|YUNG的洞穴生物群系 (YUNG's Cave Biomes)¨yungs-cave-biomes-fabric|YUNG的洞穴生物群系 (YUNG's Cave Biomes)
+@legacy-command-registry|旧式命令注册 (Legacy Command Registry)
+@screensaver|屏保 (Screensaver)
+@startuptone|启动提示音 (StartupTone)
+
+
+
+va-11-hall-a-drinks|Vall-11 Hall-A饮品 (Vall-11 Hall-A Drinks)
+bettergive-reborn@bettergivereborn
+@blur-server-address
+foodvariations@|生活调味料:经典版 (Spice of Life: Classic Edition)
+pizza-delight
+simplefog@
+@butter
+tnt-plus-mod|TNT Plus - 所有¨@tnt-plus-mod|TNT Plus - 部分失效
+
+@kelvins-better-player-animations-(port)
+tokusatsurecord|特摄唱片 (Tokusatsu Record)
+
+origins-backgrounds-forge¨origins-backgrounds
+cataclysm-apotheosis-addon
+cave-dweller-reimagined¨cave-dweller-reimagined-fabric
+out-of-combat|脱战 (Out of Combat)
+soulfragment|灵魂碎片 (Soul Fragment)
+
+create-peaceful@
+baby-players
+dynaores|动态粗矿 (Dynamic Raw Ores)
+mc-mods-arrows-infinity-fix
+bundles-backport
+its-magispelling-time
+
+@windows-starting-overlay|Windows启动界面 (WindowsStartingOverlay)
+@dontsneakprogress|偷跑禁止 (DontSneakProgress)
+@unkindled
+recipe-generator@
+@carbonfltr
+argonmod@|氩 (Argon)
+foggypalegarden@
+@items-stack
+arkane-domains
+
+yakumoblade|幻想之刃 / 刀藏录 (yakumoblade)
+no-random-drop-offset|垂直落下/移除掉落随机偏移*
+croptopias-chocolaterie-unofficial|巧克力工坊:非官方版 (Croptopia's Chocolaterie Unofficial)
+epic-fight-wukong-moveset|史诗战斗:悟空 (Epic Fight - Wukong Moveset)
+
+@petphrasex|口癖X (PetPhraseX)
+
+dust-and-ash|尘与烬 (Dust and Ash)
+@particle-interactions
+@trial-spawner-timer|试炼刷怪笼计时器 (Trial Spawner Timer)
+enigmatic-delicacy|神秘佳肴*
+slashblade-resharped-renderfix-patch@|拔刀剑:重锋 渲染修复补丁 (SlashBlade Resharped RenderFix Patch)
+@invulnerable-wheat
+skin3d-but-not-cubic@skins3d-but-not-cubic
+leavesly@
+create-totem-factory
+snad-redstone-edition|子沙:红石版 (Snad:Redstone Edition)
+@party-flames
+subtle-effects@
+@dma
+cc-wasm
+legacy-patch
+
+@glowup
+@customsavedirs
+just-enough-magiculture
+ae2-jei-integration
+entangled-fix
+@customdurabilitybar
+
+scape-and-run-revenants
+warden-s-domain
+more-wardens
+hidden-realm@
+spells-gone-wrong@
+wandrous@
+cnpc-dbc-addon@customnpc-plus-dbc-addon
+salmons-genesis
+the-afterdark
+endlesssword|无尽拔刀剑 (Endless Sword)
+bh3|崩坏3
+
+loaded-mods-checker@loadedmodschecker|存档模组核对*
+i-have-slept@|朕已安寝 (I have slept)
+@itemstackpromax|物品堆叠ProMax (ItemStackProMax)
+item-split-bug-fix|物品分裂修复*
+spontaneous-replace-cobwebbed@|自然更替:蛛丝网迹 (Spontaneous-Replace: Cobwebbed)
+modernconverter
+frozify@
+potato-library
+swisscaves@
+title-screen-blobcat@
+punching-creepers@
+ancient-curses
+resettleable-traders-forge-fabric@resettleable-traders|流浪商人安家 (Resettleable Traders)
+ihearttfc@
+itemphysic-1-7-10-unofficial@itemphysic-1.7.10-unofficial|物品物理掉落非官方版 (ItemPhysic Legacy Unofficial)
+jei-area-fixer
+ars-elixirum-forge@ars-elixirum¨ars-elixirum-neoforge¨ars-elixirum
+gold-nugget-recycling
+sodium-options-api@
+@totom-register
+
+crazyae|疯狂的AE (CrazyAE)
+ae2-crafting-tree-legacy|AE2合成树-传统版 (AE2CT-Legacy)
+ae2-alchemistry-addon
+
+novaultlimit@
+game-discs@|游戏光碟*
+luncheon-meat-s-delight@|午餐肉乐事 (Luncheon Meat 'S Delight)
+
+vanilla-plus-resourceful-tools
+create-horse-power@
+ars-technica
+plustic-reforged@
+spawners-plus@spawners_plus|刷怪笼拓展 (Spawners+)
+dracolotl@
+
+sacks-n-such@
+
+
+camellialib@|山茶花核心 (CamelliaLib)
+cameliaarmory@|花朵武器库 (FlowerArmory)
+
+moonrise@moonrise-opt
+corpsecomplex-unofficial@
+@starlish
+languagereloadunofficial@
+mekanism-covers@
+enchanted-wilderness-elegant-countryside@enchanted-wilderness-elegant-countryside.|雅致乡下 (Elegant Countryside)
+better-durability-reforged|更好的耐久度:重铸*
+sos|我来助你! (SOS)
+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
+the-knocker@
+vll-r-mystery
+scp-origin
+sniffers-delicacies
+fix-changed
+valkyrien-pirates@|瓦尔基里海盗*
+pathogen
+chrysalis@
+alekis-ridiculously-simple-roofs@alekiroofs
+@trails
+rainboows@
+texturized-particles@
+rple@
+reign-of-nether-rts-in-minecraft@reign-of-nether-rts|下界之治 (Reign of Nether RTS)
+the-secret-doors
+
+
+tame-tools@|驯兽工具*
+@bia
+vounierns-turrets
+seafarer-forge
+zombies-plus-frenzied-horde@
+@tetrismc
+ecliptic-seasons|节气*
+xibaoxx@|喜报 ×× (XiBao ××)
+
+
+murasama-daker|鬼妖村正 (Murasama)
+particle-storm|粒子风暴*
+@2d-minecraft
+till-it-breaks-updated@
+debark@
+till-it-breaks@
+end-reborn-expansion@end-reborn
+@origins-genshin
+
+
+the-glitched-one
+progression-reborn@
+
+@simple-info-display
+@automaticmemories
+zoom-camera@
+dpi-fix
+@item-information-display|物品信息显示 (Item Information Display)
+almanac-lib@almanac
+
+remove-blindness|移除失明 (Remove Blindess) - 不全¨@remove-blindess|移除失明 (Remove Blindess)
+simplerpgcore@
+droppable-villager-trades@
+stackupper@
+colorful-orbs@
+@idmxd
+modernxl-2-in-survival
+creepercollateral
+findmyitemsandfluids
+@numeric-experience-info
+aquapatch
+aviator-dreams@
+macaws-stairs@
+cross-stitch-colours@
+gaming-deco@
+@reactive-music|韵律回响 (Reactive Music)
+easel-does-it@
+rbasamoyais-betsy-ross@betsy-ross
+
+
+
+
+
+dropped-item-tweaks@droppeditemtweaks|掉落物调整*
+movingquickly|禁用快速移动检查 (MovingQuickly)
+@soup-api
+@tsa-decorations
+@autofarm-mod
+gelato-galore-2@|意式冰淇淋盛宴*
+dyeable-jack-olanterns@
+white-pumpkins@
+dawn-of-the-flood@
+deatbutthreereload@|逝不过三:重开 (Death But Three Reload)
+common-weapons
+ore-silverfish-mod@
+gtbcs-spellbooks
+project-lambda
+
+
+
+
+
+@pretty-hitboxes-create-addition|可配置的碰撞箱显示-机械动力功能附加 (Pretty Hitbox-Create Addition)
+watersource-reborn@|水源:重生 (WaterSource: Reborn)
+locks-unofficial@|锁:非官方版 (Locks-Unofficial)
+@epicfight_improve|史诗战斗:改进 (EpicFight:Improve)
+just-in-nether@
+jinghui|镜回 (JingHui)
+@ripples-of-the-past-mandom-addon|RotP:男人领域附属 (Ripples of the Past: Mandom Addon)
+@auto-gg
+better-brightness-slider-respawn@
+
+@playergyro
+animatica-foxified@
+mcchatgpt@
+@aimobs
+ambiances@ambiance|氛围! (Ambiance!)
+screw-your-items@
+mace-port
+bloodlines
+vampiric-ageing-a-vampirism-addon
+extra-enchantments-and-curses@|更多附魔和诅咒 (Extra Enchantments and Curses)
+enchanting-tweaker@enchanting-tweaks
+just-a-lot-more-enchantments@
+alessandrvs-enchantments@alessandrv-enchantments
+armor-abilities@
+sleepy-hollows@
+
+nametag-tweaks@nametagtweaks|名称标签调整 (Nametag (Display) Tweaks)
+@sodium-no-alerts
+headshot-respawn@|爆头:重生 (Head Shot Respawn)
+leafdecaystopper|树叶腐烂抑制器 (Leaf Decay Stopper)
+animated-loading-screen@
+@rotp_whitesnake|RotP:白蛇附属 (Whitesnake Addon (Ripples of the Past))
+@ripples-of-the-past-made-in-heaven-addon|RotP:天堂制造附属 (Ripples of the Past: Made in Heaven Addon)
+@ripples-of-the-past-c-moon-addon|RotP:新月附属 (Ripples of the Past: C-Moon Addon)
+imbued-gear@
+tacz-js@
+ipla@
+bcg-util
+sciophobia@|全局自定义文本阴影渲染*
+lithic-addon-mod
+light-blocks-from-1-17@
+light-and-shadow@
+excavein@
+spanish-delight-a-farmers-delight-add-on@spanish-delight|西班牙乐事 (Spanish Delight)
+ars-nouveaus-flavors-delight@arsdelight|新生乐事 (Ars Nouveau's Flavors & Delight / Ars Delight)
+oceanic-delight@|海之乐事*
+the-player
+kelka-craftable-animals@craftableanimals
+super-hyper
+wans-ancient-beasts@wab
+invocations-spell-engine@invocations
+gearifiers@
+recall-potion-neoforge@|回忆药水 (Recall Potion)
+flowing-fluids@
+frights-and-foliage@
+blorfstone
+filinkus-s-huge-tools
+necromancer-mod
+e_kedis-electronics
+uprade-yourself
+
+smelting-touch-enchantment
+shinobiorigins-utils
+unifine
+trailier-tales@
+mixinjs-coremod
+mcwen
+rank-system|评分系统*
+@creative-one-punch-forge
+bundle-inventory|Bundle Inventory - 问题反馈¨@bundle-inventory
+scroll-tweaks@scrolltweaks|鼠标滚轮调整*
+neko-server@
+ride-every-thing@|骑乘一切*
+cit-resewn-neopatcher@
+legendary-creatures|传说生物*
+@meowcraft-mod
+
+
+
+
+frozenlib@
+clientblock@
+eventjs
+backup-manager@
+the-root-of-fear@
+the-modifiger@
+@keypreset|键位预设 (KeyPreset)
+@tweakeroo-features-menu
+sanity-prequel|理智:前传 (Sanity: Prequel)
+ayame-paperdoll@|Ayame 纸娃娃 (Ayame PaperDoll)
+@registry-helper-lib
+@carpet-permissions
+passive-shield@
+trap-expansion
+roost-2-flying-higher
+@amber
+@liteminer
+@lvlz-better-nether
+@lvlz-better-end
+cc-shops@
+modular-machinery-reborn@
+flare@
+rs-crafting-monitor-in-grid@
+@photonics
+lambdynamiclights-unofficial-neoforge@
+
+@heracles-button
+voicecontrol|声控*
+hex-ars-linker@|魔力媒质链接 (Hex-Ars Linker)
+bingolobby@|宾果游戏大厅 (BingoLobby)
+worldedit-legacy@worldedit-legacy-enhanced
+@nohealthbar
+asm-fixes-j-a-f-m
+moestweaks
+prickle@
+farming-3x3@
+trickery@
+kebab@bogismokkebab
+farmers-croptopia@
+mc-mods-trees-drop-apples
+infinite-dimensions@
+
+mofus-broken-constellations
+starlance@
+beneath@
+@simple-client-base|简易客户端:基础 (SimpleClientBase)
+
+
+ultimate-enchantment@|终极附魔*
+ron-settings
+backpack-display@|背包物品内容显示*
+horrrs-pvz|Horrrs的植物大战僵尸*
+@optibabric
+item-restrictions@
+vintage-animations@
+the-one-who-watches@
+scp-ultimate
+herobrines-omen
+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
+zbgt@
+casinocraft
+@mql-tir5d-turtle
+toadlib@
+
+
+crosshair-tweaks@crosshairtweaks|十字准星调整*
+what-did-i-just-kill@
+scalablelux@
+weather-changer@
+iron-nugget-recycling
+@picohud
+re-exposer@|Exposer 重置版 (Re-Exposer)
+gtmoreoreprocessing@
+computercraftedu
+bibliocraft-legacy
+simple-clouds@
+
+
+furniture-modcraft@several-furniture-craft
+
+spellblades-and-such-hexblade
+
+sons-of-sins-organs-additions|七罪之子:器官添加 (Sons Of Sins: Organs Additions)
+sons-of-sins-organs-museum|七罪之子:器官博物馆 (Sons of Sins: Organ Museum)
+mobs-of-sins-alexs-mobs-integration-forge|七罪之子: Alex的生物集成 (Mobs of Sins: Alex's Mobs Integration [Forge])
+stitched-sins|缝合的罪*
+@bio-delight|血肉乐事 (Biomantic Delight)
+
+the-sculpture
+goop@
+neepmeat@
+
+
+
+@huangsheng1s-more-monster|更多怪物 (HuangSheng1's More Monster)
+creeping-woods
+@zombie-apocalypse-core
+dragon-mounts-2-expanded@dragon-mounts-2|龙骑士2:延续 (DragonMounts2: Expanded)
+life-fruits@|生命果*
+ati-structures
+fantasy-armor@fantasy_armor
+beautiful-enchanted-books@beautiful-enchanted-books-mod-edition
+
+
+void-fog@voidfog
+@legacy-rocket-model|旧版火箭模型 (Legacy Rocket Model)
+
+
+
+tinker-i-o-ce|工匠接口社区版 (Tinker I/O CE)
+more-shield-variants@
+
+bendy-items
+paladins-oath
+
+roundabout-the-jojo-mod@roundabout_the_jojo_mod
+
+
+enviromine-continuation@
+@toffys-hooks
+mutants-buff
+
+@blockplayer|玩家方块模型 (BlockPlayer)
+terrablenderfix@
+craterlib@
+@caramel-chat
+spark-unforged@
+pack-mode-reborn
+mc-mods-the-wood-weve-got@all-the-wood-weve-got
+bibliowoods-legacy
+hydrological@
+unilib@
+
+modchu_playerformlittlemaidfml
+quiet|迁噪碍音/玩家禁言*
+galacticraft-compatibility@|星系兼容*
+@bettertab
+modular-machinery-reborn-mekanism@
+
+dawn-of-time-roa-edition
+buildpaste¨buildpaste-for-fabric
+mutils@
+better-campfires@|更好的营火*
+@mtr-renfe-addon
+gt-community-additions@
+more-create-burners|机械动力:燃烧室附加*
+create-logistics@
+create-pneumatic-equipment@
+collisiondamage|碰撞伤害 (CollisionDamage)
+@absolutely-proprietary
+tensura-reincarnated@|关于我转生变成史莱姆这档事:转生 (Tensura: Reincarnated)
+transformer|奥特化身:无限 (Becomes Ultraman:Infinity)
+henshin|变身*
+ridiculous-world
+
+quartz_craft@|石英工艺 (Quartz_Craft)
+cursed-stare@
+legendblade|传世之刃 (LegendBlade)
+@hypothermic-reforged
+scape-and-run-parasites-extra@|逃逸:寄生体扩展 (Scape and Run: Parasites Extra)
+lauchs-shutters@shutters
+golden-foods@golden-foods!
+@alexscaves-delight
+@spmreborn|烤地瓜:重生 (Sweet Potato Reborn)
+dragon-fight-config-endergetic-addon
+hour-clock-24@|24小时制 (24Hourclock)
+
+
+celestial-forge|星月再锻*
+mahjongcraft@|麻将工艺 (MahjongCraft)
+formidulus@
+ames-stranger-things
+herobrine-origins
+milkable-blazes@
+
+furniture-beetle@
+mekanism-x-create-northstar@
+@createz
+create-smart-crafter@
+create-fuel-and-water-information@create-fuel-water-information
+thematic@
+more-composter-variants@
+more-fletching-tables@
+more-grindstone-variants@
+more-bed-variants@
+more-loom-variants@
+more-feeding-trough-variants@
+more-smoker-variants@
+manascore@
+ames-mobs-configuration|AME生物配置 (Ame's Mobs' Configuration)
+display-delight@
+realistic-torches-roa-edition
+explorers-compass-edited|探险者指南针 修改 (Explorer's Compass Edited)
+
+@slime
+x-backup@
+iron-barrels-forge@iron-barrels
+spectral-decorations@
+
+
+trims-on-tools@
+galacticraft-automatic-resources|星系自动资源*
+
+@lx-light-assist-tool|轻辅助工具 (light assist tool)
+
+
+roberts-building-pack|Robert的建筑扩展包 (Robert's Building Pack)
+arcanus-continuum@
+ultimate-chess@
+sparkweave@
+visualjs
+lights-out
+a-night-stalker
+the-faceless-duo
+scape-and-run-parasites-meteorite@|逃逸:祸从天降 (Scape and Run: Parasites Meteorite)
+cravencrafts-bloody-bits@
+
+@carcrash|撞车 (CarCrash)
+
+fastlang
+min-3-hall-a@
+modular-machinery-reborn-energistics@
+tweaks-for-galactic-science
+java-version-checker
+chatcopyrite
+chat-boost|聊天增强*
+terracart
+noteblock-backport@
+scape-and-run-parasite-dimension@srdimension|逃逸:寄染之世 (Scape and Run: Dimension)
+hexdebug@|咒术调试 (HexDebug)
+paths-of-sin@
+@mapperplugin
+@enchantment-decomposition|附魔分解 (Enchantment Dismantling)
+hide-key-binding@|隐藏按键绑定*
+
+inline@
+fluffy-fur@
+
+
+head-cutter|玩家剪头*
+salty-s-realistic-forging@saltys-realistic-forging
+@vs-wakes-compat
+@vs-crumbles
+@vs-ship-in-a-bottle
+rustic-pancakes@
+ice-and-fire-delight@|冰火传说乐事 (Ice and Fire Delight)
+additional-dragons
+enigmatic-unity
+ephemera@
+theurgy-kubejs@
+immersive-lanterns@
+txnilib@
+default-key-setup@|默认按键配置*
+the-man-king
+create-arms-race@
+powered-flashlight@
+vmc-mc@virtual-motion-capture-for-minecraft
+@hexcellular
+hex-conjuring@|咒术构筑*
+hexjs@
+craftsense@
+
+zenkorium-creatures
+zhengelss-techguns-addon
+more-artifacts
+
+tfcgenviewer@
+terra-math@
+lukis-crazy-chambers
+tumbleweed-wasteland
+irons-jewelry
+atlas-api
+loading-screen-messages
+@pipesounds
+nemos-invertory-sorting@nemos-inventory-sorting
+@better-ux
+@hudmini
+ribs-panning
+hotkettles@
+gift-drop@
+dragns-livestock-overhaul@
+storage-drawers-unofficial@|储物抽屉非官方版*
+
+centrifuge-tiers-reproduced@
+dense-mekanism@
+simple-missles@
+galosphere-spellbooks
+discerning-the-eldritch
+@hexcassettes
+wardens-plus
+ender-wyrmlings
+
+
+new-classic-battle-towers
+chitin-equipment@|甲壳装备*
+mowzies-cataclysm|Mowzie的灾变 (Mowzie's Cataclysm)
+stardew-fishing-fabric@
+piglin-slayer@
+cataclysm-tools@
+witherite-gear
+faunus@
+oo-ee-a-e-a@
+@translator|翻译器 (Translator)
+
+
+sodium-embeddium-options-mod-compat@sodium-options-mod-compat
+secureseed-reborn
+creative-eats
+mazerooms@
+friendly-chests
+@chitinous_ties
+brewable-dragons-breath@|龙息可酿造 (Brewable Dragon's Breath)
+flerovium|鈇*
+
+
+gathering-torches-become-sunlight|薪火,造炬成阳*
+
+@morechathistory-neoforge|更多聊天记录-NeoForge (MoreChatHistoryNeoForge)
+no-arrows-in-you-respawn@no-arrows-in-yourespawn
+
+glowcase@
+hitbox-api@
+@polynametag
+broken-nametags@
+@compostable-flesh
+a-cute-little-crock-pot@
+@bountifulharvest|感恩节乐事 (Bountiful Harvest)
+wasteland-grocers
+arcane-tablet
+@unflavoured-pipes
+curvy-pipes@
+
+
+classic-bar-legacy|经典状态条 延续版*
+glowing-eyeblossom@
+openlink@|开放式联机 (OpenLink)
+@vs-grapples
+@mydriasis
+no-mob-spawning-on-plants@
+
+durability-rework@
+more-villagers-re-employed@
+similsax-transtructors
+tcw-bedrock-breaker
+apothic-reforge-tokens
+advanced-skills-remastered@|进阶技能:重制版 (Advanced Skills: Remastered)
+monsters-and-dungeons
+cultivate-the-eternal-realm@
+too-much-bosses
+valoria@
+glowroot@
+@instaminable-deepslate
+
+greedy-bag
+naughthirium@
+chunkpurge|区块清除 (ChunkPurge)
+kubejs-tfmg@
+gauges-and-switches-ported
+mifa@
+tis-vs@
+pistronics-2
+
+
+gladius-combat-evolved
+hungry-chests
+better-withered-mobs@
+pkgbadges@
+@carbasa
+countereds-settlement-roads@
+countereds-terrain-slabs@
+
+explosive-enhancement-reforged@explosive-enhancement-forge
+tmtravlrs-arrow-trails|Tmtravlr的箭矢轨迹 (Tmtravlr's Arrow Trails)
+biome-replacer@|群系替换器*¨biome-replacer-neoforge@|群系替换器 (Biome Replacer)
+custom-suspicious-stew|自定义谜之炖菜*
+
+
+@create-stress-debug
+create-kart
+@barometry
+
+menufpsunlocker@
+bogged-spawn@
+unicode-fix@
+insanity
+sepals@
+frustum-culling-advanced
+
+
+star-meow-craft|星喵工艺*
+@defile
+vanishmod@
+@eat-it
+f1-y-f5-mod@perspective-lock
+meddlebootstrap
+mc-modstheleaks
+walk-overhaul
+no-strip@
+mcplus-remastered|MCPlus:重置版 (MCPlus Remastered)
+instant-classic-houses
+hexparse@
+betterraid|更好的劫掠 (BetterRaid)
+villagermetafix
+create-better-villager@create-better-villagers
+mc-modsjs@filesjs
+robotic-parts-first-aid-compat
+craftingpad-fabric@craftingpad
+
+exlines-sushi-mod@sushimod|寿司 (Sushi Mod)
+demanding-saplings@
+coastal-waves@
+@no-swimming
+
+stone-zone@|泛用兼容:石材 (Every Compat*)
+
+a-man-with-plushies@
+enchantmentransfer@|附魔转移 (Enchantment Transfer)
+
+
+
+@async
+@mtr-ante|无名的纸板箱交通拓展 (Aphrodite's Nemo's Transit Expansion)
+touchcontroller@|触摸控制器 (TouchController)
+
+let-me-click-and-send@
+
+careful-cast-corrector-ccc
+4691812@lagbgon-revived
+project-steam
+extended-crafting-terminals-for-applied
+sweety-archeo@sweety-archaeology
+
+chamber-clarity-tac-z@tacz-chamber-clarity
+@techguns
+
+@jdinpack
+selfexpression-slim@
+nothing-dimension@
+@glassitemframes
+portable-stations
+new-world-mod@
+
+
+
+
+book-scroll@
+@journalmod|日志模组 (Journal Mod)
+@autotool-ks|自动工具 (AutoTool)
+@fakevanilla
+
+
+
+
+@qca
+mouse-tweaks-unofficial|鼠标手势非官方版*
+baubles-lts
+elysium-api@
+noisethreader
+@no-bats
+antibat@
+illage-and-spell-age
+origincore|起源核心*
+
+
+heaven-destiny-moment|天命时刻*
+industrial-foregoing-extra-upgrades|工业先锋:更多升级 (Industrial Foregoing: Extra Upgrades)
+growableores-extension@|甘蔗转换机 (GrowableOres Extension)
+oneiricconcept@|梦华构想 (OneiricConcept)
+simelectricity
+somnia-refreshed@
+terramoment|泰拉时刻 (TerraMoment)
+miners-diary-beyond-village
+biggerchathistory
+unifier
+compass-ribbon@
+
+@elytra-hud
+creeper-drops-tnt
+visual-novel-framework@
+@experience-progress
+yapping-tooltips@
+bird-nests@birdnest
+tfc-purified-water
+@exparticle|粒子扩展 (ExParticle)
+@entity-render-disabler
+quick-right-click@
+alchimiae-magicae@|炼金魔法师 (Alchimicae Magicae)
+thaumic-forever
+gregfluxology|GTCEu/GTM能源拓展*
+create-crowns@
+@create-modular-tools
+ae2-wireless-terminal-lts
+@cme-is-bad|并发修改什么的不要鸭 (CME is Bad)
+enchantment-harmony@|和谐相处的附魔书 (EnchantmentHarmony)
+quick-refine|快捷锻刀*
+rubinated-nether@|耀红下界*
+cornucopia-bypizza573@cornucopiabypizza573|丰饶之角 (Cornucopia)
+epic-fight-bow-tweak@
+
+jjkutilities
+crystal-chronicles@
+
+water-particles-render-fix
+ray-trace-library
+delayed-thunder@
+book-and-quick-save@
+keybaordjs@keyboardjs
+better-eating-mod@better-eating
+
+@heart-effects
+@global-wind
+botaniavisualizer
+
+manavisualizer
+easierworldcreator@ewc
+kubejs-diesel-generators@
+create-slugma@
+cobblemonrider@cobblemonrider1.5
+cobbledex@cobblemon-pokedex
+
+item-descriptions@
+item-production-lib
+
+connectedtexturesmod-backport
+@litematica-printer-easyplace-extension|投影打印机 (Litematica-Printer-Easyplace-Extension)
+elegant-architecture@|雅致画栋*
+extended-workbench-reborn|扩展工作台重置版*
+
+@stans-deathshot
+pathunderfencegates@
+
+latiao-craft-2@|辣条工艺 2*
+canned@
+mushroooms@
+tacz-compatibility
+@baablu-natural-disasters
+@tacz-tweaks
+un-saddle
+@item-landing-sound
+rehooked@
+
+
+
+better-night-vision@betternightvision|更好的夜视*
+
+manors-bounty|庄园馀事 (Manor's Bounty)
+
+
+
+
+
+vanilla-anvil-repair
+tiered-depths
+tooltip-tweaker
+editableedibles@
+
+@mcdr-command-fabric
+betterbuilderswandsfix@
+pigzilla@
+pale-garden-backport@
+seasons-greetings@
+ex-nihilo-coloratus|无中生有:多彩 (Ex Nihilo: Coloratus)
+granular-mob-griefing
+@dashloader-portinglib-compat
+fermiumbooter2mixinbooter
+immersive-optimization@
+
+tab-tweaks@tabtweaks
+fermiumasm@
+mc-mods-mobs-out-of-the-pool
+clipboardplus@clipboard
+misty-world-unveiled
+balkons-weaponmod-legacy@|更多武器:传承 (Balkon's WeaponMod: Legacy)
+betterendforge-backport
+epic-fight-mod-rapier-moveset-addon
+legendary-warriors
+scape-and-run-holiday@|逃逸:节日 (Scape and Run: Holiday)
+crimson-steves-mutant-mobs@
+
+tactical-imbuements
+puppy-paws@
+ae2-mega-things
+solar-flux-reboot
+solar-flux-additions
+anvil-cell-workbench
+@ultimate-furnace
+create-sifting-fabric@
+create-magics@
+@crypto
+@polyfactory
+buildcraft-legacy@legacy-buildcraft
+
+wither-storm-delight|风暴乐事*
+
+x1000-botania@1000x-botania
+angel-ring-classic|天使指环经典版*
+
+better-animations-collection-revived-2
+scape-and-run-parasites-tweaker
+scape-and-run-parasites-medical-addon
+durability-overhaul@
+quick-hotkeys@quick_hotkeys
+@alwayschristmasday|永远的圣诞节 (AlwaysChristmasDay)
+@strongluck|强运 (StrongLuck)
+
+uncrafted@
+mcjtylib-refilmed@
+nanny@
+resource-library@
+resource-config-api@
+@threatengl
+hybridfix|混合修复 (HybridFix)
+@disable-portal-checks
+eat-eat-eat|吃吃吃*
+@recipe-stages-js|配方阶段 (Recipe Stages JS)
+create-morerecipes
+portable-craft-bench
+portable-tables@ptables
+@flux-network-fork-neoforge
+resource-backpacks@
+etherology@
+snackpirates-aeromancy-additions@aeromancy-additions
+apothic-enchanting
+maidsoul-kitchen@|女仆厨房*
+refinedtools@
+programmed-circuit-card@
+ae2-toogleable-view-cell@ae2-toggleable-viewcell|AE2可切换显示元件 (AE2 Toggleable ViewCell)
+@cobblemon-armors
+letmorefishlove@
+l_ender-s-delight
+ofaw-of-faith-and-war-1237|信仰与战争1237 (Of Faith and War 1237)
+lotmoresteves
+
+legendary-armory-zelda-mod@legendary-armory
+hot-update-1-23-by-gt@hot-update-1.23-by-gt
+management-wanted@
+perception@
+fancy-vfx@
+
+pretty-rain@
+alchemancy
+flavorful
+heavy-fallings@
+erudite-tweaks
+keymap-presets@
+mekanism-no-thanks-nbt@
+@loot-beams-up
+@tekst
+ledger-databases@
+@enderman-death-silence
+infectious-zombie-apocalypse
+xiaoanfc@|小安的农业核心 (Xiaoan's Farming Core)
+@enchant-golden-apple-reborn|附魔金苹果重生 (Enchant Golden Apple Reborn)
+cursor-mod-reload@|自定义光标:重生 (Custom Cursor: Reload)
+
+celestial-overhaul|星月改革*
+simple-faqi@|简单法器 (Simple FaQi)
+redes-coins|Rede的硬币 (Rede's Coins)
+@teleport-commands|传送指令 (Teleport Commands)
+@generic-code-library
+cosmeticarmours@
+ooo-jar@000.jar
+spawner-tweaks
+mob-brawlers
+@transformhandlers
+player-totem@
+@screenshot-uploader
+cute-companions-ducks
+loot-n-explore@
+epic-knights-ice-and-fire
+medieval-origins-revival@
+construction-sticks@
+sophisticated-storage-in-motion@|精妙存储:矿车物流 (Sophisticated Storage in Motion)
+imgur-display@
+extra-sponges@extrasponges|更多海绵*
+more-wolf-armors@|更多狼铠*
+not-enough-rocks@
+pedestal@
+@snowball-and-egg-knockback
+forestry-worktable-display
+accessorify
+high-quality-crops|高品质作物*
+immortal-enchantment@|不死附魔*
+awesome-storage|魔法存储*
+@wallpaper
+
+@modify-player-data
+idcv@|ID冲突查看器 (ID Conflicts Viewer)
+eaglemixins
+kubejs-thermal-augments
+@minopp
+
+
+
+
+camera-anim@|相机大师*
+mekanism-upgrades-reborn|通用机械升级突破:重生 (Mekanism Upgrades: Reborn)
+
+epic-fight-x-irons-spells-animation-fix@
+epic-fight-x-wings-reborn-animation-fix
+ef-bs@battle_arts
+alshanexs-familiars
+feur-skyland@
+balkons-expansion
+
+ascendant-mobs@
+duarm|羽化盔甲 (DuArm)
+bomd-addon
+create-the-air-war@
+bew76@
+block-swapper@
+@simplemap|简易地图 (SimpleMap)
+@oneconfig
+iron-chest-unofficial@|更多箱子非官方版*
+notenoughids-unofficial-1-7-10@notenoughids-unofficial|增加ID上限非官方版 (NotEnoughIDs Unofficial)
+@shutdown
+
+tornado-mod-classic@weather-and-tornadoes-classic|局部气候&风暴经典版 (Weather and Tornadoes Classic)
+strange-berries@
+jeffs-cursed-walking-structures
+terra-entity|泰拉生物*
+not-interested@
+@new-soviet
+
+
+ragexs-nuclear-tech|Ragex的核科技 (Ragex's Nuclear Tech)
+civcraft
+create-springs-loaded
+mekanism-the-factory-must-grow-compatibility@mekanism-tfmg-compat
+cfm-refurbished-circuit-breaker
+diving-bell-for-tfc
+
+piglin-safety-reburn|猪灵 多喝岩浆:重生 (Piglin Safety: Reburn)
+papers-please|请出示证件 (Papers, Please)
+the-twilight-forest-unofficial@|暮色森林非官方版*
+somnia-awoken-gui-beautify|梦醒时分 - GUI美化 (Somnia Awoken - GUI Beautify)
+redstonecraft@redstonecrafts|红石工艺 (RedStoneCraft)
+mowzies-mobs-baubles|Mowzie 的生物饰品 (Mowzie's Mobs' Baubles)
+
+
+dungeons-and-combat
+@blossomlib
+mod-erate-loading-screen-forge@
+fastquit-forge@
+ingame-info-reborn|游戏信息显示复兴版 (InGame Info Reborn)
+@overlay-mod
+bedisnottoofarawayrefoxed|就要睡觉 (Bed Is Not Too Far Away Refoxed)
+goopreforged@
+
+@crimes-against-the-jvm
+better-split-stack
+sdm-engine-core@sdm-core
+sdm-gamestages@
+
+
+huh-fabric@huh|哼? (Huh?)
+@acme-admin-tools
+custom-nausea@¨custom-nausea-fabric
+accesstransformerjs
+
+checklist
+
+
+
+
+puncher@
+sensible-stackables
+rei-addons-ic2classic
+@blossomtpa
+@lithium-raycast-fix
+@yet-another-minecraft-bingo
+tools-attack|工具也横扫!*
+darkster-epic-guns-tacz-epicfight
+epic-tweaks@
+epic-fight-x-gliders
+
+
+familiar-faces@
+eyes-in-the-dark
+
+jellyfishing-delight@|抓水母乐事*
+
+carrot-rarity@
+more-husbandry
+chest-with-legs@
+biodiverse
+railway-wires
+bullseye|靶心*
+
+
+create-mob-spawners@
+salvation-archives-lobotomy
+rlcraft-parasited
+dregora-rl
+underneath
+@leaf-me-alone
+rocket-mons
+farsighted-mobs@
+@hev-suit-voice-system¨@h.e.v-suit-voice-system|HEV Suit Voice System - 已失效
+astral-sorcery-anti-anti-fake-player-asaafp@astral-sorcery-anti-anti-fake-player
+apotheosis-ascended@
+@better-than-wolves-legacy-unofficial
+warium
+@ait_plus
+fabulous-fletching
+universal-wrench@
+@bottle_ship|瓶中船 (Bottle Ship)
+smallblueslimes-ladders
+doomsday-decoration|末日装饰*
+overclocked-watches@
+portable-wardrobes-easy-armor-swap
+
+
+
+@eternalappetite|永恒食欲 (Eternal Appetite)
+
+platform@
+wandering-cocoa@|流浪的可可豆*
+
+no-more-potion-particles@
+larion-world-generation@larion-worldgen
+blueprint-texture-issue-fix
+transparentcapespatch@|透明披风修复 (TransparentCapesPatch)
+
+
+saltyfish-vehicle@
+
+tacz-leawinds-third-person-compat@
+
+
+gtmaid|血汗工厂 (GTMaid)
+
+@damage-tilt-fix
+illager-raid-music@
+@apron
+@coordinated-compass
+prinegorerouse|尼格洛兹·旧日重铸/尼格洛兹·万神重归*
+feur-extension-desert@
+scape-and-run-parasites-quark@|逃逸:疫染夸克 (Scape and Run: Parasites Quark)
+infested-swarms-and-spiders@
+integrated-cataclysm
+
+
+
+
+@searchworldenhance
+colonies-maid-citizen@|殖民地女仆市民*
+relictium@
+royal-city-gladiolus-sweep|王城剑兰-清理*
+@inventory-blur
+personal-cloud-storage@|个人云存储*
+
+@omni-mobs
+@him
+steve-anomaly@the-anomaly
+integrated-simply-swords
+obsidian-equipment@
+
+
+
+the-deep-void@(mortius)-the-deep-void
+
+cataclysm-incinerators-try-hard|灾变:武器技参数配置/冒火的剑 (Cataclysm: Weapon Skill Config/ Incinerator's Try Hard)
+scp-apollyon-scp-apollyon
+trickster@
+pale-garden
+shulker-enchantments@
+xenotech@
+sewing-kit
+zephyr@zephyr-mod
+create-radars@|机械动力:雷达 (Create: Radars)
+create-more-additions@
+
+
+minecart-turning-reforged@¨@minecart-turning
+offlimits@
+apotheosis-threat
+@string-duper-fix-remover
+
+special-model-loader@
+vs-ship-handler@
+
+
+@dice-rebuild
+lets-do-apple-wood@lets-do-addon-apple-wood
+ascended-quark@
+enchanted-vertical-slabs@
+@better-ladders
+would@
+
+gendustry-community-edition@|基因工业:社区版 (Gendustry: Community Edition)
+ae2fix@|AE2修复 (AE2Fix)
+
+
+betterenddelight@|更好的末地乐事 (BetterEndDelight)
+
+@spanish-delight-refabricated
+crate-delight-croptopia@|装箱乐事:作物盛景 (Crate Delight: Croptopia)
+foodspoiling@
+natura-legacy
+
+
+
+
+
+
+riding-partners-reforged@riding-partnersreforged|双人骑行:重铸 (Riding Partners:Reforged)
+
+cat-healing
+plumbob
+@variants-cit
+
+@more-enchantments-soybeani-more-enchantments|豆浆更多附魔 (More Enchantments by Soybeani)
+
+@clearviews
+@immersive_weapons_sounds
+epic-fight-invincible|无坚不摧 (Epic Fight - Invincible)
+ore-dict-re-order
+electrostatics
+
+evil-captcha@
+
+
+
+
+mythic-charms@|神话护符*
+
+
+
+minecraft-add@|原版补充*
+stacked-blocks@|捆扎方块*
+stacked-blocks-farmers-delight@|捆扎方块:农夫乐事 (Stacked Blocks: Farmer's Delight)
+@fowlplay
+
+ender-totem
+@faewufs-diversity
+tonsofenchants
+
+
+cave-root
+
+whos-there-ghost-players@
+the-cuckoo-clock
+final-samurai
+classic-battle-towers|经典战斗高塔*
+sihywtcamd-extensions@difficulty-extensions
+
+@heart-crystals
+@partialhearts
+@the-abyss-runtime
+cosy-critters@
+omnilib@
+astages
+@aloria-loading
+@parentalcontrols
+jason-bot|Jason机器人*
+
+fly-in-the-sky|高空飞行 (Fly in the sky)
+@smartelytra|智能鞘翅 (SmartElytra)
+momotinker|墨工坊 (momotinker)
+electrostaticsre|每日任务 (ElectrostaticsRE)
+
+brooms-unofficial|扫帚:非官方版*
+fishing-dangers
+distinctpaintings@
+@search-on-mcmod|MC百科搜索 (Search on MCMOD)
+infinite-lava-renewed
+@better-fabric-console
+
+fastchest-reforged
+@better-caves-archaic|古早洞穴优化 (Better Caves Archaic)
+
+alexis-64-food
+cosmetic-armor-revitalized-fabric@
+@ambience-v2
+umapyoi-addon-pack@umapyoi-addon|马儿蹦跳:拓展包 (Umapyoi:Addon Pack)
+the-god-of-roadkill
+@visual-snowy-leaves
+verdant@verdant-mod|葱茏绿意*
+grounded@
+gallery@mod
+
+lost-cities-survive-the-dead-edition
+
+
+power-of-the-void|虚空之力 (Power of the Void)
+aerlune-rpg
+
+hitboxbackport
+fsmm@
+create-pantographs-and-wires@
+footwork-api@footwork
+@halplibe
+
+
+crash-assistant@
+
+
+inventorytweak@
+@miss
+legendary-tabs|传说选项卡*
+sky-breaker-blue-skies-limit-remover-1-18-2|天穹突破_1.18.2 (Sky Breaker_1.18.2)
+
+
+
+
+@mxa-tools|MX的工具A (MXA Tools)
+
+xiyus-lucky-block|饩雨的幸运方块 (Xiyu's Lucky Block)
+@epicfight_oldsolar|史诗战斗:旧版solar (EpicFight_oldsolar)
+reskillable-reimagined
+renaissance-core
+@startup-chime|启动音效 (Startup Chime)
+carpet-takeneko-addition@tnca|竹猫的地毯扩展*
+chain-mod
+speedrunmod
+insta-boom
+infinite-night
+nether-lake
+@create-cards
+create-powerlines
+emerald-nugget
+what-dah-dog-doin-tetra-compat
+endbiome
+jasons-fly-mod
+conium@
+yellowtext@
+
+healight@
+@body-health-system
+savemykeybinds@
+nomenublur@
+
+more-loot-tables@
+noteblocks
+@crunchy-crunchy-advancements
+chainvein@
+mobs-on-rails@
+@no-ones-view-model
+gravestone-x-curios-api-compat
+nolijium@
+eventslib
+etst-lib|乙硫醇的匠魂词条库 (EtST Lib)¨@etstlib|乙硫醇的匠魂词条库 (EtST Lib) - 更新可能较慢
+orbs-of-crafting
+@leonmtrmod|Leon轨道交通Mod
+
+
+
+
+
+whatimpressing@|正击何键 (What I'm Pressing)
+
+
+stackcraft@
+@extremeview
+@materialexcavator
+@startinthenether
+mekanismmixinhelp
+simple-warp-commands|简单传送指令 (Simple warp commands)
+@mojangfix
+tatercart@
+irons-spell-book-mob-attribute-addon
+epic-fightexsilium-gladius|EpicFight—放逐之刃 (Epic Fight — Exsilium Gladius)
+@kamenriderweaponcraft|假面骑士武器大全集 (KamenRiderWeaponCraft)
+
+
+@blood-blade
+@mineimpact|方神 (MineImpact)
+jadens-nether-expansion-delight@
+
+archery-expansion@
+jjsk
+
+@so-crazy-try|趣亦合成 (So Crazy Try)
+ash-of-sin-overlord|罪业余烬:不死者之王 (Ash Of Sin: Overlord)
+carbon-strip-craft-augmented-edition@|碳条工艺:加强版 (Carbon Strip Craft:Augmented Edition)
+@ripples-of-the-past-bad-company-addon|RotP:极恶中队附属 (Ripples of the Past: Bad Company Addon)
+true-prime-piece
+maxims-core|箴言与本源 (Maxims&Core)
+arphex@
+cubic-combat-simulator
+
+webmapview@|网页地图浏览 (webmapview)
+f3-is-my-shawty
+ollamachat¨@ollamachat|ollamachat - 正在审核
+potion-stacker
+@betterweather
+better-gamemode
+better-command
+fishermans-haven@
+bakeries|烘焙坊*
+
+female-plastic-surgery-bust-limit-tweaker
+
+feur-pet-hat-forge@feur-pet-hat
+feur-dimensional-skyland@
+feur-colors@
+feur-statue@
+feur-builder@
+feur-elytra-wings@
+feur-magic-more-enchantments-xp-bottle-forge@feur-magic
+veggies-delight@|蔬菜乐事*
+
+ramadan-delight@|斋月乐事*
+
+
+potion-core-reborn|药水核心:重生*
+
+rolling-gate@|卷帘门*
+bettertab@better-tab|更好的Tab (BetterTab)
+
+put-mana-in|手充魔力*
+ars-botania
+
+baublesex
+custom-block-highlight@|自定义方块高亮*
+tinkers-simple
+threedmusket
+rtmmetro|真实火车:地铁扩展 (RTMMetro)
+
+
+
+lobotomy
+parrot-power
+fortune-ores-2
+
+
+feur-dungeon-spawner@
+feur-extension-fossil@
+feur-extension-jungle@
+cybernetic-system|赛博系统*
+
+
+liquid-dirt
+liquid-blocks@
+apothic-combat@
+toms-trading-network@
+somanyapples|超多苹果 (So Many Apples)
+static-block
+cherryvillage
+ductwork@|管道*
+cooldown-coordinator@
+librarianlib-continuous@
+luna@
+loot-integrations
+realtimescale
+
+crafting-recipe-exporter
+
+extra-apoth-compat
+pathtracker@
+elytra-protection
+skip-sleep
+stony-cliffs-are-cool
+simple-ender-things
+mimecraft-mod
+warden-sword
+arch-bows@
+fancy-trinkets@|精美的小饰品*
+block-n-zapping@
+cgs@
+dynamic-trees-jadens
+dynamic-trees-croptopia-new
+stacksonstacks
+more-of-all@
+
+
+popular-biology|热门生物
+tolkien-tweaks
+tolkien-tweaks-mobs-edition
+soulplied-energistics
+valarian-conquest
+mowzies-mobs-respawner
+crafting-station-jei-edition@|合成站:JEI版 (Crafting Station: JEI Edition)
+more-waterlogging
+the-ancient-trinkets-index@
+straw-golem-rebaled-ported
+haybale-1-20-1
+gtbcs-spelllib
+haybale@
+
+creative-eating-reforged|创造也能吃*
+@better-mod-list-button-fix|Better ModList 按钮修复 (Better Mod List Button Fix)
+krypton-foxified@|氪 NeoForge 版*
+proximity-text-chat@
+justleveling-fork@
+
+redirected@
+asynchronous-reprojection@
+not-enough-keybinds@
+daily-deliveries@
+vehicular-movement-light
+vehicular-movement
+onysdvehicles@onysd-vehicles
+
+zunpet-forge|Zun号非官方Forge版 (ZUNPet Forge)
+more-hitboxes@|更多碰撞箱*
+
+shipwreck-fix|沉船修复*
+
+narcissus-farewell|水仙辞*
+@mcmod-splashes-plusplus|百科闪烁标语++ (MCMod Splashes PlusPlus)
+random-enchants-2|随机附魔 2*
+sparkle-reforge@|火花:重铸 (Sparkle: Reforge)
+led-lighting
+not-enough-stuff
+iron-fence
+bricks
+additional-records
+firefly-bush
+spring-update-snapshot-to-old-versions
+@godot-signal-system-lib
+
+leons-minecraft-dungeons-paintings-mod
+miscellaneous-additions@
+rupters-figure-mod-doors
+sophisticated-backpack-storage-emerald-upgrade@sophisticated-backpackstorage-emerald-upgrade
+sophisticatedinjections@
+immortalers-delight|千古乐事*
+
+createnuclear@|机械动力:原子核动 (Create Nuclear)
+keepthatinventoryopen@
+falcons-single-person-sleep
+realisticsleep
+
+avaritia-1-1x-unofficial|无尽贪婪1.1x非官方延续版 (Avaritia 1.1x Unofficial Extended Life)
+lightsaberx
+prime-punch-one-punch-mod
+rpgmana@arch-rpgmana
+opposite-dimensions
+migamigos
+@cloudmusicroom|云音乐房间 / 服内一起听音乐与点歌 (CloudMusicRoom)
+theonesmeagle
+witchery-companion@
+@tpa-utilities
+@remote-player-waypoints-for-xaeros-map
+
+addonslib@
+siliconedolls@silicone-dolls|硅胶玩偶 (SiliconeDolls)
+@authshield|认证防护 (AuthShield)
+
+redstone-armoury-minus-thermal
+elytras-plus
+ae2uel-wireless-universal-terminal|无线通用终端 (AE2UEL Wireless Universal Terminal)
+presents
+create-picky-waterwheels@create-picky-wheels
+
+@green-screen
+fancy-lamps
+resizing-potion
+miku
+advanced-smelter|荒古炼金炉 (Advanced Alchemical Furnace)
+
+
+
+bedsheet@|床单 (BedSheet)
+despawn-tweaks@
+planetguylib
+@fabric-kotlin-extensions
+crafting-table-plus@
+@replay-export
+@loot4everyone
+containerfix
+cool-rain@coolrain
+queqiao|鹊桥 (QueQiao)
+lexikon@
+
+idmid@
+simple-concrete@
+chunk-activity-tracker@
+lets-do-lilis-lucky-lures@
+craftformers-prime@
+@pumpkinmoon
+@vbe
+arts-by-kev-mod-2023@
+the-lion-king-mod
+epic-terrain-compatible
+the-beginning
+create-simple-ore-doubling@
+create-low-heated@
+planetguy_gizmos
+@thirdpersonfix-babric
+@retrocommands
+@mana-peeper
+retro-sophisticated-backpacks|复古精妙背包*
+grid@
+emienchants@
+random-complement|随意补充 (RandomComplement)
+thedulling
+batsword
+unidye@
+obs-overlay@
+
+cwhitelist@
+
+raw-mouse-input-blessed-edition@|原始鼠标输入 - 祝福版 (Raw Mouse Input - Blessed Edition)
+
+@hidetags
+grass-kiss@
+attack-through-grass@
+structurify@
+netherite-beacons-forge@netherite-beacons¨netherite-beacons
+esoteric-reforging-forge
+dimensional-leap@
+replant-it@replant-it!
+trail-tales-delight@trailtales-delight|樱途旅事 (Trail&Tales Delight)
+pams-harvestcraft-2-fantasy
+alexs-caves-rad-fork@
+hongbao-red-envelope@red-envelope|红包 (HongBao: Red Envelope)
+
+sortilege@
+
+lazymodder
+
+
+minecraft-skill-tree
+skill-tree-mod
+
+
+easy-shulker-access
+@ysbb|森罗物语 (Kaleidoscope Mod)
+torch-slabs-renewed|更好的火把放置重制版*
+
+wormhole-artifact@
+
+
+
+
+create-diamond-factory@
+create-realism@
+create-sound-of-steam@
+
+big-explosives@
+ice-cream-creepers
+aquatic-craft
+village-taverns@|乡村酒馆*
+f-a-rebirth-and-expand-genesis
+fossils-archeology-origins@fossilsarchaeology-origins|考古与化石:起源 (Fossils & Archeology Origins)
+faunify@
+
+wills-hedgehogs
+cryptography@metaphysics
+@spasm
+sketch@
+@envirosoundbeta
+@keybindsgaloreplus
+
+@smoothbeta
+no-fall
+
+scopic
+memory-leak-fix-unofficial-remastered
+@distance-traveled-calculation
+
+@control-craft
+reforbidden-magic|禁忌魔法:重生 (ReForbidden Magic)
+moses-mod
+craftable-animals|可合成动物*
+avaritia-1-1x-kedition
+@more_avaritia|更多无尽物品 (MoreAvaritia)
+the-block-box@|方块百宝箱*
+the-dirtbike-mod
+ore-seeds
+
+mana-visualizer
+custom-tnt-igniter
+
+
+
+jaiz@jaizmod
+harder-illage-and-spillage
+dagercraft@|旧超神之路 (DagerCraft)
+@deus-ex-minecraft-s-conspiracy|杀出重围 (Deus Ex Minecraft's Conspiracy)
+thaumic-staff|神秘法杖*
+thaumic-bases-mystical-rose-rework|神秘基础学玫瑰重制*
+rhyme-pvz@|PVZ:润 (Rhyme PVZ)
+ichorium-kit
+reforgotten-relics|失落遗物学:重生 (ReForgotten Relics)
+in-your-world@
+warpapocalypse
+
+arcane-arteries-kedition|奥法献祭学非官方版*
+thaumic-tinkerer-unofficial|神秘工匠非官方版*
+
+asyncparticles@|异步粒子 (AsyncParticles)
+@dragonfly-model-library
+@terrainapi
+mnmutils
+betterend-elytra-fix@
+player-swap
+@configurable-everything
+no-hearts-ui@
+
+
+old-loading-progress-bar@|旧版加载进度条*
+raw-tp-chat-command@raw-tp-chat-cmd
+@waterplayer
+@home-utilities
+multiplayer-button@
+
+@kelui
+brick-lib
+@no-slow-motion|无拘速行 (No Slow Motion)
+@minecartsloadchunks
+guideme@
+neoculus@
+chatwithmobs|与生物聊天 (ChatWithMobs)
+@kiss-mod
+extra-armor-info@
+feature-recycler@
+ore-hammer@
+
+exp-chest
+sophisticated-backpack-allthemodium-tier
+sophisticated-storage-allthemodium-tier
+the-amazing-world-of-mobs-renovated
+ash-of-sin-tempest|罪业余烬:特恩佩斯特 (Ash Of Sin: Tempest)
+
+@cauldronfix|铁锅工艺 (Cauldronfix)
+spelunker@
+lil-tater-reloaded@ltr
+no-modern-industrialization-efficiency
+@emitechreborn
+@better-mipmaps
+showmyskin@
+rhizo
+deimos-fabric-forge-neoforge@deimos
+
+excavation|挖掘*
+basic-end-ores|基础末地矿石*
+@portable-crafting
+
+simple-bbq-unofficial|简易烧烤非官方版 (Simple BBQ Unofficial)
+
+eggy-goodness
+@slide-show-pro|幻灯片:增强 (Slide Show Pro)
+alternative-twilight@alternative_twilight|暮色:另类*
+
+
+pepsied-item-glow
+create-bigger-storage@
+tacz-create
+
+terrific-trash-cans
+
+colourful_portals_mod
+gud_coolers
+suppergerrie2s-drone-mod
+data-essence@
+
+nautec@
+
+thorny-bush-protection@
+gudlib
+reflex-antilag
+tfc-nei
+
+nolan
+@repeater-sound
+audio-hotkeys@
+voicechat-soundboard@
+@the-moon-bta
+ftb-ocean-mobs
+fake-ores
+
+@mystical
+crackedzombie
+
+error-404-not-found-horror-entity@error-404-not-found
+apotheosis-totem
+
+
+tbone
+
+
+@cdnperspective3d
+@volume-fix
+chorus-fruit-drops-nearby@|紫颂果集中掉落*
+better-first-aid-visual
+tpms@|传送命令·简 (Teleport Master Simplified)
+@scoreboard-overhaul
+
+tensura-mysticism
+furnection@lyivxs-furniture|LYIVX的家具*
+@colourful-portals-reimagined
+
+craft-of-the-wild-cotw
+piglands-mod
+vanilla-extension
+floating-items-mod-datapack
+butterfly-mania
+@rensumo_enchanted_books_addition|Rensumo 的附魔书附加包 (Rensumo Enchanted Books Addition)
+kbi-mmd
+sowdispenser
+metalchests
+jumppad
+obsidian-boat|黑曜石船*
+@moderner-beta
+the-animals-plus
+create-fluid-stuffs|机械动力:流体附加 (Create:Fluid Stuffs)
+create-goggle-placement-lieonlion@
+minecraft-multi-purpose-mod
+kubeloader@
+updatefixerupper
+configurable-data-fixers@
+lenient-death@
+short-circuit@
+tensura-kumodesu
+tinkers-advanced@|前沿匠艺 (Tinkers' Advanced)
+more-structure-processors@
+quick-stack-to-nearby-chests@quickstack|快速堆叠 (QuickStack / Quick Stack To Nearby Chests)
+
+
+yggdrasilproxy@yggdrasil-proxy
+
+thaumic-meme|神秘吐槽*
+starlightxae|星光xAE2修复 (StarlightxAE)
+
+
+@drops-into-shulker
+ftb-team-dimensions
+epicfight-resurrection|史诗战斗-重生 (EpicFight : Resurrection)
+epic-fight-dual-axes
+epic-fight-dual-spears
+realistic-fire-spread@
+palehell
+elainasbroom|伊蕾娜的扫帚 (Elaina's Broom)
+extinction-z@
+pomkots-mechs@
+
+replication@
+better-sugar-cane|更好的甘蔗*
+apogee@
+btrultima-tensura
+dragon-block-c-additions
+twilight-forest-final-boss
+generations-structures@
+oceanic-realms
+zoomers-extended-apotheosis
+the-god@
+relics-rpg@
+macaws-quark@
+macaws-sajevius@
+
+
+
+
+macaws-modding-legacy@
+fancy-spawn-eggs
+
+
+
+
+@queryinfo
+ftb-pause-menu-api
+
+modifyjs@
+@bettershields|更好的盾牌 (BetterShields)
+@better-selection
+
+ytgld-better-beacon@better-ytgld-orb-beacon|亚特格拉德的信标 (Ytgld : Better Beacon)
+colorfultools
+colorful-armor
+ibench
+crafting-table-on-a-stick|手持工作台 (Crafting Table on a Stick)
+
+villager-anti-doors
+liquipacks
+create-advanced-crafting
+create-stone-generators@
+create-cold-sweat@|机械动力:冷汗 (Create: Cold Sweat)
+another-unbreakable-enchantment
+passive-piglins
+void-miners
+gwycraft-a-colored-blocks-mod
+creative-sandbox|创造沙盒*
+signboard-preview@|告示牌预览 (SignBoard Preview)
+
+hexoverpowered@
+hexflow@
+
+@chihayaannos-steel-sheet|MyGO!!!!!:千早爱音的前置装甲方案 (Chihaya Anno's steel sheet)
+moremekasuitmodules|更多 MekaSuit 模块 (MoreMekaSuitModules)
+eternal-attributes@
+
+easy-pickings
+relics-in-chaos@relic-in-chaos
+archers-expansion@
+witcher-rpg-class@
+forcemaster-rpg-class@
+death-knights@
+
+immersive-slumber@|沉浸睡眠 (ImmersivelySleep)
+bountifulbaubles-reforked@bountifulbaublesreforked|丰富的饰品:重铸 (BountifulBaubles:Reforked)
+@anvil-uses|铁砧使用次数显示 (Anvil Uses)
+@lucidity-mod|我的视界 (Lucidity)
+@mekanismmoremodules|通用机械:更多单元 (Mekanism: More Modules)
+@better-armor-swap
+
+the-man-from-the-shadow
+
+
+
+
+arcanmira
+comparties-reborn|组队兼容:重生*
+tape-stop@|停碟*
+persistent-inventory-search@
+@no-multiplayer-menu
+@litematica-enderchest-materials
+@mob-protect
+only-bags@
+@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
+bots-lib@
+
+botaniaoverpowered@
+@simple_no_survival|蓟县生存 (Simple ? Survival)
+
+
+@auto-pillar-generator|一键柱子生成 (Auto Pillar Generator)
+vanilla-delight
+jade-vs@
+
+vics-modern-warfare-patched|维克的现代战争(修复版) (Vic's Modern Warfare (Patched))
+waterframesbackported
+
+tweakeroo-update-port
+xkballs-auto-translate|xkball的自动汉化 (xkball's Auto Translate)
+
+block-replacement-mc1-7-10
+@mekanismqq
+
+confluence@|汇流来世 (Confluence: Otherworld)
+
+
+@trinkets-client-optional
+data-packs-helper|数据包助手*
+@gokiskills|技能专精 (GokiSkills)
+
+tconstructjs|有头脑的工匠 (TConstructJs)
+maid-storage-manager|女仆仓管*
+
+lush-scented-paradise|芬芳茶园*
+
+hold-my-items@
+achievement-extreme
+advancement-extreme
+recipe-machine-stages-zs-js
+configurable-keepinventory
+better-chat@betterchat
+mekalus-oculus-fork-with-fixed-mekanism-mekasuit
+view-emc
+@fortitude
+experience-ore|经验矿石*
+passive-torch
+productive-metalworks
+better-leveling-respawn|更好的升级:重铸 (Better Leveling: Reforge)
+drive-by-wire
+spirit-of-fight@|战魂 (Spirit of Fight)
+grim-bleak@grim-and-bleak
+carrymyboss
+ranged-damage-limit|远域挡招/远程伤害保护*
+magichem
+iglee-library@
+component-model-hider
+
+@dont-cut-tripwire
+extra-achievements
+keybindjs@
+@autototem
+@totem-cooldown
+sound-physics-legacy|物理声效:遗产*
+
+
+marblegates-exotic-enchantment-reborn@marblegates-exotic-enchantment-flowing-agony-reborn|白门的奇异附魔:苦痛长河:重生 (MarbleGate's Exotic Enchantment: Flowing Agony: Reborn)
+
+@beta-1.7.3-music-discs
+@spawn-eggs|刷怪蛋 (Spawn Eggs)
+
+create-waystones-recipes@
+mekanistic-routers@
+just-healerblock
+ftb-stuff-things
+infinibows
+tacz-labs@
+@mineshock-cybernightmare|网络奇兵 (MineSHOCK: CyberNightmare)
+make-a-wish
+archaeological_research@|考古研究 (Archaeological Research)
+forgotten-nunchakus
+create-big-cannons-advanced-technologies@|机械动力火炮:先进技术 (Create Big Cannons: Advanced Technologies)
+quality-food@
+snekcraft@
+hearth-and-harvest|暖灶丰年 (Hearth and Harvest)
+
+
+
+wjys|无尽药水 (WJYS)
+schematicpreview@|原理图预览 (SchematicPreview)
+
+@collect-emerald|聚集石 (Collect Emerald)
+sakura-tinker|魂樱工匠*
+@appspec
+heart-crystal|心之水晶*
+default-skin@
+letyourfriendeating@let-your-friend-eating!|让你的朋友吃! (Let Your Friend Eating!)
+
+ftb-jei-extras
+shader-reload@
+simple-menu@
+@asahi
+keybindspurger@
+iguana-lib@
+map-making-tools|地图制作工具*
+
+turkeyutil
+drcyanos-wonderful-wands-wizarding-robes
+dragon-survival-griffin-addon
+rpg-skillpower@rpgsp
+unknown-null
+slime-origin
+@slime-origin
+slime-origin-remastered@slimeorigin
+alchemy-kingdom|炼金王国*
+tcondiadema|匠魂领域 (TconDiadema)
+
+despawn-wand
+tome-of-knowledge-sharing
+
+
+
+
+
+
+archers-helicopter@|阿辰的直升机 (Archer's Helicopter)
+fels-machine-guns-wwi
+fels-machine-guns-wwii
+steamatic-locomotion
+dungeon-revival
+new-order
+create-more-recipes-addon
+cbc-nuclear@
+@smart-zombies
+
+
+libguifoxified@
+
+valkyrien-relogs@
+@section-31|简单监控 (Section 31)
+chatalert
+aaa-particles-world@
+pick-up-tnt@
+ymadditions@
+immersive-machinery@
+xp-synthesiser@
+oredictionaryconverter
+cbc-modern-warfare@cbcmodernwarfare|机械动力火炮:现代战争 (CBC: Modern Warfare)
+shulker-charm@
+villager-license|村民证件*
+description-tags
+harvestwithdispenser
+sound-physics-fabric|物理声效 Fabric 版*
+cleanroom-relauncher
+fast-item-frames@
+hammering@
+vanity-elementals
+fmprojecte-advanced-alchemy-fmpeaa
+peex
+animal-origins@
+narutoto-ninja-world
+the-ender-scrolls-skyrimcraft-the-revival@the-ender-scrolls-skyrimcraft|末影卷轴 - 天际工艺 (The Ender Scrolls - Skyrimcraft)
+kingdom-of-rin-mobs
+scape-and-run-parasites-mutant-beasts|逃逸:寄染突变体 (Scape and Run: Parasites Mutant Beasts)
+alexscavesexemplified@alexs-caves-exemplified
+akutolib
+@glass-networking
+@always-more-items
+@mostlymodernrecipes-stationapi
+toomuchloot
+toomuchtime
+cs2-box|CS2箱子 (CS2 Box)
+edit-mob-drops@
+
+tea-aroma@|茶香*
+
+
+
+overpowered-inventory
+bbrains-umfixes
+
+foodstats|食物增强 (foodstats)
+hxchud
+sorted-enchantments@|排序附魔*
+assassins-creed-parkour-mod|刺客信条跑酷 (Assassin's Creed Parkour Mod)
+critter-armory@
+the-harvest
+better-minoshroomtaur|Better 米诺菇*
+moobloom|哞菇与哞花 (Mooblooms and Mooshrooms)
+mooblooms-and-mooshrooms-bop-edition|哞菇与哞花BoP版 (Mooblooms and Mooshrooms (BoP Edition))
+the-pure-suffering-mod
+dooglamoo-jr-archaeology|Dooglamoo 的考古学 (Dooglamoo Jr. Archaeology)
+legionary
+voiceless-survival@|寂静求生*
+fabulous-blades@fabulous-blade
+
+prism-plan|棱镜计划*
+ae2cc-bridge@ae2cc
+ae2-mousetweaks-fix@
+ae2-qol-recipes@
+cyberspace@
+
+displayed@|展示 (Display)
+engineered-compatibility@
+@createabysscatalysis|机械动力:深渊催化 (Create: AbyssCatalysis)
+
+@worlddownloaderlegacy|世界下载器旧版 Forge (WorldDownloaderLegacy)
+level-up-rpg-skills-more
+
+@creeper-healing
+render-control|渲染控制*
+chemical-tweaker
+health-bar
+see-the-rarities-str|查看稀有度 (See The Rarities)
+craftpilot@
+@game-of-reborn|复活赛 (Game of Reborn)
+legendary-slash|传说之刃*
+@dota.rg
+@appledog|苹狗 (Appledog)
+superb-warfare@|卓越前线*
+
+aetherworks-extended-life
+pomkots-mechs-extension-pack@
+@monkey-config
+some-more-blocks@
+
+projecte-advanced-alchemy
+
+replicore@|复制核心 (RepliCore)
+create-reforged-foundations@
+
+@jgfe|我踏马吃吃吃! (Just Go Fxxking Eat)
+better-experience@better-experience-of-confluence|更好的体验*
+
+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-security-program@
+furnies@
+cobblefurnies@
+thdilos-fox-origin-expanded@|ThDilos的狐狸起源拓展 (ThDilos Fox Origin Expanded)
+potion-sauce@
+copious-dogs-reforged
+@slot-customization-api
+hqm-keybind@
+zoomer-simple-zoom
+
+curioenchantment|饰品附魔 (CurioEnchantment)
+wall-jump-continuation
+armor-stand-shift-swap@
+moveui-forged@
+harvest-level-config
+brewing-helper@
+theatrical@
+
+
+@taczaddon
+@heartcanister|心之容器 (Heart Canisters)
+gunswithoutroses-expansions@gunswithoutrosesexpansion-s|枪炮“没”瑰:扩展 (Guns Without Roses : Expansions)
+ati-structures-datapack-vanilla
+create-backpack-pixel|机素背包 (Create: Backpack Pixel)
+crazy-alloy
+spellbound-enchantments@spellbound
+
+
+
+fortunas-anvil
+create-deep-dark@
+create-ranged@
+create-enlightend@
+create-winery@
+create-filters-anywhere
+create-cheese@
+create-copper-zinc@
+create-ultimate-factory@
+
+lyivxs-core@
+exclusions-lib@
+json-paintings@
+keybind-unconflict@
+apothic-compats
+apothic-materials
+pepsied-easy-multi-item-sorter
+tetra-apotheosis-fix@
+ron-timer
+empty-air-bubbles
+bundles-throw-potions
+ftb-team-bases
+raworesmelting@
+dpt@
+potion-effect-enchantment
+mythical-origins
+assorted-armaments@|百般武艺*
+
+spanksters-craft
+heavenlyabyss
+slashblade-twin-attack|拔刀剑:连携攻击 (SlashBlade Twin Attack)
+dungeon-crawl-dimensional-patch
+apothic-supplementaries-enchanting
+apothic-amendments-enchanting
+apothic-mutant-heads
+apothic-science-moa-stats
+ye-gamol-chattels-reborn@
+
+
+@my-more-enchantments|我的更多附魔 (My More Enchantments)
+@some-little-enchant|一些小附魔 (Some Little Enchant)
+minepulse@|一脉相挖 (Mine Pulse)
+
+
+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
+wiidudes-custom-origin-mod@wiidudes-custom-origins
+wolfkin-origin@
+horrorgins@
+
+aspects-origins-addon@aspects
+@origins-minus
+hapi-pillager@
+@stop-picking
+pepsied-super-hopper
+pepsied-item-catcher
+pepsied-auto-harvest
+@bulk-compost
+centered-item-frames
+undergarden-botany-pots-compats
+
+
+bbl-flight-blocks@
+mjs-raw-ores
+create-gunpowder-crafting
+luminax@
+cable-facades@
+easy-storage@ezstorage|简易存储*
+
+elrols-gui-elevator
+@minecraft-found-footage
+exposure-polaroid@
+
+
+
+fluxloading
+inventory-swapping
+seamless-trading@
+sophisticated-auto-walk
+mmmmmmmmmmmmpatch|试验假人补丁 (MmmMmmMmmMmmPatch)
+global-datapacks@datapacks
+world-guard
+
+tetradelight
+
+dragon-mounts-2
+heroes-of-might-and-magic-3@
+another-changed
+randomly-tp|随意传送 (Freely/Randomly TP)
+illager-war-trireme
+
+hominid
+anvians-lib@
+
+amblekit@
+
+nocubes-better-fletching-table
+better-fletching-table@|更好的制箭台*
+jsconfig@jsconf
+@lightspeed-boat|光速船 (Lightspeed Boat)
+nomissingmods
+urban-decor@
+nospawnzone
+keybind-bundles@
+
+cloudflared@
+@aikoyori-tweaks
+flowingfluidfixer
+absolutely-stuffed-uncapped-hunger-saturation
+gluttony-oversaturation-overeat
+overeat
+oversaturation
+
+etheria
+
+legends-of-slugterra@
+gussdx-decoration
+
+projectiles@
+space-patrol-delta
+@neomtr|纸板箱特色我的世界铁路 (NeoMTR)
+@contigency-contract-lite|轻量合约 (Contigency Contract Lite)
+assorted-lib@
+taterlib@
+
+nocapes@
+inventory-sorter-configurable@
+smartcursor|智能指针 (SmartCursor)
+wither-spawn-animation@
+@boom-enchantment
+prioritiser
+engineered-schematics@
+@mojangfix-stationapi-edition
+@nedologin
+
+@sprint-on-ctrl
+kill-counter-kill-streak-announcer-mob-death|连杀计数 (Kill Counter)
+calm-down-dog@
+keybinding-hider@|按键绑定隐藏器 (KeyBinding Hider)
+darkerer@
+no-more-sleep-problems@
+configbackuper@
+
+
+inhrtburys-gilding|Inhrtbury 的镀金 (Inhrtbury's gilding)
+@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
+snowy-tents@
+nuhayuponosiks-bosses
+goety-cataclysm@|诡厄灾变*
+cave-delight
+@forkcart
+silentgear-compat
+timeless-ivy-reborn@|永恒常春藤:重生 (Timeless Ivy:Reborn)
+simply-more@simplymore
+wetland-whimsy@
+
+
+
+ileafcore
+dirt-tools-and-armor@|泥土工具与盔甲 (Dirt Tools and Armor)
+thaumic-rings-of-power|神秘众戒*
+lotr-valkyrien-compat|魔戒:瓦尔基里兼容 (Lotr+Valkyrien Compat)
+improvedcrashreports@
+colorful-paradise@
+oracle-index@
+come-back-my-villagers@
+screenshot-with-coords@|带标截图 (Screenshot with Coords)
+
+ccddi-damage-indicator@ccddi
+lidar
+@segmented
+goety-gotta-summon-em-all
+aerlune-rpg-2
+origin-furs@
+grounded-origins-forge@groundedorigins¨grounded-origins-fabric
+
+tinkers-tool-leveling-2@|匠魂工具升级2*
+remodifier-reborn|你别造了!!!*
+exporbrecall@|经验球召回 (ExpOrbRecall)
+functional-economy|功能性钱币 (FunctionalEconomy)
+ranckors-rings-of-power|Ranckor的力量之戒 (Ranckor's Rings of Power)
+lotr-improvements-lite|魔戒改进:轻量版 (LOTR Improvements Lite)
+
+
+astounding
+carpets-core@
+carpet-variants@
+@mineable-unmineable
+tetra-addition
+styled-boss-bar-api@|自定义Boss栏 (Styled Boss Bar API)
+
+random-enchant-fix@
+immersive-armor-hud@|沉浸式盔甲 HUD (Immersive Armor HUD)
+
+yeast-n-feast@
+
+
+
+essentia-pipes@|源质管道*
+msp-backpacks
+simple-block-breaker@
+salis-arcana@
+thaumic-mixins
+sanguimancypatch
+sanguimancy
+jungle-villager-trader
+origin-furs-unofficial-forge-fork@origin-furs-unofficial-forge-port
+ephemeral-wither-skulls@|消逝的凋灵之首*
+fox-friend@|狐友*
+
+simple-difficulty-reforged
+create-recycling-everything-neo
+
+aquaticamod
+hell-ntegrations
+@mcmod-more-food-enchantment|更多的附魔食物 (More Food Enchantment)
+mapletree|枫树*
+drink-it-refoxed|喝!重生 (Drink It! Refoxed)
+
+galospheric-delight@
+growmeal-fabric@growmeal-(fabric)
+
+katharite-slimes@slimes-of-kathare
+botania-ceu
+magic-from-the-east
+blatapi@
+@materials-lib
+cavelib@
+economy-api@
+
+
+no-enchantment-cap-level@
+create-ultimine
+serene-wild@
+kitchen-decorations
+refined-storage-curios-integration@
+refined-storage-trinkets-integration@
+refined-storage-emi-integration@|精致存储 - EMI 集成 (Refined Storage - EMI Integration)
+refined-storage-rei-integration@|精致存储 - REI 集成 (Refined Storage - REI Integration)
+refined-storage-jei-integration@|精致存储 - JEI 集成 (Refined Storage - JEI Integration)
+refined-storage-mekanism-integration@|精致存储 - 通用机械集成 (Refined Storage - Mekanism Integration)
+
+
+
+lukis-ancient-cities
+ungebauts-wildlife
+changed-mcreator-slop
+forgotten-battle-towers
+@back-at-it-again
+summer-cottage@
+marius0x-furniture
+youssefs-currency@youssefs_currency
+bibliobiomes-legacy
+sniffers-delight
+easters-delight@
+
+
+
+better-carryon-maid|更好的女仆搬运*
+tensura-no-game-no-life|转生:游戏人生 (Tensura: No Game No Life)
+summoner-scrolls@
+refined-storage-quartz-arsenal@
+create-advanced-logistics@createadvlogistics
+create-electric-drive
+@lachryvisions
+the-heaven-sword-and-dragon-saber|倚天屠龙 (The Heaven Sword and Dragon Saber)
+
+
+
+wolfs-enchantment-lib|狼王的附魔库 (Wolf's Enchantment Lib)
+
+disturb-not-my-dream@|勿扰吾梦 / 起床气
+@contigency-shop-lite|轻量兑换所 (Exchange Site Lite)
+lootbagsrenewal|战利品袋重制版 (LootBagsRenewal)
+
+
+
+@no-elytra-mod
+no-elytra
+title-changer@
+
+itemzoomer
+@surveystones
+particular-reforged
+cloud-revive@cloud_revive|云生*
+paranoia|偏执狂*
+thaumic-brewing|神秘酿造学*
+aether-treasure-reforging@
+animations@
+@pet-necropolis
+paddle-boats
+true-darkness-elementriy
+
+
+sophisticated-storage-create-integration@
+
+@removespeeddetection|移除服务端速度检测消息 (RemoveSpeedDetection)
+shared-advancements@
+@polytime
+wild-growth
+ssmlegacy@
+super-sound-muffler-revived
+current-game-music-track@
+create-mobile-packages
+create-cardboarded-conveynience@
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Resources/java-wrapper.jar b/Plain Craft Launcher 2/Resources/java-wrapper.jar
index 29d7632..bd14bd6 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/Plain Craft Launcher 2/Resources/libwebp32.dll b/Plain Craft Launcher 2/Resources/libwebp32.dll
deleted file mode 100644
index 9ebce90..0000000
Binary files a/Plain Craft Launcher 2/Resources/libwebp32.dll and /dev/null differ
diff --git a/Plain Craft Launcher 2/app.manifest b/Plain Craft Launcher 2/app.manifest
index 2d4820f..b4ae5d8 100644
--- a/Plain Craft Launcher 2/app.manifest
+++ b/Plain Craft Launcher 2/app.manifest
@@ -48,13 +48,17 @@
自动缩放。Windows Presentation Foundation (WPF)应用程序自动感知 DPI,无需
选择加入。选择加入此设置的 Windows 窗体应用程序(目标设定为 .NET Framework 4.6 )还应
在其 app.config 中将 "EnableWindowsFormsHighDpiAutoResizing" 设置设置为 "true"。-->
-
-
- true
-
+
+ true
+
+ PerMonitorV2
+ true
+
- -->
diff --git a/README.md b/README.md
index 8c12114..99e8076 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,17 @@
-# Plain Craft Launcher 源代码库
+# Plain Craft Launcher
-[](https://github.com/Hex-Dragon/PCL2/)
-[](https://github.com/Hex-Dragon/PCL2/issues)
-[](https://afdian.com/@LTCat)
+[](https://github.com/Hex-Dragon/PCL2/)
+[](https://github.com/Hex-Dragon/PCL2/issues)
+[](https://space.bilibili.com/11343203/dynamic)
+[](https://afdian.com/@LTCat)
+
+包含 PCL 的大部分源代码,包括 UI 库、动画模块、下载模块、Minecraft 启动模块等!
+代码并不是实时更新的,它会在每次 PCL 发布更新时同步一次。
+非常感谢社区的大力支持!
-## 介绍
-
-这里提供了 PCL 的大多数源代码,包括 UI 库、动画模块、下载模块、Minecraft 启动模块等!
-
-PCL 的代码绝大多数其实都是几年前学生时代的产物了……那时候英语还不过关……所以经常出现奇葩命名,还有令人高血压的高耦合啊,没做单例啊,瞎勾八乱糊啊之类的问题……额,我也不可能把这一堆玩意儿再从头写一次,各位就基于能跑就行的原则凑合凑合着看吧,求求别喷了……
-
-
-
-## 相关内容
-- [PCL 功能投票](https://github.com/Hex-Dragon/PCL2/discussions/2):来参加投票吧,开发者会优先处理票数较高的帖子!
-- [程序下载](https://afdian.com/p/0164034c016c11ebafcb52540025c377):下载免费的正式版 PCL。
-- [帮助文档库](https://github.com/LTCatt/PCL2Help):PCL 帮助文档在 GitHub 上的存储库(是的,帮助库在另一个 Repo……)
-
-
-
-## 一些无趣的小细节
-- 别想在这里面找到解密游戏的线索,那部分的代码被我抠掉了
-- 解锁隐藏主题的相关代码也被抠掉了,所以别想着用翻源代码这种歪门邪道来绕过解密了.jpg
-- 源代码库并不是即时更新的,而是在每次 PCL 发布更新时(手动)同步一次
+### 相关资源
+- [PCL 下载](https://afdian.com/p/0164034c016c11ebafcb52540025c377):下载可免费使用的正式版 PCL!
+- [PCL 功能投票](https://github.com/Hex-Dragon/PCL2/discussions/2):来参加投票吧,开发者会优先处理票数较高的项目!
+- [帮助文档库](https://github.com/LTCatt/PCL2Help):PCL 内置帮助文档的存储库
diff --git a/最新正式版.zip b/最新正式版.zip
index 252209c..9eec8e2 100644
Binary files a/最新正式版.zip and b/最新正式版.zip differ