ModMain.vb 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084
  1. Imports System.Collections.ObjectModel
  2. Imports System.Windows.Interop
  3. Imports System.Windows.Threading
  4. Public Module ModMain
  5. #Region "弹出提示"
  6. ''' <summary>
  7. ''' 提示信息的种类。
  8. ''' 该枚举在自定义事件中使用,是公开 API 的一部分。
  9. ''' </summary>
  10. Public Enum HintType
  11. Blue
  12. Green
  13. Red
  14. End Enum
  15. Private Structure HintMessage
  16. Public Text As String
  17. Public Type As HintType
  18. Public Log As Boolean
  19. End Structure
  20. ''' <summary>
  21. ''' 等待弹出的提示列表。以 {String, HintType, Log As Boolean} 形式存储为数组。
  22. ''' </summary>
  23. Private HintWaiting As New ConcurrentQueue(Of HintMessage)
  24. ''' <summary>
  25. ''' 在窗口左下角弹出提示文本。
  26. ''' </summary>
  27. Public Sub Hint(Text As String, Optional Type As HintType = HintType.Blue, Optional Log As Boolean = True)
  28. HintWaiting.Enqueue(New HintMessage With {.Text = If(Text, ""), .Type = Type, .Log = Log})
  29. End Sub
  30. Private Sub HintTick()
  31. Try
  32. 'Tag 存储了:{ 是否可以重用, Uuid }
  33. Dim CurrentHint As HintMessage = Nothing
  34. Do While HintWaiting.TryDequeue(CurrentHint)
  35. ''清除空提示
  36. 'If IsNothing(HintWaiting(0)) OrElse IsNothing(HintWaiting(0)(0)) Then
  37. ' HintWaiting.RemoveAt(0)
  38. ' Continue Do
  39. 'End If
  40. '去回车
  41. CurrentHint.Text = CurrentHint.Text.ReplaceLineEndings(" ", mergeMultiple:=True)
  42. '超量提示直接忽略
  43. If FrmMain.PanHint.Children.Count >= 20 Then GoTo EndHint
  44. '检查是否有重复提示
  45. Dim DoubleStack As Border = Nothing
  46. For Each stack As Border In FrmMain.PanHint.Children
  47. If stack.Tag(0) AndAlso CType(stack.Child, TextBlock).Text = CurrentHint.Text Then DoubleStack = stack
  48. Next
  49. '获取渐变颜色
  50. Dim TargetColor0, TargetColor1 As MyColor
  51. Dim Percent As Double = 0.3
  52. Select Case CurrentHint.Type
  53. Case HintType.Blue
  54. TargetColor0 = New MyColor(215, 37, 155, 252)
  55. TargetColor1 = New MyColor(215, 10, 142, 252)
  56. Case HintType.Green
  57. TargetColor0 = New MyColor(215, 33, 177, 33)
  58. TargetColor1 = New MyColor(215, 29, 160, 29)
  59. Case Else 'HintType.Red
  60. TargetColor0 = New MyColor(215, 255, 53, 11)
  61. TargetColor1 = New MyColor(215, 255, 43, 0)
  62. End Select
  63. If Not IsNothing(DoubleStack) Then
  64. '有重复提示,且该提示的进入动画已播放
  65. If Not AniIsRun("Hint Show " & DoubleStack.Tag(1)) Then
  66. AniStop("Hint Hide " & DoubleStack.Tag(1))
  67. Dim Delay As Double = (800 + CurrentHint.Text.Length.Clamp(5, 23) * 180) * AniSpeed
  68. AniStart({
  69. AaX(DoubleStack, -12 - DoubleStack.Margin.Left, 50,, New AniEaseOutFluent),
  70. AaX(DoubleStack, -8, 50, 50, New AniEaseInFluent),
  71. AaX(DoubleStack, 8, 50, 100, New AniEaseOutFluent),
  72. AaX(DoubleStack, -8, 50, 150, New AniEaseInFluent),
  73. AaDouble(Sub(i)
  74. Percent += i
  75. Dim Gradient As LinearGradientBrush = DoubleStack.Background
  76. Gradient.GradientStops(0).Color = TargetColor0 * Percent + New MyColor(255, 255, 255) * (1 - Percent)
  77. Gradient.GradientStops(1).Color = TargetColor1 * Percent + New MyColor(255, 255, 255) * (1 - Percent)
  78. End Sub, 0.7, 250),
  79. AaX(DoubleStack, -50, 200, Delay, New AniEaseInFluent),
  80. AaOpacity(DoubleStack, -1, 150, Delay),
  81. AaCode(Sub() DoubleStack.Tag(0) = False, Delay),
  82. AaHeight(DoubleStack, -26, 100,, New AniEaseOutFluent, True),
  83. AaCode(Sub() FrmMain.PanHint.Children.Remove(DoubleStack), , True)
  84. }, "Hint Hide " & DoubleStack.Tag(1))
  85. End If
  86. Else
  87. '准备控件
  88. Dim NewHintControl As New Border With {.Tag = {True, GetUuid()}, .Margin = New Thickness(-70, 0, 20, 0), .Opacity = 0, .Height = 0, .HorizontalAlignment = HorizontalAlignment.Left, .CornerRadius = New CornerRadius(0, 6, 6, 0)}
  89. NewHintControl.Background = New LinearGradientBrush(New GradientStopCollection(New List(Of GradientStop) From {
  90. New GradientStop(TargetColor0 * Percent + New MyColor(255, 255, 255) * (1 - Percent), 0),
  91. New GradientStop(TargetColor1 * Percent + New MyColor(255, 255, 255) * (1 - Percent), 1)}), 90)
  92. NewHintControl.Child = New TextBlock With {.TextTrimming = TextTrimming.CharacterEllipsis, .FontSize = 13, .Text = CurrentHint.Text, .Foreground = New MyColor(255, 255, 255), .Margin = New Thickness(33, 5, 8, 5)}
  93. 'AddHandler NewHintControl.MouseLeftButtonDown, AddressOf HideAllHint
  94. FrmMain.PanHint.Children.Add(NewHintControl)
  95. '控件动画
  96. Dim Animations As New List(Of AniData)
  97. If FrmMain.PanHint.Children.Count > 1 Then
  98. '已有提示
  99. Animations.Add(AaHeight(NewHintControl, 26, 150, , New AniEaseOutFluent))
  100. Else
  101. '是唯一提示
  102. NewHintControl.Height = 26
  103. End If
  104. '开始动画
  105. Animations.AddRange({
  106. AaX(NewHintControl, 30, 400, , New AniEaseOutElastic(AniEasePower.Weak)),
  107. AaX(NewHintControl, 20, 200, , New AniEaseOutFluent),
  108. AaOpacity(NewHintControl, 1, 100),
  109. AaDouble(Sub(i)
  110. Percent += i
  111. Dim Gradient As LinearGradientBrush = NewHintControl.Background
  112. Gradient.GradientStops(0).Color = TargetColor0 * Percent + New MyColor(255, 255, 255) * (1 - Percent)
  113. Gradient.GradientStops(1).Color = TargetColor1 * Percent + New MyColor(255, 255, 255) * (1 - Percent)
  114. End Sub, 0.7, 250, 100)
  115. })
  116. AniStart(Animations, "Hint Show " & NewHintControl.Tag(1))
  117. '结束动画
  118. Dim Delay As Double = (800 + CurrentHint.Text.Length.Clamp(5, 23) * 180) * AniSpeed
  119. AniStart({
  120. AaX(NewHintControl, -50, 200, Delay, New AniEaseInFluent),
  121. AaOpacity(NewHintControl, -1, 150, Delay),
  122. AaCode(Sub() NewHintControl.Tag(0) = False, Delay),
  123. AaHeight(NewHintControl, -26, 100,, New AniEaseOutFluent, True),
  124. AaCode(Sub() FrmMain.PanHint.Children.Remove(NewHintControl), , True)
  125. }, "Hint Hide " & NewHintControl.Tag(1))
  126. End If
  127. '结束处理
  128. EndHint:
  129. If CurrentHint.Log Then Logger.Info($"弹出提示:{CurrentHint.Text}")
  130. Loop
  131. Catch ex As Exception
  132. Logger.Info(ex, "显示弹出提示失败")
  133. End Try
  134. End Sub
  135. Private Sub HideAllHint()
  136. For Each Control As Border In FrmMain.PanHint.Children
  137. Control.IsHitTestVisible = False
  138. AniStart({
  139. AaX(Control, -50, 200, , New AniEaseInFluent),
  140. AaOpacity(Control, -1, 150, , New AniEaseInFluent),
  141. AaCode(Sub() Control.Tag(0) = False),
  142. AaHeight(Control, -26, 100,, New AniEaseOutFluent, True),
  143. AaCode(Sub() FrmMain.PanHint.Children.Remove(Control), , True)
  144. }, "Hint Hide " & Control.Tag(1))
  145. Next
  146. End Sub
  147. #End Region
  148. #Region "弹窗"
  149. ''' <summary>
  150. ''' 存储弹窗信息的转换器。
  151. ''' </summary>
  152. Public Class MyMsgBoxConverter
  153. Public Type As MyMsgBoxType
  154. Public Title As String
  155. Public Text As String
  156. ''' <summary>
  157. ''' 输入模式:文本框的文本。
  158. ''' 选择模式:需要放进去的 IEnumberable(Of IMyRadio)。
  159. ''' 登录模式:登录步骤 1 中返回的 JSON。
  160. ''' </summary>
  161. Public Content As Object
  162. ''' <summary>
  163. ''' 输入模式:输入验证规则。
  164. ''' </summary>
  165. Public ValidateRules As Collection(Of Validate)
  166. ''' <summary>
  167. ''' 输入模式:提示文本。
  168. ''' </summary>
  169. Public HintText As String = ""
  170. ''' <summary>
  171. ''' 有多个按钮时,是否给第一个按钮加高亮。
  172. ''' </summary>
  173. Public HighLight As Boolean
  174. Public Button1 As String = "确定"
  175. Public Button2 As String = ""
  176. Public Button3 As String = ""
  177. ''' <summary>
  178. ''' 点击第一个按钮将执行该方法,不关闭弹窗。
  179. ''' </summary>
  180. Public Button1Action As Action = Nothing
  181. ''' <summary>
  182. ''' 点击第二个按钮将执行该方法,不关闭弹窗。
  183. ''' </summary>
  184. Public Button2Action As Action = Nothing
  185. ''' <summary>
  186. ''' 点击第三个按钮将执行该方法,不关闭弹窗。
  187. ''' </summary>
  188. Public Button3Action As Action = Nothing
  189. Public IsWarn As Boolean = False
  190. Public ForceWait As Boolean = False
  191. Public WaitFrame As New DispatcherFrame(True)
  192. ''' <summary>
  193. ''' 弹窗是否已经关闭。
  194. ''' </summary>
  195. Public IsExited As Boolean = False
  196. ''' <summary>
  197. ''' 输入模式:输入的文本。若点击了 非 第一个按钮,则为 Nothing。
  198. ''' 选择模式:点击的按钮编号,从 1 开始。
  199. ''' 登录模式:字符串数组 {AccessToken, RefreshToken} 或一个 Exception。
  200. ''' </summary>
  201. Public Result As Object
  202. End Class
  203. Public Enum MyMsgBoxType
  204. Text
  205. [Select]
  206. Input
  207. Login
  208. End Enum
  209. ''' <summary>
  210. ''' 显示弹窗,返回点击按钮的编号(从 1 开始)。
  211. ''' </summary>
  212. ''' <param name="Title">弹窗的标题。</param>
  213. ''' <param name="Caption">弹窗的内容。</param>
  214. ''' <param name="Button1">显示的第一个按钮,默认为“确定”。</param>
  215. ''' <param name="Button2">显示的第二个按钮,默认为空。</param>
  216. ''' <param name="Button3">显示的第三个按钮,默认为空。</param>
  217. ''' <param name="Button1Action">点击第一个按钮将执行该方法,不关闭弹窗。</param>
  218. ''' <param name="Button2Action">点击第二个按钮将执行该方法,不关闭弹窗。</param>
  219. ''' <param name="Button3Action">点击第三个按钮将执行该方法,不关闭弹窗。</param>
  220. ''' <param name="IsWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
  221. Public Function MyMsgBox(Caption As String, Optional Title As String = "提示",
  222. Optional Button1 As String = "确定", Optional Button2 As String = "", Optional Button3 As String = "",
  223. Optional IsWarn As Boolean = False, Optional HighLight As Boolean = True, Optional ForceWait As Boolean = False,
  224. Optional Button1Action As Action = Nothing, Optional Button2Action As Action = Nothing, Optional Button3Action As Action = Nothing) As Integer
  225. '将弹窗列入队列
  226. Dim Converter As New MyMsgBoxConverter With {.Type = MyMsgBoxType.Text, .Button1 = Button1, .Button2 = Button2, .Button3 = Button3, .Text = Caption, .IsWarn = IsWarn, .Title = Title, .HighLight = HighLight, .ForceWait = True, .Button1Action = Button1Action, .Button2Action = Button2Action, .Button3Action = Button3Action}
  227. If Button2.Length > 0 OrElse ForceWait Then
  228. '若有多个按钮则开始等待
  229. If FrmMain Is Nothing OrElse FrmMain.PanMsg Is Nothing AndAlso RunInUi() Then
  230. '主窗体尚未加载,用老土的弹窗来替代
  231. If Button2.Length > 0 Then
  232. Dim RawResult As MsgBoxResult =
  233. MsgBox(Caption, If(Button3.Length > 0, MsgBoxStyle.YesNoCancel, MsgBoxStyle.YesNo) + If(IsWarn, MsgBoxStyle.Critical, MsgBoxStyle.Question), Title)
  234. Select Case RawResult
  235. Case MsgBoxResult.Yes
  236. Converter.Result = 1
  237. Case MsgBoxResult.No
  238. Converter.Result = 2
  239. Case MsgBoxResult.Cancel
  240. Converter.Result = 3
  241. End Select
  242. Else
  243. MsgBox(Caption, MsgBoxStyle.OkOnly + If(IsWarn, MsgBoxStyle.Critical, MsgBoxStyle.Question), Title)
  244. Converter.Result = 1
  245. End If
  246. Logger.Warn($"主窗体加载完成前出现意料外的等待弹窗:{Button1},{Button2},{Button3}")
  247. Else
  248. WaitingMyMsgBox.Enqueue(Converter)
  249. Try
  250. FrmMain.DragStop()
  251. If RunInUi() Then MyMsgBoxTick()
  252. ComponentDispatcher.PushModal()
  253. Dispatcher.PushFrame(Converter.WaitFrame)
  254. Finally
  255. ComponentDispatcher.PopModal()
  256. End Try
  257. End If
  258. Logger.Info($"普通弹框返回:{If(Converter.Result, "null")}")
  259. Return Converter.Result
  260. Else
  261. '不进行等待,直接返回
  262. WaitingMyMsgBox.Enqueue(Converter)
  263. Return 1
  264. End If
  265. End Function
  266. ''' <summary>
  267. ''' 显示输入框并返回输入的文本。若点击第二个按钮,则返回 Nothing。
  268. ''' </summary>
  269. ''' <param name="Title">弹窗的标题。</param>
  270. ''' <param name="ValidateRules">文本框的输入检测。</param>
  271. ''' <param name="Text">弹窗的介绍文本。</param>
  272. ''' <param name="DefaultInput">文本框的默认内容。</param>
  273. ''' <param name="HintText">文本框的提示内容。</param>
  274. ''' <param name="Button1">显示的第一个按钮,默认为“确定”。</param>
  275. ''' <param name="Button2">显示的第二个按钮,默认为“取消”。</param>
  276. ''' <param name="IsWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
  277. Public Function MyMsgBoxInput(Title As String, Optional Text As String = Nothing, Optional DefaultInput As String = Nothing, Optional ValidateRules As Collection(Of Validate) = Nothing, Optional HintText As String = "", Optional Button1 As String = "确定", Optional Button2 As String = "取消", Optional IsWarn As Boolean = False) As String
  278. '将弹窗列入队列
  279. Dim Converter As New MyMsgBoxConverter With {.Text = If(Text, ""), .HintText = If(HintText, ""), .Type = MyMsgBoxType.Input, .ValidateRules = If(ValidateRules, New Collection(Of Validate)), .Button1 = Button1, .Button2 = Button2, .Content = DefaultInput, .IsWarn = IsWarn, .Title = Title}
  280. WaitingMyMsgBox.Enqueue(Converter)
  281. '虽然我也不知道这是啥但是能用就成了 :)
  282. Try
  283. If FrmMain IsNot Nothing Then FrmMain.DragStop()
  284. If RunInUi() Then MyMsgBoxTick()
  285. ComponentDispatcher.PushModal()
  286. Dispatcher.PushFrame(Converter.WaitFrame)
  287. Finally
  288. ComponentDispatcher.PopModal()
  289. End Try
  290. Logger.Info($"输入弹框返回:{If(Converter.Result, "null")}")
  291. Return Converter.Result
  292. End Function
  293. ''' <summary>
  294. ''' 显示选择框并返回选择的第几项(从 0 开始)。若点击第二个按钮,则返回 Nothing。
  295. ''' </summary>
  296. ''' <param name="Title">弹窗的标题。</param>
  297. ''' <param name="Button1">显示的第一个按钮,默认为 “确定”。</param>
  298. ''' <param name="Button2">显示的第二个按钮,默认为空。</param>
  299. ''' <param name="IsWarn">是否为警告弹窗,若为 True,弹窗配色和背景会变为红色。</param>
  300. Public Function MyMsgBoxSelect(Selections As IEnumerable(Of IMyRadio), Optional Title As String = "提示", Optional Button1 As String = "确定", Optional Button2 As String = "", Optional IsWarn As Boolean = False) As Integer?
  301. '将弹窗列入队列
  302. Dim Converter As New MyMsgBoxConverter With {.Type = MyMsgBoxType.Select, .Button1 = Button1, .Button2 = Button2, .Content = Selections, .IsWarn = IsWarn, .Title = Title}
  303. WaitingMyMsgBox.Enqueue(Converter)
  304. '虽然我也不知道这是啥但是能用就成了 :)
  305. Try
  306. If FrmMain IsNot Nothing Then FrmMain.DragStop()
  307. If RunInUi() Then MyMsgBoxTick()
  308. ComponentDispatcher.PushModal()
  309. Dispatcher.PushFrame(Converter.WaitFrame)
  310. Finally
  311. ComponentDispatcher.PopModal()
  312. End Try
  313. Logger.Info($"选择弹框返回:{If(Converter.Result, "null")}")
  314. Return Converter.Result
  315. End Function
  316. ''' <summary>
  317. ''' 等待显示的弹窗。
  318. ''' </summary>
  319. Public WaitingMyMsgBox As New ConcurrentQueue(Of MyMsgBoxConverter)
  320. Public Sub MyMsgBoxTick()
  321. Try
  322. If FrmMain?.PanMsg Is Nothing OrElse FrmMain.WindowState = WindowState.Minimized Then Return
  323. Dim TargetMsgBox As MyMsgBoxConverter = Nothing
  324. If FrmMain.PanMsg.Children.Count > 0 Then
  325. '弹窗中
  326. FrmMain.PanMsg.Visibility = Visibility.Visible
  327. ElseIf WaitingMyMsgBox.TryDequeue(TargetMsgBox) Then
  328. '没有弹窗,显示一个等待的弹窗
  329. FrmMain.PanMsg.Visibility = Visibility.Visible
  330. Select Case TargetMsgBox.Type
  331. Case MyMsgBoxType.Input
  332. FrmMain.PanMsg.Children.Add(New MyMsgInput(TargetMsgBox))
  333. Case MyMsgBoxType.Select
  334. FrmMain.PanMsg.Children.Add(New MyMsgSelect(TargetMsgBox))
  335. Case MyMsgBoxType.Text
  336. FrmMain.PanMsg.Children.Add(New MyMsgText(TargetMsgBox))
  337. Case MyMsgBoxType.Login
  338. FrmMain.PanMsg.Children.Add(New MyMsgLogin(TargetMsgBox))
  339. End Select
  340. Else
  341. '没有弹窗,没有等待的弹窗
  342. If FrmMain.PanMsg.Visibility <> Visibility.Collapsed Then FrmMain.PanMsg.Visibility = Visibility.Collapsed
  343. End If
  344. Catch ex As Exception
  345. Logger.Error(ex, "处理等待中的弹窗失败")
  346. End Try
  347. End Sub
  348. #End Region
  349. #Region "页面声明"
  350. '在最后进行页面声明,避免颜色尚未加载完毕
  351. '窗体声明
  352. Public FrmMain As FormMain
  353. Public FrmStart As SplashScreen
  354. '页面声明(出于单元测试考虑,初始化页面已转入 FormMain 中)
  355. Public FrmLaunchLeft As PageLaunchLeft
  356. Public FrmLaunchRight As PageLaunchRight
  357. Public FrmSelectLeft As PageSelectLeft
  358. Public FrmSelectRight As PageSelectRight
  359. Public FrmSpeedLeft As PageSpeedLeft
  360. Public FrmSpeedRight As PageSpeedRight
  361. '联机页面声明
  362. Public FrmLinkMain As PageLinkMain
  363. '下载页面声明
  364. Public FrmDownloadLeft As PageDownloadLeft
  365. Public FrmDownloadInstall As PageDownloadInstall
  366. Public FrmDownloadMod As PageDownloadMod
  367. Public FrmDownloadPack As PageDownloadPack
  368. Public FrmDownloadDataPack As PageDownloadDataPack
  369. Public FrmDownloadShader As PageDownloadShader
  370. Public FrmDownloadResourcePack As PageDownloadResourcePack
  371. '设置页面声明
  372. Public FrmSetupLeft As PageSetupLeft
  373. Public FrmSetupLaunch As PageSetupLaunch
  374. Public FrmSetupUI As PageSetupUI
  375. Public FrmSetupSystem As PageSetupSystem
  376. Public FrmSetupLink As PageSetupLink
  377. '其他页面声明
  378. Public FrmOtherLeft As PageOtherLeft
  379. Public FrmOtherHelp As PageOtherHelp
  380. Public FrmOtherAbout As PageOtherAbout
  381. Public FrmOtherTest As PageOtherTest
  382. '登录页面声明
  383. Public FrmLoginLegacy As PageLoginLegacy
  384. Public FrmLoginNide As PageLoginNide
  385. Public FrmLoginNideSkin As PageLoginNideSkin
  386. Public FrmLoginAuth As PageLoginAuth
  387. Public FrmLoginAuthSkin As PageLoginAuthSkin
  388. Public FrmLoginMs As PageLoginMs
  389. Public FrmLoginMsSkin As PageLoginMsSkin
  390. '版本设置页面声明
  391. Public FrmInstanceLeft As PageInstanceLeft
  392. Public FrmInstanceOverall As PageInstanceOverall
  393. Public FrmInstanceMod As PageInstanceMod
  394. Public FrmInstanceModDisabled As PageInstanceModDisabled
  395. Public FrmInstanceSetup As PageInstanceSetup
  396. Public FrmInstanceExport As PageInstanceExport
  397. '资源信息分页声明
  398. Public FrmDownloadResourceDetail As PageDownloadResourceDetail
  399. #End Region
  400. #Region "帮助"
  401. Public Class HelpEntry
  402. ''' <summary>
  403. ''' 原始信息路径。用于刷新。
  404. ''' </summary>
  405. Public RawPath As String
  406. '基础
  407. ''' <summary>
  408. ''' 显示标题。
  409. ''' </summary>
  410. Public Title As String
  411. ''' <summary>
  412. ''' 显示描述。
  413. ''' </summary>
  414. Public Desc As String
  415. ''' <summary>
  416. ''' 检索关键字。
  417. ''' </summary>
  418. Public Search As String
  419. ''' <summary>
  420. ''' 用于分类的标签列表。
  421. ''' </summary>
  422. Public Types As List(Of String)
  423. '显示(可选)
  424. ''' <summary>
  425. ''' 帮助项的自定义图标。可能为 Nothing。
  426. ''' </summary>
  427. Public Logo As String = Nothing
  428. ''' <summary>
  429. ''' 是否显示在搜索结果。默认为 True。
  430. ''' </summary>
  431. Public ShowInSearch As Boolean = True
  432. ''' <summary>
  433. ''' 是否在公开版的 PCL 中显示(这会影响主页与搜索)。默认为 True。
  434. ''' </summary>
  435. Public ShowInPublic As Boolean = True
  436. ''' <summary>
  437. ''' 是否在快照版的 PCL 中显示(这会影响主页与搜索)。默认为 True。
  438. ''' </summary>
  439. Public ShowInSnapshot As Boolean = True
  440. '动作
  441. ''' <summary>
  442. ''' 是否为 “执行事件”。
  443. ''' </summary>
  444. Public IsEvent As Boolean
  445. Public EventType As CustomEvent.EventType
  446. Public EventData As String
  447. ''' <summary>
  448. ''' 若非执行事件,其对应的 .xaml 本地文件内容。
  449. ''' </summary>
  450. Public XamlContent As String
  451. '转换
  452. ''' <summary>
  453. ''' 从文件初始化 HelpEntry 对象,失败会抛出异常。
  454. ''' </summary>
  455. Public Sub New(FilePath As String)
  456. RawPath = FilePath
  457. If Not FileUtils.Exists(FilePath) Then Throw New FileNotFoundException("未找到帮助文件:" & FilePath, FilePath)
  458. Dim JsonData As JObject = ArgumentReplace(FileUtils.ReadAsString(FilePath), AddressOf StringUtils.XmlEscape).DeserializeJson()
  459. '加载常规信息
  460. If JsonData("Title") IsNot Nothing Then
  461. Title = JsonData("Title")
  462. Else
  463. Throw New ArgumentException("未找到 Title 项")
  464. End If
  465. Desc = If(JsonData("Description"), "")
  466. Search = If(JsonData("Keywords"), "")
  467. Logo = JsonData("Logo") '为保持 Nothing,不要加 If
  468. ShowInSearch = If(JsonData("ShowInSearch"), ShowInSearch)
  469. ShowInPublic = If(JsonData("ShowInPublic"), ShowInPublic)
  470. ShowInSnapshot = If(JsonData("ShowInSnapshot"), ShowInSnapshot)
  471. Types = New List(Of String)
  472. For Each NameOfType In If(JsonData("Types"), New JArray)
  473. Types.Add(NameOfType)
  474. Next
  475. '加载事件信息
  476. If If(JsonData("IsEvent"), False) Then
  477. EventType = JsonData("EventType").ToObject(Of CustomEvent.EventType)
  478. EventData = If(JsonData("EventData"), "")
  479. IsEvent = True
  480. Else
  481. Dim XamlAddress As String = FilePath.Lower.Replace(".json", ".xaml")
  482. If FileUtils.Exists(XamlAddress) Then
  483. XamlContent = FileUtils.ReadAsString(XamlAddress)
  484. IsEvent = False
  485. Else
  486. Throw New FileNotFoundException("未找到帮助条目 .json 对应的 .xaml 文件(" & XamlAddress & ")")
  487. End If
  488. End If
  489. End Sub
  490. ''' <summary>
  491. ''' 获取该 HelpEntry 对应的 MyListItem。
  492. ''' </summary>
  493. Public Function ToListItem() As MyListItem
  494. Return SetToListItem(New MyListItem)
  495. End Function
  496. ''' <summary>
  497. ''' 将属性设置入一个现有的 ListItem。
  498. ''' </summary>
  499. Public Function SetToListItem(Item As MyListItem) As MyListItem
  500. Dim Logo As String
  501. If IsEvent Then
  502. If EventType = CustomEvent.EventType.弹出窗口 Then
  503. Logo = PathImage & "Blocks/GrassPath.png"
  504. Else
  505. Logo = PathImage & "Blocks/CommandBlock.png"
  506. End If
  507. Else
  508. Logo = PathImage & "Blocks/Grass.png"
  509. End If
  510. '设置属性
  511. With Item
  512. .SnapsToDevicePixels = True
  513. .Title = Title
  514. .Info = Desc
  515. .Logo = If(Me.Logo, Logo)
  516. .Height = 42
  517. .Type = MyListItem.CheckType.Clickable
  518. .Tag = Me
  519. End With
  520. CustomEventService.SetEventType(Item, CustomEvent.EventType.None) '清空自定义事件属性,它们会被下面的点击事件处理
  521. CustomEventService.SetEventData(Item, Nothing)
  522. '项目的点击事件
  523. AddHandler Item.Click, Sub(sender, e) PageOtherHelp.OnItemClick(sender.Tag)
  524. Return Item
  525. End Function
  526. End Class
  527. Public HelpLoader As New LoaderTask(Of Integer, List(Of HelpEntry))("Help Page", AddressOf HelpLoad,, ThreadPriority.BelowNormal)
  528. Private ReadOnly HelpLoadLock As New Object
  529. ''' <summary>
  530. ''' 初始化帮助列表对象。
  531. ''' </summary>
  532. Private Sub HelpLoad(Loader As LoaderTask(Of Integer, List(Of HelpEntry)))
  533. SyncLock HelpLoadLock '避免重复解压文件导致出错
  534. Try
  535. '解压内置文件
  536. HelpTryExtract()
  537. '遍历文件
  538. Dim FileList As New List(Of String)
  539. Try
  540. Dim IgnoreList As New List(Of String)
  541. '读取自定义文件
  542. If DirectoryUtils.Exists(Paths.Base & "PCL\Help\") Then
  543. For Each File In DirectoryUtils.EnumerateFiles(Paths.Base & "PCL\Help\", True)
  544. Select Case PathUtils.GetExtension(File)
  545. Case "helpignore"
  546. '加载忽略列表
  547. Logger.Info($"发现 .helpignore 文件:{File}")
  548. For Each Line In FileUtils.ReadAsLines(File, True)
  549. Dim RealString As String = Line.BeforeFirst("#").Trim
  550. If String.IsNullOrWhiteSpace(RealString) Then Continue For
  551. IgnoreList.Add(RealString)
  552. Logger.Trace($"> {RealString}")
  553. Next
  554. Case "json"
  555. FileList.Add(File)
  556. End Select
  557. Next
  558. End If
  559. Logger.Info($"已扫描 PCL 文件夹下的帮助文件,目前总计 {FileList.Count} 条")
  560. '读取自带文件
  561. For Each File In DirectoryUtils.EnumerateFiles(PathTemp & "Help", True)
  562. '跳过非 json 文件与以 . 开头的文件夹
  563. If PathUtils.RemoveLastPart(File).Replace(PathTemp & "Help", "").Contains("\.") OrElse PathUtils.GetExtension(File) <> "json" Then Continue For
  564. '检查忽略列表
  565. Dim RealPath As String = File.Replace(PathTemp & "Help\", "")
  566. For Each Ignore In IgnoreList
  567. If RealPath.RegexCheck(Ignore) Then
  568. Logger.Trace($"已忽略 {RealPath}:{Ignore}")
  569. GoTo NextFile
  570. End If
  571. Next
  572. FileList.Add(File)
  573. NextFile:
  574. Next
  575. Logger.Info($"已扫描缓存文件夹下的帮助文件,目前总计 {FileList.Count} 条")
  576. Catch ex As Exception
  577. Logger.Error(ex, "检查帮助文件夹失败", LogBehavior.Alert)
  578. End Try
  579. If Loader.IsCanceled Then Return
  580. '将文件实例化
  581. Dim Dict As New List(Of HelpEntry)
  582. For Each FilePath As String In FileList
  583. Try
  584. Dim Entry As New HelpEntry(FilePath)
  585. Dict.Add(Entry)
  586. Logger.Trace($"已加载的帮助条目:{Entry.Title} ← {FilePath}")
  587. Catch ex As Exception
  588. Logger.Error(ex, $"初始化帮助条目失败({FilePath})", LogBehavior.Alert)
  589. End Try
  590. Next
  591. '回设
  592. If Not Dict.Any() Then Throw New Exception("未找到可用的帮助;若不需要帮助页面,可以在 设置 → 个性化 → 功能隐藏 中将其隐藏")
  593. If Loader.IsCanceled Then Return
  594. Loader.Output = Dict
  595. Catch ex As Exception
  596. Logger.Warn(ex, "帮助列表初始化失败")
  597. Throw
  598. End Try
  599. End SyncLock
  600. End Sub
  601. ''' <summary>
  602. ''' 尝试解压内置帮助文件。
  603. ''' </summary>
  604. Public Sub HelpTryExtract()
  605. If Settings.Get(Of Integer)("SystemHelpVersion") <> VersionCode OrElse Not FileUtils.Exists(PathTemp & "Help\启动器\备份设置.xaml") Then
  606. DirectoryUtils.Delete(PathTemp & "Help")
  607. ExtractResources(PathTemp & "Cache\Help.zip", "Help")
  608. FileUtils.ExtractToDirectory(PathTemp & "Cache\Help.zip", PathTemp & "Help")
  609. Settings.Set("SystemHelpVersion", VersionCode)
  610. Logger.Info($"已解压内置帮助文件,目前状态:{FileUtils.Exists(PathTemp + "Help\启动器\备份设置.xaml")}")
  611. End If
  612. End Sub
  613. #End Region
  614. #Region "愚人节"
  615. Private _IsAprilEnabled As Boolean? = Nothing
  616. Public ReadOnly Property IsAprilEnabled As Boolean
  617. Get
  618. If _IsAprilEnabled Is Nothing Then
  619. _IsAprilEnabled =
  620. Date.Now.Month = 4 AndAlso Date.Now.Day = 1 AndAlso
  621. Settings.Get(Of Integer)("AprilYear") <> Date.Now.Year '成功后同年内则不再触发
  622. End If
  623. Return _IsAprilEnabled.Value
  624. End Get
  625. End Property
  626. Public IsAprilGiveup As Boolean = False
  627. Private AprilDifficultiedDistance As Integer = 0
  628. Private Sub TimerFool()
  629. Try
  630. If FrmLaunchLeft Is Nothing OrElse FrmLaunchLeft.AprilPosTrans Is Nothing OrElse FrmMain.lastMouseArg Is Nothing Then Return
  631. If IsAprilGiveup OrElse FrmMain.PageCurrent <> FormMain.PageType.Launch OrElse AniControlEnabled <> 0 OrElse Not FrmLaunchLeft.BtnLaunch.IsLoaded Then Return
  632. '计算是否空闲
  633. Dim MousePos = FrmMain.lastMouseArg.GetPosition(FrmMain)
  634. Static IdieCount As Integer = 0
  635. Static MousePosLast As New Point(0, 0)
  636. If MousePos = MousePosLast Then
  637. IdieCount += 1
  638. Else
  639. MousePosLast = MousePos
  640. IdieCount = 0
  641. End If
  642. '计算躲避移动
  643. Dim Direction As Vector
  644. Dim Distance As Double
  645. Dim ButtonWidth = FrmLaunchLeft.BtnLaunch.ActualWidth / 2, ButtonHeight = FrmLaunchLeft.BtnLaunch.ActualHeight / 2
  646. Dim Vec As Vector = FrmMain.lastMouseArg.GetPosition(FrmLaunchLeft.BtnLaunch) - New Vector(ButtonWidth, ButtonHeight)
  647. Dim Dir As New Vector(Vec.X, Vec.Y)
  648. Dir.Normalize()
  649. Direction = -Dir
  650. Distance = New Vector(Math.Max(0, Math.Abs(Vec.X) - ButtonWidth), Math.Max(0, Math.Abs(Vec.Y) - ButtonHeight)).Length
  651. Dim BreathScale = Math.Sin(Timer150Count / 37.5 * Math.PI)
  652. Dim Difficulty As Double = 1 / (1 + (AprilDifficultiedDistance / 6000) ^ 2) + 0.25 '难度,初始为 1.25x,6000 距离为 0.75x,12000 为 0.5x,24000 为 0.256x,最低 0.25x
  653. Dim Acc = Math.Max(0, -0.65 - Math.Log((Distance / Difficulty + 0.4) / 200)) * Direction * Difficulty '加速度
  654. 'FrmMain.Title = CInt(AprilTotalDistance) & " - " & Difficulty
  655. '5s 不动时回到起始点
  656. If IdieCount >= 64 * 5 Then
  657. Dim SafeDist As Vector = FrmMain.lastMouseArg.GetPosition(FrmMain.PanMain) - New Vector(ButtonWidth, FrmMain.PanMain.ActualHeight - ButtonHeight * 3)
  658. Dim Back As New Vector(FrmLaunchLeft.AprilPosTrans.X, FrmLaunchLeft.AprilPosTrans.Y)
  659. If SafeDist.Length > 250 AndAlso Back.Length > 0.4 Then
  660. Acc -= Back * 0.0005
  661. Back.Normalize()
  662. Acc -= Back * 0.15
  663. End If
  664. End If
  665. '回到边界
  666. Static Speed As New Vector(0, 0)
  667. Dim Relative As Point = FrmLaunchLeft.BtnLaunch.TranslatePoint(New Point(0, 0), FrmMain.PanForm)
  668. If Relative.X < -ButtonWidth * 2 Then
  669. FrmLaunchLeft.AprilPosTrans.X += FrmMain.PanForm.ActualWidth + ButtonWidth * 2 '离开左边界
  670. Speed.X -= 80
  671. If Relative.Y < 0 Then
  672. FrmLaunchLeft.AprilPosTrans.Y += ButtonHeight * 2.5
  673. ElseIf Relative.Y > FrmMain.PanForm.ActualHeight - ButtonHeight * 2 Then
  674. FrmLaunchLeft.AprilPosTrans.Y -= ButtonHeight * 2.5
  675. End If
  676. ElseIf Relative.X > FrmMain.PanForm.ActualWidth Then
  677. FrmLaunchLeft.AprilPosTrans.X -= FrmMain.PanForm.ActualWidth + ButtonWidth * 2 '离开右边界
  678. Speed.X += 80
  679. If Relative.Y < 0 Then
  680. FrmLaunchLeft.AprilPosTrans.Y += ButtonHeight * 2.5
  681. ElseIf Relative.Y > FrmMain.PanForm.ActualHeight - ButtonHeight * 2 Then
  682. FrmLaunchLeft.AprilPosTrans.Y -= ButtonHeight * 2.5
  683. End If
  684. ElseIf Relative.Y < -ButtonHeight * 2 Then
  685. FrmLaunchLeft.AprilPosTrans.Y += FrmMain.PanForm.ActualHeight + ButtonHeight * 2 '离开上边界
  686. Speed.Y -= 25
  687. If Relative.X < 0 Then
  688. FrmLaunchLeft.AprilPosTrans.X += ButtonWidth * 2
  689. ElseIf Relative.X > FrmMain.PanForm.ActualWidth - ButtonWidth * 2 Then
  690. FrmLaunchLeft.AprilPosTrans.X -= ButtonWidth * 2
  691. End If
  692. ElseIf Relative.Y > FrmMain.PanForm.ActualHeight Then
  693. FrmLaunchLeft.AprilPosTrans.Y -= FrmMain.PanForm.ActualHeight + ButtonHeight * 2 '离开下边界
  694. Speed.Y += 25
  695. If Relative.X < 0 Then
  696. FrmLaunchLeft.AprilPosTrans.X += ButtonWidth * 2
  697. ElseIf Relative.X > FrmMain.PanForm.ActualWidth - ButtonWidth * 2 Then
  698. FrmLaunchLeft.AprilPosTrans.X -= ButtonWidth * 2
  699. End If
  700. End If
  701. '移动
  702. Speed = Speed * 0.8 + Acc
  703. Dim SpeedValue = Math.Min(60, Speed.Length)
  704. If SpeedValue < 0.01 Then Return
  705. Speed.Normalize()
  706. Speed *= SpeedValue
  707. AprilDifficultiedDistance += SpeedValue
  708. FrmLaunchLeft.AprilPosTrans.X += Speed.X
  709. FrmLaunchLeft.AprilPosTrans.Y += Speed.Y
  710. '大小改变
  711. FrmLaunchLeft.AprilScaleTrans.ScaleX = (1 - (Math.Abs(Direction.X) - Math.Abs(Direction.Y)) * (SpeedValue / 160)).Clamp(0.2, 1.8)
  712. FrmLaunchLeft.AprilScaleTrans.ScaleY = (1 - (Math.Abs(Direction.Y) - Math.Abs(Direction.X)) * (SpeedValue / 100)).Clamp(0.2, 1.8)
  713. '放弃提示
  714. Static GiveUpDistance As Double = -1500
  715. GiveUpDistance += SpeedValue
  716. If GiveUpDistance > 2500 Then
  717. GiveUpDistance = 0
  718. Select Case RandomInteger(0, 3)
  719. Case 0
  720. Hint("放弃吧!只需要点一下右下角的小白旗……", HintType.Red)
  721. Case 1
  722. Hint("看到右下角的那面小白旗了吗?", HintType.Red)
  723. Case 2
  724. Hint("这里建议点一下右下角的小白旗投降呢.jpg", HintType.Red)
  725. Case 3
  726. Hint("右下角的小白旗永远等着你……", HintType.Red)
  727. End Select
  728. End If
  729. Catch ex As Exception
  730. Logger.Error(ex, "愚人节移动出错")
  731. End Try
  732. End Sub
  733. #End Region
  734. #Region "系统"
  735. ''' <summary>
  736. ''' 把某个 PCL 窗口拖到最前面。
  737. ''' </summary>
  738. Public Sub ShowWindowToTop(Handle As IntPtr)
  739. Try
  740. PostMessage(Handle, 400 * 16 + 2, 0, 0)
  741. SetForegroundWindow(Handle) '不在这里放不行,神秘 WinAPI,建议别动
  742. Catch ex As Exception
  743. Logger.Error(ex, "设置窗口置顶失败", LogBehavior.Toast)
  744. End Try
  745. End Sub
  746. Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ClassName As String, WindowName As String) As IntPtr
  747. Public Declare Function SetForegroundWindow Lib "user32" (hWnd As IntPtr) As Integer
  748. Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (hWnd As IntPtr, msg As UInteger, wParam As Long, lParam As Long) As Boolean
  749. ''' <summary>
  750. ''' 将特定程序设置为使用高性能显卡启动。
  751. ''' 如果失败,则抛出异常。
  752. ''' </summary>
  753. Public Sub SetGPUPreference(Executeable As String)
  754. Const REG_KEY As String = "Software\Microsoft\DirectX\UserGpuPreferences"
  755. Const REG_VALUE As String = "GpuPreference=2;"
  756. '查看现有设置
  757. Using ReadOnlyKey = My.Computer.Registry.CurrentUser.OpenSubKey(REG_KEY, False)
  758. If ReadOnlyKey IsNot Nothing Then
  759. If REG_VALUE = ReadOnlyKey.GetValue(Executeable)?.ToString() Then
  760. Logger.Info($"无需调整显卡设置:{Executeable}")
  761. Return
  762. End If
  763. Else
  764. '创建父级键
  765. Logger.Info($"需要创建显卡设置的父级键")
  766. My.Computer.Registry.CurrentUser.CreateSubKey(REG_KEY)
  767. End If
  768. End Using
  769. '写入新设置
  770. Using WriteKey = My.Computer.Registry.CurrentUser.OpenSubKey(REG_KEY, True)
  771. WriteKey.SetValue(Executeable, REG_VALUE)
  772. Logger.Info($"已调整显卡设置:{Executeable}")
  773. End Using
  774. End Sub
  775. ''' <summary>
  776. ''' 对替换标记进行处理。会对替换内容使用 EscapeHandler 进行转义。
  777. ''' </summary>
  778. Public Function ArgumentReplace(Text As String, Optional EscapeHandler As Func(Of String, String) = Nothing, Optional ReplaceTime As Boolean = True) As String
  779. If Text Is Nothing OrElse Not Text.Contains("{") Then Return Text
  780. '预处理(注意,文件夹必须以 \ 结尾)
  781. Static Replacer As Func(Of String, String) =
  782. Function(s As String) As String
  783. If s Is Nothing Then Return ""
  784. If EscapeHandler Is Nothing Then Return s
  785. If s.Contains(":\") Then
  786. Dim IsFolder = s.EndsWithF("\") OrElse s.EndsWithF("/")
  787. s = PathUtils.ToShortPath(s)
  788. If IsFolder Then s = PathUtils.AddSlashSuffix(s)
  789. End If
  790. Return EscapeHandler(s)
  791. End Function
  792. '基础
  793. Text = Text.Replace("{pcl_version}", Replacer(VersionBaseName))
  794. Text = Text.Replace("{pcl_version_code}", Replacer(VersionCode))
  795. Text = Text.Replace("{pcl_version_branch}", Replacer(BuildTypeDisplay))
  796. Text = Text.Replace("{pcl_build_type}", Replacer(BuildType.ToString))
  797. Text = Text.Replace("{pcl_branch}", Replacer(VersionBranchMain))
  798. Text = Text.Replace("{identify}", Replacer(Identify))
  799. Text = Text.Replace("{path}", Replacer(Paths.Base))
  800. Text = Text.Replace("{path_with_name}", Replacer(PathExe))
  801. Text = Text.Replace("{path_temp}", Replacer(PathTemp))
  802. Text = Text.Replace("{pcl_md5}", Function() Replacer(CryptographyUtils.ComputeFileHash(PathExe, CryptographyUtils.HashMethod.Md5)))
  803. Text = Text.Replace("{pcl_sha1}", Function() Replacer(CryptographyUtils.ComputeFileHash(PathExe, CryptographyUtils.HashMethod.Sha1)))
  804. '时间
  805. If ReplaceTime Then '在窗口标题中,时间会被后续动态替换,所以此时不应该替换
  806. Text = Text.Replace("{date}", Replacer(Date.Now.ToString("yyyy'/'M'/'d")))
  807. Text = Text.Replace("{time}", Replacer(Date.Now.ToString("HH':'mm':'ss")))
  808. End If
  809. 'Minecraft
  810. Text = Text.Replace("{java}", Replacer(McLaunchJavaSelected?.Folder))
  811. Text = Text.Replace("{minecraft}", Replacer(McFolderSelected))
  812. If McInstanceSelected?.IsLoaded Then
  813. Text = Text.Replace("{version_path}", Replacer(McInstanceSelected.PathVersion)) : Text = Text.Replace("{verpath}", Replacer(McInstanceSelected.PathVersion))
  814. Text = Text.Replace("{version_indie}", Replacer(McInstanceSelected.PathIndie)) : Text = Text.Replace("{verindie}", Replacer(McInstanceSelected.PathIndie))
  815. Text = Text.Replace("{name}", Replacer(McInstanceSelected.Name))
  816. If {"unknown", "old", "pending"}.Contains(McInstanceSelected.Version.VanillaName.Lower) Then
  817. Text = Text.Replace("{version}", Replacer(McInstanceSelected.Name))
  818. Else
  819. Text = Text.Replace("{version}", Replacer(McInstanceSelected.Version.VanillaName))
  820. End If
  821. Else
  822. Text = Text.Replace("{version_path}", Replacer(Nothing)) : Text = Text.Replace("{verpath}", Replacer(Nothing))
  823. Text = Text.Replace("{version_indie}", Replacer(Nothing)) : Text = Text.Replace("{verindie}", Replacer(Nothing))
  824. Text = Text.Replace("{name}", Replacer(Nothing))
  825. Text = Text.Replace("{version}", Replacer(Nothing))
  826. End If
  827. '登录信息
  828. If McLoginLoader.State = LoadState.Finished Then
  829. Text = Text.Replace("{user}", Replacer(McLoginLoader.Output.Name))
  830. Text = Text.Replace("{uuid}", Replacer(McLoginLoader.Output.Uuid?.Lower))
  831. Select Case McLoginLoader.Input.Type
  832. Case McLoginType.Legacy
  833. Text = Text.Replace("{login}", Replacer("离线"))
  834. Case McLoginType.Ms
  835. Text = Text.Replace("{login}", Replacer("正版"))
  836. Case McLoginType.Nide
  837. Text = Text.Replace("{login}", Replacer("统一通行证"))
  838. Case McLoginType.Auth
  839. Text = Text.Replace("{login}", Replacer("Authlib-Injector"))
  840. End Select
  841. Else
  842. Text = Text.Replace("{user}", Replacer(Nothing))
  843. Text = Text.Replace("{uuid}", Replacer(Nothing))
  844. Text = Text.Replace("{login}", Replacer(Nothing))
  845. End If
  846. '高级
  847. Text = Text.RegexReplace("\{hint\}", Function() Replacer(PageOtherTest.GetRandomHint()))
  848. Text = Text.RegexReplace("\{cave\}", Function() Replacer(PageOtherTest.GetRandomCave()))
  849. Text = Text.RegexReplace("\{setup:([a-zA-Z0-9]+)\}", Function(m) Replacer(Settings.GetSafe(m.Groups(1).Value, McInstanceSelected)))
  850. Text = Text.RegexReplace("\{varible:([^:\}]+)(?::([^\}]+))?\}", Function(m) Replacer(ReadReg("CustomEvent" & m.Groups(1).Value, m.Groups(2).Value)))
  851. Text = Text.RegexReplace("\{variable:([^:\}]+)(?::([^\}]+))?\}", Function(m) Replacer(ReadReg("CustomEvent" & m.Groups(1).Value, m.Groups(2).Value)))
  852. Return Text
  853. End Function
  854. #End Region
  855. #Region "任务缓存"
  856. Private IsTaskTempCleared As Boolean = False
  857. Private IsTaskTempClearing As Boolean = False
  858. ''' <summary>
  859. ''' 尝试清理任务缓存文件夹。
  860. ''' 在整次运行中只会实际清理一次。
  861. ''' </summary>
  862. Public Sub TryClearTaskTemp()
  863. If Not IsTaskTempCleared Then
  864. IsTaskTempCleared = True
  865. IsTaskTempClearing = True
  866. Try
  867. Logger.Info("开始清理任务缓存文件夹")
  868. DirectoryUtils.Delete($"{OsDrive}ProgramData\PCL\TaskTemp\")
  869. DirectoryUtils.Delete($"{PathTemp}TaskTemp\")
  870. Logger.Info("已清理任务缓存文件夹")
  871. Catch ex As Exception
  872. Logger.Warn(ex, "清理任务缓存文件夹失败")
  873. Finally
  874. IsTaskTempClearing = False
  875. End Try
  876. ElseIf IsTaskTempClearing Then
  877. '等待另一个清理步骤完成
  878. Do While IsTaskTempClearing
  879. Thread.Sleep(1)
  880. Loop
  881. End If
  882. End Sub
  883. ''' <summary>
  884. ''' 申请一个可用于任务缓存的临时文件夹,以 \ 结尾。这些文件夹无需进行后续清理。
  885. ''' 若所有缓存位置均没有权限,会抛出异常。
  886. ''' </summary>
  887. ''' <param name="RequireNonSpace">是否要求路径不包含空格。</param>
  888. Public Function RequestTaskTempFolder(Optional RequireNonSpace As Boolean = False) As String
  889. TryClearTaskTemp()
  890. Dim ResultFolder As String
  891. Try
  892. ResultFolder = $"{PathTemp}TaskTemp\{GetUuid()}-{RandomInteger(0, 1000000)}\"
  893. If RequireNonSpace AndAlso ResultFolder.Contains(" ") Then Exit Try '带空格
  894. DirectoryUtils.Create(ResultFolder)
  895. CheckPermissionWithException(ResultFolder)
  896. Return ResultFolder
  897. Catch
  898. End Try
  899. '使用备用路径
  900. ResultFolder = $"{OsDrive}ProgramData\PCL\TaskTemp\{GetUuid()}-{RandomInteger(0, 1000000)}\"
  901. DirectoryUtils.Create(ResultFolder)
  902. CheckPermission(ResultFolder)
  903. Return ResultFolder
  904. End Function
  905. #End Region
  906. #Region "反馈与遥测"
  907. '反馈
  908. Public Sub Feedback(Optional ShowMsgbox As Boolean = True, Optional ForceOpenLog As Boolean = False)
  909. On Error Resume Next
  910. FeedbackInfo()
  911. If ForceOpenLog OrElse (ShowMsgbox AndAlso MyMsgBox("若你在汇报一个 Bug,请点击 打开文件夹 按钮,并上传 Log(1~5).txt 中包含错误信息的文件。" & vbCrLf & "游戏崩溃一般与启动器无关,请不要因为游戏崩溃而提交反馈。", "反馈提交提醒", "打开文件夹", "不需要") = 1) Then
  912. OpenExplorer(Paths.Base & "PCL\Log1.txt")
  913. End If
  914. OpenWebsite("https://github.com/Meloong-Git/PCL/issues/")
  915. End Sub
  916. ''' <summary>
  917. ''' 在日志中输出系统诊断信息。
  918. ''' </summary>
  919. Public Sub FeedbackInfo()
  920. On Error Resume Next
  921. Logger.Warn($"诊断信息:{vbCrLf}" &
  922. "操作系统:" & My.Computer.Info.OSFullName & vbCrLf &
  923. "剩余内存:" & Int(My.Computer.Info.AvailablePhysicalMemory / 1024 / 1024) & " M / " & Int(My.Computer.Info.TotalPhysicalMemory / 1024 / 1024) & " M" & vbCrLf &
  924. "DPI:" & DPI & "(" & Math.Round(DPI / 96, 2) * 100 & "%)" & vbCrLf &
  925. "MC 文件夹:" & If(McFolderSelected, "Nothing") & vbCrLf &
  926. "文件位置:" & Paths.Base)
  927. End Sub
  928. '遥测
  929. Public Sub Telemetry([Event] As String, ParamArray Datas As String())
  930. If BuildType = BuildTypes.Debug Then Return '开发版不上传遥测
  931. If Not Settings.Get(Of Boolean)("SystemSystemTelemetry") Then Return '用户关闭了遥测
  932. If Not ClsBaseUrl.StartsWithF("http") Then Return '开源版没有设置遥测地址
  933. RunInNewThread(
  934. Sub()
  935. Try
  936. Logger.Info($"匿名数据上报:{[Event]}")
  937. Dim Url As String = $"{ClsBaseUrl}&Event={StringUtils.UrlEscape([Event])}"
  938. For i = 0 To Datas.Length - 1 Step 2
  939. Url &= "&" & StringUtils.UrlEscape(Datas(i)) & "=" & StringUtils.UrlEscape(Datas(i + 1).ReplaceLineEndings(vbLf, mergeMultiple:=True))
  940. Next
  941. NetRequestByClient(Url, MakeLog:=False)
  942. Catch ex As Exception
  943. Logger.Warn(ex, "匿名数据上报失败")
  944. End Try
  945. End Sub, "Telemetry", ThreadPriority.Lowest)
  946. End Sub
  947. #End Region
  948. Public DragControl = Nothing
  949. Private Timer4Count As Integer = 0
  950. Private Timer150Count As Integer = 0
  951. Private Sub TimerMain()
  952. Try
  953. #Region "每 50ms 执行一次的代码"
  954. HintTick()
  955. MyMsgBoxTick()
  956. FrmMain.DragTick()
  957. LoaderTaskbarProgressRefresh()
  958. If ThemeDontClick = 2 Then ThemeRefresh()
  959. #End Region
  960. Catch ex As Exception
  961. Logger.Error(ex, "短程主时钟执行异常", LogBehavior.AlertThenCrash)
  962. End Try
  963. Timer4Count += 1
  964. If Timer4Count = 4 Then
  965. Timer4Count = 0
  966. Try
  967. #Region "每 250ms 执行一次的代码"
  968. If ThemeNow = 12 Then ThemeRefresh()
  969. #End Region
  970. Catch ex As Exception
  971. Logger.Warn(ex, "中程主时钟执行异常")
  972. End Try
  973. End If
  974. Timer150Count += 1
  975. If Timer150Count = 150 Then
  976. Timer150Count = 0
  977. Try
  978. #Region "每 7.5s 执行一次的代码"
  979. If FrmMain.BtnExtraApril_ShowCheck AndAlso AprilDifficultiedDistance <> 0 Then FrmMain.BtnExtraApril.Ribble()
  980. '以未知原因窗口被丢到一边去的修复(Top、Left = -25600),还有 #745
  981. RunInUi(
  982. Sub()
  983. If Not FrmMain.Hidden Then
  984. If FrmMain.Top < -9000 Then FrmMain.Top = 100
  985. If FrmMain.Left < -9000 Then FrmMain.Left = 100 '窗口拉至最大时 Left = -18.8
  986. End If
  987. End Sub)
  988. #End Region
  989. Catch ex As Exception
  990. Logger.Error(ex, "长程主时钟执行异常", LogBehavior.AlertThenCrash)
  991. End Try
  992. End If
  993. End Sub
  994. Public Sub TimerMainStart()
  995. RunInNewThread(
  996. Sub()
  997. Try
  998. Do While True
  999. RunInUiWait(AddressOf TimerMain)
  1000. Thread.Sleep(50 * 0.98)
  1001. Loop
  1002. Catch ex As Exception
  1003. Logger.Error(ex, "程序主时钟出错")
  1004. End Try
  1005. End Sub, "Timer Main")
  1006. If Not IsAprilEnabled Then Return
  1007. RunInNewThread(
  1008. Sub()
  1009. Try
  1010. Dim LastTime = My.Computer.Clock.TickCount
  1011. Do While True
  1012. If LastTime <> My.Computer.Clock.TickCount Then
  1013. LastTime = My.Computer.Clock.TickCount
  1014. RunInUiWait(AddressOf TimerFool)
  1015. End If
  1016. Thread.Sleep(1)
  1017. Loop
  1018. Catch ex As Exception
  1019. Logger.Error(ex, "愚人节主时钟出错")
  1020. End Try
  1021. End Sub, "Timer Main Fool")
  1022. End Sub
  1023. End Module