refactor(net): 重构 CE 的部分网络库到 Core (#1387)

* chore(http): 修正以确保正确使用 Core 内的网络调用方式

* feat(http): 替换部分 http 实现

* feat: 完成部分 Stun Client

* perf(binary): 换用 BinaryPrimitives 代替自己瞎搓的工具

* feat: 完成 Telemetry 的网络库迁移

* fix(style): 命名更改未同步至 CE

* fix(telemetry): 未传递启动器的版本信息

* feat(http): 正版登录处换用 core 的 http 实现

---------

Co-authored-by: 任天天 <61044187+ruattd@users.noreply.github.com>
This commit is contained in:
tangge233
2025-09-06 16:55:26 +08:00
committed by GitHub
parent bd950eb2ab
commit 3daeeeefc9
22 changed files with 272 additions and 484 deletions

View File

@@ -13,7 +13,7 @@ public class DiffTest
public async Task TestBsDiff()
{
var diff = new BsDiff();
var res = await diff.Apply(
var res = await diff.ApplyAsync(
[
73, 32, 97, 109, 32, 110, 111, 116, 32, 115, 117, 114, 101, 32, 104, 111, 119, 32, 99, 104, 111, 117,
108,
@@ -48,6 +48,6 @@ public class DiffTest
if (string.IsNullOrEmpty(from) || string.IsNullOrEmpty(outFile) || string.IsNullOrEmpty(diffFile))
return;
var diff = new BsDiff();
File.WriteAllBytes(outFile, await diff.Apply(File.ReadAllBytes(from), File.ReadAllBytes(diffFile)));
File.WriteAllBytes(outFile, await diff.ApplyAsync(File.ReadAllBytes(from), File.ReadAllBytes(diffFile)));
}
}

View File

@@ -17,7 +17,7 @@ namespace PCL.Test
{
// Java 搜索是否稳定
var jas = new JavaManager();
await jas.ScanJava();
await jas.ScanJavaAsync();
var firstScanedCount = jas.JavaList.Count;
foreach (var ja in jas.JavaList)
{
@@ -25,11 +25,11 @@ namespace PCL.Test
Assert.IsTrue(ja.Version.Major > 0, "Java version is not valid: " + ja.JavaFolder);
Assert.IsTrue(!string.IsNullOrWhiteSpace(ja.JavaFolder));
}
await jas.ScanJava();
await jas.ScanJavaAsync();
var secondScanedCount = jas.JavaList.Count;
Assert.IsTrue(firstScanedCount == secondScanedCount);
// Java 搜索是否能够正确选择
Assert.IsTrue(jas.JavaList.Count == 0 || (jas.JavaList.Count > 0 && (await jas.SelectSuitableJava(new Version(1, 8, 0), new Version(30, 0, 0))).Count > 0));
Assert.IsTrue(jas.JavaList.Count == 0 || (jas.JavaList.Count > 0 && (await jas.SelectSuitableJavaAsync(new Version(1, 8, 0), new Version(30, 0, 0))).Count > 0));
// Java 是否有重复
Assert.IsFalse(jas.JavaList.GroupBy(x => x.JavawExePath).Any(x => x.Count() > 1));
}

View File

@@ -22,16 +22,44 @@
<OutputPath>bin\</OutputPath>
<PlatformTarget>$(Platform)</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Beta' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'CI' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="fNbt" Version="1.0.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="Microsoft.TestPlatform.AdapterUtilities" Version="17.14.1" />
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" Version="17.14.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.10.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.10.1" />
<PackageReference Include="MSTest.TestAdapter" Version="3.10.3" />
<PackageReference Include="MSTest.TestFramework" Version="3.10.3" />
<PackageReference Include="System.Text.Json" Version="9.0.8" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PCL.Core\PCL.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Net\**" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Remove="Net\**" />
</ItemGroup>
<ItemGroup>
<Page Remove="Net\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Net\**" />
</ItemGroup>
</Project>

View File

@@ -26,19 +26,19 @@ public class WebServerTest
server.Route("/test", (path, _) => RoutedResponse.Text(path));
Console.WriteLine("Test(/test): 200 OK (path relative to /test)");
await server.StartResponseOnce();
await server.StartResponseOnceAsync();
dynamic obj = new { a = 123, b = new { c = this, d = "text" } };
server.Route("/json", () => RoutedResponse.Json(obj));
Console.WriteLine("Test(/json): 200 OK (request JSON)");
await server.StartResponseOnce();
await server.StartResponseOnceAsync();
Console.WriteLine("Test(/any/path): 404 Not Found");
await server.StartResponseOnce();
await server.StartResponseOnceAsync();
server.Route("/", () => RoutedResponse.NoContent);
Console.WriteLine("Test(/): 204 No Content");
await server.StartResponseOnce();
await server.StartResponseOnceAsync();
server.Dispose();
Console.WriteLine("Test complete.");

View File

@@ -19,6 +19,8 @@ Public Class Application
#End If
Public Sub New()
Basics.VersionName = VersionBaseName
Basics.VersionNumber = VersionCode
'注册生命周期事件
Lifecycle.When(LifecycleState.Loaded, AddressOf Application_Startup)
End Sub

View File

@@ -1,4 +1,5 @@
Imports PCL.Core.Utils
Imports PCL.Core.Net
Imports PCL.Core.Utils
Public Class MyImage
Inherits Image
@@ -133,7 +134,7 @@ RetryStart:
Directory.CreateDirectory(GetPathFromFullPath(TempPath)) '重新实现下载,以避免携带 Header#5072
Using request As New Net.Http.HttpRequestMessage(Http.HttpMethod.Get, Url)
Using fs As New FileStream(TempDownloadingPath, FileMode.Create)
Using response = MyHttpClient.SendAsync(request).Result
Using response = NetworkService.GetClient().SendAsync(request).Result
response.EnsureSuccessStatusCode()
Dim res = response.Content.ReadAsByteArrayAsync().Result
fs.Write(res, 0, res.Length)

View File

@@ -217,8 +217,12 @@ Public Class FormMain
'遥测提示
If Setup.IsUnset("SystemTelemetry") Then
Select Case MyMsgBox("这是一项与 Steam 硬件调查类似的计划,参与调查可以帮助我们更好的进行规划和开发,且我们会不定期发布该调查的统计结果。" & vbCrLf &
"如果选择参与调查,我们将会收集以下信息:" & vbCrLf &
"启动器版本信息与识别码,使用的 Windows 系统版本与架构已安装的物理内存大小NAT 与 IPv6 支持情况,是否使用过官方版 PCL、HMCL 或 BakaXL" & vbCrLf & vbCrLf &
"如果选择参与调查,我们将会收集以下信息:" & vbCrLf & vbCrLf &
"- 启动器版本信息与识别码" & vbCrLf &
"- Windows 系统版本与架构" & vbCrLf &
"- 已安装的物理内存大小" & vbCrLf &
"- NAT 与 IPv6 支持情况" & vbCrLf &
"- 是否使用过官方版 PCL、HMCL 或 BakaXL" & vbCrLf & vbCrLf &
"这些数据均不与你关联,我们也绝不会向第三方出售数据。" & vbCrLf &
"如果不想参与该调查,可以选择拒绝,不会影响其他功能使用。" & vbCrLf &
"你可以随时在启动器设置中调整这项设置。", "参与 PCL CE 软硬件调查", "同意", "拒绝")
@@ -227,12 +231,9 @@ Public Class FormMain
Case 2
Setup.Set("SystemTelemetry", False)
End Select
ElseIf Setup.Get("SystemTelemetry") Then
RunInNewThread(Sub() SendTelemetry())
End If
'启动加载器池
Try
' InitJava() ignore as JavaSerivce will InitJava automatically
Thread.Sleep(100)
DlClientListMojangLoader.Start(1) 'PCL 会同时根据这里的加载结果决定是否使用官方源进行下载
RunCountSub()

View File

@@ -4,8 +4,6 @@ Imports System.Runtime.InteropServices
Imports System.Threading.Tasks
Imports System
Imports System.IO.Compression
Imports CacheCow.Client
Imports CacheCow.Common
Imports PCL.Core.Net
Imports PCL.Core.Utils
Imports PCL.Core.Utils.Hash
@@ -13,114 +11,6 @@ Imports PCL.Core.Utils.Hash
Public Module ModNet
Public Const NetDownloadEnd As String = ".PCLDownloading"
Public ReadOnly MyHttpClient As New HttpClient(New HttpClientHandler() With {
.Proxy = HttpProxyManager.Instance,
.MaxConnectionsPerServer = 256,
.SslProtocols = System.Security.Authentication.SslProtocols.None,
.AutomaticDecompression = DecompressionMethods.All,
.AllowAutoRedirect = True,
.UseCookies = False
})
Public ReadOnly MyHttpCacheClient As HttpClient = ClientExtensions.CreateClient(New NetCacheStorage(IO.Path.Combine(PathTemp, "Cache", "Net")), New HttpClientHandler() With {
.Proxy = HttpProxyManager.Instance,
.SslProtocols = System.Security.Authentication.SslProtocols.None,
.AutomaticDecompression = DecompressionMethods.All,
.AllowAutoRedirect = True,
.UseCookies = False
})
Private Class NetCacheStorage
Implements CacheCow.Common.ICacheStore
Implements IDisposable
Private disposedValue As Boolean
Private _storagePath As String
Private _contentSerializer As New MessageContentHttpMessageSerializer()
Sub New(storagePath As String)
_storagePath = storagePath
If Not Directory.Exists(storagePath) Then Directory.CreateDirectory(storagePath)
End Sub
Public Async Function GetValueAsync(key As CacheKey) As Task(Of HttpResponseMessage) Implements ICacheStore.GetValueAsync
Try
Dim cacheFile As New FileInfo(IO.Path.Combine(_storagePath, GetCacheNameByKey(key)))
If Not cacheFile.Exists Then Return Nothing
Dim ms As New MemoryStream()
Using fs = cacheFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read)
Using decompress As New DeflateStream(fs, CompressionMode.Decompress)
Await decompress.CopyToAsync(ms)
End Using
End Using
ms.Seek(0, SeekOrigin.Begin)
Return Await _contentSerializer.DeserializeToResponseAsync(ms)
Catch ex As Exception
Log(ex, $"[Net] 获取缓存资源({key.ResourceUri} : {key.HashBase64})出现异常")
End Try
Return Nothing
End Function
Public Async Function AddOrUpdateAsync(key As CacheKey, response As HttpResponseMessage) As Task Implements ICacheStore.AddOrUpdateAsync
Try
Dim cacheFile As New FileInfo(IO.Path.Combine(_storagePath, GetCacheNameByKey(key)))
Using fs = cacheFile.Open(FileMode.Create, FileAccess.Write, FileShare.None)
fs.SetLength(0) '先清空之前的内容
Using compress As New DeflateStream(fs, CompressionMode.Compress)
Await _contentSerializer.SerializeAsync(response, compress)
End Using
End Using
Catch ex As Exception
Log(ex, $"[Net] 更新缓存资源({key.ResourceUri} : {key.HashBase64})出现异常")
End Try
End Function
Public Async Function TryRemoveAsync(key As CacheKey) As Task(Of Boolean) Implements ICacheStore.TryRemoveAsync
Return Await Task.Run(Function()
Try
Dim cacheFile As New FileInfo(IO.Path.Combine(_storagePath, GetCacheNameByKey(key)))
If Not cacheFile.Exists Then Return True
cacheFile.Delete()
Return True
Catch ex As Exception
Log(ex, $"[Net] 移除缓存资源({key.ResourceUri} : {key.HashBase64})出现异常")
End Try
Return False
End Function)
End Function
Public Async Function ClearAsync() As Task Implements ICacheStore.ClearAsync
Await Task.Run(Sub()
Try
Dim dir As New DirectoryInfo(_storagePath)
Dim cacheFiles = dir.EnumerateFiles()
For Each cacheFile In cacheFiles
cacheFile.Delete()
Next
Catch ex As Exception
Log(ex, $"[Net] 清空缓存资源出现异常")
End Try
End Sub)
End Function
Private Function GetCacheNameByKey(key As CacheKey)
Return SHA512Provider.Instance.ComputeHash($"{key.HashBase64}{New Uri(key.ResourceUri).Host}")
End Function
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
End If
disposedValue = True
End If
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(disposing:=True)
GC.SuppressFinalize(Me)
End Sub
End Class
''' <summary>
''' 测试 Ping。失败则返回 -1。
''' </summary>
@@ -196,73 +86,6 @@ Public Module ModNet
End If
End Sub
''' <summary>
''' 以 HttpClient 获取网页源代码。会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。
''' </summary>
''' <param name="Url">网页的 Url。</param>
''' <param name="Encoding">网页的编码,通常为 UTF-8。</param>
Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Optional Accept As String = "application/json, text/javascript, */*; q=0.01", Optional UseBrowserUserAgent As Boolean = False) As String
Dim RetryCount As Integer = 0
Dim RetryException As Exception = Nothing
Dim StartTime As Long = TimeUtils.GetTimeTick()
While RetryCount <= 3
RetryCount += 1
Try
Select Case RetryCount
Case 0 '正常尝试
Return NetGetCodeByClient(Url, Encoding, 10000, Accept, UseBrowserUserAgent)
Case 1 '慢速重试
Thread.Sleep(500)
Return NetGetCodeByClient(Url, Encoding, 30000, Accept, UseBrowserUserAgent)
Case Else '快速重试
If TimeUtils.GetTimeTick() - StartTime > 5500 Then
'若前两次加载耗费 5 秒以上,才进行重试
Thread.Sleep(500)
Return NetGetCodeByClient(Url, Encoding, 4000, Accept, UseBrowserUserAgent)
Else
Throw RetryException
End If
End Select
Catch ex As Exception
RetryException = ex
End Try
End While
Throw RetryException
End Function
Public Function NetGetCodeByClient(Url As String, Encoding As Encoding, Timeout As Integer, Accept As String, Optional UseBrowserUserAgent As Boolean = False) As String
Try
Url = SecretCdnSign(Url)
Log("[Net] 获取客户端网络结果:" & Url & ",最大超时 " & Timeout)
Using cts As New CancellationTokenSource
cts.CancelAfter(Timeout)
Using request As New HttpRequestMessage(HttpMethod.Get, Url)
SecretHeadersSign(Url, request, UseBrowserUserAgent)
request.Headers.Accept.ParseAdd(Accept)
request.Headers.AcceptLanguage.ParseAdd("en-US,en;q=0.5")
request.Headers.Add("X-Requested-With", "XMLHttpRequest")
Using response = MyHttpCacheClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token).Result
EnsureSuccessStatusCode(response)
Using responseStream As Stream = response.Content.ReadAsStreamAsync().Result
If Encoding Is Nothing Then Encoding = Encoding.UTF8
'读取流并转换为字符串
Using reader As New StreamReader(responseStream, Encoding)
Dim content As String = reader.ReadToEnd()
If String.IsNullOrEmpty(content) Then Throw New WebException("获取结果失败,内容为空(" & Url & "")
Return content
End Using
End Using
End Using
End Using
End Using
Catch ex As TaskCanceledException
Throw New TimeoutException("连接服务器超时(" & Url & "", ex)
Catch ex As HttpRequestFailedException
Throw New HttpWebException("获取结果失败," & ex.Message & "" & Url & "", ex)
Catch ex As Exception
Throw New WebException("获取结果失败," & ex.Message & "" & Url & "", ex)
End Try
End Function
''' <summary>
''' 以 WebRequest 获取网页源代码或 Json。会进行至多 45 秒 3 次的尝试,允许最长 30s 的超时。
''' </summary>
@@ -313,7 +136,7 @@ Public Module ModNet
Using request As New HttpRequestMessage(HttpMethod.Get, Url)
request.Headers.Accept.ParseAdd(Accept)
SecretHeadersSign(Url, request, UseBrowserUserAgent)
Using response = MyHttpCacheClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token).Result
Using response = NetworkService.GetClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token).Result
EnsureSuccessStatusCode(response)
If Encode Is Nothing Then Encode = Encoding.UTF8
Using responseStream As Stream = response.Content.ReadAsStreamAsync().Result
@@ -379,7 +202,7 @@ Public Module ModNet
If File.Exists(LocalFile) Then File.Delete(LocalFile)
Using request As New HttpRequestMessage(HttpMethod.Get, Url)
SecretHeadersSign(Url, request, UseBrowserUserAgent)
Using response As HttpResponseMessage = Await MyHttpCacheClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
Using response As HttpResponseMessage = Await NetworkService.GetClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead)
EnsureSuccessStatusCode(response)
Using httpStream As Stream = Await response.Content.ReadAsStreamAsync()
Using fileStream As New FileStream(LocalFile, FileMode.Create)
@@ -530,7 +353,7 @@ Public Module ModNet
request.Headers.Add(Pair.Key, Pair.Value)
Next
End If
Using response = MyHttpCacheClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token).Result
Using response = NetworkService.GetClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token).Result
EnsureSuccessStatusCode(response)
Using responseStream = response.Content.ReadAsStreamAsync().Result
Using reader As New StreamReader(responseStream, Encoding.UTF8)
@@ -1163,7 +986,7 @@ StartThread:
If Not Info.IsFirstThread OrElse Info.DownloadStart <> 0 Then request.Headers.Range = New Headers.RangeHeaderValue(Info.DownloadStart, Nothing)
Using cts As New CancellationTokenSource
cts.CancelAfter(Timeout)
Using response = MyHttpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token).Result
Using response = NetworkService.GetClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token).Result
EnsureSuccessStatusCode(response)
If State = NetState.Error Then GoTo SourceBreak '快速中断
Dim Redirected = response.RequestMessage.RequestUri.OriginalString

View File

@@ -1,4 +1,6 @@
Imports PCL.Core.Utils
Imports PCL.Core.Net
Imports System.Net.Http
Imports PCL.Core.Utils
Public Module ModDownload
@@ -414,7 +416,14 @@ Public Module ModDownload
''' </summary>
Public DlOptiFineListOfficialLoader As New LoaderTask(Of Integer, DlOptiFineListResult)("DlOptiFineList Official", AddressOf DlOptiFineListOfficialMain)
Private Sub DlOptiFineListOfficialMain(Loader As LoaderTask(Of Integer, DlOptiFineListResult))
Dim Result As String = NetGetCodeByClient("https://optifine.net/downloads", Encoding.Default)
Dim Result As String = HttpRequestBuilder.
Create("https://optifine.net/downloads", HttpMethod.Get).
WithHeader("Accept", "application/json, text/javascript, */*; q=0.01").
WithHeader("Accept-Language", "en-US,en;q=0.5").
WithHeader("X-Requested-With", "XMLHttpRequest").
SendAsync(True).
Result.
AsStringContent()
If Result.Length < 200 Then Throw New Exception("获取到的版本列表长度不足(" & Result & "")
Try
'获取所有版本信息

