diff --git a/Plain Craft Launcher 2/App.config b/Plain Craft Launcher 2/App.config
index d464350..0e1a473 100644
--- a/Plain Craft Launcher 2/App.config
+++ b/Plain Craft Launcher 2/App.config
@@ -30,6 +30,10 @@
+
+
+
+
-
\ No newline at end of file
+
diff --git a/Plain Craft Launcher 2/Application.xaml.vb b/Plain Craft Launcher 2/Application.xaml.vb
index 5997359..5ef002b 100644
--- a/Plain Craft Launcher 2/Application.xaml.vb
+++ b/Plain Craft Launcher 2/Application.xaml.vb
@@ -181,53 +181,29 @@ WaitRetry:
End Sub
'动态 DLL 调用
- Private Shared AssemblyNAudio As Assembly
- Private Shared AssemblyJson As Assembly
- Private Shared AssemblyDialog As Assembly
- Private Shared AssemblyImazenWebp As Assembly
- Private Shared ReadOnly AssemblyNAudioLock As New Object
- Private Shared ReadOnly AssemblyJsonLock As New Object
- Private Shared ReadOnly AssemblyDialogLock As New Object
- Private Shared ReadOnly AssemblyImazenWebpLock As New Object
Private Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean
- Public Shared Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
- If args.Name.StartsWithF("NAudio") Then
- SyncLock AssemblyNAudioLock
- If AssemblyNAudio Is Nothing Then
- Log("[Start] 加载 DLL:NAudio")
- AssemblyNAudio = Assembly.Load(GetResources("NAudio"))
- End If
- Return AssemblyNAudio
- End SyncLock
- ElseIf args.Name.StartsWithF("Newtonsoft.Json") Then
- SyncLock AssemblyJsonLock
- If AssemblyJson Is Nothing Then
- Log("[Start] 加载 DLL:Json")
- AssemblyJson = Assembly.Load(GetResources("Json"))
- End If
- Return AssemblyJson
- End SyncLock
- ElseIf args.Name.StartsWithF("Ookii.Dialogs.Wpf") Then
- SyncLock AssemblyDialogLock
- If AssemblyDialog Is Nothing Then
- Log("[Start] 加载 DLL:Dialogs")
- AssemblyDialog = Assembly.Load(GetResources("Dialogs"))
- End If
- Return AssemblyDialog
- End SyncLock
- ElseIf args.Name.StartsWithF("Imazen.WebP") Then
- SyncLock AssemblyImazenWebpLock
- If AssemblyImazenWebp Is Nothing Then
- Log("[Start] 加载 DLL:Imazen.WebP")
- AssemblyImazenWebp = Assembly.Load(GetResources("Imazen_WebP"))
- SetDllDirectory(PathPure.TrimEnd("\"))
+ Public Shared Function AssemblyResolve(sender As Object, Args As ResolveEventArgs) As Assembly
+ '缓存
+ Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "System.Net.Http.Formatting"}
+ Static Locks As New Dictionary(Of String, Object)(StringComparer.Ordinal)
+ Static LoadedAssembly As New Dictionary(Of String, Assembly)(StringComparer.Ordinal)
+ '查找对应的 DLL
+ Dim Prefix As String = Prefixes.FirstOrDefault(Function(p) Args.Name.StartsWithF(p))
+ If Prefix Is Nothing Then Return Nothing
+ '加载 DLL
+ If Not Locks.ContainsKey(Prefix) Then Locks(Prefix) = New Object()
+ SyncLock Locks(Prefix)
+ If Not LoadedAssembly.ContainsKey(Prefix) Then
+ Log($"[Start] 加载 DLL:{Prefix}")
+ LoadedAssembly(Prefix) = Assembly.Load(GetResources(Prefix))
+ 'WebP 特判
+ If Prefix = "Imazen.WebP" Then
+ SetDllDirectory(PathPure.TrimEnd("\"c))
WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64"))
End If
- Return AssemblyImazenWebp
- End SyncLock
- Else
- Return Nothing
- End If
+ End If
+ Return LoadedAssembly(Prefix)
+ End SyncLock
End Function
'切换窗口
diff --git a/Plain Craft Launcher 2/FormMain.xaml.vb b/Plain Craft Launcher 2/FormMain.xaml.vb
index 14a2c12..554bb8a 100644
--- a/Plain Craft Launcher 2/FormMain.xaml.vb
+++ b/Plain Craft Launcher 2/FormMain.xaml.vb
@@ -83,6 +83,13 @@ Public Class FormMain
'3:BUG+ IMP* FEAT-
'2:BUG* IMP-
'1:BUG-
+ If LastVersion < 363 Then 'Snapshot 2.10.4
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "优化:下载资源时,会单独记忆每种资源上次下载到的文件夹,以防混淆"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:网络底层框架与下载稳定性"))
+ FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法启动一部分 LabyMod 和 GTNH 客户端"))
+ FeatureCount += 22
+ BugCount += 26
+ End If
If LastVersion < 362 Then 'Snapshot 2.10.3
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:无法安装部分使用老版本 PCL 导出的整合包"))
End If
@@ -204,8 +211,6 @@ Public Class FormMain
End Sub, "UpdateLog Output")
End Sub
- Private ReadOnly Helper As New DragFileHelper()
-
'窗口加载
Private IsWindowLoadFinished As Boolean = False
Public Sub New()
@@ -228,7 +233,7 @@ Public Class FormMain
If Not Setup.IsUnset("LaunchArgumentIndie") Then
Log("[Start] 从老 PCL 迁移版本隔离")
Setup.Set("LaunchArgumentIndieV2", Setup.Get("LaunchArgumentIndie"))
- ElseIf Not Setup.IsUnset("WindowHeight") Then
+ ElseIf Not Setup.IsUnset("LaunchVersionSelect") Then
Log("[Start] 从老 PCL 升级,但此前未调整版本隔离,使用老的版本隔离默认值")
Setup.Set("LaunchArgumentIndieV2", Setup.GetDefault("LaunchArgumentIndie"))
Else
@@ -244,36 +249,24 @@ Public Class FormMain
[AddHandler](DragDrop.DragOverEvent, New DragEventHandler(AddressOf HandleDrag), handledEventsToo:=True)
'加载 UI
InitializeComponent()
-
-
-
Opacity = 0
- '旧代码
- ''开启管理员权限下的文件拖拽,但下列代码也没用(#2531)
- 'If IsAdmin() Then
- ' Log("[Start] PCL 正以管理员权限运行")
+ '开启管理员权限下的文件拖拽
+ If IsAdmin() Then
+ Static Helper As New DragHelper
+ AddHandler SourceInitialized,
+ Sub()
+ Dim WpfHelper As New WindowInteropHelper(Me)
+ Helper.HwndIntPtrSource = HwndSource.FromHwnd(WpfHelper.Handle)
+ Helper.AddHook()
+ End Sub
+ AddHandler Closing, Sub() Helper.RemoveDragHook()
+ AddHandler Helper.DragDrop, Sub() FileDrag(Helper.DropFilePaths)
+ End If
+ 'If IsAdmin() Then '下列代码没用(#2531)
' ChangeWindowMessageFilter(&H233, 1)
' ChangeWindowMessageFilter(&H4A, 1)
' ChangeWindowMessageFilter(&H49, 1)
'End If
-
- '开启管理员权限下的文件拖拽
- If IsAdmin() Then
- AddHandler Me.SourceInitialized,
- Sub(sender, e)
- Dim wpfHelper As New WindowInteropHelper(Me)
- Helper.HwndIntPtrSource = HwndSource.FromHwnd(wpfHelper.Handle)
- Helper.AddHook()
- End Sub
- AddHandler Me.Closing,
- Sub(sender, e)
- Helper.RemoveDragHook()
- End Sub
- AddHandler Helper.DragDrop,
- Sub(sender, e)
- Me.FileDrag(Helper.DropFilePaths)
- End Sub
- End If
'切换到首页
If Not IsNothing(FrmLaunchLeft.Parent) Then FrmLaunchLeft.SetValue(ContentPresenter.ContentProperty, Nothing)
If Not IsNothing(FrmLaunchRight.Parent) Then FrmLaunchRight.SetValue(ContentPresenter.ContentProperty, Nothing)
@@ -390,7 +383,7 @@ Public Class FormMain
Catch ex As Exception
Log(ex, "清理自动更新文件失败")
End Try
- End Sub, "Start Loader", ThreadPriority.Lowest)
+ End Sub, "初始化", ThreadPriority.Lowest)
Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
End Sub
@@ -616,11 +609,12 @@ Public Class FormMain
'按键事件
Private Sub FormMain_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
If e.IsRepeat Then Return
- '调用弹窗:回车选择第一个,Esc 选择最后一个
- If PanMsg.Children.Count > 0 Then
+ '修复按下 Alt 后误认为弹出系统菜单导致的冻结
+ If e.SystemKey = Key.LeftAlt OrElse e.SystemKey = Key.RightAlt Then e.Handled = True
+ '在有弹窗时:回车选择第一个,Esc 选择最后一个
+ If PanMsg.Children.Any Then
If e.Key = Key.Enter Then
CType(PanMsg.Children(0), Object).Btn1_Click()
- Return
ElseIf e.Key = Key.Escape Then
Dim Msg As Object = PanMsg.Children(0)
If TypeOf Msg IsNot MyMsgInput AndAlso TypeOf Msg IsNot MyMsgSelect AndAlso Msg.Btn3.Visibility = Visibility.Visible Then
@@ -630,9 +624,14 @@ Public Class FormMain
Else
Msg.Btn1_Click()
End If
- Return
End If
+ Return
End If
+
+ '==========================
+ ' 在没有弹窗时:继续检查……
+ '==========================
+
'按 ESC 返回上一级
If e.Key = Key.Escape Then TriggerPageBack()
'更改隐藏版本可见性
@@ -666,8 +665,6 @@ Public Class FormMain
FrmLaunchLeft.LaunchButtonClick()
End If
End If
- '修复按下 Alt 后误认为弹出系统菜单导致的冻结
- If e.SystemKey = Key.LeftAlt OrElse e.SystemKey = Key.RightAlt Then e.Handled = True
End Sub
Private Sub FormMain_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles Me.MouseDown
'鼠标侧键返回上一级
@@ -732,6 +729,7 @@ Public Class FormMain
End Sub
Private Sub FrmMain_Drop(sender As Object, e As DragEventArgs) Handles Me.Drop
Try
+ ShowWindowToTop()
If e.Data.GetDataPresent(DataFormats.Text) Then
'获取文本
Try
@@ -1505,4 +1503,4 @@ Public Class FormMain
lastMouseArg = e
End Sub
-End Class
\ No newline at end of file
+End Class
diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb
index 407855a..86eb2a3 100644
--- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb
+++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb
@@ -13,13 +13,13 @@ Public Module ModBase
#Region "声明"
'下列版本信息由更新器自动修改
- Public Const VersionBaseName As String = "2.10.3" '不含分支前缀的显示用版本名
- Public Const VersionStandardCode As String = "2.10.3." & VersionBranchCode '标准格式的四段式版本号
+ Public Const VersionBaseName As String = "2.10.4" '不含分支前缀的显示用版本名
+ Public Const VersionStandardCode As String = "2.10.4." & VersionBranchCode '标准格式的四段式版本号
Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
#If BETA Then
Public Const VersionCode As Integer = 361 'Release
#Else
- Public Const VersionCode As Integer = 362 'Snapshot
+ Public Const VersionCode As Integer = 363 'Snapshot
#End If
'自动生成的版本信息
Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -1551,7 +1551,7 @@ RetryDir:
'''
''' 获取 JSON 对象。
'''
- Public Function GetJson(Data As String)
+ Public Function GetJson(Data As String) As JToken
Try
Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local})
Catch ex As Exception
@@ -2131,6 +2131,19 @@ RetryDir:
End Function
End Class
+ '''
+ ''' 将二维数组转换为字典。
+ '''
+ Public Function ToDictionary(Of T)(arr As T(,)) As Dictionary(Of T, T)
+ Dim result As New Dictionary(Of T, T)
+ If arr.Length = 0 Then Return result
+ If arr.GetLength(1) <> 2 Then Throw New ArgumentException("数组必须为两列,第一列为 Key,第二列为 Value。")
+ For i As Integer = 0 To arr.GetLength(0) - 1
+ result(arr(i, 0)) = arr(i, 1)
+ Next
+ Return result
+ End Function
+
'''
''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。
'''
@@ -2179,10 +2192,10 @@ RetryDir:
End Function
'''
- ''' 判断当前系统语言是否为 zh-CN。
+ ''' 判断当前系统语言是否为中文。
'''
Public Function IsSystemLanguageChinese() As Boolean
- Return CultureInfo.CurrentCulture.Name = "zh-CN" OrElse CultureInfo.CurrentUICulture.Name = "zh-CN"
+ Return CultureInfo.CurrentCulture.Name.StartsWithF("zh-") OrElse CultureInfo.CurrentUICulture.Name.StartsWithF("zh-")
End Function
Private Uuid As Integer = 1
@@ -2225,6 +2238,9 @@ NextElement:
Next i
Return ResultArray
End Function
+ Public Function Any(Arr As UIElementCollection) As Boolean
+ Return Arr?.Count > 0
+ End Function
'''
''' 获取格式类似于“11:08:52.037”的当前时间的字符串。
@@ -2462,7 +2478,7 @@ NextElement:
'''
Public Sub RunInThread(Action As Action)
If RunInUi() Then
- RunInNewThread(Action, "Runtime Invoke " & GetUuid() & "#")
+ RunInNewThread(Action, "Invoke " & GetUuid())
Else
Action()
End If
@@ -2760,11 +2776,11 @@ Retry:
Using Reader As New XamlXmlReader(Stream)
While Reader.Read()
For Each BlackListType In {GetType(WebBrowser), GetType(Frame), GetType(MediaElement), GetType(ObjectDataProvider), GetType(XamlReader), GetType(Window), GetType(XmlDataProvider)}
- If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 类型。")
- If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListType.Name} 值。")
+ If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 类型。")
+ If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 值。")
Next
For Each BlackListMember In {"Code", "FactoryMethod", "Static"}
- If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"不允许使用 {BlackListMember} 成员。")
+ If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListMember} 成员。")
Next
End While
End Using
@@ -2922,7 +2938,7 @@ Retry:
'处理错误会导致再次调用 Log() 导致无限循环
'输出日志
- Dim AppendText As String = "[" & GetTimeNow() & "] " & Text & vbCrLf '减轻同步锁占用
+ Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Text}{vbCrLf}" '减轻同步锁占用
If ModeDebug Then
SyncLock LogListLock
LogList.Append(AppendText)
@@ -2986,7 +3002,7 @@ Retry:
Dim ExFull As String = Desc & ":" & GetExceptionDetail(Ex)
'输出日志
- Dim AppendText As String = "[" & GetTimeNow() & "] " & Desc & ":" & GetExceptionDetail(Ex, True) & vbCrLf '减轻同步锁占用
+ Dim AppendText As String = $"[{GetTimeNow()}] <{If(Thread.CurrentThread.Name = "", "主线程", Thread.CurrentThread.Name)}> {Desc}:{GetExceptionDetail(Ex, True)}{vbCrLf}" '减轻同步锁占用
If ModeDebug Then
SyncLock LogListLock
LogList.Append(AppendText)
@@ -3048,7 +3064,7 @@ Retry:
If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then
OpenExplorer(Path & "PCL\Log1.txt")
End If
- OpenWebsite("https://github.com/Hex-Dragon/PCL2/issues/")
+ OpenWebsite("https://github.com/Meloong-Git/PCL/issues/")
End Sub
Public Function CanFeedback(ShowHint As Boolean) As Boolean
If False.Equals(PageSetupSystem.IsLauncherNewest) Then
diff --git a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb
index c0bb4c8..4719587 100644
--- a/Plain Craft Launcher 2/Modules/Base/ModLoader.vb
+++ b/Plain Craft Launcher 2/Modules/Base/ModLoader.vb
@@ -339,7 +339,7 @@
[Error] = ex
State = LoadState.Failed
End Try
- End Sub) With {.Name = Name, .Priority = ThreadPriority}
+ End Sub) With {.Name = "L/" & Name, .Priority = ThreadPriority}
LastRunningThread.Start() '不能使用 RunInNewThread,否则在函数返回前线程就会运行完,导致误判 IsAborted
End Sub
Public Overrides Sub Abort()
diff --git a/Plain Craft Launcher 2/Modules/Base/ModNet.vb b/Plain Craft Launcher 2/Modules/Base/ModNet.vb
index f7d1b3c..37c8c2e 100644
--- a/Plain Craft Launcher 2/Modules/Base/ModNet.vb
+++ b/Plain Craft Launcher 2/Modules/Base/ModNet.vb
@@ -1,301 +1,41 @@
-Imports System.Drawing
-Imports System.Net.Http
-Imports System.Net.Http.Headers
+Imports System.Net.Http.Headers
Imports System.Threading.Tasks
-Imports Newtonsoft.Json
+Imports CacheCow.Client
Public Module ModNet
- Public Const NetDownloadEnd As String = ".PCLDownloading"
- Public ReadOnly HttpProxy As New HttpWebProxyFactory()
- Private ClientFactory As New HttpClientFactory()
- Private Request As New HttpRequest()
- Public Class HttpRequest
- Public Sub New()
- End Sub
- Public Function GetRequestMessage(RequestUrl As String,
- Optional Method As String = "GET",
- Optional Headers As Dictionary(Of String, String) = Nothing,
- Optional Data As Byte() = Nothing,
- Optional UseBrowserUA As Boolean = False,
- Optional RequireHeaderSign As Boolean = False,
- Optional RequireCdnSign As Boolean = False) As HttpRequestMessage
- Dim RequestMethod As HttpMethod = GetHttpMethod(Method)
- If RequireCdnSign Then RequestUrl = SecretCdnSign(RequestUrl)
-
- Dim RequestMessage As New HttpRequestMessage(RequestMethod, RequestUrl)
- '基本参数验证
- If Data IsNot Nothing AndAlso Not {HttpMethod.Get, HttpMethod.Head}.Contains(RequestMethod) Then
- RequestMessage.Content = New ByteArrayContent(Data)
- RequestMessage.Content.Headers.ContentLength = Data.Length
- End If
-
- If Headers IsNot Nothing Then
- For Each Header In Headers
- If Header.Key.ContainsF("Content") AndAlso RequestMessage.Content IsNot Nothing Then
- 'FixContentHeader 往下挪一行就 NullReference 了
-FixContentHeader:
- If Data Is Nothing OrElse RequestMessage.Content Is Nothing Then Continue For
- If RequestMessage.Content.Headers.Contains(Header.Key) Then Continue For
- If Not RequestMessage.Content.Headers.Contains(Header.Key) Then
- If Header.Key.ToLower() = "Content-Type" Then RequestMessage.Content.Headers.ContentType = New MediaTypeHeaderValue(Header.Value)
- RequestMessage.Content.Headers.TryAddWithoutValidation(Header.Key, Header.Value)
- End If
- Else
- '不知道为什么 Content-Type 标头会通过字符串验证然后加入不应该被加入的 HttpRequestMessage Headers
- '这会导致 HttpRequestMessage 内部的标头验证失败,并抛出一个 Exception 导致网络请求失败
- If Header.Key.ContainsF("Content") Then GoTo FixContentHeader
- If RequestMessage.Headers.Contains(Header.Key) Then Continue For
- RequestMessage.Headers.Add(Header.Key, Header.Value)
- End If
- Next
- End If
-
- If RequireHeaderSign Then
- SecretHeadersSign(RequestUrl, requestMessage, UseBrowserUA)
- End If
-
- Return RequestMessage
-
- End Function
-
- ' 发送请求
- Public Function SendRequest(Req As HttpRequestMessage,
- Optional RequireDecode As Boolean = True,
- Optional Encode As Encoding = Nothing,
- Optional Timeout As Integer = 25000,
- Optional RequireReturnRespObj As Boolean = False)
- Using CTS As New CancellationTokenSource(Timeout)
- Dim Client = ClientFactory.GetHttpClient()
- Dim Resp As HttpResponseMessage = Client.SendAsync(Req, HttpCompletionOption.ResponseHeadersRead, CTS.Token).GetAwaiter().GetResult()
-
- If RequireReturnRespObj Then Return Resp
-
- Resp.EnsureSuccessStatusCode()
- CTS.CancelAfter(Timeout)
- Dim ResultStream = Resp.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
- Using DataStream As New MemoryStream()
- ResultStream.CopyTo(DataStream)
- Dim Bytes = DataStream.ToArray()
- Resp.Dispose()
- Req.Dispose()
-
- If RequireDecode Then
- If Encode Is Nothing Then Encode = Encoding.UTF8
- Return Encode.GetString(Bytes)
- Else
- Return Bytes
- End If
- End Using
- End Using
-
-
- End Function
-
- ' 获取HttpMethod
- Private Function GetHttpMethod(Method As String) As HttpMethod
- Select Case Method?.Trim().ToUpperInvariant()
- Case "HEAD"
- Return HttpMethod.Head
- Case "POST"
- Return HttpMethod.Post
- Case "PUT"
- Return HttpMethod.Put
- Case "DELETE"
- Return HttpMethod.Delete
- Case Else
- Return HttpMethod.Get
- End Select
- End Function
-
-
- End Class
-
- Public Class HttpWebProxyFactory
- Implements IWebProxy
- Public DisableHttpProxy As Boolean = True
- Public ProxyAddress As String
- Private CurrentProxy As WebProxy
- Public RequiredReloadProxy As Boolean
- Public RefreshSyncLock As New Object
- Public SystemProxy As New WebProxy()
- Public Sub New()
- End Sub
-
- Public Property Credentials As ICredentials Implements IWebProxy.Credentials
- Get
- Return AuthCredentials
- End Get
- Set(value As ICredentials)
- AuthCredentials = value
- End Set
- End Property
- Private AuthCredentials As ICredentials
-
- Public Function GetProxy(destination As Uri) As Uri Implements IWebProxy.GetProxy
- Return GetProxy(destination.AbsoluteUri).Address
- End Function
- Public Function GetProxy(destination As String) As WebProxy
- If CurrentProxy IsNot Nothing AndAlso Not RequiredReloadProxy Then Return CurrentProxy
- If RequiredReloadProxy OrElse CurrentProxy Is Nothing Then
- If ProxyAddress IsNot Nothing OrElse
- Not String.IsNullOrWhiteSpace(ProxyAddress) Then
- SyncLock RefreshSyncLock
- CurrentProxy = New WebProxy(ProxyAddress, True)
- If AuthCredentials IsNot Nothing Then CurrentProxy.Credentials = AuthCredentials
- Return CurrentProxy
- End SyncLock
- Else
- SyncLock RefreshSyncLock
- CurrentProxy = New WebProxy()
- If AuthCredentials IsNot Nothing Then CurrentProxy.Credentials = AuthCredentials
- Return CurrentProxy
- End SyncLock
- End If
- End If
- Return Nothing
- End Function
- Public Function IsBypassed(host As Uri) As Boolean Implements IWebProxy.IsBypassed
- If DisableHttpProxy Then Return True
- If CurrentProxy Is Nothing Then
- SyncLock RefreshSyncLock
- CurrentProxy = GetProxy(host.AbsoluteUri)
- End SyncLock
- End If
- If CurrentProxy.IsBypassed(host) Then Return True
- Return False
- End Function
- End Class
-
- Public Class HttpClientFactory
- Private PreviousClient As HttpClient
- Private CurrentClient As HttpClient
- Private HttpClientLock As New Object
- Public ClientHandler As New HttpClientHandler() With {
- .UseProxy = True,
- .Proxy = HttpProxy,
- .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None
- }
- Public Sub New()
- HttpClientReload()
- End Sub
- Public Function GetHttpClient(Optional Handler As HttpClientHandler = Nothing) As HttpClient
- SyncLock HttpClientLock
- If Handler IsNot Nothing Then
- ClientHandler = Handler
- CurrentClient.Dispose()
- CurrentClient = New HttpClient(Handler)
- Return CurrentClient
- End If
- If CurrentClient IsNot Nothing Then Return CurrentClient
- CurrentClient = New HttpClient(ClientHandler) With {.Timeout = Timeout.InfiniteTimeSpan}
- Return CurrentClient
- End SyncLock
- End Function
- Public Sub HttpClientReload()
- RunInNewThread(
- Sub()
- While True
- Thread.Sleep(TimeSpan.FromHours(6))
- If PreviousClient IsNot Nothing Then PreviousClient.Dispose()
- PreviousClient = CurrentClient
- CurrentClient = GetHttpClient()
- End While
- End Sub
- )
- End Sub
-
- End Class
+#Region "网络请求"
'''
- ''' 测试 Ping。失败则返回 -1。
+ ''' 发送一次网络请求并获取返回内容。
'''
- '''
- Public Function Ping(Ip As String, Optional Timeout As Integer = 10000, Optional MakeLog As Boolean = True) As Integer
- Dim PingResult As NetworkInformation.PingReply
+ Public Function NetRequestByClient(Url As String, Optional Method As HttpMethod = Nothing,
+ Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*",
+ Optional Timeout As Integer = 25000, Optional Headers As String(,) = Nothing,
+ Optional Encoding As Encoding = Nothing, Optional UseBrowserUserAgent As Boolean = False) As String
+ If Method Is Nothing Then Method = HttpMethod.Get
+ Log("[Net] 发起网络请求(" & Method.Method & "," & Url & "),最大超时 " & Timeout)
Try
- PingResult = (New NetworkInformation.Ping).Send(Ip)
+ Dim HeaderDictionary = If(Headers, {}).ToDictionary
+ If ContentType IsNot Nothing Then HeaderDictionary("Content-Type") = ContentType
+ If Accept IsNot Nothing Then HeaderDictionary("Accept") = Accept
+ Return If(Encoding, Encoding.UTF8).GetString(SendRequest(Url, Method, Content, HeaderDictionary, Timeout:=Timeout, Encoding:=Encoding, UseBrowserUserAgent:=UseBrowserUserAgent))
+ Catch ex As ThreadInterruptedException
+ Throw
Catch ex As Exception
- If MakeLog Then Log("[Net] Ping " & Ip & " 失败:" & ex.Message)
- Return -1
+ Log(ex, "网络请求失败", LogLevel.Developer)
+ Throw
End Try
- If PingResult.Status = NetworkInformation.IPStatus.Success Then
- If MakeLog Then Log("[Net] Ping " & Ip & " 结束:" & PingResult.RoundtripTime & "ms")
- Return PingResult.RoundtripTime
- Else
- If MakeLog Then Log("[Net] Ping " & Ip & " 失败")
- Return -1
- End If
End Function
-
'''
- ''' 以 WebClient 获取网页源代码。会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。
+ ''' 发送网络请求并获取返回内容。
+ ''' 会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。
'''
- ''' 网页的 Url。
- ''' 网页的编码,通常为 UTF-8。
- Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Optional Accept As String = "application/json, text/javascript, */*; q=0.01", Optional UseBrowserUserAgent As Boolean = False) As String
- Dim RetryCount As Integer = 0
- Dim RetryException As Exception = Nothing
- Dim StartTime As Long = GetTimeTick()
- Try
-Retry:
- Select Case RetryCount
- Case 0 '正常尝试
- Return NetGetCodeByClient(Url, Encoding, 10000, Accept, UseBrowserUserAgent)
- Case 1 '慢速重试
- Thread.Sleep(500)
- Return NetGetCodeByClient(Url, Encoding, 30000, Accept, UseBrowserUserAgent)
- Case Else '快速重试
- If GetTimeTick() - StartTime > 5500 Then
- '若前两次加载耗费 5 秒以上,才进行重试
- Thread.Sleep(500)
- Return NetGetCodeByClient(Url, Encoding, 4000, Accept, UseBrowserUserAgent)
- Else
- Throw RetryException
- End If
- End Select
- Catch ex As Exception
- Select Case RetryCount
- Case 0
- RetryException = ex
- RetryCount += 1
- GoTo Retry
- Case 1
- RetryCount += 1
- GoTo Retry
- Case Else
- Throw
- End Select
- End Try
- End Function
- Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Timeout As Integer, Accept As String, Optional UseBrowserUserAgent As Boolean = False) As String
- Log("[Net] 获取客户端网络结果:" & Url & ",最大超时 " & Timeout)
- Try
- Dim Headers As New Dictionary(Of String, String) From {
- {"Accept", Accept}
- }
- Using Req As HttpRequestMessage = Request.GetRequestMessage(Url, Headers:=Headers, RequireHeaderSign:=True, RequireCdnSign:=True, UseBrowserUA:=UseBrowserUserAgent)
- Req.Headers.AcceptLanguage.Add(New StringWithQualityHeaderValue("en-US,en;q=0.5"))
- Req.Headers.TryAddWithoutValidation("X-Request-With","XMLHttpRequest")
- Dim Resp As String = Request.SendRequest(Req, Encode:=Encoding, Timeout:=Timeout)
-
- Return Resp
- End Using
- Catch ex As TaskCanceledException
- Throw New TaskCanceledException("连接服务器超时(" & Url & ")", ex)
- Catch ex As Exception
- Throw New WebException("获取结果失败," & ex.Message & "(" & Url & ")", ex)
- End Try
- End Function
-
- '''
- ''' 以 WebRequest 获取网页源代码或 Json。会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。
- '''
- ''' 网页的 Url。
- ''' 网页的编码,通常为 UTF-8。
''' 如果第一次尝试失败,换用的备用 URL。
- Public Function NetGetCodeByRequestRetry(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "",
- Optional IsJson As Boolean = False, Optional BackupUrl As String = Nothing, Optional UseBrowserUserAgent As Boolean = False)
+ Public Function NetRequestByClientRetry(Url As String, Optional Method As HttpMethod = Nothing, Optional BackupUrl As String = Nothing,
+ Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*",
+ Optional Headers As String(,) = Nothing,
+ Optional Encoding As Encoding = Nothing, Optional UseBrowserUserAgent As Boolean = False) As String
Dim RetryCount As Integer = 0
Dim RetryException As Exception = Nothing
Dim StartTime As Long = GetTimeTick()
@@ -303,15 +43,15 @@ Retry:
Retry:
Select Case RetryCount
Case 0 '正常尝试
- Return NetGetCodeByRequestOnce(Url, Encode, 10000, IsJson, Accept, UseBrowserUserAgent)
+ Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 10000, Headers, Encoding, UseBrowserUserAgent)
Case 1 '慢速重试
Thread.Sleep(500)
- Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 30000, IsJson, Accept, UseBrowserUserAgent)
+ Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 30000, Headers, Encoding, UseBrowserUserAgent)
Case Else '快速重试
If GetTimeTick() - StartTime > 5500 Then
'若前两次加载耗费 5 秒以上,才进行重试
Thread.Sleep(500)
- Return NetGetCodeByRequestOnce(If(BackupUrl, Url), Encode, 4000, IsJson, Accept, UseBrowserUserAgent)
+ Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 4000, Headers, Encoding, UseBrowserUserAgent)
Else
Throw RetryException
End If
@@ -333,20 +73,22 @@ Retry:
End Try
End Function
'''
- ''' 以 WebRequest 获取网页源代码或 Json。会逐渐生成 4 个尝试线程,并在 60s 后超时。
+ ''' 生成数个线程,同时发送网络请求并获取返回内容。
+ ''' 每个线程开始之前会略有延迟。
'''
- ''' 网页的 Url。
- ''' 网页的编码,通常为 UTF-8。
- Public Function NetGetCodeByRequestMultiple(Url As String, Optional Encode As Encoding = Nothing, Optional Accept As String = "", Optional IsJson As Boolean = False)
+ Public Function NetRequestByClientMultiple(Url As String, Optional Method As HttpMethod = Nothing, Optional ThreadCount As Integer = 3,
+ Optional Content As Object = Nothing, Optional ContentType As String = Nothing, Optional Accept As String = "*/*",
+ Optional Headers As String(,) = Nothing, Optional Timeout As Integer = 25000,
+ Optional Encoding As Encoding = Nothing, Optional UseBrowserUserAgent As Boolean = False) As String
Dim Threads As New List(Of Thread)
Dim RequestResult = Nothing
Dim RequestEx As Exception = Nothing
Dim FailCount As Integer = 0
- For i = 1 To 4
+ For i = 1 To ThreadCount
Dim th As New Thread(
Sub()
Try
- RequestResult = NetGetCodeByRequestOnce(Url, Encode, 30000, IsJson, Accept)
+ RequestResult = NetRequestByClient(Url, Method, Content, ContentType, Accept, Timeout, Headers, Encoding, UseBrowserUserAgent)
Catch ex As Exception
FailCount += 1
RequestEx = ex
@@ -367,7 +109,7 @@ RequestFinished:
Catch
End Try
Return RequestResult
- ElseIf FailCount = 4 Then
+ ElseIf FailCount = ThreadCount Then
Try
For Each th In Threads
If th.IsAlive Then th.Interrupt()
@@ -378,112 +120,71 @@ RequestFinished:
End If
Thread.Sleep(20)
Loop
- Throw New Exception("未知错误")
- End Function
- Public Function NetGetCodeByRequestOnce(Url As String, Optional Encode As Encoding = Nothing, Optional Timeout As Integer = 30000, Optional IsJson As Boolean = False, Optional Accept As String = "", Optional UseBrowserUserAgent As Boolean = False)
- If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求")
- Log($"[Net] 获取网络结果:{Url},超时 {Timeout}ms{If(IsJson, ",要求 json", "")}")
- Try
- Dim Headers As New Dictionary(Of String, String) From {
- {"Accept", If(Accept = "", "*/*", Accept)}
- }
- Using Req As HttpRequestMessage = Request.GetRequestMessage(Url, Headers:=Headers, UseBrowserUA:=UseBrowserUserAgent, RequireHeaderSign:=True, RequireCdnSign:=True)
- Dim Resp As String = Request.SendRequest(Req, Encode:=Encode, Timeout:=Timeout)
- Return If(IsJson, GetJson(Resp), Resp)
- End Using
-
- Catch ex As ThreadInterruptedException
- Throw
-
- Catch ex As TaskCanceledException
- Throw New TimeoutException($"获取结果失败({ex.Message},{Url})", ex)
- Catch ex As HttpRequestException
- Throw New WebException($"获取结果失败({ex.Message},{Url})", ex)
- End Try
+ Return Nothing
End Function
'''
- ''' 以多线程下载网页文件的方式获取网页源代码。
+ ''' 以多线程下载网页文件的方式获取内容。
+ ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。
'''
- ''' 网页的 Url。
- Public Function NetGetCodeByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
- Dim Temp As String = RequestTaskTempFolder() & "download.txt"
- Dim NewTask As New LoaderDownload("源码获取 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, Temp, New FileChecker With {.IsJson = IsJson}, UseBrowserUserAgent)})
- Try
- NewTask.WaitForExitTime(Timeout, TimeoutMessage:="连接服务器超时(" & Url & ")")
- NetGetCodeByLoader = ReadFile(Temp)
- File.Delete(Temp)
- Finally
- NewTask.Abort()
- End Try
+ Public Function NetRequestByLoader(Url As String, Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
+ Return NetRequestByLoader({Url}, Timeout, IsJson, UseBrowserUserAgent)
End Function
'''
- ''' 以多线程下载网页文件的方式获取网页源代码。
+ ''' 以多线程下载网页文件的方式获取内容。
+ ''' 不支持缓存协商;若需缓存协商,需要换用 NetRequestByClient。
'''
- ''' 网页的 Url 列表。
- Public Function NetGetCodeByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional UseBrowserUserAgent As Boolean = False) As String
+ Public Function NetRequestByLoader(Urls As IEnumerable(Of String), Optional Timeout As Integer = 45000, Optional IsJson As Boolean = False, Optional 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 & ")")
- NetGetCodeByLoader = ReadFile(Temp)
+ NetRequestByLoader = ReadFile(Temp)
File.Delete(Temp)
Finally
NewTask.Abort()
End Try
End Function
+#End Region
+
+#Region "文件下载"
+
'''
- ''' 使用 WebClient 从网络中下载文件。这不能下载 CDN 中的文件。
+ ''' 通过网络请求直接下载小文件,若文件已存在将被覆盖。
+ ''' 不建议用于下载大文件。
'''
- ''' 网络 Url。
- ''' 下载的本地地址。
- Public Sub NetDownloadByClient(Url As String, LocalFile As String, Optional UseBrowserUserAgent As Boolean = False)
- Log("[Net] 直接下载文件:" & Url)
- '初始化
+ Public Sub NetDownloadByClient(Url As String, LocalPath As String)
+ Log($"[Net] 通过网络请求直接下载小文件:{Url} → {LocalPath}")
+ '初始化文件路径
Try
- '建立目录
- Directory.CreateDirectory(GetPathFromFullPath(LocalFile))
- '尝试删除原文件
- File.Delete(LocalFile)
+ Directory.CreateDirectory(GetPathFromFullPath(LocalPath))
+ File.Delete(LocalPath)
Catch ex As Exception
- Throw New WebException($"预处理下载文件路径失败({LocalFile})", ex)
+ Throw New Exception($"预处理下载文件路径失败({LocalPath})", ex)
End Try
'下载
- Using Client As New WebClient
- Try
- 'SecretHeadersSign(Url, Client, UseBrowserUserAgent)
- Client.DownloadFile(Url, LocalFile)
- Catch ex As Exception
- File.Delete(LocalFile)
- Throw New WebException($"直接下载文件失败({Url})", ex)
- End Try
- End Using
- End Sub
-
- '''
- ''' 简单的多线程下载文件。可以下载 CDN 中的文件。
- '''
- ''' 文件的 Url。
- ''' 下载的本地地址。
- Public Sub NetDownloadByLoader(Url As String, LocalFile As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False)
- Dim NewTask As New LoaderDownload("文件下载 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile({Url}, LocalFile, Check, UseBrowserUserAgent)})
Try
- NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress)
+ File.WriteAllBytes(LocalPath, SendRequest(Url, HttpMethod.Get))
Catch ex As Exception
- Throw New WebException($"多线程直接下载文件失败({Url})", ex)
- Finally
- NewTask.Abort()
+ File.Delete(LocalPath)
+ Throw New WebException($"通过网络请求直接下载小文件失败({LocalPath})", ex)
End Try
End Sub
'''
- ''' 简单的多线程下载文件。可以下载 CDN 中的文件。
+ ''' 通过多线程下载引擎下载文件。
+ '''
+ Public Sub NetDownloadByLoader(Url As String, LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False)
+ NetDownloadByLoader({Url}, LocalPath, LoaderToSyncProgress, Check, UseBrowserUserAgent)
+ End Sub
+ '''
+ ''' 通过多线程下载引擎下载文件。
'''
''' 文件的 Url 列表。
- ''' 下载的本地地址。
- Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalFile As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False)
- Dim NewTask As New LoaderDownload("文件下载 " & GetUuid() & "#", New List(Of NetFile) From {New NetFile(Urls, LocalFile, Check, UseBrowserUserAgent)})
+ ''' 下载的本地地址。
+ Public Sub NetDownloadByLoader(Urls As IEnumerable(Of String), LocalPath As String, Optional LoaderToSyncProgress As LoaderBase = Nothing, Optional Check As FileChecker = Nothing, Optional UseBrowserUserAgent As Boolean = False)
+ Dim NewTask As New LoaderDownload($"文件下载 {GetUuid()}#", New List(Of NetFile) From {New NetFile(Urls, LocalPath, Check, UseBrowserUserAgent)})
Try
NewTask.WaitForExit(LoaderToSyncProgress:=LoaderToSyncProgress)
Catch ex As Exception
@@ -493,140 +194,86 @@ RequestFinished:
End Try
End Sub
- '''
- ''' 发送一个网络请求并获取返回内容,会重试三次并在最长 45s 后超时。
- '''
- ''' 请求的服务器地址。
- ''' 请求方式(POST 或 GET)。
- ''' 请求的内容。
- ''' 请求的套接字类型。
- ''' 当返回 40x 时不重试。
- Public Function NetRequestRetry(Url As String, Method As String, Data As Object, ContentType As String, Optional DontRetryOnRefused As Boolean = True, Optional Headers As Dictionary(Of String, String) = Nothing, Optional RequireReturnRespObj As Boolean = False) As String
- Dim RetryCount As Integer = 0
- Dim RetryException As Exception = Nothing
- Dim StartTime As Long = GetTimeTick()
+#End Region
+
+#Region "基础请求引擎"
+
+ '基础请求函数
+ Private Function SendRequest(Url As String, Method As HttpMethod,
+ Optional Content As Object = Nothing, Optional Headers As Dictionary(Of String, String) = Nothing,
+ Optional UseBrowserUserAgent As Boolean = False, Optional Timeout As Integer = 25000, Optional Encoding As Encoding = Nothing) As Byte()
+ If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求")
+ Dim Request As HttpRequestMessage = Nothing, CancelToken As CancellationTokenSource = Nothing, Response As HttpResponseMessage = Nothing
Try
-Retry:
- Select Case RetryCount
- Case 0 '正常尝试
- Return NetRequestOnce(Url, Method, Data, ContentType, 15000, Headers, RequiredReturnRespObj:=RequireReturnRespObj)
- Case 1 '慢速重试
- Thread.Sleep(500)
- Return NetRequestOnce(Url, Method, Data, ContentType, 25000, Headers, RequiredReturnRespObj:=RequireReturnRespObj)
- Case Else '快速重试
- If GetTimeTick() - StartTime > 5500 Then
- '若前两次加载耗费 5 秒以上,才进行重试
- Thread.Sleep(500)
- Return NetRequestOnce(Url, Method, Data, ContentType, 4000, Headers,RequiredReturnRespObj:=RequireReturnRespObj)
+ Static Client As HttpClient = ClientExtensions.CreateClient(New FileCacheStore.FileStore(PathTemp & "Cache/Http/"), New HttpClientHandler With {
+ .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None,
+ .UseCookies = False '不设为 False 就不能从 Header 手动传入 Cookies
+ })
+ Url = SecretCdnSign(Url)
+ Request = New HttpRequestMessage(Method, Url)
+ '写入 Content
+ If Content IsNot Nothing AndAlso Request.Method <> HttpMethod.Get AndAlso Request.Method <> HttpMethod.Head Then
+ Dim Bytes As Byte() = If(TypeOf Content Is Byte(), Content, If(Encoding, Encoding.UTF8).GetBytes(Content.ToString))
+ Request.Content = New ByteArrayContent(Bytes)
+ Request.Content.Headers.ContentLength = Bytes.Length
+ End If
+ '写入 Headers
+ If Headers IsNot Nothing Then
+ For Each Header In Headers
+ If Header.Key.ToLower = "content-type" Then
+ Request.Content?.Headers.TryAddWithoutValidation(Header.Key, Header.Value)
Else
- Throw RetryException
+ Request.Headers.Add(Header.Key, Header.Value)
End If
- End Select
+ Next
+ End If
+ SecretHeadersSign(Url, Request, UseBrowserUserAgent)
+ '发送请求
+ CancelToken = New CancellationTokenSource(Timeout)
+ Response = Client.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult()
+ Dim ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
+ Dim ResponseBytes As Byte()
+ Using Stream As New MemoryStream
+ ResponseStream.CopyToAsync(Stream, 81920, CancelToken.Token).GetAwaiter().GetResult()
+ ResponseBytes = Stream.ToArray()
+ End Using
+ '输出
+ If Response.IsSuccessStatusCode Then
+ Return ResponseBytes
+ Else
+ Throw New ResponsedWebException($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Method},{Url}", If(Encoding, Encoding.UTF8).GetString(ResponseBytes))
+ End If
Catch ex As ThreadInterruptedException
Throw
+ Catch ex As ResponsedWebException
+ Throw
+ Catch ex As TaskCanceledException 'CancellationTokenSource 超时
+ Throw New WebException($"连接服务器超时,请检查你的网络环境是否良好({Method}, {Url})", WebExceptionStatus.Timeout)
Catch ex As Exception
- If ex.InnerException IsNot Nothing AndAlso ex.InnerException.Message.Contains("(40") AndAlso DontRetryOnRefused Then Throw
- Select Case RetryCount
- Case 0
- If ModeDebug Then Log(ex, "[Net] 网络请求第一次失败(" & Url & ")")
- RetryException = ex
- RetryCount += 1
- GoTo Retry
- Case 1
- If ModeDebug Then Log(ex, "[Net] 网络请求第二次失败(" & Url & ")")
- RetryCount += 1
- GoTo Retry
- Case Else
- Throw
- End Select
+ Throw New Exception($"网络请求出现意外异常({Method}, {Url})", ex)
+ Finally
+ Request?.Dispose()
+ CancelToken?.Dispose()
+ Response?.Dispose()
End Try
End Function
- '''
- ''' 同时发送多个网络请求并要求返回内容。
- '''
- Public Function NetRequestMultiple(Url As String, Method As String, Data As Object, ContentType As String, Optional RequestCount As Integer = 4, Optional Headers As Dictionary(Of String, String) = Nothing, Optional MakeLog As Boolean = True)
- Dim Threads As New List(Of Thread)
- Dim RequestResult = Nothing
- Dim RequestEx As Exception = Nothing
- Dim FailCount As Integer = 0
- For i = 1 To RequestCount
- Dim th As New Thread(
- Sub()
- Try
- RequestResult = NetRequestOnce(Url, Method, Data, ContentType, 30000, Headers, MakeLog)
- Catch ex As Exception
- FailCount += 1
- RequestEx = ex
- End Try
- End Sub)
- th.Start()
- Threads.Add(th)
- Thread.Sleep(i * 250)
- If RequestResult IsNot Nothing Then GoTo RequestFinished
- Next
- Do While True
- If RequestResult IsNot Nothing Then
-RequestFinished:
- For Each th In Threads
- If th.IsAlive Then th.Interrupt()
- Next
- Return RequestResult
- ElseIf FailCount = RequestCount Then
- For Each th In Threads
- If th.IsAlive Then th.Interrupt()
- Next
- Throw RequestEx
- End If
- Thread.Sleep(20)
- Loop
- Throw New Exception("未知错误")
- End Function
- '''
- ''' 发送一次网络请求并获取返回内容。
- '''
- Public Function NetRequestOnce(Url As String, Method As String, Data As Object, ContentType As String, Optional Timeout As Integer = 25000, Optional Headers As Dictionary(Of String, String) = Nothing, Optional MakeLog As Boolean = True, Optional UseBrowserUserAgent As Boolean = False, Optional RequiredReturnRespObj As Boolean = False) As Object
- If RunInUi() AndAlso Not Url.Contains("//127.") Then Throw New Exception("在 UI 线程执行了网络请求")
- If MakeLog Then Log("[Net] 发起网络请求(" & Method & "," & Url & "),最大超时 " & Timeout)
- Try
- Dim ReqHeaders As New Dictionary(Of String, String)
- If ContentType IsNot Nothing AndAlso Data IsNot Nothing Then
- ' 可能指定了 Content-Type 但是 Headers 是 Nothing
- If Headers IsNot Nothing Then
- If Not Headers.ContainsKey("Content-Type") Then Headers.Add("Content-Type", ContentType)
- Else
- If Not ReqHeaders.ContainsKey("Content-Type") Then ReqHeaders.Add("Content-Type", ContentType)
- End If
- End If
- Dim ReqData As Byte() = If(TypeOf Data Is String, Encoding.UTF8.GetBytes(Data), Data)
-
- Dim Req As HttpRequestMessage = Request.GetRequestMessage(Url, Method, If(Headers, ReqHeaders), If(Data IsNot Nothing, ReqData, Nothing), UseBrowserUserAgent, True, True)
- Return Request.SendRequest(Req, If(RequiredReturnRespObj, False, True), Timeout:=Timeout, RequireReturnRespObj:=RequiredReturnRespObj)
- Catch ex As ThreadInterruptedException
- Throw
- Catch ex As TaskCanceledException
- Return New WebException($"连接服务器超时,请检查你的网络环境是否良好({ex.Message},{Url})", ex)
- Catch ex As HttpRequestException
- If MakeLog Then Log(ex, "NetRequestOnce 失败", LogLevel.Developer)
- Throw New WebException(ex.Message, ex)
- Catch ex As Exception
- ex = New WebException("网络请求失败(" & Url & ")", ex)
- If MakeLog Then Log(ex, "NetRequestOnce 失败", LogLevel.Developer)
- Throw ex
- End Try
- End Function
Public Class ResponsedWebException
Inherits WebException
'''
''' 远程服务器给予的回复。
'''
Public Overloads Property Response As String
- Public Sub New(Message As String, Response As String, InnerException As Exception)
- MyBase.New(Message, InnerException)
+ Public Sub New(Message As String, Response As String)
+ MyBase.New(Message)
Me.Response = Response
End Sub
End Class
+#End Region
+
+#Region "多线程下载引擎"
+
'''
''' 最大线程数。
'''
@@ -659,7 +306,10 @@ RequestFinished:
Public Url As String
Public FailCount As Integer
Public Ex As Exception
- Public Thread As NetThread
+ '''
+ ''' 若该下载源正在进行强制单线程下载,标记这个唯一的线程。
+ '''
+ Public SingleThread As NetThread
Public IsFailed As Boolean
Public Overrides Function ToString() As String
Return Url
@@ -728,7 +378,7 @@ RequestFinished:
''' 下载线程。
'''
Public Class NetThread
- Implements IEnumerable(Of NetThread)
+ Implements IEnumerable(Of NetThread), IEquatable(Of NetThread)
'''
''' 对应的下载任务。
@@ -787,7 +437,7 @@ RequestFinished:
SyncLock Task.LockChain
If NextThread Is Nothing Then
If Task.IsUnknownSize Then
- Return 5 * 1024 * 1024 * 1024L '5G
+ Return 1000 * 1000 * 1000 * 1000L '约 1T
Else
Return Task.FileSize - 1
End If
@@ -861,6 +511,19 @@ RequestFinished:
'''
Public Source As NetSource
+ '允许进行 UUID 比较
+ Public Overloads Function Equals(other As NetThread) As Boolean Implements IEquatable(Of NetThread).Equals
+ Return other IsNot Nothing AndAlso Uuid = other.Uuid
+ End Function
+ Public Overrides Function Equals(obj As Object) As Boolean
+ Return Equals(TryCast(obj, NetThread))
+ End Function
+ Public Shared Operator =(left As NetThread, right As NetThread) As Boolean
+ Return EqualityComparer(Of NetThread).Default.Equals(left, right)
+ End Operator
+ Public Shared Operator <>(left As NetThread, right As NetThread) As Boolean
+ Return Not left = right
+ End Operator
End Class
'''
''' 下载单个文件。
@@ -886,12 +549,17 @@ RequestFinished:
'''
Public SourcesOnce As New SafeList(Of NetSource)
'''
+ ''' 仅当合并失败或首次下载失败时,会将所有下载源重新标记为不允许断点续传的下载源,逐个重新尝试下载。
+ ''' 这一策略可以兼容多个下载源中的一部分返回错误的文件的情况,以及部分在多线程下载时会抽风的源。
+ '''
+ Private Retried As Boolean = False
+ '''
''' 获取从某个源开始,第一个可用的源。
'''
Private Function GetSource(Optional Id As Integer = 0) As NetSource
If Id >= Sources.Count OrElse Id < 0 Then Id = 0
SyncLock LockSource
- If Not IsSourceFailed(False) Then
+ If HasAvailableSource(False) Then
'存在多线程可用源
Dim CurrentSource As NetSource = Sources(Id)
While CurrentSource.IsFailed
@@ -902,7 +570,7 @@ RequestFinished:
Return CurrentSource
ElseIf SourcesOnce.Any Then
'仅存在单线程可用源
- Return SourcesOnce(0)
+ Return SourcesOnce.First
Else
'没有可用源
Return Nothing
@@ -912,14 +580,12 @@ RequestFinished:
'''
''' 是否已经没有可用源了。
'''
- Public Function IsSourceFailed(Optional AllowOnceSource As Boolean = True) As Boolean
- If AllowOnceSource AndAlso SourcesOnce.Any Then Return False
+ Public Function HasAvailableSource(Optional AllowOnceSource As Boolean = True) As Boolean
SyncLock LockSource
- For Each Source As NetSource In Sources
- If Not Source.IsFailed Then Return False
- Next
+ If AllowOnceSource AndAlso SourcesOnce.Any Then Return True
+ If Sources.Any(Function(s) Not s.IsFailed) Then Return True
End SyncLock
- Return True
+ Return False
End Function
'''
@@ -961,12 +627,11 @@ RequestFinished:
Get
Return IsUnknownSize OrElse FileSize < FilePieceLimit
End Get
-
End Property
'''
- ''' 为不需要分割的小文件进行临时存储。
+ ''' 为不需要分割的小文件在内存中提供临时存储。
'''
- Private SmailFileCache As Queue(Of Byte)
+ Private Cache As MemoryStream
'''
''' 文件的已下载大小。
@@ -1101,7 +766,7 @@ RequestFinished:
Try
'条件检测
- If NetTaskThreadCount >= NetTaskThreadLimit OrElse IsSourceFailed() OrElse
+ If NetTaskThreadCount >= NetTaskThreadLimit OrElse Not HasAvailableSource() OrElse
(IsNoSplit AndAlso Threads IsNot Nothing AndAlso Threads.State <> NetState.Error) Then Return Nothing
If State >= NetState.Merge OrElse State = NetState.WaitForCheck Then Return Nothing
SyncLock LockState
@@ -1113,20 +778,12 @@ RequestFinished:
SyncLock LockChain
'获取线程起点与下载源
'不分割
- If IsNoSplit Then GoTo Capture
- '单线程
- If IsSourceFailed(False) Then
- '确认没有其他线程正使用此点
- If SourcesOnce(0).Thread IsNot Nothing AndAlso SourcesOnce(0).Thread.State <> NetState.Error Then Return Nothing
- '占用此点
-Capture:
- SmailFileCache = Nothing
- Threads = Nothing
- NetManager.DownloadDone -= DownloadDone
- SyncLock LockDone
- DownloadDone = 0
- End SyncLock
- SpeedLastDone = 0
+ If IsNoSplit Then GoTo StartSingleThreadDownload
+ '只剩下单线程可用点
+ If Not HasAvailableSource(False) Then
+ If SourcesOnce.First.SingleThread IsNot Nothing AndAlso SourcesOnce.First.SingleThread.State <> NetState.Error Then Return Nothing
+StartSingleThreadDownload:
+ Reset()
State = NetState.Get
End If
'首个开始点
@@ -1164,7 +821,7 @@ StartThread:
'构建线程
Dim ThreadUuid As Integer = GetUuid()
If Not Tasks.Any() Then Return Nothing '由于中断,已没有可用任务
- Th = New Thread(AddressOf Thread) With {.Name = $"NetTask {Tasks(0).Uuid}/{Uuid} Download {ThreadUuid}#", .Priority = ThreadPriority.BelowNormal}
+ Th = New Thread(AddressOf Thread) With {.Name = $"下载 {Tasks(0).Uuid}/{Uuid}/{ThreadUuid}#", .Priority = ThreadPriority.BelowNormal}
ThreadInfo = New NetThread With {.Uuid = ThreadUuid, .DownloadStart = StartPosition, .Thread = Th, .Source = StartSource, .Task = Me, .State = NetState.WaitForDownload}
'链表处理
If ThreadInfo.IsFirstThread OrElse Threads Is Nothing Then
@@ -1184,7 +841,7 @@ StartThread:
NetTaskThreadCount += 1
End SyncLock
SyncLock LockSource
- If IsSourceFailed(False) Then SourcesOnce(0).Thread = ThreadInfo
+ If Not HasAvailableSource(False) Then SourcesOnce.First.SingleThread = ThreadInfo
End SyncLock
Th.Start(ThreadInfo)
Return ThreadInfo
@@ -1197,319 +854,297 @@ StartThread:
'''
''' 每个下载线程执行的代码。
'''
- Private Sub Thread(Info As NetThread)
- If ModeDebug OrElse Info.DownloadStart = 0 Then Log("[Download] " & LocalName & " " & Info.Uuid & "#:开始,起始点 " & Info.DownloadStart & "," & Info.Source.Url)
- Dim HttpRequest As HttpRequestMessage
+ Private Sub Thread(Th As NetThread)
+ If ModeDebug OrElse Th.DownloadStart = 0 Then Log($"[Download] {LocalName}:开始,起始点 {Th.DownloadStart},{Th.Source.Url}")
Dim ResultStream As Stream = Nothing
- Dim HttpResponse As HttpResponseMessage
'部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟
'延迟过长会导致某些特别慢的链接迟迟不被掐死
- Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 6000) * (1 + Info.Source.FailCount), 30000)
- Info.State = NetState.Connect
+ Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 6000) * (1 + Th.Source.FailCount), 30000)
+ Th.State = NetState.Connect
Try
Dim HttpDataCount As Integer = 0
- If SourcesOnce.Contains(Info.Source) AndAlso Not Info.Equals(Info.Source.Thread) Then GoTo SourceBreak
- HttpRequest = Request.GetRequestMessage(Info.Source.Url,
- RequireHeaderSign:=True)
- '可能出现结束减去起始等于或者小于 0 的情况,导致下载抛出 ArgumentOutOfRangeException
- If Not Info.IsFirstThread Then HttpRequest.Headers.Range = New RangeHeaderValue(Info.DownloadStart, Nothing)
- HttpResponse = Request.SendRequest(HttpRequest, False, Timeout:=Timeout, RequireReturnRespObj:=True)
+ If SourcesOnce.Contains(Th.Source) AndAlso Th <> Th.Source.SingleThread Then GoTo SourceBreak
+ Dim HttpRequest As New HttpRequestMessage(HttpMethod.Get, Th.Source.Url)
+ SecretHeadersSign(Th.Source.Url, HttpRequest, UseBrowserUserAgent)
+ If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New RangeHeaderValue(Th.DownloadStart, Nothing)
Dim ContentLength As Long = 0
- Using HttpResponse
- If State = NetState.Error Then GoTo SourceBreak '快速中断
- If ModeDebug AndAlso HttpResponse.RequestMessage.RequestUri.ToString <> Info.Source.Url Then
- Log($"[Download] {LocalName} {Info.Uuid}#:重定向至 {HttpResponse.RequestMessage.RequestUri.ToString}")
- 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.Content.Headers.ContentLength.GetValueOrDefault(-1)
-
- If ContentLength = -1 Then
- If FileSize > 1 Then
- If Info.DownloadStart = 0 Then
- Log($"[Download] {LocalName} {Info.Uuid}#:文件大小未知,但已从其他下载源获取,不作处理")
- Else
- Log($"[Download] {LocalName} {Info.Uuid}#:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持")
- GoTo NotSupportRange
- End If
- Else
- FileSize = -1 : IsUnknownSize = True
- Log($"[Download] {LocalName} {Info.Uuid}#:文件大小未知")
- End If
- ElseIf ContentLength < 0 Then
- Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。")
- ElseIf Info.IsFirstThread Then
- If Check IsNot Nothing Then
- If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then
- Throw New Exception($"文件大小不足,获取结果为 {ContentLength},要求至少为 {Check.MinSize}。")
- End If
- If ContentLength <> Check.ActualSize AndAlso Check.ActualSize > 0 Then
- Throw New Exception($"文件大小不一致,获取结果为 {ContentLength},要求必须为 {Check.ActualSize}。")
- End If
- End If
- FileSize = ContentLength : IsUnknownSize = False
- Log($"[Download] {LocalName} {Info.Uuid}#:文件大小 {ContentLength}({GetString(ContentLength)})")
- '若文件大小大于 50 M,进行剩余磁盘空间校验
- If ContentLength > 50 * 1024 * 1024 Then
- For Each Drive As DriveInfo In DriveInfo.GetDrives
- Dim DriveName As String = Drive.Name.First.ToString
- Dim RequiredSpace = If(PathTemp.StartsWithF(DriveName), ContentLength * 1.1, 0) +
- If(LocalPath.StartsWithF(DriveName), ContentLength + 5 * 1024 * 1024, 0)
- If Drive.TotalFreeSpace < RequiredSpace Then
- Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" &
- If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", ""))
- End If
- Next
- End If
- ElseIf FileSize < 0 Then
- Throw New Exception("非首线程运行时,尚未获取文件大小")
- ElseIf Info.DownloadStart > 0 AndAlso ContentLength = FileSize Then
-NotSupportRange:
- SyncLock LockSource
- If SourcesOnce.Contains(Info.Source) Then
- GoTo SourceBreak
- Else
- SourcesOnce.Add(Info.Source)
- End If
- End SyncLock
-
- Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Info.DownloadStart},预期 ContentLength 为 {FileSize - Info.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
- ElseIf Not FileSize - Info.DownloadStart = ContentLength Then
- Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Info.DownloadStart},预期 ContentLength 为 {FileSize - Info.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
- End If
- Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}")
- Info.State = NetState.Get
- SyncLock LockState
- If State < NetState.Get Then State = NetState.Get
- End SyncLock
- '创建缓存文件
- If IsNoSplit Then
- Info.Temp = Nothing
- SmailFileCache = New Queue(Of Byte)
- Else
- Info.Temp = $"{PathTemp}Download\{Uuid}_{Info.Uuid}_{RandomInteger(0, 999999)}.tmp"
- ResultStream = New FileStream(Info.Temp, FileMode.Create, FileAccess.Write, FileShare.Read)
- End If
- '开始下载
- Using HttpStream = HttpResponse.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
- If Setup.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000))
- Dim HttpData As Byte() = New Byte(16384) {}
- HttpDataCount = HttpStream.Read(HttpData, 0, 16384)
- While (IsUnknownSize OrElse Info.DownloadUndone > 0) AndAlso '判断是否下载完成
- HttpDataCount > 0 AndAlso Not IsProgramEnded AndAlso State < NetState.Merge AndAlso (Not Info.Source.IsFailed OrElse Info.Equals(Info.Source.Thread))
- '限速
- While NetTaskSpeedLimitHigh > 0 AndAlso NetTaskSpeedLimitLeft <= 0
- Threading.Thread.Sleep(16)
- End While
- Dim RealDataCount As Integer = If(IsUnknownSize, HttpDataCount, Math.Min(HttpDataCount, Info.DownloadUndone))
- SyncLock NetTaskSpeedLimitLeftLock
- If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
- End SyncLock
- Dim DeltaTime = GetTimeTick() - Info.LastReceiveTime
- If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值
- If RealDataCount > 0 Then
- '有数据
- If Info.DownloadDone = 0 Then
- '第一次接受到数据
- Info.State = NetState.Download
- SyncLock LockState
- If State < NetState.Download Then State = NetState.Download
- End SyncLock
- SyncLock LockCount
- ConnectCount += 1
- ConnectTime += GetTimeTick() - Info.InitTime
- End SyncLock
- End If
- SyncLock LockCount
- Info.Source.FailCount = 0
- For Each Task In Tasks
- Task.FailCount = 0
- Next
- End SyncLock
- NetManager.DownloadDone += RealDataCount
- SyncLock LockDone
- DownloadDone += RealDataCount
- End SyncLock
- Info.DownloadDone += RealDataCount
- If IsNoSplit Then
- If HttpData.Count = RealDataCount Then
- 'SmailFileCache.AddRange(HttpData)
- For Each B In HttpData
- SmailFileCache.Enqueue(B)
- Next
- Else
- 'SmailFileCache.AddRange(HttpData.ToList.GetRange(0, RealDataCount))
- For i = 0 To RealDataCount - 1
- SmailFileCache.Enqueue(HttpData(i))
- Next
- End If
+ Using CancelToken As New CancellationTokenSource(Timeout)
+ Static Client As New HttpClient(New HttpClientHandler With {
+ .AutomaticDecompression = DecompressionMethods.Deflate Or DecompressionMethods.GZip Or DecompressionMethods.None
+ })
+ Using Response As HttpResponseMessage = Client.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetAwaiter().GetResult()
+ If Not Response.IsSuccessStatusCode Then Throw New Exception($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Th.Source.Url}") '状态码检查
+ If State = NetState.Error Then GoTo SourceBreak '快速中断
+ If ModeDebug AndAlso Response.RequestMessage.RequestUri.ToString <> Th.Source.Url Then Log($"[Download] {LocalName}:重定向至 {Response.RequestMessage.RequestUri}")
+ '文件大小校验
+ ContentLength = Response.Content.Headers.ContentLength.GetValueOrDefault(-1)
+ If ContentLength = -1 Then
+ If FileSize > 1 Then
+ If Th.DownloadStart = 0 Then
+ Log($"[Download] {LocalName}:文件大小未知,但已从其他下载源获取,不作处理")
Else
- ResultStream.Write(HttpData, 0, RealDataCount)
+ Log($"[Download] {LocalName}:ContentLength 返回了 -1,无法确定是否支持分段下载,视作不支持")
+ GoTo NotSupportRange
End If
- '检查速度是否过慢
- If DeltaTime > 1500 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1.5s,且速度小于 1.5K/s
- Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
- End If
- Info.LastReceiveTime = GetTimeTick()
- '已完成
- If Info.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While
- ElseIf Info.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then
- '无数据,且已超时
- Throw New TimeoutException("操作超时,无数据。")
+ Else
+ FileSize = -1 : IsUnknownSize = True
+ Log($"[Download] {LocalName}:文件大小未知")
End If
- HttpDataCount = HttpStream.Read(HttpData, 0, 16384)
- End While
+ ElseIf ContentLength < 0 Then
+ Throw New Exception("获取片大小失败,结果为 " & ContentLength & "。")
+ ElseIf Th.IsFirstThread Then
+ If Check IsNot Nothing Then
+ If ContentLength < Check.MinSize AndAlso Check.MinSize > 0 Then
+ Throw New Exception($"文件大小不足,获取结果为 {ContentLength},要求至少为 {Check.MinSize}。")
+ End If
+ If ContentLength <> Check.ActualSize AndAlso Check.ActualSize > 0 Then
+ Throw New Exception($"文件大小不一致,获取结果为 {ContentLength},要求必须为 {Check.ActualSize}。")
+ End If
+ End If
+ FileSize = ContentLength : IsUnknownSize = False
+ Log($"[Download] {LocalName}:文件大小 {ContentLength}({GetString(ContentLength)})")
+ '若文件大小大于 50 M,进行剩余磁盘空间校验
+ If ContentLength > 50 * 1024 * 1024 Then
+ For Each Drive As DriveInfo In DriveInfo.GetDrives
+ Dim DriveName As String = Drive.Name.First.ToString
+ Dim RequiredSpace = If(PathTemp.StartsWithF(DriveName), ContentLength * 1.1, 0) +
+ If(LocalPath.StartsWithF(DriveName), ContentLength + 5 * 1024 * 1024, 0)
+ If Drive.TotalFreeSpace < RequiredSpace Then
+ Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" &
+ If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", ""))
+ End If
+ Next
+ End If
+ ElseIf FileSize < 0 Then
+ Throw New Exception("非首线程运行时,尚未获取文件大小")
+ ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then
+NotSupportRange:
+ SyncLock LockSource
+ If SourcesOnce.Contains(Th.Source) Then
+ GoTo SourceBreak
+ Else
+ SourcesOnce.Add(Th.Source)
+ End If
+ End SyncLock
+ Throw New WebException($"该下载源不支持分段下载:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
+ ElseIf Not FileSize - Th.DownloadStart = ContentLength Then
+ Throw New WebException($"获取到的分段大小不一致:Range 起始于 {Th.DownloadStart},预期 ContentLength 为 {FileSize - Th.DownloadStart},返回 ContentLength 为 {ContentLength},总文件大小 {FileSize}")
+ End If
+ 'Log($"[Download] {LocalName} {Info.Uuid}#:通过大小检查,文件大小 {FileSize},起始点 {Info.DownloadStart},ContentLength {ContentLength}")
+ Th.State = NetState.Get
+ SyncLock LockState
+ If State < NetState.Get Then State = NetState.Get
+ End SyncLock
+ '创建缓存文件
+ If IsNoSplit Then
+ Th.Temp = Nothing
+ Cache = New MemoryStream
+ ResultStream = Cache
+ Else
+ Th.Temp = $"{PathTemp}Download\{Uuid}_{Th.Uuid}_{RandomInteger(0, 999999)}.tmp"
+ ResultStream = New FileStream(Th.Temp, FileMode.Create, FileAccess.Write, FileShare.Read)
+ End If
+ '开始下载
+ Using ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
+ If Setup.Get("SystemDebugDelay") Then Threading.Thread.Sleep(RandomInteger(50, 3000))
+ Dim ResponseBytes As Byte() = New Byte(16384) {}
+ HttpDataCount = ResponseStream.ReadAsync(ResponseBytes, 0, 16384, CancelToken.Token).GetAwaiter().GetResult()
+ While (IsUnknownSize OrElse Th.DownloadUndone > 0) AndAlso '判断是否下载完成
+ HttpDataCount > 0 AndAlso Not IsProgramEnded AndAlso State < NetState.Merge AndAlso (Not Th.Source.IsFailed OrElse Th.Source.SingleThread = Th)
+ '限速
+ While NetTaskSpeedLimitHigh > 0 AndAlso NetTaskSpeedLimitLeft <= 0
+ Threading.Thread.Sleep(16)
+ End While
+ Dim RealDataCount As Integer = If(IsUnknownSize, HttpDataCount, Math.Min(HttpDataCount, Th.DownloadUndone))
+ SyncLock NetTaskSpeedLimitLeftLock
+ If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft -= RealDataCount
+ End SyncLock
+ Dim DeltaTime = GetTimeTick() - Th.LastReceiveTime
+ If DeltaTime > 1000000 Then DeltaTime = 1 '时间刻反转导致出现极大值
+ If RealDataCount > 0 Then
+ '有数据
+ If Th.DownloadDone = 0 Then
+ '第一次接受到数据
+ Th.State = NetState.Download
+ SyncLock LockState
+ If State < NetState.Download Then State = NetState.Download
+ End SyncLock
+ SyncLock LockCount
+ ConnectCount += 1
+ ConnectTime += GetTimeTick() - Th.InitTime
+ End SyncLock
+ End If
+ SyncLock LockCount
+ Th.Source.FailCount = 0
+ For Each Task In Tasks
+ Task.FailCount = 0
+ Next
+ End SyncLock
+ NetManager.DownloadDone += RealDataCount
+ SyncLock LockDone
+ DownloadDone += RealDataCount
+ End SyncLock
+ Th.DownloadDone += RealDataCount
+ ResultStream.Write(ResponseBytes, 0, RealDataCount)
+ '检查速度是否过慢
+ If DeltaTime > 1500 AndAlso DeltaTime > RealDataCount Then '数据包间隔大于 1.5s,且速度小于 1.5K/s
+ Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
+ End If
+ Th.LastReceiveTime = GetTimeTick()
+ '已完成
+ If Th.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While
+ ElseIf Th.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then
+ '无数据,且已超时
+ Throw New TimeoutException("操作超时,无数据。")
+ End If
+ HttpDataCount = ResponseStream.Read(ResponseBytes, 0, 16384)
+ End While
+ End Using
End Using
End Using
SourceBreak:
- If State = NetState.Error OrElse Info.Source.IsFailed OrElse (Info.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then
+ If State = NetState.Error OrElse (Th.Source.IsFailed AndAlso Th.Source.SingleThread <> Th) OrElse (Th.DownloadUndone > 0 AndAlso Not IsUnknownSize) Then
'被外部中断
- Info.State = NetState.Error
- Log($"[Download] {LocalName} {Info.Uuid}#:中断")
- ElseIf HttpDataCount = 0 AndAlso Info.DownloadUndone > 0 AndAlso Not IsUnknownSize Then
+ Th.State = NetState.Error
+ Log($"[Download] {LocalName}:中断")
+ ElseIf HttpDataCount = 0 AndAlso Th.DownloadUndone > 0 AndAlso Not IsUnknownSize Then
'服务器无返回数据
- Throw New Exception($"返回的 ContentLength 过多:ContentLength 为 {ContentLength},但获取到的总数据量仅为 {Info.DownloadDone}(全文件总数据量 {DownloadDone})")
+ Throw New Exception($"返回的 ContentLength 过多:ContentLength 为 {ContentLength},但获取到的总数据量仅为 {Th.DownloadDone}(全文件总数据量 {DownloadDone})")
Else
'本线程完成
- Info.State = NetState.Finish
- If ModeDebug Then Log($"[Download] {LocalName} {Info.Uuid}#:完成,已下载 {Info.DownloadDone}")
+ Th.State = NetState.Finish
+ If ModeDebug Then Log($"[Download] {LocalName}:完成,已下载 {Th.DownloadDone}({Th.DownloadStart}~{Th.DownloadEnd})")
End If
Catch ex As Exception
- '状态变更
- SyncLock LockCount
- Info.Source.FailCount += 1
- For Each Task In Tasks
- Task.FailCount += 1
- Next
- End SyncLock
- Dim IsTimeoutString As String = GetExceptionSummary(ex).ToLower.Replace(" ", "")
- Dim IsTimeout As Boolean = IsTimeoutString.Contains("由于连接方在一段时间后没有正确答复或连接的主机没有反应") OrElse
- IsTimeoutString.Contains("超时") OrElse IsTimeoutString.Contains("timeout") OrElse IsTimeoutString.Contains("timedout")
- Log("[Download] " & LocalName & " " & Info.Uuid & If(IsTimeout, "#:超时(" & (Timeout * 0.001) & "s)", "#:出错," & GetExceptionDetail(ex)))
- Info.State = NetState.Error
- ''使用该下载源的线程是否没有速度
- ''下载超时也会导致没有速度,容易误判下载失败,所以已弃用此方法
- 'Dim IsNoSpeed As Boolean = True
- 'SyncLock LockChain
- ' If Threads IsNot Nothing Then
- ' For Each Thread As NetThread In Threads
- ' If Thread.Source.Id = Info.Source.Id AndAlso Thread.Speed > 0 Then
- ' IsNoSpeed = False
- ' Exit For
- ' End If
- ' Next
- ' End If
- 'End SyncLock
- Info.Source.Ex = ex
- '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传)
- If ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse
- ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse
- (ex.Message.Contains("(403)") AndAlso Not Info.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403,所以不应因此禁用下载源
- (Info.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse
- Info.Source.FailCount > NetTaskThreadLimit + 2 Then
- Dim IsThisFail As Boolean = False
- SyncLock LockSource
- If Info.Source.Thread IsNot Nothing AndAlso Info.Source.Thread.Equals(Info) Then
- '单线程下,本线程出错
- SourcesOnce.RemoveAt(0)
- GoTo Wrong
- ElseIf Not Info.Source.IsFailed Then
- '多线程下,本线程出错
-Wrong:
- Info.Source.IsFailed = True
- IsThisFail = True
- End If
- End SyncLock
- '本线程引发下载源被禁用
- If IsThisFail Then
- Log($"[Download] {LocalName} {Info.Uuid}#:下载源被禁用({Info.Source.Id}):{Info.Source.Url}")
- Log(ex, "下载源 " & Info.Source.Id & " 已被禁用", If(ex.Message.Contains("不支持分段下载") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(416)"), LogLevel.Developer, LogLevel.Debug))
- If IsSourceFailed() Then
- '没有可用源
- Log("[Download] 文件 " & LocalName & " 已无可用下载源")
- Dim ExampleEx As Exception = Nothing
- SyncLock LockSource
- For Each Source As NetSource In Sources
- Log("[Download] 已禁用的下载源:" & Source.Url)
- If Source.Ex IsNot Nothing Then
- ExampleEx = Source.Ex
- Log(Source.Ex, "下载源禁用原因", LogLevel.Developer)
- End If
- Next
- End SyncLock
- Fail(ExampleEx)
- ElseIf ex.Message.Contains("空间不足") Then
- '没有空间
- Fail(ex)
- End If
- End If
- End If
- '首线程错误
- If FileSize = -2 Then
- SyncLock LockChain
- Threads = Nothing
- End SyncLock
- End If
+ Log($"[Download] {LocalName}:出错,{If(TypeOf ex Is TaskCanceledException, $"已超时({Timeout}ms)", GetExceptionDetail(ex))}")
+ SourceFail(Th, ex, False)
Finally
- If ResultStream IsNot Nothing Then ResultStream.Dispose()
SyncLock NetTaskThreadCountLock
NetTaskThreadCount -= 1
End SyncLock
- '可能在没有下载完的时候开始合并文件了,这造成了大多数合并失败
- If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso State < NetState.Merge Then Merge()
+ '合并
+ If Not IsNoSplit Then ResultStream?.Dispose()
+ If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso State < NetState.Merge Then Merge(Th)
End Try
End Sub
- '''
- ''' 从 HTTP 响应头中获取文件名。
- ''' 如果没有,返回 Nothing。
- '''
- Private Function GetFileNameFromResponse(response As HttpWebResponse) As String
- Dim header As String = response.Headers("Content-Disposition")
- If String.IsNullOrEmpty(header) Then Return Nothing
- 'attachment; filename="filename.ext"
- If Not header.Contains("filename=") Then Return Nothing
- Return header.AfterLast("filename=").Trim(""""c, " "c).BeforeFirst(";")
- End Function
+ Private Sub SourceFail(Th As NetThread, ex As Exception, IsMergeFailure As Boolean)
+ '状态变更
+ SyncLock LockCount
+ Th.Source.FailCount += 1
+ For Each Task In Tasks
+ Task.FailCount += 1
+ Next
+ End SyncLock
+ Th.State = NetState.Error
+ Th.Source.Ex = ex
+ '根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传)
+ If IsMergeFailure OrElse
+ ex.Message.Contains("该下载源不支持") OrElse ex.Message.Contains("未能解析") OrElse ex.Message.Contains("(404)") OrElse
+ ex.Message.Contains("(502)") OrElse ex.Message.Contains("无返回数据") OrElse ex.Message.Contains("空间不足") OrElse ex.Message.Contains("获取到的分段大小不一致") OrElse
+ (ex.Message.Contains("(403)") AndAlso Not Th.Source.Url.ContainsF("bmclapi")) OrElse 'BMCLAPI 的部分源在高频率请求下会返回 403,所以不应因此禁用下载源
+ (Th.Source.FailCount >= MathClamp(NetTaskThreadLimit, 5, 30) AndAlso DownloadDone < 1) OrElse Th.Source.FailCount > NetTaskThreadLimit + 2 Then
+ '当一个下载源有多个线程在下载时,只选择其中一个线程进行后续处理
+ Dim IsThisFail As Boolean = False
+ SyncLock LockSource
+ If Not Th.Source.IsFailed OrElse Th.Source.SingleThread = Th Then
+ IsThisFail = True
+ Th.Source.IsFailed = True
+ End If
+ End SyncLock
+ '……后续处理
+ If IsThisFail Then
+ Log($"[Download] {LocalName}:下载源被禁用({Th.Source.Id}):{Th.Source.Url}")
+ Log(ex, $"{If(SourcesOnce.FirstOrDefault?.SingleThread Is Nothing, "", "单线程")}下载源 {Th.Source.Id} 已被禁用",
+ If(ex.Message.Contains("不支持分段下载") OrElse ex.Message.Contains("(404)") OrElse ex.Message.Contains("(416)"), LogLevel.Developer, LogLevel.Debug))
+ SyncLock LockSource
+ SourcesOnce.Remove(Th.Source)
+ End SyncLock
+ If ex.Message.Contains("空间不足") Then
+ '硬盘空间不足:强制失败
+ Fail(ex)
+ ElseIf HasAvailableSource() AndAlso Not IsMergeFailure Then
+ '当前源失败,但还有下载源:正常地继续执行
+ ElseIf Not Retried AndAlso Sources.Count > 1 Then
+ '合并失败或首次下载失败,未重试:将所有下载源重新标记为不允许断点续传的下载源,逐个重新尝试下载
+ Log($"[Download] {LocalName}:文件下载失败,正在自动重试……", If(ModeDebug, LogLevel.Hint, LogLevel.Debug)) '在调试模式下给出红色提示
+ Retried = True
+ SyncLock LockSource
+ SourcesOnce.Clear()
+ For Each Source In Sources
+ SourcesOnce.Add(Source)
+ Source.IsFailed = True
+ Next
+ End SyncLock
+ Reset()
+ SyncLock LockState
+ State = NetState.WaitForDownload
+ End SyncLock
+ ElseIf HasAvailableSource() AndAlso IsMergeFailure Then
+ '合并失败且单个源失败:继续下一个源
+ Reset()
+ SyncLock LockState
+ State = NetState.WaitForDownload
+ End SyncLock
+ Else
+ '失败
+ Log($"[Download] {LocalName}:已无可用下载源,下载失败")
+ Dim ExampleEx As Exception = Nothing
+ SyncLock LockSource
+ For Each Source As NetSource In Sources
+ Log("[Download] 已禁用的下载源:" & Source.Url)
+ If Source.Ex IsNot Nothing Then
+ ExampleEx = Source.Ex
+ Log(Source.Ex, "下载源禁用原因", LogLevel.Developer)
+ End If
+ Next
+ End SyncLock
+ Fail(ExampleEx)
+ End If
+ End If
+ End If
+ '清理当前已下载的内容
+ If FileSize = -2 Then Reset()
+ End Sub
+ Private Sub Reset()
+ FileSize = -2
+ Cache?.Dispose() : Cache = Nothing
+ SyncLock LockChain
+ Threads = Nothing
+ End SyncLock
+ NetManager.DownloadDone -= DownloadDone
+ SyncLock LockDone
+ DownloadDone = 0
+ End SyncLock
+ SpeedLastDone = 0
+ End Sub
+
+ '最终收束事件
- '下载文件的最终收束事件
'''
''' 下载完成。合并文件。
'''
- Private Sub Merge()
+ Private Sub Merge(Th As NetThread)
'状态判断
SyncLock LockState
If State < NetState.Merge Then
State = NetState.Merge
Else
- Exit Sub
+ Return
End If
End SyncLock
Dim RetryCount As Integer = 0
- Dim MergeFile As Stream = Nothing, AddWriter As BinaryWriter = Nothing
Try
Retry:
SyncLock LockChain
'创建文件夹
If File.Exists(LocalPath) Then File.Delete(LocalPath)
- Dim Info As New FileInfo(LocalPath)
- Info.Directory.Create()
+ Directory.CreateDirectory(GetPathFromFullPath(LocalPath))
'合并文件
If IsNoSplit Then
'仅有一个线程,从缓存中输出
- If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从缓存输出文件,长度:" & SmailFileCache.Count)
- MergeFile = New FileStream(LocalPath, FileMode.Create)
- AddWriter = New BinaryWriter(MergeFile)
- AddWriter.Write(SmailFileCache.ToArray)
- AddWriter.Dispose() : AddWriter = Nothing
- MergeFile.Dispose() : MergeFile = Nothing
+ If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从内存输出文件")
+ Cache.Position = 0
+ WriteFile(LocalPath, Cache)
ElseIf Threads.DownloadDone = DownloadDone AndAlso Threads.Temp IsNot Nothing Then
'仅有一个文件,直接复制
If ModeDebug Then Log($"[Download] {LocalName}:下载结束,仅有一个文件,无需合并")
@@ -1517,40 +1152,37 @@ Retry:
Else
'有多个线程,合并
If ModeDebug Then Log($"[Download] {LocalName}:下载结束,开始合并文件")
- MergeFile = New FileStream(LocalPath, FileMode.Create)
- AddWriter = New BinaryWriter(MergeFile)
- For Each Thread As NetThread In Threads
- If Thread.DownloadDone = 0 OrElse Thread.Temp Is Nothing Then Continue For
- Using fs As New FileStream(Thread.Temp, FileMode.Open, FileAccess.Read, FileShare.Read)
- Using TempReader As New BinaryReader(fs)
- AddWriter.Write(TempReader.ReadBytes(Thread.DownloadDone))
- End Using
+ Using MergeFile As New FileStream(LocalPath, FileMode.Create)
+ Using AddWriter As New BinaryWriter(MergeFile)
+ For Each Thread As NetThread In Threads
+ If Thread.DownloadDone = 0 OrElse Thread.Temp Is Nothing Then Continue For
+ Using fs As New FileStream(Thread.Temp, FileMode.Open, FileAccess.Read, FileShare.Read)
+ Using TempReader As New BinaryReader(fs)
+ AddWriter.Write(TempReader.ReadBytes(Thread.DownloadDone))
+ End Using
+ End Using
+ Next
End Using
- Next
- AddWriter.Dispose() : AddWriter = Nothing
- MergeFile.Dispose() : MergeFile = Nothing
- End If
- '写入大小要求
- If Not IsUnknownSize AndAlso Check IsNot Nothing Then
- If Check.ActualSize = -1 Then
- Check.ActualSize = FileSize
- ElseIf Check.ActualSize <> FileSize Then
- Throw New Exception($"文件大小不一致:任务要求为 {Check.ActualSize} B,网络获取结果为 {FileSize}B")
- End If
+ End Using
End If
'检查文件
Dim CheckResult As String = Check?.Check(LocalPath)
+ If CheckResult Is Nothing AndAlso Not IsUnknownSize AndAlso Check IsNot Nothing Then
+ If Check.ActualSize = -1 Then
+ CheckResult = (New FileChecker With {.ActualSize = FileSize}).Check(LocalPath) '不修改原始的 Checker,以免污染原始实例
+ ElseIf Check.ActualSize <> FileSize Then
+ CheckResult = $"文件大小不一致:任务校验要求为 {Check.ActualSize},请求结果为 {FileSize}"
+ End If
+ End If
If CheckResult IsNot Nothing Then
Log($"[Download] {LocalName} 文件校验失败,下载线程细节:")
- For Each Th As NetThread In Threads
- Log($"[Download] {Th.Uuid}#,状态 {GetStringFromEnum(Th.State)},范围 {Th.DownloadStart}~{Th.DownloadStart + Th.DownloadDone},完成 {Th.DownloadDone},剩余 {Th.DownloadUndone}")
+ For Each T As NetThread In Threads
+ Log($"[Download] - {T.Uuid}#,状态 {GetStringFromEnum(T.State)},范围 {T.DownloadStart}~{T.DownloadEnd},已下载 {T.DownloadDone},未下载 {T.DownloadUndone}")
Next
Throw New Exception(CheckResult)
End If
'后处理
- If IsNoSplit Then
- SmailFileCache = Nothing
- Else
+ If Not IsNoSplit Then
For Each Thread As NetThread In Threads
If Thread.Temp IsNot Nothing Then File.Delete(Thread.Temp)
Next
@@ -1558,20 +1190,18 @@ Retry:
Finish()
End SyncLock
Catch ex As Exception
- Log(ex, "合并文件出错(" & LocalName & ")")
- If MergeFile IsNot Nothing Then
- MergeFile.Dispose() : MergeFile = Nothing
- End If
- If AddWriter IsNot Nothing Then
- AddWriter.Dispose() : AddWriter = Nothing
- End If
- '重试
- If RetryCount <= 3 Then
- Threading.Thread.Sleep(RandomInteger(500, 1000))
- RetryCount += 1
+ RetryCount += 1
+ Log(ex, $"合并文件出错,第 {RetryCount} 次尝试({LocalName})")
+ '重新尝试合并
+ If RetryCount < 3 Then
+ Threading.Thread.Sleep(500 * RetryCount)
GoTo Retry
End If
- Fail(ex)
+ '失败,禁用当前下载源并重启下载
+ If File.Exists(LocalPath) Then File.Delete(LocalPath)
+ SourceFail(Th, ex, True)
+ Finally
+ Cache?.Dispose()
End Try
End Sub
'''
@@ -1579,12 +1209,12 @@ Retry:
'''
Private Sub Fail(Optional RaiseEx As Exception = Nothing)
SyncLock LockState
- If State >= NetState.Finish Then Exit Sub
+ If State >= NetState.Finish Then Return
If RaiseEx IsNot Nothing Then Ex.Add(RaiseEx)
'凉凉
State = NetState.Error
End SyncLock
- InterruptAndDelete()
+ AbortInternal()
For Each Task In Tasks
Task.OnFileFail(Me)
Next
@@ -1592,24 +1222,24 @@ Retry:
'''
''' 下载中断。
'''
- Public Sub Abort(CausedByTask As LoaderDownload)
+ Public Sub Abort(AbortedTask As LoaderDownload)
'从特定任务中移除,如果它还属于其他任务,则继续下载
- Tasks.Remove(CausedByTask)
- If Tasks.Any Then Exit Sub
+ Tasks.Remove(AbortedTask)
+ If Tasks.Any Then Return
'确认中断
SyncLock LockState
- If State >= NetState.Finish Then Exit Sub
+ If State >= NetState.Finish Then Return
State = NetState.Error
End SyncLock
- InterruptAndDelete()
+ AbortInternal()
End Sub
- Private Sub InterruptAndDelete()
+ Private Sub AbortInternal()
On Error Resume Next
- If File.Exists(LocalPath) Then File.Delete(LocalPath)
+ Reset()
SyncLock NetManager.LockRemain
NetManager.FileRemain -= 1
- Log($"[Download] {LocalName}:状态 {State},剩余文件 {NetManager.FileRemain}")
End SyncLock
+ Log($"[Download] {LocalName}:已终止,当前状态 {State}")
End Sub
'状态改变接口
@@ -1618,13 +1248,13 @@ Retry:
'''
Public Sub Finish(Optional PrintLog As Boolean = True)
SyncLock LockState
- If State >= NetState.Finish Then Exit Sub
+ If State >= NetState.Finish Then Return
State = NetState.Finish
End SyncLock
SyncLock NetManager.LockRemain
NetManager.FileRemain -= 1
- If PrintLog Then Log("[Download] " & LocalName & ":已完成,剩余文件 " & NetManager.FileRemain)
End SyncLock
+ If PrintLog Then Log($"[Download] {LocalName}:已完成")
For Each Task In Tasks
Task.OnFileFinish(Me)
Next
@@ -1745,7 +1375,7 @@ NextElement:
'输入检测
If Not Files.Any() Then
OnFinish()
- Exit Sub
+ Return
End If
For Each File As NetFile In Files
If File Is Nothing Then Throw New ArgumentException("存在空文件请求!")
@@ -1755,7 +1385,7 @@ NextElement:
Source.IsFailed = True
End If
Next
- If File.IsSourceFailed() Then Throw New ArgumentException("输入的下载链接不正确!")
+ If Not File.HasAvailableSource() Then Throw New ArgumentException("输入的下载链接不正确!")
If Not File.LocalPath.ToLower.Contains(":\") Then Throw New ArgumentException("输入的本地文件地址不正确!")
If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径!")
'文件夹检测
@@ -1785,30 +1415,31 @@ NextElement:
If FilesInThread.Count = FilesPerThread Then
Dim FilesToRun As New List(Of NetFile)
FilesToRun.AddRange(FilesInThread)
- RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid)
+ RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "下载 文件复制 " & Uuid)
FilesInThread.Clear()
End If
Next
If FilesInThread.Any Then
Dim FilesToRun As New List(Of NetFile)
FilesToRun.AddRange(FilesInThread)
- RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "NetTask FileCopy " & Uuid)
+ RunInNewThread(Sub() StartCopy(FilesToRun, FoldersFinal), "下载 文件复制 " & Uuid)
FilesInThread.Clear()
End If
Catch ex As Exception
OnFail(New List(Of Exception) From {ex})
End Try
- End Sub, "NetTask " & Uuid & " Main")
+ End Sub, "L/下载 " & Uuid)
End Sub
Private Sub StartCopy(Files As List(Of NetFile), FolderList As List(Of String))
Try
- If ModeDebug Then Log("[Download] 检查线程分配文件数:" & Files.Count & ",线程名:" & Thread.CurrentThread.Name)
+ If ModeDebug Then Log($"[Download] 检查线程分配文件数:{Files.Count},线程名:{Thread.CurrentThread.Name}")
'试图从已存在的 Minecraft 文件夹中寻找目标文件
Dim ExistFiles As New List(Of KeyValuePair(Of NetFile, String)) '{NetFile, Target As String}
For Each File As NetFile In Files
Dim ExistFilePath As String = Nothing
'判断是否有已存在的文件
- If File.Check IsNot Nothing AndAlso McFolderList IsNot Nothing AndAlso PathMcFolder IsNot Nothing AndAlso File.Check.CanUseExistsFile AndAlso File.LocalPath.StartsWithF(PathMcFolder) Then
+ If File.Check IsNot Nothing AndAlso McFolderList IsNot Nothing AndAlso PathMcFolder IsNot Nothing AndAlso
+ File.Check.CanUseExistsFile AndAlso File.LocalPath.StartsWithF(PathMcFolder) Then
Dim Relative = File.LocalPath.Replace(PathMcFolder, "")
For Each Folder In FolderList
Dim Target = Folder & Relative
@@ -1834,7 +1465,7 @@ NextElement:
For Each FileToken In ExistFiles
Dim File As NetFile = FileToken.Key
SyncLock LockState
- If File.State > NetState.WaitForCopy Then Exit Sub
+ If File.State > NetState.WaitForCopy Then Return
End SyncLock
Dim LocalPath As String = FileToken.Value
Dim RetryCount As Integer = 0
@@ -1845,7 +1476,7 @@ Retry:
File.Finish(False)
Catch ex As Exception
RetryCount += 1
- Log(ex, String.Format("复制已存在的文件失败,重试第 {2} 次({0} -> {1})", LocalPath, File.LocalPath, RetryCount))
+ Log(ex, $"复制已存在的文件失败,重试第 {RetryCount} 次({LocalPath} -> {File.LocalPath})")
If RetryCount < 3 Then
Thread.Sleep(200)
GoTo Retry
@@ -1863,14 +1494,14 @@ Retry:
'要求全部文件完成
SyncLock FileRemainLock
FileRemain -= 1
- If FileRemain > 0 Then Exit Sub
+ If FileRemain > 0 Then Return
End SyncLock
OnFinish()
End Sub
Public Sub OnFinish()
RaisePreviewFinish()
SyncLock LockState
- If State > LoadState.Loading Then Exit Sub
+ If State > LoadState.Loading Then Return
State = LoadState.Finished
End SyncLock
End Sub
@@ -1883,25 +1514,26 @@ Retry:
End Sub
Public Sub OnFail(ExList As List(Of Exception))
SyncLock LockState
- If State > LoadState.Loading Then Exit Sub
+ If State > LoadState.Loading Then Return
If ExList Is Nothing OrElse Not ExList.Any() Then ExList = New List(Of Exception) From {New Exception("未知错误!")}
- '寻找第一个不是 404 的下载源
- Dim UsefulExs = ExList.Where(Function(e) Not e.Message.Contains("(404)")).ToList
- [Error] = If(UsefulExs.Any, UsefulExs(0), ExList(0))
+ '寻找有效的错误信息
+ Dim UsefulExs = ExList.Where(Function(e) TypeOf e IsNot TaskCanceledException AndAlso TypeOf e IsNot ThreadInterruptedException).ToList
+ [Error] = If(UsefulExs.FirstOrDefault, ExList.FirstOrDefault)
'获取实际失败的文件
For Each File In Files
- If File.State = NetState.Error Then
- [Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf & Join(
- File.Sources.Select(Function(s) If(s.Ex Is Nothing, s.Url, s.Ex.Message & "(" & s.Url & ")")), vbCrLf), [Error])
- Exit For
- End If
+ If File.State <> NetState.Error Then Continue For
+ [Error] = New Exception("文件下载失败:" & File.LocalPath & vbCrLf &
+ "各下载源的错误如下:" & vbCrLf &
+ Join(File.Sources.Select(Function(s) $"{If(s.Ex?.Message, "无错误信息。")}({s.Url})"), vbCrLf),
+ [Error])
+ Exit For
Next
'在设置 Error 对象后再更改为失败,避免 WaitForExit 无法捕获错误
State = LoadState.Failed
End SyncLock
'中断所有文件
For Each TaskFile In Files
- If TaskFile.State < NetState.Merge Then TaskFile.State = NetState.Error
+ TaskFile.Abort(Me)
Next
'在退出同步锁后再进行日志输出
Dim ErrOutput As New List(Of String)
@@ -1912,7 +1544,7 @@ Retry:
End Sub
Public Overrides Sub Abort()
SyncLock LockState
- If State >= LoadState.Finished Then Exit Sub
+ If State >= LoadState.Finished Then Return
State = LoadState.Aborted
End SyncLock
Log("[Download] " & Name & " 已取消!")
@@ -2029,7 +1661,7 @@ Retry:
''' 启动监控线程,用于新增下载线程。
'''
Private Sub StartManager()
- If IsManagerStarted Then Exit Sub
+ If IsManagerStarted Then Return
IsManagerStarted = True
Dim ThreadStarter =
Sub(Id As Integer) '0 或 1
@@ -2141,9 +1773,8 @@ Retry:
Files(File.LocalPath) = File
SyncLock LockRemain
FileRemain += 1
- If ModeDebug Then Log("[Download] " & File.LocalName & ":已替换列表,剩余文件 " & FileRemain)
- 'FileRemainList.Add(File.LocalPath)
End SyncLock
+ If ModeDebug Then Log($"[Download] {File.LocalName}:重新加入下载列表")
Else
'该文件正在下载中
'将当前文件替换成下载中的文件,即两个任务指向同一个文件
@@ -2156,9 +1787,8 @@ Retry:
Files.Add(File.LocalPath, File)
SyncLock LockRemain
FileRemain += 1
- If ModeDebug Then Log("[Download] " & File.LocalName & ":已加入列表,剩余文件 " & FileRemain)
- 'FileRemainList.Add(File.LocalPath)
End SyncLock
+ If ModeDebug Then Log($"[Download] {File.LocalName}:已加入下载列表")
End If
Task.Files(i) = File '回设
Next
@@ -2181,4 +1811,26 @@ Retry:
Return False
End Function
+#End Region
+
+ '''
+ ''' 测试 Ping。失败则返回 -1。
+ '''
+ Public Function Ping(Ip As String, Optional Timeout As Integer = 10000, Optional MakeLog As Boolean = True) As Integer
+ Dim PingResult As NetworkInformation.PingReply
+ Try
+ PingResult = (New NetworkInformation.Ping).Send(Ip)
+ Catch ex As Exception
+ If MakeLog Then Log("[Net] Ping " & Ip & " 失败:" & ex.Message)
+ Return -1
+ End Try
+ If PingResult.Status = NetworkInformation.IPStatus.Success Then
+ If MakeLog Then Log("[Net] Ping " & Ip & " 结束:" & PingResult.RoundtripTime & "ms")
+ Return PingResult.RoundtripTime
+ Else
+ If MakeLog Then Log("[Net] Ping " & Ip & " 失败")
+ Return -1
+ End If
+ End Function
+
End Module
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
index 727013a..c62beb9 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModComp.vb
@@ -1076,7 +1076,7 @@ Retry:
Try
'获取工程列表
Log("[Comp] 开始从 CurseForge 获取工程列表:" & CurseForgeUrl)
- Dim RequestResult As JObject = DlModRequest(CurseForgeUrl, IsJson:=True)
+ Dim RequestResult As JObject = DlModRequest(CurseForgeUrl)
Task.Progress += 0.2
Dim ProjectList As New List(Of CompProject)
For Each JsonEntry As JObject In RequestResult("data")
@@ -1107,7 +1107,7 @@ Retry:
Sub()
Try
Log("[Comp] 开始从 Modrinth 获取工程列表:" & ModrinthUrl)
- Dim RequestResult As JObject = DlModRequest(ModrinthUrl, IsJson:=True)
+ Dim RequestResult As JObject = DlModRequest(ModrinthUrl)
Task.Progress += 0.2
Dim ProjectList As New List(Of CompProject)
For Each JsonEntry As JObject In RequestResult("hits")
@@ -1396,7 +1396,7 @@ Retry:
'DownloadAddress
Dim Url = Data("downloadUrl").ToString
If Url = "" Then Url = $"https://media.forgecdn.net/files/{CInt(Id.ToString.Substring(0, 4))}/{CInt(Id.ToString.Substring(4))}/{FileName}"
- Url = Url.Replace(FileName, Net.WebUtility.UrlEncode(FileName)) '对文件名进行编码
+ Url = Url.Replace(FileName, WebUtility.UrlEncode(FileName)) '对文件名进行编码
DownloadUrls = HandleCurseForgeDownloadUrls(Url) '对脑残 CurseForge 的下载地址进行多种修正
DownloadUrls.AddRange(DownloadUrls.Select(Function(u) DlSourceModGet(u)).ToList) '添加镜像源,这个写法是为了让镜像源排在后面
DownloadUrls = DownloadUrls.Distinct.ToList '最终去重
@@ -1492,9 +1492,8 @@ Retry:
Return {
Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."),
Url.Replace("://edge.", "://mediafilez.").Replace("://media.", "://mediafilez."),
- Url.Replace("-service.overwolf.wtf", ".forgecdn.net").Replace("://edge.", "://media."),
Url.Replace("-service.overwolf.wtf", ".forgecdn.net"),
- Url.Replace("://edge.", "://media."),
+ Url.Replace("://media.", "://edge."),
Url
}.Distinct.ToList
End Function
@@ -1591,27 +1590,40 @@ Retry:
If CompProjectCache.ContainsKey(ProjectId) Then '存在缓存
TargetProject = CompProjectCache(ProjectId)
ElseIf FromCurseForge Then 'CurseForge
- TargetProject = New CompProject(DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId, IsJson:=True)("data"))
+ TargetProject = New CompProject(DlModRequest("https://api.curseforge.com/v1/mods/" & ProjectId)("data"))
Else 'Modrinth
- TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId, IsJson:=True))
+ TargetProject = New CompProject(DlModRequest("https://api.modrinth.com/v2/project/" & ProjectId))
End If
'获取工程对象的文件列表
If Not CompFilesCache.ContainsKey(ProjectId) Then '有缓存也不能直接返回,这时候前置可能没获取(#5173)
Log("[Comp] 开始获取文件列表:" & ProjectId)
- Dim ResultJsonArray As JArray
+ Dim ResultJsonArray As JArray = Nothing
If FromCurseForge Then
'CurseForge
- 'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522)
- ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=10000", IsJson:=True)("data")
- '之前只请求一部分文件的方法备份如下:
- 'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件
- ' ResultJsonArray = GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json"))("data")
- 'Else '否则使用全部文件
- ' ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999", IsJson:=True)("data")
- 'End If
+ For RetryCount As Integer = 0 To 2
+ Dim ResultJson As JObject = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=" &
+ (10000 + RetryCount)) '每次重试多请求一个文件,以避免触发 CDN 缓存
+ 'HMCL 一次性请求了 10000 个文件,虽然不知道会不会出问题但先这样吧……(#5522)
+ '之前只请求一部分文件的方法备份如下:
+ 'If TargetProject.Type = CompType.Mod Then 'Mod 使用每个版本最新的文件
+ ' ResultJsonArray = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(TargetProject.CurseForgeFileIds, ",") & "]}", "application/json")("data")
+ 'Else '否则使用全部文件
+ ' ResultJsonArray = DlModRequest($"https://api.curseforge.com/v1/mods/{ProjectId}/files?pageSize=999")("data")
+ 'End If
+ If ResultJson("pagination")("resultCount").ToObject(Of Integer) = ResultJson("pagination")("totalCount").ToObject(Of Integer) Then
+ ResultJsonArray = ResultJson("data")
+ Exit For
+ ElseIf RetryCount < 2 Then
+ Log($"[Comp] CurseForge 返回的文件列表存在缺失,即将进行第 {RetryCount + 1} 次重试", LogLevel.Debug) '#6224
+ Log($"[Comp] 返回的原始内容如下:{vbCrLf}{ResultJson}")
+ Else
+ Log($"[Comp] CurseForge 返回的文件列表存在缺失,返回的原始内容如下:{vbCrLf}{ResultJson}")
+ Throw New Exception("CurseForge 返回的文件列表存在缺失")
+ End If
+ Next
Else
'Modrinth
- ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version", IsJson:=True)
+ ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version")
End If
CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).
Where(Function(a) a.Available).ToList.
@@ -1625,10 +1637,10 @@ Retry:
Log($"[Comp] {ProjectId} 文件列表中还需要获取信息的前置:{Join(UndoneDeps, ",")}")
Dim Projects As JArray
If TargetProject.FromCurseForge Then
- Projects = GetJson(DlModRequest("https://api.curseforge.com/v1/mods",
- "POST", "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json"))("data")
+ Projects = DlModRequest("https://api.curseforge.com/v1/mods",
+ HttpMethod.Post, "{""modIds"": [" & Join(UndoneDeps, ",") & "]}", "application/json")("data")
Else
- Projects = DlModRequest($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]", IsJson:=True)
+ Projects = DlModRequest($"https://api.modrinth.com/v2/projects?ids=[""{Join(UndoneDeps, """,""")}""]")
End If
For Each Project In Projects
Dim Unused As New CompProject(Project) '在 New 的时候会添加缓存以便之后读取
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
index 33fe597..2ed1744 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModCrash.vb
@@ -1018,9 +1018,9 @@ NextStack:
End If
Case CrashReason.特定实体导致崩溃
If Additional.Count = 1 Then
- Results.Add("游戏似乎因为实体 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并生成一个该实体,然后观察游戏的运行情况:\n - 若正常运行,则是该实体导致出错,你或许需要使用一些方式删除此实体。\n - 若仍然出错,问题就可能来自其他原因……\h")
+ Results.Add("游戏似乎因为实体 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并生成一个该实体,然后观察游戏的运行情况。\h")
Else
- Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n你可以创建一个新世界,并生成各种实体,观察游戏的运行情况:\n - 若正常运行,则是某些实体导致出错,你或许需要删除该世界。\n - 若仍然出错,问题就可能来自其他原因……\h")
+ Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n你可以创建一个新世界,并生成各种实体,然后观察游戏的运行情况。\h")
End If
Case CrashReason.OptiFine与Forge不兼容
Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。")
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
index ef7e020..683adee 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
@@ -198,7 +198,7 @@
Private IsNewClientVersionHinted As Boolean = False
Private Sub DlClientListMojangMain(Loader As LoaderTask(Of String, DlClientListResult))
Dim StartTime As Long = GetTimeTick()
- Dim Json As JObject = NetGetCodeByRequestRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", IsJson:=True)
+ Dim Json As JObject = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json"))
Try
Dim Versions As JArray = Json("versions")
If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
@@ -235,7 +235,7 @@
'''
Public DlClientListBmclapiLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Bmclapi", AddressOf DlClientListBmclapiMain)
Private Sub DlClientListBmclapiMain(Loader As LoaderTask(Of String, DlClientListResult))
- Dim Json As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json", IsJson:=True)
+ Dim Json As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/mc/game/version_manifest.json"))
Try
Dim Versions As JArray = Json("versions")
If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
@@ -377,7 +377,7 @@
'''
Public DlOptiFineListOfficialLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Official", AddressOf DlOptiFineListOfficialMain)
Private Sub DlOptiFineListOfficialMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
- Dim Result As String = NetGetCodeByClient("https://optifine.net/downloads", Encoding.Default)
+ Dim Result As String = NetRequestByClientRetry("https://optifine.net/downloads", Encoding:=Encoding.Default)
If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
Try
'获取所有版本信息
@@ -413,7 +413,7 @@
'''
Public DlOptiFineListBmclapiLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Bmclapi", AddressOf DlOptiFineListBmclapiMain)
Private Sub DlOptiFineListBmclapiMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
- Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/optifine/versionList", IsJson:=True)
+ Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/optifine/versionList"))
Try
Dim Versions As New List(Of DlOptiFineListEntry)
For Each Token As JObject In Json
@@ -483,7 +483,7 @@
'''
Public DlForgeListOfficialLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Official", AddressOf DlForgeListOfficialMain)
Private Sub DlForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
- Dim Result As String = NetGetCodeByRequestRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding.Default, "text/html", UseBrowserUserAgent:=True)
+ Dim Result As String = NetRequestByClientRetry("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.2.4.html", Encoding:=Encoding.Default, Accept:="text/html", UseBrowserUserAgent:=True)
If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
'获取所有版本信息
Dim Names As List(Of String) = RegexSearch(Result, "(?<=a href=""index_)[0-9.]+(_pre[0-9]?)?(?=.html)")
@@ -497,7 +497,7 @@
'''
Public DlForgeListBmclapiLoader As New LoaderTask(Of Integer, DlForgeListResult)("DlForgeList Bmclapi", AddressOf DlForgeListBmclapiMain)
Private Sub DlForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlForgeListResult))
- Dim Result As String = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding.Default)
+ Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft", Encoding:=Encoding.Default)
If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & ")")
'获取所有版本信息
Dim Names As List(Of String) = RegexSearch(Result, "[0-9.]+(_pre[0-9]?)?")
@@ -629,7 +629,7 @@
Public Sub DlForgeVersionOfficialMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
Dim Result As String
Try
- Result = NetGetCodeByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" &
+ Result = NetRequestByLoader("https://files.minecraftforge.net/maven/net/minecraftforge/forge/index_" &
Loader.Input.Replace("-", "_") & '兼容 Forge 1.7.10-pre4,#4057
".html", UseBrowserUserAgent:=True)
Catch ex As Exception
@@ -700,9 +700,8 @@
''' Forge 版本列表,BMCLAPI。
'''
Public Sub DlForgeVersionBmclapiMain(Loader As LoaderTask(Of String, List(Of DlForgeVersionEntry)))
- Dim Json As JArray = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" &
- Loader.Input.Replace("-", "_"), '兼容 Forge 1.7.10-pre4,#4057
- IsJson:=True)
+ Dim Json As JArray = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/minecraft/" &
+ Loader.Input.Replace("-", "_"))) '兼容 Forge 1.7.10-pre4,#4057
Dim Versions As New List(Of DlForgeVersionEntry)
Try
Dim Recommended As String = McDownloadForgeRecommendedGet(Loader.Input)
@@ -836,8 +835,8 @@
Public DlNeoForgeListOfficialLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Official", AddressOf DlNeoForgeListOfficialMain)
Private Sub DlNeoForgeListOfficialMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
'获取版本列表 JSON
- Dim ResultLatest As String = NetGetCodeByLoader("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True)
- Dim ResultLegacy As String = NetGetCodeByLoader("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True)
+ Dim ResultLatest As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/neoforge")
+ Dim ResultLegacy As String = NetRequestByClientRetry("https://maven.neoforged.net/api/maven/versions/releases/net/neoforged/forge")
If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
'解析
Try
@@ -854,8 +853,8 @@
Public DlNeoForgeListBmclapiLoader As New LoaderTask(Of Integer, DlNeoForgeListResult)("DlNeoForgeList Bmclapi", AddressOf DlNeoForgeListBmclapiMain)
Public Sub DlNeoForgeListBmclapiMain(Loader As LoaderTask(Of Integer, DlNeoForgeListResult))
'获取版本列表 JSON
- Dim ResultLatest As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge", UseBrowserUserAgent:=True, IsJson:=True)
- Dim ResultLegacy As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge", UseBrowserUserAgent:=True, IsJson:=True)
+ Dim ResultLatest As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/neoforge")
+ Dim ResultLegacy As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/neoforge/meta/api/maven/details/releases/net/neoforged/forge")
If ResultLatest.Length < 100 OrElse ResultLegacy.Length < 100 Then Throw New Exception("获取到的版本列表长度不足(" & ResultLatest & ")")
'解析
Try
@@ -960,7 +959,7 @@
'''
Public DlLiteLoaderListOfficialLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Official", AddressOf DlLiteLoaderListOfficialMain)
Private Sub DlLiteLoaderListOfficialMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
- Dim Result As JObject = NetGetCodeByRequestRetry("https://dl.liteloader.com/versions/versions.json", IsJson:=True)
+ Dim Result As JObject = GetJson(NetRequestByClientRetry("https://dl.liteloader.com/versions/versions.json"))
Try
Dim Json As JObject = Result("versions")
Dim Versions As New List(Of DlLiteLoaderListEntry)
@@ -988,7 +987,7 @@
'''
Public DlLiteLoaderListBmclapiLoader As New LoaderTask(Of Integer, DlLiteLoaderListResult)("DlLiteLoaderList Bmclapi", AddressOf DlLiteLoaderListBmclapiMain)
Private Sub DlLiteLoaderListBmclapiMain(Loader As LoaderTask(Of Integer, DlLiteLoaderListResult))
- Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json", IsJson:=True)
+ Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json"))
Try
Dim Json As JObject = Result("versions")
Dim Versions As New List(Of DlLiteLoaderListEntry)
@@ -1059,7 +1058,7 @@
'''
Public DlFabricListOfficialLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Official", AddressOf DlFabricListOfficialMain)
Private Sub DlFabricListOfficialMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
- Dim Result As JObject = NetGetCodeByRequestRetry("https://meta.fabricmc.net/v2/versions", IsJson:=True)
+ Dim Result As JObject = GetJson(NetRequestByClientRetry("https://meta.fabricmc.net/v2/versions"))
Try
Dim Output = New DlFabricListResult With {.IsOfficial = True, .SourceName = "Fabric 官方源", .Value = Result}
If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
@@ -1074,7 +1073,7 @@
'''
Public DlFabricListBmclapiLoader As New LoaderTask(Of Integer, DlFabricListResult)("DlFabricList Bmclapi", AddressOf DlFabricListBmclapiMain)
Private Sub DlFabricListBmclapiMain(Loader As LoaderTask(Of Integer, DlFabricListResult))
- Dim Result As JObject = NetGetCodeByRequestRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions", IsJson:=True)
+ Dim Result As JObject = GetJson(NetRequestByClientRetry("https://bmclapi2.bangbang93.com/fabric-meta/v2/versions"))
Try
Dim Output = New DlFabricListResult With {.IsOfficial = False, .SourceName = "BMCLAPI", .Value = Result}
If Output.Value("game") Is Nothing OrElse Output.Value("loader") Is Nothing OrElse Output.Value("installer") Is Nothing Then Throw New Exception("获取到的列表缺乏必要项")
@@ -1101,50 +1100,14 @@
#Region "DlMod | Mod 镜像源请求"
'''
- ''' 对可能涉及 Mod 镜像源的请求进行处理,返回字符串或 JObject。
- ''' 调用 NetGetCodeByRequest,会进行重试。
- '''
- Public Function DlModRequest(Url As String, Optional IsJson As Boolean = False) As Object
- Dim Urls As New List(Of KeyValuePair(Of String, Integer))
- Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
- Urls.Add(New KeyValuePair(Of String, Integer)(Url, 20))
- 'Dim McimUrl As String = DlSourceModGet(Url)
- 'If McimUrl <> Url Then
- ' Select Case Setup.Get("ToolDownloadMod")
- ' Case 0
- ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5))
- ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
- ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15))
- ' Case 1
- ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
- ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 5))
- ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15))
- ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
- ' Case Else
- ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
- ' Urls.Add(New KeyValuePair(Of String, Integer)(Url, 15))
- ' Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
- ' End Select
- 'End If
- Dim Exs As String = ""
- For Each Source In Urls
- Try
- Return NetGetCodeByRequestOnce(Source.Key, Encode:=Encoding.UTF8, Timeout:=Source.Value * 1000, IsJson:=IsJson, UseBrowserUserAgent:=True)
- Catch ex As Exception
- Exs += ex.Message + vbCrLf
- End Try
- Next
- Throw New Exception(Exs)
- End Function
-
- '''
- ''' 对可能涉及 Mod 镜像源的请求进行处理。
+ ''' 对可能涉及 Mod 镜像源的请求进行处理,返回 JToken。
''' 调用 NetRequest,会进行重试。
'''
- Public Function DlModRequest(Url As String, Method As String, Data As String, ContentType As String) As String
+ Public Function DlModRequest(Url As String, Optional Method As HttpMethod = Nothing,
+ Optional Content As String = Nothing, Optional ContentType As String = Nothing) As JToken
Dim Urls As New List(Of KeyValuePair(Of String, Integer))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 5))
- Urls.Add(New KeyValuePair(Of String, Integer)(Url, 20))
+ Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
'Dim McimUrl As String = DlSourceModGet(Url)
'If McimUrl <> Url Then
' Select Case Setup.Get("ToolDownloadMod")
@@ -1166,7 +1129,7 @@
Dim Exs As String = ""
For Each Source In Urls
Try
- Return NetRequestOnce(Source.Key, Method, Data, ContentType, Timeout:=Source.Value * 1000)
+ Return GetJson(NetRequestByClient(Source.Key, Method, Content, ContentType, Timeout:=Source.Value * 1000, Encoding:=Encoding.UTF8))
Catch ex As Exception
Exs += ex.Message + vbCrLf
End Try
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
index bf45fec..5a55344 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModJava.vb
@@ -117,7 +117,7 @@
If Output = "" Then Throw New ApplicationException("尝试运行该 Java 失败")
If ModeDebug Then Log("[Java] Java 检查输出:" & PathFolder & "java.exe" & vbCrLf & Output)
If Output.Contains("/lib/ext exists") Then Throw New ApplicationException("无法运行该 Java,请在删除 Java 文件夹中的 /lib/ext 文件夹后再试")
- If Output.Contains("a fatal error") Then Throw New ApplicationException("由于操作系统或 JVM 存在问题,无法运行该 Java。")
+ If Output.Contains("a fatal error") Then Throw New ApplicationException("无法运行该 Java,该 Java 或系统存在问题")
'获取详细信息
Dim VersionString = If(RegexSeek(Output, "(?<=version "")[^""]+"), If(RegexSeek(Output, "(?<=openjdk )[0-9]+"), "")).Replace("_", ".").Split("-").First
If VersionString.Split(".").Count > 4 Then VersionString = VersionString.Replace(".0.", ".") '#3493,VersionString = "21.0.2.0.2"
@@ -460,10 +460,12 @@ NoUserJava:
''' 将 Java 按照适用性排序。
'''
Public Function JavaSorter(Left As JavaEntry, Right As JavaEntry) As Boolean
+ Dim PathInfo As New DirectoryInfo(ShortenPath(Path))
+ Dim PathMcInfo As New DirectoryInfo(ShortenPath(PathMcFolder))
'1. 尽量在当前文件夹或当前 Minecraft 文件夹
Dim ProgramPathParent As String, MinecraftPathParent As String = ""
- ProgramPathParent = If(New DirectoryInfo(Path).Parent, New DirectoryInfo(Path)).FullName
- If PathMcFolder <> "" Then MinecraftPathParent = If(New DirectoryInfo(PathMcFolder).Parent, New DirectoryInfo(PathMcFolder)).FullName
+ ProgramPathParent = If(PathInfo.Parent, PathInfo).FullName
+ If PathMcFolder <> "" Then MinecraftPathParent = If(PathMcInfo.Parent, PathMcInfo).FullName
If Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Not Right.PathFolder.StartsWithF(ProgramPathParent) Then Return True
If Not Left.PathFolder.StartsWithF(ProgramPathParent) AndAlso Right.PathFolder.StartsWithF(ProgramPathParent) Then Return False
If PathMcFolder <> "" Then
@@ -530,7 +532,7 @@ NoUserJava:
If Disk.DriveType = DriveType.Network Then Continue For '跳过网络驱动器(#3705)
JavaSearchFolder(Disk.Name, JavaPreList, False)
Next
- '查找 ~/.jdks 文件夹中的 Java
+ '查找 .jdks 文件夹中的 Java
JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) & "\.jdks\", JavaPreList, False)
'查找 APPDATA 文件夹中的 Java
JavaSearchFolder(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\", JavaPreList, False)
@@ -680,8 +682,8 @@ Wait:
'若该目录有 Java,则加入结果
If File.Exists(Path & "javaw.exe") Then Results(Path) = Source
'查找其下的所有文件夹
- '不应使用网易的 Java:https://github.com/HMeloong-Git/PCL/issues/1279#issuecomment-2761489121
- Dim Keywords = {"java", "jdk", "env", "环境", "run", "软件", "jre", "mc", "dragon",
+ '不应使用网易的 Java:https://github.com/Meloong-Git/PCL/issues/1279#issuecomment-2761489121
+ Dim Keywords = {"java", "jdk", "env", "环境", "run", "软件", "jre", "mc", "dragon", "bin",
"soft", "cache", "temp", "corretto", "roaming", "users", "craft", "program", "世界", "net",
"游戏", "oracle", "game", "file", "data", "jvm", "服务", "server", "客户", "client", "整合",
"应用", "运行", "前置", "mojang", "官启", "新建文件夹", "eclipse", "microsoft", "hotspot",
@@ -748,7 +750,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 = NetGetCodeByLoader(DlVersionListOrder(
+ Dim IndexFileStr As String = NetRequestByLoader(DlVersionListOrder(
{"https://piston-meta.mojang.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"},
{"https://bmclapi2.bangbang93.com/v1/products/java-runtime/2ec0cc96c44e5a76b9c8b7c39df7210883d12871/all.json"}
), IsJson:=True)
@@ -760,7 +762,7 @@ Wait:
Dim Address As String = TargetEntry.Value("manifest")("url")
Log($"[Java] 准备下载 Java {TargetEntry.Value("version")("name")}({TargetEntry.Key}):{Address}")
'获取文件列表
- Dim ListFileStr As String = NetGetCodeByLoader(DlSourceOrder({Address}, {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com")}), IsJson:=True)
+ Dim ListFileStr As String = NetRequestByLoader(DlSourceOrder({Address}, {Address.Replace("piston-meta.mojang.com", "bmclapi2.bangbang93.com")}), IsJson:=True)
LastJavaBaseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.minecraft\runtime\" & TargetEntry.Key & "\"
Dim Results As New List(Of NetFile)
For Each File As JProperty In CType(GetJson(ListFileStr), JObject)("files")
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
index 00469b8..2e1f9fd 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModLaunch.vb
@@ -684,11 +684,10 @@ LoginFinish:
'发送登录请求
Dim RequestData As New JObject(
New JProperty("accessToken", AccessToken), New JProperty("clientToken", ClientToken), New JProperty("requestUser", True))
- NetRequestRetry(
- Url:=Data.Input.BaseUrl & "/validate",
- Method:="POST",
- Data:=RequestData.ToString(0),
- Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}},
+ NetRequestByClientRetry(
+ Data.Input.BaseUrl & "/validate", HttpMethod.Post,
+ Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None),
+ Headers:={{"Accept-Language", "zh-CN"}},
ContentType:="application/json; charset=utf-8") '没有返回值的
'将登录结果输出
Data.Output.AccessToken = AccessToken
@@ -701,10 +700,9 @@ LoginFinish:
End Sub
Private Sub McLoginRequestRefresh(ByRef Data As LoaderTask(Of McLoginServer, McLoginResult), RequestUser As Boolean)
McLaunchLog("刷新登录开始(Refresh, " & Data.Input.Token & ")")
- Dim LoginJson As JObject = GetJson(NetRequestRetry(
- Url:=Data.Input.BaseUrl & "/refresh",
- Method:="POST",
- Data:=New JObject(
+ Dim LoginJson As JObject = GetJson(NetRequestByClientRetry(
+ Data.Input.BaseUrl & "/refresh", HttpMethod.Post,
+ Content:=New JObject(
New JProperty("selectedProfile", New JObject(
New JProperty("name", Setup.Get($"Cache{Data.Input.Token}Name")),
New JProperty("id", Setup.Get($"Cache{Data.Input.Token}Uuid"))
@@ -712,7 +710,7 @@ LoginFinish:
New JProperty("accessToken", Setup.Get($"Cache{Data.Input.Token}Access")),
New JProperty("requestUser", True)
).ToString(Newtonsoft.Json.Formatting.None),
- Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}},
+ Headers:={{"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") & " 无效!")
@@ -739,11 +737,10 @@ LoginFinish:
New JProperty("username", Data.Input.UserName),
New JProperty("password", Data.Input.Password),
New JProperty("requestUser", True))
- Dim LoginJson As JObject = GetJson(NetRequestRetry(
- Url:=Data.Input.BaseUrl & "/authenticate",
- Method:="POST",
- Data:=RequestData.ToString(0),
- Headers:=New Dictionary(Of String, String) From {{"Accept-Language", "zh-CN"}},
+ Dim LoginJson As JObject = GetJson(NetRequestByClientRetry(
+ Data.Input.BaseUrl & "/authenticate", HttpMethod.Post,
+ Content:=RequestData.ToString(Newtonsoft.Json.Formatting.None),
+ Headers:={{"Accept-Language", "zh-CN"}},
ContentType:="application/json; charset=utf-8"))
'检查登录结果
If LoginJson("availableProfiles").Count = 0 Then
@@ -858,8 +855,9 @@ LoginFinish:
'初始请求
Retry:
McLaunchLog("开始微软登录步骤 1/6(原始登录)")
- Dim PrepareJson As JObject = GetJson(NetRequestRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", "POST",
- $"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access", "application/x-www-form-urlencoded"))
+ Dim PrepareJson As JObject = GetJson(NetRequestByClientRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", HttpMethod.Post,
+ Content:=$"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access",
+ Accept:="application/x-www-form-urlencoded"))
McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString)
'弹窗
@@ -888,9 +886,11 @@ Retry:
Dim Result As String
Try
- Result = NetRequestMultiple("https://login.live.com/oauth20_token.srf", "POST",
- $"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access",
- "application/x-www-form-urlencoded", 2)
+ Result = NetRequestByClientMultiple("https://login.live.com/oauth20_token.srf", HttpMethod.Post,
+ Content:=$"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access",
+ ContentType:="application/x-www-form-urlencoded",
+ Headers:={{"Accept-Language", "en-US,en;q=0.5"}, {"X-Requested-With", "XMLHttpRequest"}},
+ ThreadCount:=2)
Catch ex As 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
@@ -918,8 +918,8 @@ Retry:
""RelyingParty"": ""http://auth.xboxlive.com"",
""TokenType"": ""JWT""
}"
- Dim Result As String = NetRequestMultiple("https://user.auth.xboxlive.com/user/authenticate", "POST", Request, "application/json", 3)
-
+ Dim Result As String = NetRequestByClientMultiple("https://user.auth.xboxlive.com/user/authenticate", HttpMethod.Post,
+ Content:=Request, ContentType:="application/json")
Dim ResultJson As JObject = GetJson(Result)
Dim XBLToken As String = ResultJson("Token").ToString
Return XBLToken
@@ -940,7 +940,8 @@ Retry:
}"
Dim Result As String
Try
- Result = NetRequestMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", "POST", Request, "application/json", 3)
+ Result = NetRequestByClientMultiple("https://xsts.auth.xboxlive.com/xsts/authorize", HttpMethod.Post,
+ Content:=Request, ContentType:="application/json")
Catch ex As Net.WebException
'参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js
If ex.Message.Contains("2148916227") Then
@@ -971,10 +972,15 @@ Retry:
End If
End Try
- Dim ResultJson As JObject = GetJson(Result)
- Dim XSTSToken As String = ResultJson("Token").ToString
- Dim UHS As String = ResultJson("DisplayClaims")("xui")(0)("uhs").ToString
- Return {XSTSToken, UHS}
+ Try '这个 Try 块仅为了追踪 #6683 而添加
+ Dim ResultJson As JObject = GetJson(Result)
+ Dim XSTSToken As String = ResultJson("Token").ToString
+ Dim UHS As String = ResultJson("DisplayClaims")("xui")(0)("uhs").ToString
+ Return {XSTSToken, UHS}
+ Catch
+ Log("[Launch] 返回内容:" & vbCrLf & Result)
+ Throw
+ End Try
End Function
'微软登录步骤 4:从 {XSTSToken, UHS} 获取 Minecraft AccessToken
Private Function MsLoginStep4(Tokens As String()) As String
@@ -983,7 +989,8 @@ Retry:
Dim Request As String = New JObject(New JProperty("identityToken", $"XBL3.0 x={Tokens(1)};{Tokens(0)}")).ToString(0)
Dim Result As String
Try
- Result = NetRequestRetry("https://api.minecraftservices.com/authentication/login_with_xbox", "POST", Request, "application/json")
+ Result = NetRequestByClientRetry("https://api.minecraftservices.com/authentication/login_with_xbox", HttpMethod.Post,
+ Content:=Request, ContentType:="application/json")
Catch ex As Net.WebException
Dim Message As String = GetExceptionSummary(ex)
If Message.Contains("(429)") Then
@@ -1005,7 +1012,8 @@ Retry:
Private Sub MsLoginStep5(AccessToken As String)
McLaunchLog("开始微软登录步骤 5/6")
- Dim Result As String = NetRequestMultiple("https://api.minecraftservices.com/entitlements/mcstore", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
+ Dim Result As String = NetRequestByClientMultiple("https://api.minecraftservices.com/entitlements/mcstore",
+ ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}})
Try
Dim ResultJson As JObject = GetJson(Result)
If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any) Then
@@ -1026,7 +1034,8 @@ Retry:
Dim Result As String
Try
- Result = NetRequestMultiple("https://api.minecraftservices.com/minecraft/profile", "GET", "", "application/json", 2, New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
+ Result = NetRequestByClientMultiple("https://api.minecraftservices.com/minecraft/profile",
+ ContentType:="application/json", ThreadCount:=2, Headers:={{"Authorization", "Bearer " & AccessToken}})
Catch ex As Net.WebException
Dim Message As String = GetExceptionSummary(ex)
If Message.Contains("(429)") Then
@@ -1099,7 +1108,7 @@ Retry:
If Len(Uuid) = 32 Then Return Uuid
'从官网获取
Try
- Dim GotJson As JObject = NetGetCodeByRequestRetry("https://api.mojang.com/users/profiles/minecraft/" & Name, IsJson:=True)
+ Dim GotJson As JObject = GetJson(NetRequestByClientRetry("https://api.mojang.com/users/profiles/minecraft/" & Name))
If GotJson Is Nothing Then Throw New FileNotFoundException("正版玩家档案不存在(" & Name & ")")
Uuid = If(GotJson("id"), "")
Catch ex As Exception
@@ -1205,6 +1214,12 @@ Retry:
End If
End If
+ 'LiteLoader 检测
+ If McVersionCurrent.Version.HasLiteLoader AndAlso McVersionCurrent.Version.IsStandardVersion Then '不管非标准版本
+ '最高 Java 8
+ MaxVer = If(New Version(1, 8, 999, 999) < MaxVer, New Version(1, 8, 999, 999), MaxVer)
+ End If
+
'统一通行证检测
If Setup.Get("LoginType") = McLoginType.Nide Then
'至少 Java 8u101
@@ -1356,16 +1371,6 @@ Retry:
If McLaunchJavaSelected.VersionCode >= 18 Then
If Not Arguments.Contains("-Dfile.encoding=") Then Arguments = "-Dfile.encoding=COMPAT " & Arguments
End If
- '替换参数
- Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader)
- If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
- '若自定义信息为空,则去掉该部分
- Arguments = Arguments.Replace(" --versionType ${version_type}", "")
- ReplaceArguments("${version_type}") = """"""
- End If
- For Each entry As KeyValuePair(Of String, String) In ReplaceArguments
- Arguments = Arguments.Replace(entry.Key, If(entry.Value.Contains(" ") OrElse entry.Value.Contains(":\"), """" & entry.Value & """", entry.Value))
- Next
'MJSB
Arguments = Arguments.Replace(" -Dos.name=Windows 10", " -Dos.name=""Windows 10""")
'全屏
@@ -1374,31 +1379,47 @@ Retry:
For Each Arg In CurrentLaunchOptions.ExtraArgs
Arguments += " " & Arg.Trim
Next
+ '自定义
+ Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent)
+ Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame)
+ '进行参数替换
+ Dim ReplaceArguments = McLaunchArgumentsReplace(McVersionCurrent, Loader)
+ If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
+ '若自定义信息为空,则去掉该部分
+ Arguments = Arguments.Replace(" --versionType ${version_type}", "")
+ ReplaceArguments("${version_type}") = """"""
+ End If
+ Dim FinalArguments As String = ""
+ For Each Argument In Arguments.Split(" ")
+ For Each Entry As KeyValuePair(Of String, String) In ReplaceArguments
+ Argument = Argument.Replace(Entry.Key, Entry.Value)
+ Next
+ If (Argument.Contains(" ") OrElse Argument.Contains(":\")) AndAlso Not Argument.EndsWithF("""") Then Argument = $"""{Argument}"""
+ FinalArguments += Argument & " "
+ Next
+ FinalArguments = FinalArguments.TrimEnd
'进服
Dim Server As String = If(String.IsNullOrEmpty(CurrentLaunchOptions.ServerIp), Setup.Get("VersionServerEnter", McVersionCurrent), CurrentLaunchOptions.ServerIp)
If Server.Length > 0 Then
If McVersionCurrent.ReleaseTime > New Date(2023, 4, 4) Then
'QuickPlay
- Arguments += $" --quickPlayMultiplayer ""{Server}"""
+ FinalArguments += $" --quickPlayMultiplayer ""{Server}"""
Else
'老版本
If Server.Contains(":") Then
'包含端口号
- Arguments += " --server " & Server.Split(":")(0) & " --port " & Server.Split(":")(1)
+ FinalArguments += $" --server {Server.Split(":")(0)} --port {Server.Split(":")(1)}"
Else
'不包含端口号
- Arguments += " --server " & Server & " --port 25565"
+ FinalArguments += $" --server {Server} --port 25565"
End If
If McVersionCurrent.Version.HasOptiFine Then Hint("OptiFine 与自动进入服务器可能不兼容,有概率导致材质丢失甚至游戏崩溃!", HintType.Critical)
End If
End If
- '自定义
- Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Version:=McVersionCurrent)
- Arguments += " " & If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame)
'输出
McLaunchLog("Minecraft 启动参数:")
- McLaunchLog(Arguments)
- McLaunchArgument = Arguments
+ McLaunchLog(FinalArguments)
+ McLaunchArgument = FinalArguments
End Sub
'Jvm 部分(第一段)
@@ -1424,12 +1445,12 @@ Retry:
End If
'Authlib-Injector
If McLoginLoader.Output.Type = "Auth" Then
- If (McLaunchJavaSelected.VersionCode >= 6) Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT")
+ If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#)
Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy,
"http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录
Setup.Get("VersionServerAuthServer", McVersionCurrent))
Try
- Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
+ Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8)
DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
" -Dauthlibinjector.side=client" &
" -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
@@ -1494,12 +1515,12 @@ NextVersion:
End If
'Authlib-Injector
If McLoginLoader.Output.Type = "Auth" Then
- If (McLaunchJavaSelected.VersionCode >= 6) Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT")
+ If McLaunchJavaSelected.VersionCode >= 6 Then DataList.Add("-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT") '信任系统根证书(5252#)
Dim Server As String = If(McLoginLoader.Input.Type = McLoginType.Legacy,
"http://hiperauth.tech/api/yggdrasil-hiper/", 'HiPer 登录
Setup.Get("VersionServerAuthServer", Version:=McVersionCurrent))
Try
- Dim Response As String = NetGetCodeByRequestRetry(Server, Encoding.UTF8)
+ Dim Response As String = NetRequestByClientRetry(Server, Encoding:=Encoding.UTF8)
DataList.Insert(0, "-javaagent:""" & PathPure & "authlib-injector.jar""=" & Server &
" -Dauthlibinjector.side=client" &
" -Dauthlibinjector.yggdrasil.prefetched=" & Convert.ToBase64String(Encoding.UTF8.GetBytes(Response)))
@@ -1533,10 +1554,8 @@ NextVersion:
DeDuplicateDataList.Add(CurrentEntry.Trim.Replace("McEmu= ", "McEmu="))
Next
- '#3511 的清理
- DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M")
-
'去重
+ DeDuplicateDataList.Remove("-XX:MaxDirectMemorySize=256M") '#3511 的清理
Dim Result As String = Join(DeDuplicateDataList.Distinct.ToList, " ")
'添加 MainClass
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
index 5ef12ee..422cf67 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMinecraft.vb
@@ -169,8 +169,8 @@ Public Module ModMinecraft
_McVersionCurrent = value '由于有可能是 Nothing,导致无法初始化,才得这样弄一圈
_McVersionLast = value
If value Is Nothing Then Return
- '重置缓存的 Mod 文件夹
- PageDownloadCompDetail.CachedFolder = Nothing
+ '重置缓存的下载文件夹
+ PageDownloadCompDetail.CachedFolder.Clear()
'统一通行证重判
If AniControlEnabled = 0 AndAlso
Setup.Get("VersionServerNide", Version:=value) <> Setup.Get("CacheNideServer") AndAlso
@@ -440,6 +440,33 @@ VersionSearchFinish:
'''
Public ReleaseTime As New Date(1970, 1, 1, 15, 0, 0)
+ '''
+ ''' 获取该版本的 JSON 路径。
+ '''
+ Public Function GetJsonPath() As String
+ Dim JsonPath As String = Path & Name & ".json"
+ If Not File.Exists(JsonPath) Then
+ '尝试寻找 JSON 文件
+ JsonPath = Nothing
+ For Each JsonCandidatePath In Directory.GetFiles(Path, "*.json")
+ Try
+ Dim JsonCandidate As JObject = GetJson(ReadFile(JsonCandidatePath))
+ If Not JsonCandidate.ContainsKey("mainClass") Then Continue For
+ If Not JsonCandidate.ContainsKey("type") Then Continue For
+ If Not JsonCandidate.ContainsKey("id") Then Continue For
+ Catch
+ End Try
+ JsonPath = JsonCandidatePath
+ Exit For
+ Next
+ If JsonPath Is Nothing Then
+ Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json")
+ Else
+ Log("[Minecraft] 未找到同名版本 JSON,自动换用 " & JsonPath, LogLevel.Debug)
+ End If
+ End If
+ Return JsonPath
+ End Function
'''
''' 该版本的 Json 文本。
'''
@@ -452,17 +479,7 @@ VersionSearchFinish:
Return TrimedJson.StartsWithF("{") AndAlso TrimedJson.EndsWithF("}")
End Function
If _JsonText Is Nothing Then
- Dim JsonPath As String = Path & Name & ".json"
- If Not File.Exists(JsonPath) Then
- '如果文件夹下只有一个 JSON 文件,则将其作为版本 JSON
- Dim JsonFiles As String() = Directory.GetFiles(Path, "*.json")
- If JsonFiles.Count = 1 Then
- JsonPath = JsonFiles(0)
- Log("[Minecraft] 未找到同名版本 JSON,自动换用 " & JsonPath, LogLevel.Debug)
- Else
- Throw New Exception($"未找到版本 JSON 文件:{Path}{Name}.json")
- End If
- End If
+ Dim JsonPath As String = GetJsonPath()
_JsonText = ReadFile(JsonPath)
'如果 ReadFile 失败会返回空字符串;这可能是由于文件被临时占用,故延时后重试
If Not FastJsonCheck(_JsonText) Then
@@ -540,9 +557,15 @@ Recheck:
'继续循环
If Inherit.InheritVersion = InheritVersion Then Throw New Exception("版本依赖项出现嵌套:" & InheritVersion)
InheritVersion = Inherit.InheritVersion
- '合并
+ '合并 Libraries 项:子版本放在前面,父版本放在后面(5978#:如果多个 jar 包中含有相同的类,Java 8 和之前的版本按照 -cp 指定的顺序选择第一个)
+ Dim CurrentLib As JArray = _JsonObject("libraries").DeepClone()
+ For Each LibToken In Inherit.JsonObject("libraries")
+ CurrentLib.Add(LibToken)
+ Next
+ '合并其他项:子版本优先于父版本
Inherit.JsonObject.Merge(_JsonObject)
_JsonObject = Inherit.JsonObject
+ _JsonObject("libraries") = CurrentLib
GoTo Recheck
End If
Catch ex As Exception
@@ -580,20 +603,18 @@ Recheck:
Get
If Not JsonVersionInited Then
JsonVersionInited = True
- If File.Exists(Path & Name & ".jar") Then
- Try
- Using JarArchive As New ZipArchive(New FileStream(Path & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
- Dim VersionJson As ZipArchiveEntry = JarArchive.GetEntry("version.json")
- If VersionJson IsNot Nothing Then
- Using VersionJsonStream As New StreamReader(VersionJson.Open)
- _JsonVersion = GetJson(VersionJsonStream.ReadToEnd)
- End Using
- End If
+ Try
+ If Not File.Exists($"{Path}{Name}.jar") Then Exit Try
+ Using JarArchive As New ZipArchive(New FileStream(Path & Name & ".jar", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+ Dim VersionJson As ZipArchiveEntry = JarArchive.GetEntry("version.json")
+ If VersionJson Is Nothing Then Exit Try
+ Using VersionJsonStream As New StreamReader(VersionJson.Open)
+ _JsonVersion = GetJson(VersionJsonStream.ReadToEnd)
End Using
- Catch ex As Exception
- Log(ex, "从版本 jar 中读取 version.json 失败")
- End Try
- End If
+ End Using
+ Catch ex As Exception
+ Log(ex, $"从版本 jar 中读取 version.json 失败({Path}{Name}.jar)")
+ End Try
End If
Return _JsonVersion
End Get
@@ -1538,7 +1559,7 @@ OnLoaded:
''' 要求玩家选择一个皮肤文件,并进行相关校验。
'''
Public Function McSkinSelect() As McSkinInfo
- Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.webp)|*.png;*.jpg;*.webp", "选择皮肤文件")
+ Dim FileName As String = SelectFile("皮肤文件(*.png;*.jpg;*.jpeg;*.webp)|*.png;*.jpg;*.jpeg;*.webp", "选择皮肤文件")
'验证有效性
If FileName = "" Then Return New McSkinInfo With {.IsVaild = False}
@@ -1589,7 +1610,7 @@ OnLoaded:
Case Else
Throw New ArgumentException("皮肤地址种类无效:" & If(Type, "null"))
End Select
- Dim SkinString = NetGetCodeByRequestRetry(Url & Uuid)
+ Dim SkinString = NetRequestByClientRetry(Url & Uuid)
If SkinString = "" Then Throw New Exception("皮肤返回值为空,可能是未设置自定义皮肤的用户")
'处理皮肤地址
Dim SkinValue As String
@@ -1611,7 +1632,7 @@ OnLoaded:
Throw New Exception("用户未设置自定义皮肤")
Else
Dim SkinUrl As String = SkinJson("textures")("skin")("url").ToString
- SkinValue = If(SkinUrl.Contains("minecraft.net"),SkinUrl.Replace("http:", "https:"),SkinUrl)
+ SkinValue = If(SkinUrl.Contains("minecraft.net/"), SkinUrl.Replace("http://", "https://"), SkinUrl)
End If
'保存缓存
WriteIni(PathTemp & "Cache\Skin\Index" & Type & ".ini", Uuid, SkinValue)
@@ -1628,9 +1649,9 @@ OnLoaded:
Dim FileAddress As String = PathTemp & "Cache\Skin\" & GetHash(Address) & ".png"
SyncLock McSkinDownloadLock
If Not File.Exists(FileAddress) Then
- NetDownloadByClient(Address, FileAddress & NetDownloadEnd)
+ NetDownloadByClient(Address, FileAddress & ".PCLDownloading")
File.Delete(FileAddress)
- FileSystem.Rename(FileAddress & NetDownloadEnd, FileAddress)
+ FileSystem.Rename(FileAddress & ".PCLDownloading", FileAddress)
Log("[Minecraft] 皮肤下载成功:" & FileAddress)
End If
Return FileAddress
@@ -1773,61 +1794,60 @@ OnLoaded:
'获取当前支持库列表
Log("[Minecraft] 获取支持库列表:" & Version.Name)
- McLibListGet = McLibListGetWithJson(Version.JsonObject)
- If Not IncludeVersionJar Then Return McLibListGet
+ Dim Result = McLibListGetWithJson(Version.JsonObject)
'需要添加原版 Jar
- Dim RealVersion As McVersion
- Dim RequiredJar As String = Version.JsonObject("jar")?.ToString
- If Version.IsHmclFormatJson OrElse RequiredJar Is Nothing Then
- 'HMCL 项直接使用自身的 Jar
- '根据 Inherit 获取最深层版本
- Dim OriginalVersion As McVersion = Version
- '1.17+ 的 Forge 不寻找 Inherit
- If Not ((Version.Version.HasForge OrElse Version.Version.HasNeoForge) AndAlso Version.Version.McCodeMain >= 17) Then
- Do Until OriginalVersion.InheritVersion = ""
- If OriginalVersion.InheritVersion = OriginalVersion.Name Then Exit Do
- OriginalVersion = New McVersion(PathMcFolder & "versions\" & OriginalVersion.InheritVersion & "\")
- Loop
+ If IncludeVersionJar Then
+ Dim RealVersion As McVersion
+ Dim RequiredJar As String = Version.JsonObject("jar")?.ToString
+ If Version.IsHmclFormatJson OrElse RequiredJar Is Nothing Then
+ 'HMCL 项直接使用自身的 Jar
+ '根据 Inherit 获取最深层版本
+ Dim OriginalVersion As McVersion = Version
+ '1.17+ 的 Forge 不寻找 Inherit
+ If Not ((Version.Version.HasForge OrElse Version.Version.HasNeoForge) AndAlso Version.Version.McCodeMain >= 17) Then
+ Do Until OriginalVersion.InheritVersion = ""
+ If OriginalVersion.InheritVersion = OriginalVersion.Name Then Exit Do
+ OriginalVersion = New McVersion(PathMcFolder & "versions\" & OriginalVersion.InheritVersion & "\")
+ Loop
+ End If
+ '需要新建对象,否则后面的 Check 会导致 McVersionCurrent 的 State 变回 Original
+ '复现:启动一个 Snapshot 版本
+ RealVersion = New McVersion(OriginalVersion.Path)
+ Else
+ 'Json 已提供 Jar 字段,使用该字段的信息
+ RealVersion = New McVersion(RequiredJar)
End If
- '需要新建对象,否则后面的 Check 会导致 McVersionCurrent 的 State 变回 Original
- '复现:启动一个 Snapshot 版本
- RealVersion = New McVersion(OriginalVersion.Path)
- Else
- 'Json 已提供 Jar 字段,使用该字段的信息
- RealVersion = New McVersion(RequiredJar)
+ Dim ClientUrl As String, ClientSHA1 As String
+ '判断需求的版本是否存在
+ '不能调用 RealVersion.Check(),可能会莫名其妙地触发 CheckPermission 正被另一进程使用,导致误判前置不存在
+ If Not File.Exists(RealVersion.Path & RealVersion.Name & ".json") Then
+ RealVersion = Version
+ Log("[Minecraft] 可能缺少前置版本 " & RealVersion.Name & ",找不到对应的 json 文件", LogLevel.Debug)
+ End If
+ '获取详细下载信息
+ If RealVersion.JsonObject("downloads") IsNot Nothing AndAlso RealVersion.JsonObject("downloads")("client") IsNot Nothing Then
+ ClientUrl = RealVersion.JsonObject("downloads")("client")("url")
+ ClientSHA1 = RealVersion.JsonObject("downloads")("client")("sha1")
+ Else
+ ClientUrl = Nothing
+ ClientSHA1 = Nothing
+ End If
+ '把所需的原版 Jar 添加进去
+ Result.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1})
End If
- Dim ClientUrl As String, ClientSHA1 As String
- '判断需求的版本是否存在
- '不能调用 RealVersion.Check(),可能会莫名其妙地触发 CheckPermission 正被另一进程使用,导致误判前置不存在
- If Not File.Exists(RealVersion.Path & RealVersion.Name & ".json") Then
- RealVersion = Version
- Log("[Minecraft] 可能缺少前置版本 " & RealVersion.Name & ",找不到对应的 json 文件", LogLevel.Debug)
- End If
- '获取详细下载信息
- If RealVersion.JsonObject("downloads") IsNot Nothing AndAlso RealVersion.JsonObject("downloads")("client") IsNot Nothing Then
- ClientUrl = RealVersion.JsonObject("downloads")("client")("url")
- ClientSHA1 = RealVersion.JsonObject("downloads")("client")("sha1")
- Else
- ClientUrl = Nothing
- ClientSHA1 = Nothing
- End If
- '把所需的原版 Jar 添加进去
- McLibListGet.Add(New McLibToken With {.LocalPath = RealVersion.Path & RealVersion.Name & ".jar", .Size = 0, .IsNatives = False, .Url = ClientUrl, .SHA1 = ClientSHA1})
+ Return Result
End Function
'''
''' 获取 Minecraft 某一版本忽视继承的支持库列表,即结果中没有继承项。
'''
Public Function McLibListGetWithJson(JsonObject As JObject, Optional KeepSameNameDifferentVersionResult As Boolean = False, Optional CustomMcFolder As String = Nothing) As List(Of McLibToken)
CustomMcFolder = If(CustomMcFolder, PathMcFolder)
- Dim BasicArray As New List(Of McLibToken)
-
- '添加基础 Json 项
- Dim AllLibs As JArray = JsonObject("libraries")
'转换为 LibToken
- For Each Library As JObject In AllLibs.Children
+ Dim BasicArray As New List(Of McLibToken)
+ For Each Library As JObject In CType(JsonObject("libraries"), JArray).Children
'清理 null 项(BakaXL 会把没有的项序列化为 null,但会被 Newtonsoft 转换为 JValue,导致 Is Nothing = false;这导致了 #409)
For i = Library.Properties.Count - 1 To 0 Step -1
@@ -1946,8 +1966,7 @@ OnLoaded:
Try
Log("[Minecraft] 开始获取统一通行证下载信息")
'测试链接:https://auth.mc-user.com:233/00000000000000000000000000000000/
- DownloadInfo = GetJson(NetGetCodeByLoader({
- "https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version)}, IsJson:=True))
+ DownloadInfo = GetJson(NetRequestByClientRetry("https://auth.mc-user.com:233/" & Setup.Get("VersionServerNide", Version:=Version)))
Catch ex As Exception
Log(ex, "获取统一通行证下载信息失败")
End Try
@@ -1969,10 +1988,9 @@ OnLoaded:
'获取下载信息
Try
Log("[Minecraft] 开始获取 Authlib-Injector 下载信息")
- DownloadInfo = GetJson(NetGetCodeByLoader({
+ DownloadInfo = GetJson(NetRequestByClientRetry(
"https://authlib-injector.yushi.moe/artifact/latest.json",
- "https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"
- }, IsJson:=True))
+ BackupUrl:="https://bmclapi2.bangbang93.com/mirrors/authlib-injector/artifact/latest.json"))
Catch ex As Exception
Log(ex, "获取 Authlib-Injector 下载信息失败")
End Try
@@ -2017,12 +2035,16 @@ OnLoaded:
'获取
For Each Token As McLibToken In Libs
'检查文件
- Dim Checker As New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1)
+ Dim Checker As FileChecker
+ If Token.Name.ContainsF("labymod") Then
+ Checker = New FileChecker '不检查 LabyMod 的文件,它们提供的文件校验信息是错的(#3225)
+ Else
+ Checker = New FileChecker(ActualSize:=If(Token.Size = 0, -1, Token.Size), Hash:=Token.SHA1)
+ End If
If Checker.Check(Token.LocalPath) Is Nothing Then Continue For
'文件不符合,添加下载
Dim Urls As New List(Of String)
- If Token.Url Is Nothing AndAlso Token.Name = "net.minecraftforge:forge:universal" Then
- '特判修复 Forge 部分 universal 文件缺失 URL(#5455)
+ If Token.Url Is Nothing AndAlso Token.Name = "net.minecraftforge:forge:universal" Then '特判修复 Forge 部分 universal 文件缺失 URL(#5455)
Token.Url = "https://maven.minecraftforge.net" & Token.LocalPath.Replace(CustomMcFolder & "libraries", "").Replace("\", "/")
End If
If Token.Url IsNot Nothing Then
@@ -2194,16 +2216,16 @@ OnLoaded:
Dim LocalPath As String
If Json("map_to_resources") IsNot Nothing AndAlso Json("map_to_resources").ToObject(Of Boolean) Then
'Remap
- LocalPath = Version.PathIndie & "resources\" & File.Name.Replace("/", "\")
+ LocalPath = $"{Version.PathIndie}resources\{File.Name}"
ElseIf Json("virtual") IsNot Nothing AndAlso Json("virtual").ToObject(Of Boolean) Then
'Virtual
- LocalPath = PathMcFolder & "assets\virtual\legacy\" & File.Name.Replace("/", "\")
+ LocalPath = $"{PathMcFolder}assets\virtual\legacy\{File.Name}"
Else
'正常
- LocalPath = PathMcFolder & "assets\objects\" & Left(File.Value("hash").ToString, 2) & "\" & File.Value("hash").ToString
+ LocalPath = $"{PathMcFolder}assets\objects\{Left(File.Value("hash").ToString, 2)}\{File.Value("hash")}"
End If
Result.Add(New McAssetsToken With {
- .LocalPath = LocalPath,
+ .LocalPath = LocalPath.Replace("/", "\"),
.SourcePath = File.Name,
.Hash = File.Value("hash").ToString,
.Size = File.Value("size").ToString
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
index bb9b3d5..e6225e4 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModMod.vb
@@ -1,7 +1,7 @@
Imports System.IO.Compression
Public Module ModMod
- Private Const LocalModCacheVersion As Integer = 7
+ Private Const LocalModCacheVersion As Integer = 8
Public Class McMod
@@ -946,8 +946,8 @@ Finished:
Try
'步骤 1:获取 Hash 与对应的工程 ID
Dim ModrinthHashes = Mods.Select(Function(m) m.ModrinthHash).ToList()
- Dim ModrinthVersion = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files", "POST",
- $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")), JObject)
+ Dim ModrinthVersion As JObject = DlModRequest("https://api.modrinth.com/v2/version_files", HttpMethod.Post,
+ $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")
Log($"[Mod] 从 Modrinth 获取到 {ModrinthVersion.Count} 个本地 Mod 的对应信息")
'步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
If ModrinthVersion.Count = 0 Then Return
@@ -967,9 +967,8 @@ Finished:
Log($"[Mod] 需要从 Modrinth 获取 {ModrinthMapping.Count} 个本地 Mod 的工程信息")
'步骤 3:获取工程信息
If Not ModrinthMapping.Any() Then Return
- Dim ModrinthProject = CType(GetJson(DlModRequest(
- $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]",
- "GET", "", "application/json")), JArray)
+ Dim ModrinthProject As JArray = DlModRequest(
+ $"https://api.modrinth.com/v2/projects?ids=[""{ModrinthMapping.Keys.Join(""",""")}""]")
For Each ProjectJson In ModrinthProject
Dim Project As New CompProject(ProjectJson)
For Each Entry In ModrinthMapping(Project.Id)
@@ -978,9 +977,9 @@ Finished:
Next
Log($"[Mod] 已从 Modrinth 获取本地 Mod 信息,继续获取更新信息")
'步骤 4:获取更新信息
- Dim ModrinthUpdate = CType(GetJson(DlModRequest("https://api.modrinth.com/v2/version_files/update", "POST",
+ Dim ModrinthUpdate As JObject = DlModRequest("https://api.modrinth.com/v2/version_files/update", HttpMethod.Post,
$"{{""hashes"": [""{ModrinthMapping.SelectMany(Function(l) l.Value.Select(Function(m) m.ModrinthHash)).Join(""",""")}""], ""algorithm"": ""sha1"",
- ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json")), JObject)
+ ""loaders"": [""{ModLoaders.Join(""",""").ToLower}""],""game_versions"": [""{McVersion}""]}}", "application/json")
For Each Entry In Mods
If Not ModrinthUpdate.ContainsKey(Entry.ModrinthHash) OrElse Entry.CompFile Is Nothing Then Continue For
Dim UpdateFile As New CompFile(ModrinthUpdate(Entry.ModrinthHash), CompType.Mod)
@@ -1015,8 +1014,8 @@ Finished:
CurseForgeHashes.Add(Entry.CurseForgeHash)
If Loader.IsAbortedWithThread(CurrentTaskThread) Then Return
Next
- Dim CurseForgeRaw = CType(CType(GetJson(DlModRequest("https://api.curseforge.com/v1/fingerprints/432", "POST",
- $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")), JObject)("data")("exactMatches"), JContainer)
+ Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432", HttpMethod.Post,
+ $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")("data")("exactMatches")
Log($"[Mod] 从 CurseForge 获取到 {CurseForgeRaw.Count} 个本地 Mod 的对应信息")
'步骤 2:尝试读取工程信息缓存,构建其他 Mod 的对应关系
If Not CurseForgeRaw.Any() Then Return
@@ -1038,8 +1037,8 @@ Finished:
Log($"[Mod] 需要从 CurseForge 获取 {CurseForgeMapping.Count} 个本地 Mod 的工程信息")
'步骤 3:获取工程信息
If Not CurseForgeMapping.Any() Then Return
- Dim CurseForgeProject = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods", "POST",
- $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")), JObject)("data")
+ Dim CurseForgeProject = DlModRequest("https://api.curseforge.com/v1/mods", HttpMethod.Post,
+ $"{{""modIds"": [{CurseForgeMapping.Keys.Join(",")}]}}", "application/json")("data")
Dim UpdateFileIds As New Dictionary(Of Integer, List(Of McMod)) 'FileId -> 本地 Mod 文件列表
Dim FileIdToProjectSlug As New Dictionary(Of Integer, String)
For Each ProjectJson In CurseForgeProject
@@ -1079,8 +1078,8 @@ Finished:
Log($"[Mod] 已从 CurseForge 获取本地 Mod 信息,需要获取 {UpdateFileIds.Count} 个用于检查更新的文件信息")
'步骤 4:获取更新文件信息
If Not UpdateFileIds.Any() Then Return
- Dim CurseForgeFiles = CType(GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST",
- $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")), JObject)("data")
+ Dim CurseForgeFiles = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post,
+ $"{{""fileIds"": [{UpdateFileIds.Keys.Join(",")}]}}", "application/json")("data")
Dim UpdateFiles As New Dictionary(Of McMod, CompFile)
For Each FileJson In CurseForgeFiles
Dim File As New CompFile(FileJson, CompType.Mod)
diff --git a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
index ce0c443..d1de713 100644
--- a/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
+++ b/Plain Craft Launcher 2/Modules/Minecraft/ModModpack.vb
@@ -244,7 +244,7 @@ Retry:
'获取 Mod 下载信息
ModDownloadLoaders.Add(New LoaderTask(Of Integer, JArray)("获取 Mod 下载信息",
Sub(Task As LoaderTask(Of Integer, JArray))
- Task.Output = GetJson(DlModRequest("https://api.curseforge.com/v1/mods/files", "POST", "{""fileIds"": [" & Join(ModList, ",") & "]}", "application/json"))("data")
+ Task.Output = DlModRequest("https://api.curseforge.com/v1/mods/files", HttpMethod.Post, "{""fileIds"": [" & Join(ModList, ",") & "]}", "application/json")("data")
'如果文件已被删除,则 API 会跳过那一项
If ModList.Count > Task.Output.Count Then Throw New Exception("整合包中的部分 Mod 版本已被 Mod 作者删除,所以没法继续安装了,请向整合包作者反馈该问题")
End Sub) With {.ProgressWeight = ModList.Count / 10}) '每 10 Mod 需要 1s
diff --git a/Plain Craft Launcher 2/Modules/ModSecret.vb b/Plain Craft Launcher 2/Modules/ModSecret.vb
index 3c74d22..c690527 100644
--- a/Plain Craft Launcher 2/Modules/ModSecret.vb
+++ b/Plain Craft Launcher 2/Modules/ModSecret.vb
@@ -1,6 +1,5 @@
'由于包含加解密等安全信息,本文件中的部分代码已被删除
-Imports System.Net.Http
Imports System.Security.Cryptography
Friend Module ModSecret
@@ -14,7 +13,6 @@ Friend Module ModSecret
'CurseForge API Key
Public CurseForgeAPIKey As String = If(Environment.GetEnvironmentVariable("PCL_CURSEFORGE_API_KEY"), "")
-
Friend Sub SecretOnApplicationStart()
'提升 UI 线程优先级
Thread.CurrentThread.Priority = ThreadPriority.Highest
@@ -73,7 +71,7 @@ Friend Module ModSecret
#Region "网络鉴权"
- Friend Function SecretCdnSign(UrlWithMark As String)
+ Friend Function SecretCdnSign(UrlWithMark As String) As String
If Not UrlWithMark.EndsWithF("{CDN}") Then Return UrlWithMark
Return UrlWithMark.Replace("{CDN}", "").Replace(" ", "%20")
End Function
@@ -92,7 +90,6 @@ Friend Module ModSecret
If Url.Contains("api.curseforge.com") Then Req.Headers.Add("x-api-key", CurseForgeAPIKey)
End Sub
-
#End Region
#Region "字符串加解密"
diff --git a/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb b/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb
deleted file mode 100644
index 1f5ea45..0000000
--- a/Plain Craft Launcher 2/Modules/Reference/CookieWebClient.vb
+++ /dev/null
@@ -1,59 +0,0 @@
-Imports System.Net
-
-Public Class CookieWebClient
- Inherits WebClient
-
- Public Sub New(container As CookieContainer, Headers As Dictionary(Of String, String))
- Me.New(container)
- For Each keyVal In Headers
- Me.Headers(keyVal.Key) = keyVal.Value
- Next
- End Sub
-
- Public Sub New()
- Me.New(New CookieContainer())
- End Sub
-
- Public Sub New(container As CookieContainer)
- Me.container = container
- End Sub
-
- Private Shadows ReadOnly container As New CookieContainer()
-
- '''
- ''' 以毫秒为单位的超时。
- '''
- Public Timeout As Integer = 600000
-
- Protected Overrides Function GetWebRequest(address As Uri) As WebRequest
- Dim r As WebRequest = MyBase.GetWebRequest(address)
- Dim request = TryCast(r, HttpWebRequest)
-
- If request IsNot Nothing Then
- request.CookieContainer = container
- request.Timeout = Timeout
- End If
-
- Return r
- End Function
-
- Protected Overrides Function GetWebResponse(request As WebRequest, result As IAsyncResult) As WebResponse
- Dim response As WebResponse = MyBase.GetWebResponse(request, result)
- ReadCookies(response)
- Return response
- End Function
-
- Protected Overrides Function GetWebResponse(request As WebRequest) As WebResponse
- Dim response As WebResponse = MyBase.GetWebResponse(request)
- ReadCookies(response)
- Return response
- End Function
-
- Private Sub ReadCookies(r As WebResponse)
- Dim response = TryCast(r, HttpWebResponse)
- If response IsNot Nothing Then
- Dim cookies As CookieCollection = response.Cookies
- container.Add(cookies)
- End If
- End Sub
-End Class
diff --git a/Plain Craft Launcher 2/Modules/Base/ModDragHelper.vb b/Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb
similarity index 68%
rename from Plain Craft Launcher 2/Modules/Base/ModDragHelper.vb
rename to Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb
index 8db1bf5..ff950c4 100644
--- a/Plain Craft Launcher 2/Modules/Base/ModDragHelper.vb
+++ b/Plain Craft Launcher 2/Modules/ThirdParty/DragHelper.vb
@@ -1,11 +1,10 @@
-Imports System
+'来源:https://blog.csdn.net/simpleman2000/article/details/140952294
+
Imports System.ComponentModel
Imports System.Runtime.InteropServices
-Imports System.Text
Imports System.Windows.Interop
-
-Public Class DragFileHelper
+Public Class DragHelper
Public Event DragDrop As EventHandler
@@ -32,22 +31,22 @@ Public Class DragFileHelper
Public Property HwndIntPtrSource As HwndSource
Public Sub AddHook()
- Me.RemoveDragHook()
- Me.HwndIntPtrSource.AddHook(AddressOf WndProc)
- Dim handle As IntPtr = Me.HwndIntPtrSource.Handle
+ RemoveDragHook()
+ HwndIntPtrSource.AddHook(AddressOf WndProc)
+ Dim handle As IntPtr = HwndIntPtrSource.Handle
If IsUserAnAdmin() Then RevokeDragDrop(handle)
DragAcceptFiles(handle, True)
ChangeMessageFilter(handle)
End Sub
Public Sub RemoveDragHook()
- Me.HwndIntPtrSource.RemoveHook(AddressOf WndProc)
- DragAcceptFiles(Me.HwndIntPtrSource.Handle, False)
+ HwndIntPtrSource.RemoveHook(AddressOf WndProc)
+ DragAcceptFiles(HwndIntPtrSource.Handle, False)
End Sub
- Private Function WndProc(ByVal hwnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, ByVal lParam As IntPtr, ByRef handled As Boolean) As IntPtr
+ Private Function WndProc(hwnd As IntPtr, msg As Integer, wParam As IntPtr, lParam As IntPtr, ByRef handled As Boolean) As IntPtr
Dim filePaths As String() = Nothing
- Dim point As POINT = New POINT()
+ Dim point As New POINT()
If TryGetDropInfo(msg, wParam, filePaths, point) Then
DropPoint = point
@@ -59,31 +58,31 @@ Public Class DragFileHelper
End Function
- Private Shared Function ChangeWindowMessageFilterEx(ByVal hWnd As IntPtr, ByVal msg As UInteger, ByVal action As UInteger, ByRef pChangeFilterStruct As CHANGEFILTERSTRUCT) As Boolean
+ Private Shared Function ChangeWindowMessageFilterEx(hWnd As IntPtr, msg As UInteger, action As UInteger, ByRef pChangeFilterStruct As CHANGEFILTERSTRUCT) As Boolean
End Function
- Private Shared Function ChangeWindowMessageFilter(ByVal msg As UInteger, ByVal flags As UInteger) As Boolean
+ Private Shared Function ChangeWindowMessageFilter(msg As UInteger, flags As UInteger) As Boolean
End Function
- Private Shared Sub DragAcceptFiles(ByVal hWnd As IntPtr, ByVal fAccept As Boolean)
+ Private Shared Sub DragAcceptFiles(hWnd As IntPtr, fAccept As Boolean)
End Sub
- Private Shared Function DragQueryFile(ByVal hWnd As IntPtr, ByVal iFile As UInteger, ByVal lpszFile As StringBuilder, ByVal cch As Integer) As UInteger
+ Private Shared Function DragQueryFile(hWnd As IntPtr, iFile As UInteger, lpszFile As StringBuilder, cch As Integer) As UInteger
End Function
- Private Shared Function DragQueryPoint(ByVal hDrop As IntPtr, ByRef lppt As POINT) As Boolean
+ Private Shared Function DragQueryPoint(hDrop As IntPtr, ByRef lppt As POINT) As Boolean
End Function
- Private Shared Sub DragFinish(ByVal hDrop As IntPtr)
+ Private Shared Sub DragFinish(hDrop As IntPtr)
End Sub
- Private Shared Function RevokeDragDrop(ByVal hWnd As IntPtr) As Integer
+ Private Shared Function RevokeDragDrop(hWnd As IntPtr) As Integer
End Function
@@ -109,13 +108,13 @@ Public Class DragFileHelper
Private Const MSGFLT_ADD As UInteger = 1
Private Const MAX_PATH As Integer = 260
- Private Shared Sub ChangeMessageFilter(ByVal handle As IntPtr)
+ Private Shared Sub ChangeMessageFilter(handle As IntPtr)
Dim ver As Version = Environment.OSVersion.Version
Dim isVistaOrHigher As Boolean = ver >= New Version(6, 0)
Dim isNt61OrHiger As Boolean = ver >= New Version(6, 1)
If isVistaOrHigher Then
- Dim status As CHANGEFILTERSTRUCT = New CHANGEFILTERSTRUCT With {.cbSize = 8}
+ Dim status As New CHANGEFILTERSTRUCT With {.cbSize = 8}
For Each msg As UInteger In New UInteger() {WM_DROPFILES, WM_COPYGLOBALDATA, WM_COPYDATA}
Dim [error] As Boolean = False
If isNt61OrHiger Then
@@ -129,7 +128,7 @@ Public Class DragFileHelper
End If
End Sub
- Private Shared Function TryGetDropInfo(ByVal msg As Integer, ByVal wParam As IntPtr, ByRef dropFilePaths As String(), ByRef dropPoint As POINT) As Boolean
+ Private Shared Function TryGetDropInfo(msg As Integer, wParam As IntPtr, ByRef dropFilePaths As String(), ByRef dropPoint As POINT) As Boolean
dropFilePaths = Nothing
dropPoint = New POINT()
@@ -141,7 +140,7 @@ Public Class DragFileHelper
For i As UInteger = 0 To fileCount - 1
Dim sb As New StringBuilder(MAX_PATH)
Dim result As UInteger = DragQueryFile(wParam, i, sb, sb.Capacity)
- If result > 0 Then dropFilePaths(CInt(i)) = sb.ToString()
+ If result > 0 Then dropFilePaths(i) = sb.ToString()
Next
DragQueryPoint(wParam, dropPoint)
@@ -149,4 +148,4 @@ Public Class DragFileHelper
Return True
End Function
-End Class
\ No newline at end of file
+End Class
diff --git a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb
index 8d8a259..9de41ac 100644
--- a/Plain Craft Launcher 2/My Project/AssemblyInfo.vb
+++ b/Plain Craft Launcher 2/My Project/AssemblyInfo.vb
@@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices
' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
' 方法是按如下所示使用“*”
-
-
+
+
diff --git a/Plain Craft Launcher 2/My Project/Resources.Designer.vb b/Plain Craft Launcher 2/My Project/Resources.Designer.vb
index 81e998d..dfc568a 100644
--- a/Plain Craft Launcher 2/My Project/Resources.Designer.vb
+++ b/Plain Craft Launcher 2/My Project/Resources.Designer.vb
@@ -63,9 +63,9 @@ Namespace My.Resources
'''
''' 查找 System.Byte[] 类型的本地化资源。
'''
- Friend ReadOnly Property Custom() As Byte()
+ Friend ReadOnly Property CacheCow_Client() As Byte()
Get
- Dim obj As Object = ResourceManager.GetObject("Custom", resourceCulture)
+ Dim obj As Object = ResourceManager.GetObject("CacheCow.Client", resourceCulture)
Return CType(obj,Byte())
End Get
End Property
@@ -73,9 +73,29 @@ Namespace My.Resources
'''
''' 查找 System.Byte[] 类型的本地化资源。
'''
- Friend ReadOnly Property Dialogs() As Byte()
+ Friend ReadOnly Property CacheCow_Client_FileStore() As Byte()
Get
- Dim obj As Object = ResourceManager.GetObject("Dialogs", resourceCulture)
+ Dim obj As Object = ResourceManager.GetObject("CacheCow.Client.FileStore", resourceCulture)
+ Return CType(obj,Byte())
+ End Get
+ End Property
+
+ '''
+ ''' 查找 System.Byte[] 类型的本地化资源。
+ '''
+ Friend ReadOnly Property CacheCow_Common() As Byte()
+ Get
+ Dim obj As Object = ResourceManager.GetObject("CacheCow.Common", resourceCulture)
+ Return CType(obj,Byte())
+ End Get
+ End Property
+
+ '''
+ ''' 查找 System.Byte[] 类型的本地化资源。
+ '''
+ Friend ReadOnly Property Custom() As Byte()
+ Get
+ Dim obj As Object = ResourceManager.GetObject("Custom", resourceCulture)
Return CType(obj,Byte())
End Get
End Property
@@ -105,7 +125,7 @@ Namespace My.Resources
'''
Friend ReadOnly Property Imazen_WebP() As Byte()
Get
- Dim obj As Object = ResourceManager.GetObject("Imazen_WebP", resourceCulture)
+ Dim obj As Object = ResourceManager.GetObject("Imazen.WebP", resourceCulture)
Return CType(obj,Byte())
End Get
End Property
@@ -120,16 +140,6 @@ Namespace My.Resources
End Get
End Property
- '''
- ''' 查找 System.Byte[] 类型的本地化资源。
- '''
- Friend ReadOnly Property Json() As Byte()
- Get
- Dim obj As Object = ResourceManager.GetObject("Json", resourceCulture)
- Return CType(obj,Byte())
- End Get
- End Property
-
'''
''' 查找 System.Byte[] 类型的本地化资源。
'''
@@ -160,6 +170,36 @@ Namespace My.Resources
End Get
End Property
+ '''
+ ''' 查找 System.Byte[] 类型的本地化资源。
+ '''
+ Friend ReadOnly Property Newtonsoft_Json() As Byte()
+ Get
+ Dim obj As Object = ResourceManager.GetObject("Newtonsoft.Json", resourceCulture)
+ Return CType(obj,Byte())
+ End Get
+ End Property
+
+ '''
+ ''' 查找 System.Byte[] 类型的本地化资源。
+ '''
+ Friend ReadOnly Property Ookii_Dialogs_Wpf() As Byte()
+ Get
+ Dim obj As Object = ResourceManager.GetObject("Ookii.Dialogs.Wpf", resourceCulture)
+ Return CType(obj,Byte())
+ End Get
+ End Property
+
+ '''
+ ''' 查找 System.Byte[] 类型的本地化资源。
+ '''
+ Friend ReadOnly Property System_Net_Http_Formatting() As Byte()
+ Get
+ Dim obj As Object = ResourceManager.GetObject("System.Net.Http.Formatting", resourceCulture)
+ Return CType(obj,Byte())
+ End Get
+ End Property
+
'''
''' 查找 System.Byte[] 类型的本地化资源。
'''
diff --git a/Plain Craft Launcher 2/My Project/Resources.resx b/Plain Craft Launcher 2/My Project/Resources.resx
index 8f1da65..8db2019 100644
--- a/Plain Craft Launcher 2/My Project/Resources.resx
+++ b/Plain Craft Launcher 2/My Project/Resources.resx
@@ -118,27 +118,30 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ ..\Resources\CacheCow.Client.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\CacheCow.Client.FileStore.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\CacheCow.Common.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
..\resources\custom.xaml;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
- ..\resources\ookii.dialogs.wpf.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
..\Resources\forge-installer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
..\Resources\Help.zip;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
+
..\Resources\Imazen.WebP.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
..\Resources\java-wrapper.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
- ..\Resources\Newtonsoft.Json.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
..\Resources\libwebp64.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
@@ -148,6 +151,15 @@
..\Resources\NAudio.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ ..\Resources\Newtonsoft.Json.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\resources\ookii.dialogs.wpf.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ ..\Resources\System.Net.Http.Formatting.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
..\Resources\transformer.jar;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml
index 2e3c8c5..944bca6 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml
+++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml
@@ -92,4 +92,4 @@
-
+
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb
index 18be6d0..f3d278c 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageComp.xaml.vb
@@ -99,7 +99,6 @@ Public Class PageComp
End If
TargetVersion = Nothing
If TargetName IsNot Nothing Then
- '设置搜索框内容
TextSearchName.Text = TargetName
TargetName = Nothing
End If
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb
index 3f1b0c9..32a0c61 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/Comp/PageDownloadCompDetail.xaml.vb
@@ -73,13 +73,14 @@
'筛选类型相同的结果(Modrinth 会返回 Mod、服务端插件、数据包混合的列表)
Private Function GetResults() As List(Of CompFile)
Dim Results As List(Of CompFile) = CompFileLoader.Output
- If PageType = CompType.Any Then
- Results = Results.Where(Function(r) r.Type <> CompType.Plugin).ToList
- ElseIf PageType = CompType.Shader OrElse PageType = CompType.ResourcePack Then
- Results = Results.ToList
- Else
- Results = Results.Where(Function(r) r.Type = PageType).ToList
- End If
+ Select Case PageType
+ Case CompType.Any
+ Results = Results.Where(Function(r) r.Type <> CompType.Plugin).ToList
+ Case CompType.Shader, CompType.ResourcePack
+ '不筛选光影和资源包,否则原版光影会因为是资源包格式而被过滤(#6473)
+ Case Else
+ Results = Results.Where(Function(r) r.Type = PageType).ToList
+ End Select
Return Results
End Function
Private Sub Load_OnFinish()
@@ -358,7 +359,7 @@
Return False
End Function
'获取常规资源默认下载位置
- If CachedFolder.ContainsKey(Project.Type) AndAlso Not String.IsNullOrEmpty(CachedFolder(Project.Type)) Then
+ If CachedFolder.ContainsKey(Project.Type) Then
DefaultFolder = CachedFolder(Project.Type)
Log($"[Comp] 使用上次下载时的文件夹作为默认下载位置:{DefaultFolder}")
ElseIf McVersionCurrent IsNot Nothing AndAlso IsVersionSuitable(McVersionCurrent) Then
@@ -424,13 +425,7 @@
If Not Target.Contains("\") Then Return
'构造步骤加载器
Dim LoaderName As String = Desc & "下载:" & GetFileNameWithoutExtentionFromPath(Target) & " "
- If Target <> DefaultFolder Then
- If CachedFolder.ContainsKey(Project.Type) Then
- CachedFolder(Project.Type) = GetPathFromFullPath(Target)
- Else
- CachedFolder.Add(Project.Type, GetPathFromFullPath(Target))
- End If
- End If
+ If Target <> DefaultFolder Then CachedFolder(Project.Type) = GetPathFromFullPath(Target)
Dim Loaders As New List(Of LoaderBase)
Loaders.Add(New LoaderDownload("下载文件", New List(Of NetFile) From {File.ToNetFile(Target)}) With {.ProgressWeight = 6, .Block = True})
'启动
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
index 5829841..02c92e2 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/ModDownloadLib.vb
@@ -570,7 +570,8 @@ pause"
'官方源
Dim PageData As String
Try
- PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True)
+ PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile,
+ Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True)
Task.Progress = 0.8
Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
@@ -733,7 +734,8 @@ Retry:
'官方源
Dim PageData As String
Try
- PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True)
+ PageData = NetRequestByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile,
+ Encoding:=New UTF8Encoding(False), Timeout:=15000, Accept:="text/html", UseBrowserUserAgent:=True)
Task.Progress = 0.8
Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
@@ -1340,7 +1342,7 @@ Retry:
If Json("data") IsNot Nothing AndAlso Json("data")("MOJMAPS") IsNot Nothing Then
'下载原版 Json 文件
Task.Progress = 0.4
- Dim RawJson As JObject = GetJson(NetGetCodeByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True))
+ Dim RawJson As JObject = GetJson(NetRequestByLoader(DlSourceLauncherOrMetaGet(DlClientListGet(Inherit)), IsJson:=True))
'[net.minecraft:client:1.17.1-20210706.113038:mappings@txt] 或 @tsrg]
Dim OriginalName As String = Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).BeforeFirst("@")
Dim Address = McLibGet(OriginalName).Replace(".jar", "-mappings." & Json("data")("MOJMAPS")("client").ToString.Trim("[]".ToCharArray()).Split("@")(1))
@@ -1641,30 +1643,31 @@ Retry:
Public Sub McDownloadForgeRecommendedRefresh()
If IsForgeRecommendedRefreshed Then Return
IsForgeRecommendedRefreshed = True
- RunInNewThread(Sub()
- Try
- Log("[Download] 刷新 Forge 推荐版本缓存开始")
- Dim Result As String = NetGetCodeByLoader("https://bmclapi2.bangbang93.com/forge/promos")
- If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")")
- Dim ResultJson As JContainer = GetJson(Result)
- '获取所有推荐版本列表
- Dim RecommendedList As New List(Of String)
- For Each Version As JObject In ResultJson
- If Version("name") Is Nothing OrElse Version("build") Is Nothing Then Continue For
- Dim Name As String = Version("name")
- If Not Name.EndsWithF("-recommended") Then Continue For
- '内容为:"1.15.2":"31.2.0"
- RecommendedList.Add("""" & Name.Replace("-recommended", """:""" & Version("build")("version").ToString & """"))
- Next
- If RecommendedList.Count < 5 Then Throw New Exception("获取的推荐版本数过少(" & Result & ")")
- '保存
- Dim CacheJson As String = "{" & Join(RecommendedList, ",") & "}"
- WriteFile(PathTemp & "Cache\ForgeRecommendedList.json", CacheJson)
- Log("[Download] 刷新 Forge 推荐版本缓存成功")
- Catch ex As Exception
- Log(ex, "刷新 Forge 推荐版本缓存失败")
- End Try
- End Sub, "ForgeRecommendedRefresh")
+ RunInNewThread(
+ Sub()
+ Try
+ Log("[Download] 刷新 Forge 推荐版本缓存开始")
+ Dim Result As String = NetRequestByClientRetry("https://bmclapi2.bangbang93.com/forge/promos")
+ If Result.Length < 1000 Then Throw New Exception("获取的结果过短(" & Result & ")")
+ Dim ResultJson As JContainer = GetJson(Result)
+ '获取所有推荐版本列表
+ Dim RecommendedList As New List(Of String)
+ For Each Version As JObject In ResultJson
+ If Version("name") Is Nothing OrElse Version("build") Is Nothing Then Continue For
+ Dim Name As String = Version("name")
+ If Not Name.EndsWithF("-recommended") Then Continue For
+ '内容为:"1.15.2":"31.2.0"
+ RecommendedList.Add("""" & Name.Replace("-recommended", """:""" & Version("build")("version").ToString & """"))
+ Next
+ If RecommendedList.Count < 5 Then Throw New Exception("获取的推荐版本数过少(" & Result & ")")
+ '保存
+ Dim CacheJson As String = "{" & Join(RecommendedList, ",") & "}"
+ WriteFile(PathTemp & "Cache\ForgeRecommendedList.json", CacheJson)
+ Log("[Download] 刷新 Forge 推荐版本缓存成功")
+ Catch ex As Exception
+ Log(ex, "刷新 Forge 推荐版本缓存失败")
+ End Try
+ End Sub, "ForgeRecommendedRefresh")
End Sub
Private IsForgeRecommendedRefreshed As Boolean = False
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
index 5578905..35c1a4e 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml
@@ -27,8 +27,7 @@
-
-
+
diff --git a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
index 0e347f7..61eedf1 100644
--- a/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageDownload/PageDownloadInstall.xaml.vb
@@ -476,11 +476,6 @@
Else
HintModOptiFine.Visibility = Visibility.Collapsed
End If
- If SelectedFabric IsNot Nothing AndAlso VersionSortInteger(SelectedMinecraftId, "1.21.4") > 0 Then
- HintModOptiFineHigh.Visibility = Visibility.Visible
- Else
- HintModOptiFineHigh.Visibility = Visibility.Collapsed
- End If
'结束
IsReloading = False
End Sub
@@ -676,13 +671,16 @@
If SelectedNeoForge IsNot Nothing Then Return "与 NeoForge 不兼容"
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
- '检查 Fabric 1.20.5+:没有 OptiFabric 故全部不兼容
- If SelectedFabric IsNot Nothing AndAlso VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then Return "与 Fabric 不兼容"
'检查 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
+ '检查 Fabric 1.20.5+:全部不兼容
+ If SelectedFabric IsNot Nothing AndAlso
+ VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then
+ Return "与 Fabric 不兼容"
+ End If
'检查 Forge 版本
Dim HasAny As Boolean = False
Dim HasRequiredVersion As Boolean = False
@@ -988,8 +986,11 @@
Private Function LoadFabricGetError() As String
If LoadFabric Is Nothing OrElse LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Run Then Return "加载中……"
If LoadFabric.State.LoadingState = MyLoading.MyLoadingState.Error Then Return "获取版本列表失败:" & CType(LoadFabric.State, Object).Error.Message
- '检查 Fabric 1.20.5+:没有 OptiFabric 故全部不兼容
- If SelectedOptiFine IsNot Nothing AndAlso VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then Return "与 OptiFine 不兼容"
+ '检查 OptiFine 1.20.5+:没有 OptiFabric 故全部不兼容
+ If SelectedOptiFine IsNot Nothing AndAlso
+ VersionSortInteger(SelectedMinecraftId, "1.20.4") > 0 Then
+ Return "与 OptiFine 不兼容"
+ End If
For Each Version As JObject In DlFabricListLoader.Output.Value("game")
If Version("version").ToString = SelectedMinecraftId.Replace("∞", "infinite").Replace("Combat Test 7c", "1.16_combat-3") Then
If SelectedForge IsNot Nothing Then Return "与 Forge 不兼容"
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
index 533ce72..7181978 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/MyMsgLogin.xaml.vb
@@ -105,13 +105,15 @@
Dim UnknownFailureCount As Integer = 0
Do While Not MyConverter.IsExited
Try
- Dim Result = NetRequestOnce(
- "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", "POST",
- "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" &
- "client_id=" & OAuthClientId & "&" &
- "device_code=" & DeviceCode & "&" &
- "scope=XboxLive.signin%20offline_access",
- "application/x-www-form-urlencoded", 5000 + UnknownFailureCount * 5000, MakeLog:=False)
+ Dim Result = NetRequestByClient(
+ "https://login.microsoftonline.com/consumers/oauth2/v2.0/token", HttpMethod.Post,
+ Content:=
+ "grant_type=urn:ietf:params:oauth:grant-type:device_code" & "&" &
+ "client_id=" & OAuthClientId & "&" &
+ "device_code=" & DeviceCode & "&" &
+ "scope=XboxLive.signin%20offline_access",
+ ContentType:="application/x-www-form-urlencoded",
+ Timeout:=5000 + UnknownFailureCount * 5000)
'获取结果
Dim ResultJson As JObject = GetJson(Result)
McLaunchLog($"令牌过期时间:{ResultJson("expires_in")} 秒")
@@ -125,8 +127,8 @@
ElseIf ex.Message.Contains("expired_token") Then
Finished(New Exception("$登录用时太长啦,重新试试吧!"))
Return
- Else If ex.Message.Contains("Account security interrupt") Then
- Finished(New Exception("$非常抱歉,该账号由于安全问题无法登陆,请前往 Microsoft 账户页获取更多信息。"))
+ ElseIf ex.Message.Contains("Account security interrupt") Then
+ Finished(New Exception("$该账号由于安全问题无法登陆,请前往微软账户页获取更多信息。"))
Return
ElseIf ex.Message.Contains("service abuse") Then
Finished(New Exception("$非常抱歉,该账号已被微软封禁,无法登录。"))
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
index 190326d..352c69b 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/MySkin.xaml.vb
@@ -259,12 +259,13 @@ Retry:
End Sub)
If SelId Is Nothing Then Return
'发送请求
- Dim Result As String = NetRequestRetry("https://api.minecraftservices.com/minecraft/profile/capes/active",
- If(SelId = 0, "DELETE", "PUT"),
- If(SelId = 0, "", New JObject(New JProperty("capeId", SkinData("capes")(SelId - 1)("id"))).ToString(0)),
- "application/json", Headers:=New Dictionary(Of String, String) From {{"Authorization", "Bearer " & AccessToken}})
+ Dim Result As String = NetRequestByClientRetry("https://api.minecraftservices.com/minecraft/profile/capes/active",
+ If(SelId = 0, HttpMethod.Delete, HttpMethod.Put),
+ Content:=If(SelId = 0, "", New JObject(New JProperty("capeId", SkinData("capes")(SelId - 1)("id"))).ToString(0)),
+ ContentType:="application/json",
+ Headers:={{"Authorization", "Bearer " & AccessToken}})
If Result.Contains("""errorMessage""") Then
- Hint("更改披风失败:" & GetJson(Result)("errorMessage"), HintType.Critical)
+ Hint("更改披风失败:" & GetJson(Result)("errorMessage").ToString, HintType.Critical)
Return
Else
Hint("更改披风成功!", HintType.Finish)
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
index f9a2be3..2fafa6f 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLaunchRight.xaml.vb
@@ -156,7 +156,7 @@ Download:
Dim Version As String = ""
Dim NeedDownload As Boolean = True
Try
- Version = NetGetCodeByRequestOnce(VersionAddress, Timeout:=10000)
+ Version = NetRequestByClientRetry(VersionAddress)
If Version.Length > 1000 Then Throw New Exception($"获取的主页版本过长({Version.Length} 字符)")
Dim CurrentVersion As String = Setup.Get("CacheSavedPageVersion")
If Version <> "" AndAlso CurrentVersion <> "" AndAlso Version = CurrentVersion Then
@@ -171,7 +171,7 @@ Download:
End Try
'实际下载
If NeedDownload Then
- Dim FileContent As String = NetGetCodeByRequestRetry(Address)
+ Dim FileContent As String = NetRequestByClientRetry(Address)
Log($"[Page] 已联网下载主页,内容长度:{FileContent.Length},来源:{Address}")
Setup.Set("CacheSavedPageUrl", Address)
Setup.Set("CacheSavedPageVersion", Version)
@@ -231,7 +231,9 @@ Download:
Try
'修改时应同时修改 PageOtherHelpDetail.Init
Content = HelpArgumentReplace(Content)
- If Content.Contains("xmlns") Then Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间
+ Do While Content.Contains("xmlns")
+ Content = Content.RegexReplace("xmlns[^""']*(""|')[^""']*(""|')", "").Replace("xmlns", "") '禁止声明命名空间
+ Loop
Content = "" & Content & ""
Log($"[Page] 实例化:加载主页 UI 开始,最终内容长度:{Content.Count}")
PanCustom.Children.Add(GetObjectFromXML(Content))
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml
index ff528d2..2ca71d0 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml
@@ -5,7 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PCL"
mc:Ignorable="d" d:DesignWidth="297.6">
-
+
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb
index f3879f4..33cafbb 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginLegacy.xaml.vb
@@ -64,6 +64,7 @@ Public Class PageLoginLegacy
Private Sub ComboLegacy_TextChanged(sender As Object, e As TextChangedEventArgs) Handles ComboName.TextChanged
If Setup.Get("LaunchSkinType") = 0 Then PageLaunchLeft.SkinLegacy.Start(IsForceRestart:=True)
HintChinese.Visibility = If(RegexCheck(ComboName.Text, "^[0-9A-Za-z_]*$"), Visibility.Collapsed, Visibility.Visible)
+ RunInUi(Sub() PanMain.InvalidateMeasure(), True) '由于 WPF 的 Bug,它不一定会自动更新 HintChinese 的大小(#6627)
End Sub
Private Sub Skin_Click() Handles Skin.Click
If (Setup.Get("UiHiddenPageSetup") OrElse Setup.Get("UiHiddenSetupLaunch")) AndAlso Not PageSetupUI.HiddenForceShow Then
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml
index 170f534..f6c72da 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml
@@ -23,9 +23,11 @@
-
-
+
+
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
index dd33df6..325a077 100644
--- a/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageLaunch/PageLoginMsSkin.xaml.vb
@@ -107,7 +107,7 @@ Retry:
McLoginMsLoader.Start(GetLoginData(), IsForceRestart:=True)
GoTo Retry
ElseIf Result.Contains("""error""") Then
- Hint("更改皮肤失败:" & GetJson(Result)("error"), HintType.Critical)
+ Hint("更改皮肤失败:" & GetJson(Result)("error").ToString, HintType.Critical)
Return
End If
'获取新皮肤地址
diff --git a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
index eb864b7..cff69c5 100644
--- a/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
+++ b/Plain Craft Launcher 2/Pages/PageOther/PageOtherAbout.xaml
@@ -1,468 +1,491 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
index b553cc7..19f89cf 100644
--- a/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSelectRight.xaml.vb
@@ -227,7 +227,7 @@
DeleteDirectory(Version.Path)
Hint("版本 " & Version.Name & " 已永久删除!", HintType.Finish)
Else
- FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+ FileIO.FileSystem.DeleteDirectory(Version.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
Hint("版本 " & Version.Name & " 已删除到回收站!", HintType.Finish)
End If
Case 2
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
index 1416b83..a04f7f7 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupLaunch.xaml
@@ -205,7 +205,7 @@
-
+
@@ -240,10 +240,10 @@
ToolTip="还原" ToolTipService.Placement="Right" ToolTipService.InitialShowDelay="200" ToolTipService.VerticalOffset="-1"
Logo="M530 0c287 0 521 229 521 511s-233 511-521 511c-233 0-436-151-500-368a63 63 0 0 1 44-79 65 65 0 0 1 80 43c48 162 200 276 375 276 215 0 390-171 390-383s-174-383-390-383c-103 0-199 39-270 106l21-5a63 63 0 0 1 33 123l-157 42a65 65 0 0 1-90-42l-49-183a65 65 0 1 1 126-33l6 26A524 524 0 0 1 530 0z" LogoScale="0.9" />
-
-
+
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
index 4142ee9..9fd7422 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupSystem.xaml
@@ -198,7 +198,7 @@
-
+
diff --git a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
index a4cb54d..e39438e 100644
--- a/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageSetup/PageSetupUI.xaml.vb
@@ -1,6 +1,4 @@
-Imports NAudio.Wave
-
-Public Class PageSetupUI
+Public Class PageSetupUI
Public Shadows IsLoaded As Boolean = False
@@ -276,7 +274,7 @@ Public Class PageSetupUI
'顶部栏
Private Sub BtnLogoChange_Click(sender As Object, e As EventArgs) Handles BtnLogoChange.Click
- Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp;*.jpeg)|*.png;*.jpg;*.gif;*.webp;*.jpeg", "选择图片")
+ Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.jpeg;*.gif;*.webp)|*.png;*.jpg;*.jpeg;*.gif;*.webp", "选择图片")
If FileName = "" Then Return
Try
'拷贝文件
@@ -321,7 +319,7 @@ Refresh:
Return
End If
'没有图片则要求选择
- Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.webp)|*.png;*.jpg;*.gif;*.webp", "选择图片")
+ Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpeg;*.jpg;*.gif;*.webp)|*.png;*.jpeg;*.jpg;*.gif;*.webp", "选择图片")
If FileName = "" Then
FrmMain.ImageTitleLogo.Source = Nothing
e.Handled = True
@@ -359,15 +357,9 @@ Refresh:
PanMusicVolume.Visibility = Visibility.Visible
PanMusicDetail.Visibility = Visibility.Visible
BtnMusicClear.Visibility = Visibility.Visible
- Dim validMusicCount As Integer = 0
- For Each File In EnumerateFiles(Path & "PCL\Musics\")
- Try
- If {".ini", ".jpg", ".txt", ".cfg", ".lrc", ".db", ".png"}.Contains(File.Extension.ToLower) Then Continue For
- validMusicCount += 1
- Catch ex As Exception
- End Try
- Next
- CardMusic.Title = "背景音乐(" & validMusicCount & " 首)"
+ CardMusic.Title = "背景音乐(" &
+ EnumerateFiles(Path & "PCL\Musics\").Count(Function(f) {".ini", ".jpg", ".txt", ".cfg", ".lrc", ".db", ".png"}.Contains(f.Extension.ToLower)) &
+ " 首)"
Else
PanMusicVolume.Visibility = Visibility.Collapsed
PanMusicDetail.Visibility = Visibility.Collapsed
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb
index 6a5e6b0..9f03eed 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionExport.xaml.vb
@@ -564,8 +564,8 @@ Public Class PageVersionExport
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)
+ Dim ModrinthRaw As JObject = DlModRequest("https://api.modrinth.com/v2/version_files", HttpMethod.Post,
+ $"{{""hashes"": [""{ModrinthHashes.Join(""",""")}""], ""algorithm"": ""sha1""}}", "application/json")
For Each ModFile In Loader.Input
'查找对应的文件
If Not ModrinthRaw.ContainsKey(ModFile.ModrinthHash) Then Continue For
@@ -589,8 +589,8 @@ Public Class PageVersionExport
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)
+ Dim CurseForgeRaw As JContainer = DlModRequest("https://api.curseforge.com/v1/fingerprints/432/", HttpMethod.Post,
+ $"{{""fingerprints"": [{CurseForgeHashes.Join(",")}]}}", "application/json")("data")("exactMatches")
For Each ResultJson As JObject In CurseForgeRaw
If Not ResultJson.ContainsKey("file") Then Continue For
Dim File As JObject = ResultJson("file")
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
index 3fe78d0..ed319a7 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionMod.xaml.vb
@@ -381,8 +381,9 @@ Install:
''' 下载 Mod。
'''
Private Sub BtnManageDownload_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageDownload.Click, BtnHintDownload.Click
- PageComp.TargetVersion = PageVersionLeft.Version '将当前版本设置为筛选器
- PageComp.TargetName = SearchBox.Text '将当前搜索框内容设置为筛选器
+ '预设置当前版本和搜索框内容
+ PageComp.TargetVersion = PageVersionLeft.Version
+ PageComp.TargetName = SearchBox.Text
FrmMain.PageChange(FormMain.PageType.Download, FormMain.PageSubType.DownloadMod)
End Sub
@@ -429,6 +430,11 @@ Install:
If FrmMain.PageRight IsNot Me Then Return
If My.Computer.Keyboard.CtrlKeyDown AndAlso e.Key = Key.A Then ChangeAllSelected(True)
End Sub
+ Private Sub SearchBox_PreviewKeyDown(sender As Object, e As KeyEventArgs) Handles SearchBox.PreviewKeyDown
+ 'Ctrl + A 会被搜索框捕获,导致无法全选,所以在按下 Ctrl + A 时转移焦点以便捕获
+ If SearchBox.Text.Any Then Return
+ If My.Computer.Keyboard.CtrlKeyDown AndAlso e.Key = Key.A Then PanBack.Focus()
+ End Sub
#End Region
@@ -637,14 +643,14 @@ Install:
Try
For Each Entry As McMod In ModList
If File.Exists(Entry.Path) Then
- My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+ My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
Else
Log($"[Mod] 未找到更新前的 Mod 文件,跳过对它的删除:{Entry.Path}", LogLevel.Debug)
End If
Next
For Each Entry As KeyValuePair(Of String, String) In FileCopyList
If File.Exists(Entry.Value) Then
- My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+ My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
Log($"[Mod] 更新后的 Mod 文件已存在,将会把它放入回收站:{Entry.Value}", LogLevel.Debug)
End If
If Directory.Exists(GetPathFromFullPath(Entry.Value)) Then
@@ -733,7 +739,7 @@ Install:
If IsShiftPressed Then
File.Delete(ModEntity.Path)
Else
- My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
+ My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.AllDialogs, FileIO.RecycleOption.SendToRecycleBin)
End If
Catch ex As OperationCanceledException
Log(ex, "删除 Mod 被主动取消")
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
index 67afc38..075e7eb 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionOverall.xaml.vb
@@ -116,12 +116,14 @@
Dim TempName As String = NewName & "_temp"
Dim TempPath As String = PathMcFolder & "versions\" & TempName & "\"
Dim IsCaseChangedOnly As Boolean = NewName.ToLower = OldName.ToLower
- '重新加载版本 Json 信息,避免 HMCL 项被合并
+ '重新读取版本 JSON 信息,避免 JsonObject 中已被合并的项被重新存储
Dim JsonObject As JObject
Try
- JsonObject = GetJson(ReadFile(PageVersionLeft.Version.Path & PageVersionLeft.Version.Name & ".json"))
+ Dim JsonPath As String = PageVersionLeft.Version.GetJsonPath()
+ JsonObject = GetJson(ReadFile(JsonPath))
+ File.Delete(JsonPath)
Catch ex As Exception
- Log(ex, "重命名读取 json 时失败")
+ Log(ex, "在重命名读取 json 时失败")
JsonObject = PageVersionLeft.Version.JsonObject
End Try
'重命名主文件夹
@@ -130,32 +132,24 @@
'清理 ini 缓存
IniClearCache(PageVersionLeft.Version.PathIndie & "options.txt")
IniClearCache(PageVersionLeft.Version.Path & "PCL\Setup.ini")
- '重命名 JAR 文件、JSON 文件与 natives 文件夹
- If Directory.Exists(NewPath & OldName & "-natives") Then
+ '重命名 jar 文件与 natives 文件夹
+ '不能进行遍历重命名,否则在版本名很短的时候容易误伤其他文件(#6443)
+ If Directory.Exists($"{NewPath}{OldName}-natives") Then
If IsCaseChangedOnly Then
- My.Computer.FileSystem.RenameDirectory(NewPath & OldName & "-natives", OldName & "natives" & "_temp")
- My.Computer.FileSystem.RenameDirectory(NewPath & OldName & "-natives" & "_temp", NewName & "-natives")
+ My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives", $"{OldName}natives_temp")
+ My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives_temp", $"{NewName}-natives")
Else
- DeleteDirectory(NewPath & NewName & "-natives")
- My.Computer.FileSystem.RenameDirectory(NewPath & OldName & "-natives", NewName & "-natives")
+ DeleteDirectory($"{NewPath}{NewName}-natives")
+ My.Computer.FileSystem.RenameDirectory($"{NewPath}{OldName}-natives", $"{NewName}-natives")
End If
End If
- If File.Exists(NewPath & OldName & ".jar") Then
+ If File.Exists($"{NewPath}{OldName}.jar") Then
If IsCaseChangedOnly Then
- My.Computer.FileSystem.RenameFile(NewPath & OldName & ".jar", OldName & "_temp" & ".jar")
- My.Computer.FileSystem.RenameFile(NewPath & OldName & "_temp" & ".jar", NewName & ".jar")
+ My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}.jar", $"{OldName}_temp.jar")
+ My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}_temp.jar", $"{NewName}.jar")
Else
- File.Delete(NewPath & NewName & ".jar")
- My.Computer.FileSystem.RenameFile(NewPath & OldName & ".jar", NewName & ".jar")
- End If
- End If
- If File.Exists(NewPath & OldName & ".json") Then
- If IsCaseChangedOnly Then
- My.Computer.FileSystem.RenameFile(NewPath & OldName & ".json", OldName & "_temp" & ".json")
- My.Computer.FileSystem.RenameFile(NewPath & OldName & "_temp" & ".json", NewName & ".json")
- Else
- File.Delete(NewPath & NewName & ".json")
- My.Computer.FileSystem.RenameFile(NewPath & OldName & ".json", NewName & ".json")
+ File.Delete($"{NewPath}{NewName}.jar")
+ My.Computer.FileSystem.RenameFile($"{NewPath}{OldName}.jar", $"{NewName}.jar")
End If
End If
'替换版本设置文件中的路径
@@ -166,15 +160,13 @@
If ReadIni(PathMcFolder & "PCL.ini", "Version") = OldName Then
WriteIni(PathMcFolder & "PCL.ini", "Version", NewName)
End If
- '更改版本 Json
- If File.Exists(NewPath & NewName & ".json") Then
- Try
- JsonObject("id") = NewName
- WriteFile(NewPath & NewName & ".json", JsonObject.ToString)
- Catch ex As Exception
- Log(ex, "重命名版本 json 失败")
- End Try
- End If
+ '写入版本 Json
+ Try
+ JsonObject("id") = NewName
+ WriteFile(NewPath & NewName & ".json", JsonObject.ToString)
+ Catch ex As Exception
+ Log(ex, "重命名版本 json 失败")
+ End Try
'刷新与提示
Hint("重命名成功!", HintType.Finish)
PageVersionLeft.Version = New McVersion(NewName).Load()
@@ -192,7 +184,7 @@
'选择 自定义 时修改图片
Try
If ComboDisplayLogo.SelectedItem Is ItemDisplayLogoCustom Then
- Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpg;*.gif;*.jpeg)|*.png;*.jpg;*.gif;*.jpeg", "选择图片")
+ Dim FileName As String = SelectFile("常用图片文件(*.png;*.jpeg;*.jpg;*.gif;*.webp)|*.png;*.jpeg;*.jpg;*.gif;*.webp", "选择图片")
If FileName = "" Then
Reload() '还原选项
Return
diff --git a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
index dc55cae..a933ed1 100644
--- a/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
+++ b/Plain Craft Launcher 2/Pages/PageVersion/PageVersionSetup.xaml
@@ -209,10 +209,10 @@
-
-
+
diff --git a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
index aa02536..999063b 100644
--- a/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
+++ b/Plain Craft Launcher 2/Plain Craft Launcher 2.vbproj
@@ -140,6 +140,21 @@
false
+
+ False
+ Resources\CacheCow.Client.dll
+ False
+
+
+ False
+ Resources\CacheCow.Client.FileStore.dll
+ False
+
+
+ False
+ Resources\CacheCow.Common.dll
+ False
+
False
Resources\Imazen.WebP.dll
@@ -149,8 +164,9 @@
False
False
-
- ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll
+
+ False
+ Resources\Newtonsoft.Json.dll
False
@@ -158,13 +174,20 @@
Resources\Ookii.Dialogs.Wpf.dll
False
+
+
+ False
+ Resources\System.Net.Http.Formatting.dll
+ False
+
+
@@ -195,11 +218,14 @@
MyIconTextButton.xaml
-
+
+
+
+
MyLocalModItem.xaml
@@ -233,9 +259,6 @@
-
- Component
-
PageComp.xaml
@@ -499,6 +522,7 @@
FormMain.xaml
Code
+
MSBuild:Compile
Designer
@@ -721,6 +745,7 @@
+
@@ -946,6 +971,7 @@
+
@@ -957,5 +983,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Resources/CacheCow.Client.FileStore.dll b/Plain Craft Launcher 2/Resources/CacheCow.Client.FileStore.dll
new file mode 100644
index 0000000..e0457c9
Binary files /dev/null and b/Plain Craft Launcher 2/Resources/CacheCow.Client.FileStore.dll differ
diff --git a/Plain Craft Launcher 2/Resources/CacheCow.Client.dll b/Plain Craft Launcher 2/Resources/CacheCow.Client.dll
new file mode 100644
index 0000000..33c79be
Binary files /dev/null and b/Plain Craft Launcher 2/Resources/CacheCow.Client.dll differ
diff --git a/Plain Craft Launcher 2/Resources/CacheCow.Common.dll b/Plain Craft Launcher 2/Resources/CacheCow.Common.dll
new file mode 100644
index 0000000..a47fb9e
Binary files /dev/null and b/Plain Craft Launcher 2/Resources/CacheCow.Common.dll differ
diff --git a/Plain Craft Launcher 2/Resources/Custom.xaml b/Plain Craft Launcher 2/Resources/Custom.xaml
index 28d3524..ae282bc 100644
--- a/Plain Craft Launcher 2/Resources/Custom.xaml
+++ b/Plain Craft Launcher 2/Resources/Custom.xaml
@@ -134,6 +134,10 @@
Text="切换到下载页面" EventType="切换页面" EventData="1|1"/>
+
+
diff --git a/Plain Craft Launcher 2/Resources/Help.zip b/Plain Craft Launcher 2/Resources/Help.zip
index 54d1dd0..8936ebe 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 d5d9eac..88ddec9 100644
--- a/Plain Craft Launcher 2/Resources/ModData.txt
+++ b/Plain Craft Launcher 2/Resources/ModData.txt
@@ -19,7 +19,7 @@ modular-forcefield-system|模块化力场系统*
minechem-recompile|化学 (MineChem) - 非官方重编译版¨73373|化学 (MineChem) - 已失效
iron-chests@|更多箱子*¨iron-chests-for-fabric@cyberanner-ironchest|更多箱子 (Iron Chests)¨iron-chests-fabric@|更多箱子 (Iron Chests)
balkons-weaponmod|更多武器 (Balkon's WeaponMod)
-advanced-machines|高级机械 (AdvancedMachines)¨@advanced-machines-uuum|高级机械 (AdvancedMachines) - Immibis版
+advanced-machines|高级机械 (AdvancedMachines) - Chocohead版¨@advanced-machines-uuum|高级机械 (AdvancedMachines) - Immibis版
advanced-solar-panels|高级太阳能*
nuclear-control-2|核电控制2*
@@ -121,7 +121,7 @@ blockphysics_by_id_miner|方块物理学 (BlockPhysics)
remain-in-motion|运动红石机构 (Remain in Motion)
-modular-powersuits|模块化动力套装*
+modular-powersuits@modularpowersuits|模块化动力套装*
@@ -189,7 +189,7 @@ mekanism@|通用机械*
magic-crusade-rpg-mod|魔法远征:魔法十字军 (Magic Crusade)
invasions|入侵 (Invasion)
-mb-battlegear-2|挖矿与砍杀:双持与战备2 (Mine & Blade: Battlegear 2)
+mb-battlegear-2|挖矿与砍杀:双持与战备 2 (Mine & Blade: Battlegear 2)
galacticraft-legacy@|星系 (GalactiCraft)
@@ -490,7 +490,7 @@ sim-u-kraft-old|模拟城市 (Sim-U-Kraft)
superheroes-unlimited|超级英雄无限*
oceancraft-mod|海洋世界 (Oceancraft)
-
+more-enchantments-legacy|更多附魔 (More Enchantments)
witching-gadgets|巫师宝具*
buildcraft-compat@|建筑兼容*
@@ -1006,7 +1006,7 @@ roost|鸡窝*
croparia@|矿石作物/魔种之咏*
simple-corn|简单玉米*
thaumic-jei@
-tinkers-steelworks|匠魂炼钢厂 (Tinkers' Steelworks)
+tinkers-steelworks|匠魂炼钢厂 (Tinkers' Steelworks)¨tinkers-steelworks-fork|匠魂炼钢厂 (Tinkers' Steelworks)
unicode-font-extension|Unicode 字体扩展*
placeable-items|可放置物品*
compendium|工匠防御 (Tinkers' Compendium (Tinkers' Defense))
@@ -1187,7 +1187,7 @@ minecraft-transport-simulator@immersive-vehicles|沉浸车辆 (Immersive Vehicle
chest-transporter|搬箱器*
i18nupdatemod@|自动汉化更新 (I18nUpdateMod)
realistic-item-drops|真实物品掉落*
-pixelmon@|像素精灵宝可梦 重铸*
+pixelmon@|像素宝可梦 重铸 (Pixelmon Reforged)
controlling@|键位冲突显示*¨controlling-for-fabric|键位冲突显示 (Controlling)
@@ -1250,7 +1250,7 @@ career-bees|职业蜜蜂*
level-up|升级!重置 (Level Up!Reloaded)
level-up-legacy|升级!前版 (Level Up!Legacy)
moar-boats|模块化船*
-numina|模块化动力装甲前置*
+numina@|模块化动力装甲前置*
tinkers-complement@|匠魂补充 (Tinkers' Complement)
everlastingabilities@everlasting-abilities|永恒能力 (Everlasting Abilities)
abyssalcraft-heads|深渊国度头颅 (AbyssalCraft Heads)
@@ -1274,7 +1274,7 @@ just-enough-pattern-banners
primitive-mobs|远古生物 / 原始生物*
realistic-cobwebs@
simpleharvest|简单收获 (Simple Harvest)
-mrcrayfishs-device-mod|MrCrayfish的设备 (MrCrayfish's Device Mod)
+mrcrayfishs-device-mod|MrCrayfish 的设备 (MrCrayfish's Device Mod)
block-armor|方块盔甲*
stockpile|储物桶*
loot-slash-conquer
@@ -1378,7 +1378,7 @@ arcade-mod|街机*
asmodeuscore@
building-gadgets|建筑小帮手*
dooglamoo-worlds|Dooglamoo的世界*
-scp-lockdown|SCP基金会:封锁 (SCP: Lockdown)
+scp-lockdown@|SCP基金会:封锁 (SCP: Lockdown)
middle-earth-thaumaturgy|中土神秘学 (Middle-Earth Thaumaturgy)
structpro-mod-fast-schematic-spawning-system|自然神殿 (Structpro)
betterfps|更好的FPS (BetterFPS)
@@ -1525,7 +1525,7 @@ better-questing-standard-expansion|更好的任务-基础扩展 (Better Questing
exchangers|方块交换器*
flexible-tools|灵活的工具*
dimensional-control|维度控制*
-valkyrien-warfare@valkyrien-skies|瓦尔基里天空/瓦尔基里战争 (Valkyrien Skies/Valkyrien Warfare)
+valkyrien-skies@|瓦尔基里天空*
item-scroller@|物品滚轮*
better-advancements@|更好的进度*
lucraft-core
@@ -2165,7 +2165,7 @@ betterachievements|更好的成就 (Better Achievements)
extra-bows|更多的弓*
forgotten-relics|失落遗物学*
ore-core@|矿石核心*
-ordinary-coins|普通硬币 (Ordinary Coin)
+ordinary-coins|普通硬币*
peanut-mod|花生! (Peanuts!)
in-game-wiki-mod
@@ -2233,7 +2233,7 @@ citadel@
screenshot-browser|内置截图管理器*
-curio-of-undying@charm-of-undying|不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying)¨curio-of-undying-fabric|不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying)
+curio-of-undying@charm-of-undying|不死图腾插槽/不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying)¨curio-of-undying-fabric|不死图腾插槽/不死图腾槽位 (Curio of Undying / Trinket of Undying / Charm of Undying)
explorercraft-worldexpansion|探险者 (Explorercraft)
enigmatic-legacy@|神秘遗物*
@@ -2246,7 +2246,7 @@ the-rc-mod
rubybyimxiaoanag|红宝石 (Ruby)
jojo-hamon-era@|JoJo-波纹时代 (JoJo-Hamon Era)
-OpenSecurity|开放式防卫*
+OpenSecurity|开放式安全*
the-legend-era|传说纪元*
level-up-hp-forge|生命提升 (Level Up HP)¨level-up-hp|生命提升 (Level Up HP)
more-planet-extras
@@ -2442,7 +2442,7 @@ not-enough-crashes@notenoughcrashes|崩溃优化*¨not-enough-crashes-forge|崩
crash-to-main-menu|崩溃返回主菜单*
tinkers-jei
-better-questing-quest-book|更好的任务-任务书 (Better Questing - Quest Book)
+better-questing-quest-book@|更好的任务-任务书 (Better Questing - Quest Book)
engineers-doors|工程师的门 (Engineer's Doors)
tps-generator|TPS发电机 (TPS Generator)
better-drowning@|更好的溺水*
@@ -2491,7 +2491,7 @@ beesourceful
middle-earth-industry|中洲工业 (Middle-Earth Industry)
thut-wearables
after-the-drizzle|细雨田园 (After the Drizzle)
-cmdcam|CMD 指令摄影机 (CMDCam)
+cmdcam@|CMD 指令摄影机 (CMDCam)
climate-control-geographicraft@geographicraft|气候控制 / 地理世界 (Climate Control / Geographicraft)
cosmetic-armor-reworked|时装盔甲重置版*
@@ -2743,7 +2743,7 @@ vials|瓶瓶罐罐*
advanced-lightsabers|高级光剑*
fall_attack|下落击杀 (fall_attack)
swimsuit|泳装 (swimsuit)
-more-overlays-updated
+more-overlays-updated@
dothack-hack-weapons
immersive-energy|沉浸能源*
crimson-moon|绯红之月*
@@ -3262,7 +3262,7 @@ devtech|GTCEu-CrT开发套件*
-jurassic-world-reborn-mod|侏罗纪世界重生*
+jurassic-world-reborn-mod|侏罗纪重生 (Jurassic Reborn)
cardboard@
enchanting-with-thaumcraft|神秘附魔学*
@@ -3920,7 +3920,7 @@ the-elemelon-mod|元素西瓜*
dcsp|钻石压缩 (Diamonds Compressor SP)
corpse-complex@
modularvoicechat|模块化语音聊天 (ModularVoiceChat)
-elytra-slot@|鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot)¨elytra-slot-fabric|鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot)
+elytra-slot@|鞘翅插槽/鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot)¨elytra-slot-fabric|鞘翅插槽/鞘翅槽位 (Curious Elytra / Elytra Trinket / Elytra Slot)
tconstructcompatible|匠魂兼容 (TConstructCompatible)
@@ -3955,7 +3955,7 @@ satako-library¨satako-fabric
unique-enchantments-utils|独特的附魔:效用扩展 (Unique Enchantments - Utils)
forge-ae2-additional-opportunity@ae2ao¨ae2ao¨legacy-ae2-additional-opportunity
iconexporter@icon-exporter
-curious-shulker-boxes@shulker-box-slot|潜影盒槽位 (Shulker Box Slot)¨curious-shulker-boxes-fabric|潜影盒槽位 (Shulker Box Slot)
+curious-shulker-boxes@shulker-box-slot|潜影盒插槽/潜影盒槽位 (Shulker Box Slot)¨curious-shulker-boxes-fabric|潜影盒插槽/潜影盒槽位 (Shulker Box Slot)
xenoclus-2
darker-depths|深岩之下*
terracraft-journey
@@ -4136,7 +4136,7 @@ falling-leaves-forge@fallingleavesforge|落叶 (Falling Leaves (NeoForge/Forge))
custom-entity-models-cem@cem|自定义实体模型 (Custom Entity Models)
-tweakmyclient
+tweakmyclient@
lovely-snails@lovely_snails|可爱蜗牛*
clipboard|备忘录*
@@ -4288,7 +4288,7 @@ reroll|刷新附魔*
tax-free-levels@|免税附魔*
portable-dimensions|便携式传送门*
stone-age-by-yanny
-terra-world-generator|Terra - 已经失效¨@terra
+terra-world-generator|Terra - 已失效¨@terra
omnis|灾厄村民拓展*
omnis-backpacks
@@ -4472,7 +4472,7 @@ travelers-titles@|旅人标题 (Traveler's Titles)¨travelers-titles-fabric|旅
expand-chinese-sword|中式剑拓展 (Arsenal Integration)
littleframes@littlepictureframes|小画 (LittlePictureFrames)
-lavalogged-blocks|岩浆充填 (Lavalogged blocks)
+lavalogged-blocks@|岩浆充填 (Lavalogged blocks)
awoken-world
gt4-reimagined|格雷科技4:重构想 (GregTech 4 Reimagined)
camouflaged-creepers
@@ -4925,7 +4925,7 @@ illagers@illagers+|更多的灾厄村民 (Illagers+)
villagers-follow-emeralds-fabric@|村民跟随绿宝石 (Villagers follow Emeralds)
-diabloloot|暗黑破坏神战利品 (DiabloLoot)
+diabloloot|暗黑破坏神战利品 (DiabloLoot - Bosses Update)
yggdrasil|世界树*
ftb-dripper-forge
forge-snad|子沙1.14+ (Snad)¨fabric-snad|子沙1.14+ (Snad)
@@ -4967,7 +4967,7 @@ happiness-is-a-warm-gun@
kvc-blocks|蛋挞君的原版补全方块篇 (KVCBlocks)
enigtech2-util
-environmental-creepers|环保型苦力怕*
+environmental-creepers@|环保型苦力怕*
look-at-that
advanced-peripherals@advancedperipherals|高级外设*
@@ -4986,9 +4986,9 @@ expandability
squirrels|松鼠*
ftb-power-pots
the-graveyard-forge@|墓园 (The Graveyard)¨the-graveyard-fabric@|墓园 (The Graveyard)
-nether-agriculture|下界农业*
+nether-agriculture@netheragriculture|下界农业*
conjuring@
-awesome-dungeon-forge@¨awesome-dungeon-fabric@
+awesome-dungeon-forge@awesome-dungeon¨awesome-dungeon-fabric¨awesome-dungeon-neoforge
out-of-sight@
decorative-blocks-modded-compat|装饰方块模组兼容 (Decorative Blocks modded compat)
camp-chair
@@ -5283,7 +5283,7 @@ ore-prospecting-stick-forge|探矿之戒 (Rings Of Prospecting)¨ore-prospecting
worldedit-express-forge¨worldedit-express-fabric
awesome-dungeon-edition-ocean-forge@awesome-dungeon-edition-ocean¨awesome-dungeon-edition-ocean-fabric
pistorder
-escape-rope-pokemon-item-forge¨awesome-dungeon-neoforge
+escape-rope-pokemon-item-forge
awesome-flooring-forge@¨awesome-flooring-fabric@
medievial-entity-forge@medieval-entity-forge|中世纪生物 (Medieval Entity)¨medievial-entity-fabric@medieval-entity-fabric|中世纪生物 (Medieval Entity)
upgraded-shulkers|更多潜影盒*
@@ -5292,7 +5292,7 @@ bobby@|服务器区块缓存*
boosted-brightness@|亮度增强*
effective@¨effective-forge
-voidtotem|虚空图腾 (Void Totem)¨voidtotem-fabric|虚空图腾 (Void Totem)
+voidtotem@|虚空图腾 (Void Totem)¨voidtotem-fabric|虚空图腾 (Void Totem)
tool-leveling-plus@tool-leveling|工具升级 (Tool Leveling+)¨tool-leveling-plus-fabric|工具升级 (Tool Leveling+)
mythicmetals@|神话金属 (Mythic Metals)
creeper-firework@|烟花苦力怕*
@@ -5635,7 +5635,7 @@ just-enough-piglin-bartering|JEI猪灵以物易物*
@aftersans53228-fabric-road|暗访谈的Fabric道路 (aftersans53228's fabric roads)
primal-magick@|原初魔法*
-ore-creeper|矿石苦力怕*
+ore-creeper@|矿石苦力怕*
panthalassa|泛古洋*
stardew-armory|星露谷武器*¨stardew-armory-fabric|星露谷武器 (Stardew Armory)
no-more-nether-desert-edition
@@ -5693,13 +5693,13 @@ storage-drawers-crafting-recipe
enhanced-attack-indicator@|增强攻击提示器*
parasites-addon-overlast|寄生兽:末世扩展 (OverLast)
cancel-block-update@|取消方块更新*
-franciscofunari|中世纪工艺 (Medieval Craft)
+franciscofunari@medieval-craft-(structures)|中世纪工艺 (Medieval Craft)
d3core
green-thumb-v1-2-03
greenthumb
tfc-adventure|群峦冒险 (TFC+ Adventure)
just-zoom-forge@just-zoom
-@holograms|全息图 (Holograms)
+server-holograms@holograms|全息图 (Holograms)
eidolons-hexblades|幻梦咒剑 (Eidolon's Hexblades)
monke-madness
@@ -5770,7 +5770,7 @@ zettai-psi
psicaster
tweakermore@
legacy-display@
-the-backrooms|后室*
+the-backrooms
creatures-of-the-snow@creatures-from-the-snow-fabric|雪地里的生物! (Creatures From The Snow!)¨creatures-from-the-snow-forge|雪地里的生物! (Creatures From The Snow!)
collisions-lib@
wooled-boots@|羊毛靴子*
@@ -5827,10 +5827,10 @@ realistic-block-physics@
domestication-innovation@|驯养革新*
snuffles@|魄罗*
flan-forge@flan¨flan
-awesome-dungeon-the-end-forge@¨awesome-dungeon-the-end-fabric@
+awesome-dungeon-the-end-forge@awesome-dungeon-the-end¨awesome-dungeon-the-end-fabric
tehnuts-torcherino
library-ferret-forge@library-ferret¨library-ferret-fabric¨library-ferret-neoforge
-awesome-dungeon-nether-forge@¨awesome-dungeon-nether-fabric@
+awesome-dungeon-nether-forge@awesome-dungeon-nether¨awesome-dungeon-nether-fabric
ae2-extended-life|应用能源非官方延续版 (AE2 Unofficial Extended Life)
refined-cooking@|精致厨房*
@@ -5996,7 +5996,7 @@ oedldoedl-resources
oedldoedl-gear
disguiseheads@
simple-tcon
-enchanted-tooltips
+enchanted-tooltips@
stackablepotions-forge¨stackablepotions
mobifier¨mobifier-fabric
art-of-forging-a-tetra-addon|锻造之艺 (Art of Forging)
@@ -6084,7 +6084,7 @@ onastick-forge@onastick¨onastick
tradernpcs
more-pads|更多Pad*
texture-dump
-spartan-weaponry-ice-and-fire|斯巴达的武器:冰火传说 (Spartan Weaponry: Ice and Fire)
+spartan-weaponry-ice-and-fire@|斯巴达的武器:冰火传说 (Spartan Weaponry: Ice and Fire)
hefty-crops@
@strangefuelsmod|奇奇怪怪的燃料 (Strange Fuels Mod)
custom-player-models@|自定义玩家模型 (Customizable Player Models)¨custom-player-models-fabric|自定义玩家模型 (Customizable Player Models)
@@ -6482,7 +6482,7 @@ ninjaxxs-needs
pixelmon-gui-blocks
more-mekanism-processing@
-item-banning
+item-banning@
probejs@
impaled@|更多三叉戟 (Impaled🔱)
laserio
@@ -6507,7 +6507,7 @@ advancements-enlarger
better-muffling|更好的消音器*
lightest-lamps@¨lightest-lamps-fabric
bigbuckets|大铁桶 (Big Buckets)¨bigbuckets-fabric|大铁桶 (Big Buckets)
-dimstorage
+dimstorage@
spawner-imbuer@
unnamed-delight|无名乐事*
@@ -6858,7 +6858,7 @@ the-nexus-deep-dark-dimension
jepp|JEI绘画预览 (Just Enough Painting Previews)
create-curios
-ctm-refabricated
+ctm-refabricated¨@ctm-refabricated
placeable-pumpkin-pie|可放置南瓜派*
birch-forest-upgrade|白桦林更新*
ok-zoomer-forge
@@ -7011,7 +7011,7 @@ withcoffee|咖啡相伴/天堂之路 (WithCoffee)
emeraldstuff
grass-slabs-carpets-stairs
not-mutually-exclusive|不冲突*¨@not-mutually-exclusive|不冲突 (Not Mutually Exclusive) - 已失效
-builders-delight-forge
+builders-delight-forge@builders-delight
miners-minerals@
days-to-mine@7-days-to-mine|七日杀 (7 Days to Mine)
pieces-of-ore
@@ -7082,7 +7082,7 @@ auto-workstations-fabric@auto-workstations|自动化工作站 (Auto Workstations
l2-backpack@l2backpack|莱特兰-背包*
@mbp
-capybara-fabric¨unnamed-animal-mod-capybara-only
+capybara-uam¨unnamed-animal-mod-capybara-only
just-enough-petroleum
diamethysts@diamethysts!
twigs@
@@ -7713,7 +7713,7 @@ odysseymod
libipn@
hulsealib
blues-dynamic-lights
-gtc-expansion
+gtc-expansion@
nether-depths-upgrade@|炽海生机*
cloud-factory
disc-holders-dj
@@ -7901,7 +7901,7 @@ your-items-are-safe@¨your-items-are-safe-fabric
techemistry
the-missing-villages
continents
-yungs-better-ocean-monuments@|YUNG的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-fabric|YUNG的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-neoforge|YUNG的海底神殿优化 (YUNG's Better Ocean Monuments)
+yungs-better-ocean-monuments@|YUNG 的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-fabric|YUNG 的海底神殿优化 (YUNG's Better Ocean Monuments)¨yungs-better-ocean-monuments-neoforge|YUNG 的海底神殿优化 (YUNG's Better Ocean Monuments)
echoing-depths
advanced-finders
@@ -7968,13 +7968,13 @@ status-effect-timer@statuseffecttimer
logical-zoom@
the-space-age-mod|太空时代*
-touhou-origins@|起源:东方project附加包*
+touhou-origins@|起源:东方 Project 附加包*
earth-mobs@earthmobs|地球生物*
simpleores-tin
just-enough-keys¨just-enough-keys-fabric
why-am-i-on-fire@
-bouncing-balls-api@
+bouncing-balls-api@|弹跳球 API (Bouncing Balls API)
bouncing-balls@|弹跳球*
teletubbies-mod|天线宝宝*
almost-unified@
@@ -7985,15 +7985,15 @@ time-arrow
catch-me-if-you-can-cmiyc
mc-modsis-one-way-glass|Alli的单向玻璃 (Alli's One Way Glass)
sanguis
-ender-trigon
+ender-trigon@
litlaunch
puzzle@
-
+why-am-i-on-fire-refoxed
@tdfp|暮色乐事:冰雪豪饮 (Twilight Delight: Frozen Pride)
ftb-pack-companion
teams-stages
-kiwi-boi
+kiwi-boi@
rubidium-toolkit@|铷 · 工具箱*
advanced-combat|高级战斗装备*
advanced-combat-revitalized@|高级战斗装备重置 (Advanced Combat (Revitalized))
@@ -8033,7 +8033,7 @@ better-stats@|更好的统计信息界面 (Better Statistics Screen)
abyssalblocks|深渊国度幸运方块 (AbyssalBlocks)
-archon
+archon@
stackie
@cullclouds
@superfancyclouds
@@ -8050,14 +8050,14 @@ berdinskiybears-armor-hud
tiered-tooltips
rgb-blocks-forge|调色方块 (RGB Blocks)
-quicksand-fabric|流沙 (Quicksand)
-better-durability|更好的耐久度*
+quicksand-fabric@quicksand|流沙 (Quicksand)
+better-durability@|更好的耐久度*
passthrough-signs
excorrelation@
packet-size-doubler
@packet-size-double-forge
arachnophobia-mode-for-minecraft@arachnophobia-mode
-villagerconfig@|村民配置 (VillagerConfig)
+villagerconfig@|村民配置 (Villager Config)
jasm@speedometer
paginated-advancements@paginatedadvancements|分页进度 (Paginated Advancements & Custom Frames)
@backport-gamerules
@@ -8070,7 +8070,7 @@ ingame-info-xml-addon-serene-seasons
staff-of-building|建筑之杖 (Staff of Building)
-dark-mode-everywhere|夜间模式无处不在*
+dark-mode-everywhere@|夜间模式无处不在*
custom-shop|自定义商店 (CustomShop)
another-liquid-milk@
dark-mode-everywhere-fabric@|夜间模式无处不在Fabric版 (Dark Mode Everywhere - Fabric)
@@ -8820,7 +8820,7 @@ glacier-ice
gtnhmixins@
dawn-to-dusk
open-tower-defence@|开放式塔防*
-mtr-station-decoration@station-decoration|MTR车站装饰 (MTR Station Decoration)
+mtr-station-decoration@station-decoration|MTR车站装饰 (Station Decoration)
perviaminvenire
silverbirch@silver-birch
@@ -8841,7 +8841,7 @@ extended-slabs-plus
brewin-and-chewin-fabric@|饮酒作乐 Fabric 版 (Brewin' And Chewin' [Fabric])
particular
longer-login-times
-broglis-plants
+broglis-plants@
rlmixins@
simplehats@simple-hats|简单帽子 (Simple Hats)
timeoutout-fabric@timeoutout
@@ -8855,7 +8855,7 @@ create-industrial-chemistry@|机械动力:工业化学 (Create: Industrial Che
cruelars-triforcemod
@morespawneggs|更多刷怪蛋 (MoreSpawnEggs)
-beautify-refabricated@|美化!Fabric版 (Beautify: Refabricated)
+beautify-refabricated@|美化!重织 (Beautify: Refabricated)
manatweaks
chicken-nugget
@@ -8908,7 +8908,7 @@ clear-skies-forge-port
spell-power@
morecrashinfo|更多崩溃报告信息 (MoreCrashInfo)
-epicfight-saopack@epicacg|史诗战斗:ACG附属包 (EpicFight ACG AddonPack)
+epicfight-saopack|史诗战斗:ACG附属包 (EpicFight ACG AddonPack)
screenshot-viewer@|截图查看器*
gml@
@futurepack-additions|飞向未来附加 (Futurepack Additions)
@@ -9981,13 +9981,13 @@ tskimi-seirans-fruit-wine@tskimiseirans-fruit-wine|月见清兰的果酒 (Tskimi
graduated-cylinders@|刻度量筒*
fps-monitor
-stitch@
+athena@athena-mod
calm-mornings@
fog-looks-good-now@
guarding@
cats-expanded@
modular-bosses
-experience-obelisk@
+experience-obelisk@cognition
here-be-no-dragons
gnumus-settlement
fading-night-vision
@@ -10003,7 +10003,7 @@ mannequins
simple-mango@
betterdrawhoveringtext|更好的工具信息显示 (betterDrawHoveringText)
-badges-by-harmex
+badges-by-harmex@cobblebadges2
terrarian-arsenal
zombie-veterinarian|僵尸科兽医*
just-enough-sculk-sensor
@@ -10453,7 +10453,7 @@ easydropper|轻松投掷 (Easy Dropper)
varied-adventure
kubejs-delight
block-physics-overhaul
-searchlight-forge|探照灯 (Searchlight(& Wall Lights))¨searchlight|探照灯 (Searchlight(& Wall Lights))
+searchlight-forge|探照灯 (Searchlight (& Wall Lights))¨searchlight|探照灯 (Searchlight (& Wall Lights))
ancient-manuscripts@
gazebos@
scoreboard-get-time|记分板获取时间 (scoreboard get time)
@@ -10705,7 +10705,7 @@ enhancedsnowman
new-world-height-and-depth
@inertiaanticheat
-scroll-of-harvests
+scroll-of-harvests@
@scripts-chunk-loaders
clean-tooltips@
@@ -10893,7 +10893,7 @@ do-api@
grabby-mobs@
pandas-falling-trees@
iron-ender-chests@
-delightful-creators-fabric@|动力乐事 (Delightful Creators)
+delightful-creators-forge@delightful-creators|动力乐事 (Delightful Creators)¨delightful-creators-fabric|动力乐事 (Delightful Creators)
deathbutthree@death-but-three|逝不过三 (Death But Three)
comparties|组队兼容*
@@ -10906,7 +10906,7 @@ light-the-night
tweakerge@
terrafirmaships@
better-ocelots
-actually-useful-smithing-table
+actually-useful-smithing-table@
denyblocks
sculk-horn@-sculk-horn
ydms-red-panda@
@@ -11052,7 +11052,7 @@ toomuchrain@
thirst-was-remade|口渴-重制*¨@thirst-was-remade|口渴-重制 (Thirst Was Remade) - 已失效
ies-tweaks-fabric@
brown-mooshrooms
-@whetstones
+whetstones@
@better-log4j-config
@harvest
wooden-hoppers
@@ -11168,7 +11168,7 @@ shulker-meat
accurate-block-placement-reborn@
favorite-items
-@smart-villagers-follow-emeralds|见钱眼开 (Smart Villagers Follow Emeralds)
+smart-villagers-follow-emeralds@|见钱眼开*
@villager-pickup
step-it-up@stepitup
@@ -11307,7 +11307,7 @@ jagms-kiwis@
improved-trident@
mystic-bows
forgiving-world
-@skillcloaks
+skill-cloaks@skillcloaks
swordinthestone@
lazy-ae2-patch|懒人AE2补丁 (Lazy AE2 Patch)
@@ -11322,7 +11322,7 @@ rei-custom-command|REI自定义命令 (REI Custom Command)
copperative@|铜心协力*
growable-storage-cells
growable-cells-2
-cinchcraft
+cinchcraft@
day-counter@ag-day-counter
makecold|冷境 (MakeCold)
@@ -11362,9 +11362,9 @@ cute-kiwi-birds-new-animal
moremobheads@
ouchies@
@traditional-asphalt
-@thaumic_boots
+thaumic-boots@thaumic_boots
carpeted-stairs@
-just-enough-archaeology
+just-enough-archaeology@
bedrock_world
world-preview@|世界预览*
@@ -11426,7 +11426,7 @@ gigeresque
lookaround
projectileimmunityfix
@betterzoom
-@player-visibility
+player-visibility@
bag-of-holding-forge@bag-of-holding
@@ -11645,7 +11645,7 @@ simply-skills@
cctv-craft
-redeco@
+redeco@|重铸家具 (Re:Deco)
pocket-storage
animated-player-mod|动画化玩家*
its-thirst@
@@ -12254,7 +12254,7 @@ more-crafting-tables-mct@more-crafting-tables-lieonlion
@cobblemon-quests
akkamaddis-classical-alchemy|Classical Alchemy - 新
@big-pony|彩虹大马 (Big Pony)
-
+qraftyfied
@theft
akkamaddis-golden-glitter@
@obsidianbucket
@@ -12331,7 +12331,7 @@ advancement-based-spawning@
heroic-armory-legacy
yungs-menu-tweaks@|YUNG的菜单调整 (YUNG's Menu Tweaks)¨yungs-menu-tweaks-fabric|YUNG的菜单调整 (YUNG's Menu Tweaks)¨yungs-menu-tweaks-neoforge|YUNG的菜单调整 (YUNG's Menu Tweaks)
-entity-distance-1-12-2|实体距离 (Entity Distance)
+entity-distance-1-12-2@entity-distance-1.12.2|实体距离 (Entity Distance)
coolesthardcore@coolest-hardcore
taxcastlepillager
pesky-pirates@
@@ -12412,7 +12412,7 @@ halloween-luckyblocks
spooky-halloween
isle-of-berk-variant-loader@
spooky-arms
-
+qraftys-end-villages
bed-is-not-too-far-away@
@enderman-tweaks
@@ -12542,7 +12542,7 @@ the-monkey-bomb@
@cobblemon-hunt
ancient-animals
beavery@
-
+qraftys-halloween-villages
vemerioraptor
technique-to-kill-thrasher|毙鲨技 (technique to kill thrasher)
the-open-sauce-toast-killer@
@@ -12552,7 +12552,7 @@ better-wake-up-position@|更好的起床位置 (Better Wake-up Position)
railroad-blocks-forge@railroad-blocks¨railroad-blocks
fossilmon@
-opack2reload|资源包惰性重载/抑验怠载 (0Pack2Reload)
+opack2reload|资源包惰性重载/抑验怠载 (0Pack2Reload/OPack2Reload)
wits@
@motleys-music-discs
@@ -12678,7 +12678,7 @@ cheat-command
pumpkinpal
johnnys-biological-notes@johnnys-biological-notes-1.0-beta|Johnny的生物笔记 (Johnny's biological notes)
-@autodrop
+autodrop@
mutation-rerun
@compactspawners
@@ -12936,7 +12936,7 @@ combatify@
go-fish-updated|一起钓鱼吧!(更新版) (Go Fish (updated))
what-are-they-up-to@
-@fumo
+fumofumo|Fumo - 仅 16.0.0+¨@fumo
@elytraplus
mcaic@
@zombification
@@ -12994,7 +12994,7 @@ default-world-generator-ssp
homyatos-explosives
infchest@|无限箱子 (Inf Chest)
@squashed
-unremovableeffects
+unremovableeffects@unremovable-effects
traders-in-disguise-the-illager-assassin-project@
cherished-items
verdantvibes@|绿意盎然 (VerdantVibes)
@@ -13077,7 +13077,7 @@ rpg-parties@
astikor-carts-tfc@tfc-astikor-carts
nanexs-mobs
no-feather-trample@
-embers-extended-life|余烬非官方延续版 (Embers Unofficial Extended Life)
+embers-extended-life@embers-unofficial-extended-life|余烬非官方延续版 (Embers Unofficial Extended Life)
terrafirmajousting@
jujutsu-animation
@@ -13199,12 +13199,12 @@ pescatore@
apocalypse-now@
cenozoic-nightmare-the-horror-of-prehistory
-quickbackupmulti@|多槽位备份 (QuickBackupMulti-Fabric)
+quickbackupmulti@|多槽位备份 (QuickBackupMulti)
slashblade-japanese-addon-pack-reforked|拔刀剑日系附属重置版 (SlashBlade Japanese Addon Pack Reforked)
@create-fabric-fixes
crate-delight-forge@crate-delight|装箱乐事 (Crate Delight)¨crate-delight-fabric|装箱乐事 (Crate Delight)
-cultural-creators-fabric-create-and-cultural
+cultural-creators-forge-create-and-cultural@cultural-crators¨cultural-creators-fabric-create-and-cultural
shuffle@
probejs-legacy
create-destroy@destroy|坍毁化合 (Destroy)
@@ -13256,7 +13256,7 @@ bagus-cosmetic@
floatingitems
ars-ocultas@
tetracelium@
-fugue
+fugue@
create-food@
@@ -13367,7 +13367,7 @@ magical-jewelry
epicsoulsbosses
epic-puss-in-boots
soul-like-npc
-epic-empires@
+epic-empires¨@epic-empires|Epic Empires - 已失效
@@ -13530,7 +13530,7 @@ timeless-heroes@superhero
sky-origins@sky
glitchcore@
light-level-overlay-reloaded
-eirin-yagokoros-pharmacy@eirin-yagokoro-pharmacy|八意永琳的药房 (Eirin Yagokoro's Pharmacy)
+eirin-yagokoros-pharmacy@eirin-yagokoro-pharmacy|蓬莱药局 (Eirin Yagokoro's Pharmacy)
tfc-ore-excavation
tac-interact-key|TAC交互优化 (TAC Interact Key)
@@ -13687,7 +13687,7 @@ missions@
votemission@
immersive-vehicles-automobilwerke-schwikau-veb
cheesemaking
-cardiac
+cardiac@
materialtweaker@
whirlwind@
warden-tools@
@@ -13888,7 +13888,7 @@ ad-astra-extra-additions@
infinite-fluid-bucket@|无限流体桶*
@voxy
-vmod|VMod - 已失效¨@vmod
+vmod@
minestuck
star-wars-planets-ad-astra@star-wars-planets
@@ -13900,7 +13900,7 @@ cat_jam@
remastered-structure
spacore
fancyvideo-api
-goatman-jar-v2-dweller
+goatman-jar-v2-dweller@the-goatman-dwller
mineral-overhaul-shellstone-tuff
clear-gpu-cache@|清除GPU缓存*
@@ -13958,7 +13958,7 @@ ash-of-sin-custom-entity-item|罪业余烬:自定义实体装备 (Ash Of Sin:
ash-of-sin-eternal-entity|罪业余烬:永恒实体 (Ash Of Sin: Eternal Entity)
fundamental@
-wendigo
+wendigo-jar-dweller
the-super-god-road@|超神之路 (TheSuperGodRoad)
@backrooms
@@ -14042,7 +14042,7 @@ melee-ex-machina-meexma@melee-ex-machina
arc@
cores@
-@citymod|我的城市 (City Mod)
+@citymod|我的城市:交通与生活 (CityMod:Traffic and life)
too-hard-to-dig@|隐岩砥凿 (Too Hard to Dig)
simplecobblestonegenerator@simple-cobble-gen
natural-baby-animals
@@ -14138,7 +14138,7 @@ mace-backport
@steve-eat-stew
imaginary
kuvalich|赤毒玄骸 (KuvaLich)
-ae2-import-export-card@
+ae2-import-export-card@|AE2输入输出卡 (AE2 Import Export Card)
@magick-experience
@repairable-disc-11
@@ -14255,7 +14255,7 @@ simple-dimensions|简单维度*
portals-gui@|传送门GUI*
interworlds-splashscreens@|进入维度加载界面*
@creative-menu-tweaks
-vampires-delight@
+vampires-delight@|血族乐事 (Vampire's Delight)
thats-my-bed@
caligo@
@sharpenedswords
@@ -14264,7 +14264,7 @@ donut-and-mochi
ducky@ducky!
lost-idols
simple-trashcan@
-darwin-water-ambience
+darwin-water-ambience@
decorative-wooden-lattices
apotheotic-additions
lithosphere
@@ -14687,8 +14687,8 @@ bananarangs@
remorphed@
particle-core@
fermiumbooter@
-ytech
-@bbrb
+ytech@
+better-biome-reblend@bbrb
mcterra@
siren-head-the-arrival
classic-musical-discs@
@@ -14740,7 +14740,7 @@ seed-delight@|种子乐事*
transmog@|幻化*
nuclear-guitars
cubic-racers
-
+lotr-ucp|魔戒非官方兼容性补丁 (LOTR UCP)
delightful-sandwiches@
shader-fixer@|光影修复 (Shader fixer)
resiever@
@@ -14842,7 +14842,7 @@ dwarven-realm|矮人诸国*
renewed-eras-of-arda-addon|阿尔达纪元:复兴 (Eras of Arda: Renewed Addon (First and Second Age))
legendary-item@|魔戒传说物品*
lotr-middleearth-tweaks|中洲微调 (MiddleEarth Tweaks)
-thaumcraft-integration
+thaumcraft-integration@
reach-display@
gravitation-suite-patcher
anti-rat-dungeon
@@ -14916,7 +14916,7 @@ frostbytes-improved-inventory@
mekanism-extras@mekanism_extra|通用机械:扩展*
leaf-beds@
unidentified-enchantments@unidentified_enchantments
-protection-pixel@|机素防护*
+protection-pixel|机素防护*¨@protection-pixel|机素防护 (Protection Pixel) - 已失效
createdpcompat@
@@ -15313,7 +15313,7 @@ more-affordable-than-ever-never-too-expensive@more-affordable-than-ever
what-am-i
@camoverlay
-autoplant
+autoplant|自动种植 (AutoPlant)
@skip-backup-screen
@polysprint
@crashpatch
@@ -15458,7 +15458,7 @@ lush-cave-trader@
oceans-enhancements@
xp-jellies
aromatic
-onekeyminer-nf@onekeyminer_nf|一键连锁(Neo)Forge (OneKeyMiner(Forge/NeoForge))
+onekeyminer-nf@onekeyminer_nf|一键连锁(Neo)Forge (OneKeyMiner(Neo)Forge)
retargetato@|约谊敌众 (ReTargetato)
iceandfire-ce@|冰火传说社区版 (IceAndFire Community Edition)
@@ -15485,7 +15485,7 @@ hungry-zombies
@gravity-gourds
usa-delight|USA Delight - 已失效¨@usa-delight
experience-farm
-ad-extendra-dandi2k8-version@ad_extendra-continuation
+ad-extendra-dandi2k8-version|Dandi2k8's Ad Extendra - 已失效¨@ad_extendra-continuation
nebulus-juice-trader@
cthulhu-fishing@
large-super-potato|大型超级马铃薯*
@@ -15851,7 +15851,7 @@ nbt-tool
-dglab-x-minecraft@
+dglab-x-minecraft¨@dglab-x-minecraft|Minecraft x DgLab - 已失效
twintails|双马尾 (TwinTails)¨twin-tails|双马尾 (TwinTails)
gt-wireless|格雷无线科技 (GT Wireless)
forcejava21@|强制 Java21 (ForceJava21)
@@ -15946,7 +15946,7 @@ to-tweaks-irons-spells@
horde-moon@
laser-mod-2
-mekanism-lasers
+mekanism-lasers@
wizards-reborn@
tinkervillager|工匠村民 (Tinker Villager)
customnpc-contentback@cnpc-items
@@ -15992,7 +15992,7 @@ refreshdisplaylists
vulkanmod-android-libs
@black-bar-concealer
building-shift@
-mob-ai-tweaks
+mob-ai-tweaks@
the-masquerade-illager-boss@the-masquerade-(illager-boss)
better-armory-x
sweetys-horse-armor-tweaks@
@@ -16163,7 +16163,7 @@ ynet-an-xnet-fork
rosatech
nutrition-gtceu@
nutrition-unofficial-extended-life@|营养学非官方延续版*
-@manametalmod-tweaks|魔金微调 (ManaMetalMod Tweaks)
+manametalmod-tweaks-m3-t@manametalmod-tweaks|魔金微调 (ManaMetalMod Tweaks)
ae2-crafting-tree|合成树 (AE2: Crafting Tree)
client-automatic-doors@client_automatic_doors|客户端自动门*
@@ -16207,7 +16207,7 @@ fibers-guns-pack-for-tacz
beams@
discarnate@
@fishmc
-mob-armor-trims@
+mob-armor-trims
bubbles-a-baubles-fork
arst-motion-blur@
@mpalladium|钯 (Palladium)
@@ -16469,7 +16469,7 @@ water-balloon@
cataclysm-spellbooks@
call-of-drowner@|溺亡者之嚎 (Call of Drowner)
-
+endofday|终焉之日 (Endofdays)
fastfood-delight-unofficial|重启速食/快餐乐事非官方版 (FastFood Delight Unofficial)
faster-fly-block-breaking|飞行速速破*
@@ -16830,7 +16830,7 @@ languagereloadunofficial@
mekanism-covers@
enchanted-wilderness-elegant-countryside@enchanted-wilderness-elegant-countryside.|雅致乡下 (Elegant Countryside)
better-durability-reforged|更好的耐久度:重铸*
-sos|我来助你! (SOS)
+fast-tpa|我来助你! (Fast TPA)
cherry-on-1-12-2@cherry_on_1.12.2|1.12.2的樱花 (Cherry_on_1.12.2)
scape-and-spartan-parasites|寄体斯巴达 (Scape and Spartan: Parasites)
@ripples-of-the-past-extra-stands-addon
@@ -16850,7 +16850,7 @@ rple@
reign-of-nether-rts-in-minecraft@reign-of-nether-rts|下界之治 (Reign of Nether RTS)
the-secret-doors
-
+immersive-vehicles-iv-mts-veb-iav-improved-2000|“改进-2000”
tame-tools@|驯兽工具*
@bia
vounierns-turrets
@@ -16937,7 +16937,7 @@ better-brightness-slider-respawn@
animatica-foxified@
mcchatgpt@
@aimobs
-ambiances@ambiance|氛围! (Ambiance!)
+ambiances@aurae|氛围! (Aurae!)
screw-your-items@
mace-port
bloodlines
@@ -16994,7 +16994,7 @@ rank-system|评分系统*
@creative-one-punch-forge
bundle-inventory|Bundle Inventory - 问题反馈¨@bundle-inventory
scroll-tweaks@scrolltweaks|鼠标滚轮调整*
-neko-server@
+st@server-tools|服务器工具 (Server Tools)
ride-every-thing@|骑乘一切*
cit-resewn-neopatcher@
legendary-creatures|传说生物*
@@ -17019,7 +17019,7 @@ passive-shield@
trap-expansion
roost-2-flying-higher
@amber
-@liteminer
+liteminer@
@lvlz-better-nether
@lvlz-better-end
cc-shops@
@@ -17081,7 +17081,7 @@ iron-nugget-recycling
re-exposer@|Exposer 重置版 (Re-Exposer)
gtmoreoreprocessing@
computercraftedu
-bibliocraft-legacy
+bibliocraft-legacy@
simple-clouds@
@@ -17107,7 +17107,7 @@ creeping-woods
dragon-mounts-2-expanded@dragon-mounts-2|龙骑士2:延续 (DragonMounts2: Expanded)
life-fruits@|生命果*
ati-structures
-fantasy-armor@fantasy_armor
+fantasy-armor@fantasy_armor|幻想铠甲*
beautiful-enchanted-books@beautiful-enchanted-books-mod-edition
@@ -17125,7 +17125,7 @@ paladins-oath
roundabout-the-jojo-mod@roundabout_the_jojo_mod
-enviromine-continuation@
+enviromine-continuation@|更多生存要素:延续 (EnviroMine continuation)
@toffys-hooks
mutants-buff
@@ -17152,7 +17152,7 @@ mutils@
better-campfires@|更好的营火*
@mtr-renfe-addon
gt-community-additions@
-more-create-burners|机械动力:燃烧室附加*
+more-create-burners|机械动力:更多燃烧室*
create-logistics@
create-pneumatic-equipment@
collisiondamage|碰撞伤害 (CollisionDamage)
@@ -17293,7 +17293,7 @@ storage-drawers-unofficial@|储物抽屉非官方版*
centrifuge-tiers-reproduced@
dense-mekanism@
simple-missles@
-galosphere-spellbooks
+galosphere-spellbooks@
discerning-the-eldritch
@hexcassettes
wardens-plus
@@ -17302,7 +17302,7 @@ ender-wyrmlings
new-classic-battle-towers
chitin-equipment@|甲壳装备*
-mowzies-cataclysm|Mowzie的灾变 (Mowzie's Cataclysm)
+mowzies-cataclysm@|Mowzie的灾变 (Mowzie's Cataclysm)
stardew-fishing-fabric@
piglin-slayer@
cataclysm-tools@
@@ -17460,7 +17460,7 @@ book-scroll@
-@qca
+qualia-carpet-addition@qca
mouse-tweaks-unofficial|鼠标手势非官方版*
baubles-lts
elysium-api@
@@ -17470,7 +17470,7 @@ antibat@
illage-and-spell-age
origincore|起源核心*
-
+pet-connect|收容宠物*
heaven-destiny-moment|天命时刻*
industrial-foregoing-extra-upgrades|工业先锋:更多升级 (Industrial Foregoing: Extra Upgrades)
growableores-extension@|甘蔗转换机 (GrowableOres Extension)
@@ -17618,7 +17618,7 @@ nanny@
resource-library@
resource-config-api@
@threatengl
-hybridfix|混合修复 (HybridFix)
+hybridfix@|混合修复 (HybridFix)
@disable-portal-checks
eat-eat-eat|吃吃吃*
@recipe-stages-js|配方阶段 (Recipe Stages JS)
@@ -17678,7 +17678,7 @@ cute-companions-ducks
loot-n-explore@
epic-knights-ice-and-fire
medieval-origins-revival@
-construction-sticks@
+construction-sticks@|建筑棒*
sophisticated-storage-in-motion@|精妙物流 (Sophisticated Storage in Motion)
imgur-display@
extra-sponges@extrasponges|更多海绵*
@@ -17939,10 +17939,10 @@ mythic-charms@|神话护符*
minecraft-add@|原版补充*
stacked-blocks@|捆扎方块*
stacked-blocks-farmers-delight@|捆扎方块:农夫乐事 (Stacked Blocks: Farmer's Delight)
-@fowlplay
+fowlplay@
ender-totem
-@faewufs-diversity
+diversity@faewufs-diversity
tonsofenchants
@@ -17951,7 +17951,7 @@ cave-root
whos-there-ghost-players@
the-cuckoo-clock
final-samurai
-classic-battle-towers|经典战斗高塔*
+classic-battle-towers@|经典战斗高塔*
sihywtcamd-extensions@difficulty-extensions
@heart-crystals
@@ -17995,7 +17995,7 @@ lost-cities-survive-the-dead-edition
power-of-the-void|虚空之力 (Power of the Void)
aerlune-rpg
-hitboxbackport
+hitboxbackport@
fsmm@
create-pantographs-and-wires@
footwork-api@footwork
@@ -18018,7 +18018,7 @@ sky-breaker-blue-skies-limit-remover-1-18-2|天穹突破_1.18.2 (Sky Breaker_1.1
xiyus-lucky-block|饩雨的幸运方块 (Xiyu's Lucky Block)
@epicfight_oldsolar|史诗战斗:旧版solar (EpicFight_oldsolar)
reskillable-reimagined
-renaissance-core
+renaissance-core@
@startup-chime|启动音效 (Startup Chime)
carpet-takeneko-addition@tnca|竹猫的地毯扩展*
chain-mod
@@ -18072,7 +18072,7 @@ irons-spell-book-mob-attribute-addon
epic-fightexsilium-gladius|EpicFight—放逐之刃 (Epic Fight — Exsilium Gladius)
@kamenriderweaponcraft|假面骑士武器大全集 (KamenRiderWeaponCraft)
-advancearmy@|先遣部队 (AdvanceArmy)
+advancearmy@|先遣部队 (Advance Army)
@blood-blade
@mineimpact|方神 (MineImpact)
jadens-nether-expansion-delight@
@@ -18258,7 +18258,7 @@ create-picky-waterwheels@create-picky-wheels
fancy-lamps
resizing-potion
miku
-advanced-smelter|荒古炼金炉 (Advanced Alchemical Furnace)
+advanced-smelter@|荒古炼金炉 (Advanced Alchemical Furnace)
@@ -18361,8 +18361,8 @@ scopic
memory-leak-fix-unofficial-remastered
@distance-traveled-calculation
-@control-craft
-reforbidden-magic|禁忌魔法:重生 (ReForbidden Magic)
+@control-craft|控制学 (Control Craft)
+reforbidden-magic@|禁忌魔法:重生 (ReForbidden Magic)
moses-mod
craftable-animals|可合成动物*
avaritia-1-1x-kedition
@@ -18386,10 +18386,10 @@ rhyme-pvz@|PVZ:润 (Rhyme PVZ)
ichorium-kit
reforgotten-relics|失落遗物学:重生 (ReForgotten Relics)
in-your-world@
-warpapocalypse
+warpapocalypse@warp-apocalypse
arcane-arteries-kedition|奥法献祭学非官方版*
-thaumic-tinkerer-unofficial|神秘工匠非官方版*
+thaumic-tinkerer-unofficial@|神秘工匠非官方版*
asyncparticles@|异步粒子 (AsyncParticles)
@dragonfly-model-library
@@ -18457,11 +18457,11 @@ gud_coolers
suppergerrie2s-drone-mod
data-essence@
-nautec@
+nautec@|海蓝科技 (NauTec)
thorny-bush-protection@
gudlib
-reflex-antilag
+reflex-antilag@
tfc-nei
nolan
@@ -18480,7 +18480,7 @@ apotheosis-totem
tbone
-
+fastfurnace-decreased|熔炉性能优化-- (FastFurnace--)
@cdnperspective3d
@volume-fix
@@ -18519,13 +18519,13 @@ tinkers-advanced@|前沿匠艺 (Tinkers' Advanced)
more-structure-processors@
quick-stack-to-nearby-chests@quickstack|快速堆叠 (QuickStack / Quick Stack To Nearby Chests)
-
+@kallfix
yggdrasilproxy@yggdrasil-proxy
thaumic-meme|神秘吐槽*
starlightxae|星光xAE2修复 (StarlightxAE)
-
+@levelclear
@drops-into-shulker
ftb-team-dimensions
epicfight-resurrection|史诗战斗-重生 (EpicFight : Resurrection)
@@ -18627,7 +18627,7 @@ loquat-forge@loquat¨loquat
@customhealthhud
@fix-horizontal-camera-lag
create-cyber-goggles@|机械动力:赛博护目镜 (Create: Cyber Goggles)
-mekanism-science
+mekanism-elements
bots-lib@
botaniaoverpowered@
@@ -18839,12 +18839,12 @@ health-bar
see-the-rarities-str|查看稀有度 (See The Rarities)
craftpilot@
@game-of-reborn|复活赛 (Game of Reborn)
-legendary-slash|传说之刃*
+rough-blade|无锋道*
@dota.rg
@appledog|苹狗 (Appledog)
superb-warfare@|卓越前线*
-aetherworks-extended-life
+aetherworks-extended-life@aetherworks-unofficial-extended-life
pomkots-mechs-extension-pack@
@monkey-utils
some-more-blocks@
@@ -18896,7 +18896,7 @@ create-deep-dark@
create-ranged@
create-enlightend@
create-winery@
-create-filters-anywhere
+create-filters-anywhere@
create-cheese@
create-copper-zinc@
create-ultimate-factory@
@@ -18930,7 +18930,7 @@ apothic-science-moa-stats
ye-gamol-chattels-reborn@
-@my-more-enchantments|我的更多附魔 (My More Enchantments)
+mme@my-more-enchantments|我的更多附魔 (My More Enchantments)
@some-little-enchant|一些小附魔 (Some Little Enchant)
minepulse@|一脉相挖 (Mine Pulse)
@@ -18966,12 +18966,12 @@ cable-facades@
easy-storage@ezstorage|简易存储*
@tmwin|问君何所需 (TellMeWhatINeed)
elrols-gui-elevator
-@minecraft-found-footage
+backrooms-found-footage@minecraft-found-footage
exposure-polaroid@
fallingalchemy|坠落炼金 (FallingAlchemy)
-fluxloading
+fluxloading@
inventory-swapping
seamless-trading@
sophisticated-auto-walk
@@ -19009,7 +19009,7 @@ gluttony-oversaturation-overeat
overeat
oversaturation
-etheria
+etheria@
legends-of-slugterra@
gussdx-decoration
@@ -19156,7 +19156,7 @@ summer-cottage@
marius0x-furniture
youssefs-currency@youssefs_currency
bibliobiomes-legacy@
-sniffers-delight
+sniffers-delight@sniffers_delight
easters-delight@
@@ -19179,7 +19179,7 @@ disturb-not-my-dream@|勿扰吾梦 / 起床气*
lootbagsrenewal@lootbags|战利品袋重制版 (LootBagsRenewal)
-
+autoswaplowdurabilityelytra|自动鞘翅替换 (AutoSwapLowDurabilityElytra)
no-elytra-equip@no-elytra-mod
no-elytra
title-changer@
@@ -19249,7 +19249,7 @@ epic-fight-mesh-model|史诗战斗网格模型模组*
monster-of-the-night-skies@
spooky-doors@
@mclientapi
-@addonapi
+moffs-addonapi-dynload@addonapi
@shiv
@tab-list
dokimobs@|萌化生物 (DokiMobs)
@@ -19286,7 +19286,7 @@ blades-derby@|骐骥刀剑风云*
create-simple-generator@|机械动力:简单发电机 (Create: Simple Generator)
sophisticated-backpacks-create-integration@
-@ticex
+ticex-tinkers-construct-ex@ticex
eternal-starlight-vanilla-ores@eternal-starlight-vo
supercritical
supercritical-gtqt
@@ -19317,7 +19317,7 @@ expanded-ae
modular-machinery-reborn-ars-nouveau@
bonsai-crops@|盆栽作物*
playing-cards-chips|扑克牌与筹码 (Playing Cards & Chips)
-enderdrives
+enderdrives@
lucky77
i-dont-see-you@
@@ -19343,7 +19343,7 @@ whimcraft@|奇思妙想 (WhimCraft)
recipeconflict-fixer|合成冲突消除:修复版 (RecipeConflict Fixer)
dooglamoo-painter-mod|Dooglamoo 的绘画*
notafreaks-plushies@
-enchanting-vanilla
+enchanting-vanilla@|原韵新生*
quest-items-rpg@quest-items
@@ -19397,7 +19397,7 @@ apotheosis-x-irons-spellbooks-compat
customtab@
nightmares|噩梦*
graveyards
-sky-arena
+sky-arena@demis-sky-arena
progressive-mechanics-library@
@shy
@@ -19452,7 +19452,7 @@ slightly-newer-art-of-forging
custom-treasure-maps@
evolved-mekanism|通用机械:进化*
-
+brain-delight|脑子乐事*
villager-library|村民图书馆*
trade-enchant-overhaul
more-additional-paintings@
@@ -19513,7 +19513,7 @@ applied-web-terminal@|AE网络终端*
itemzoommore
@noitemtooltips
shuttfup|Stfu - 已停更¨@shuttfup
-baublesreforked|饰品重铸 (BaublesReforked)
+baublesreforked@|饰品重铸 (BaublesReforked)
@wills-lively-villages
tmrv@
@@ -19521,7 +19521,7 @@ twilight-treehouses@
improved-rails@|更好的铁轨(可转弯の动力铁轨!) (Improved Rail (TURNABLE POWERED RAILS))
redstone-signal-bind-wand@|非Create版无线红石*
-
+@ingametranslator|游戏内聊天翻译 (In game Translator)
carnivorous-plants-addition
mekanism-curios
tetra-compat
@@ -19563,7 +19563,7 @@ scaffolding-backported
hold-my-items-reforged@
distraction-free-recipes@
-seeking-immortals-nightmare@|求仙者:噩梦之章 (Seeking Immortals : Nightmare)
+seeking-immortals-nightmare@|月之石:噩梦之章/求仙者:噩梦之章 (Moonstone :Nightmare/Seeking Immortals : Nightmare)
enchanted-crystal-arrow|魔法水晶箭 (Enchanted Crystal Arrows)
touhou-danmaku-kagura-core|东方弹幕神乐核心 (Touhou Danmaku Kagura core)
hah-ueuh@hah!-ueuh
@@ -19583,7 +19583,7 @@ dontstarve4@|不要饿死4 (Don't Starve 4)
spark-core@|星火核心*
nether-core-useful-nether-rock|下界核心 (Nether Core)
slashblade-js|拔刀剑 JS (SlashBlade JS)
-@caerula-arbor|方块与海嗣与深蓝之树 (Caerula Arbor)
+caerula-arbor@|方块与深蓝之树*
alices-doll|元歌 (Alice's Doll)
bedrock-redux@|基岩再现*
item-entity-control|掉落物清理*
@@ -19600,12 +19600,12 @@ pokecube-alternative
create-propulsion@|机械动力:推进工程 (Create: Propulsion)
-event-wrapper|事件包装器*¨@event-wrapper|事件包装器 (Event Wrapper) - 审核中
+event-wrapper@|事件包装器*
@modwhitelist|模组白名单 (ModWhiteList)
beacon-conduit-tweaks|信标与潮涌核心调整 (Beacon & Conduit Tweaks)
-@tfc-tools-trim|群峦传说:工具纹饰 (TFC Tools Trim)
+tfc-tools-trim@|群峦传说:工具纹饰 (TFC Tools Trim)
@kill-youkai-with-knives
mo-bees@|更多蜜蜂 (Mo' Bees)
@@ -19618,7 +19618,7 @@ create-unlimited@
skeleton-ai-fix@
climate-rivers@
dungeonsdelight@dungeons_delight|地牢乐事 (Dungeons Delight)
-cleanroom-relauncher-unofficial|Cleanroom 模组式启动器非官方版*
+cleanroom-relauncher-unofficial@|Cleanroom 模组式启动器非官方版*
silents-power-scale@
trident-return-fabric
resource-world@|资源世界*
@@ -19702,7 +19702,7 @@ alienevo
banit|干掉它! (BanIt)
alexs-mobs-naturalist-compat@
-
+tinkers-ingenuity|匠心之作 (Tinkers' Ingenuity)
curiosities@|趣味物品*
@cs2_c4|Qis的C4 (C4)
@@ -19790,7 +19790,7 @@ verdant-wonders@
underlay@
lazy-item-generator|咸鱼物品生成器*
-crab-street-light|螃蟹的路灯*
+crab-street-light@|螃蟹的路灯*
undertale-delight@|决心乐事*
@@ -19828,7 +19828,7 @@ projecteteams@
battle-frenzy|越战越勇*
@plane-advancements
cosmopolitan@|四海一家*
-
+redtassel|红缨 (redtassel)
sridr|拔刀剑锻造增伤改革 (SlashBlade Refine Increased Damage Reform)
mianbaos-modernwarfare|面包军火库 (Mianbao's ModernWarfare)
@@ -19838,7 +19838,7 @@ create-no-touching
create-aether-industry|机械动力:天境工业*
create-entitycontroller@create-entity-control|机械动力:实体控制 (Create: Entity Control)
better-advancements-blessed-edtion|更好的进度 - 祝福版 (Better Advancements - Blessed Edtion)
-@gloom-raider
+@gloom-raiders
herobrine-prime
legend-of-the-dwerden@
dnt-stronghold-overhaul-lite-edition
@@ -19859,7 +19859,7 @@ fluxed-core@
backpack-plus|背倍加*
@hydration-mod
-defiled-lands-reborn|污秽之地重置版*
+defiled-lands-reborn@|污秽之地重置版*
@copper-bulb-early|铜灯补充 (Copper Bulb Early)
future-fireproof@|未来防火*
@low-durability-switcher
@@ -19888,11 +19888,11 @@ aesthetic-shelving
arboreal-nature@
warborn-resurgence@warbornresurgence
+@ceres
-
-super-creeper|智能爆破专家*
+super-creeper|智能爆破专家 (Creeper Super)
expanded-experience
-reworked-phantoms
+reworked-phantoms@
ex-nihilo-fabricio@
hybrid-delights@
create-clothes@|机械动力:服装 (Create: Clothes)
@@ -19947,4 +19947,924 @@ tinkers-jewelry-ex|工匠珠宝拓展 (Tinkers' Jewelry EX)
lovely-sparkle-pieces|璀璨饰品*
yuns-weather@
-qliphoth-awakening@
\ No newline at end of file
+qliphoth-awakening@
+
+
+universal-mod-localizer@universal_mod_localizer|通用模组汉化器*
+smart-creeper@|智慧苦力怕*
+peaceful-depletion@peaceful_depletion|和平消耗*
+@everything-is-tnt
+structure-pool-api@
+create-bluemap@
+tragicmc-2-continuation|悲惨世界2:延续 (TragicMC 2 Continuation)
+nebula-parasites@
+bonsai-trees-unofficial-extended-life|盆栽非官方延续版*
+@its-as-shrimple-as-that
+worldedit-items@
+slashblade-skin@|拔刀皮肤 (SlashBlade Skin)
+iron-door-key@
+just-enough-fishing-jef@just-enough-fishing-(jef)
+containersearch@container-search
+mousepets@
+backported-minecarts@
+simple-incubator@
+amethyst-stairs-slabs-and-walls
+@vessels
+
+legendary-weapons-byititor
+slender-reborn
+craft-vania-lite
+pollution-of-create
+create-chocolate-fountain@|机械动力:巧克力喷泉 (Create: Chocolate Fountain)
+create-currency-shops@|机械动力:货币商店 (Create: Currency Shops)
+create-nether-industry@|机械动力:下界工业 (Create: Nether Industry)
+backported-diamond-ore-gen
+tfthreadsafetyaddon
+serverautosaver
+infinistack@|无限堆叠 (InfiniStack)
+powerful-dummy|强力假人*
+beyond-bosses@beyondbosses
+mini-utilities-y
+concoction@concoction!
+zombietactics2@
+radiation-zone-reborn|辐射荒原:再行 (Radiation Zone: Reborn)
+
+@crumbling-hearts
+
+dye-the-world@
+modernmarkings@
+hololive-product@
+fetzis-displays@
+shadered@
+sweety-garden@
+
+@twilight-forest-aurora-palace-regeared
+
+
+medieval-buildings-nether-edition
+@ribbits-croaks
+echoes-of-time@
+horror-alexbrine
+zzz-craft|绝区零·星见雅 (ZZZ Miyabi)
+
+cozy-cabins-and-cottages
+servereye
+deepseek@deepseek-neoforge
+ai-command|AI指令生成 (AI Command Generator)
+
+flighthud-do-a-barrel-roll-fix
+
+@fast-container-placement|快速容器放置 (Fast Container Placement)
+@teleport-mod
+@what-meme
+crackers-wither-storm-mod-v-sides-soundtrack@
+
+roberts-game-tweaks|Robert的游戏调整 (Robert's Game Tweaks)
+
+@datapack-load-error-fix|数据包错误修复:重生 (Datapack Load Error Fix Reborn)
+@whats-this
+@additional-jewelryz
+tacz-tes-compat@
+
+
+directionhud
+raw-netherite-reforged
+blast-travel-reborn@blasttravelreborn|旅行火炮·重生*
+shanxi-skeleton@shanxiskeleton|山西骷髅*
+@gunpowder-overhaul|火药改革 (Gunpowder Overhaul)
+smoking-stuff-a-wizards-hobby
+@alloy-smelter
+@autocrafter-early
+time-in-a-bottle-charger@
+just-a-sculk-vial@
+lockylocks@
+server-i18n-api¨@server-i18n-api|Server I18n API - 审核中
+fragmentum-forge@fragmentum¨fragmentum-neoforge¨fragmentum-fabric
+@monument-data-fix|Monument 数据修复 (Monument Data Fix)
+@better_tp
+waystone-currencys@|消币则至 / 传送石碑:货币 (Waystone: Currencys)
+@armor-stand|盔甲架 (ArmorStand)
+threadtweak-reforged|ThreadTweak (Neo)Forge 移植版 (ThreadTweak Reforged)
+@environmental-creepers-backport
+com-bat
+touhou-little-maid-rpg-class-task
+
+heat-and-climate-patch|热量与气候修复*
+
+@clientupdater
+
+modern-lucky-block@
+foxrecipemaker
+@global-mob-health-multiplier|全局生物血量倍率 (Global Mob Health Multiplier)
+chatbox-for-mc@chatbox|对话框 (ChatBox)
+perfectly-balanced-bows
+@playerex-alcohol|PlayerEx 酒精版 (PlayerEx: Alcohol)
+hak-minepiece
+grease@
+cleaver-compedium
+phayriosis-parasite-infection
+srp-calamity-catastrophe-cataclysm@|逃寄:无妄之灾 (SRP: Calamity Catastrophe Cataclysm)
+moss-overgrowth
+create-pillager-arise
+create-qol@
+create-remote-terminal@|机械动力:远程终端 (Create: Remote Terminal)
+
+
+songs-of-war@|战争之歌 (Songs of War)
+cataclysm-summons@
+cutepuppymod@
+giant-swamp-tree|沼泽巨树*
+big-oak-tree@|大橡树*
+giant-tree-stump|大树桩*
+
+polars-mad-tweaks@
+cobblemon-ranked@|方块宝可梦-排位赛*
+eiramoticons
+horse-breeding-fix@
+tablistextension@|TAB 列表扩展 (TabListExtension)
+rally-of-the-guard-guardvillagers
+bncci
+@recipe-maker
+portaltransform@|传送门嬗变 (PortalTransform)
+@a555sd|更多的画 (More Paintings)
+unusual-furniture@
+xiaoanmooncake@|小安的月饼狂热 (Xiaoan's Mooncake Fever)
+foragers-insight@
+palestinian-delight@
+mekanism-more-machine@mekanismmoremachine|通用机械:更多机器 (Mekanism: More Machine)
+custom-battleroyale@|自定义大逃杀 (Custom BattleRoyale)
+improved-pillager-outpost
+rebalanced-enchanting@
+dark-doppelganger@
+enders-spellsandstuff
+hazen-n-stuff
+spongeforge@
+
+@don%22t-die|补药趋势啊! (Don't Die!)
+sdm-market@
+@omnilook
+
+survivalist-essentials@
+villager-trading-plus@trading+
+mending-fix|经验修补修复*
+
+colorwheel@
+tlib@
+smoothscreen@
+
+@fuzz
+advanced-loot-info@
+resourcepackcached@
+aotake-sweep|竹叶清*
+
+effectual@
+cold-light@|冷晖之枪*
+
+huanles-weapons@|Huanle的武器 (Huanle's weapons)
+@clay-soldiers-remake
+threateningly-mobs@
+basic-spinosaurus
+european-wildlife
+marvelous-menagerie@
+roman-weaponary
+compat-structure@
+flower-bed@
+archery-expansion-squared
+create-more-linked-remote@
+
+
+
+gambagoons-better-emeralds
+ingameime-vintage@|游戏内输入法-1.12.2 (IngameIME-Vintage)
+extreme-sound-muffler-legacy@
+amazing-trophies
+
+
+global-server-configs@global-server-config|全局服务器配置*
+expandedbonemeal
+@aireplyassistant|AI回复助手 (AIReplyAssistant)
+utiljs
+moon-info-updated@
+@server-market|服务器市场 (Server Market)
+nbt-tooltip@nbttooltip
+delicate-dyes@
+@modernmixins
+sdm-economy
+sdm-ui-lib@
+mcllm¨@mcllm|MCLLM - 审核中
+@inventory-pause-yipeekiyaay
+chestsearch|容器检索 (ChestSearch)
+clicktor|Clicktor连点器 (Clicktor - Auto Clicker)
+extended-bone-meal@¨extended-bone-meal-fabric
+show-me-the-enchantment|怎魔,你不附器*
+better-sign-edit@
+disc-recipes@|唱片合成 (Disc Recipe)
+
+
+scoutreforked|侦察兵 重构 (Scout Reforked)¨@scoutreforked|侦察兵 重构 (Scout Reforked) - 审核中
+immersion-in-traffic-context|沉浸交通道路 (Immersion in Traffic Context)¨@immersion-in-traffic-context|沉浸交通道路 (Immersion in Traffic Context) - 审核中
+arknightsfurniture@|明日方舟家具 (ArknightsFurniture)
+gunsmith-lib
+
+experiencelib@
+mystical-mobs
+
+progressive-difficulty-chaos-integration@
+ottapomos-scps
+scp-foundation-requisites@
+thermal-construct@
+war-wither|战争凋灵*
+status-effect-bars-reforged
+dark-tower-maze
+
+the-occult
+wolftailui@
+better-client@better-client-enc|更好的客户端*
+clientharvest@
+auto-afk@
+particle-effects-reforged
+hold-my-items-refoxed
+dont-claim-our-structures
+cosy-critters-creepy-crawlies@cosy-critters-creepy-crawlies-neo
+thin-air-ad-astra-compat
+yyzsbackpack@yyzs-backpack|yyz的背包 (Yyz's Backpack)
+
+vital-delight
+
+@mining-in-place
+pathfinder-navigation|寻路者导航*
+adorable-eggs@
+food-addition@
+everything-totem|万物皆可图腾*
+thaumic-renaissance@thaumcraft-renaissance
+@executive
+@make-i18n-great-again
+steve-goes-fishing@
+voltaic-api@voltaic
+boat-delete-begone
+zs-packet
+raise-them-hotbars
+quick-leafdecay
+footprint-particle-returns
+inventory-crafter-returns
+colorwheel-patcher@
+
+
+
+
+difficulty-lock@¨difficulty-lock-fabric
+ryymr@
+@lazybeefix
+
+lazy-ae2-tweaks
+coordinates-hud@coordinateshud
+just-enough-botania
+fumo-fumo
+touhoupixelcanteen-a-farmers-delight-plugin@touhoupixelcanteen|东方像素食堂 (TouhouPixelCanteen)
+perfect-parity-the-garden-awakens-edition@
+perfect-parity-spring-to-life-reforged
+hypertubes@hypertube|机械动力:超级管道 (Create: Hypertubes)
+perfect-parity-spring-to-life-edition@perfect-parity-1-21-5
+create-geography|机械动力:地理学 (Create: Geography)
+create-apothic-enchanting@
+demis-enigmatic-dice@
+
+@mpem|月夜性能优化 (Moonlit Performance Enhancement Module)
+@eventbenchmark|事件性能测试 (EventBenchmark)
+no-more-garbage-in-cell
+pick-up-filter-puf@pick-up-filter
+
+identity-fix-morph@identity-fix
+
+stick-it@
+aimod
+srp-reset-mod|逃逸寄生体:重置
+legendary-relics|传说遗物*
+
+true-power@|真正的力量 (True POWER)
+ender-villages|末地村庄*
+twilight-forest-villages@|暮色森林村庄*
+poland-nature
+amethyst-extra
+villagers-windmill
+extended-wrenches@
+why-stack-only-64|为什么才64个呢? (Why Stack Only 64?)
+@digcooldown|挖掘冷却控制 (DigCoolDown)
+
+
+potentials@
+deltaboxlib@
+gobbobs-animated-blocks|Gobbob 的动画方块 (GobBob's Animated Blocks)
+glowingfootsteps|发光足迹粒子 (GlowingFootsteps)
+simply-swords-overhaul@
+farms-and-foods@farms-foods
+@achievement-fix|成就修复 (Achievement Fix)
+
+lang-magic-tweaker
+spawn-dimension-choice|诞生维度自选*
+reforge-stone@|重铸石*
+numericalbotany
+cameraoverhaul-vintage@
+
+
+advanced-hoppers
+
+ars-meteorites
+the-hard-herobrine-mod
+
+tektopia-addons
+jason-voorhees
+biome-improvement-mindthemoods-pile-of-rocks
+mochila@|简彩跃光*
+soulytra
+@advanced-tfc-blueprint|高级群峦蓝图 (Advanced TFC Blueprint)
+@accesswidener-loader
+improperui-forged@
+
+enchantment-to-attack-faster|并非环取/攻击速度附魔非官方版 (Attack Speed Enchantment Unofficial)
+@stopgrowth|停止生长 (StopGrowth)
+squid-no-glitch
+@permanent-night-vision|无痕夜视 (Permanent Night Vision)
+@rems-carpet-addition
+
+colorful-hex@
+
+bbl-no-rain-fog
+
+@be-quiet-negotiator
+link15s-no-i-frames
+jei-integration-reborn@|JEI扩展:重生 (JEI Integration: Reborn)
+appliedsync
+jei-uu-assembler@
+ominous-overlays
+create-nuclear-shell@
+world-of-wonders
+horrible-meteor-shower|陨石雨*
+desertification@
+cinchs-abandoned-houses@
+curse-breaker@
+hundred-years-warfare@|百年战争*
+biologica@
+oritech-things
+potato-vehicle-industries
+warfare-wings@
+avaritias-delight@|无尽乐事 (Avaritia's Delight)
+mamas-herbs-and-harvest@
+vs-orbit@|瓦尔基里:轨道学 (VS Orbit)
+touhou-little-maid-spell@
+despawn-notifier|余影待终/掉落物消失提醒*
+customnpcs-unofficial-from-betazavr|自定义 NPC BetaZavr 非官方版 (CustomNPCs Unofficial from BetaZavr)
+
+efficient-core-automatic-mining|Efficient Core自动挖掘器*
+@bento_box|便当盒 (Bento Box)
+custom-100-day-boss@|自定义100天Boss (Custom 100-Day BOSS)
+
+cinchs-extra-torches@
+corpse-x-cosmetic-armor-reworked-compat
+modular-angel-ring
+magic-feather-replumed@magicfeather
+essentialcraft-magic-reflux
+ad-astra-star
+neomopeds
+create-more-parallel-pipes@
+create-garnished-reworked@
+create-shimmer|机械动力:微光 (Create: Shimmer)
+blue-archive-school-banners
+cialloblade
+windows-keyboard-fixes-returns
+aquatic-torches-returns
+cratetech-crate-storage
+bigger-and-better@
+blabber@
+reducedores@
+durabilityrarity@
+ironfist-lite
+shear-chicken@shear-chickens
+no-moon-jar@no_moon.jar
+@deep_space
+blue-grove
+quarkponders@
+sussy-sniffers
+firefly-bush-backport@
+agritech-trees-automated-trees@agritech-trees
+agritech-evolved@
+wealizh|WLH工业故事*
+@hydrometallurgy|湿法冶金学 (Hydrometallurgy)
+mekanism-ecoenergistics-unofficial
+
+woodlandfoxverses@|林间狐咏 (WoodlandFoxVerses)
+
+
+swaying-garden|摇曳花园*
+time-feeds-villager@|逝者如饲:村民 (Time Feeds: Villager)
+ftb-backups-3|FTB 备份 3 (FTB Backups 3)
+kurolib@
+trimmed@
+free-camera-api-tripod@|自由相机API-三脚架 (Free Camera API Tripod)
+abelia¨@abelia|Abelia - 审核中
+
+simple-weather
+simple-copy@
+natures-aura-render-fix@
+creakingchopperenchantment@
+daily-rewards@
+initialdimensionreborn
+
+
+
+
+the-plugin-mod@
+cave-vision
+@el-addon-retrieve-to-slot
+aether-gloves-for-all
+reducedloot
+@irisfixes|Iris 修复 (IrisFixes)
+musicjs
+
+suspendtheserver|自动挂起服务器 (Suspend The Server)
+screenshot-layers
+anti-spider-climbing
+rechiseled-chipped@
+
+weatherdisplay@
+radium-fabric|Radium - 已失效¨@radium-mod
+cinnabar
+
+pquality@
+
+mte-patches
+happy-ghast-breeding
+happy-ghast-flight
+the-angry-ghast@
+@morfonica-in-minecraft
+for-vanilla-flowers
+@skillpractise|技能练习 (SkillPractise)
+
+
+
+bedrock-hotbar-offset¨@bedrock-hotbar|Bedrock Hotbar Offset - 审核中
+mss-more-slot-swap¨more-slot-swap
+show-all-advancements|显示所有进度*¨@show-all-advancements|显示所有进度 (Show All Advancements) - 审核中
+
+mana-display@
+
+curse-forge|诅咒锻造*
+
+
+disconnect-packet-fix@
+happyghast-on-1-12-2@happyghast_on_1.12.2|1.12.2的快乐恶魂 (HappyGhast_on_1.12.2)
+automationera@|自动化时代 (Automation Era)
+@tears-backport
+lava-chicken-disc
+lava-dennis
+album-go-wall
+forgotten-java
+johanness-renaissance-rebirth@johanness-renaissancerebirth|约翰内斯的文艺复兴:火器重生 (Johannes's Renaissance:Musket Rebirth)
+swarm-infection@
+necro-rooted
+@mycelial-crisis|菌丝危机:崛起 (Mycelial Crisis: Mushroom Rising)
+
+lethalsteelproject|致命拔刀计划 (Lethal Steel Project)
+lovecraft-girlfriends-3@
+@faithful-mace
+bard-craft
+
+items-timer
+neo-language-reload@
+@oxidizium
+ixeris@
+substrate-sodium-addon@substrate
+hide-item-frame@|隐藏物品展示框*
+@smoothmaps
+neoessentials@
+structure-layout-optimizer@
+mutant-creatures-legacy
+dynamites-overhaul@
+dynamite-addon¨dynamite-addon-fabric
+maid-mana-source|女仆魔源*
+maidens-merry-making@mamas-merrymaking
+bbl-invisible-lights@
+superior-trees@
+underground-rivers@
+improved-village-placement@
+@photosynthesis
+@mineno
+slashbladetetra
+
+irons-spells-n-spellbooks-dynamic-skill-trees@
+spell-elemental@
+ward-ring-extension-for-irons-spells-n-spellbooks
+dungeons-and-combat-x-irons-spells
+smallcolonies
+farmers-cutting-regions-unexplored
+serene-landscapes@
+blood-arsenal-reawakened@
+
+
+spell-colonies
+nature-reimagined
+ominous-bottle-add
+only-hammers@
+auto-resource|自动资源*
+iu-additions
+rubiks-cube|魔方*
+clockworklib
+
+fabridge
+
+true-fullbright
+quickslots
+hide-hud-in-third-person-view@hide-hud-third-person
+automlg@auto_mlg
+elytraautopilot@elytra_autopilot
+simple-conveyor-belts@
+smidgeon-o-bliss-remastered
+xl-food-reborn|超多食物:重生 (XL Food Mod Reborn)
+create-spin-spawner
+vs-rotanator
+create-refilling-boxes
+cbc-chemical-warfare|机械动力火炮:化学与生物战争 (CBC: Chemical Warfare)
+cbc-warium-projectiles|机械动力火炮:Warium炮弹 (CBC: Warium Projectiles)
+create-colored
+create-law-and-order|机械动力:法律与秩序 (Create: Law and Order)
+blockexporter
+fix-attack-lag@|修复攻击卡顿*
+@clienthings
+@misinput
+lavablocker@
+gildedarmors@gilded-armor|鎏金盔甲 (Gilded Armor)
+inventory-management-deluxe
+runningmapmusic|跑图音乐 (RunningMapMusic)
+lava-chicken-music-disc
+porting-dead-libs
+panoramica¨panoramica-fabric
+create-mt-integration|资源统合-机械动力配方附加*
+@valkyrien-warpeace|瓦尔基里:战争与和平 (Valkyrien War and Peace)
+sillys-auto-totem
+repairables-anvil@|可修复的铁砧*
+@throwable-bricks
+@now-time|准点报时 (Now Time)
+fluidjs¨@fluidjs|FluidJS - 审核中
+hybridcompat|混合兼容 (HybridCompat)
+@recipecooldown
+counter-mod
+lotr-renewed-class-mod|魔戒种族 (LOTR Races)
+
+craftable-chain-armors
+item-cooling-reduction@|物品冷却缩减*
+yosbs
+
+
+@no-bugs
+dark-window-bar@
+sodium-core-shader-support@
+@anvilcraft-headtap|铁砧工艺:敲你脑袋 (AnvilCraft: HeadTap)
+hearts-in-a-bottle@
+copper-tools-and-shears@
+windchime-unofficial-continued@
+
+potionsplus
+tinkers-wood@
+illuminated@
+galosphere-argent-edge
+brutality-a-terramity-addon
+free-epic-games@|FE 游戏框架 (FreeEpicGames)
+
+fabric-database-h2@|H2数据库*
+tokien-theme-bq|更好的任务-托尔金主题 (Tolkien Theme [BQ])
+tpa-utilities
+more-diamond-spawn|更多钻石生成*
+shutup-opengl
+maid-command|女仆指令*
+speedy-hopper-vanilla
+suggestions-fix-fabric
+no-ixapi@no.ixmodapi
+
+epicfight-zylob|史诗战斗:刀光修改 (EpicFight Sword Light Modifications)
+perfect-parity-pale-garden-awakens-neo
+expressive-advanced
+philia-amulet|友爱护符 (PhiLia Amulet)
+stronger-fire-boss|原焰帝臻*
+myec@|更多渴望:附魔与制造 (More of Yearn: Enchants & Crafts)
+abandoned-urban|废弃城市 (Abandoned urban)
+fungal-hazard|蕈骸迷途
+more-useful-copper-2-0@more-useful-copper|更有用的铜 (More Useful Copper)
+boom-bombs@
+polars-exploration-additions@
+
+ozymandias-sundries
+tensura-enigmatic
+
+create-collection-bundles@
+create-enhanced-mastery
+create-industrial-tools
+create-smart-bounds
+create-fantasizing-again@|机械动力:又在幻想了 (Create: Fantasizing Again)
+crafting-station-jei-edition-updated
+uncraft-everything@
+@videoplayer
+@net-music-play-list|网络音乐机:播放列表 (NetMusicList)
+
+gregs-modern-construct
+
+smart-key-prompts@|灵活按键提示 (SmartKeyPrompts)
+@zero-whitelist|Zero白名单 (Zero Whitelist)
+
+@pearlchunkloading|珍珠加载区块移植 (Pearl Chunk Loading)
+clean-drops
+auto-replace
+@footprint
+
+simple-villager-follow@
+tinkers-experience
+hedgehog-companion
+i-want-to-spin@|我要开转 (I want to spin)
+@kiss-fabric
+
+
+
+nuggets-repair-items@|矿粒修复物品*
+
+
+terra-atmos|群峦环境音效*
+tlm-true-power@true-power-of-maid|车万女仆:真正的力量 (TLM: True POWER)
+
+
+
+@splash-window
+
+@showcase
+enhancedtooltips@
+locate-fixer
+journeymap-fixes
+ancient-structure@ancient-structures
+
+wizard-samurai-irons-spells-addon¨@wizard-samurai-irons-spells-addon|Wizard Samurai - Iron's Spells Addon - 更新较慢
+survival-instinct
+enderzoo-patched
+illager-keep
+codex-of-champions
+ars-controle@
+medieval-craft-historical-firearms
+too-many-withers
+reliquified-l-ender-s-cataclysm@
+
+@log-creator-finder-findshit|找史大王 / 日志溯源 (findshit)
+enchantment-lookup@|附魔查询*
+
+
+
+
+@mods-checker
+no-enchanting-xp-requirment
+client-dynamic-light|客户端动态光源*
+
+multiyggdrasil@
+thaumcraft-4-patched
+patchful
+gtnh-customizer@
+view-sounds@
+ae-inventory-profiles
+tooltips-reforged@|提示框:重铸*
+@wyr
+soaring-phantoms@
+tick128¨@tick128|Tick128 - 审核中
+ritchies-projectile-library@rpl
+modopedia@
+@translate-allinone|一体化翻译 (Translate All in One)
+button-fix@
+@birthday-fabric|生日模组 (Birthday Fabric)
+bucketstacking
+creeper-no-break-blocks@
+create-cog-hud@|机械动力:齿轮 HUD (Create: Cog HUD)
+
+create-pipe-networks
+
+multicolored-redstone-lamps
+reliquified-twilight-forest@
+witch-tower
+the-nightmare-000
+meowmere
+@fallenflowers-server-economy|Fallenflower的服务器经济 (Fallenflower's Server Economy)
+deadout-medical-supplies|死亡突围医疗用品 (Deadout Medical)
+@thermal-shock
+better-questing-npc-integration|更好的任务-NPC扩展 (Better Questing - NPC Integration)
+
+eu2emc|EU转EMC能量转换器 (EU2EMC)
+
+simplesorter@|简单整理 (SimpleSorter)
+spawncursion|生成式入侵 (SpawnCursion)
+grass-be-gone|不要草*
+tacz-unidict|TACZ:铳械协议 (TACZ: Unidict)
+gem-lichens|宝石地衣*
+lethality-a-terramity-addon@
+lootr-fixes
+no-name-provideds-potion-fruit@potion-fruit
+fungus-stew
+auto-sapling|自动树苗种植*
+alans-bees
+snack-sticks@
+fish-fix
+stuttering-screen-lag-protection
+tilefinder@
+immersive-furniture@|沉浸式家具*
+modernnetworking
+like1-8
+spoduos-future-mineration
+enderio-continuous
+enchantment-broker@
+
+new-dawn
+enhanced-glass
+hospitals
+comfy-cozy
+epicnpc|史诗战斗NPC (epicnpc)
+epic-fight-script-extension|史诗战斗:脚本扩展版*
+@undead-transformation
+
+skullskinless|头颅无皮肤/易颜得帧 (SkullSkinless)
+staynight|永夜定驻 (Stay Night)
+blockblocker|方块放置阻止/抑用挡置 (BlockBlocker)
+shoving-shovels@
+electric-coating|不止电池 / 电力镀层 (NotOnlyBattery / Electric Coating)
+araxers-bestiary
+better-questing-immersive-engineering-multiblock
+bloodyquests
+croaker@
+crawl-on-demand@
+gaze-a-malum-addon
+osce|矿石菌种社区版 (OreSpawn Community Edition)
+explosive-arrow
+more-relics@
+no-sun@no-sun-mod|暗无天日*
+opposing-force@
+
+@pingfix
+@volumescroll
+talk-balloons@
+health-indicator-txf@
+
+snowy-weather
+
+
+fluidium|锍*
+
+peritia
+
+
+corpse-kibou-no-hana|希望の花 (Corpse: Kibou no Hana)
+redyeable
+@natural-motion-blur
+@modpack-checker
+convenient-achievements
+vintage-vibes@
+
+@smelter-the-hedgehog
+@hopper-the-hedgehog
+custom-trades-mod|自定义村民交易*
+tameable-zombie-horse-and-skeleton-horse|可驯服的僵尸马和骷髅马*
+mi-tweaks
+qa-villager-tweaks|Qa村调整*
+multiblocked-extended-compatibility
+enum-extender-js
+@btc
+
+no-pigs
+vanimals@
+wind-charged-weaponry@
+untagged-mobs
+enchanted-potato
+cit-for-nbt
+
+bonemealtweaker
+hellstone-golem|狱岩魔像*
+netherstar-crafting|可合成的下界之星*
+tutes-cn-sweetinks-items|塔兹与甜墨的物品 (Tutes-cn & Sweetink's Items)
+i-am-steve@i..am-steve
+backported-stufff
+
+tumbleweeds¨@tumbleweeds|Tumbleweeds - 审核中
+mending-mirror|破镜重圆*
+
+natures-life
+yggdrasils
+
+
+@deepseeks-enchant|DeepSeek的附魔 (DeepSeek's Enchant)
+
+
+
+isitwaxed
+simple-hp-bar
+login-hp-fix
+shape-shifter-curse|幻形者诅咒*
+
+
+tinkers-planner-reborn|匠魂蓝图:重燃 (Tinker's Planner Reborn)¨@tinkers-planner-reborn|匠魂蓝图:重燃 (Tinker's Planner Reborn) - 审核中
+hunger-overflow
+nocreeper
+mo-creatures-extended
+skippy-pearls
+spelunkery-plus
+forge-better-redstone
+jeb-just-enough-book@justenoughbook
+pal@
+data-anchor@
+elytra-hopping
+oh-more-wandering-trades|更多游商交易*¨@oh-more-wandering-trades|更多游商交易 (Oh More Wandering Trades) - 审核中
+beacon-anti-phantom@|不时轻轻肘击幻翼的邻座信标同学 (Beacon Anti-Phantom)
+
+
+cooler-animations
+
+hardcore-architect
+
+carpet-neoforged@neoforge-carpet
+no-campfire-smoke
+tiny-tasks
+fancy-outline
+echo-compass-hud@
+decorative-stands
+confluence-otherworld-gun-fix@
+
+sbm-cardboardboxes
+
+
+bedrock-age|基岩时代*
+
+
+@koniavacraft|娜拉工艺 (Koniavacraft)
+emojicraft|Emoji工艺*
+kylin-arm|麒麟臂*
+server-hats@
+forgotten-ruins
+
+creeperhost-presents-steves-carts@
+perspatium@per-spatium
+face-stealer
+offlawn-legacy
+metalborn
+@wizards-mod
+mo-creatures-nostalgia-edition@
+mocreatures-awakened
+sterling
+cuisine-tfc|料理群峦 (Cuisine TFC)
+@natural-temperature
+silent-gear-metalworks
+treetap@
+better-cave-update
+@affix
+starry-end
+dynamic-trees-for-cobblemon
+immersive-portal-gun-for-forge@
+adorable-hamster-pets@
+infinite-lava@|无限熔岩*
+ethereal-shots|虚无射击*
+
+epic-fight-super-golem|超级铁傀儡 (Epic Fight - Super Golem)
+fallen-log-hideout
+pastel@pastel-mod|彩绘世界*
+mob-catcher-unofficial
+retro-pet-items
+confluence-dimension-patch|汇流来世维度补丁*
+convenient-storage|便捷存储*
+legendreliclib|传世遗产库 (LegendRelicLib)
+jauml
+nemexlib
+cryonicconfig@
+@knight-lib
+
+
+@okom
+
+craft-slabs-back-into-blocks@
+highland-statue
+restore-ore@|复原矿石*
+factory-blocks@|工厂方块*
+
+rotten-planks-and-logs
+
+create-unify-updated-to-create-6
+create-arm-made-cuisine|机械动力:臂力臂力炒菜 (Create: Arm-made Cuisine)
+create-cultivation-craft@|机械动力:栽培工艺
+kaleidoscope-cookery@|森罗物语:烹饪 (kaleidoscope cookery)
+retroemi-forge@
+@pearlecho
+@mcdaycounter
+minecolonies-war-n-taxes
+falling-down@
+more-red-x-cc-tweaked-compat
+
+
+
+wither-reincarnated
+
+toros-auto-attack
+creeperfire|自定义烧货 (CreeperFire)
+fastspawner|刷怪笼渲染优化/产器速算 (Fast Spawner)
+sophisticated-storage-plus|存取思深/精致存储扩容*
+whaleborne
+
+newspaper-big-book-bibliocraft-legacy-backport
+bic-clipboard
+templates-2@
\ No newline at end of file
diff --git a/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll b/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll
index 8069902..341d08f 100644
Binary files a/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll and b/Plain Craft Launcher 2/Resources/Newtonsoft.Json.dll differ
diff --git a/Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll b/Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll
new file mode 100644
index 0000000..e9e80be
Binary files /dev/null and b/Plain Craft Launcher 2/Resources/System.Net.Http.Formatting.dll differ
diff --git a/Plain Craft Launcher 2/Resources/java-wrapper.jar b/Plain Craft Launcher 2/Resources/java-wrapper.jar
index bd14bd6..9ab92b2 100644
Binary files a/Plain Craft Launcher 2/Resources/java-wrapper.jar and b/Plain Craft Launcher 2/Resources/java-wrapper.jar differ