ModMain.vb 48 KB

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