This commit is contained in:
龙腾猫跃
2026-05-21 01:15:20 +08:00
parent 8c3896da36
commit c86fdc3af1
36 changed files with 275 additions and 255 deletions
+14 -33
View File
@@ -10,12 +10,12 @@ Public Module ModBase
#Region "声明"
'下列版本信息由更新器自动修改
Public Const VersionBaseName As String = "2.12.8.0" '显示用版本名
Public Const VersionBaseName As String = "2.12.8.1" '显示用版本名
Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
#If RELEASE Then
Public Const VersionCode As Integer = 400 '正式版
Public Const VersionCode As Integer = 402 '正式版
#Else
Public Const VersionCode As Integer = 399 '快照版
Public Const VersionCode As Integer = 401 '快照版
#End If
'版本信息
@@ -710,13 +710,13 @@ Public Module ModBase
End If
If Not String.IsNullOrEmpty(Hash) Then
If Hash.Length < 35 Then 'MD5
Dim md5 As String = HashUtils.ComputeFromFile(LocalPath, HashUtils.HashMethod.Md5)
Dim md5 As String = CryptographyUtils.ComputeFileHash(LocalPath, CryptographyUtils.HashMethod.Md5)
If Hash.Lower <> md5 Then Return "文件 MD5 应为 " & Hash & ",实际为 " & md5
ElseIf Hash.Length = 64 Then 'SHA256
Dim sha256 As String = HashUtils.ComputeFromFile(LocalPath, HashUtils.HashMethod.Sha256)
Dim sha256 As String = CryptographyUtils.ComputeFileHash(LocalPath, CryptographyUtils.HashMethod.Sha256)
If Hash.Lower <> sha256 Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & sha256
Else 'SHA1 (40)
Dim sha1 As String = HashUtils.ComputeFromFile(LocalPath, HashUtils.HashMethod.Sha1)
Dim sha1 As String = CryptographyUtils.ComputeFileHash(LocalPath, CryptographyUtils.HashMethod.Sha1)
If Hash.Lower <> sha1 Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & sha1
End If
End If
@@ -793,30 +793,6 @@ Public Module ModBase
End Try
End Function
''' <summary>
''' 获取字符串哈希值。
''' </summary>
Public Function GetHash(Str As String) As ULong
Dim Result As ULong = 5381
For Each v In Str
Result = (Result << 5) Xor Result Xor CType(AscW(v), ULong)
Next
Return Result Xor &HA98F501BC684032FUL
End Function
''' <summary>
''' 获取字符串 MD5。
''' </summary>
Public Function GetStringMD5(Str As String) As String
Using h = MD5Cng.Create()
Dim hashedDataBytes = h.ComputeHash(Encoding.UTF8.GetBytes(Str))
Dim tmp As New StringBuilder(hashedDataBytes.Length * 2)
For Each i As Byte In hashedDataBytes
tmp.Append(i.ToString("x2"))
Next
Return tmp.ToString()
End Using
End Function
''' <summary>
''' 不会报错的 Val。
''' 如果输入有误,返回 0。
@@ -1399,12 +1375,17 @@ Public Module ModBase
''' 将程序中的资源释放到文件。
''' </summary>
Public Sub ExtractResources(FilePath As String, ResourceName As String)
Logger.Info($"将资源写入到文件:{ResourceName} → {FilePath}")
Dim Resource = My.Resources.ResourceManager.GetObject(ResourceName)
If TypeOf Resource Is Byte() Then
FileUtils.Write(FilePath, DirectCast(Resource, Byte()))
Dim Bytes = DirectCast(Resource, Byte())
If FileUtils.Exists(FilePath) AndAlso CryptographyUtils.ComputeFileHash(FilePath) = CryptographyUtils.ComputeHash(Bytes) Then Return
Logger.Info($"将资源写入到文件:{ResourceName} → {FilePath}")
FileUtils.Write(FilePath, Bytes)
ElseIf TypeOf Resource Is String Then
FileUtils.Write(FilePath, DirectCast(Resource, String))
Dim Content = DirectCast(Resource, String)
If FileUtils.Exists(FilePath) AndAlso FileUtils.ReadAsString(FilePath) = Content Then Return
Logger.Info($"将资源写入到文件:{ResourceName} → {FilePath}")
FileUtils.Write(FilePath, Content)
Else
Throw New Exception($"资源 {ResourceName} 的类型不支持:{Resource.GetType.Name}")
End If
@@ -8,7 +8,6 @@
Implements ILoadingTrigger
Public ReadOnly Property IsLoader As Boolean = True Implements ILoadingTrigger.IsLoader
Protected Shared LogBlackList As String() = {"EasyTier CLI"}
'基础属性
''' <summary>
@@ -90,7 +89,7 @@
Dim OldState = _State
If value = LoadState.Finished AndAlso Settings.Get(Of Boolean)("SystemDebugDelay") Then Thread.Sleep(RandomInteger(100, 2000))
_State = value
If Not LogBlackList.Contains(Name) Then Logger.Info($"加载器 {Name} 状态改变:{value}")
Logger.Log($"加载器 {Name} 状态改变:{value}", If(value = LoadState.Failed, LogLevel.Error, LogLevel.Info))
'实现 ILoadingTrigger 接口与 OnStateChanged 回调
RunInUi(
Sub()
@@ -319,13 +318,13 @@
Sub()
Try
IsForceRestarting = IsForceRestart
If Not LogBlackList.Contains(Name) Then Logger.Trace(Function() $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动")
Logger.Trace(Function() $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已{If(IsForceRestarting, "强制", "")}启动")
LoadDelegate(Me)
If IsInterrupted Then
Logger.Warn($"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已中断但线程正常运行至结束,输出被弃用(最新线程:{If(LastRunningThread Is Nothing, -1, LastRunningThread.ManagedThreadId)})")
Return
End If
If Not LogBlackList.Contains(Name) Then Logger.Trace(Function() $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已完成")
Logger.Trace(Function() $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已完成")
RaisePreviewFinish()
State = LoadState.Finished
LastFinishedTime = GetTimeMs() '未中断,本次输出有效
@@ -261,8 +261,11 @@ Retry:
})
End If
End SyncLock
CancelToken.Dispose() : CancelToken = New CancellationTokenSource(Timeout)
Response = RequestClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
ResilientUtils.RetryOn(Of FileNotFoundException, IOException)( 'CacheCow 读取缓存时若文件被占用,会调用不存在的 System.Net.Http.Formatting.resources,抛出此异常(#8150)
Sub()
CancelToken.Dispose() : CancelToken = New CancellationTokenSource(Timeout)
Response = RequestClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
End Sub)
Dim ResponseBytes As Byte()
CancelToken.Dispose() : CancelToken = New CancellationTokenSource(Timeout)
Using ResponseStream = Response.Content.ReadAsStreamAsync().GetResultWithTimeout(CancelToken, Timeout)
@@ -286,9 +289,6 @@ Retry:
Throw
Catch ex As HttpRequestException
Throw
Catch ex As FileNotFoundException
'由于 CacheCow 读取缓存失败,然后系统调用不存在的 System.Net.Http.Formatting.resources 抛出(#8150)
Throw New Exception($"网络请求时读取缓存失败", ex)
Catch ex As Exception
RecordIPReliability(HostIp, -1)
If TypeOf ex Is OperationCanceledException OrElse TypeOf ex Is TimeoutException Then 'CancellationToken 超时
@@ -333,7 +333,7 @@ NextInner:
Me.Type = Type
End Sub
Public Overrides Function GetHashCode() As Integer
Return GetHash(UserName & Password & BaseUrl & Token & Type) Mod Integer.MaxValue
Return (UserName & Password & BaseUrl & Token & Type).GetStableHashCode() Mod Integer.MaxValue
End Function
End Class
@@ -353,7 +353,7 @@ NextInner:
Type = McLoginType.Ms
End Sub
Public Overrides Function GetHashCode() As Integer
Return GetHash(OAuthRefreshToken & AccessToken & Uuid & UserName & ProfileJson) Mod Integer.MaxValue
Return (OAuthRefreshToken & AccessToken & Uuid & UserName & ProfileJson).GetStableHashCode() Mod Integer.MaxValue
End Function
End Class
Public Class McLoginLegacy
@@ -375,7 +375,7 @@ NextInner:
Type = McLoginType.Legacy
End Sub
Public Overrides Function GetHashCode() As Integer
Return GetHash(UserName & SkinType & SkinName & Type) Mod Integer.MaxValue
Return (UserName & SkinType & SkinName & Type).GetStableHashCode() Mod Integer.MaxValue
End Function
End Class
@@ -535,21 +535,15 @@ NextInner:
Data.Progress = 0.05
'检查是否已经登录完成
Dim ExpiresAt = Settings.Get(Of Long)("CacheMsV2Expires")
If Not Data.IsForceRestarting AndAlso '不要求强行重启
ExpiresAt > 0 AndAlso ExpiresAt > GetUnixTimestampUtc() Then 'AccessToken 尚未过期
'https://github.com/Meloong-Git/PCL/issues/8638
'如果是程序刚启动时调用的登录,此时加载器的状态为 Waiting,GetLoginData 函数会返回不带 ProfileJson 的数据作为输入
If String.IsNullOrEmpty(Input.ProfileJson) Then
Input = PageLoginMsSkin.GetLoginData(True)
End If
ExpiresAt > 0 AndAlso ExpiresAt > GetUnixTimestampUtc() AndAlso 'AccessToken 尚未过期
Input.UserName = Settings.Get(Of String)("CacheMsV2Name") Then
Data.Output = New McLoginResult With {
.AccessToken = Input.AccessToken,
.Name = Input.UserName,
.Uuid = Input.Uuid,
.Type = "Microsoft",
.ClientToken = Input.Uuid,
.ProfileJson = Input.ProfileJson
.Name = Input.UserName, .Type = "Microsoft",
.AccessToken = Settings.Get(Of String)("CacheMsV2Access"),
.Uuid = Settings.Get(Of String)("CacheMsV2Uuid"),
.ClientToken = Settings.Get(Of String)("CacheMsV2Uuid"),
.ProfileJson = Settings.Get(Of String)("CacheMsV2ProfileJson")
}
McLaunchLog("无需登录,AccessToken 尚未过期")
GoTo SkipLogin
@@ -1018,16 +1012,18 @@ Retry:
Result = NetRequestByClientRetry("https://api.minecraftservices.com/authentication/login_with_xbox", HttpMethod.Post,
Content:=Request, ContentType:="application/json", RequireJson:=True)
Catch ex As HttpRequestCodeException
Select Case ex.StatusCode
Case 429
Logger.Warn(ex, "微软登录第 4 步汇报 429")
Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
Case HttpStatusCode.Forbidden
Logger.Warn(ex, "微软登录第 4 步汇报 403")
Throw New Exception("$当前 IP 的登录尝试异常。" & vbCrLf & "如果你使用了 VPN 或加速器,请把它们关掉或更换节点后再试!")
Case Else
Throw
End Select
If ex.StatusCode = 429 Then
Logger.Warn(ex, "微软登录第 4 步汇报 429")
Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
ElseIf ex.StatusCode = HttpStatusCode.Forbidden Then
Logger.Warn(ex, "微软登录第 4 步汇报 403")
Throw New Exception("$当前 IP 的登录尝试异常。" & vbCrLf & "如果你使用了 VPN 或加速器,请把它们关掉或更换节点后再试!")
ElseIf ex.Response?.Contains("ACCOUNT_SUSPENDED") Then '#8655
MyMsgBox("该账号似乎已被微软封禁,无法登录。", "登录失败", "我知道了", IsWarn:=True)
Throw New Exception("$$")
Else
Throw
End If
End Try
Dim ResultJson As JObject = GetJson(Result)
@@ -1152,7 +1148,7 @@ Retry:
Return Uuid
End Function
Public Function McLoginLegacyUuid(Name As String)
Dim FullUuid As String = Name.Length.ToString("X").EnsureLength("0", 16) & GetHash(Name).ToString("X").EnsureLength("0", 16)
Dim FullUuid As String = Name.Length.ToString("X").EnsureLength("0", 16) & Name.GetStableHashCode().ToString("X").EnsureLength("0", 16)
Return FullUuid.Substring(0, 12) & "3" & FullUuid.Substring(13, 3) & "9" & FullUuid.Substring(17, 15)
End Function
@@ -1445,14 +1441,13 @@ NextInstance:
End If
'JLW
'修复 Java 18- 在 Windows UTF-8 Beta 模式(非 GBK 编码)下命令行参数乱码的问题(JDK-8272352)
'非 GBK 编码下命令行参数乱码的问题(JDK-8272352)
Dim UseJLW As Boolean =
Not Settings.Get(Of Boolean)("LaunchAdvanceDisableJLW") AndAlso Not Settings.Get(Of Boolean)("VersionAdvanceDisableJLW", McInstanceSelected) AndAlso
McLaunchJavaSelected.MajorVersion <= 18 AndAlso 'Java 19+ 已经修复了该 Bug
Not IsGBKEncoding AndAlso Not McFolderSelected.IsAsciiOnly() '仅在该环境下才会触发该 Bug
If UseJLW AndAlso CustomArg.Contains("-javaagent") Then
UseJLW = False
McLaunchLog("由于使用自定义 javaagent,已禁用 JLW,这可能导致游戏崩溃!", LogLevel.Warn)
McLaunchLog("由于使用了自定义 javaagent,已禁用 JLW,这可能导致游戏崩溃!", LogLevel.Warn)
End If
If UseJLW Then
If McLaunchJavaSelected.MajorVersion >= 9 Then
@@ -1286,7 +1286,7 @@ ExitDataLoad:
GoTo OnLoaded
End If
'有可用版本
Dim FolderListCheck As Integer = GetHash(Versions.MCINSTANCE_CACHE_VERSION & "#" & FolderList.Join("#"c)) Mod (Integer.MaxValue - 1) '根据文件夹名列表生成辨识码
Dim FolderListCheck As Integer = (Versions.MCINSTANCE_CACHE_VERSION & "#" & FolderList.Join("#"c)).GetStableHashCode() Mod (Integer.MaxValue - 1) '根据文件夹名列表生成辨识码
If Not McInstanceListForceRefresh AndAlso Val(ReadIni(PathMc & "PCL.ini", "InstanceCache")) = FolderListCheck Then
'可以使用缓存
Dim Result = InitMcInstanceListWithCache(PathMc)
@@ -1747,7 +1747,7 @@ OnLoaded:
''' </summary>
Public Function McSkinDownload(Address As String) As String
Dim SkinName As String = PathUtils.GetLastPart(Address)
Dim FileAddress As String = PathTemp & "Cache\Skin\" & GetHash(Address) & ".png"
Dim FileAddress As String = PathTemp & "Cache\Skin\" & Address.GetStableHashCode() & ".png"
SyncLock McSkinDownloadLock
If Not FileUtils.Exists(FileAddress) Then
NetDownloadByClient(Address, FileAddress & ".PCLDownloading")
@@ -162,11 +162,11 @@
'Else
If State = MinecraftState.Loading Then
'窗口未出现
WatcherLog("Minecraft 尚未加载完成,可能已崩溃")
WatcherLog("Minecraft 尚未加载完成,可能已崩溃", LogLevel.Error)
Crashed()
ElseIf GameProcess.ExitCode <> 0 AndAlso State = MinecraftState.Running AndAlso Instance.ReleaseTime.Year >= 2012 Then
'返回值不为 0 且未结束
WatcherLog("Minecraft 返回值异常,可能已崩溃")
WatcherLog("Minecraft 返回值异常,可能已崩溃", LogLevel.Error)
Crashed()
ElseIf State <> MinecraftState.Crashed Then
'正常关闭
@@ -215,15 +215,15 @@
ElseIf Text.Contains("Crash report saved to") OrElse Text.Contains("This crash report has been saved to:") Then
' Text.Contains("Minecraft ran into a problem! Report saved to:") Then
'Minecraft 崩溃,忽略 VanillaFix
WatcherLog("识别为崩溃的 Log:" & Text)
WatcherLog("识别为崩溃的 Log:" & Text, LogLevel.Error)
Crashed()
ElseIf Text.Contains("Could not save crash report to") Then
'Minecraft 崩溃,无法保存崩溃日志
WatcherLog("识别为崩溃的 Log:" & Text)
WatcherLog("识别为崩溃的 Log:" & Text, LogLevel.Error)
Crashed()
ElseIf Text.Contains("/ERROR]: Unable to launch") OrElse Text.Contains("An exception was thrown, the game will display an error screen and halt.") Then
'Forge 崩溃
WatcherLog("识别为崩溃的 Log:" & Text)
WatcherLog("识别为崩溃的 Log:" & Text, LogLevel.Error)
Crashed()
'ElseIf Text.Contains("Shutdown failure!") Then
' 'Minecraft 强行崩溃,由于点 X 强行关闭也会触发这句话,所以不可用
@@ -231,8 +231,8 @@
End If
End If
End Sub
Private Sub WatcherLog(Text As String)
McLaunchLog("[" & PID & "] " & Text)
Private Sub WatcherLog(Text As String, Optional Level As LogLevel = LogLevel.Info)
McLaunchLog("[" & PID & "] " & Text, Level)
End Sub
'进度更新
@@ -349,7 +349,7 @@
If State = MinecraftState.Crashed OrElse State = MinecraftState.Ended Then Return
State = MinecraftState.Crashed
'崩溃分析
WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析")
WatcherLog("Minecraft 已崩溃,将在 2 秒后开始崩溃分析", LogLevel.Error)
Hint("检测到 Minecraft 出现错误,错误分析已开始……", HintType.Red)
FeedbackInfo()
RunInNewThread(
+3 -2
View File
@@ -850,7 +850,8 @@ NextFile:
Text = Text.Replace("{path}", Replacer(PathUtils.CurrentFolder))
Text = Text.Replace("{path_with_name}", Replacer(PathExe))
Text = Text.Replace("{path_temp}", Replacer(PathTemp))
Text = Text.Replace("{pcl_md5}", Function() Replacer(HashUtils.ComputeFromFile(PathExe, HashUtils.HashMethod.Md5)))
Text = Text.Replace("{pcl_md5}", Function() Replacer(CryptographyUtils.ComputeFileHash(PathExe, CryptographyUtils.HashMethod.Md5)))
Text = Text.Replace("{pcl_sha1}", Function() Replacer(CryptographyUtils.ComputeFileHash(PathExe, CryptographyUtils.HashMethod.Sha1)))
'时间
If ReplaceTime Then '在窗口标题中,时间会被后续动态替换,所以此时不应该替换
Text = Text.Replace("{date}", Replacer(Date.Now.ToString("yyyy'/'M'/'d")))
@@ -976,7 +977,7 @@ NextFile:
''' </summary>
Public Sub FeedbackInfo()
On Error Resume Next
Logger.Info($"诊断信息:{vbCrLf}" &
Logger.Warn($"诊断信息:{vbCrLf}" &
"操作系统:" & My.Computer.Info.OSFullName & "(32 位:" & Is32BitSystem & ")" & vbCrLf &
"剩余内存:" & Int(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024) & " M / " & Int(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024) & " M" & vbCrLf &
"DPI:" & DPI & "(" & Math.Round(DPI / 96, 2) * 100 & "%)" & vbCrLf &
@@ -1,7 +1,5 @@
'由于包含加解密等安全信息,本文件中的部分代码已被删除
Imports System.Security.Cryptography
Friend Module ModSecret
'标注 PCL 的不同分支,仅用于替换标记
@@ -40,51 +38,6 @@ Friend Module ModSecret
#End Region
#Region "字符串加解密"
''' <summary>
''' 获取八位密钥。
''' </summary>
Private Function SecretKeyGet(Key As String) As String
Return "00000000"
End Function
''' <summary>
''' 加密字符串。
''' </summary>
Friend Function SecretEncrypt(SourceString As String, Optional Key As String = "") As String
Key = SecretKeyGet(Key)
Dim btKey As Byte() = Encoding.UTF8.GetBytes(Key)
Dim btIV As Byte() = Encoding.UTF8.GetBytes("87160295")
Dim des As New DESCryptoServiceProvider
Using MS As New MemoryStream
Dim inData As Byte() = Encoding.UTF8.GetBytes(SourceString)
Using cs As New CryptoStream(MS, des.CreateEncryptor(btKey, btIV), CryptoStreamMode.Write)
cs.Write(inData, 0, inData.Length)
cs.FlushFinalBlock()
Return Convert.ToBase64String(MS.ToArray())
End Using
End Using
End Function
''' <summary>
''' 解密字符串。
''' </summary>
Friend Function SecretDecrypt(SourceString As String, Optional Key As String = "") As String
Key = SecretKeyGet(Key)
Dim btKey As Byte() = Encoding.UTF8.GetBytes(Key)
Dim btIV As Byte() = Encoding.UTF8.GetBytes("87160295")
Dim des As New DESCryptoServiceProvider
Using MS As New MemoryStream
Dim inData As Byte() = Convert.FromBase64String(SourceString)
Using cs As New CryptoStream(MS, des.CreateDecryptor(btKey, btIV), CryptoStreamMode.Write)
cs.Write(inData, 0, inData.Length)
cs.FlushFinalBlock()
Return Encoding.UTF8.GetString(MS.ToArray())
End Using
End Using
End Function
#End Region
#Region "主题"
Public Color1 As New MyColor(52, 61, 74)
@@ -418,7 +418,7 @@ Finished:
Get
If _CurseForgeHash Is Nothing Then
'读取缓存
Dim CacheKey As String = GetHash($"{EnabledFullName}-{File.LastWriteTime.ToLongTimeString}-{File.Length}-C")
Dim CacheKey As String = $"{EnabledFullName}-{File.LastWriteTime.ToLongTimeString}-{File.Length}-C".GetStableHashCode()
Dim Cached As String = ReadIni(PathTemp & "Cache\ModHash.ini", CacheKey)
If Cached <> "" AndAlso Cached.RegexCheck("^\d+$") Then '#5062
_CurseForgeHash = Cached
@@ -473,14 +473,14 @@ Finished:
Get
If _ModrinthHash Is Nothing Then
'读取缓存
Dim CacheKey As String = GetHash($"{EnabledFullName}-{File.LastWriteTime.ToLongTimeString}-{File.Length}-M")
Dim CacheKey As String = $"{EnabledFullName}-{File.LastWriteTime.ToLongTimeString}-{File.Length}-M".GetStableHashCode()
Dim Cached As String = ReadIni(PathTemp & "Cache\ModHash.ini", CacheKey)
If Cached <> "" Then
_ModrinthHash = Cached
Return _ModrinthHash
End If
'计算 SHA1
_ModrinthHash = HashUtils.ComputeFromFile(File.FullName, HashUtils.HashMethod.Sha1)
_ModrinthHash = CryptographyUtils.ComputeFileHash(File.FullName, CryptographyUtils.HashMethod.Sha1)
'写入缓存
WriteIni(PathTemp & "Cache\ModHash.ini", CacheKey, _ModrinthHash)
End If
@@ -189,6 +189,7 @@
#Region "中文搜索"
Dim RawSearchText As String = If(Request.SearchText, "").Trim
RawSearchText = StrConv(RawSearchText, VbStrConv.SimplifiedChinese) '繁体转简体
RawSearchText = RawSearchText.Lower
Logger.Info($"工程列表搜索原始文本:{RawSearchText}")