Compare commits

...
31 Commits
Author SHA1 Message Date
龙腾猫跃 be67c4ccc2 2.12.3 2026-03-08 20:56:13 +08:00
龙腾猫跃 2bffafcd7a Merge pull request #8236 from PCL-Community/#8147
fix(download): OptiFine 与 LiteLoader 一并安装时安装路径错误
2026-03-03 04:50:17 +08:00
龙腾猫跃 8b49aea476 Merge pull request #8237 from PCL-Community/#7996
fix: Catch 后未 Continue For
2026-03-03 02:16:32 +08:00
wyc-26 9aa882e2fa fix: Catch 后未 Continue For 2026-03-01 09:37:07 +08:00
wyc-26 c184cc22e5 fix(download): OptiFine 与 LiteLoader 一并安装时安装路径错误 2026-03-01 09:03:22 +08:00
龙腾猫跃 156dadc202 Merge pull request #8003 from PCL-Community/#7979
fix(export): 导入配置时“打包资源文件,以避免在导入时下载”和“仅从Modrinth下载资源文件”允许同时勾选
2026-02-28 21:23:17 +08:00
龙腾猫跃 85aa14aa25 Merge pull request #8062 from LuoYun-Team/audio
chore: use MediaFoundationReader
2026-02-28 21:09:55 +08:00
龙腾猫跃 c75ee952c5 Merge pull request #8043 from 0x1A5/main
fix: Issue #7983
2026-02-28 21:02:06 +08:00
龙腾猫跃 d2d302901f Merge pull request #8190 from Ignis-Studio/main
chore: 更换杂志主页链接
2026-02-28 20:47:26 +08:00
龙腾猫跃 a601802dc5 Merge pull request #8206 from tangge233/fix/hash
fix(hash): 修复哈希处理部分资源处理问题
2026-02-28 20:34:46 +08:00
tangge233 dc7d6b3e91 chore: 漏了一个 2026-02-22 22:47:54 +08:00
tangge233 4c1578f9aa fix(hash): 修复哈希处理部分资源处理问题 2026-02-22 22:41:30 +08:00
Zhengyuan Huang 26ecd049c1 chore: 更换杂志主页链接 2026-02-18 12:30:03 +08:00
薄奚梦灵 15820c893f chore: fallback
Add error handling for audio file loading fallback.
2026-01-28 20:24:24 +08:00
薄奚梦灵 cd765a9097 chore: use MediaFoundationReader 2026-01-28 16:28:36 +08:00
YIJANGs 9f87b694cf 剪切板中复制联机码后跳转界面
取消检测是否有邀请码
直接返回
2026-01-24 12:06:31 +08:00
wyc-26 2749812fe3 fix(export): 导入配置时“打包资源文件,以避免在导入时下载”和“仅从Modrinth下载资源文件”允许同时勾选 2026-01-17 19:55:45 +08:00
龙腾猫跃 73bdc53309 2.12.2 正式版 2026-01-15 03:30:55 +08:00
龙腾猫跃 52b4c87fbc 2.12.2 2026-01-15 03:20:43 +08:00
龙腾猫跃 87f5cc2ea9 Merge pull request #7919 from PCL-Community/#7894
fix(cape): 更换披风后未更新当前选择的披风
2026-01-15 01:18:01 +08:00
龙腾猫跃 8323c1f116 Merge pull request #7948 from PCL-Community/#7932
feat(event): 增加“检查更新”自定义事件
2026-01-14 22:33:39 +08:00
龙腾猫跃 4ad3acb8ff Merge pull request #7906 from MoYuan-CN/fix/7904
fix(ModDownload): 跟进新的 NeoForge 命名方式以正确识别测试版
2026-01-14 21:48:22 +08:00
wyc-26 7ee3ebb51a feat(event): 增加“检查更新”自定义事件 2026-01-10 11:26:40 +08:00
wyc-26 1f1a3348bc Merge branch 'main' into #7894 2026-01-10 11:14:48 +08:00
wyc-26 8babdf990f refactor(Cape): 直接修改 ProfileJson 以防止卡登录 2026-01-10 11:12:09 +08:00
wyc-26 b8eab42c33 fix(cape): 更换披风后未更新当前选择的披风 2026-01-03 20:15:17 +08:00
墨渊and薄奚梦灵 3ec91e1b0b Update Plain Craft Launcher 2/Modules/Minecraft/ModDownload.vb
Co-authored-by: 薄奚梦灵 <[email protected]>
2026-01-02 10:27:24 +08:00
MoYuan-CN bd4498cec4 fix: unexcepted equal symbol 2026-01-02 04:16:45 +08:00
MoYuan-CN 6dc9dadf1c fix: mismatched neoforge type description 2026-01-02 04:11:13 +08:00
龙腾猫跃 68f4808c1c 2.12.1 正式版 2025-12-31 15:57:53 +08:00
龙腾猫跃 a674630f54 2.12.1 2025-12-27 05:37:27 +08:00
40 changed files with 3345 additions and 3615 deletions

No files matched your search

