2.11.1
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
Public Class PageLinkMain
|
||||
Imports System.Net.Sockets
|
||||
|
||||
Public Class PageLinkMain
|
||||
|
||||
'===============================
|
||||
' 状态机与前端页面
|
||||
@@ -6,33 +8,33 @@
|
||||
|
||||
#Region "状态管理"
|
||||
|
||||
Private Enum States
|
||||
Public Enum LinkStates
|
||||
Waiting
|
||||
Loading
|
||||
Failed
|
||||
Finished
|
||||
End Enum
|
||||
Private State As States = States.Waiting
|
||||
Public Shared LinkState As LinkStates = LinkStates.Waiting
|
||||
|
||||
''' <summary>
|
||||
''' 切换到指定的状态。
|
||||
''' </summary>
|
||||
Private Sub ChangeState(NewState As States)
|
||||
If State = NewState Then Return
|
||||
Dim OldState = State
|
||||
State = NewState
|
||||
Private Sub ChangeState(NewState As LinkStates)
|
||||
If LinkState = NewState Then Return
|
||||
Dim OldState = LinkState
|
||||
LinkState = NewState
|
||||
Log($"[Link] 主状态由 {GetStringFromEnum(OldState)} 变更为 {GetStringFromEnum(NewState)}")
|
||||
'触发状态切换
|
||||
RunInUi(
|
||||
Sub()
|
||||
Select Case NewState
|
||||
Case States.Waiting
|
||||
Case LinkStates.Waiting
|
||||
SwitchToWaiting(OldState)
|
||||
Case States.Loading
|
||||
Case LinkStates.Loading
|
||||
SwitchToLoading(OldState)
|
||||
Case States.Failed
|
||||
Case LinkStates.Failed
|
||||
SwitchToFailed(OldState)
|
||||
Case States.Finished
|
||||
Case LinkStates.Finished
|
||||
SwitchToFinished(OldState)
|
||||
End Select
|
||||
End Sub)
|
||||
@@ -45,18 +47,18 @@
|
||||
Sub(Loader As LoaderBase, NewState As LoadState, OldState As LoadState)
|
||||
Select Case NewState
|
||||
Case LoadState.Finished
|
||||
ChangeState(States.Finished)
|
||||
ChangeState(LinkStates.Finished)
|
||||
Case LoadState.Failed
|
||||
Telemetry("联机失败", "Exception", FilterUserName(Loader.Error.GetDetail, "*"))
|
||||
ChangeState(States.Failed)
|
||||
ChangeState(LinkStates.Failed)
|
||||
End Select
|
||||
End Sub
|
||||
End Sub
|
||||
|
||||
'页面动画兼容
|
||||
Private Sub UpdatePanelVisibility() Handles Me.PageEnter
|
||||
FrmLinkMain.PanSelect.Visibility = If(State = States.Waiting, Visibility.Visible, Visibility.Collapsed)
|
||||
FrmLinkMain.PanFinish.Visibility = If(State = States.Finished, Visibility.Visible, Visibility.Collapsed)
|
||||
FrmLinkMain.PanSelect.Visibility = If(LinkState = LinkStates.Waiting, Visibility.Visible, Visibility.Collapsed)
|
||||
FrmLinkMain.PanFinish.Visibility = If(LinkState = LinkStates.Finished, Visibility.Visible, Visibility.Collapsed)
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
@@ -64,7 +66,7 @@
|
||||
#Region "初始页面 (Waiting)"
|
||||
|
||||
'由任意状态切换到 Waiting
|
||||
Private Sub SwitchToWaiting(OldState As States)
|
||||
Private Sub SwitchToWaiting(OldState As LinkStates)
|
||||
'加载器与进程状态
|
||||
LinkLoader.Abort()
|
||||
SyncLock LinkLoader.LockState
|
||||
@@ -72,7 +74,7 @@
|
||||
End SyncLock
|
||||
ProcessStop()
|
||||
'页面切换
|
||||
If OldState <> States.Finished Then
|
||||
If OldState <> LinkStates.Finished Then
|
||||
'从完成页面退出时,PageOnContentExit 结束会触发 PageEnter 事件,这会调用 UpdatePanelVisibility
|
||||
'如果在现在提前调用了,会丢失完成页面的退出动画
|
||||
UpdatePanelVisibility()
|
||||
@@ -87,7 +89,7 @@
|
||||
'创建
|
||||
Private Sub Create_MouseLeftButtonUp() Handles PanSelectCreate.MouseLeftButtonUp
|
||||
'输入端口号
|
||||
Dim Port As String = MyMsgBoxInput("输入端口", $"在单人游戏的暂停菜单选择 {vbLQ}对局域网开放{vbRQ},然后输入端口数字。{vbCrLf}其实也可以输入其他游戏的端口……",
|
||||
Dim Port As String = MyMsgBoxInput("输入端口", $"在单人游戏的暂停菜单选择 {vbLQ}对局域网开放{vbRQ},然后输入端口数字。{vbCrLf}甚至可以输入其他游戏的端口……嗯……",
|
||||
ValidateRules:=New ObjectModel.Collection(Of Validate) From {New ValidateInteger(1024, 65535)},
|
||||
HintText:="端口号")
|
||||
If Port Is Nothing Then Return
|
||||
@@ -106,50 +108,31 @@
|
||||
NetworkSecret = GenerateRandomCode()
|
||||
Log($"[Link] 尝试创建房间,网络名 {NetworkName},网络密码 {NetworkSecret},端口 {ServerPort}")
|
||||
'启动
|
||||
ChangeState(States.Loading)
|
||||
ChangeState(LinkStates.Loading)
|
||||
End Sub
|
||||
|
||||
'加入
|
||||
Private LastCode As String = Nothing
|
||||
Private Sub Join_MouseLeftButtonUp() Handles PanSelectJoin.MouseLeftButtonUp
|
||||
'输入邀请码
|
||||
Private Shared LastCode As String = Nothing
|
||||
''' <summary>
|
||||
''' 输入邀请码,切换到联机页并立即加入房间。
|
||||
''' </summary>
|
||||
Public Shared Sub Join() Handles PanSelectJoin.MouseLeftButtonUp
|
||||
Dim Code As String = MyMsgBoxInput("输入邀请码", "输入房主发给你的邀请码。",
|
||||
HintText:=If(String.IsNullOrEmpty(LastCode), "", "使用上一次的邀请码:" & LastCode))
|
||||
HintText:=If(String.IsNullOrEmpty(LastCode), "", "使用上一次的邀请码:" & LastCode))
|
||||
If Not String.IsNullOrEmpty(LastCode) AndAlso Code IsNot Nothing AndAlso Code = "" Then Code = LastCode
|
||||
If Code Is Nothing Then Return
|
||||
Join(Code)
|
||||
End Sub
|
||||
Public Sub JoinInternal(Code As String)
|
||||
If Code Is Nothing Then Return
|
||||
'基础格式校验
|
||||
Code = Code.Between("【", "】").Between("[", "]") '从完整消息中提取
|
||||
Code = Code.ToUpper.Replace("O", "0").Replace("I", "1") '输入修正
|
||||
If Not (Code.Length >= 14 AndAlso Code(0) = "P"c AndAlso Code(5) = "-"c AndAlso Code(11) = "-"c) Then
|
||||
If Code.StartsWithF("U/") Then 'HMCL
|
||||
Hint("请让房主使用 PCL 创建房间!", HintType.Red)
|
||||
ElseIf Code.Length = 10 Then 'PCL CE
|
||||
Hint("请让房主使用非社区版的 PCL 创建房间!", HintType.Red)
|
||||
ElseIf Code.StartsWithF("P") Then '远古版 PCL
|
||||
Hint("你的 PCL 版本与房主的 PCL 版本不一致,或输入的邀请码有误!", HintType.Red)
|
||||
Else
|
||||
Hint("邀请码有误,请让房主使用 PCL 创建房间!", HintType.Red)
|
||||
End If
|
||||
Dim ValidateResult = ValidateCodeFormat(Code)
|
||||
If ValidateResult IsNot Nothing Then
|
||||
Hint(ValidateResult, HintType.Red)
|
||||
Return
|
||||
End If
|
||||
'离线登录警告
|
||||
If McLoginLoader.State = LoadState.Finished AndAlso McLoginLoader.Output.Type = "Legacy" AndAlso Not Setup.Get("LinkOfflineHint") Then
|
||||
Dim Solution As String
|
||||
If McVersionCurrent IsNot Nothing AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 21, 5) Then
|
||||
Solution = $"请换用正版登录,或是让房主安装 {vbLQ}更高级联机设置{vbRQ} Mod 并禁用正版验证。"
|
||||
ElseIf McVersionCurrent IsNot Nothing AndAlso McVersionCurrent.Version.McVersion >= New Version(1, 12, 0) Then
|
||||
Solution = $"请换用正版登录,或是让房主安装 {vbLQ}自定义局域网联机{vbRQ} Mod 并关闭在线模式。"
|
||||
Else
|
||||
Solution = $"请换用正版登录,或是让房主用服务端开服,关闭 online-mode,然后在创建房间时输入服务端的端口号。"
|
||||
End If
|
||||
Select Case MyMsgBox($"如果不用正版登录可能会进不了服,显示 {vbLQ}无效会话{vbRQ}。" & vbCrLf & Solution,
|
||||
"离线登录警告", "继续", "继续且不再提示", "取消", IsWarn:=True)
|
||||
Case 2
|
||||
Setup.Set("LinkOfflineHint", True)
|
||||
Case 3
|
||||
Return
|
||||
End Select
|
||||
End If
|
||||
'基础信息
|
||||
IsServerSide = False
|
||||
ServerPort = RadixConvert(Code.Substring(1, 4), 16, 10)
|
||||
@@ -158,7 +141,45 @@
|
||||
Log($"[Link] 尝试加入房间,网络名 {NetworkName},网络密码 {NetworkSecret},端口 {ServerPort}")
|
||||
'启动
|
||||
LastCode = Code
|
||||
ChangeState(States.Loading)
|
||||
ChangeState(LinkStates.Loading)
|
||||
End Sub
|
||||
Public Shared Function ValidateCodeFormat(Code As String) As String
|
||||
If Code Is Nothing Then Return "邀请码为空!"
|
||||
Code = Code.Between("【", "】").Between("[", "]") '从完整消息中提取
|
||||
Code = Code.ToUpper.Replace("O", "0").Replace("I", "1") '输入修正
|
||||
If Not (Code.Length >= 14 AndAlso Code(0) = "P"c AndAlso Code(5) = "-"c AndAlso Code(11) = "-"c) Then
|
||||
If Code.StartsWithF("U/") Then 'HMCL
|
||||
Return "请让房主使用 PCL 创建房间!"
|
||||
ElseIf Code.Length = 10 Then 'PCL CE
|
||||
Return "请让房主使用非社区版的 PCL 创建房间!"
|
||||
Else
|
||||
Return "邀请码有误,请让房主使用 PCL 创建房间!"
|
||||
End If
|
||||
End If
|
||||
Return Nothing
|
||||
End Function
|
||||
|
||||
'自动加入
|
||||
|
||||
''' <summary>
|
||||
''' 切换到联机页并立即加入指定房间。
|
||||
''' </summary>
|
||||
Public Shared Sub Join(Code As String)
|
||||
If LinkState <> LinkStates.Waiting Then
|
||||
Hint("你已经在联机房间中了!", HintType.Red)
|
||||
ElseIf FrmMain.PageCurrent = FormMain.PageType.Link Then
|
||||
FrmLinkMain.JoinInternal(Code)
|
||||
Else
|
||||
AutoJoinCode = Code
|
||||
FrmMain.PageChange(FormMain.PageType.Link, FormMain.PageSubType.LinkMain)
|
||||
End If
|
||||
End Sub
|
||||
'在进入页面时自动尝试加入房间
|
||||
Private Shared AutoJoinCode As String = Nothing
|
||||
Private Sub PanSelectJoin_Loaded() Handles PanSelectJoin.Loaded
|
||||
If AutoJoinCode Is Nothing Then Return
|
||||
If LinkState = LinkStates.Waiting Then JoinInternal(AutoJoinCode)
|
||||
AutoJoinCode = Nothing
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
@@ -166,7 +187,7 @@
|
||||
#Region "加载页面 (Loading | Failed)"
|
||||
|
||||
'由 Waiting 或 Failed 状态切换到 Loading
|
||||
Private Sub SwitchToLoading(OldState As States)
|
||||
Private Sub SwitchToLoading(OldState As LinkStates)
|
||||
'清理变量
|
||||
Peers = Nothing
|
||||
FailCount = 0
|
||||
@@ -180,7 +201,7 @@
|
||||
End Sub
|
||||
|
||||
'由 Loading 状态切换到 Failed
|
||||
Private Sub SwitchToFailed(OldState As States)
|
||||
Private Sub SwitchToFailed(OldState As LinkStates)
|
||||
'加载器与进程状态
|
||||
LinkLoader.Abort()
|
||||
ProcessStop()
|
||||
@@ -217,12 +238,12 @@
|
||||
|
||||
'取消加载
|
||||
Private Sub BtnLoadCancel_Click() Handles BtnLoadCancel.Click
|
||||
ChangeState(States.Waiting)
|
||||
ChangeState(LinkStates.Waiting)
|
||||
End Sub
|
||||
|
||||
'点击重试
|
||||
Private Sub CardLoad_MouseLeftButtonUp() Handles CardLoad.MouseLeftButtonUp
|
||||
ChangeState(States.Loading)
|
||||
ChangeState(LinkStates.Loading)
|
||||
End Sub
|
||||
|
||||
'进度条
|
||||
@@ -252,7 +273,7 @@
|
||||
#Region "完成页面 (Finished)"
|
||||
|
||||
'由 Loading 状态切换到 Finished
|
||||
Private Sub SwitchToFinished(OldState As States)
|
||||
Private Sub SwitchToFinished(OldState As LinkStates)
|
||||
'由于只可能从加载器的完成事件触发,不需要管加载器
|
||||
UpdatePanelVisibility() '页面的实际切换会由 Loader 调用 MyPageRight 来触发
|
||||
|
||||
@@ -260,26 +281,27 @@
|
||||
If IsServerSide Then
|
||||
LabFinishTitle.Text = "已创建房间"
|
||||
LabFinishDesc.Text = $"把邀请码发给朋友,让大家加入房间吧!{vbCrLf}邀请码:{NetworkName}-{NetworkSecret}"
|
||||
BtnFinishCopy.Text = "复制邀请码"
|
||||
BtnFinishExit.Text = "关闭"
|
||||
BtnFinishCopy.Visibility = Visibility.Visible
|
||||
Copy() '立即复制邀请码
|
||||
'下边栏
|
||||
BtnFinishPing.ToolTip = "网络延迟"
|
||||
LineFinishPort.Visibility = Visibility.Visible
|
||||
BtnFinishPort.Visibility = Visibility.Visible
|
||||
LabFinishPort.Visibility = Visibility.Visible
|
||||
BtnFinishIp.Visibility = Visibility.Collapsed
|
||||
LabFinishIp.Visibility = Visibility.Collapsed
|
||||
LabFinishPort.Text = ServerPort
|
||||
Else
|
||||
LabFinishTitle.Text = "已加入房间"
|
||||
LabFinishDesc.Text = $"在多人游戏中选择直接连接,粘贴地址即可加入游戏!{vbCrLf}服务器地址:{ClientAddress}" '"如果提示无效会话,不要退出联机房间,重新启动游戏即可!"
|
||||
BtnFinishCopy.Text = "复制地址"
|
||||
LabFinishDesc.Text = $"在多人游戏页面的最下方就能找到联机房间!{vbCrLf}注意:使用离线登录时不要手动输入 IP!"
|
||||
BtnFinishExit.Text = "离开"
|
||||
BtnFinishCopy.Visibility = Visibility.Collapsed
|
||||
'下边栏
|
||||
BtnFinishPing.ToolTip = "与房主的延迟"
|
||||
LineFinishPort.Visibility = Visibility.Collapsed
|
||||
BtnFinishPort.Visibility = Visibility.Collapsed
|
||||
LabFinishPort.Visibility = Visibility.Collapsed
|
||||
LabFinishIp.Text = ClientAddress
|
||||
BtnFinishIp.Visibility = Visibility.Visible
|
||||
LabFinishIp.Visibility = Visibility.Visible
|
||||
End If
|
||||
Copy() '复制信息
|
||||
Update() '立即刷新
|
||||
End Sub
|
||||
|
||||
@@ -289,30 +311,37 @@
|
||||
|
||||
'退出
|
||||
Private Sub BtnFinishExit_Click() Handles BtnFinishExit.Click
|
||||
TryExit(True)
|
||||
TryExit(False, False)
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 退出联机。
|
||||
''' 返回是否弹出了警告窗口并且玩家选择了取消。
|
||||
''' </summary>
|
||||
Public Function TryExit(SendWarning As Boolean) As Boolean
|
||||
If State = States.Waiting OrElse State = States.Failed Then Return False
|
||||
If IsServerSide AndAlso PeopleCount > 1 Then
|
||||
If MyMsgBox("你确定要关闭联机房间吗?" & vbCrLf & "所有玩家都需要重新输入邀请码才可加入游戏!", "关闭房间", "确定", "取消", IsWarn:=True) = 2 Then Return True
|
||||
Public Function TryExit(Slient As Boolean, Closing As Boolean) As Boolean
|
||||
If LinkState = LinkStates.Waiting OrElse LinkState = LinkStates.Failed Then Return False
|
||||
If Not Slient Then
|
||||
If IsServerSide AndAlso PeopleCount > 1 Then
|
||||
If MyMsgBox("你确定要关闭联机房间吗?" & vbCrLf & "所有玩家都需要重新输入邀请码才可加入游戏!", "退出联机", "确定", "取消", IsWarn:=True) = 2 Then Return True
|
||||
ElseIf Closing Then
|
||||
If MyMsgBox(If(IsServerSide, "你确定要关闭联机房间吗?", "你确定要离开联机房间吗?"), "退出联机", "确定", "取消") = 2 Then Return True
|
||||
End If
|
||||
End If
|
||||
ChangeState(States.Waiting)
|
||||
ChangeState(LinkStates.Waiting)
|
||||
Return False
|
||||
End Function
|
||||
|
||||
'复制
|
||||
'复制邀请码
|
||||
Private Sub Copy() Handles BtnFinishCopy.Click
|
||||
If IsServerSide Then
|
||||
ClipboardSet($"在 PCL 启动器中输入邀请码【{NetworkName}-{NetworkSecret}】,即可加入联机房间!", False)
|
||||
Hint("已复制邀请码!", HintType.Green)
|
||||
Else
|
||||
ClipboardSet(ClientAddress, False)
|
||||
Hint("已复制服务器地址!", HintType.Green)
|
||||
End If
|
||||
Dim CodeText As String = $"在 PCL 启动器中输入邀请码【{NetworkName}-{NetworkSecret}】,即可加入联机房间!"
|
||||
ClipboardSet(CodeText, False)
|
||||
Setup.Set("LinkLastAutoJoinInviteCode", CodeText)
|
||||
Hint("已复制邀请码!", HintType.Green)
|
||||
End Sub
|
||||
|
||||
'复制 IP
|
||||
Private Sub BtnFinishIp_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs) Handles BtnFinishIp.MouseLeftButtonUp
|
||||
ClipboardSet(ClientAddress, False)
|
||||
Hint("已复制服务器地址!", HintType.Green)
|
||||
End Sub
|
||||
|
||||
#End Region
|
||||
@@ -334,6 +363,14 @@
|
||||
''' </summary>
|
||||
Private ServerPort As Integer
|
||||
''' <summary>
|
||||
''' 客户端映射后的端口号。
|
||||
''' </summary>
|
||||
Private ClientPort As Integer
|
||||
''' <summary>
|
||||
''' RPC 端口号。
|
||||
''' </summary>
|
||||
Private RPCPort As Integer
|
||||
''' <summary>
|
||||
''' 在客户端映射后的地址。
|
||||
''' 除非已作为客户端启动 EasyTier,否则一直为 Nothing。
|
||||
''' </summary>
|
||||
@@ -414,7 +451,7 @@
|
||||
'解压
|
||||
Dim ExtractPath As String = RequestTaskTempFolder()
|
||||
ExtractFile(PathEasyTier & "EasyTier.zip", ExtractPath,
|
||||
ProgressIncrementHandler:=Sub(Progress) Task.Progress += Progress * 0.06)
|
||||
ProgressIncrementHandler:=Sub(Progress) Task.Progress += Progress * 0.05)
|
||||
Dim ExtractedPath As String = New DirectoryInfo(ExtractPath).EnumerateDirectories.FirstOrDefault?.FullName
|
||||
Log("[Link] 联机模块解压时的临时路径:" & ExtractedPath)
|
||||
CopyDirectory(ExtractedPath, PathEasyTier)
|
||||
@@ -427,28 +464,42 @@
|
||||
File.Delete(PathEasyTier & "EasyTier.zip")
|
||||
Setup.Set("LinkEasyTierVersion", ServerVersion)
|
||||
End If
|
||||
Task.Progress = 0.1
|
||||
'获取启动参数
|
||||
Task.Progress = 0.07
|
||||
'获取节点列表
|
||||
Dim Peers As List(Of String)
|
||||
Dim CustomPeers As String = Setup.Get("LinkCustomPeer")
|
||||
If String.IsNullOrWhiteSpace(CustomPeers) Then
|
||||
Peers = GetOnlinePeers()
|
||||
Else
|
||||
Peers = CustomPeers.Split(",,".ToCharArray).Select(Function(p) p.Trim).Where(Function(p) Not String.IsNullOrEmpty(p)).ToList()
|
||||
Log("[Link] 使用自定义节点")
|
||||
End If
|
||||
Task.Progress = 0.13
|
||||
'获取空闲端口
|
||||
UpdateLoadingPage("正在启动联机模块……", "启动联机模块")
|
||||
Dim FreePorts = FindFreePorts(5, ServerPort)
|
||||
ClientPort = FreePorts(0)
|
||||
RPCPort = FreePorts(1)
|
||||
Dim ListenersPort As Integer = FreePorts(2) '它会占用【当前、当前 +1、当前 +2】共三个端口
|
||||
'获取启动参数
|
||||
Dim Arguments As String = ServerConfig("Link")("Argument")
|
||||
Arguments += $" --network-name={NetworkName} --network-secret={NetworkSecret}"
|
||||
Arguments += $" --network-name={NetworkName} --network-secret={NetworkSecret} --listeners {ListenersPort} --rpc-portal {RPCPort}"
|
||||
HostName = If(IsServerSide, "Server-", "Client-") & RadixConvert(Math.Abs(Identify.GetHashCode), 10, 36)
|
||||
If IsServerSide Then
|
||||
Arguments += $" -i 10.114.114.114 --hostname={HostName} --tcp-whitelist={ServerPort} --udp-whitelist={ServerPort}"
|
||||
Else
|
||||
Arguments += $" -d --hostname={HostName} --tcp-whitelist=0 --udp-whitelist=0"
|
||||
'端口转发
|
||||
Dim Port As Integer = GetAvailablePort()
|
||||
ClientAddress = $"localhost:{Port}"
|
||||
Arguments += $" --port-forward tcp://[{IPAddress.IPv6Loopback}]:{Port}/10.114.114.114:{ServerPort}"
|
||||
Arguments += $" --port-forward udp://[{IPAddress.IPv6Loopback}]:{Port}/10.114.114.114:{ServerPort}"
|
||||
Arguments += $" --port-forward tcp://{IPAddress.Loopback}:{Port}/10.114.114.114:{ServerPort}"
|
||||
Arguments += $" --port-forward udp://{IPAddress.Loopback}:{Port}/10.114.114.114:{ServerPort}"
|
||||
ClientAddress = $"localhost:{ClientPort}"
|
||||
Arguments += $" --port-forward tcp://[{IPAddress.IPv6Loopback}]:{ClientPort}/10.114.114.114:{ServerPort}"
|
||||
Arguments += $" --port-forward udp://[{IPAddress.IPv6Loopback}]:{ClientPort}/10.114.114.114:{ServerPort}"
|
||||
Arguments += $" --port-forward tcp://{IPAddress.Loopback}:{ClientPort}/10.114.114.114:{ServerPort}"
|
||||
Arguments += $" --port-forward udp://{IPAddress.Loopback}:{ClientPort}/10.114.114.114:{ServerPort}"
|
||||
End If
|
||||
For Each Peer As String In ServerConfig("Link")("Peers")
|
||||
For Each Peer As String In Peers
|
||||
Arguments += $" -p=""{Peer}"""
|
||||
Next
|
||||
If Not Setup.Get("LinkShareMode") Then Arguments += " --private-mode true"
|
||||
Arguments += " --private-mode true" '老好人模式现在莫得用:If Not Setup.Get("LinkShareMode") Then
|
||||
'启动进程
|
||||
ProcessStart(Arguments)
|
||||
Task.Progress = 0.15
|
||||
@@ -482,9 +533,9 @@
|
||||
'超时判定
|
||||
If LastProgress <> Task.Progress Then
|
||||
TimeoutCounter = 0
|
||||
ElseIf TimeoutCounter < 20 Then
|
||||
ElseIf TimeoutCounter < 30 * 2 Then
|
||||
TimeoutCounter += 1
|
||||
Else '进度停滞超过 10s
|
||||
Else '进度停滞超过 30s
|
||||
Select Case PeerCount
|
||||
Case -1 'CLI 无返回
|
||||
Panic("无法启动联机模块", "近期日志:" & vbCrLf & LogHistory.Join(vbCrLf))
|
||||
@@ -497,6 +548,49 @@
|
||||
Loop Until Task.IsAborted
|
||||
If Task.IsAborted Then Throw New ThreadInterruptedException
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 从在线配置和 API 获取节点列表。
|
||||
''' </summary>
|
||||
Private Function GetOnlinePeers() As List(Of String)
|
||||
Dim Peers As List(Of String) = ServerConfig("Link")("Peers").Select(Function(p) p.ToString).ToList()
|
||||
Try
|
||||
'从 API 获取节点列表
|
||||
Dim BlackList As List(Of String) = ServerConfig("Link")("PeersBlackList").Select(Function(p) p.ToString).ToList() '黑名单
|
||||
Dim CentralNodes As New List(Of String)
|
||||
Dim RandomNodes As New List(Of Tuple(Of String, Double))
|
||||
For Each Node As JObject In CType(GetJson(NetRequestByClientRetry("https://uptime.easytier.cn/api/nodes?page=1&per_page=200")), JObject)("data")("items")
|
||||
'状态检查
|
||||
If Node("protocol").ToString <> "tcp" Then Continue For
|
||||
If Node("current_health_status").ToString <> "healthy" Then Continue For
|
||||
If Not Node("is_active").ToObject(Of Boolean) Then Continue For
|
||||
If Not Node("is_approved").ToObject(Of Boolean) Then Continue For
|
||||
Dim Tags = Node("tags").Select(Function(t) t.ToString).ToList
|
||||
If Not Tags.Contains("国内") Then Continue For
|
||||
If Tags.Contains("即将下线") Then Continue For
|
||||
Dim Address As String = Node("address").ToString
|
||||
If BlackList.Contains(Address) Then Continue For
|
||||
'添加节点
|
||||
If Tags.Contains("MC") Then
|
||||
CentralNodes.Add(Address)
|
||||
Else
|
||||
If Not Node("allow_relay").ToObject(Of Boolean) Then Continue For
|
||||
If Node("usage_percentage").ToObject(Of Double) = 0 Then Continue For '或许节点有问题才导致是 0 负载
|
||||
RandomNodes.Add(New Tuple(Of String, Double)(
|
||||
Address,
|
||||
Node("usage_percentage").ToObject(Of Double) * (103 - Node("health_percentage_24h").ToObject(Of Double)))) '负载,越低越好
|
||||
End If
|
||||
Next
|
||||
RandomNodes = RandomNodes.OrderBy(Function(n) n.Item2).ToList()
|
||||
Log($"[Link] 获取到 {CentralNodes.Count} 个中心节点,{RandomNodes.Count} 个随机节点")
|
||||
'选择节点
|
||||
Dim RandomCount As Integer = ServerConfig("Link")("RandomPeer").ToObject(Of Integer)
|
||||
If RandomNodes.Count < RandomCount Then Throw New Exception($"可用的随机节点数量不足,需要 {RandomCount} 个,实际 {RandomNodes.Count} 个")
|
||||
Peers = CentralNodes.Concat(RandomNodes.Take(RandomCount).Select(Function(n) n.Item1)).ToList()
|
||||
Catch ex As Exception
|
||||
Log(ex, "获取节点列表失败,联机质量可能受到影响", LogLevel.Hint)
|
||||
End Try
|
||||
Return Peers
|
||||
End Function
|
||||
|
||||
#End Region
|
||||
|
||||
@@ -544,26 +638,14 @@
|
||||
''' 出现意外错误,给出错误信息并结束联机。
|
||||
''' </summary>
|
||||
Private Sub Panic(Brief As String, Detail As String)
|
||||
If State = States.Loading Then
|
||||
If LinkState = LinkStates.Loading Then
|
||||
FailReason = Brief
|
||||
LinkLoader.Failed(New Exception(Detail))
|
||||
Else
|
||||
MyMsgBox(Detail, "联机出错:" & Brief, IsWarn:=True)
|
||||
ChangeState(States.Waiting)
|
||||
ChangeState(LinkStates.Waiting)
|
||||
Telemetry("联机失败", "Exception", FilterUserName(Brief & ":" & Detail, "*"))
|
||||
End If
|
||||
'收集到的 CLI 错误:
|
||||
|
||||
'Error: Timeout : deadline has elapsed
|
||||
'Caused by
|
||||
' deadline has elapsed
|
||||
|
||||
'Error: failed to get peer manager client
|
||||
'Caused by
|
||||
' 0: rust tun error failed to connect to server: Url {scheme: "tcp", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("127.0.0.1")), port: Some(15888), path: "", query: None, fragment: None }
|
||||
' 1: failed to connect to server: Url {scheme: "tcp", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("127.0.0.1")), port: Some(15888), path: "", query: None, fragment: None }
|
||||
' 2: IO error
|
||||
' 3: 由于目标计算机积极拒绝, 无法连接。 (os error 10061)
|
||||
End Sub
|
||||
|
||||
''' <summary>
|
||||
@@ -656,32 +738,33 @@
|
||||
''' 名称。
|
||||
''' </summary>
|
||||
Public ReadOnly Name As String
|
||||
''' <summary>
|
||||
''' 连接方式。
|
||||
''' </summary>
|
||||
Public ReadOnly Cost As String
|
||||
|
||||
''' <summary>
|
||||
''' 从 CLI 给出的信息行分析对应的数据。
|
||||
''' 从 CLI 给出的信息分析对应的数据。
|
||||
''' </summary>
|
||||
Public Sub New(Info As String)
|
||||
'具体每个部分的索引见 GetPeerData()
|
||||
Dim Parts = Info.Split("|").Select(Function(s) If(s.All(Function(c) c = " "c), s, s.Trim)).Where(Function(s) s <> "").ToList
|
||||
If Parts.Count <> 10 Then Throw New Exception($"信息列数有误")
|
||||
'延迟
|
||||
Ping = If(Parts(3) = "-", 0, Parts(3))
|
||||
Public Sub New(Info As JObject)
|
||||
'类别
|
||||
Dim PeerName = Parts(1)
|
||||
Dim PeerName = Info("hostname").ToString
|
||||
If PeerName = HostName Then
|
||||
Type = Types.Self
|
||||
ElseIf PeerName.StartsWithF("Client") Then
|
||||
Type = Types.Client
|
||||
ElseIf Parts(0).StartsWithF("10.114.114.114") Then '只允许这个 IP 作为房主,避免网络被多个房主搞炸的情况
|
||||
ElseIf Info("ipv4") = "10.114.114.114" Then '只允许这个 IP 作为房主,避免网络被多个房主搞炸的情况
|
||||
Type = Types.Server
|
||||
Else
|
||||
Type = Types.Misc
|
||||
End If
|
||||
'主机名
|
||||
'基础信息
|
||||
Ping = If(Info("lat_ms").ToString = "-", 0, Info("lat_ms").ToString)
|
||||
Name = PeerName
|
||||
Cost = Info("cost")
|
||||
End Sub
|
||||
Public Overrides Function ToString() As String
|
||||
Return $"{Type} - {Name} - Ping {Ping:0.0}ms"
|
||||
Return $"{Type} - {Name} - Ping {Ping:0.0}ms [{Cost}]"
|
||||
End Function
|
||||
End Class
|
||||
|
||||
@@ -705,12 +788,12 @@
|
||||
'| 10.114.114.114/24 | Server-J6P6IW | p2p | 5.63 | 0.0% | 1.65 kB | 1.64 kB | udp | PortRestricted | 2.4.5-4c4d172e |
|
||||
'| 10.114.114.114/24 | Server-J6PHIW (连接中) | relay(2) | 1000.00 | 0.0% | 0 B | 0 B | | PortRestricted | 2.4.5-4c4d172e |
|
||||
Try
|
||||
Dim CliResult = StartProcessAndGetOutput(PathEasyTier & "联机模块 CLI.exe", "peer", 2000, Encoding:=Encoding.UTF8, PrintLog:=False)
|
||||
Dim CliResult = StartProcessAndGetOutput(PathEasyTier & "联机模块 CLI.exe", $"-o json -p 127.0.0.1:{RPCPort} peer", 2000, Encoding:=Encoding.UTF8, PrintLog:=False)
|
||||
'解析
|
||||
If Not CliResult.Contains("lat(ms)") Then Throw New Exception("CLI 调用失败:" & vbCrLf & CliResult)
|
||||
If GetUuid() Mod If(ModeDebug, 4, 30) = 0 Then Log("[EasyTier] CLI 输出抽样:" & vbCrLf & CliResult)
|
||||
If Not CliResult.Contains("lat_ms") Then Throw New Exception("CLI 调用失败:" & vbCrLf & CliResult)
|
||||
If GetUuid() Mod If(ModeDebug, 23, 103) = 0 Then Log("[EasyTier] CLI 输出抽样:" & vbCrLf & CliResult)
|
||||
Dim NewPeers As New List(Of Peer)
|
||||
For Each Line In CliResult.Split(vbCrLf.ToCharArray, StringSplitOptions.RemoveEmptyEntries).Skip(2)
|
||||
For Each Line As JObject In CType(GetJson(CliResult), JArray).Skip(1)
|
||||
Try
|
||||
Dim Peer = New Peer(Line)
|
||||
If Peer.Type = Peer.Types.Self Then Continue For '自己
|
||||
@@ -724,7 +807,7 @@
|
||||
FailCount = 0
|
||||
Catch ex As Exception
|
||||
Log(ex, "获取节点信息失败")
|
||||
If State = States.Finished Then
|
||||
If LinkState = LinkStates.Finished Then
|
||||
FailCount += 1
|
||||
If FailCount >= 4 Then Panic("获取节点信息失败", ex.Message)
|
||||
End If
|
||||
@@ -741,18 +824,26 @@
|
||||
''' 0 代表尚不可用。
|
||||
''' </summary>
|
||||
Private Function GetPeerPing() As Double
|
||||
If Peers Is Nothing Then Return 0
|
||||
Dim PingTargets As IEnumerable(Of Peer)
|
||||
Dim Peer = GetTargetPeer()
|
||||
If Peer Is Nothing Then Return 0
|
||||
Return Peer.Ping
|
||||
End Function
|
||||
''' <summary>
|
||||
''' 服务端会返回所有节点中 Ping 的那一个,客户端会返回服务端。
|
||||
''' 若没有则为 Nothing。
|
||||
''' </summary>
|
||||
Private Function GetTargetPeer() As Peer
|
||||
If Peers Is Nothing Then Return Nothing
|
||||
Dim Targets As IEnumerable(Of Peer)
|
||||
If IsServerSide Then
|
||||
PingTargets = Peers
|
||||
Targets = Peers.Where(Function(p) p.Ping > 0)
|
||||
Else
|
||||
PingTargets = Peers.Where(Function(p) p.Type = Peer.Types.Server)
|
||||
End If
|
||||
If Not PingTargets.Any Then
|
||||
Return 0
|
||||
Else
|
||||
Return Math.Max(1, PingTargets.Select(Function(p) p.Ping).Min())
|
||||
Targets = Peers.Where(Function(p) p.Type = Peer.Types.Server AndAlso p.Ping > 0)
|
||||
End If
|
||||
If Not Targets.Any Then Return Nothing
|
||||
'返回 Ping 大于 0 且最低的那个
|
||||
Dim MinPing = Targets.Min(Function(p) p.Ping)
|
||||
Return Targets.First(Function(p) p.Ping = MinPing)
|
||||
End Function
|
||||
|
||||
''' <summary>
|
||||
@@ -768,7 +859,7 @@
|
||||
|
||||
#End Region
|
||||
|
||||
#Region "定时状态更新"
|
||||
#Region "定时任务"
|
||||
|
||||
'启动
|
||||
Private IsTimerStarted As Boolean = False
|
||||
@@ -781,13 +872,13 @@
|
||||
Try
|
||||
Thread.Sleep(200)
|
||||
Counter += 200
|
||||
'每 4 秒执行一次
|
||||
If Counter >= 4000 Then
|
||||
'每秒执行一次
|
||||
If Counter >= 1000 Then
|
||||
Counter = 0
|
||||
Update()
|
||||
End If
|
||||
'每 200ms 更新进度条
|
||||
If State = States.Loading Then RunInUi(AddressOf UpdateProgressBar)
|
||||
If LinkState = LinkStates.Loading Then RunInUi(AddressOf UpdateProgressBar)
|
||||
Catch ex As Exception
|
||||
Log(ex, "联机模块主时钟出错", LogLevel.Feedback)
|
||||
Thread.Sleep(10000)
|
||||
@@ -796,9 +887,10 @@
|
||||
End Sub, "Link Timer")
|
||||
End Sub
|
||||
|
||||
'每 4 秒或进入页面时更新一次信息
|
||||
'每秒或进入页面时触发
|
||||
Private BroadcastSocket As New Socket(SocketType.Dgram, ProtocolType.Udp)
|
||||
Private Sub Update() Handles Me.Loaded
|
||||
If State <> States.Finished Then Return
|
||||
If LinkState <> LinkStates.Finished Then Return
|
||||
'重新获取信息
|
||||
SyncLock RefreshPeerLoader.LockState
|
||||
If RefreshPeerLoader.State <> LoadState.Loading Then RefreshPeerLoader.Start(IsForceRestart:=True)
|
||||
@@ -808,13 +900,25 @@
|
||||
RunInUi(
|
||||
Sub()
|
||||
Dim Ping As Double = GetPeerPing()
|
||||
Dim RelayLayer As Integer = If(GetTargetPeer().Cost.RegexSeek("(?<=relay\()\d+") Is Nothing,
|
||||
0, Val(GetTargetPeer().Cost.RegexSeek("(?<=relay\()\d+")) - 1)
|
||||
'更新 Ping 显示
|
||||
If Ping Mod 500 = 0 OrElse $"{Ping:0.0}" = "1.0" OrElse FailCount > 0 Then
|
||||
LabFinishPing.Text = "连接中"
|
||||
AniStop("Link Logo Rotation")
|
||||
Else
|
||||
LabFinishPing.Text = If(Ping >= 10, $"{Ping:0} ms", $"{Ping:0.0} ms")
|
||||
LabFinishPing.Text =
|
||||
If(RelayLayer > 0, If(RelayLayer > 1, $"中继 {RelayLayer} · ", "中继 · "), "") & '使用中继连接时显示 “中继” 前缀
|
||||
If(Ping >= 10, $"{Ping:0} ms", $"{Ping:0.0} ms")
|
||||
AniStart(AaRotateTransform(ImgFinishLogo, 500, 5000), "Link Logo Rotation") 'Logo 旋转动画
|
||||
End If
|
||||
'更新 Ping 的 Tooltip 显示
|
||||
Dim Tooltip As String = If(IsServerSide, "网络延迟", "与房主的延迟")
|
||||
If RelayLayer Then Tooltip &= If(IsServerSide,
|
||||
$"(你的网络环境较差,正经过 {RelayLayer} 层中继,可能会有点卡)",
|
||||
$"(你或者房主的网络环境较差,正经过 {RelayLayer} 层中继,可能会有点卡)")
|
||||
BtnFinishPing.ToolTip = Tooltip
|
||||
'更新人数显示
|
||||
LabFinishPlayer.Text = PeopleCount & " 人"
|
||||
End Sub)
|
||||
End If
|
||||
@@ -827,9 +931,20 @@
|
||||
End If
|
||||
If Not IsServerSide AndAlso Not Peers.Any(Function(p) p.Type = Peer.Types.Server) Then
|
||||
MyMsgBox("房主已离开房间!", "联机结束")
|
||||
ChangeState(States.Waiting)
|
||||
ChangeState(LinkStates.Waiting)
|
||||
Return
|
||||
End If
|
||||
'广播联机房间端口
|
||||
If Not IsServerSide Then
|
||||
Try
|
||||
BroadcastSocket.SendTo(
|
||||
Encoding.UTF8.GetBytes($"[MOTD]PCL 联机房间[/MOTD][AD]{ClientPort}[/AD]"),
|
||||
SocketFlags.None,
|
||||
New IPEndPoint(IPAddress.Loopback, 4445))
|
||||
Catch ex As Exception
|
||||
Log(ex, "广播联机房间端口失败")
|
||||
End Try
|
||||
End If
|
||||
End Sub
|
||||
''' <summary>
|
||||
''' 检查联机模块是否崩溃。
|
||||
|
||||
Reference in New Issue
Block a user