View File

@@ -118,11 +118,11 @@ Public Module ModJava
Javas.CheckJavaAvailability()
Dim reqMin = If(MinVersion, New Version(1, 0, 0))
Dim reqMax = If(MaxVersion, New Version(999, 999, 999))
Dim ret = Javas.SelectSuitableJava(reqMin, reqMax).Result.FirstOrDefault()
Dim ret = Javas.SelectSuitableJavaAsync(reqMin, reqMax).Result.FirstOrDefault()
If ret Is Nothing Then
Log("[Java] 没有找到合适的 Java 开始尝试重新搜索后选择")
Javas.ScanJava().GetAwaiter().GetResult()
ret = Javas.SelectSuitableJava(reqMin, reqMax).Result.FirstOrDefault()
Javas.ScanJavaAsync().GetAwaiter().GetResult()
ret = Javas.SelectSuitableJavaAsync(reqMin, reqMax).Result.FirstOrDefault()
End If
Log($"[Java] 返回自动选择的 Java {If(ret IsNot Nothing, ret.ToString(), "无结果")}")
Return ret
@@ -216,7 +216,7 @@ Public Module ModJava
Log($"[Java] 由于下载未完成,清理未下载完成的 Java 文件:{LastJavaBaseDir}", LogLevel.Debug)
DeleteDirectory(LastJavaBaseDir)
ElseIf NewState = LoadState.Finished Then
Javas.ScanJava().GetAwaiter().GetResult()
Javas.ScanJavaAsync().GetAwaiter().GetResult()
LastJavaBaseDir = Nothing
End If
End Sub