+7 -3
View File
@@ -145,8 +145,8 @@ WaitRetry:
Setup.Load("ToolDownloadCert")
Setup.Load("ToolDownloadSpeed")
'计时
Log("[Start] 第一阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
ApplicationStartTick = GetTimeTick()
Log("[Start] 第一阶段加载用时:" & GetTimeMs() - ApplicationStartTick & " ms")
ApplicationStartTick = GetTimeMs()
'执行测试
#If DEBUG Then
Test()
@@ -213,7 +213,11 @@ WaitRetry:
'WebP 特判
If Prefix = "Imazen.WebP" Then
SetDllDirectory(PathPure.TrimEnd("\"c))
WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64"))
Try
WriteFile(PathPure & "libwebp.dll", GetResources("libwebp64"))
Catch ex As Exception
Log(ex, "写入 libwebp.dll 失败") '防止同时加载多个图片时,同时写入文件导致文件占用,进而导致崩溃
End Try
End If
End If
Return LoadedAssembly(Prefix)
+2 -1
View File
@@ -126,7 +126,8 @@
Try
'下载
NetDownloadByLoader(
If(FallbackSource?.StartsWithF("http") AndAlso Source <> FallbackSource, {Source, FallbackSource}, {Source}),
If(FallbackSource IsNot Nothing AndAlso FallbackSource.StartsWithF("http") AndAlso Source <> FallbackSource,
{Source, FallbackSource}, {Source}),
TempPath, SimulateBrowserHeaders:=True)
ActualSource = TempPath
If Not EnableCache Then File.Delete(TempPath)
+2 -1
View File
@@ -95,7 +95,8 @@
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Canvas x:Name="ImgBack" IsHitTestVisible="False" Grid.Row="1" RenderOptions.BitmapScalingMode="LowQuality" />
<Canvas x:Name="ImgBack" IsHitTestVisible="False" Grid.Row="1" RenderOptions.BitmapScalingMode="LowQuality"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Grid x:Name="PanTitle" Height="48">
<Grid.Background>
<LinearGradientBrush EndPoint="1,0" StartPoint="0,0">
+47 -7
View File
@@ -11,8 +11,23 @@ Public Class FormMain
Dim FeatureList As New List(Of KeyValuePair(Of Integer, String))
'统计更新日志条目
#If BETA Then
If LastVersion < 381 Then 'Release 2.12.2
FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:无法搜索 CurseForge 上的社区资源"))
FeatureCount += 3
BugCount += 13
End If
If LastVersion < 379 Then 'Release 2.12.1
If LastVersion >= 376 Then
FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "删除:暂时隐藏联机入口……不过只是暂时关闭,它还会回来的!"))
End If
FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:适配新的 Minecraft 版本号系统与 Unobfuscated 版本"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:巨幅优化各个下载页面和 Mod 管理页面的性能"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:更换披风时会显示当前使用的披风"))
FeatureCount += 33
BugCount += 29
End If
If LastVersion < 376 Then 'Release 2.11.2
FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:联机功能!"))
'FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "新增:联机功能!"))
FeatureCount += 32
BugCount += 21
End If
@@ -118,6 +133,24 @@ Public Class FormMain
'3:BUG+ IMP* FEAT-
'2:BUG* IMP-
'1:BUG-
If LastVersion < 382 Then 'Snapshot 2.12.3
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:部分关键词搜不到资源,特别是中文 Mod 搜索经常没有结果"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "修复:下载可能完全卡住,或是下载进度反复回退"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "新增:内存管理设置,可以选择 G1GC、ZGC 或分代 ZGC"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:来自 Modrinth 的文件下载速度"))
FeatureCount += 15
BugCount += 16
End If
If LastVersion < 380 Then 'Snapshot 2.12.2
FeatureCount += 3
BugCount += 13
End If
If LastVersion < 378 Then 'Snapshot 2.12.1
If LastVersion >= 377 Then
FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "修复:在版本列表中存在 OptiFine 时可能无法加载版本列表"))
BugCount += 2
End If
End If
If LastVersion < 377 Then 'Snapshot 2.12.0
If LastVersion >= 373 Then
FeatureList.Add(New KeyValuePair(Of Integer, String)(5, "删除:暂时隐藏联机入口……不过只是暂时关闭,它还会回来的!"))
@@ -125,8 +158,8 @@ Public Class FormMain
FeatureList.Add(New KeyValuePair(Of Integer, String)(3, "新增:适配新的 Minecraft 版本号系统与 Unobfuscated 版本"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(2, "优化:巨幅优化各个下载页面和 Mod 管理页面的性能"))
FeatureList.Add(New KeyValuePair(Of Integer, String)(1, "优化:更换披风时会显示当前使用的披风"))
FeatureCount += 31
BugCount += 27
FeatureCount += 33
BugCount += 29
End If
If LastVersion < 375 Then 'Snapshot 2.11.2
'If LastVersion >= 373 Then
@@ -317,7 +350,7 @@ Public Class FormMain
'窗口加载
Private IsWindowLoadFinished As Boolean = False
Public Sub New()
ApplicationStartTick = GetTimeTick()
ApplicationStartTick = GetTimeMs()
'窗体参数初始化
FrmMain = Me
FrmLaunchLeft = New PageLaunchLeft
@@ -381,10 +414,10 @@ Public Class FormMain
'尽早执行的加载池
McFolderListLoader.Start(0) '为了让下载已存在文件检测可以正常运行,必须跑一次;为了让启动按钮尽快可用,需要尽早执行;为了与 PageLaunchLeft 联动,需要为 0 而不是 GetUuid
Log("[Start] 第二阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
Log("[Start] 第二阶段加载用时:" & GetTimeMs() - ApplicationStartTick & " ms")
End Sub
Private Sub FormMain_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
ApplicationStartTick = GetTimeTick()
ApplicationStartTick = GetTimeMs()
Handle = New Interop.WindowInteropHelper(Me).Handle
'读取设置
Setup.Load("UiBackgroundOpacity")
@@ -490,7 +523,7 @@ Public Class FormMain
Telemetry("启动")
End Sub, "初始化", ThreadPriority.Lowest)
Log("[Start] 第三阶段加载用时:" & GetTimeTick() - ApplicationStartTick & " ms")
Log("[Start] 第三阶段加载用时:" & GetTimeMs() - ApplicationStartTick & " ms")
End Sub
'根据打开次数触发的事件
Private Sub RunCountSub()
@@ -585,6 +618,12 @@ Public Class FormMain
Setup.Set("ToolDownloadTranslateV2", Setup.Get("ToolDownloadTranslate") + 1)
Log("[Start] 已从老版本迁移 Mod 命名设置")
End If
'重置 JVM 参数设置
If LastVersionCode <= 381 AndAlso Not Setup.IsUnset("LaunchAdvanceJvm") AndAlso
Setup.Get("LaunchAdvanceJvm").ToString.Replace("-XX:+UseG1GC ", "").Replace("-XX:-UseAdaptiveSizePolicy ", "").Trim = Setup.GetDefault("LaunchAdvanceJvm") Then
Setup.Reset("LaunchAdvanceJvm")
Log("[Start] 已重置 JVM 参数设置")
End If
'输出更新日志
If LastVersionCode <= 0 Then Return
If LowerVersionCode >= VersionCode Then Return
@@ -798,6 +837,7 @@ Public Class FormMain
Log(ex, "切回窗口时出错", LogLevel.Feedback)
End Try
'读取剪贴板,自动加入联机房间
Return 'UNDONE: 联机复活赛
If PageLinkMain.LinkState <> PageLinkMain.LinkStates.Waiting Then Return '已启动联机
If PageCurrent = PageType.Link Then Return '已在联机界面
Dim Code = ClipboardGetText() : If Code Is Nothing Then Return '剪贴板无文本
@@ -743,9 +743,9 @@ Public Module ModAnimation
''' <param name="AniGroup">由 Aa 开头的函数初始化的 AniData 对象集合。</param>
''' <param name="Name">动画组的名称。如果重复会直接停止同名动画组。</param>
Public Sub AniStart(AniGroup As IList, Optional Name As String = "", Optional RefreshTime As Boolean = False)
If RefreshTime Then AniLastTick = GetTimeTick() '避免处理动画时已经造成了极大的延迟,导致动画突然结束
If RefreshTime Then AniLastTick = GetTimeMs() '避免处理动画时已经造成了极大的延迟,导致动画突然结束
'添加到正在执行的动画组
Dim NewEntry As New AniGroupEntry With {.Data = GetFullList(Of AniData)(AniGroup), .StartTick = GetTimeTick()}
Dim NewEntry As New AniGroupEntry With {.Data = GetFullList(Of AniData)(AniGroup), .StartTick = GetTimeMs()}
If Name = "" Then
Name = NewEntry.Uuid
Else
@@ -788,7 +788,7 @@ Public Module ModAnimation
''' </summary>
Public Sub AniStart()
'初始化计时器
AniLastTick = GetTimeTick()
AniLastTick = GetTimeMs()
AniFPSTimer = AniLastTick
AniRunning = True '标记动画执行开始
@@ -798,9 +798,9 @@ Public Module ModAnimation
Log("[Animation] 动画线程开始")
Do While True
'两帧之间的间隔时间
Dim DeltaTime As Long = MathClamp(GetTimeTick() - AniLastTick, 0, 100000)
Dim DeltaTime As Long = MathClamp(GetTimeMs() - AniLastTick, 0, 100000)
If DeltaTime < 3 Then GoTo Sleeper
AniLastTick = GetTimeTick()
AniLastTick = GetTimeMs()
'记录 FPS
If ModeDebug Then
If MathClamp(AniLastTick - AniFPSTimer, 0, 100000) >= 500 Then
+151 -114
View File
@@ -14,13 +14,13 @@ Public Module ModBase
#Region "声明"
'下列版本信息由更新器自动修改
Public Const VersionBaseName As String = "2.12.0" '不含分支前缀的显示用版本名
Public Const VersionStandardCode As String = "2.12.0." & VersionBranchCode '标准格式的四段式版本号
Public Const VersionBaseName As String = "2.12.3" '不含分支前缀的显示用版本名
Public Const VersionStandardCode As String = "2.12.3." & VersionBranchCode '标准格式的四段式版本号
Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
#If BETA Then
Public Const VersionCode As Integer = 372 'Release
Public Const VersionCode As Integer = 381 'Release
#Else
Public Const VersionCode As Integer = 377 'Snapshot
Public Const VersionCode As Integer = 382 'Snapshot
#End If
'自动生成的版本信息
Public Const VersionDisplayName As String = VersionBranchName & " " & VersionBaseName
@@ -62,7 +62,7 @@ Public Module ModBase
''' <summary>
''' 程序的打开计时。
''' </summary>
Public ApplicationStartTick As Long = GetTimeTick()
Public ApplicationStartTick As Long = GetTimeMs()
''' <summary>
''' 程序打开时的时间。
''' </summary>
@@ -589,8 +589,7 @@ Public Module ModBase
''' </summary>
Public Function ReadReg(Key As String, Optional DefaultValue As String = "") As String
Try
Dim SubKey = My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, False)
Return If(SubKey?.GetValue(Key), DefaultValue)
Return If(My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, False)?.GetValue(Key), DefaultValue)
Catch ex As Exception
Log(ex, "读取注册表出错:" & Key, LogLevel.Hint)
Return DefaultValue
@@ -1072,25 +1071,25 @@ Public Module ModBase
''' 获取文件 MD5,若失败则返回空字符串。
''' </summary>
Public Function GetFileMD5(FilePath As String) As String
Dim Retry As Boolean = False
Dim Retried As Boolean = False
Re:
Try
'获取 MD5
Dim Result As New StringBuilder()
Dim File As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Dim MD5 As MD5 = New MD5CryptoServiceProvider()
Dim Retval As Byte() = MD5.ComputeHash(File)
File.Close()
For i = 0 To Retval.Length - 1
Result.Append(Retval(i).ToString("x2"))
Next
Return Result.ToString
Using File As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Using h = MD5Cng.Create()
Dim Retval As Byte() = h.ComputeHash(File)
Dim Result As New StringBuilder(Retval.Length * 2)
For i = 0 To Retval.Length - 1
Result.Append(Retval(i).ToString("x2"))
Next
Return Result.ToString
End Using
End Using
Catch ex As Exception
If Retry OrElse TypeOf ex Is FileNotFoundException Then
If Retried OrElse TypeOf ex Is FileNotFoundException Then
Log(ex, "获取文件 MD5 失败:" & FilePath)
Return ""
Else
Retry = True
Retried = True
Log(ex, "获取文件 MD5 可重试失败:" & FilePath, LogLevel.Normal)
Thread.Sleep(RandomInteger(200, 500))
GoTo Re
@@ -1101,27 +1100,25 @@ Re:
''' 获取文件 SHA512,若失败则返回空字符串。
''' </summary>
Public Function GetFileSHA512(FilePath As String) As String
Dim Retry As Boolean = False
Dim Retried As Boolean = False
Re:
Try
''检测该文件是否在下载中,若在下载则放弃检测
'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
'获取 SHA512
Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Dim sha512 As SHA512 = New SHA512CryptoServiceProvider()
Dim retval As Byte() = sha512.ComputeHash(file)
file.Close()
Dim Result As New StringBuilder()
For i As Integer = 0 To retval.Length - 1
Result.Append(retval(i).ToString("x2"))
Next
Return Result.ToString
Using File As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Using h = SHA512Cng.Create()
Dim retval As Byte() = h.ComputeHash(File)
Dim Result As New StringBuilder(retval.Length * 2)
For i As Integer = 0 To retval.Length - 1
Result.Append(retval(i).ToString("x2"))
Next
Return Result.ToString
End Using
End Using
Catch ex As Exception
If Retry OrElse TypeOf ex Is FileNotFoundException Then
If Retried OrElse TypeOf ex Is FileNotFoundException Then
Log(ex, "获取文件 SHA512 失败:" & FilePath)
Return ""
Else
Retry = True
Retried = True
Log(ex, "获取文件 SHA512 可重试失败:" & FilePath, LogLevel.Normal)
Thread.Sleep(RandomInteger(200, 500))
GoTo Re
@@ -1135,18 +1132,16 @@ Re:
Dim Retry As Boolean = False
Re:
Try
''检测该文件是否在下载中,若在下载则放弃检测
'If IgnoreOnDownloading AndAlso NetManage.Files.ContainsKey(FilePath) AndAlso NetManage.Files(FilePath).State <= NetState.Merge Then Return ""
'获取 SHA256
Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Dim sha256 As SHA256 = New SHA256CryptoServiceProvider()
Dim retval As Byte() = sha256.ComputeHash(file)
file.Close()
Dim Result As New StringBuilder()
For i As Integer = 0 To retval.Length - 1
Result.Append(retval(i).ToString("x2"))
Next
Return Result.ToString
Using File As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Using h = SHA256Cng.Create()
Dim retval As Byte() = h.ComputeHash(File)
Dim Result As New StringBuilder(retval.Length * 2)
For i As Integer = 0 To retval.Length - 1
Result.Append(retval(i).ToString("x2"))
Next
Return Result.ToString()
End Using
End Using
Catch ex As Exception
If Retry OrElse TypeOf ex Is FileNotFoundException Then
Log(ex, "获取文件 SHA256 失败:" & FilePath)
@@ -1166,16 +1161,16 @@ Re:
Dim Retry As Boolean = False
Re:
Try
'获取 SHA1
Dim file As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Dim sha1 As SHA1 = New SHA1CryptoServiceProvider()
Dim retval As Byte() = sha1.ComputeHash(file)
file.Close()
Dim Result As New StringBuilder()
For i As Integer = 0 To retval.Length - 1
Result.Append(retval(i).ToString("x2"))
Next
Return Result.ToString
Using File As New FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)
Using h = SHA1Cng.Create()
Dim retval As Byte() = h.ComputeHash(File)
Dim Result As New StringBuilder(retval.Length * 2)
For i As Integer = 0 To retval.Length - 1
Result.Append(retval(i).ToString("x2"))
Next
Return Result.ToString
End Using
End Using
Catch ex As Exception
If Retry OrElse TypeOf ex Is FileNotFoundException Then
Log(ex, "获取文件 SHA1 失败:" & FilePath)
@@ -1285,27 +1280,31 @@ Re:
''' <summary>
''' 尝试根据后缀名判断文件种类并解压文件,支持 gz 与 zip,会尝试将 jar 以 zip 方式解压。
''' 会尝试创建,但不会清空目标文件夹。
''' 会先使用 UTF-8 解压,失败后换用 GB18030 解压。
''' </summary>
Public Sub ExtractFile(CompressFilePath As String, DestDirectory As String, Optional Encode As Encoding = Nothing,
Optional ProgressIncrementHandler As Action(Of Double) = Nothing)
Public Sub ExtractCompressedFile(CompressFilePath As String, DestDirectory As String, Optional ProgressIncrementHandler As Action(Of Double) = Nothing)
Directory.CreateDirectory(DestDirectory)
'解压 gz(gz 不需要考虑编码)
If CompressFilePath.EndsWithF(".gz", True) Then
'以 gz 方式解压
Dim stream As New GZipStream(New FileStream(CompressFilePath, FileMode.Open, FileAccess.ReadWrite), CompressionMode.Decompress)
Dim decompressedFile As New FileStream(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", ""), FileMode.OpenOrCreate, FileAccess.Write)
Dim data As Integer = stream.ReadByte()
While data <> -1
decompressedFile.WriteByte(data)
data = stream.ReadByte()
End While
decompressedFile.Close()
stream.Close()
Else
'以 zip 方式解压
Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, If(Encode, Encoding.GetEncoding("GB18030")))
Dim TotalCount As Long = Archive.Entries.Count
For Each Entry As ZipArchiveEntry In Archive.Entries
If ProgressIncrementHandler IsNot Nothing AndAlso TotalCount > 0 Then ProgressIncrementHandler(1 / TotalCount)
Using stream As New GZipStream(New FileStream(CompressFilePath, FileMode.Open, FileAccess.Read, FileShare.Read), CompressionMode.Decompress)
Using decompressedFile As New FileStream(DestDirectory & GetFileNameFromPath(CompressFilePath).ToLower.Replace(".tar", "").Replace(".gz", ""),
FileMode.OpenOrCreate, FileAccess.Write)
Dim data As Integer = stream.ReadByte()
While data <> -1
decompressedFile.WriteByte(data)
data = stream.ReadByte()
End While
End Using
End Using
Return
End If
'解压 zip
Dim TryExtractZip =
Sub(Encoding As Encoding)
Using Archive = ZipFile.Open(CompressFilePath, ZipArchiveMode.Read, Encoding)
Dim EntryCount = Archive.Entries.Count
For Each Entry In Archive.Entries
If ProgressIncrementHandler IsNot Nothing AndAlso EntryCount > 0 Then ProgressIncrementHandler(1 / EntryCount)
Dim DestinationFullPath As String = IO.Path.Combine(DestDirectory, Entry.FullName)
If Not IO.Path.GetFullPath(DestinationFullPath).StartsWithF(IO.Path.GetFullPath(DestDirectory), True) Then Continue For 'ZipSlip 漏洞修复
If DestinationFullPath.EndsWithF("\") OrElse DestinationFullPath.EndsWithF("/") Then Continue For '不创建空文件夹
@@ -1314,7 +1313,17 @@ Re:
Entry.ExtractToFile(DestinationFullPath.Replace(DestinationDirectory, ShortenPath(DestinationDirectory)), True) '#7329
Next
End Using
End If
End Sub
Try
TryExtractZip(New UTF8Encoding(False, True))
Catch ex As Exception
Log(ex, $"首次解压尝试失败,将更换编码并重试({CompressFilePath} → {DestDirectory})")
Try
TryExtractZip(Encoding.GetEncoding("GB18030"))
Catch
Throw
End Try
End Try
End Sub
''' <summary>
@@ -1610,6 +1619,7 @@ RetryDir:
End Function
''' <summary>
''' 分割字符串。
''' 若原始字符串为空,则返回 {""}。
''' </summary>
<Extension> Public Function Split(FullStr As String, SplitStr As String) As String()
If SplitStr.Length = 1 Then
@@ -1633,14 +1643,14 @@ RetryDir:
''' 获取字符串 MD5。
''' </summary>
Public Function GetStringMD5(Str As String) As String
Dim md5Hasher As New MD5CryptoServiceProvider
Dim hashedDataBytes As Byte()
hashedDataBytes = md5Hasher.ComputeHash(Encoding.GetEncoding("gb2312").GetBytes(Str))
Dim tmp As New StringBuilder()
For Each i As Byte In hashedDataBytes
tmp.Append(i.ToString("x2"))
Next
Return tmp.ToString()
Using h = MD5Cng.Create()
Dim hashedDataBytes = h.ComputeHash(Encoding.GetEncoding("gb2312").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>
''' 检查字符串中的字符是否均为 ASCII 字符。
@@ -1874,50 +1884,53 @@ RetryDir:
''' <param name="Source">被搜索的长内容。</param>
''' <param name="Query">用户输入的搜索文本。</param>
Private Function SearchSimilarity(Source As String, Query As String) As Double
If String.IsNullOrEmpty(Source) OrElse String.IsNullOrEmpty(Query) Then Return 0
Dim qp As Integer = 0, lenSum As Double = 0
Source = Source.ToLower.Replace(" ", "")
Query = Query.ToLower.Replace(" ", "")
Dim sourceLength As Integer = Source.Length, queryLength As Integer = Query.Length '用于计算最后因数的长度缓存
Dim str As New StringBuilder(Source.Length)
str.Append(Source.ToLower().Replace(" ", ""))
Query = Query.ToLower().Replace(" ", "")
Dim sourceLength As Integer = str.Length, queryLength As Integer = Query.Length '用于计算最后因数的长度缓存
If queryLength = 0 Then Return 0
Do While qp < queryLength
'对 qp 作为开始位置计算
Dim sp As Integer = 0, lenMax As Integer = 0, spMax As Integer = 0
'查找以 qp 为头的最大子串
Do While sp < Source.Length
'对每个 sp 作为开始位置计算最大子串
Dim currentSourceLength As Integer = str.Length
Do While sp < currentSourceLength
Dim len As Integer = 0
Do While (qp + len) < queryLength AndAlso (sp + len) < Source.Length AndAlso Source(sp + len) = Query(qp + len)
While (qp + len) < queryLength AndAlso (sp + len) < currentSourceLength AndAlso str(sp + len) = Query(qp + len)
len += 1
Loop
End While
'存储 len
If len > lenMax Then
lenMax = len
spMax = sp
End If
'根据结果增加 sp
sp += Math.Max(1, len)
sp += If(len > 0, len, 1)
Loop
If lenMax > 0 Then
Source = Source.Substring(0, spMax) & If(Source.Count > spMax + lenMax, Source.Substring(spMax + lenMax), String.Empty) '将源中的对应字段替换空
str.Remove(spMax, lenMax) '将源中的对应字段移除
'存储 lenSum
Dim IncWeight = (Math.Pow(1.4, 3 + lenMax) - 3.6) '根据长度加成
IncWeight *= 1 + 0.3 * Math.Max(0, 3 - Math.Abs(qp - spMax)) '根据位置加成
lenSum += IncWeight
End If
'根据结果增加 qp
qp += Math.Max(1, lenMax)
qp += If(lenMax > 0, lenMax, 1)
Loop
'计算结果:重复字段量 × 源长度影响比例
Return (lenSum / queryLength) * (3 / Math.Pow(sourceLength + 15, 0.5)) * If(queryLength <= 2, 3 - queryLength, 1)
Return (lenSum / queryLength) * (3 / Math.Sqrt(sourceLength + 15)) * If(queryLength <= 2, 3 - queryLength, 1)
End Function
''' <summary>
''' 获取多段文本加权后的相似度。
''' </summary>
Private Function SearchSimilarityWeighted(Source As List(Of KeyValuePair(Of String, Double)), Query As String) As Double
Private Function SearchSimilarityWeighted(Source As List(Of SearchSource), Query As String) As Double
Dim TotalWeight As Double = 0
Dim Sum As Double = 0
For Each Pair In Source
Sum += SearchSimilarity(Pair.Key, Query) * Pair.Value
TotalWeight += Pair.Value
If Pair.Aliases.Any Then
Sum += Pair.Aliases.Max(Function(a) SearchSimilarity(a, Query)) * Pair.Weight
End If
TotalWeight += Pair.Weight
Next
Return Sum / TotalWeight
End Function
@@ -1930,9 +1943,10 @@ RetryDir:
''' </summary>
Public Item As T
''' <summary>
''' 该项目用于搜索的源。
''' 该项目用于搜索的文本源。
''' 在搜索时,会对每个文本源单独加权,但单个文本源内的多个别名只取最高的一个的相似度。
''' </summary>
Public SearchSource As List(Of KeyValuePair(Of String, Double))
Public SearchSource As List(Of SearchSource)
''' <summary>
''' 相似度。
''' </summary>
@@ -1941,9 +1955,28 @@ RetryDir:
''' 是否完全匹配。
''' </summary>
Public AbsoluteRight As Boolean
Public Overrides Function ToString() As String
Return Math.Round(Similarity, 3) & " - " & Item.ToString()
End Function
End Class
''' <summary>
''' 单个用于搜索的文本源。
''' </summary>
Public Class SearchSource
Public Aliases As String()
Public Weight As Double
Public Sub New(Aliases As String(), Optional Weight As Double = 1)
Me.Aliases = Aliases
Me.Weight = Weight
End Sub
Public Sub New(Text As String, Optional Weight As Double = 1)
Me.Aliases = {Text}
Me.Weight = Weight
End Sub
End Class
''' <summary>
''' 进行多段文本加权搜索,获取相似度较高的数项结果。
''' 在搜索时,会对每个文本源单独加权,但单个文本源内的多个别名只取最高的一个的相似度。
''' </summary>
''' <param name="MaxBlurCount">返回的最大模糊结果数。</param>
''' <param name="MinBlurSimilarity">返回结果要求的最低相似度。</param>
@@ -1952,15 +1985,19 @@ RetryDir:
Dim ResultList As New List(Of SearchEntry(Of T))
If Not Entries.Any() Then Return ResultList
'进行搜索,获取相似信息
Dim QueryParts = Query.Split({" "c}, StringSplitOptions.RemoveEmptyEntries)
Dim Candidates As New List(Of SearchEntry(Of T))
For Each Entry In Entries
Entry.Similarity = SearchSimilarityWeighted(Entry.SearchSource, Query)
Entry.AbsoluteRight =
Query.Split(" ").All( '对于按空格分割的每一段
QueryParts.All( '对于按空格分割的每一段
Function(QueryPart) Entry.SearchSource.Any( '若与任意一个搜索源完全匹配,则标记为完全匹配项
Function(Source) Source.Key.Replace(" ", "").ContainsF(QueryPart, True)))
Function(Source) Source.Aliases.Any(
Function([Alias]) [Alias].Replace(" ", "").ContainsF(QueryPart, True))))
If Entry.AbsoluteRight OrElse Entry.Similarity >= MinBlurSimilarity Then Candidates.Add(Entry)
Next
'按照相似度进行排序
Entries = Entries.SortByComparison(
Candidates = Candidates.SortByComparison(
Function(Left, Right) As Boolean
If Left.AbsoluteRight Xor Right.AbsoluteRight Then
Return Left.AbsoluteRight
@@ -1970,11 +2007,11 @@ RetryDir:
End Function)
'返回结果
Dim BlurCount As Integer = 0
For Each Entry In Entries
For Each Entry In Candidates
If Entry.AbsoluteRight Then
ResultList.Add(Entry) '完全匹配,直接加入
Else
If Entry.Similarity < MinBlurSimilarity OrElse BlurCount = MaxBlurCount Then Exit For '模糊结果边界条件
If BlurCount = MaxBlurCount Then Exit For
ResultList.Add(Entry)
BlurCount += 1 '模糊结果计数
End If
@@ -2153,7 +2190,7 @@ RetryDir:
''' </summary>
<Extension> Public Function GetResultWithTimeout(Of T)(TargetTask As Task(Of T), TokenSource As CancellationTokenSource, TimeoutMs As Integer) As T
Dim DelayTask = Task.Delay(TimeoutMs)
If Task.WhenAny(TargetTask, DelayTask).GetAwaiter().GetResult() Is DelayTask Then
If Task.WhenAny(TargetTask, DelayTask).ConfigureAwait(False).GetAwaiter().GetResult() Is DelayTask Then
TokenSource.Cancel()
Throw New TimeoutException($"任务超时({TimeoutMs} ms)")
End If
@@ -2164,7 +2201,7 @@ RetryDir:
''' </summary>
<Extension> Public Sub GetResultWithTimeout(TargetTask As Task, TokenSource As CancellationTokenSource, TimeoutMs As Integer)
Dim DelayTask = Task.Delay(TimeoutMs)
If Task.WhenAny(TargetTask, DelayTask).GetAwaiter().GetResult() Is DelayTask Then
If Task.WhenAny(TargetTask, DelayTask).ConfigureAwait(False).GetAwaiter().GetResult() Is DelayTask Then
TokenSource.Cancel()
Throw New TimeoutException($"任务超时({TimeoutMs} ms)")
End If
@@ -2298,10 +2335,10 @@ NextElement:
Return Date.Now.ToString("HH':'mm':'ss'.'fff")
End Function
''' <summary>
''' 获取系统运行时间(毫秒),保证为正 Long 且大于 1,但可能突变减小。
''' 获取一个单调递增时间值(毫秒)。
''' </summary>
Public Function GetTimeTick() As Long
Return My.Computer.Clock.TickCount + 2147483651L
Public Function GetTimeMs() As Long
Return Stopwatch.GetTimestamp() \ (Stopwatch.Frequency \ 1000L)
End Function
''' <summary>
''' 将时间间隔转换为类似“5 分 10 秒前”的易于阅读的形式。
@@ -2515,7 +2552,7 @@ NextElement:
''' 选择最大值对应的对象。
''' 若没有元素则返回 Nothing。
''' </summary>
<Extension> Public Function MaxOf(Of T, C As IComparable(Of C))(Source As IEnumerable(Of T), Selector As Func(Of T, C)) As T
<Extension> Public Function MaxBy(Of T, C As IComparable(Of C))(Source As IEnumerable(Of T), Selector As Func(Of T, C)) As T
Using Enumerator = Source.GetEnumerator()
If Not Enumerator.MoveNext() Then Return Nothing
Dim MaxItem As T = Enumerator.Current
@@ -2533,7 +2570,7 @@ NextElement:
''' 选择最小值对应的对象。
''' 若没有元素则返回 Nothing。
''' </summary>
<Extension> Public Function MinOf(Of T, C As IComparable(Of C))(List As IEnumerable(Of T), Selector As Func(Of T, C)) As T
<Extension> Public Function MinBy(Of T, C As IComparable(Of C))(List As IEnumerable(Of T), Selector As Func(Of T, C)) As T
Using Enumerator = List.GetEnumerator()
If Not Enumerator.MoveNext() Then Return Nothing
Dim MinItem As T = Enumerator.Current
@@ -295,7 +295,7 @@
If (State = LoadState.Loading OrElse State = LoadState.Finished) AndAlso '正在加载或已结束…
(IgnoreReloadTimeout OrElse ReloadTimeout = -1 OrElse
LastFinishedTime = 0 OrElse GetTimeTick() - LastFinishedTime < ReloadTimeout) Then '…且没有超时…
LastFinishedTime = 0 OrElse GetTimeMs() - LastFinishedTime < ReloadTimeout) Then '…且没有超时…
Return False '…则不重试
Else
Return True '否则需要重启
@@ -328,7 +328,7 @@
If ModeDebug AndAlso Not LogBlackList.Contains(Name) Then Log($"[Loader] 加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已完成")
RaisePreviewFinish()
State = LoadState.Finished
LastFinishedTime = GetTimeTick() '未中断,本次输出有效
LastFinishedTime = GetTimeMs() '未中断,本次输出有效
Catch ex As CancelledException
If ModeDebug Then Log(ex, $"加载线程 {Name} ({Thread.CurrentThread.ManagedThreadId}) 已触发取消中断,已完成 {Math.Round(Progress * 100)}%")
If Not IsAborted Then State = LoadState.Aborted
+127 -183
View File
@@ -1,5 +1,4 @@
Imports System.Net.Http.Headers
Imports System.Net.Sockets
Imports System.Net.Sockets
Imports System.Runtime.CompilerServices
Imports System.Threading.Tasks
@@ -52,7 +51,7 @@ Public Module ModNet
Optional Encoding As Encoding = Nothing, Optional SimulateBrowserHeaders As Boolean = False) As String
Dim RetryCount As Integer = 0
Dim RetryException As Exception = Nothing
Dim StartTime As Long = GetTimeTick()
Dim StartTime As Long = GetTimeMs()
Try
Retry:
Select Case RetryCount
@@ -62,7 +61,7 @@ Retry:
Thread.Sleep(500)
Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 30000, Headers, RequireJson, Encoding, SimulateBrowserHeaders)
Case Else '快速重试
If GetTimeTick() - StartTime > 5500 Then
If GetTimeMs() - StartTime > 5500 Then
'若前两次加载耗费 5 秒以上,才进行重试
Thread.Sleep(500)
Return NetRequestByClient(Url, Method, Content, ContentType, Accept, 4000, Headers, RequireJson, Encoding, SimulateBrowserHeaders)
@@ -248,7 +247,7 @@ RequestFinished:
SecretHeadersSign(Url, Request, SimulateBrowserHeaders)
'DNS 解析
CancelToken = New CancellationTokenSource(Timeout)
HostIp = DNSLookup(Request, CancelToken)
HostIp = DNSLookup(Request, CancelToken.Token)
If HostIp IsNot Nothing AndAlso Not IPReliability.ContainsKey(HostIp) Then
IPReliability(HostIp) = -0.01 '预先降低一点,这样快速的重复请求会使用不同的 IP 以提高成功率
End If
@@ -263,11 +262,12 @@ RequestFinished:
End If
End SyncLock
Response = RequestClient.SendAsync(Request, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
Dim ResponseStream = Response.Content.ReadAsStreamAsync().GetAwaiter().GetResult()
Dim ResponseBytes As Byte()
Using Stream As New MemoryStream
ResponseStream.CopyToAsync(Stream, 81920, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
ResponseBytes = Stream.ToArray()
Using ResponseStream = Response.Content.ReadAsStreamAsync().GetResultWithTimeout(CancelToken, Timeout)
Using Stream As New MemoryStream
ResponseStream.CopyToAsync(Stream, 81920, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
ResponseBytes = Stream.ToArray()
End Using
End Using
'输出
If Response.IsSuccessStatusCode Then
@@ -304,10 +304,9 @@ RequestFinished:
''' <summary>
''' 进行 DNS 解析。它仅在选择的 IP 与系统默认的不一致时才对 URL 中的 Host 进行替换。
''' 返回请求时应使用的 IP;若为 IPv6,则加上了方括号。
''' 若解析失败,则返回 Nothing。
''' 返回请求时应使用的 IP。若解析失败,则返回 Nothing。
''' </summary>
Private Function DNSLookup(Request As HttpRequestMessage, CancelToken As CancellationTokenSource) As String
Private Function DNSLookup(Request As HttpRequestMessage, CancelToken As CancellationToken) As String
'若一分钟内已失败过,则不再重复尝试解析,以减少断网时的 Exception 数量
Dim Host = Request.RequestUri.Host
SyncLock DNSFailureRecord.SyncRoot
@@ -320,43 +319,36 @@ RequestFinished:
End If
End SyncLock
'初步 DNS 解析
Dim DnsTask As Task(Of IPAddress()) = Nothing
Dim Candidates As IPAddress()
Try
DnsTask = Dns.GetHostAddressesAsync(Host)
DnsTask.Wait(CancelToken.Token)
Dim AsyncResult = Dns.BeginGetHostAddresses(Host, Nothing, Nothing)
If WaitHandle.WaitAny(New WaitHandle() {AsyncResult.AsyncWaitHandle, CancelToken.WaitHandle}) Then Throw New TimeoutException("DNS 解析超时")
Candidates = Dns.EndGetHostAddresses(AsyncResult).Distinct.ToArray
Catch ex As Exception
Log(ex, $"DNS 解析失败({Host})")
DNSFailureRecord(Host) = Date.Now
Return Nothing
End Try
Dim Candidates As IPAddress() = DnsTask.Result.ToArray
If Not Candidates.Any Then
Log($"[Net] DNS 解析无结果({Host})")
Return Nothing
End If
'若同时存在 IPv4 和 IPv6 地址,仅选择其中一类(因为 GFW 可能只屏蔽了 IPv4 或 IPv6)
Dim GetIPReliability = Function(Key) IPReliability.GetOrDefault(Key.ToString, 0)
Dim Reliabilities = Candidates.ToDictionary(Function(i) i, Function(i) IPReliability.GetOrDefault(i.ToString, 0))
Dim IPv4Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetwork).ToArray
Dim IPv6Targets = Candidates.Where(Function(i) i.AddressFamily = AddressFamily.InterNetworkV6).ToArray
If IPv4Targets.Any AndAlso IPv6Targets.Any Then
Dim IPv4Reliability = IPv4Targets.Average(Function(i) GetIPReliability(i))
Dim IPv6Reliability = IPv6Targets.Average(Function(i) GetIPReliability(i))
Dim IPv4Reliability = IPv4Targets.Max(Function(i) Reliabilities(i))
Dim IPv6Reliability = IPv6Targets.Max(Function(i) Reliabilities(i))
If Host = "api.modrinth.com" Then IPv6Reliability -= 0.1 '让 Modrinth 优先使用 IPv4 地址(#6887)
If ModeDebug Then Log($"[Net] DNS IPv4/IPv6 选择({Host}),IPv4 {IPv4Reliability:0.000},IPv6 {IPv6Reliability:0.000}")
Candidates = If(IPv4Reliability >= IPv6Reliability, IPv4Targets, IPv6Targets)
End If
'选择可靠度最高的 IP
Dim MaxReliability As Double = Candidates.Max(Function(i) GetIPReliability(i))
Dim Target As IPAddress = Candidates.First(Function(i) GetIPReliability(i) = MaxReliability)
Dim TargetIp As String = If(Target.AddressFamily = AddressFamily.InterNetworkV6, $"[{Target}]", Target.ToString)
If Target IsNot Candidates.First Then
'优选结果与系统默认 IP 不一致,替换域名并设置 Host 头
If ModeDebug Then Log($"[Net] DNS 解析结果:{Host} → {Target},所有可能的 IP 与可靠度:{DnsTask.Result.Select(Function(i) $"{i} → {GetIPReliability(i):0.000}").Join(";")}")
Request.Headers.Host = Host
Dim Builder As New UriBuilder(Request.RequestUri)
Builder.Host = TargetIp
Request.RequestUri = Builder.Uri
End If
Return TargetIp
Dim Target As IPAddress = Candidates.MaxBy(Function(i) Reliabilities(i))
Request.Headers.Host = Request.RequestUri.Authority
Request.RequestUri = (New UriBuilder(Request.RequestUri) With {.Host = If(Target.AddressFamily = AddressFamily.InterNetworkV6, $"[{Target}]", Target.ToString)}).Uri
Return Target.ToString
End Function
Private DNSFailureRecord As New SafeDictionary(Of String, Date)
''' <summary>
@@ -369,8 +361,10 @@ RequestFinished:
''' </summary>
Private Sub RecordIPReliability(IP As String, Result As Double)
If IP Is Nothing Then Return
If Not IPReliability.ContainsKey(IP) Then IPReliability(IP) = 0
IPReliability(IP) = (IPReliability(IP) + Result) / 2
SyncLock IPReliability.SyncRoot
If Not IPReliability.ContainsKey(IP) Then IPReliability(IP) = 0
IPReliability(IP) = (IPReliability(IP) + Result) / 2
End SyncLock
End Sub
''' <summary>
@@ -418,12 +412,10 @@ RequestFinished:
''' 基于限速,当前可以下载的剩余量。
''' </summary>
Public NetTaskSpeedLimitLeft As Long = -1
Private ReadOnly NetTaskSpeedLimitLeftLock As New Object
''' <summary>
''' 正在运行中的线程数。
''' </summary>
Public NetTaskThreadCount As Integer = 0
Private ReadOnly NetTaskThreadCountLock As New Object
''' <summary>
''' 下载源。
@@ -586,7 +578,7 @@ RequestFinished:
''' <summary>
''' 上次记速时的时间。
''' </summary>
Private SpeedLastTime As Long = GetTimeTick()
Private SpeedLastTime As Long = GetTimeMs()
''' <summary>
''' 上次记速时的已下载大小。
''' </summary>
@@ -596,8 +588,8 @@ RequestFinished:
''' </summary>
Public ReadOnly Property Speed As Long
Get
If GetTimeTick() - SpeedLastTime > 200 Then
Dim DeltaTime As Long = GetTimeTick() - SpeedLastTime
If GetTimeMs() - SpeedLastTime > 200 Then
Dim DeltaTime As Long = GetTimeMs() - SpeedLastTime
_Speed = (DownloadDone - SpeedLastDone) / (DeltaTime / 1000)
SpeedLastDone = DownloadDone
SpeedLastTime += DeltaTime
@@ -610,7 +602,7 @@ RequestFinished:
''' <summary>
''' 线程初始化时的时间。
''' </summary>
Public InitTime As Long = GetTimeTick()
Public InitTime As Long = GetTimeMs()
''' <summary>
''' 上次接受到有效数据的时间,-1 表示尚未有有效数据。
''' </summary>
@@ -760,7 +752,6 @@ RequestFinished:
''' 文件的已下载大小。
''' </summary>
Public DownloadDone As Long = 0
Private ReadOnly LockDone As New Object
''' <summary>
''' 文件的校验规则。
''' </summary>
@@ -773,7 +764,7 @@ RequestFinished:
''' <summary>
''' 上次记速时的时间。
''' </summary>
Private SpeedLastTime As Long = GetTimeTick()
Private SpeedLastTime As Long = GetTimeMs()
''' <summary>
''' 上次记速时的已下载大小。
''' </summary>
@@ -783,8 +774,8 @@ RequestFinished:
''' </summary>
Public ReadOnly Property Speed As Long
Get
If GetTimeTick() - SpeedLastTime > 200 Then
Dim DeltaTime As Long = GetTimeTick() - SpeedLastTime
If GetTimeMs() - SpeedLastTime > 200 Then
Dim DeltaTime As Long = GetTimeMs() - SpeedLastTime
_Speed = (DownloadDone - SpeedLastDone) / (DeltaTime / 1000)
SpeedLastDone = DownloadDone
SpeedLastTime += DeltaTime
@@ -840,14 +831,12 @@ RequestFinished:
''' </summary>
Private ReadOnly Property ConnectAverage As Integer
Get
SyncLock LockCount
Return If(ConnectCount = 0, -1, ConnectTime / ConnectCount)
End SyncLock
Dim CurrentConnectCount As Integer = ConnectCount '防止竞态条件
Return If(CurrentConnectCount = 0, -1, ConnectTime / CurrentConnectCount)
End Get
End Property
Private Const FilePieceLimit As Long = 256 * 1024
Public ReadOnly LockCount As New Object
Public ReadOnly LockState As New Object
Public ReadOnly LockChain As New Object
Public ReadOnly LockSource As New Object
@@ -929,7 +918,7 @@ StartSingleThreadDownload:
If Source Is Nothing Then Return Nothing
Dim TargetUrl As String = Source.Url
If TargetUrl.Contains("pcl2-server") OrElse TargetUrl.Contains("bmclapi") OrElse TargetUrl.Contains("github.com") OrElse
TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("modrinth") OrElse TargetUrl.Contains("momot.rs") Then Return Nothing
TargetUrl.Contains("optifine.net") OrElse TargetUrl.Contains("momot.rs") Then Return Nothing
'寻找最大碎片
'FUTURE: 下载引擎重做,计算下载源平均链接时间和线程下载速度,按最高时间节省来开启多线程
Dim FilePieceMax As NetThread = Threads
@@ -962,9 +951,7 @@ StartThread:
End SyncLock
'开始线程
SyncLock NetTaskThreadCountLock
NetTaskThreadCount += 1
End SyncLock
Interlocked.Increment(NetTaskThreadCount)
SyncLock LockSource
If Not HasAvailableSource(False) Then SourcesOnce.First.SingleThread = ThreadInfo
End SyncLock
@@ -986,7 +973,7 @@ StartThread:
CancelToken As CancellationTokenSource = Nothing, HostIp As String = Nothing
'部分下载源真的特别慢,并且只需要一个请求,例如 Ping 为 20s,如果增长太慢,就会造成类似 2.5s 5s 7.5s 10s 12.5s... 的极大延迟
'延迟过长会导致某些特别慢的链接迟迟不被掐死
Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 10000) * (1 + Th.Source.FailCount), 30000)
Dim Timeout As Integer = Math.Min(Math.Max(ConnectAverage, 15000) * (1 + Th.Source.FailCount), 30000)
Th.State = NetState.Connecting
Try
Dim HttpDataCount As Integer = 0
@@ -994,8 +981,8 @@ StartThread:
HttpRequest = New HttpRequestMessage(HttpMethod.Get, Th.Source.Url)
SecretHeadersSign(Th.Source.Url, HttpRequest, SimulateBrowserHeaders)
CancelToken = New CancellationTokenSource(Timeout)
HostIp = DNSLookup(HttpRequest, CancelToken) 'DNS 预解析
If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New RangeHeaderValue(Th.DownloadStart, Nothing)
HostIp = DNSLookup(HttpRequest, CancelToken.Token) 'DNS 预解析
If Not Th.IsFirstThread Then HttpRequest.Headers.Range = New Headers.RangeHeaderValue(Th.DownloadStart, Nothing)
Dim ContentLength As Long = 0
Response = ThreadClient.SendAsync(HttpRequest, HttpCompletionOption.ResponseHeadersRead, CancelToken.Token).GetResultWithTimeout(CancelToken, Timeout)
If Not Response.IsSuccessStatusCode Then Throw New Exception($"错误码 {Response.StatusCode} ({CInt(Response.StatusCode)}),{Th.Source.Url}") '状态码检查
@@ -1032,7 +1019,7 @@ StartThread:
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
If RequiredSpace > 0 AndAlso Drive.TotalFreeSpace < RequiredSpace Then
Throw New Exception(DriveName & " 盘空间不足,无法进行下载。" & vbCrLf & "需要至少 " & GetString(RequiredSpace) & " 空间,但当前仅剩余 " & GetString(Drive.TotalFreeSpace) & "。" &
If(PathTemp.StartsWithF(DriveName), vbCrLf & vbCrLf & "下载时需要与文件同等大小的空间存放缓存,你可以在设置中调整缓存文件夹的位置。", ""))
End If
@@ -1040,7 +1027,7 @@ StartThread:
End If
ElseIf FileSize < 0 Then
Throw New Exception("非首线程运行时,尚未获取文件大小")
ElseIf Th.DownloadStart > 0 AndAlso ContentLength = FileSize Then
ElseIf Th.DownloadStart > 0 AndAlso ContentLength >= FileSize Then
NotSupportRange:
SyncLock LockSource
If SourcesOnce.Contains(Th.Source) Then GoTo SourceBreak
@@ -1076,11 +1063,9 @@ NotSupportRange:
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 NetTaskSpeedLimitHigh > 0 Then Interlocked.Add(NetTaskSpeedLimitLeft, -RealDataCount)
Dim DeltaTime = GetTimeMs() - Th.LastReceiveTime
If DeltaTime > 100000000 Then DeltaTime = 1 '避免时间刻反转导致出现极大值
If RealDataCount > 0 Then
'有数据
If Th.DownloadDone = 0 Then
@@ -1089,31 +1074,25 @@ NotSupportRange:
SyncLock LockState
If State < NetState.Downloading Then State = NetState.Downloading
End SyncLock
SyncLock LockCount
ConnectCount += 1
ConnectTime += GetTimeTick() - Th.InitTime
End SyncLock
Interlocked.Increment(ConnectCount)
Interlocked.Add(ConnectTime, GetTimeMs() - Th.InitTime)
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.Source.FailCount = 0
For Each Task In Tasks
Task.FailCount = 0
Next
Interlocked.Add(NetManager.DownloadDone, RealDataCount)
Interlocked.Add(DownloadDone, RealDataCount)
Th.DownloadDone += RealDataCount
ResultStream.Write(ResponseBytes, 0, RealDataCount)
'已完成
If Th.DownloadUndone = 0 AndAlso Not IsUnknownSize Then Exit While
'检查速度是否过慢
If DeltaTime > 5000 AndAlso DeltaTime > RealDataCount AndAlso '数据包间隔大于 5s,且速度小于 1K/s
If Th.LastReceiveTime > 0 AndAlso DeltaTime > 5000 AndAlso DeltaTime > RealDataCount AndAlso '数据包间隔大于 5s,且速度小于 1K/s
Th.Source.SingleThread Is Nothing Then '且并非单线程下载
Throw New TimeoutException("由于速度过慢断开链接,下载 " & RealDataCount & " B,消耗 " & DeltaTime & " ms。")
End If
Th.LastReceiveTime = GetTimeTick()
Th.LastReceiveTime = GetTimeMs()
ElseIf Th.LastReceiveTime > 0 AndAlso DeltaTime > Timeout Then
'无数据,且已超时
Throw New TimeoutException("操作超时,无数据。")
@@ -1147,9 +1126,7 @@ SourceBreak:
CancelToken?.Dispose()
If Not IsNoSplit Then ResultStream?.Dispose()
'改变计数
SyncLock NetTaskThreadCountLock
NetTaskThreadCount -= 1
End SyncLock
Interlocked.Decrement(NetTaskThreadCount)
'合并
If ((FileSize >= 0 AndAlso DownloadDone >= FileSize) OrElse (FileSize = -1 AndAlso DownloadDone > 0)) AndAlso
State < NetState.Merging AndAlso Th.State <> NetState.Interrupted Then Merge(Th)
@@ -1157,12 +1134,10 @@ SourceBreak:
End Sub
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
Interlocked.Increment(Th.Source.FailCount)
For Each Task In Tasks
Interlocked.Increment(Task.FailCount)
Next
Th.State = NetState.Interrupted
Th.Source.Ex = ex
'根据情况判断,是否在多线程下禁用下载源(连续错误过多,或不支持断点续传)
@@ -1241,10 +1216,8 @@ SourceBreak:
SyncLock LockChain
Threads = Nothing
End SyncLock
NetManager.DownloadDone -= DownloadDone
SyncLock LockDone
DownloadDone = 0
End SyncLock
Interlocked.Add(NetManager.DownloadDone, -DownloadDone)
DownloadDone = 0
SpeedLastDone = 0
End Sub
@@ -1271,7 +1244,7 @@ Retry:
SyncLock LockChain
'合并文件
If IsNoSplit Then
'仅有一个线程,从缓存中输出
'仅有一个线程,从内存中输出
If ModeDebug Then Log($"[Download] {LocalName}:下载结束,从内存输出文件")
Cache.Position = 0
WriteFile(LocalPath, Cache)
@@ -1321,6 +1294,7 @@ Retry:
End SyncLock
Catch ex As Exception
RetryCount += 1
If State > NetState.Merging Then Return '通常是因为在合并时被中断了
Log(ex, $"合并文件出错,第 {RetryCount} 次尝试({LocalName})")
'重新尝试合并
If RetryCount < 3 Then
@@ -1346,6 +1320,7 @@ Retry:
If State >= NetState.Finished Then Return
If RaiseEx IsNot Nothing Then Ex.Add(RaiseEx)
'凉凉
Log($"[Download] {LocalName}:已失败,当前状态 {State}")
State = NetState.Interrupted
End SyncLock
AbortInternal()
@@ -1363,6 +1338,7 @@ Retry:
'确认中断
SyncLock LockState
If State >= NetState.Finished Then Return
Log($"[Download] {LocalName}:已中断,当前状态 {State}")
State = NetState.Interrupted
End SyncLock
AbortInternal()
@@ -1370,10 +1346,7 @@ Retry:
Private Sub AbortInternal()
On Error Resume Next
Reset()
SyncLock NetManager.LockRemain
NetManager.FileRemain -= 1
End SyncLock
Log($"[Download] {LocalName}:已终止,当前状态 {State}")
Interlocked.Decrement(NetManager.FileRemain)
End Sub
'状态改变接口
@@ -1385,9 +1358,7 @@ Retry:
If State >= NetState.Finished Then Return
State = NetState.Finished
End SyncLock
SyncLock NetManager.LockRemain
NetManager.FileRemain -= 1
End SyncLock
Interlocked.Decrement(NetManager.FileRemain)
If PrintLog Then Log($"[Download] {LocalName}:已完成")
For Each Task In Tasks
Task.OnFileFinish(Me)
@@ -1521,8 +1492,8 @@ FinishExCatch:
If File.LocalPath.EndsWithF("\") Then Throw New ArgumentException("请输入含文件名的完整文件路径:" & File.LocalPath)
Directory.CreateDirectory(GetPathFromFullPath(File.LocalPath)) '创建目标文件夹
Next
'接入下载管理器
NetManager.Start(Me)
'接入下载管理器,获取新开始下载的文件列表
Dim NewFiles = NetManager.Start(Me)
'====================================
' 已存在文件查找
@@ -1530,9 +1501,8 @@ FinishExCatch:
'整理允许进行查找的文件
Dim FilesToCheck As New List(Of NetFile)
Dim DisabledCopy As Boolean = Setup.Get("SystemDebugSkipCopy") '在设置中禁用了复制
For Each File In Files
If Not DisabledCopy AndAlso File.Check?.CanUseExistsFile Then
For Each File In NewFiles
If File.Check?.CanUseExistsFile Then
FilesToCheck.Add(File)
Else '不允许,直接开始下载
SyncLock LockState
@@ -1569,7 +1539,7 @@ FinishExCatch:
End Sub
Private Sub CheckExistingFiles(Files As List(Of NetFile), FolderList As List(Of String))
Try
If ModeDebug Then Log($"[Download] 文件检查线程已启动,分配的文件数:{Files.Count}")
If ModeDebug Then Log($"[Download] 文件检查开始,本线程负责的文件数:{Files.Count}")
'列出 MC 文件夹中的各个版本文件夹
Dim VersionFolders As New List(Of String)
For Each McFolder In FolderList
@@ -1583,15 +1553,9 @@ FinishExCatch:
'处理每个文件
For Each File As NetFile In Files
Dim Target As String = CheckExistingFile(FolderList, VersionFolders, File)
If File.State >= NetState.WaitingToDownload Then Return '中断
If Target Is Nothing Then
'未找到相同文件
SyncLock LockState
File.State = NetState.WaitingToDownload
File.IsCopy = False
End SyncLock
Else
'已找到相同文件
If File.State >= NetState.WaitingToDownload Then Continue For '中断
'已找到相同文件
If Target IsNot Nothing Then
File.IsCopy = True
Dim RetryCount As Integer = 0
Retry:
@@ -1599,22 +1563,25 @@ Retry:
If Target <> File.LocalPath Then
Log($"[Download] 复制已存在的文件:{Target} → {File.LocalPath}")
CopyFile(Target, File.LocalPath)
Else
If ModeDebug Then Log($"[Download] 需要下载的文件已存在,并通过校验:{Target}")
End If
File.Finish(False)
Continue For
Catch ex As Exception
RetryCount += 1
Log(ex, $"复制已存在的文件失败,第 {RetryCount} 次重试({Target} → {File.LocalPath})")
If RetryCount < 3 Then
Thread.Sleep(200)
Thread.Sleep(2000)
GoTo Retry
End If
'失败,回退到下载
SyncLock LockState
File.State = NetState.WaitingToDownload
File.IsCopy = False
End SyncLock
End Try
End If
'回退到下载
SyncLock LockState
File.State = NetState.WaitingToDownload
File.IsCopy = False
End SyncLock
Next
Catch ex As Exception
OnFail(New List(Of Exception) From {New Exception("下载已存在文件查找失败", ex)})
@@ -1623,6 +1590,8 @@ Retry:
Private Function CheckExistingFile(FolderList As List(Of String), VersionFolders As List(Of String), File As NetFile) As String
'目标文件已存在
If File.Check.Check(File.LocalPath) Is Nothing Then Return File.LocalPath
'在设置中禁用了复制
If Setup.Get("SystemDebugSkipCopy") Then Return Nothing
'没有可用的检查规则,只能开始下载
If File.Check.Hash Is Nothing AndAlso File.Check.ActualSize < 0 Then Return Nothing
'大致判断文件类别
@@ -1637,8 +1606,9 @@ Retry:
Return Nothing
End If
End If
Dim Type = TypeIndexes.MaxOf(Function(kv) kv.Item2).FolderName.TrimStart("\"c)
Dim Type = TypeIndexes.MaxBy(Function(kv) kv.Item2).FolderName.TrimStart("\"c)
'根据类别进行查找
Static Sizes As New SafeDictionary(Of String, Long)
Select Case Type
Case "assets\", "libraries\"
'assets/libraries:查找 MC 文件夹下的相同路径
@@ -1651,6 +1621,10 @@ Retry:
For Each VersionFolder In VersionFolders
For Each Candidate In Directory.GetFiles(VersionFolder,
"*." & GetFileNameFromPath(File.LocalPath).AfterLast(".").ToLower, SearchOption.TopDirectoryOnly)
'快速进行大小校验
If Not Sizes.ContainsKey(Candidate) Then Sizes(Candidate) = New FileInfo(Candidate).Length
If File.Check.ActualSize <> Sizes(Candidate) Then Continue For
'Hash 校验
If File.Check.Check(Candidate) Is Nothing Then Return Candidate
Next
Next
@@ -1662,7 +1636,6 @@ Retry:
If Not Directory.Exists(TargetFolder) Then Continue For
For Each Candidate In Directory.GetFiles(TargetFolder)
'快速进行大小校验
Static Sizes As New SafeDictionary(Of String, Long)
If Not Sizes.ContainsKey(Candidate) Then Sizes(Candidate) = New FileInfo(Candidate).Length
If File.Check.ActualSize <> Sizes(Candidate) Then Continue For
'Hash 校验
@@ -1757,8 +1730,7 @@ Retry:
''' <summary>
''' 需要下载的文件。为“本地地址 - 文件对象”键值对。
''' </summary>
Public Files As New Dictionary(Of String, NetFile)
Public ReadOnly LockFiles As New Object
Public AllFiles As New SafeDictionary(Of String, NetFile)
''' <summary>
''' 当前的所有下载任务。
@@ -1768,25 +1740,12 @@ Retry:
''' <summary>
''' 已下载完成的大小。
''' </summary>
Public Property DownloadDone As Long
Get
Return _DownloadDone
End Get
Set(value As Long)
SyncLock LockDone
_DownloadDone = value
End SyncLock
End Set
End Property
Private _DownloadDone As Long = 0
Private ReadOnly LockDone As New Object
Public DownloadDone As Long = 0
''' <summary>
''' 尚未完成下载的文件数。
''' </summary>
Public FileRemain As Integer = 0
Public ReadOnly LockRemain As New Object
'这些属性由 RefreshStat 刷新
''' <summary>
@@ -1803,7 +1762,7 @@ Retry:
''' </summary>
Private Sub RefreshStat()
Try
Dim DeltaTime As Long = GetTimeTick() - RefreshStatLast
Dim DeltaTime As Long = GetTimeMs() - RefreshStatLast
If DeltaTime = 0 Then Return
RefreshStatLast += DeltaTime
#Region "刷新整体速度"
@@ -1855,9 +1814,9 @@ Retry:
Thread.Sleep(20)
'获取文件列表
Dim AllFiles As List(Of NetFile)
SyncLock LockFiles
If Id = 0 AndAlso FileRemain = 0 AndAlso Files.Any() Then Files.Clear() '若已完成,则清空
AllFiles = Files.Values.ToList()
SyncLock Me.AllFiles.SyncRoot
If Id = 0 AndAlso FileRemain = 0 AndAlso Me.AllFiles.Any() Then Me.AllFiles.Clear() '若已完成,则清空
AllFiles = Me.AllFiles.Values.ToList()
End SyncLock
Dim WaitingFiles As New List(Of NetFile)
Dim OngoingFiles As New List(Of NetFile)
@@ -1865,7 +1824,7 @@ Retry:
If File.Uuid Mod 2 = Id Then Continue For
If File.State = NetState.WaitingToDownload Then
WaitingFiles.Add(File)
ElseIf File.State < NetState.Merging Then
ElseIf File.State < NetState.Merging AndAlso File.State >= NetState.WaitingToDownload Then
OngoingFiles.Add(File)
End If
Next
@@ -1905,19 +1864,19 @@ Retry:
RunInNewThread(
Sub()
Try
Dim NextTick As Long = GetTimeTick()
Dim NextTick As Long = GetTimeMs()
While True
'增加限速余量
If NetTaskSpeedLimitHigh > 0 Then NetTaskSpeedLimitLeft = NetTaskSpeedLimitHigh / 10
If NetTaskSpeedLimitHigh > 0 Then Interlocked.Add(NetTaskSpeedLimitLeft, CLng(NetTaskSpeedLimitHigh / 10))
'刷新公开属性
RefreshStat()
'等待 100 ms
NextTick += 100
Dim SleepTime = NextTick - GetTimeTick()
Dim SleepTime = NextTick - GetTimeMs()
If SleepTime > 0 Then
Thread.Sleep(SleepTime)
Else
NextTick = GetTimeTick() '超时,直接追帧,不等待
NextTick = GetTimeMs() '超时,直接追帧,不等待
End If
End While
Catch ex As Exception
@@ -1931,7 +1890,7 @@ Retry:
''' <summary>
''' 开始一个下载任务。
''' </summary>
Public Sub Start(Task As LoaderDownload)
Public Function Start(Task As LoaderDownload) As List(Of NetFile)
StartManager()
'清理缓存
SyncLock DownloadCacheLock '防止同时开启多个下载任务时重复清理
@@ -1943,45 +1902,30 @@ Retry:
Catch ex As Exception
Log(ex, "清理下载缓存失败")
End Try
IsDownloadCacheCleared = True
End If
IsDownloadCacheCleared = True
End SyncLock
'文件处理
SyncLock LockFiles
'添加每个文件
For i = 0 To Task.Files.Count - 1
Dim File = Task.Files(i)
If Files.ContainsKey(File.LocalPath) Then
'已有该文件
If Files(File.LocalPath).State >= NetState.Finished Then
'该文件已经下载过一次,且下载完成
'将已下载的文件替换成当前文件,重新下载
File.Tasks.Add(Task)
Files(File.LocalPath) = File
SyncLock LockRemain
FileRemain += 1
End SyncLock
If ModeDebug Then Log($"[Download] {File.LocalName}:重新加入下载列表,目标 {File.LocalPath}")
Else
'该文件正在下载中
'将当前文件替换成下载中的文件,即两个任务指向同一个文件
File = Files(File.LocalPath)
File.Tasks.Add(Task)
End If
Else
'没有该文件
File.Tasks.Add(Task)
Files.Add(File.LocalPath, File)
SyncLock LockRemain
FileRemain += 1
End SyncLock
If ModeDebug Then Log($"[Download] {File.LocalName}:已加入下载列表,目标 {File.LocalPath}")
End If
Task.Files(i) = File '回设
Next
End SyncLock
'添加每个文件
Dim NewFiles As New List(Of NetFile)
For i = 0 To Task.Files.Count - 1
Dim File As NetFile = Task.Files(i)
If AllFiles.ContainsKey(File.LocalPath) AndAlso AllFiles(File.LocalPath).State < NetState.Finished Then
'该文件正在被另一个任务下载
If ModeDebug Then Log($"[Download] {File.LocalName}:无需开始,该文件已在下载中,目标 {File.LocalPath}")
File = AllFiles(File.LocalPath) '将列表中的文件替换成下载中的文件,即两个任务指向同一个文件;抛弃现在的 File 对象
Task.Files(i) = File
Else
'常规情况(该文件可能已在其他任务中下载完成,但这并不影响)
If ModeDebug Then Log($"[Download] {File.LocalName}:开始,目标 {File.LocalPath}")
AllFiles(File.LocalPath) = File
NewFiles.Add(File)
Interlocked.Increment(FileRemain)
End If
File.Tasks.Add(Task)
Next
Tasks.Add(Task)
End Sub
Return NewFiles
End Function
End Class
@@ -72,7 +72,7 @@ Public Class MyBitmap
End If
Else
'使用这种自己接管 FileStream 的方法加载才能解除文件占用
Using InputStream As New FileStream(FilePathOrResourceName, FileMode.Open)
Using InputStream As New FileStream(FilePathOrResourceName, FileMode.Open, FileAccess.Read, FileShare.Read)
'判断是否为 WebP 文件头
Dim Header(1) As Byte
InputStream.Read(Header, 0, 2)
@@ -48,9 +48,9 @@
#Region "CompDatabase | Mod 数据库"
Private _CompDatabase As List(Of CompDatabaseEntry) = Nothing
Private ReadOnly Property CompDatabase As List(Of CompDatabaseEntry)
Get
Static _CompDatabase As List(Of CompDatabaseEntry) = Nothing
If _CompDatabase IsNot Nothing Then Return _CompDatabase
'初始化数据库
_CompDatabase = New List(Of CompDatabaseEntry)
@@ -561,7 +561,7 @@
Dim EndName = McVersion.DropToVersion(EndDrop)
If StartDrop = EndDrop Then
Segments.Add(StartName)
ElseIf AllDrops?.Any AndAlso StartDrop >= AllDrops.First Then
ElseIf AllDrops IsNot Nothing AndAlso StartDrop >= AllDrops.First Then
If EndDrop < 100 Then
Segments.Clear()
Segments.Add("全版本")
@@ -846,6 +846,11 @@ NoSubtitle:
''' </summary>
Public SearchText As String = Nothing
''' <summary>
''' 在进行中文搜索时,CurseForge 的替代搜索文本。
''' 由于 CurseForge API 在有任意关键词未匹配的时候就不显示结果,所以不能使用与 Modrinth 相同的算法。
''' </summary>
Public CurseForgeAltSearchText As String = Nothing
''' <summary>
''' 允许的来源。
''' </summary>
Public Source As CompSourceType = CompSourceType.Any
@@ -881,10 +886,10 @@ NoSubtitle:
Case CompType.ResourcePack
Address += "&classId=12"
End Select
Address += "&categoryId=" & If(Tag = "", "0", Tag.BeforeFirst("/"))
If Tag <> "" Then Address += "&categoryId=" & Tag.BeforeFirst("/")
If ModLoader <> CompModLoaderType.Any Then Address += "&modLoaderType=" & CType(ModLoader, Integer)
If Not String.IsNullOrEmpty(GameVersion) Then Address += "&gameVersion=" & GameVersion
If Not String.IsNullOrEmpty(SearchText) Then Address += "&searchFilter=" & Net.WebUtility.UrlEncode(SearchText)
If Not String.IsNullOrEmpty(If(CurseForgeAltSearchText, SearchText)) Then Address += "&searchFilter=" & Net.WebUtility.UrlEncode(If(CurseForgeAltSearchText, SearchText))
If Storage.CurseForgeOffset > 0 Then Address += "&index=" & Storage.CurseForgeOffset
Return Address
End Function
@@ -995,62 +1000,86 @@ NoSubtitle:
'构造搜索请求
Dim SearchEntries As New List(Of SearchEntry(Of CompDatabaseEntry))
For Each Entry In CompDatabase
If Entry.ChineseName.Contains("动态的树") Then Continue For '这玩意儿附属太多了
SearchEntries.Add(New SearchEntry(Of CompDatabaseEntry) With {
.Item = Entry,
.SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
New KeyValuePair(Of String, Double)(Entry.ChineseName & If(Entry.CurseForgeSlug, "") & If(Entry.ModrinthSlug, ""), 1)
.SearchSource = New List(Of SearchSource) From {
New SearchSource(Entry.ChineseName.BeforeFirst(" (").Split({"/"c}, StringSplitOptions.RemoveEmptyEntries), 1), '部分 Mod 有别名
New SearchSource(Entry.ChineseName.AfterFirst(" (") & If(Entry.CurseForgeSlug, "") & If(Entry.ModrinthSlug, ""), 0.5)
}
})
Next
'获取搜索结果
Dim SearchResults = Search(SearchEntries, Request.SearchText, 3)
Dim SearchResults = Search(SearchEntries, Request.SearchText, 40, 0.2)
If Not SearchResults.Any() Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
Dim SearchResult As String = ""
For i = 0 To Math.Min(4, SearchResults.Count - 1) '就算全是准确的,也最多只要 5 个
If Not SearchResults(i).AbsoluteRight AndAlso i >= Math.Min(2, SearchResults.Count - 1) Then Exit For '把 3 个结果拼合以提高准确度
If SearchResults(i).Item.CurseForgeSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.CurseForgeSlug.Replace("-", " ").Replace("/", " ") & " "
If SearchResults(i).Item.ModrinthSlug IsNot Nothing Then SearchResult += SearchResults(i).Item.ModrinthSlug.Replace("-", " ").Replace("/", " ") & " "
SearchResult += SearchResults(i).Item.ChineseName.AfterLast(" (").TrimEnd(") ").BeforeFirst(" - ").
Replace(":", "").Replace("(", "").Replace(")", "").ToLower.Replace("/", " ") & " "
'提取可能的英文名
Dim ExtractWords =
Function(Result As SearchEntry(Of CompDatabaseEntry)) As String()
Dim Word As String = ""
If Result.Item.CurseForgeSlug IsNot Nothing Then Word += Result.Item.CurseForgeSlug.Replace("-", " ").Replace("/", " ") & " "
If Result.Item.ModrinthSlug IsNot Nothing Then Word += Result.Item.ModrinthSlug.Replace("-", " ").Replace("/", " ") & " "
Word += Result.Item.ChineseName.AfterLast(" (").TrimEnd(") ").BeforeFirst(" - ").
Replace(":", "").Replace("(", "").Replace(")", "").ToLower.Replace("/", " ").Replace("-", " ")
Dim Words = Word.ToLower.Split(" ")
Words = Words.Select(Function(w) w.TrimStart("{[(").TrimEnd("}])")).Where(
Function(w)
If w.Length <= 1 Then Return False '单字词
If {"the", "of", "mod", "and"}.Contains(w) Then Return False '常见词
If Val(w) > 0 Then Return False '数字
If w.Split(" ").Count > 3 AndAlso w.Contains("ftb") Then Return False '神秘 FTB
Return True
End Function).Distinct.ToArray
Return Words
End Function
Dim WordWeights As New Dictionary(Of String, Double) '各个单词及其出现的权重
For Each Result In SearchResults
For Each Word In ExtractWords(Result)
Dim Similarity = If(Result.SearchSource.Any(Function(s) s.Aliases.Contains(Request.SearchText)), 100000, Result.Similarity) '完全匹配为 100000
If Not WordWeights.ContainsKey(Word) Then WordWeights.Add(Word, 0)
WordWeights(Word) += Similarity
Next
Next
Log("[Comp] 中文搜索原始关键词:" & SearchResult, LogLevel.Developer)
'去除常见连接词
Dim RealFilter As String = ""
For Each Word In SearchResult.Split(" ")
If {"the", "of", "a", "mod", "and"}.Contains(Word.ToLowerInvariant) OrElse Val(Word) > 0 Then Continue For
If SearchResult.Split(" ").Count > 3 AndAlso {"ftb"}.Contains(Word.ToLower) Then Continue For
RealFilter += Word.TrimStart("{[(").TrimEnd("}])") & " "
Next
Request.SearchText = RealFilter
Log("[Comp] 中文搜索最终关键词:" & RealFilter, LogLevel.Developer)
If Not WordWeights.Any() Then Throw New Exception("无搜索结果,请尝试搜索英文名称")
'根据权重选取英文单词
Dim SortedWords = WordWeights.OrderByDescending(Function(w) w.Value).ToList
If SortedWords.First.Value >= 100000 Then '如果有完全匹配的,就只选完全匹配的
Request.SearchText = SortedWords.Where(Function(w) w.Value >= 100000).Select(Function(w) w.Key).Join(" ")
Else '否则,CurseForge 选第一个结果,Modrinth 选前 5 个单词
Request.SearchText = SortedWords.Take(5).Select(Function(w) w.Key).Join(" ")
Request.CurseForgeAltSearchText = ExtractWords(SearchResults.First).Join(" ")
Log("[Comp] 中文搜索基础关键词(CurseForge):" & Request.CurseForgeAltSearchText, LogLevel.Developer)
End If
Log("[Comp] 中文搜索基础关键词:" & Request.SearchText, LogLevel.Developer)
End If
'驼峰英文请求关键字处理
Dim SpacedKeywords = Request.SearchText.RegexReplace("([A-Z]+|[a-z]+?)(?=[A-Z]+[a-z]+[a-z ]*)", "$& ")
Dim ConnectedKeywords = Request.SearchText.Replace(" ", "")
Dim AllPossibleKeywords = (SpacedKeywords & " " & If(IsChineseSearch, Request.SearchText, ConnectedKeywords & " " & RawFilter)).ToLower
'最终处理关键字:分割、去重
Dim RightKeywords As New List(Of String)
For Each Keyword In AllPossibleKeywords.Split(" ")
Keyword = Keyword.Trim("["c, "]"c)
If Keyword = "" Then Continue For
If {"forge", "fabric", "for", "mod", "quilt"}.Contains(Keyword) Then '#208
Log("[Comp] 已跳过搜索关键词:" & Keyword, LogLevel.Developer)
Continue For
Dim ProcessKeywords =
Sub(ByRef Text As String)
If Text Is Nothing Then Return
Text = Text.ToLowerInvariant
Dim Words As New List(Of String)
For Each Keyword In Text.Split(" ")
Keyword = Keyword.Trim("["c, "]"c)
If Keyword = "" Then Continue For
If {"forge", "fabric", "for", "mod", "quilt"}.Contains(Keyword) Then '#208
Log("[Comp] 已跳过搜索关键词:" & Keyword, LogLevel.Developer)
Continue For
End If
Words.Add(Keyword)
Next
If RawFilter.Length > 0 AndAlso Not Words.Any() Then
Text = RawFilter '全都被过滤掉了
Else
Text = Join(Words.Distinct.ToList, " ")
End If
RightKeywords.Add(Keyword)
Next
If RawFilter.Length > 0 AndAlso Not RightKeywords.Any() Then
Request.SearchText = RawFilter '全都被过滤掉了
Else
Request.SearchText = Join(RightKeywords.Distinct.ToList, " ").ToLower
End If
'例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
If RawFilter.Replace(" ", "").ContainsF("optiforge", True) Then Text = "optiforge"
If RawFilter.Replace(" ", "").ContainsF("optifabric", True) Then Text = "optifabric"
End Sub
'例外项:OptiForge、OptiFabric(拆词后因为包含 Forge/Fabric 导致无法搜到实际的 Mod)
If RawFilter.Replace(" ", "").ContainsF("optiforge", True) Then Request.SearchText = "optiforge"
If RawFilter.Replace(" ", "").ContainsF("optifabric", True) Then Request.SearchText = "optifabric"
If Request.CurseForgeAltSearchText IsNot Nothing Then
ProcessKeywords(Request.CurseForgeAltSearchText)
Log("[Comp] 工程列表搜索最终文本(CurseForge):" & Request.CurseForgeAltSearchText, LogLevel.Debug)
End If
ProcessKeywords(Request.SearchText)
Log("[Comp] 工程列表搜索最终文本:" & Request.SearchText, LogLevel.Debug)
Task.Progress = 0.1
@@ -1195,7 +1224,7 @@ Retry:
Not Storage.Results.Any(Function(b) r.IsLike(b))).ToList
'加入列表
RealResults.AddRange(RawResults)
Log($"[Comp] 去重、筛选后累计新增结果 {RealResults.Count} 个")
Log($"[Comp] 去重、筛选后累计新增结果 {RealResults.Count} 个(目前已有结果 {Storage.Results.Count} 个)")
#End Region
@@ -1241,16 +1270,19 @@ Retry:
For Each Result As CompProject In RealResults
Scores.Add(Result, If(Result.WikiId > 0, 0.2, 0) +
Math.Log10(Math.Max(Result.DownloadCount, 1) * GetDownloadCountMult(Result)) / 9)
Entry.Add(New SearchEntry(Of CompProject) With {.Item = Result, .SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
New KeyValuePair(Of String, Double)(If(IsChineseSearch, Result.TranslatedName, Result.RawName), 1),
New KeyValuePair(Of String, Double)(Result.Description, 0.05)}})
Entry.Add(New SearchEntry(Of CompProject) With {.Item = Result, .SearchSource = New List(Of SearchSource) From {
New SearchSource(If(IsChineseSearch, Result.TranslatedName, Result.RawName).Split({"/"c}, StringSplitOptions.RemoveEmptyEntries), 1),
New SearchSource(Result.Description, 0.05)}})
Next
Dim SearchResult = Search(Entry, RawFilter, 101, -1)
For Each OneResult In SearchResult
Scores(OneResult.Item) += OneResult.Similarity / SearchResult(0).Similarity '最高 1 分的相似度分
Scores(OneResult.Item) +=
If(OneResult.AbsoluteRight, 10, OneResult.Similarity) /
If(SearchResult.First.AbsoluteRight, 10, SearchResult.First.Similarity) '最高 1 分的相似度分
Next
End If
'根据排序分得出结果并添加
If Task.IsAborted Then Throw New ThreadInterruptedException '#8246
Storage.Results.AddRange(
Scores.OrderByDescending(Function(s) s.Value).Select(Function(r) r.Key))
@@ -1355,6 +1387,10 @@ Retry:
''' </summary>
Public ReadOnly Hash As String = Nothing
''' <summary>
''' 文件大小。不可用时为 -1。
''' </summary>
Public ReadOnly Size As Integer = -1
''' <summary>
''' 该文件的所有依赖工程的原始 ID。
''' 这些 ID 可能没有加载,在加载后会添加到 Dependencies 中(主要是因为 Modrinth 返回的是字符串 ID 而非 Slug,导致 Project.Id 查询不到)。
''' </summary>
@@ -1368,7 +1404,7 @@ Retry:
''' </summary>
''' <param name="LocalAddress">目标本地文件夹,或完整的文件路径。会自动判断类型。</param>
Public Function ToNetFile(LocalAddress As String) As NetFile
Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash), SimulateBrowserHeaders:=True)
Return New NetFile(DownloadUrls, LocalAddress & If(LocalAddress.EndsWithF("\"), FileName, ""), New FileChecker(Hash:=Hash, ActualSize:=Size), SimulateBrowserHeaders:=True)
End Function
'实例化
@@ -1390,6 +1426,7 @@ Retry:
If Data.ContainsKey("FileName") Then FileName = Data("FileName").ToString
If Data.ContainsKey("DownloadUrls") Then DownloadUrls = Data("DownloadUrls").ToObject(Of List(Of String))
If Data.ContainsKey("ModLoaders") Then ModLoaders = Data("ModLoaders").ToObject(Of List(Of CompModLoaderType))
If Data.ContainsKey("Size") Then Size = Data("Size").ToObject(Of Integer)
If Data.ContainsKey("Hash") Then Hash = Data("Hash").ToString
If Data.ContainsKey("GameVersions") Then GameVersions = Data("GameVersions").ToObject(Of List(Of String))
If Data.ContainsKey("RawDependencies") Then RawDependencies = Data("RawDependencies").ToObject(Of List(Of String))
@@ -1407,6 +1444,7 @@ Retry:
Status = CType(Data("releaseType").ToObject(Of Integer), CompFileStatus)
DownloadCount = Data("downloadCount")
FileName = Data("fileName")
Size = Data("fileLength")
Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 1)?("value")
If Hash Is Nothing Then Hash = CType(Data("hashes"), JArray).ToList.FirstOrDefault(Function(s) s("algo").ToObject(Of Integer) = 2)?("value")
'DownloadAddress
@@ -1424,7 +1462,10 @@ Retry:
End If
'GameVersions
Dim RawVersions As List(Of String) = Data("gameVersions").Select(Function(t) t.ToString.Trim.ToLower).ToList
GameVersions = RawVersions.Where(Function(v) McVersion.IsFormatFit(v)).Select(Function(v) v.Replace("-snapshot", " 预览版")).ToList
GameVersions = RawVersions.
Where(Function(v) McVersion.IsFormatFit(v)).
Select(Function(v) v.Replace("-snapshot", " 预览版")).
Distinct.ToList
If GameVersions.Count > 1 Then
GameVersions = GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本
@@ -1453,6 +1494,7 @@ Retry:
Dim File As JToken = Data("files")(0)
FileName = File("filename")
DownloadUrls = New List(Of String) From {File("url"), DlSourceModGet(File("url"))}.Distinct.ToList '同时添加了镜像源
Size = File("size")
Hash = File("hashes")("sha1")
End If
'ModLoaders
@@ -1485,7 +1527,8 @@ Retry:
'GameVersions
Dim RawVersions As List(Of String) = Data("game_versions").Select(Function(t) t.ToString.Trim.ToLower).ToList
GameVersions = RawVersions.Where(Function(v) v.Contains(".")).
Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).ToList
Select(Function(v) If(v.Contains("-"), v.BeforeFirst("-") & " 预览版", If(v.StartsWithF("b1."), "远古版本", v))).
Distinct.ToList
If GameVersions.Count > 1 Then
GameVersions = GameVersions.SortByComparison(AddressOf CompareVersionGE).ToList
If Type = CompType.ModPack Then GameVersions = New List(Of String) From {GameVersions(0)} '整合包理应只 “支持” 一个版本
@@ -1531,6 +1574,7 @@ Retry:
If FileName IsNot Nothing Then Json.Add("FileName", FileName)
If DownloadUrls IsNot Nothing Then Json.Add("DownloadUrls", New JArray(DownloadUrls))
If Hash IsNot Nothing Then Json.Add("Hash", Hash)
If Size >= 0 Then Json.Add("Size", Size)
Json.Add("RawDependencies", New JArray(RawDependencies))
Json.Add("Dependencies", New JArray(Dependencies))
Return Json
@@ -1640,7 +1684,7 @@ Retry:
Next
Else
'Modrinth
ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version")
ResultJsonArray = DlModRequest($"https://api.modrinth.com/v2/project/{ProjectId}/version?include_changelog=false")
End If
CompFilesCache(ProjectId) = ResultJsonArray.Select(Function(a) New CompFile(a, TargetProject.Type)).
Where(Function(a) a.Available).ToList.
@@ -98,7 +98,7 @@
Try
Dim Info As New FileInfo(FilePath)
If Info.Exists AndAlso Info.Length > 0 AndAlso Not FilePath.EndsWithF(".jar", True) Then
ExtractFile(FilePath, TempFolder & "Temp\")
ExtractCompressedFile(FilePath, TempFolder & "Temp\")
Log("[Crash] 已解压导入的日志文件:" & FilePath)
GoTo Extracted
End If
@@ -712,11 +712,11 @@ NextStack:
Dim Word As String = Splited(i)
If Word.Length <= 2 OrElse Word.StartsWithF("func_") Then Continue For
If {"com", "org", "net", "asm", "fml", "mod", "jar", "sun", "lib", "map", "gui", "dev", "nio", "api", "dsi", "top", "mcp",
"core", "init", "mods", "main", "file", "game", "load", "read", "done", "util", "tile", "item", "base", "oshi", "impl", "data", "pool", "task",
"forge", "setup", "block", "model", "mixin", "event", "unimi", "netty", "world", "lwjgl",
"gitlab", "common", "server", "config", "mixins", "compat", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe", "render", "packet", "events",
"preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content", "systems", "modules", "service",
"fastutil", "optifine", "internal", "platform", "override", "fabricmc", "neoforge",
"core", "init", "mods", "main", "file", "game", "load", "read", "done", "util", "tile", "item", "base", "fake", "oshi", "impl", "data", "pool", "task",
"forge", "setup", "block", "model", "mixin", "event", "unimi", "netty", "world", "lwjgl", "fakes",
"gitlab", "common", "server", "config", "mixins", "compat", "loader", "launch", "script", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe", "render", "packet", "events",
"preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content", "systems", "modules", "service", "scripts", "network",
"fastutil", "optifine", "internal", "platform", "override", "fabricmc", "neoforge", "external",
"injection", "listeners", "scheduler", "minecraft", "universal", "multipart", "neoforged", "microsoft",
"transformer", "transformers", "minecraftforge", "blockentity", "spongepowered", "electronwill", "concurrent"
}.Contains(Word.ToLower) Then Continue For
@@ -152,14 +152,16 @@
#Region "DlClientList | Minecraft 客户端 版本列表"
''' <summary>
''' 所有正式版的 Minecraft Drop 序数,按从高到低排列。
''' Nothing 代表从未完成过获取。
''' 所有正式版的 Minecraft Drop 序数。
''' 若从未完成过获取,返回 Nothing;否则必定存在元素,且从高到低排列。
''' </summary>
Public Property AllDrops As List(Of Integer)
Get
If _AllDrops Is Nothing Then _AllDrops = Setup.Get("CacheDrops").ToString.Split(","c).Select(Function(d) CInt(d)).ToList()
If Not _AllDrops.Any Then _AllDrops = Nothing
Return _AllDrops
If _AllDrops Is Nothing Then
_AllDrops = Setup.Get("CacheDrops").ToString.
Split(",".ToCharArray, StringSplitOptions.RemoveEmptyEntries).Select(Function(d) CInt(Val(d))).ToList()
End If
Return If(_AllDrops.Any, _AllDrops, Nothing) '不要将 _AllDrops 再设为 Nothing,以防止反复获取设置尝试初始化
End Get
Set(value As List(Of Integer))
_AllDrops = value
@@ -220,14 +222,14 @@
''' </summary>
Public DlClientListMojangLoader As New LoaderTask(Of String, DlClientListResult)("DlClientList Mojang", AddressOf DlClientListMojangMain)
Private Sub DlClientListMojangMain(Loader As LoaderTask(Of String, DlClientListResult))
Dim StartTime As Long = GetTimeTick()
Dim StartTime As Long = GetTimeMs()
Dim Json As JObject = GetJson(NetRequestByClientRetry("https://launchermeta.mojang.com/mc/game/version_manifest.json", RequireJson:=True))
Try
Dim Versions As JArray = Json("versions")
If Versions.Count < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Json.ToString & ")")
'确定官方源是否可用
If Not DlPreferMojang Then
Dim DeltaTime = GetTimeTick() - StartTime
Dim DeltaTime = GetTimeMs() - StartTime
DlPreferMojang = DeltaTime < 4000
Log($"[Download] Mojang 官方源加载耗时:{DeltaTime}ms,{If(DlPreferMojang, "可优先使用官方源", "不优先使用官方源")}")
End If
@@ -842,7 +844,7 @@
Public Sub New(ApiName As String)
IsNeoForge = True
Me.ApiName = ApiName
IsBeta = ApiName.Contains("beta")
IsBeta = ApiName.ContainsF("beta", True) OrElse ApiName.ContainsF("alpha", True)
If ApiName.Contains("1.20.1") Then '1.20.1-47.1.99
VersionName = ApiName.Replace("1.20.1-", "")
Version = New Version("19." & VersionName)
@@ -1174,17 +1176,17 @@
Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
Case 1
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10)) '至少 10s,要不然有时候远端服务器来不及完成
Urls.Add(New KeyValuePair(Of String, Integer)(Url, If(Url.Contains("modrinth"), 20, 10))) '至少 20s,要不然 Modrinth 返回不过来
Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 10))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
Case Else
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, If(Url.Contains("modrinth"), 20, 10)))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
Urls.Add(New KeyValuePair(Of String, Integer)(McimUrl, 30))
End Select
Else
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 10))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, If(Url.Contains("modrinth"), 20, 10)))
Urls.Add(New KeyValuePair(Of String, Integer)(Url, 30))
End If
Dim Exs As String = ""
@@ -95,6 +95,7 @@
Try
'确定文件存在
If Not File.Exists(PathJava) Then Throw New FileNotFoundException("未找到 java.exe 文件", PathJava)
If PathJava.ContainsF("finalshell", True) Then Throw New Exception("不兼容 FinalShell 的 Java") '#8080
If File.Exists(PathFolder & "pdf-bookmark") Then Throw New Exception("不兼容 PDF Bookmark 的 Java") '#5326
IsJre = Not File.Exists(PathFolder & "javac.exe")
'运行 -version
@@ -465,9 +466,10 @@ NoUserJava:
If Not Left.IsJre AndAlso Right.IsJre Then Return False
'4. Java 大版本
If Left.MajorVersion <> Right.MajorVersion Then
' Java 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23...
Dim Weight = {0, 1, 2, 3, 4, 5, 6, 14, 30, 10, 12, 15, 13, 9, 8, 7, 11, 31, 29, 16, 17, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18}
Return Weight.ElementAtOrDefault(Left.MajorVersion) >= Weight.ElementAtOrDefault(Right.MajorVersion)
' Java 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Dim Weight = {0, 1, 2, 3, 4, 5, 6, 14, 30, 10, 12, 15, 13, 9, 8, 7, 11, 31, 29, 16, 17} '更高的版本指定为 20,且越低越好
Return If(Left.MajorVersion > 20, 20 - Left.MajorVersion * 0.0001, Weight.ElementAtOrDefault(Left.MajorVersion)) >=
If(Right.MajorVersion > 20, 20 - Right.MajorVersion * 0.0001, Weight.ElementAtOrDefault(Right.MajorVersion))
End If
'5. 最次级版本号更接近 51
Return Math.Abs(Left.Version.Revision - 51) <= Math.Abs(Right.Version.Revision - 51)
@@ -730,7 +732,7 @@ Wait:
TargetEntry = Components.Property(Loader.Input)
Else '模糊匹配
TargetEntry = Components.Properties.FirstOrDefault(
Function(c) c.Value?.ToArray.FirstOrDefault()?("version")("name").ToString.StartsWithF(Loader.Input))
Function(c) c.Value IsNot Nothing AndAlso c.Value.ToArray.FirstOrDefault()?("version")("name").ToString.StartsWithF(Loader.Input))
If TargetEntry Is Nothing Then Throw New Exception($"未能找到所需的 Java {Loader.Input}")
End If
Dim TargetComponent = TargetEntry.Value.ToArray.FirstOrDefault
@@ -544,7 +544,7 @@ NextInner:
'检查是否已经登录完成
If Not Data.IsForceRestarting AndAlso '不要求强行重启
Input.AccessToken <> "" AndAlso '已经登录过了
(McLoginMsRefreshTime > 0 AndAlso GetTimeTick() - McLoginMsRefreshTime < 1000 * 60 * 10) Then '完成时间在 10 分钟内
(McLoginMsRefreshTime > 0 AndAlso GetTimeMs() - McLoginMsRefreshTime < 1000 * 60 * 10) Then '完成时间在 10 分钟内
Data.Output = New McLoginResult With
{.AccessToken = Input.AccessToken, .Name = Input.UserName, .Uuid = Input.Uuid, .Type = "Microsoft", .ClientToken = Input.Uuid, .ProfileJson = Input.ProfileJson}
GoTo SkipLogin
@@ -591,7 +591,7 @@ Relogin:
Setup.Set("LoginMsJson", MsJson.ToString(Newtonsoft.Json.Formatting.None))
Data.Output = New McLoginResult With {.AccessToken = AccessToken, .Name = Result(1), .Uuid = Result(0), .Type = "Microsoft", .ClientToken = Result(0), .ProfileJson = Result(2)}
'结束
McLoginMsRefreshTime = GetTimeTick()
McLoginMsRefreshTime = GetTimeMs()
McLaunchLog("微软登录完成")
SkipLogin:
Setup.Set("HintBuy", True) '关闭正版购买提示
@@ -1314,40 +1314,6 @@ Retry:
Private McLaunchArgument As String
''' <summary>
''' 释放 Java Wrapper 并返回完整文件路径。
''' </summary>
Public Function ExtractJavaWrapper() As String
Dim WrapperPath As String = PathPure & "JavaWrapper.jar"
Log("[Java] 选定的 Java Wrapper 路径:" & WrapperPath)
SyncLock ExtractJavaWrapperLock '避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突
Try
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex As Exception
If File.Exists(WrapperPath) Then
'因为未知原因 Java Wrapper 可能变为只读文件(#4243)
Log(ex, "Java Wrapper 文件释放失败,但文件已存在,将在删除后尝试重新生成", LogLevel.Developer)
Try
File.Delete(WrapperPath)
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex2 As Exception
Log(ex2, "Java Wrapper 文件重新释放失败,将尝试更换文件名重新生成", LogLevel.Developer)
WrapperPath = PathPure & "JavaWrapper2.jar"
Try
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex3 As Exception
Throw New FileNotFoundException("释放 Java Wrapper 最终尝试失败", ex3)
End Try
End Try
Else
Throw New FileNotFoundException("释放 Java Wrapper 失败", ex)
End If
End Try
End SyncLock
Return WrapperPath
End Function
Private ExtractJavaWrapperLock As New Object
'主方法,合并 Jvm、Game、Replace 三部分的参数数据
Private Sub McLaunchArgumentMain(Loader As LoaderTask(Of String, List(Of McLibToken)))
McLaunchLog("开始获取 Minecraft 启动参数")
@@ -1393,6 +1359,8 @@ Retry:
'自定义
Dim ArgumentGame As String = Setup.Get("VersionAdvanceGame", Instance:=McInstanceSelected)
Arguments += " " & ArgumentReplace(If(ArgumentGame = "", Setup.Get("LaunchAdvanceGame"), ArgumentGame))
'GC
Arguments = ProcessGCArguments(Arguments, McLaunchJavaSelected, McInstanceSelected)
'进行参数替换
Dim ReplaceArguments = McLaunchArgumentsReplace(McInstanceSelected, Loader)
If String.IsNullOrWhiteSpace(ReplaceArguments("${version_type}")) Then
@@ -1754,6 +1722,72 @@ NextInstance:
Return GameArguments
End Function
'工具方法
''' <summary>
''' 释放 Java Wrapper 并返回完整文件路径。
''' </summary>
Public Function ExtractJavaWrapper() As String
Dim WrapperPath As String = PathPure & "JavaWrapper.jar"
Log("[Java] 选定的 Java Wrapper 路径:" & WrapperPath)
Static Lock As New Object
SyncLock Lock '避免 OptiFine 和 Forge 安装时同时释放 Java Wrapper 导致冲突
Try
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex As Exception
If File.Exists(WrapperPath) Then
'因为未知原因 Java Wrapper 可能变为只读文件(#4243)
Log(ex, "Java Wrapper 文件释放失败,但文件已存在,将在删除后尝试重新生成", LogLevel.Developer)
Try
File.Delete(WrapperPath)
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex2 As Exception
Log(ex2, "Java Wrapper 文件重新释放失败,将尝试更换文件名重新生成", LogLevel.Developer)
WrapperPath = PathPure & "JavaWrapper2.jar"
Try
WriteFile(WrapperPath, GetResources("JavaWrapper"))
Catch ex3 As Exception
Throw New FileNotFoundException("释放 Java Wrapper 最终尝试失败", ex3)
End Try
End Try
Else
Throw New FileNotFoundException("释放 Java Wrapper 失败", ex)
End If
End Try
End SyncLock
Return WrapperPath
End Function
''' <summary>
''' 根据设置的 GC 方式返回对应的 JVM 参数。
''' </summary>
Private Function ProcessGCArguments(Arguments As String, SelectedJava As JavaEntry, Optional TargetInstance As McInstance = Nothing) As String
'0:在 Java 21+ 使用分代 ZGC,20~15 使用非分代的 ZGC,14- 使用 G1GC
'1:在 Java 21+ 使用分代 ZGC,20- 使用 G1GC
'2:仅 G1GC
'3:不指定
Dim SetupType As Integer = Setup.Get("LaunchAdvanceGC")
If TargetInstance IsNot Nothing AndAlso Setup.Get("VersionAdvanceGC", TargetInstance) > 0 Then SetupType = Setup.Get("VersionAdvanceGC", TargetInstance) - 1 '去掉默认
If SetupType = 3 Then Return Arguments.Trim '不指定时直接返回
Dim UseG1GC As Boolean =
(SetupType = 0 AndAlso SelectedJava.MajorVersion < 15) OrElse
(SetupType = 1 AndAlso SelectedJava.MajorVersion < 21) OrElse
SetupType = 2
McLaunchLog($"GC 设置:{SetupType},选取 {If(UseG1GC, "G1GC", "ZGC")},Java 版本:{SelectedJava.MajorVersion}")
'移除
Arguments = RegexReplace(Arguments,
"( )*-XX:[+-]?(Use\w+GC|ZGenerational|UseCompactObjectHeaders|G1\w+Percent|G1\w+Size|MaxGCPauseMillis)(=[^\s]+)?", "").Trim
'添加
If UseG1GC Then
Arguments = Arguments.Replace("-XX:+UnlockExperimentalVMOptions", "")
Arguments = "-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M " + Arguments
Else
Arguments = "-XX:+UseZGC " + Arguments
If SelectedJava.MajorVersion = 21 OrElse SelectedJava.MajorVersion = 22 Then Arguments = "-XX:+ZGenerational " + Arguments 'Java 23 起默认启用分代 ZGC,不需要再添加参数
If SelectedJava.MajorVersion >= 25 Then Arguments = "-XX:+UseCompactObjectHeaders " + Arguments
End If
Return Arguments.Trim
End Function
#End Region
#Region "解压 Natives"
@@ -1771,7 +1805,7 @@ NextInstance:
If Not Native.IsNatives Then Continue For
Dim Zip As ZipArchive
Try
Zip = New ZipArchive(New FileStream(Native.LocalPath, FileMode.Open))
Zip = New ZipArchive(New FileStream(Native.LocalPath, FileMode.Open, FileAccess.Read, FileShare.Read))
Catch ex As InvalidDataException
Log(ex, "打开 Natives 文件失败(" & Native.LocalPath & ")")
File.Delete(Native.LocalPath)
@@ -158,7 +158,7 @@ Public Module ModMinecraft
#Region "版本处理"
Public Const McInstanceCacheVersion As Integer = 32
Public Const McInstanceCacheVersion As Integer = 33
Private _McInstanceSelected As McInstance
''' <summary>
@@ -426,8 +426,8 @@ Public Module ModMinecraft
End Try
#End Region
VersionSearchFinish:
_Version.VanillaName = _Version.VanillaName.Replace("_unobfuscated", "").Replace(" Unobfuscated", "")
'获取版本号
_Version.VanillaName = _Version.VanillaName.Replace("_unobfuscated", "").Replace(" Unobfuscated", "")
If _Version.VanillaName.StartsWithF("1.") Then
Dim Segments = _Version.VanillaName.Split(" _-.".ToCharArray)
_Version.Vanilla = New Version(
@@ -471,6 +471,7 @@ VersionSearchFinish:
If Not JsonCandidate.ContainsKey("type") Then Continue For
If Not JsonCandidate.ContainsKey("id") Then Continue For
Catch
Continue For
End Try
JsonPath = JsonCandidatePath
Exit For
@@ -1013,10 +1014,10 @@ ExitDataLoad:
'字母编号,如 G2 中的 G(7)
Dim Result As Integer = Asc(OptiFine.ToUpper.First) - Asc("A"c) + 1
'末尾数字,如 C5 beta4 中的 5
Result *= Result * 100
Result *= 100
Result += Val(RegexSeek(Right(OptiFine, OptiFine.Length - 1), "[0-9]+"))
'测试标记(正式版为 99,Pre[x] 为 50+x,Beta[x] 为 x)
Result *= Result * 100
Result *= 100
If OptiFine.ContainsF("pre", True) Then Result += 50
If OptiFine.ContainsF("pre", True) OrElse OptiFine.ContainsF("beta", True) Then
If Val(Right(OptiFine, 1)) = 0 AndAlso Right(OptiFine, 1) <> "0" Then
@@ -1425,7 +1426,7 @@ OnLoaded:
'确认最新版本,若为快照则加入常用列表
Dim LargestInstance As McInstance = InstanceList.
Where(Function(v) v.State = McInstanceState.Original OrElse v.State = McInstanceState.Snapshot).
MaxOf(Function(v) v.ReleaseTime)
MaxBy(Function(v) v.ReleaseTime)
If LargestInstance IsNot Nothing AndAlso LargestInstance.State = McInstanceState.Snapshot Then
InstanceUseful.Add(LargestInstance)
InstanceList.Remove(LargestInstance)
@@ -1,7 +1,7 @@
Imports System.IO.Compression
Public Module ModMod
Private Const LocalModCacheVersion As Integer = 10
Private Const LocalModCacheVersion As Integer = 11
Public Class McMod
@@ -115,7 +115,7 @@ Public Module ModMod
End Get
Set(value As String)
If _Version IsNot Nothing AndAlso RegexCheck(_Version, "[0-9.\-]+") Then Return
If value?.ContainsF("version", True) Then value = "version" '需要修改的标识
If value IsNot Nothing AndAlso value.ContainsF("version", True) Then value = "version" '需要修改的标识
_Version = value
End Set
End Property
@@ -137,7 +137,7 @@ Public Module ModMod
'基础可用性检查、打开 Jar 文件
If Path.Length < 2 Then Throw New FileNotFoundException("错误的 Mod 文件路径(" & If(Path, "null") & ")")
If Not File.Exists(Path) Then Throw New FileNotFoundException("未找到 Mod 文件(" & Path & ")")
Jar = New ZipArchive(New FileStream(Path, FileMode.Open))
Jar = New ZipArchive(New FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read))
'信息获取
LoadMetadataFromJar(Jar)
Catch ex As UnauthorizedAccessException
@@ -424,7 +424,7 @@ Finished:
''' </summary>
Public ReadOnly Property CanUpdate As Boolean
Get
Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso Not Setup.Get("VersionAdvanceDisableModUpdate", Instance:=PageInstanceLeft.Instance) AndAlso ChangelogUrls.Any()
Return Not Setup.Get("UiHiddenFunctionModUpdate") AndAlso Not Setup.Get("VersionAdvanceDisableModUpdate", Instance:=PageInstanceLeft.Instance) AndAlso UpdateFile IsNot Nothing
End Get
End Property
@@ -604,7 +604,7 @@ Finished:
For Each ModFile As FileInfo In ModFileList
If Loader.IsAborted Then Return
Dim ModEntry As New McMod(ModFile.FullName)
Dim DumpMod As McMod = ModList.FirstOrDefault(Function(m) m.RawFileName = ModEntry.RawFileName)
Dim DumpMod As McMod = ModList.FirstOrDefault(Function(m) m.RawFileName = ModEntry.RawFileName) '存在两个文件,名称相同,但一个启用一个禁用
If DumpMod IsNot Nothing Then
Dim DisabledMod As McMod = If(DumpMod.State = McMod.McModState.Disabled, DumpMod, ModEntry)
Log($"[Mod] 重复的 Mod 文件:{DumpMod.FileName} 与 {ModEntry.FileName},已忽略 {DisabledMod.FileName}", LogLevel.Debug)
@@ -619,10 +619,7 @@ Finished:
Log($"[Mod] 共发现 {ModList.Count} 个 Mod")
'排序
ModList = ModList.SortByComparison(
Function(Left, Right) As Boolean
Return Not Right.FileName.CompareTo(Left.FileName)
End Function)
ModList = ModList.OrderBy(Function(m) m.FileName).ToList
'回设
If Loader.IsAborted Then Return
@@ -112,19 +112,14 @@ Public Module ModModpack
Private Sub ExtractModpackFiles(InstallTemp As String, FileAddress As String, Loader As LoaderBase, ProgressIncrement As Double)
'解压文件
Dim RetryCount As Integer = 1
Dim Encode = Encoding.GetEncoding("GB18030")
Dim InitialProgress = Loader.Progress
Try
Retry:
Loader.Progress = InitialProgress
DeleteDirectory(InstallTemp)
ExtractFile(FileAddress, InstallTemp, Encode, ProgressIncrementHandler:=Sub(Delta) Loader.Progress += Delta * ProgressIncrement)
ExtractCompressedFile(FileAddress, InstallTemp, ProgressIncrementHandler:=Sub(Delta) Loader.Progress += Delta * ProgressIncrement)
Catch ex As Exception
Log(ex, "第 " & RetryCount & " 次解压尝试失败")
If TypeOf ex Is ArgumentException Then
Encode = Encoding.UTF8
Log("[ModPack] 已切换压缩包解压编码为 UTF8")
End If
'完全不知道为啥会出现文件正在被另一进程使用的问题,总之加个重试
If RetryCount < 5 Then
Thread.Sleep(RetryCount * 2000)
@@ -278,7 +273,7 @@ Retry:
If ModJson("modules").Any Then 'modules 可能返回 null(#1006)
Dim ModuleNames = CType(ModJson("modules"), JArray).Select(Function(l) l("name").ToString).ToList
If ModuleNames.Contains("META-INF") OrElse ModuleNames.Contains("mcmod.info") OrElse
ModJson?("FileName")?.ToString?.EndsWithF(".jar", True) Then
ModJson?("FileName")?.ToString.EndsWithF(".jar", True) Then
TargetFolder = "mods" : Type = CompType.Mod
ElseIf ModuleNames.Contains("pack.mcmeta") Then
TargetFolder = "resourcepacks" : Type = CompType.ResourcePack
@@ -641,9 +636,8 @@ Retry:
Dim Request As New McInstallRequest With {.NewInstanceName = InstanceName, .VersionFolder = $"{McFolderSelected}versions\{InstanceName}\"}
For Each Component As JObject In PackJson("components")
If Not Component.ContainsKey("uid") Then Continue For
Select Case Component("uid").ToString
Case "org.lwjgl"
Log("[ModPack] 已跳过 LWJGL 项")
Dim UID As String = Component("uid").ToString
Select Case UID
Case "net.minecraft"
Request.MinecraftName = Component("version")
Case "net.minecraftforge"
@@ -652,9 +646,13 @@ Retry:
Request.NeoForgeVersion = Component("version")
Case "net.fabricmc.fabric-loader"
Request.FabricVersion = Component("version")
Case Else
'Case "org.quiltmc.quilt-loader" 'eg. 1.0.0
NotifyIncompatibleLoader(Component("uid"))
Case Else
If UID.StartsWithF("org.lwjgl") Then '#8210
Log("[ModPack] 已跳过 LWJGL 项:" & UID)
Else
NotifyIncompatibleLoader(UID)
End If
End Select
Next
'构造加载器
+6 -2
View File
@@ -167,6 +167,7 @@ Public Class CustomEvent
修改变量
写入变量
加入房间
检查更新
End Enum
''' <summary>
@@ -175,10 +176,10 @@ Public Class CustomEvent
Public Shared Sub Raise(Type As EventType, Arg As String)
If Type = EventType.None Then Return
Log($"[Control] 执行自定义事件:{Type}, {Arg}")
If Arg Is Nothing Then Arg = ""
Dim Args As String() = Arg.Split("|")
Try
Dim Args As String() = If(Arg?.Split("|"), {""})
Select Case Type
Case EventType.打开网页
Arg = Arg.Replace("\", "/")
If Not Arg.Contains("://") OrElse Arg.StartsWithF("file", True) Then '为了支持更多协议(#2200)
@@ -320,6 +321,9 @@ Public Class CustomEvent
End If
End Sub)
Case EventType.检查更新
UpdateCheckByButton()
Case Else
MyMsgBox("未知的事件类型:" & Type & vbCrLf & "请检查事件类型填写是否正确,或者 PCL 是否为最新版本。", "事件执行失败")
End Select
+2 -3
View File
@@ -647,7 +647,7 @@ NextFile:
DeleteDirectory(PathTemp & "Help")
Directory.CreateDirectory(PathTemp & "Help")
WriteFile(PathTemp & "Cache\Help.zip", GetResources("Help"))
ExtractFile(PathTemp & "Cache\Help.zip", PathTemp & "Help", Encoding.UTF8)
ExtractCompressedFile(PathTemp & "Cache\Help.zip", PathTemp & "Help")
Setup.Set("SystemHelpVersion", VersionCode)
Log("[Help] 已解压内置帮助文件,目前状态:" & File.Exists(PathTemp & "Help\启动器\备份设置.xaml"), LogLevel.Debug)
End If
@@ -792,8 +792,7 @@ NextFile:
'查看现有设置
Using ReadOnlyKey = My.Computer.Registry.CurrentUser.OpenSubKey(REG_KEY, False)
If ReadOnlyKey IsNot Nothing Then
Dim CurrentValue = ReadOnlyKey.GetValue(Executeable)
If REG_VALUE = CurrentValue?.ToString() Then
If REG_VALUE = ReadOnlyKey.GetValue(Executeable)?.ToString() Then
Log($"[System] 无需调整显卡设置:{Executeable}")
Return
End If
+8 -4
View File
@@ -261,6 +261,7 @@
''' <summary>
''' 当前正在播放的 NAudio.Wave.WaveOutEvent。
''' 如果为它赋了类别,则会没有必要地加载 NAudio.dll。
''' </summary>
Public MusicNAudio = Nothing
''' <summary>
@@ -279,7 +280,12 @@
CurrentWave = New NAudio.Wave.WaveOutEvent()
MusicNAudio = CurrentWave
CurrentWave.DeviceNumber = -1
Reader = New NAudio.Wave.AudioFileReader(MusicCurrent)
Try
Reader = New NAudio.Wave.AudioFileReader(MusicCurrent)
Catch ex As Exception
Log(ex, "使用 AudioFileReader 加载音频文件失败,换用 MediaFoundationReader 重试", LogLevel.Developer)
Reader = New NAudio.Wave.MediaFoundationReader(MusicCurrent)
End Try
CurrentWave.Init(Reader)
CurrentWave.Play()
'第一次打开的暂停
@@ -310,9 +316,7 @@
Hint("由于音频设备变更,音乐播放功能在重启 PCL 后才能恢复!", HintType.Red)
Thread.Sleep(1000000000)
End If
If ex.Message.Contains("Got a frame at sample rate") OrElse ex.Message.Contains("does not support changes to") Then
Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 不支持播放音频属性在中途发生变化的音乐", HintType.Red)
ElseIf Not (MusicCurrent.EndsWithF(".wav", True) OrElse MusicCurrent.EndsWithF(".mp3", True) OrElse MusicCurrent.EndsWithF(".flac", True)) OrElse
If Not (MusicCurrent.EndsWithF(".wav", True) OrElse MusicCurrent.EndsWithF(".mp3", True) OrElse MusicCurrent.EndsWithF(".flac", True)) OrElse
ex.Message.Contains("0xC00D36C4") Then '#5096:不支持给定的 URL 的字节流类型。 (异常来自 HRESULT:0xC00D36C4)
Hint("播放音乐失败(" & GetFileNameFromPath(MusicCurrent) & "):PCL 可能不支持此音乐格式,请将格式转换为 .wav、.mp3 或 .flac 后再试", HintType.Red)
Else
@@ -51,6 +51,6 @@ Imports System.Runtime.InteropServices
' 可以指定所有值,也可以使用以下所示的 "*" 预置版本号和修订号
' 方法是按如下所示使用“*”
<Assembly: AssemblyVersion("2.12.0.0")>
<Assembly: AssemblyFileVersion("2.12.0.0")>
<Assembly: AssemblyVersion("2.12.3.0")>
<Assembly: AssemblyFileVersion("2.12.3.0")>
<Assembly: NeutralResourcesLanguage("")>
@@ -111,7 +111,7 @@ Public Class PageComp
IsFirstLoaded = True
CType(Parent, MyPageRight).PageLoaderInit(Load, PanLoad, PanContent, PanAlways, Loader, AddressOf Load_OnFinish, AddressOf LoaderInput)
'将最高 Drop 加入筛选
If AllDrops?.Any AndAlso AllDrops.First > 250 Then
If AllDrops IsNot Nothing AndAlso AllDrops.First > 250 Then
Dim HighestVersion As String = McVersion.DropToVersion(AllDrops.First)
If CType(TextSearchVersion.Items(1), MyComboBoxItem).Content.ToString <> HighestVersion Then '0 是全部
TextSearchVersion.Items.Insert(1, New MyComboBoxItem With {.Content = HighestVersion})
@@ -359,8 +359,38 @@ GroupDone:
If Not Instance.IsLoaded Then Instance.Load()
'只对 Mod 和数据包进行版本检测
If File.Type = CompType.Mod OrElse File.Type = CompType.DataPack Then
If File.GameVersions.Any(Function(v) v.Contains(".")) AndAlso
Not File.GameVersions.Any(Function(v) v.Contains(".") AndAlso v = Instance.Version.VanillaName) Then Return False
If Not File.GameVersions.Any( '判断是否有任何一个版本匹配
Function(FileVersion)
Dim FileIsSnapshot As Boolean = FileVersion.Contains("预览版") OrElse Not FileVersion.Contains(".")
Dim InstanceIsSnapshot As Boolean = Instance.Version.VanillaName.Contains("snapshot") OrElse Not Instance.Version.VanillaName.Contains(".")
If FileIsSnapshot <> InstanceIsSnapshot Then
'只有一种是预览版
Return False
ElseIf Not FileIsSnapshot AndAlso Not InstanceIsSnapshot Then
'都不是预览版
Return FileVersion = Instance.Version.VanillaName
Else
'都是预览版
Dim FileIsNewFormat As Boolean = FileVersion.Contains(".") AndAlso Val(FileVersion.BeforeFirst(".")) > 1
Dim InstanceIsNewFormat As Boolean = Instance.Version.Drop > 250
If FileIsNewFormat <> InstanceIsNewFormat Then
'只有一种是新格式快照
Return False
ElseIf FileIsNewFormat AndAlso InstanceIsNewFormat Then
'都是新格式快照
Return FileVersion.BeforeFirst(" ") = Instance.Version.VanillaName.BeforeFirst("-")
Else
'都是旧格式快照
If FileVersion.Contains("w") AndAlso Instance.Version.VanillaName.Contains("w") Then
'都是 w 快照
Return FileVersion = Instance.Version.VanillaName
Else
'不全是 w 快照
Return True '只能假定相同,无法判断
End If
End If
End If
End Function) Then Return False
End If
'加载器
If Not AllowedLoaders.Any() Then Return True '无要求
@@ -254,7 +254,7 @@ echo 你可以在 PCL 的 [设置 → 启动选项] 中查看已安装的 java
echo ------------------------------
echo 如果提示 ""You need to agree to the EULA in order to run the server"",请打开 eula.txt,按说明阅读并同意 Minecraft EULA 后,将该文件最后一行中的 eula=false 改为 eula=true。
echo ------------------------------
""java"" -server -XX:+UseG1GC -Xmx4096M -Xms1024M -XX:+UseCompressedOops -jar {Id}-server.jar nogui
""java"" -server -XX:+UseG1GC -Xmx6144M -Xms1024M -jar {Id}-server.jar nogui
echo ----------------------
echo 服务端已停止。
pause"
@@ -1329,7 +1329,7 @@ Retry:
Dim Installer As ZipArchive = Nothing
Try
'解压并获取、合并两个 Json 的信息
Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open))
Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open, FileAccess.Read, FileShare.Read))
Task.Progress = 0.2
Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open))
Dim Json2 As JObject = GetJson(ReadFile(Installer.GetEntry("version.json").Open))
@@ -1417,7 +1417,7 @@ Retry:
'记录当前文件夹列表(在新建目标文件夹之前)
Dim OldList = New DirectoryInfo(McFolder & "versions\").EnumerateDirectories.Select(Function(i) i.FullName).ToList()
'解压并获取信息
Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open))
Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open, FileAccess.Read, FileShare.Read))
Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open))
'新建目标版本文件夹
Directory.CreateDirectory(VersionFolder)
@@ -1484,7 +1484,7 @@ Retry:
Dim Installer As ZipArchive = Nothing
Try
'解压并获取信息
Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open))
Installer = New ZipArchive(New FileStream(InstallerAddress, FileMode.Open, FileAccess.Read, FileShare.Read))
Task.Progress = 0.2
Dim Json As JObject = GetJson(ReadFile(Installer.GetEntry("install_profile.json").Open))
Task.Progress = 0.4
@@ -1501,7 +1501,7 @@ Retry:
Task.Progress = 0.6
'解压支持库文件
Installer.Dispose()
ExtractFile(InstallerAddress, InstallerAddress & "_unrar\")
ExtractCompressedFile(InstallerAddress, InstallerAddress & "_unrar\")
CopyDirectory(InstallerAddress & "_unrar\maven\", McFolder & "libraries\")
DeleteDirectory(InstallerAddress & "_unrar\")
Else
@@ -2102,7 +2102,11 @@ Retry:
Dim OptiFineAsMod As Boolean = Request.OptiFineEntry IsNot Nothing AndAlso Modable '选择了 OptiFine 与任意 Mod 加载器
If OptiFineAsMod Then
Log("[Download] OptiFine 将作为 Mod 进行下载")
OptiFineFolder = ModsTempFolder
If Request.LiteLoaderEntry IsNot Nothing Then
OptiFineFolder = ModsTempFolder & Request.MinecraftName & "\" '#8147
Else
OptiFineFolder = ModsTempFolder
End If
End If
'记录日志
@@ -94,7 +94,7 @@
<local:ExportOption
Title="整合包重要数据"
Description="脚本文件、内置资源包、数据包等"
Rules="addons/|multiblocked/|modpack-update-checker/|global_packs/|global_resource_packs/|global_data_packs/|optional_data_packs/|maps/|icon.png|mods-resourcepacks/|matmos/|resource_assorts/|resource_assorts.json|patchouli_books/|datapacks/|kubejs*/|!kubejs*/probe/|!kubejs*/exported/|!kubejs*/jsconfig.json|!kubejs*/README.txt|openloader/|worldshape/|resources/|scripts/|structures/|fontfiles/|oresources/|packmenu/|craftpresence/|pointblanks/|template*/|!template*/playerdata/|!template*/stats/"
Rules="hotai/|bansoukou/|addons/|multiblocked/|modpack-update-checker/|global_packs/|global_resource_packs/|global_data_packs/|optional_data_packs/|moonlight-global-datapacks/|maps/|icon.png|mods-resourcepacks/|matmos/|resource_assorts/|resource_assorts.json|patchouli_books/|datapacks/|kubejs*/|!kubejs*/probe/|!kubejs*/exported/|!kubejs*/jsconfig.json|!kubejs*/README.txt|openloader/|worldshape/|resources/|scripts/|structures/|fontfiles/|oresources/|packmenu/|craftpresence/|pointblanks/|template*/|!template*/playerdata/|!template*/stats/"
DefaultChecked="True" />
</local:MyCheckBox.Tag>
</local:MyCheckBox>
@@ -114,6 +114,14 @@
DefaultChecked="True" />
</local:MyCheckBox.Tag>
</local:MyCheckBox>
<local:MyCheckBox>
<local:MyCheckBox.Tag>
<local:ExportOption
Title="已上传的沉浸画"
Rules="immersive_paintings/"
DefaultChecked="True" />
</local:MyCheckBox.Tag>
</local:MyCheckBox>
<local:MyCheckBox>
<local:MyCheckBox.Tag>
<local:ExportOption
@@ -367,8 +367,8 @@ Public Class PageInstanceExport
TextExportVersion.Text = Ini.GetOrDefault("Version", "")
CheckOptionsPcl.Checked = Ini.GetOrDefault("IncludeLauncher", True)
CheckOptionsPclCustom.Checked = Ini.GetOrDefault("IncludeLauncherCustom", True)
CheckAdvancedModrinth.Checked = Ini.GetOrDefault("ModrinthUploadMode", False)
CheckAdvancedInclude.Checked = Ini.GetOrDefault("DontCheckHostedAssets", False)
If Not CheckAdvancedInclude.Checked Then CheckAdvancedModrinth.Checked = Ini.GetOrDefault("ModrinthUploadMode", False) '#7979,加个特判
ConfigPackPath = Ini.GetOrDefault("PackPath", Nothing)
'导出内容段
RulesOverrides = Segments(1).Replace(vbCr, vbLf).Replace(vbLf & vbLf, vbLf).Split(vbLf).ToList
@@ -424,7 +424,7 @@ Public Class PageInstanceExport
End If
If PackPath Is Nothing Then
Dim Extensions As New List(Of String)
If Not CheckAdvancedModrinth.Checked Then Extensions.Add("压缩文件(*.zip)|*.zip")
Extensions.Add("压缩文件(*.zip)|*.zip")
If Not CheckOptionsPcl.Checked Then Extensions.Add("Modrinth 整合包文件(*.mrpack)|*.mrpack")
PackPath = SelectSaveFile("选择导出位置",
PackName & If(String.IsNullOrEmpty(TextExportVersion.Text), "", " " & TextExportVersion.Text), Extensions.Join("|"))
@@ -71,7 +71,7 @@
''' <summary>
''' 已加载的 Mod UI 缓存,不确保按显示顺序排列。Key 为 Mod 的 RawFileName。
''' </summary>
Public ModItems As New Dictionary(Of String, MyLocalModItem)
Public ModItems As New SafeDictionary(Of String, MyLocalModItem)
''' <summary>
''' 将加载器结果的 Mod 列表加载为 UI。
''' </summary>
@@ -87,10 +87,12 @@
Return
End If
'修改缓存
ModItems.Clear()
For Each ModEntity As McMod In McModLoader.Output
ModItems(ModEntity.RawFileName) = McModListItem(ModEntity)
Next
SyncLock ModItems.SyncRoot
ModItems.Clear()
For Each ModEntity As McMod In McModLoader.Output
ModItems(ModEntity.RawFileName) = McModListItem(ModEntity)
Next
End SyncLock
'显示结果
Filter = FilterType.All
SearchBox.Text = "" '这会触发结果刷新,所以需要在 ModItems 更新之后,详见 #3124 的视频
@@ -151,11 +153,13 @@
If ShowingMods.Any() Then
PanList.Visibility = Visibility.Visible
PanList.Children.Clear()
For Each TargetMod In ShowingMods
Dim Item As MyLocalModItem = ModItems(TargetMod.RawFileName)
Item.Checked = SelectedMods.Contains(TargetMod.RawFileName) '更新选中状态
PanList.Children.Add(Item)
Next
SyncLock ModItems.SyncRoot
For Each TargetMod In ShowingMods
Dim Item As MyLocalModItem = ModItems(TargetMod.RawFileName)
Item.Checked = SelectedMods.Contains(TargetMod.RawFileName) '更新选中状态
PanList.Children.Add(Item)
Next
End SyncLock
Else
PanList.Visibility = Visibility.Collapsed
End If
@@ -528,6 +532,7 @@ Install:
If IndexOfUi = -1 Then Continue For '因为未知原因 Mod 的状态已经切换完了
PanList.Children.RemoveAt(IndexOfUi)
PanList.Children.Insert(IndexOfUi, NewItem)
NewItem.UpdateLayout() '#7861
Next
If IsSuccessful Then
RefreshBars()
@@ -591,7 +596,7 @@ Install:
NewestReplaceName = Join(NewestSegs, "-")
End If
'添加到下载列表
Dim TempAddress As String = PathTemp & "DownloadedMods\" & Entry.FileName.Replace(CurrentReplaceName, NewestReplaceName)
Dim TempAddress As String = PathTemp & "mods\" & Entry.FileName.Replace(CurrentReplaceName, NewestReplaceName) '需要下载到 mods 文件夹,以便重复文件检查时识别
Dim RealAddress As String = GetPathFromFullPath(Entry.Path) & Entry.FileName.Replace(CurrentReplaceName, NewestReplaceName)
FileList.Add(File.ToNetFile(TempAddress))
FileCopyList(TempAddress) = RealAddress
@@ -845,22 +850,22 @@ Install:
'构造请求
Dim QueryList As New List(Of SearchEntry(Of McMod))
For Each Entry As McMod In McModLoader.Output
Dim SearchSource As New List(Of KeyValuePair(Of String, Double))
SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.DisplayName, 1))
SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.FileName, 1))
Dim SearchSources As New List(Of SearchSource)
SearchSources.Add(New SearchSource(Entry.DisplayName, 1))
SearchSources.Add(New SearchSource(Entry.FileName, 1))
If Entry.Version IsNot Nothing Then
SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Version, 0.2))
SearchSources.Add(New SearchSource(Entry.Version, 0.2))
End If
If Entry.Description IsNot Nothing AndAlso Entry.Description <> "" Then
SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Description, 0.4))
SearchSources.Add(New SearchSource(Entry.Description, 0.4))
End If
If Entry.Comp IsNot Nothing Then
If Entry.Comp.RawName <> Entry.DisplayName Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.RawName, 1))
If Entry.Comp.TranslatedName <> Entry.Comp.RawName Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.TranslatedName, 1))
If Entry.Comp.Description <> Entry.Description Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.Description, 0.4))
SearchSource.Add(New KeyValuePair(Of String, Double)(String.Join("", Entry.Comp.Tags), 0.2))
If Entry.Comp.RawName <> Entry.DisplayName Then SearchSources.Add(New SearchSource(Entry.Comp.RawName, 1))
If Entry.Comp.TranslatedName <> Entry.Comp.RawName Then SearchSources.Add(New SearchSource(Entry.Comp.TranslatedName, 1))
If Entry.Comp.Description <> Entry.Description Then SearchSources.Add(New SearchSource(Entry.Comp.Description, 0.4))
SearchSources.Add(New SearchSource(String.Join("", Entry.Comp.Tags), 0.2))
End If
QueryList.Add(New SearchEntry(Of McMod) With {.Item = Entry, .SearchSource = SearchSource})
QueryList.Add(New SearchEntry(Of McMod) With {.Item = Entry, .SearchSource = SearchSources})
Next
'进行搜索
SearchResult = Search(QueryList, SearchBox.Text, MaxBlurCount:=6, MinBlurSimilarity:=0.35).Select(Function(r) r.Item).ToList
@@ -56,7 +56,7 @@
</Grid>
</StackPanel>
</local:MyCard>
<local:MyCard Margin="0,0,0,15" Title="游戏内存">
<local:MyCard Margin="0,0,0,15" Title="内存分配">
<StackPanel Margin="25,40,25,15">
<local:MyHint x:Name="LabRamWarn" Text="32 位 Java 只能分配最多 1 GB 的内存。强烈建议换用 64 位的 Java!" Theme="Yellow" Margin="0,0,0,12" />
<Grid Margin="0,0,0,3">
@@ -204,6 +204,8 @@
<RowDefinition Height="9" />
<RowDefinition Height="28" />
<RowDefinition Height="9" />
<RowDefinition Height="28" />
<RowDefinition Height="9" />
<RowDefinition Height="31" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
@@ -213,12 +215,24 @@
Height="100" AcceptsReturn="True" TextWrapping="Wrap" VerticalContentAlignment="Top" Padding="0,5" VerticalScrollBarVisibility="Auto" />
<TextBlock Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Left" Text="游戏参数" Margin="0,0,25,0" />
<local:MyTextBox x:Name="TextAdvanceGame" Grid.Row="2" Grid.ColumnSpan="2" Tag="VersionAdvanceGame" Grid.Column="1" ToolTip="文本框中的内容将会被直接拼合在启动参数的末尾。&#xa;例如,输入 --demo 则会以试玩模式启动游戏。&#xa;&#xa;支持 Minecraft 版本 JSON 中的字符串替换标记,例如 ${library_directory}。&#xa;也支持使用 PCL 的替换标记,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。&#xa;&#xa;若留空,则跟随全局设置的值。" HintText="跟随全局设置" />
<TextBlock Grid.Row="4" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,3" />
<local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="4" Grid.ColumnSpan="2" Tag="VersionAdvanceRun" Grid.Column="1" HintText="" Margin="0,0,0,3"
<TextBlock VerticalAlignment="Center" Grid.Row="4" HorizontalAlignment="Left" Text="内存管理" Margin="0,0,25,0" />
<local:MyComboBox x:Name="ComboAdvanceGC" Grid.Row="4" Grid.ColumnSpan="2" Tag="VersionAdvanceGC" Grid.Column="1">
<local:MyComboBoxItem Content="跟随全局设置" IsSelected="True" />
<local:MyComboBoxItem Content="尽量使用 ZGC(激进)"
ToolTipService.BetweenShowDelay="0" ToolTip="在 Java 21+ 使用分代 ZGC,20~15 使用非分代的 ZGC,14- 使用 G1GC。&#xa;在 Java 25+ 额外添加 -XX:+UseCompactObjectHeaders。&#xa;&#xa;覆盖 “Java 虚拟机参数” 设置中的 GC 相关参数。" />
<local:MyComboBoxItem Content="尽量使用分代 ZGC(平衡)"
ToolTipService.BetweenShowDelay="0" ToolTip="在 Java 21+ 使用分代 ZGC,20- 使用 G1GC。&#xa;在 Java 25+ 额外添加 -XX:+UseCompactObjectHeaders。&#xa;&#xa;覆盖 “Java 虚拟机参数” 设置中的 GC 相关参数。" />
<local:MyComboBoxItem Content="仅 G1GC"
ToolTipService.BetweenShowDelay="0" ToolTip="固定使用以下参数:&#xa;-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M&#xa;&#xa;覆盖 “Java 虚拟机参数” 设置中的 GC 相关参数。" />
<local:MyComboBoxItem Content="不指定(可自定义)"
ToolTipService.BetweenShowDelay="0" ToolTip="不自动添加 GC 参数。&#xa;&#xa;仅当选择此项时,你可以在 “Java 虚拟机参数” 设置中手动添加 GC 参数。" />
</local:MyComboBox>
<TextBlock Grid.Row="6" VerticalAlignment="Center" HorizontalAlignment="Left" Text="启动前执行命令" Margin="0,0,25,3" />
<local:MyTextBox x:Name="TextAdvanceRun" Grid.Row="6" Grid.ColumnSpan="2" Tag="VersionAdvanceRun" Grid.Column="1" HintText="" Margin="0,0,0,3"
ToolTip="该项不会覆盖全局设置:启动时会先执行全局设置的命令,再执行版本设置的命令。&#xa;&#xa;在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;你可以用替换标记来动态改变其内容或实现相对路径,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}options.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />
<local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="VersionAdvanceRunWait" Visibility="Collapsed"
<local:MyCheckBox Grid.Row="7" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="VersionAdvanceRunWait" Visibility="Collapsed"
Text="等待命令执行完成后再继续启动" Margin="0,5,0,0" />
<StackPanel Margin="0,12,0,4" Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Left">
<StackPanel Margin="0,12,0,4" Grid.Row="8" Grid.ColumnSpan="2" HorizontalAlignment="Left">
<local:MyCheckBox Height="28" Text="禁止更新 Mod" x:Name="CheckAdvanceDisableModUpdate" Tag="VersionAdvanceDisableModUpdate"
ToolTip="禁止为此版本更新 Mod,以防止整合包玩家误操作"
ToolTipService.Placement="Right" />
@@ -51,6 +51,7 @@
'高级设置
TextAdvanceJvm.Text = Setup.Get("VersionAdvanceJvm", Instance:=PageInstanceLeft.Instance)
TextAdvanceGame.Text = Setup.Get("VersionAdvanceGame", Instance:=PageInstanceLeft.Instance)
ComboAdvanceGC.SelectedIndex = Setup.Get("VersionAdvanceGC", Instance:=PageInstanceLeft.Instance)
TextAdvanceRun.Text = Setup.Get("VersionAdvanceRun", Instance:=PageInstanceLeft.Instance)
CheckAdvanceRunWait.Checked = Setup.Get("VersionAdvanceRunWait", Instance:=PageInstanceLeft.Instance)
If Setup.Get("VersionAdvanceAssets", Instance:=PageInstanceLeft.Instance) = 2 Then
@@ -86,6 +87,7 @@
Setup.Reset("VersionRamOptimize", Instance:=PageInstanceLeft.Instance)
Setup.Reset("VersionAdvanceJvm", Instance:=PageInstanceLeft.Instance)
Setup.Reset("VersionAdvanceGame", Instance:=PageInstanceLeft.Instance)
Setup.Reset("VersionAdvanceGC", Instance:=PageInstanceLeft.Instance)
Setup.Reset("VersionAdvanceAssets", Instance:=PageInstanceLeft.Instance)
Setup.Reset("VersionAdvanceAssetsV2", Instance:=PageInstanceLeft.Instance)
Setup.Reset("VersionAdvanceJava", Instance:=PageInstanceLeft.Instance)
@@ -93,7 +95,6 @@
Setup.Reset("VersionAdvanceRunWait", Instance:=PageInstanceLeft.Instance)
Setup.Reset("VersionAdvanceDisableJLW", Instance:=PageInstanceLeft.Instance)
Setup.Reset("VersionAdvanceDisableModUpdate", Instance:=PageInstanceLeft.Instance)
Setup.Reset("VersionArgumentJavaSelect", Instance:=PageInstanceLeft.Instance)
JavaSearchLoader.Start(IsForceRestart:=True)
@@ -121,7 +122,7 @@
Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderRamCustom.Change
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value, Instance:=PageInstanceLeft.Instance)
End Sub
Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboRamOptimize.SelectionChanged
Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboRamOptimize.SelectionChanged, ComboAdvanceGC.SelectionChanged
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex, Instance:=PageInstanceLeft.Instance)
End Sub
Private Shared Sub CheckBoxLikeComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndieV2.SelectionChanged
@@ -232,7 +232,7 @@ Retry:
Dim Uuid As String = McLoginMsLoader.Output.Uuid
Dim SkinData As JObject = GetJson(McLoginMsLoader.Output.ProfileJson)
'获取玩家的所有披风
Dim SelId As Integer? = Nothing
Dim SelectedIndex As Integer? = Nothing
RunInUiWait(
Sub()
Try
@@ -246,22 +246,23 @@ Retry:
{"Mojang Office", "Mojang 办公室披风"}, {"Home", "家园披风"}, {"Menace", "入侵披风"}, {"Yearn", "渴望披风"},
{"Common", "普通披风"}, {"Pan", "薄煎饼披风"}, {"Founder's", "创始人披风"}, {"Copper", "铜披风"}, {"Zombie Horse", "僵尸马披风"}
}
Dim SelectionControl As New List(Of IMyRadio) From {New MyRadioBox With {.Text = "无披风"}}
Dim SelectionControl As New List(Of IMyRadio) From {
New MyRadioBox With {.Text = "无披风", .Checked = Not SkinData("capes").Any(Function(c) c("state")?.ToString = "ACTIVE")}}
For Each Cape In SkinData("capes")
Dim CapeName As String = Cape("alias").ToString
If CapeNames.ContainsKey(CapeName) Then CapeName = CapeNames(CapeName)
SelectionControl.Add(New MyRadioBox With {.Text = CapeName, .Checked = Cape("state")?.ToString = "ACTIVE"})
Next
SelId = MyMsgBoxSelect(SelectionControl, "选择披风", "确定", "取消")
SelectedIndex = MyMsgBoxSelect(SelectionControl, "选择披风", "确定", "取消")
Catch ex As Exception
Log(ex, "获取玩家皮肤列表失败", LogLevel.Feedback)
End Try
End Sub)
If SelId Is Nothing Then Return
If SelectedIndex Is Nothing Then Return
'发送请求
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)),
If(SelectedIndex = 0, HttpMethod.Delete, HttpMethod.Put),
Content:=If(SelectedIndex = 0, "", New JObject(New JProperty("capeId", SkinData("capes")(SelectedIndex - 1)("id"))).ToString(0)),
ContentType:="application/json",
Headers:={{"Authorization", "Bearer " & AccessToken}})
If Result.Contains("""errorMessage""") Then
@@ -269,6 +270,12 @@ Retry:
Return
Else
Hint("更改披风成功!", HintType.Green)
'更新当前选择的披风
For Each Cape In SkinData("capes")
Cape("state") = "INACTIVE"
Next
If SelectedIndex > 0 Then SkinData("capes")(SelectedIndex - 1)("state") = "ACTIVE"
McLoginMsLoader.Output.ProfileJson = SkinData.ToString()
End If
Catch ex As Exception
Log(ex, "更改披风失败", LogLevel.Hint)
@@ -88,7 +88,7 @@ Public Class PageLaunchRight
Url = "https://pcl-bmcl.milu.ink/"
Case 7
Log("[Page] 主页预设:主页市场")
Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/JingHai-Lingyun/Custom.xaml"
Url = "https://pclhomeplazaoss.lingyunawa.top:26994/p/Homepages/Homepage.Market/Custom.xaml"
Case 8
Log("[Page] 主页预设:更新日志")
Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Joker2184/UpdateHomepage.xaml"
@@ -100,7 +100,7 @@ Public Class PageLaunchRight
Url = "https://files.mcimirror.top/PCL"
Case 11
Log("[Page] 主页预设:杂志主页")
Url = "https://pclhomeplazaoss.lingyunawa.top:26994/d/Homepages/Ext1nguisher/Custom.xaml"
Url = "http://118.195.192.193:26995/d/magazine-homepage-pcl/Custom.xaml"
Case 12
Log("[Page] 主页预设:PCL GitHub 仪表盘")
Url = "https://ddf.pcl-community.org/Custom.xaml"
@@ -484,7 +484,7 @@ Public Class PageLinkMain
If File.Exists(PathEasyTier & "EasyTier.zip") Then
'解压
Dim ExtractPath As String = RequestTaskTempFolder()
ExtractFile(PathEasyTier & "EasyTier.zip", ExtractPath,
ExtractCompressedFile(PathEasyTier & "EasyTier.zip", ExtractPath,
ProgressIncrementHandler:=Sub(Progress) Task.Progress += Progress * 0.05)
Dim ExtractedPath As String = New DirectoryInfo(ExtractPath).EnumerateDirectories.FirstOrDefault?.FullName
Log("[Link] 联机模块解压时的临时路径:" & ExtractedPath)
File diff suppressed because it is too large. Load diff
@@ -144,13 +144,13 @@
If Not Entry.ShowInSearch OrElse (Val(VersionBranchCode) <> 50 AndAlso Not Entry.ShowInSnapshot) Then Continue For
QueryList.Add(New SearchEntry(Of HelpEntry) With {
.Item = Entry,
.SearchSource = New List(Of KeyValuePair(Of String, Double)) From {
New KeyValuePair(Of String, Double)(Entry.Title, 1),
New KeyValuePair(Of String, Double)(Entry.Desc, 0.5),
New KeyValuePair(Of String, Double)(Entry.Search, 1.5)
.SearchSource = New List(Of SearchSource) From {
New SearchSource(Entry.Title, 1),
New SearchSource(Entry.Desc, 0.5),
New SearchSource(Entry.Search, 1.5)
}
})
'New KeyValuePair(Of String, Double)(If(Entry.IsEvent, If(Entry.EventData, ""), Entry.XamlContent), 0.2)
'New SearchSource(If(Entry.IsEvent, If(Entry.EventData, ""), Entry.XamlContent), 0.2)
Next
'进行搜索,构造列表
Dim SearchResult = Search(QueryList, SearchBox.Text, MaxBlurCount:=5, MinBlurSimilarity:=0.08)
@@ -101,12 +101,13 @@
{"LaunchArgumentWindowHeight", New SetupEntry(480)},
{"LaunchArgumentWindowType", New SetupEntry(1)},
{"LaunchArgumentRam", New SetupEntry(False, Source:=SetupSource.Registry)},
{"LaunchAdvanceJvm", New SetupEntry("-XX:+UseG1GC -XX:-UseAdaptiveSizePolicy -XX:-OmitStackTraceInFastThrow -Djdk.lang.Process.allowAmbiguousCommands=true -Dfml.ignoreInvalidMinecraftCertificates=True -Dfml.ignorePatchDiscrepancies=True -Dlog4j2.formatMsgNoLookups=true")},
{"LaunchAdvanceJvm", New SetupEntry("-XX:-OmitStackTraceInFastThrow -Djdk.lang.Process.allowAmbiguousCommands=true -Dfml.ignoreInvalidMinecraftCertificates=True -Dfml.ignorePatchDiscrepancies=True -Dlog4j2.formatMsgNoLookups=true")},
{"LaunchAdvanceGame", New SetupEntry("")},
{"LaunchAdvanceRun", New SetupEntry("")},
{"LaunchAdvanceRunWait", New SetupEntry(True)},
{"LaunchAdvanceDisableJLW", New SetupEntry(False)},
{"LaunchAdvanceGraphicCard", New SetupEntry(True, Source:=SetupSource.Registry)},
{"LaunchAdvanceGC", New SetupEntry(1)},
{"LaunchRamType", New SetupEntry(0)},
{"LaunchRamCustom", New SetupEntry(15)},
{"LinkLastAutoJoinInviteCode", New SetupEntry("", Source:=SetupSource.Registry)},
@@ -180,6 +181,7 @@
{"VersionAdvanceRunWait", New SetupEntry(True, Source:=SetupSource.Instance)},
{"VersionAdvanceDisableJLW", New SetupEntry(False, Source:=SetupSource.Instance)},
{"VersionAdvanceDisableModUpdate", New SetupEntry(False, Source:=SetupSource.Instance)},
{"VersionAdvanceGC", New SetupEntry(0, Source:=SetupSource.Instance)},
{"VersionRamType", New SetupEntry(2, Source:=SetupSource.Instance)},
{"VersionRamCustom", New SetupEntry(15, Source:=SetupSource.Instance)},
{"VersionRamOptimize", New SetupEntry(0, Source:=SetupSource.Instance)},
@@ -529,72 +531,57 @@
FrmMain.ImgBack.Margin = New Thickness(-(Value + 1) / 1.8)
End Sub
Public Sub UiBackgroundSuit(Value As Integer)
If IsNothing(FrmMain.ImgBack.Background) Then Return
Dim Width As Double = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Width
Dim Height As Double = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Height
If FrmMain.ImgBack.Background Is Nothing Then Return
Dim Brush As ImageBrush = CType(FrmMain.ImgBack.Background, ImageBrush)
If Value = 0 Then
'智能:当图片较小时平铺,较大时适应
If Width < FrmMain.PanMain.ActualWidth / 2 AndAlso Height < FrmMain.PanMain.ActualHeight / 2 Then
If Brush.ImageSource.Width < FrmMain.PanMain.ActualWidth / 2 AndAlso Brush.ImageSource.Height < FrmMain.PanMain.ActualHeight / 2 Then
Value = 4 '平铺
Else
Value = 2 '适应
End If
End If
CType(FrmMain.ImgBack.Background, ImageBrush).TileMode = TileMode.None
CType(FrmMain.ImgBack.Background, ImageBrush).Viewport = New Rect(0, 0, 1, 1)
CType(FrmMain.ImgBack.Background, ImageBrush).ViewportUnits = BrushMappingMode.RelativeToBoundingBox
Brush.Stretch = Stretch.UniformToFill
Brush.TileMode = TileMode.None
Brush.Viewport = New Rect(0, 0, 1, 1)
Brush.ViewportUnits = BrushMappingMode.RelativeToBoundingBox
Select Case Value
Case 1 '居中
FrmMain.ImgBack.HorizontalAlignment = HorizontalAlignment.Center
FrmMain.ImgBack.VerticalAlignment = VerticalAlignment.Center
CType(FrmMain.ImgBack.Background, ImageBrush).Stretch = Stretch.None
FrmMain.ImgBack.Width = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Width
FrmMain.ImgBack.Height = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Height
Case 2 '适应
FrmMain.ImgBack.HorizontalAlignment = HorizontalAlignment.Stretch
FrmMain.ImgBack.VerticalAlignment = VerticalAlignment.Stretch
CType(FrmMain.ImgBack.Background, ImageBrush).Stretch = Stretch.UniformToFill
FrmMain.ImgBack.Width = Double.NaN
FrmMain.ImgBack.Height = Double.NaN
Case 3 '拉伸
FrmMain.ImgBack.HorizontalAlignment = HorizontalAlignment.Stretch
FrmMain.ImgBack.VerticalAlignment = VerticalAlignment.Stretch
CType(FrmMain.ImgBack.Background, ImageBrush).Stretch = Stretch.Fill
FrmMain.ImgBack.Width = Double.NaN
FrmMain.ImgBack.Height = Double.NaN
Brush.Stretch = Stretch.Fill
Case 4 '平铺
FrmMain.ImgBack.HorizontalAlignment = HorizontalAlignment.Stretch
FrmMain.ImgBack.VerticalAlignment = VerticalAlignment.Stretch
CType(FrmMain.ImgBack.Background, ImageBrush).Stretch = Stretch.None
CType(FrmMain.ImgBack.Background, ImageBrush).TileMode = TileMode.Tile
CType(FrmMain.ImgBack.Background, ImageBrush).Viewport = New Rect(0, 0, CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Width, CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Height)
CType(FrmMain.ImgBack.Background, ImageBrush).ViewportUnits = BrushMappingMode.Absolute
FrmMain.ImgBack.Width = Double.NaN
FrmMain.ImgBack.Height = Double.NaN
Brush.Stretch = Stretch.None
Brush.TileMode = TileMode.Tile
Brush.Viewport = New Rect(0, 0, Brush.ImageSource.Width, Brush.ImageSource.Height)
Brush.ViewportUnits = BrushMappingMode.Absolute
Case 1 '中
Brush.AlignmentX = AlignmentX.Center
Brush.AlignmentY = AlignmentY.Center
Case 5 '左上
FrmMain.ImgBack.HorizontalAlignment = HorizontalAlignment.Left
FrmMain.ImgBack.VerticalAlignment = VerticalAlignment.Top
CType(FrmMain.ImgBack.Background, ImageBrush).Stretch = Stretch.None
FrmMain.ImgBack.Width = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Width
FrmMain.ImgBack.Height = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Height
Brush.AlignmentX = AlignmentX.Left
Brush.AlignmentY = AlignmentY.Top
Case 6 '右上
FrmMain.ImgBack.HorizontalAlignment = HorizontalAlignment.Right
FrmMain.ImgBack.VerticalAlignment = VerticalAlignment.Top
CType(FrmMain.ImgBack.Background, ImageBrush).Stretch = Stretch.None
FrmMain.ImgBack.Width = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Width
FrmMain.ImgBack.Height = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Height
Brush.AlignmentX = AlignmentX.Right
Brush.AlignmentY = AlignmentY.Top
Case 7 '左下
FrmMain.ImgBack.HorizontalAlignment = HorizontalAlignment.Left
FrmMain.ImgBack.VerticalAlignment = VerticalAlignment.Bottom
CType(FrmMain.ImgBack.Background, ImageBrush).Stretch = Stretch.None
FrmMain.ImgBack.Width = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Width
FrmMain.ImgBack.Height = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Height
Brush.AlignmentX = AlignmentX.Left
Brush.AlignmentY = AlignmentY.Bottom
Case 8 '右下
FrmMain.ImgBack.HorizontalAlignment = HorizontalAlignment.Right
FrmMain.ImgBack.VerticalAlignment = VerticalAlignment.Bottom
CType(FrmMain.ImgBack.Background, ImageBrush).Stretch = Stretch.None
FrmMain.ImgBack.Width = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Width
FrmMain.ImgBack.Height = CType(FrmMain.ImgBack.Background, ImageBrush).ImageSource.Height
Brush.AlignmentX = AlignmentX.Right
Brush.AlignmentY = AlignmentY.Bottom
Case 9 '左
Brush.AlignmentX = AlignmentX.Left
Brush.AlignmentY = AlignmentY.Center
Case 10 '右
Brush.AlignmentX = AlignmentX.Right
Brush.AlignmentY = AlignmentY.Center
Case 11 '上
Brush.AlignmentX = AlignmentX.Center
Brush.AlignmentY = AlignmentY.Top
Case 12 '下
Brush.AlignmentX = AlignmentX.Center
Brush.AlignmentY = AlignmentY.Bottom
End Select
End Sub
@@ -116,7 +116,7 @@
</Grid>
</StackPanel>
</local:MyCard>
<local:MyCard Margin="0,0,0,15" Title="游戏内存">
<local:MyCard Margin="0,0,0,15" Title="内存分配">
<StackPanel Margin="25,40,25,15">
<local:MyHint x:Name="LabRamWarn" Visibility="Visible" Text="32 位 Java 只能分配最多 1 GB 的内存。强烈建议换用 64 位的 Java!" Theme="Yellow" Margin="0,0,0,12" />
<Grid Margin="0,0,0,3">
@@ -231,6 +231,8 @@
<RowDefinition Height="9" />
<RowDefinition Height="28" />
<RowDefinition Height="Auto" />
<RowDefinition Height="9" />
<RowDefinition Height="28" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
@@ -249,13 +251,24 @@
ToolTip="在 MC 启动前执行特定命令或程序,语法与 Windows 的命令提示符一致。&#xa;&#xa;你可以用替换标记来动态改变其内容或实现相对路径,在 更多 → 帮助 中搜索 “替换标记” 以了解用法。例如:&#xa; · &quot;{verpath}test.exe&quot; :运行版本文件夹下的 test.exe 程序&#xa; · &quot;{java}java.exe&quot; -jar &quot;{verpath}test.jar&quot; :用 Java 运行版本文件夹下的 test.jar&#xa; · notepad &quot;{verindie}options.txt&quot; :使用记事本打开该版本的设置文件&#xa;&#xa;涉及路径的操作最好都打上双引号,以避免路径中的空格导致运行失败。&#xa;执行命令时,命令行所在的目录是当前的 .minecraft 文件夹。" />
<local:MyCheckBox Grid.Row="5" Grid.Column="1" x:Name="CheckAdvanceRunWait" Tag="LaunchAdvanceRunWait" Visibility="Collapsed"
Text="等待命令执行完成后再继续启动" Margin="0,8,0,7" />
<StackPanel Margin="0,12,0,4" Grid.Row="6" Grid.ColumnSpan="2" HorizontalAlignment="Left">
<TextBlock VerticalAlignment="Center" Grid.Row="7" HorizontalAlignment="Left" Text="内存管理" Margin="0,0,25,0" />
<local:MyComboBox x:Name="ComboAdvanceGC" Grid.Row="7" Grid.ColumnSpan="2" Tag="LaunchAdvanceGC" Grid.Column="1">
<local:MyComboBoxItem Content="尽量使用 ZGC(激进)"
ToolTipService.BetweenShowDelay="0" ToolTip="在 Java 21+ 使用分代 ZGC,20~15 使用非分代的 ZGC,14- 使用 G1GC。&#xa;在 Java 25+ 额外添加 -XX:+UseCompactObjectHeaders。&#xa;&#xa;覆盖 “Java 虚拟机参数” 设置中的 GC 相关参数。" />
<local:MyComboBoxItem Content="尽量使用分代 ZGC(平衡)" IsSelected="True"
ToolTipService.BetweenShowDelay="0" ToolTip="在 Java 21+ 使用分代 ZGC,20- 使用 G1GC。&#xa;在 Java 25+ 额外添加 -XX:+UseCompactObjectHeaders。&#xa;&#xa;覆盖 “Java 虚拟机参数” 设置中的 GC 相关参数。" />
<local:MyComboBoxItem Content="仅 G1GC"
ToolTipService.BetweenShowDelay="0" ToolTip="固定使用以下参数:&#xa;-XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M&#xa;&#xa;覆盖 “Java 虚拟机参数” 设置中的 GC 相关参数。" />
<local:MyComboBoxItem Content="不指定(可自定义)"
ToolTipService.BetweenShowDelay="0" ToolTip="不自动添加 GC 参数。&#xa;&#xa;仅当选择此项时,你可以在 “Java 虚拟机参数” 设置中手动添加 GC 参数。" />
</local:MyComboBox>
<StackPanel Margin="0,12,0,4" Grid.Row="8" Grid.ColumnSpan="2" HorizontalAlignment="Left">
<local:MyCheckBox Height="28" Text="禁用 Java Launch Wrapper" x:Name="CheckAdvanceDisableJLW" Tag="LaunchAdvanceDisableJLW"
ToolTipService.Placement="Right" ToolTip="PCL 会使用 Java Launch Wrapper 来修复老版本 Java 在中文路径下无法启动的问题。&#xa;如果这被反作弊拦截,或遇到其他问题,也可以将其关闭。" />
<local:MyCheckBox Height="28" Text="使用高性能显卡" x:Name="CheckAdvanceGraphicCard" Tag="LaunchAdvanceGraphicCard"
ToolTipService.Placement="Right" ToolTip="自动在 Windows 设置 → 系统 → 屏幕 → 显示卡 中将 PCL 和 Java 改为使用高性能显卡。&#xa;如果同时有集成显卡和独立显卡,这能确保 MC 使用独立显卡进行渲染,以大幅提高帧率。" />
</StackPanel>
<local:MyHint Text="版本独立设置中还有更多高级选项可供调整。" Margin="0,5,0,10" Theme="Blue" Grid.Row="7" Grid.ColumnSpan="2"
<local:MyHint Text="版本独立设置中还有更多高级选项可供调整。" Margin="0,5,0,10" Theme="Blue" Grid.Row="9" Grid.ColumnSpan="2"
CanClose="True" RelativeSetup="HintMoreAdvancedSetup" />
</Grid>
</local:MyCard>
@@ -54,6 +54,7 @@
TextAdvanceJvm.Text = Setup.Get("LaunchAdvanceJvm")
TextAdvanceGame.Text = Setup.Get("LaunchAdvanceGame")
TextAdvanceRun.Text = Setup.Get("LaunchAdvanceRun")
ComboAdvanceGC.SelectedIndex = Setup.Get("LaunchAdvanceGC")
CheckAdvanceRunWait.Checked = Setup.Get("LaunchAdvanceRunWait")
CheckAdvanceDisableJLW.Checked = Setup.Get("LaunchAdvanceDisableJLW")
CheckAdvanceGraphicCard.Checked = Setup.Get("LaunchAdvanceGraphicCard")
@@ -85,6 +86,7 @@
Setup.Reset("LaunchAdvanceJvm")
Setup.Reset("LaunchAdvanceGame")
Setup.Reset("LaunchAdvanceRun")
Setup.Reset("LaunchAdvanceGC")
Setup.Reset("LaunchAdvanceRunWait")
Setup.Reset("LaunchAdvanceDisableJLW")
Setup.Reset("LaunchAdvanceGraphicCard")
@@ -111,7 +113,7 @@
Private Shared Sub SliderChange(sender As MySlider, e As Object) Handles SliderRamCustom.Change
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.Value)
End Sub
Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndieV2.SelectionChanged, ComboArgumentVisibie.SelectionChanged, ComboArgumentWindowType.SelectionChanged, ComboArgumentPriority.SelectionChanged
Private Shared Sub ComboChange(sender As MyComboBox, e As Object) Handles ComboArgumentIndieV2.SelectionChanged, ComboArgumentVisibie.SelectionChanged, ComboArgumentWindowType.SelectionChanged, ComboArgumentPriority.SelectionChanged, ComboAdvanceGC.SelectionChanged
If AniControlEnabled = 0 Then Setup.Set(sender.Tag, sender.SelectedIndex)
End Sub
Private Shared Sub CheckBoxChange(sender As MyCheckBox, e As Object) Handles CheckAdvanceRunWait.Change, CheckArgumentRam.Change, CheckAdvanceDisableJLW.Change, CheckAdvanceGraphicCard.Change
@@ -94,10 +94,14 @@
<local:MyComboBoxItem Content="适应" ToolTip="拉伸图片并保持长宽比不变,直至填满整个窗口" />
<local:MyComboBoxItem Content="拉伸" ToolTip="拉伸图片使其恰好填满整个窗口,很可能会导致长宽比的改变" />
<local:MyComboBoxItem Content="平铺" />
<local:MyComboBoxItem Content="居于左上" />
<local:MyComboBoxItem Content="居于右上" />
<local:MyComboBoxItem Content="居于左下" />
<local:MyComboBoxItem Content="居于右下" />
<local:MyComboBoxItem Content="左上" />
<local:MyComboBoxItem Content="右上" />
<local:MyComboBoxItem Content="左下" />
<local:MyComboBoxItem Content="右下" />
<local:MyComboBoxItem Content="左" />
<local:MyComboBoxItem Content="右" />
<local:MyComboBoxItem Content="上" />
<local:MyComboBoxItem Content="下" />
</local:MyComboBox>
</Grid>
<Grid x:Name="PanBackgroundOpacity" Height="22" Margin="0,0,0,7" Visibility="Collapsed">
File diff suppressed because it is too large. Load diff
Binary file not shown.