View File

@@ -1,9 +1,12 @@
Imports System.IO.Compression
Imports System.Net.Http
Imports System.Text.Json
Imports System.Text.Json.Nodes
Imports PCL.Core.Minecraft
Imports PCL.Core.Utils
Imports PCL.Core.Utils.OS
Imports PCL.Core.Net
Public Module ModLaunch
@@ -609,8 +612,13 @@ SkipLogin:
'初始请求
Retry:
McLaunchLog("开始正版验证 Step 1/6原始登录")
Dim PrepareJson As JObject = GetJson(NetRequestRetry("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", "POST",
$"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access", "application/x-www-form-urlencoded"))
Dim PrepareJson As JObject
Using response = HttpRequestBuilder.Create("https://login.microsoftonline.com/consumers/oauth2/v2.0/devicecode", HttpMethod.Post).
WithContent(New Http.StringContent($"client_id={OAuthClientId}&tenant=/consumers&scope=XboxLive.signin%20offline_access"), "application/x-www-form-urlencoded").
SendAsync(True).Result
PrepareJson = GetJson(response.AsStringContent())
End Using
McLaunchLog("网页登录地址:" & PrepareJson("verification_uri").ToString)
'弹窗
@@ -634,18 +642,20 @@ Retry:
End If
End Function
''' <summary>
''' 正版验证步骤 1刷新登录从 OAuth Code 或 OAuth RefreshToken 获取 {OAuth AccessToken, OAuth RefreshToken}
''' 正版验证步骤 1刷新登录从 OAuth Code 或 OAuth RefreshToken 获取 {OAuth accessToken, OAuth RefreshToken}
''' </summary>
''' <param name="Code"></param>
''' <returns></returns>
Private Function MsLoginStep1Refresh(Code As String) As String()
McLaunchLog("开始正版验证 Step 1/6刷新登录")
If String.IsNullOrEmpty(Code) Then Throw New ArgumentException("传入的 Code 为空", NameOf(Code))
Dim Result As String = Nothing
Dim Result As String
Try
Result = NetRequestRetry("https://login.live.com/oauth20_token.srf", "POST",
$"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access",
"application/x-www-form-urlencoded", 2)
Using response = HttpRequestBuilder.Create("https://login.live.com/oauth20_token.srf", HttpMethod.Post).
WithContent($"client_id={OAuthClientId}&refresh_token={Uri.EscapeDataString(Code)}&grant_type=refresh_token&scope=XboxLive.signin%20offline_access", "application/x-www-form-urlencoded").
SendAsync(True).Result
Result = response.AsStringContent()
End Using
Catch ex As ThreadInterruptedException
Log(ex, "加载线程已终止")
Catch ex As Exception
@@ -672,31 +682,43 @@ Retry:
Return {AccessToken, RefreshToken}
End Function
Private Class XBLTokenRequestData
Public Class PropertiesData
Public Property AuthMethod As String
Public Property SiteName As String
Public Property RpsTicket As String
End Class
Public Property Properties As PropertiesData
Public Property RelyingParty As String
Public Property TokenType As String
End Class
''' <summary>
''' 正版验证步骤 2从 OAuth AccessToken 获取 XBLToken
''' 正版验证步骤 2从 OAuth accessToken 获取 XBLToken
''' </summary>
''' <param name="AccessToken">OAuth AccessToken</param>
''' <param name="accessToken">OAuth accessToken</param>
''' <returns>XBLToken</returns>
Private Function MsLoginStep2(AccessToken As String) As String
Private Function MsLoginStep2(accessToken As String) As String
ProfileLog("开始正版验证 Step 2/6: 获取 XBLToken")
If String.IsNullOrEmpty(AccessToken) Then Throw New ArgumentException("传入的 AccessToken 为空", NameOf(AccessToken))
Dim Request As String = New JObject(
New JProperty("Properties", New JObject(
New JProperty("AuthMethod", "RPS"),
New JProperty("SiteName", "user.auth.xboxlive.com"),
New JProperty("RpsTicket", If(AccessToken.StartsWith("d="), AccessToken, $"d={AccessToken}"))
)),
New JProperty("RelyingParty", "http://auth.xboxlive.com"),
New JProperty("TokenType", "JWT")
).ToString(Newtonsoft.Json.Formatting.None)
Dim Result As String = Nothing
If String.IsNullOrEmpty(accessToken) Then Throw New ArgumentException("传入的 AccessToken 为空", NameOf(accessToken))
Dim requestData As New XBLTokenRequestData With {
.Properties = New XBLTokenRequestData.PropertiesData With {
.AuthMethod = "RPS",
.SiteName = "user.auth.xboxlive.com",
.RpsTicket = $"d={accessToken}"
},
.RelyingParty = "http://auth.xboxlive.com",
.TokenType = "JWT"
}
Dim Result As String
Try
Result = NetRequestRetry(
"https://user.auth.xboxlive.com/user/authenticate",
"POST",
Request,
"application/json",
False)
Dim contentData = JsonSerializer.Serialize(requestData)
Using response = HttpRequestBuilder.Create("https://user.auth.xboxlive.com/user/authenticate", HttpMethod.Post).
WithContent(contentData, "application/json").
SendAsync(True).Result
Result = response.AsStringContent()
End Using
Catch ex As Exception
ProfileLog("正版验证 Step 2/6 获取 XBLToken 失败:" & ex.ToString())
Dim IsIgnore As Boolean = False
@@ -706,7 +728,6 @@ Retry:
End Sub)
If IsIgnore Then
Return "Ignore"
Exit Function
End If
End Try
@@ -714,6 +735,17 @@ Retry:
Dim XBLToken As String = ResultJson("Token").ToString
Return XBLToken
End Function
Private Class XSTSTokenRequestData
Public Class PropertiesData
Public Property SandboxId As String
Public Property UserTokens As List(Of String)
End Class
Public Property Properties As PropertiesData
Public Property RelyingParty As String
Public Property TokenType As String
End Class
''' <summary>
''' 正版验证步骤 3从 XBLToken 获取 {XSTSToken, UHS}
''' </summary>
@@ -721,23 +753,23 @@ Retry:
Private Function MsLoginStep3(XBLToken As String) As String()
ProfileLog("开始正版验证 Step 3/6: 获取 XSTSToken")
If String.IsNullOrEmpty(XBLToken) Then Throw New ArgumentException("XBLToken 为空,无法获取数据", NameOf(XBLToken))
Dim Request As String = New JObject(
New JProperty("Properties", New JObject(
New JProperty("SandboxId", "RETAIL"),
New JProperty("UserTokens", New JArray(XBLToken))
)),
New JProperty("RelyingParty", "rp://api.minecraftservices.com/"),
New JProperty("TokenType", "JWT")
).ToString(Newtonsoft.Json.Formatting.None)
Dim requestData As New XSTSTokenRequestData With {
.Properties = New XSTSTokenRequestData.PropertiesData With {
.SandboxId = "RETAIL",
.UserTokens = {XBLToken}.ToList()
},
.RelyingParty = "rp://api.minecraftservices.com/",
.TokenType = "JWT"
}
Dim Result As String
Try
Result = NetRequestRetry(
"https://xsts.auth.xboxlive.com/xsts/authorize",
"POST",
Request,
"application/json",
False)
Catch ex As WebException
Dim contentData = JsonSerializer.Serialize(requestData)
Using response = HttpRequestBuilder.Create("https://xsts.auth.xboxlive.com/xsts/authorize", HttpMethod.Post).
WithContent(contentData, "application/json").
SendAsync(True).Result
Result = response.AsStringContent()
End Using
Catch ex As HttpRequestException
'参考 https://github.com/PrismarineJS/prismarine-auth/blob/master/src/common/Constants.js
If ex.Message.Contains("2148916227") Then
MyMsgBox("该账号似乎已被微软封禁,无法登录。", "登录失败", "我知道了", IsWarn:=True)
@@ -783,28 +815,30 @@ Retry:
Return {XSTSToken, UHS}
End Function
''' <summary>
''' 正版验证步骤 4从 {XSTSToken, UHS} 获取 Minecraft AccessToken
''' 正版验证步骤 4从 {XSTSToken, UHS} 获取 Minecraft accessToken
''' </summary>
''' <param name="Tokens">包含 XSTSToken 与 UHS 的字符串组</param>
''' <returns>Minecraft AccessToken</returns>
''' <returns>Minecraft accessToken</returns>
Private Function MsLoginStep4(Tokens As String()) As String
ProfileLog("开始正版验证 Step 4/6: 获取 Minecraft AccessToken")
If Tokens.Length < 2 OrElse String.IsNullOrEmpty(Tokens.ElementAt(0)) OrElse String.IsNullOrEmpty(Tokens.ElementAt(1)) Then Throw New ArgumentException("传入的 XSTSToken 或者 UHS 错误", NameOf(Tokens))
Dim Request As String = New JObject(New JProperty("identityToken", $"XBL3.0 x={Tokens(1)};{Tokens(0)}")).ToString(0)
Dim requestData As New Dictionary(Of String, String) From {
{"identityToken", $"XBL3.0 x={Tokens(1)};{Tokens(0)}"}
}
Dim Result As String
Try
Result = NetRequestRetry(
"https://api.minecraftservices.com/authentication/login_with_xbox",
"POST",
Request,
"application/json",
False)
Catch ex As PCL.ModNet.HttpWebException
Dim contentData = JsonSerializer.Serialize(requestData)
Using response = HttpRequestBuilder.Create("https://api.minecraftservices.com/authentication/login_with_xbox", HttpMethod.Post).
WithContent(contentData, "application/json").
SendAsync(True).Result
Result = response.AsStringContent()
End Using
Catch ex As HttpRequestException
Dim Message As String = ex.Message
If CType(ex.StatusCode, Integer) = 429 Then
If ex.StatusCode.Equals(HttpStatusCode.TooManyRequests) Then
Log(ex, "正版验证 Step 4 汇报 429")
Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
ElseIf ex.StatusCode = HttpStatusCode.NotFound Then
ElseIf ex.StatusCode = HttpStatusCode.Forbidden Then
Log(ex, "正版验证 Step 4 汇报 403")
Throw New Exception("$当前 IP 的登录尝试异常。" & vbCrLf & "如果你使用了 VPN 或加速器,请把它们关掉或更换节点后再试!")
Else
@@ -830,51 +864,48 @@ Retry:
''' <summary>
''' 正版验证步骤 5验证微软账号是否持有 MC这也会刷新 XGP
''' </summary>
''' <param name="AccessToken">Minecraft AccessToken</param>
Private Sub MsLoginStep5(AccessToken As String)
''' <param name="accessToken">Minecraft accessToken</param>
Private Sub MsLoginStep5(accessToken As String)
ProfileLog("开始正版验证 Step 5/6: 验证账户是否持有 MC")
If String.IsNullOrEmpty(AccessToken) Then Throw New ArgumentException("传入的 AccessToken 为空", NameOf(AccessToken))
Dim Result As String = NetRequestRetry(
"https://api.minecraftservices.com/entitlements",
"GET",
Nothing,
"application/json",
False,
New Dictionary(Of String, String) From {{"Authorization", $"Bearer {AccessToken}"}})
If String.IsNullOrEmpty(accessToken) Then Throw New ArgumentException("传入的 AccessToken 为空", NameOf(accessToken))
Dim result As String
Try
Dim ResultJson As JObject = GetJson(Result)
Using response = HttpRequestBuilder.Create("https://api.minecraftservices.com/entitlements", HttpMethod.Get).
WithBearerToken(accessToken).
SendAsync(True).Result
result = response.AsStringContent()
End Using
Dim ResultJson As JObject = GetJson(result)
If Not (ResultJson.ContainsKey("items") AndAlso ResultJson("items").Any(Function(x) x("name")?.ToString() = "product_minecraft" OrElse x("name")?.ToString() = "game_minecraft")) Then
Select Case MyMsgBox($"暂时无法获取到此账户信息,此账户可能没有购买 Minecraft Java Edition 或者账户的 Xbox Game Pass 已过期", "登录失败", "购买 Minecraft", "取消")
Case 1
OpenWebsite("https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj")
End Select
Throw New Exception("$$")
End If
Select Case MyMsgBox($"暂时无法获取到此账户信息,此账户可能没有购买 Minecraft Java Edition 或者账户的 Xbox Game Pass 已过期", "登录失败", "购买 Minecraft", "取消")
Case 1
OpenWebsite("https://www.xbox.com/zh-cn/games/store/minecraft-java-bedrock-edition-for-pc/9nxp44l49shj")
End Select
Throw New Exception("$$")
End If
Catch ex As Exception
Log(ex, "正版验证 Step 5 异常:" & Result)
Log(ex, "正版验证 Step 5 异常:" & result)
Throw
End Try
End Sub
''' <summary>
''' 正版验证步骤 6从 Minecraft AccessToken 获取 {UUID, UserName, ProfileJson}
''' 正版验证步骤 6从 Minecraft accessToken 获取 {UUID, UserName, ProfileJson}
''' </summary>
''' <param name="AccessToken">Minecraft AccessToken</param>
''' <param name="AccessToken">Minecraft accessToken</param>
''' <returns>包含 UUID, UserName 和 ProfileJson 的字符串组</returns>
Private Function MsLoginStep6(AccessToken As String) As String()
ProfileLog("开始正版验证 Step 6/6: 获取玩家 ID 与 UUID 等相关信息")
If String.IsNullOrEmpty(AccessToken) Then Throw New ArgumentException("传入的 AccessToken 为空", NameOf(AccessToken))
Dim Result As String
Try
Result = NetRequestRetry(
"https://api.minecraftservices.com/minecraft/profile",
"GET",
"",
"application/json",
False,
New Dictionary(Of String, String) From {{"Authorization", $"Bearer {AccessToken}"}})
Catch ex As PCL.ModNet.HttpWebException
Using response = HttpRequestBuilder.Create("https://api.minecraftservices.com/minecraft/profile", HttpMethod.Get).
WithBearerToken(AccessToken).
SendAsync(True).Result
Result = response.AsStringContent()
End Using
Catch ex As HttpRequestException
Dim Message As String = ex.Message
If CType(ex.StatusCode, Integer) = 429 Then '微软!我的 TooManyRequests 枚举呢?
If ex.StatusCode.Equals(HttpStatusCode.TooManyRequests) Then
Log(ex, "正版验证 Step 6 汇报 429")
Throw New Exception("$登录尝试太过频繁,请等待几分钟后再试!")
ElseIf ex.StatusCode = HttpStatusCode.NotFound Then

View File

@@ -330,72 +330,6 @@ Public Module ModLink
Dim SupportIPv6 As Boolean = Ips.Cast(Of Object)().Any(Function(Ip) Ip.contains(":"))
Return {NatType, SupportIPv6}
End Function
''' <summary>
''' 进行网络测试,包括 IPv4 NAT 类型测试和 IPv6 支持情况测试
''' </summary>
''' <returns>NAT 类型 + IPv6 支持与否</returns>
Public Function NetTest() As String()
'申请通过防火墙以准确测试 NAT 类型
Dim RetryTime As Integer = 0
Try
PortRetry:
Dim TestTcpListener = TcpListener.Create(RandomUtils.NextInt(20000, 65000))
TestTcpListener.Start()
Thread.Sleep(200)
TestTcpListener.Stop()
Catch ex As Exception
Log(ex, "[Link] 请求防火墙通过失败")
If RetryTime >= 3 Then
Log("[Link] 请求防火墙通过失败次数已达 3 次,不再重试")
Exit Try
End If
GoTo PortRetry
End Try
'IPv4 NAT 测试
Dim NATType As String
Dim STUNServerDomain As String = "stun.miwifi.com" '指定 STUN 服务器
Log("[STUN] 指定的 STUN 服务器: " + STUNServerDomain)
Try
Dim STUNServerIP As String = Dns.GetHostAddresses(STUNServerDomain)(0).ToString() '解析 STUN 服务器 IP
Log("[STUN] 解析目标 STUN 服务器 IP: " + STUNServerIP)
Dim STUNServerEndPoint As IPEndPoint = New IPEndPoint(IPAddress.Parse(STUNServerIP), 3478) '设置 IPEndPoint
STUNClient.ReceiveTimeout = 500 '设置超时
Log("[STUN] 开始进行 NAT 测试")
Dim STUNTestResult = STUNClient.Query(STUNServerEndPoint, STUNQueryType.ExactNAT, True) '进行 STUN 测试
NATType = STUNTestResult.NATType.ToString()
Log("[STUN] 本地 NAT 类型: " + NATType)
Catch ex As Exception
Log(ex, "[STUN] 进行 NAT 测试失败", LogLevel.Normal)
NATType = "TestFailed"
End Try
'IPv6
Dim IPv6Status As String = "Unsupported"
Try
For Each ip In NatDiscovery.GetIPAddresses()
If ip.AddressFamily() = AddressFamily.InterNetworkV6 Then 'IPv6
If ip.IsIPv6LinkLocal() OrElse ip.IsIPv6SiteLocal() OrElse ip.IsIPv6Teredo() OrElse ip.IsIPv4MappedToIPv6() Then
Continue For
ElseIf ip.IsPublic() Then
Log("[IP] 检测到 IPv6 公网地址")
IPv6Status = "Public"
Exit For
ElseIf ip.IsPrivate() AndAlso Not IPv6Status = "Supported" Then
Log("[IP] 检测到 IPv6 支持")
IPv6Status = "Supported"
Continue For
End If
End If
Next
Catch ex As Exception
Log(ex, "[IP] 进行 IPv6 测试失败", LogLevel.Normal)
IPv6Status = "Unknown"
End Try
Return {NATType, IPv6Status}
End Function
#End Region
End Module

View File

@@ -9,6 +9,7 @@ Imports PCL.Core.Utils
Imports PCL.Core.Utils.Exts
Imports PCL.Core.Utils.OS
Imports PCL.Core.Utils.Secret
Imports PCL.Core.Net
Friend Module ModSecret
@@ -141,9 +142,6 @@ PCL-Community 及其成员与龙腾猫跃无从属关系,且均不会为您的
#End Region
#Region "网络鉴权"
Friend Function SecretCdnSign(UrlWithMark As String)
If Not UrlWithMark.EndsWithF("{CDN}") Then Return UrlWithMark
Return UrlWithMark.Replace("{CDN}", "").Replace(" ", "%20")
@@ -163,7 +161,6 @@ PCL-Community 及其成员与龙腾猫跃无从属关系,且均不会为您的
Client.Headers.Add("User-Agent", userAgent)
Client.Headers.Add("Referer", "http://" & VersionCode & ".ce.open.pcl2.server/")
If Url.Contains("pcl2ce.pysio.online/post") AndAlso Not String.IsNullOrEmpty(TelemetryKey) Then Client.Headers.Add("Authorization", TelemetryKey)
End Sub
#End Region
@@ -938,42 +935,6 @@ PCL-Community 及其成员与龙腾猫跃无从属关系,且均不会为您的
#End Region
#Region "遥测"
''' <summary>
''' 发送遥测数据,需要在非 UI 线程运行
''' </summary>
Public Sub SendTelemetry()
If String.IsNullOrWhiteSpace(TelemetryKey) Then Exit Sub
Dim NetResult = ModLink.NetTest()
Dim Data = New JObject From {
{"Tag", "Telemetry"},
{"Id", UniqueAddress},
{"OS", Environment.OSVersion.Version.Build},
{"Is64Bit", Not Is32BitSystem},
{"IsARM64", IsArm64System},
{"Launcher", VersionCode},
{"LauncherBranch", If(IsUpdBetaChannel, "Fast Ring", "Slow Ring")},
{"UsedOfficialPCL", ReadReg("SystemEula", Nothing, "PCL") IsNot Nothing},
{"UsedHMCL", Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\.hmcl")},
{"UsedBakaXL", Directory.Exists(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\BakaXL")},
{"Memory", SystemMemorySize},
{"NatType", NetResult(0)},
{"IPv6Status", NetResult(1)}
}
Dim SendData = New JObject From {{"data", Data}}
Try
Dim Result As String = NetRequestRetry("https://pcl2ce.pysio.online/post", "POST", SendData.ToString(), "application/json")
If Result.Contains("数据已成功保存") Then
Log("[Telemetry] 软硬件调查数据已发送")
Else
Log("[Telemetry] 软硬件调查数据发送失败,原始返回内容: " + Result)
End If
Catch ex As Exception
Log(ex, "[Telemetry] 软硬件调查数据发送失败", LogLevel.Normal)
End Try
End Sub
#End Region
#Region "系统信息"
Friend CPUName As String = Nothing
''' <summary>

View File

@@ -23,7 +23,7 @@ Public Module ModWebServer
Async Function() As Task
Log($"[WebServer] 服务端 '{name}' 已启动")
Try
Await server.StartResponse()
Await server.StartResponseAsync()
Catch ex As Exception
Log(ex, $"[WebServer] 服务端 '{name}' 运行出错")
End Try
@@ -207,7 +207,7 @@ Public Module ModWebServer
Dim code = parameters("code")
Dim resultEx As Exception = Nothing
Try
NatayarkProfileManager.GetNaidDataSync(code).Wait()
NatayarkProfileManager.GetNaidDataAsync(code).Wait()
Catch ex As AggregateException
resultEx = ex.InnerExceptions(0)
End Try

View File

@@ -137,7 +137,7 @@ Public Class UpdatesMinioModel '社区自己的更新系统格式
loaders.Add(New LoaderTask(Of String, Integer)("应用文件", Sub()
If patchUpdate Then
Dim diff As New BsDiff()
Dim newFile = diff.Apply(ReadFileBytes(ExePathWithName), ReadFileBytes(tempPath)).GetAwaiter().GetResult()
Dim newFile = diff.ApplyAsync(ReadFileBytes(ExePathWithName), ReadFileBytes(tempPath)).GetAwaiter().GetResult()
WriteFile(output, newFile)
Else
Using fs As New FileStream(tempPath, FileMode.Open, FileAccess.Read, FileShare.Read)

View File

@@ -1,5 +1,7 @@
Imports System.IO.Compression
Imports PCL.Core.Minecraft
Imports PCL.Core.Net
Imports System.Net.Http
Imports PCL.Core.UI
Public Module ModDownloadLib
@@ -606,7 +608,14 @@ pause"
'官方源
Dim PageData As String
Try
PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True)
PageData = HttpRequestBuilder.
Create("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, HttpMethod.Get).
WithHeader("Accept", "text/html").
WithHeader("Accept-Language", "en-US,en;q=0.5").
WithHeader("X-Requested-With", "XMLHttpRequest").
SendAsync(True).
Result.
AsStringContent()
Task.Progress = 0.8
Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)
@@ -769,7 +778,14 @@ Retry:
'官方源
Dim PageData As String
Try
PageData = NetGetCodeByClient("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, New UTF8Encoding(False), 15000, "text/html", True)
PageData = HttpRequestBuilder.
Create("https://optifine.net/adloadx?f=" & DownloadInfo.NameFile, HttpMethod.Get).
WithHeader("Accept", "text/html").
WithHeader("Accept-Language", "en-US,en;q=0.5").
WithHeader("X-Requested-With", "XMLHttpRequest").
SendAsync(True).
Result.
AsStringContent()
Task.Progress = 0.8
Sources.Add("https://optifine.net/" & RegexSearch(PageData, "downloadx\?f=[^""']+")(0))
Log("[Download] OptiFine " & DownloadInfo.NameDisplay & " 官方下载地址:" & Sources.Last)

View File

@@ -79,7 +79,7 @@ Public Class PageLoginAuth
Dim serverUri As String = Nothing
Dim serverName As String = Nothing
Try
serverUri = Await ApiLocation.TryRequest(serverUriInput)
serverUri = Await ApiLocation.TryRequestAsync(serverUriInput)
Dim response = Await HttpRequestBuilder.Create(serverUri, HttpMethod.Get).SendAsync()
Dim responseText As String = Await response.AsStringAsync()
serverName = Await Task.Run(Function() JObject.Parse(responseText)("meta")("serverName").ToString())

View File

@@ -304,7 +304,7 @@ Public Class PageLinkLobby
Log("[Link] 启动 EasyTier 轮询")
IsWatcherStarted = True
Dim retryCount = 0
While ETInfoProvider.CheckETStatus().GetAwaiter().GetResult() = 0 AndAlso retryCount <= 15
While ETInfoProvider.CheckETStatusAsync().GetAwaiter().GetResult() = 0 AndAlso retryCount <= 15
retryCount += GetETInfo()
If RequiresLogin AndAlso String.IsNullOrWhiteSpace(NaidProfile.AccessToken) Then
Hint("请先登录 Natayark ID 再使用大厅!", HintType.Critical)

View File

@@ -227,122 +227,101 @@
EventType="打开网页" EventData="https://iconmonstr.com/" />
<local:MyButton Grid.Row="9" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://iconmonstr.com/license/" />
<TextBlock Grid.Row="10" Grid.Column="0" Text="SixLabors.ImageSharp" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Six Labors&#xa;Licensed under the Apache License 2.0." TextWrapping="Wrap" />
<local:MyButton Grid.Row="11" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/SixLabors/ImageSharp" />
<local:MyButton Grid.Row="11" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/SixLabors/ImageSharp/blob/main/LICENSE" />
<TextBlock Grid.Row="12" Grid.Column="0" Text="Iconfont" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © ALIMAMA MUX, powered by alimama THX." TextWrapping="Wrap" />
<local:MyButton Grid.Row="13" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="10" Grid.Column="0" Text="Iconfont" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="10" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © ALIMAMA MUX, powered by alimama THX." TextWrapping="Wrap" />
<local:MyButton Grid.Row="11" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://www.iconfont.cn/" />
<TextBlock Grid.Row="14" Grid.Column="0" Text="Fody" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Simon Cropp" TextWrapping="Wrap" />
<local:MyButton Grid.Row="15" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="12" Grid.Column="0" Text="Fody" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="12" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Simon Cropp" TextWrapping="Wrap" />
<local:MyButton Grid.Row="13" Grid.Column="2" MinWidth="140" Text="查看原网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/Fody/Fody" />
<local:MyButton Grid.Row="15" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="13" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/Fody/Fody/blob/master/License.txt" />
<TextBlock Grid.Row="16" Grid.Column="0" Text="Makaretu.Nat" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2018 Richard Schneider" TextWrapping="Wrap" />
<local:MyButton Grid.Row="17" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/richardschneider/net-nat" />
<local:MyButton Grid.Row="17" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/richardschneider/net-nat/blob/master/LICENSE" />
<TextBlock Grid.Row="14" Grid.Column="0" Text="Stun.Net" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="14" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Bruce Wayne" TextWrapping="Wrap" />
<local:MyButton Grid.Row="15" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/HMBSbige/NatTypeTester" />
<local:MyButton Grid.Row="15" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/HMBSbige/NatTypeTester/blob/master/LICENSE" />
<TextBlock Grid.Row="18" Grid.Column="0" Text="IPNetwork" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="18" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015, lduchosal" TextWrapping="Wrap" />
<local:MyButton Grid.Row="19" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="16" Grid.Column="0" Text="IPNetwork" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="16" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2015, lduchosal" TextWrapping="Wrap" />
<local:MyButton Grid.Row="17" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/lduchosal/ipnetwork" />
<local:MyButton Grid.Row="19" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="17" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/lduchosal/ipnetwork/blob/master/LICENSE" />
<TextBlock Grid.Row="20" Grid.Column="0" Text="STUN" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2019 Moien007" TextWrapping="Wrap" />
<local:MyButton Grid.Row="21" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/moien007/stun" />
<local:MyButton Grid.Row="21" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/moien007/STUN/blob/master/LICENSE" />
<TextBlock Grid.Row="22" Grid.Column="0" Text="Open.NAT" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="22" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2010-2011 Erik Davidson, 2012+ Matvei Stefarov&#xa;License under 3-Clause BSD" TextWrapping="Wrap" />
<local:MyButton Grid.Row="23" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/mstefarov/fNbt" />
<local:MyButton Grid.Row="23" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://licenses.nuget.org/BSD-3-Clause" />
<TextBlock Grid.Row="24" Grid.Column="0" Text="TagLibSharp" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="24" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2006-2007 Brian Nickel.&#xa;Copyright © 2009-2020 Other contributors" TextWrapping="Wrap" />
<local:MyButton Grid.Row="25" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="18" Grid.Column="0" Text="TagLibSharp" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="18" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2006-2007 Brian Nickel.&#xa;Copyright © 2009-2020 Other contributors" TextWrapping="Wrap" />
<local:MyButton Grid.Row="19" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/mono/taglib-sharp" />
<local:MyButton Grid.Row="25" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="19" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/mono/taglib-sharp/blob/main/COPYING" />
<TextBlock Grid.Row="26" Grid.Column="0" Text="Microsoft.Identity.Client" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="26" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Microsoft Corporation" TextWrapping="Wrap" />
<local:MyButton Grid.Row="27" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="20" Grid.Column="0" Text="Microsoft.Identity.Client" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="20" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Microsoft Corporation" TextWrapping="Wrap" />
<local:MyButton Grid.Row="21" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://go.microsoft.com/fwlink/?linkid=844761" />
<local:MyButton Grid.Row="27" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="21" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://licenses.nuget.org/MIT" />
<TextBlock Grid.Row="28" Grid.Column="0" Text="Microsoft.Identity.Client.Broker" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="28" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Microsoft Corporation" TextWrapping="Wrap" />
<local:MyButton Grid.Row="29" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="22" Grid.Column="0" Text="Microsoft.Identity.Client.Broker" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="22" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Microsoft Corporation" TextWrapping="Wrap" />
<local:MyButton Grid.Row="23" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://go.microsoft.com/fwlink/?linkid=844761" />
<local:MyButton Grid.Row="29" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="23" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://licenses.nuget.org/MIT" />
<TextBlock Grid.Row="30" Grid.Column="0" Text="EntityFramework" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="30" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Microsoft Corporation" TextWrapping="Wrap" />
<local:MyButton Grid.Row="31" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="24" Grid.Column="0" Text="EntityFramework" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="24" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © Microsoft Corporation" TextWrapping="Wrap" />
<local:MyButton Grid.Row="25" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/dotnet/efcore" />
<local:MyButton Grid.Row="31" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="25" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://licenses.nuget.org/Apache-2.0" />
<TextBlock Grid.Row="32" Grid.Column="0" Text="PCL.Core" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="32" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © PCL Community" TextWrapping="Wrap" />
<local:MyButton Grid.Row="33" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="26" Grid.Column="0" Text="PCL.Core" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="26" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © PCL Community" TextWrapping="Wrap" />
<local:MyButton Grid.Row="27" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/PCL-Community/PCL.Core" />
<local:MyButton Grid.Row="33" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="27" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/PCL-Community/PCL.Core/blob/main/LICENCE" />
<TextBlock Grid.Row="34" Grid.Column="0" Text="EleCho.WpfSuite" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="34" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © EleCho" TextWrapping="Wrap" />
<local:MyButton Grid.Row="35" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="28" Grid.Column="0" Text="EleCho.WpfSuite" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="28" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © EleCho" TextWrapping="Wrap" />
<local:MyButton Grid.Row="29" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/OrgEleCho/EleCho.WpfSuite" />
<local:MyButton Grid.Row="35" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="29" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/OrgEleCho/EleCho.WpfSuite/blob/master/LICENSE.txt" />
<TextBlock Grid.Row="36" Grid.Column="0" Text="LiteDB" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="36" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2014-2022 Mauricio David" TextWrapping="Wrap" />
<local:MyButton Grid.Row="37" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="30" Grid.Column="0" Text="LiteDB" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="30" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2014-2022 Mauricio David" TextWrapping="Wrap" />
<local:MyButton Grid.Row="31" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/litedb-org/LiteDB" />
<local:MyButton Grid.Row="37" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="31" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/litedb-org/LiteDB/blob/master/LICENSE" />
<TextBlock Grid.Row="38" Grid.Column="0" Text="Markdig.Wpf" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="38" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2016-2021 Nicolas Musset" TextWrapping="Wrap" />
<local:MyButton Grid.Row="39" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="32" Grid.Column="0" Text="Markdig.Wpf" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="32" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2016-2021 Nicolas Musset" TextWrapping="Wrap" />
<local:MyButton Grid.Row="33" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/Kryptos-FR/markdig.wpf" />
<local:MyButton Grid.Row="39" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="33" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/Kryptos-FR/markdig.wpf/blob/main/LICENSE.md" />
<TextBlock Grid.Row="40" Grid.Column="0" Text="CacheCow" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="40" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2013 Ali Kheyrollahi" TextWrapping="Wrap" />
<local:MyButton Grid.Row="41" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="34" Grid.Column="0" Text="CacheCow" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="34" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2013 Ali Kheyrollahi" TextWrapping="Wrap" />
<local:MyButton Grid.Row="35" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/aliostad/CacheCow" />
<local:MyButton Grid.Row="41" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="35" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/aliostad/CacheCow/blob/master/LICENSE.txt" />
<TextBlock Grid.Row="42" Grid.Column="0" Text="Mesa Loader Windows" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="42" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2024 Glavo&#xa;Licensed under the Apache 2 License." TextWrapping="Wrap" />
<local:MyButton Grid.Row="43" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<TextBlock Grid.Row="36" Grid.Column="0" Text="Mesa Loader Windows" TextWrapping="Wrap" FontWeight="Bold" />
<TextBlock Grid.Row="36" Grid.Column="2" Grid.ColumnSpan="2" Text="Copyright © 2024 Glavo&#xa;Licensed under the Apache 2 License." TextWrapping="Wrap" />
<local:MyButton Grid.Row="37" Grid.Column="2" MinWidth="140" Text="查看来源网站" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/HMCL-dev/mesa-loader-windows" />
<local:MyButton Grid.Row="43" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
<local:MyButton Grid.Row="37" Grid.Column="3" MinWidth="140" Text="查看许可文档" Padding="13,0" Margin="0,7,20,18" HorizontalAlignment="Left"
EventType="打开网页" EventData="https://github.com/HMCL-dev/mesa-loader-windows/blob/main/LICENSE" />
</Grid>
</local:MyCard>

View File

@@ -1,5 +1,6 @@

Imports System.Text.RegularExpressions
Imports PCL.Core.Net
Public Class PageOtherVote
Public Class Vote
@@ -27,7 +28,11 @@ Public Class PageOtherVote
End Function
Public Sub VoteListGet(Task As LoaderTask(Of Integer, List(Of Vote)))
Dim content = NetGetCodeByRequestRetry("https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=is%3Aopen+category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Atop", UseBrowserUserAgent:=True)
Dim content As String
Using response = HttpRequestBuilder.Create("https://github.com/Meloong-Git/PCL/discussions/categories/%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8?discussions_q=is%3Aopen+category%3A%E5%8A%9F%E8%83%BD%E6%8A%95%E7%A5%A8+sort%3Atop", Http.HttpMethod.Get).
SendAsync(True).Result
content = response.AsStringContent()
End Using
If content Is Nothing Then Throw New Exception("空内容")
Dim pattern As String = "<div class=""d-flex flex-auto flex-items-start"">(.*?)<svg aria-hidden=""true"" height=""16"" viewBox=""0 0 16 16"" version=""1.1"" width=""16"" data-view-component=""true"" class=""octicon octicon-comment color-fg-muted mr-1"">"

View File

@@ -15,7 +15,7 @@ Public Class PageSetupJava
Return Javas.JavaList.Count
End Function
Private Sub Load_GetJavaList(loader As LoaderTask(Of Integer, List(Of Java)))
Javas.ScanJava().GetAwaiter().GetResult()
Javas.ScanJavaAsync().GetAwaiter().GetResult()
loader.Output = Javas.JavaList
End Sub

View File

@@ -66,10 +66,8 @@
<PackageReference Include="LiteDB" Version="5.0.21" />
<PackageReference Include="Markdig.Wpf" Version="0.5.0.1" />
<PackageReference Include="IPNetwork2" Version="3.1.775" />
<PackageReference Include="Makaretu.Nat" Version="0.2.0" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="STUN" Version="0.5.0" />
<PackageReference Include="System.Management" Version="9.0.8" />
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />