PageVersionMod.xaml.vb 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  1. Public Class PageVersionMod
  2. #Region "初始化"
  3. Private IsLoad As Boolean = False
  4. Public Sub PageOther_Loaded() Handles Me.Loaded
  5. If FrmMain.PageLast.Page <> FormMain.PageType.CompDetail Then PanBack.ScrollToHome()
  6. AniControlEnabled += 1
  7. SelectedMods.Clear()
  8. RefreshList()
  9. ChangeAllSelected(False)
  10. AniControlEnabled -= 1
  11. '非重复加载部分
  12. If IsLoad Then Exit Sub
  13. IsLoad = True
  14. #If DEBUG Then
  15. BtnManageCheck.Visibility = Visibility.Visible
  16. #End If
  17. End Sub
  18. ''' <summary>
  19. ''' 刷新 Mod 列表。
  20. ''' </summary>
  21. Public Sub RefreshList(Optional ForceReload As Boolean = False)
  22. If LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", If(ForceReload, LoaderFolderRunType.ForceRun, LoaderFolderRunType.RunOnUpdated)) Then
  23. Log("[System] 已刷新 Mod 列表")
  24. PanBack.ScrollToHome()
  25. SearchBox.Text = ""
  26. End If
  27. End Sub
  28. Private Sub LoaderInit() Handles Me.Initialized
  29. PageLoaderInit(Load, PanLoad, PanAllBack, Nothing, McModLoader, AddressOf LoadUIFromLoaderOutput, AutoRun:=False)
  30. End Sub
  31. Private Sub Load_Click(sender As Object, e As MouseButtonEventArgs) Handles Load.Click
  32. If McModLoader.State = LoadState.Failed Then
  33. LoaderFolderRun(McModLoader, PageVersionLeft.Version.PathIndie & "mods\", LoaderFolderRunType.ForceRun)
  34. End If
  35. End Sub
  36. #End Region
  37. #Region "UI 化"
  38. ''' <summary>
  39. ''' 已加载的 Mod UI 缓存,不确保按显示顺序排列。Key 为 Mod 的 RawFileName。
  40. ''' </summary>
  41. Public ModItems As New Dictionary(Of String, MyLocalModItem)
  42. ''' <summary>
  43. ''' 将加载器结果的 Mod 列表加载为 UI。
  44. ''' </summary>
  45. Private Sub LoadUIFromLoaderOutput()
  46. Dim Mods As List(Of McMod) = McModLoader.Output
  47. Try
  48. '判断应该显示哪一个页面
  49. If Mods.Any() Then
  50. PanBack.Visibility = Visibility.Visible
  51. PanEmpty.Visibility = Visibility.Collapsed
  52. Else
  53. PanEmpty.Visibility = Visibility.Visible
  54. PanBack.Visibility = Visibility.Collapsed
  55. Exit Sub
  56. End If
  57. '输出结果
  58. ModItems.Clear()
  59. For Each ModEntity As McMod In Mods
  60. ModItems(ModEntity.RawFileName) = McModListItem(ModEntity)
  61. Next
  62. SearchBox.Text = "" '这会触发结果刷新,所以需要在 ModItems 更新之后,详见 #3124 的视频
  63. RefreshResult(Mods)
  64. Catch ex As Exception
  65. Log(ex, "加载 Mod 列表 UI 失败", LogLevel.Feedback)
  66. End Try
  67. End Sub
  68. Private Function McModListItem(Entry As McMod) As MyLocalModItem
  69. AniControlEnabled += 1
  70. Dim NewItem As New MyLocalModItem With {.SnapsToDevicePixels = True, .Entry = Entry,
  71. .ButtonHandler = AddressOf McModContent, .Checked = SelectedMods.Contains(Entry.RawFileName)}
  72. AddHandler Entry.OnCompUpdate, AddressOf NewItem.Refresh
  73. NewItem.Refresh()
  74. AniControlEnabled -= 1
  75. Return NewItem
  76. End Function
  77. Private Sub McModContent(sender As MyLocalModItem, e As EventArgs)
  78. '点击事件
  79. AddHandler sender.Changed, AddressOf CheckChanged
  80. AddHandler sender.Click, Sub(ss As MyLocalModItem, ee As EventArgs) ss.Checked = Not ss.Checked
  81. '图标按钮
  82. Dim BtnOpen As New MyIconButton With {.LogoScale = 1.05, .Logo = Logo.IconButtonOpen, .Tag = sender}
  83. BtnOpen.ToolTip = "打开文件位置"
  84. ToolTipService.SetPlacement(BtnOpen, Primitives.PlacementMode.Center)
  85. ToolTipService.SetVerticalOffset(BtnOpen, 30)
  86. ToolTipService.SetHorizontalOffset(BtnOpen, 2)
  87. AddHandler BtnOpen.Click, AddressOf Open_Click
  88. Dim BtnCont As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonInfo, .Tag = sender}
  89. BtnCont.ToolTip = "详情"
  90. ToolTipService.SetPlacement(BtnCont, Primitives.PlacementMode.Center)
  91. ToolTipService.SetVerticalOffset(BtnCont, 30)
  92. ToolTipService.SetHorizontalOffset(BtnCont, 2)
  93. AddHandler BtnCont.Click, AddressOf Info_Click
  94. AddHandler sender.MouseRightButtonUp, AddressOf Info_Click
  95. Dim BtnDelete As New MyIconButton With {.LogoScale = 1, .Logo = Logo.IconButtonDelete, .Tag = sender}
  96. BtnDelete.ToolTip = "删除"
  97. ToolTipService.SetPlacement(BtnDelete, Primitives.PlacementMode.Center)
  98. ToolTipService.SetVerticalOffset(BtnDelete, 30)
  99. ToolTipService.SetHorizontalOffset(BtnDelete, 2)
  100. AddHandler BtnDelete.Click, AddressOf Delete_Click
  101. Dim BtnED As New MyIconButton With {.LogoScale = 1, .Logo = If(sender.Entry.State = McMod.McModState.Fine, Logo.IconButtonStop, Logo.IconButtonCheck), .Tag = sender}
  102. BtnED.ToolTip = If(sender.Entry.State = McMod.McModState.Fine, "禁用", "启用")
  103. ToolTipService.SetPlacement(BtnED, Primitives.PlacementMode.Center)
  104. ToolTipService.SetVerticalOffset(BtnED, 30)
  105. ToolTipService.SetHorizontalOffset(BtnED, 2)
  106. AddHandler BtnED.Click, AddressOf ED_Click
  107. If sender.Entry.State = McMod.McModState.Unavaliable Then
  108. sender.Buttons = {BtnCont, BtnOpen, BtnDelete}
  109. Else
  110. sender.Buttons = {BtnCont, BtnOpen, BtnED, BtnDelete}
  111. End If
  112. End Sub
  113. ''' <summary>
  114. ''' 刷新结果显示。
  115. ''' </summary>
  116. Private Sub RefreshResult(Mods As List(Of McMod))
  117. PanDefaultList.Children.Clear()
  118. PanDisabledList.Children.Clear()
  119. PanUpdateList.Children.Clear()
  120. PanUnavaliableList.Children.Clear()
  121. For Each TargetMod In Mods
  122. If TargetMod.CanUpdate Then
  123. PanUpdateList.Children.Add(ModItems(TargetMod.RawFileName))
  124. ElseIf TargetMod.State.Equals(McMod.McModState.Fine) Then
  125. PanDefaultList.Children.Add(ModItems(TargetMod.RawFileName))
  126. ElseIf TargetMod.State.Equals(McMod.McModState.Disabled) Then
  127. PanDisabledList.Children.Add(ModItems(TargetMod.RawFileName))
  128. Else
  129. PanUnavaliableList.Children.Add(ModItems(TargetMod.RawFileName))
  130. End If
  131. Next
  132. RefreshTitle()
  133. End Sub
  134. ''' <summary>
  135. ''' 刷新卡片标题。
  136. ''' </summary>
  137. Private Sub RefreshTitle()
  138. RefreshTargetTitle(PanDefaultListBack, PanDefaultList, "启用 Mod")
  139. RefreshTargetTitle(PanUpdateListBack, PanUpdateList, "可更新 Mod")
  140. RefreshTargetTitle(PanDisabledListBack, PanDisabledList, "禁用 Mod")
  141. RefreshTargetTitle(PanUnavaliableListBack, PanUnavaliableList, "暂不可用 Mod")
  142. End Sub
  143. Private Sub RefreshTargetTitle(Card As MyCard, List As StackPanel, Name As String)
  144. Dim Mods = List.Children.Cast(Of MyLocalModItem).Select(Function(i) i.Entry).ToList
  145. If Not IsSearching Then
  146. Card.Title = Name & " 列表 (" & Mods.Count & ")"
  147. ElseIf Mods.Any() Then
  148. Card.Title = Name & " 列表搜索结果 (" & Mods.Count & ")"
  149. Else
  150. Card.Title = Name & " 列表无搜索结果"
  151. End If
  152. List.Visibility = If(Mods.Any(), Visibility.Visible, Visibility.Collapsed)
  153. Card.CanSwap = If(Mods.Any(), True, False)
  154. End Sub
  155. #End Region
  156. #Region "管理"
  157. ''' <summary>
  158. ''' 打开 Mods 文件夹。
  159. ''' </summary>
  160. Private Sub BtnManageOpen_Click(sender As Object, e As EventArgs) Handles BtnManageOpen.Click, BtnHintOpen.Click
  161. Try
  162. Directory.CreateDirectory(PageVersionLeft.Version.PathIndie & "mods\")
  163. OpenExplorer("""" & PageVersionLeft.Version.PathIndie & "mods\""")
  164. Catch ex As Exception
  165. Log(ex, "打开 Mods 文件夹失败", LogLevel.Msgbox)
  166. End Try
  167. End Sub
  168. #If DEBUG Then
  169. ''' <summary>
  170. ''' 检查 Mod。
  171. ''' </summary>
  172. Private Sub BtnManageCheck_Click(sender As Object, e As EventArgs) Handles BtnManageCheck.Click
  173. Try
  174. Dim Result = McModCheck(PageVersionLeft.Version, McModLoader.Output)
  175. If Result.Any Then
  176. MyMsgBox(Join(Result, vbCrLf & vbCrLf), "Mod 检查结果")
  177. Else
  178. Hint("Mod 检查完成,未发现任何问题!", HintType.Finish)
  179. End If
  180. Catch ex As Exception
  181. Log(ex, "进行 Mod 检查时出错", LogLevel.Feedback)
  182. End Try
  183. End Sub
  184. #End If
  185. ''' <summary>
  186. ''' 全选。
  187. ''' </summary>
  188. Private Sub BtnManageSelectAll_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageSelectAll.Click
  189. If SelectedMods.Count < PanDefaultList.Children.Count + PanUnavaliableList.Children.Count + PanUpdateList.Children.Count + PanDisabledList.Children.Count Then
  190. ChangeAllSelected(True)
  191. Else
  192. ChangeAllSelected(False)
  193. End If
  194. End Sub
  195. ''' <summary>
  196. ''' 安装 Mod。
  197. ''' </summary>
  198. Private Sub BtnManageInstall_Click(sender As Object, e As MouseButtonEventArgs) Handles BtnManageInstall.Click
  199. Hint("将 Mod 文件直接拖入 PCL 窗口即可安装!")
  200. End Sub
  201. #End Region
  202. #Region "选择"
  203. '选择的 Mod 的路径(不含 .disabled)
  204. Public SelectedMods As New List(Of String)
  205. '单项切换选择状态
  206. Public Sub CheckChanged(sender As MyLocalModItem, e As RouteEventArgs)
  207. If AniControlEnabled <> 0 Then Return
  208. '更新选择了的内容
  209. Dim SelectedKey As String = sender.Entry.RawFileName
  210. If sender.Checked Then
  211. If Not SelectedMods.Contains(SelectedKey) Then SelectedMods.Add(SelectedKey)
  212. Else
  213. SelectedMods.Remove(SelectedKey)
  214. End If
  215. '更新下边栏 UI
  216. RefreshBottomBar()
  217. End Sub
  218. '改变下边栏状态
  219. Private ShownCount As Integer = 0
  220. Private Sub RefreshBottomBar()
  221. '计数
  222. Dim NewCount As Integer = SelectedMods.Count
  223. Dim Selected = NewCount > 0
  224. If Selected Then LabSelect.Text = $"已选择 {NewCount} 个文件" '取消所有选择时不更新数字
  225. '按钮可用性
  226. If Selected Then
  227. Dim HasUpdate As Boolean = False
  228. Dim HasEnabled As Boolean = False
  229. Dim HasDisabled As Boolean = False
  230. For Each ModEntity In McModLoader.Output
  231. If SelectedMods.Contains(ModEntity.RawFileName) Then
  232. If ModEntity.CanUpdate Then HasUpdate = True
  233. If ModEntity.State = McMod.McModState.Fine Then
  234. HasEnabled = True
  235. ElseIf ModEntity.State = McMod.McModState.Disabled Then
  236. HasDisabled = True
  237. End If
  238. End If
  239. Next
  240. BtnSelectDisable.IsEnabled = HasEnabled
  241. BtnSelectEnable.IsEnabled = HasDisabled
  242. BtnSelectUpdate.IsEnabled = HasUpdate
  243. End If
  244. '更新显示状态
  245. If AniControlEnabled = 0 Then
  246. If Selected Then
  247. '仅在数量增加时播放出现/跳跃动画
  248. If ShownCount >= NewCount Then
  249. ShownCount = NewCount
  250. Return
  251. Else
  252. ShownCount = NewCount
  253. End If
  254. '出现/跳跃动画
  255. CardSelect.Visibility = Visibility.Visible
  256. AniStart({
  257. AaOpacity(CardSelect, 1 - CardSelect.Opacity, 60),
  258. AaTranslateY(CardSelect, -27 - TransSelect.Y, 120, Ease:=New AniEaseOutFluent(AniEasePower.Weak)),
  259. AaTranslateY(CardSelect, 3, 150, 120, Ease:=New AniEaseInoutFluent(AniEasePower.Weak)),
  260. AaTranslateY(CardSelect, -1, 90, 270, Ease:=New AniEaseInoutFluent(AniEasePower.Weak))
  261. }, "Mod Sidebar")
  262. Else
  263. '不重复播放隐藏动画
  264. If ShownCount = 0 Then Return
  265. ShownCount = 0
  266. '隐藏动画
  267. AniStart({
  268. AaOpacity(CardSelect, -CardSelect.Opacity, 90),
  269. AaTranslateY(CardSelect, -10 - TransSelect.Y, 90, Ease:=New AniEaseInFluent(AniEasePower.Weak)),
  270. AaCode(Sub() CardSelect.Visibility = Visibility.Collapsed, After:=True)
  271. }, "Mod Sidebar")
  272. End If
  273. Else
  274. AniStop("Mod Sidebar")
  275. ShownCount = NewCount
  276. If Selected Then
  277. CardSelect.Visibility = Visibility.Visible
  278. CardSelect.Opacity = 1
  279. TransSelect.Y = -25
  280. Else
  281. CardSelect.Visibility = Visibility.Collapsed
  282. CardSelect.Opacity = 0
  283. TransSelect.Y = -10
  284. End If
  285. End If
  286. End Sub
  287. '切换所有项的选择状态
  288. Private Sub ChangeAllSelected(Value As Boolean)
  289. AniControlEnabled += 1
  290. SelectedMods.Clear()
  291. ChangeTargetListSeleted(PanUpdateListBack, PanUpdateList, Value)
  292. ChangeTargetListSeleted(PanDisabledListBack, PanDisabledList, Value)
  293. ChangeTargetListSeleted(PanUnavaliableListBack, PanUnavaliableList, Value)
  294. ChangeTargetListSeleted(PanDefaultListBack, PanDefaultList, Value)
  295. AniControlEnabled -= 1
  296. '更新下边栏 UI
  297. RefreshBottomBar()
  298. End Sub
  299. Private Sub ChangeTargetListSeleted(Card As MyCard, List As StackPanel, Value As Boolean)
  300. If Value.Equals(True) Then Card.IsSwaped = False
  301. For Each Item As MyLocalModItem In List.Children
  302. Item.Checked = Value
  303. If Value Then SelectedMods.Add(Item.Entry.RawFileName)
  304. Next
  305. End Sub
  306. Private Sub UnselectedAllWithAnimation() Handles Load.StateChanged, Me.PageExit
  307. Dim CacheAniControlEnabled = AniControlEnabled
  308. AniControlEnabled = 0
  309. ChangeAllSelected(False)
  310. AniControlEnabled += CacheAniControlEnabled
  311. End Sub
  312. Private Sub PageVersionMod_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
  313. If My.Computer.Keyboard.CtrlKeyDown AndAlso e.Key = Key.A Then ChangeAllSelected(True)
  314. End Sub
  315. #End Region
  316. #Region "下边栏"
  317. '启用 / 禁用
  318. Private Sub BtnSelectED_Click(sender As MyIconTextButton, e As RouteEventArgs) Handles BtnSelectEnable.Click, BtnSelectDisable.Click
  319. EDMods(McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName)),
  320. Not sender.Equals(BtnSelectDisable))
  321. ChangeAllSelected(False)
  322. End Sub
  323. Private Sub EDMods(ModList As IEnumerable(Of McMod), IsEnable As Boolean)
  324. Dim IsSuccessful As Boolean = True
  325. For Each ModE In ModList.ToList
  326. Dim ModEntity = ModE '仅用于去除迭代变量无法修改的限制
  327. Dim NewPath As String = Nothing
  328. If ModEntity.State = McMod.McModState.Fine AndAlso Not IsEnable Then
  329. '禁用
  330. NewPath = ModEntity.Path & If(File.Exists(ModEntity.Path & ".old"), ".old", ".disabled")
  331. ElseIf ModEntity.State = McMod.McModState.Disabled AndAlso IsEnable Then
  332. '启用
  333. NewPath = ModEntity.RawPath
  334. Else
  335. Continue For
  336. End If
  337. '重命名
  338. Try
  339. If File.Exists(NewPath) Then
  340. If File.Exists(ModEntity.Path) Then
  341. '同时存在两个名称的 Mod
  342. If GetFileMD5(ModEntity.Path) <> GetFileMD5(NewPath) Then
  343. MyMsgBox($"目前同时存在启用和禁用的两个 Mod 文件:{vbCrLf} - {NewPath}{vbCrLf} - {ModEntity.Path}{vbCrLf}{vbCrLf}注意,这两个文件的内容并不相同。{vbCrLf}在手动删除或重命名其中一个文件后,才能继续操作。", "存在文件冲突")
  344. Continue For
  345. End If
  346. Else
  347. '已经重命名过了
  348. Log("[Mod] Mod 的状态已被切换", LogLevel.Debug)
  349. Continue For
  350. End If
  351. End If
  352. File.Delete(NewPath)
  353. FileSystem.Rename(ModEntity.Path, NewPath)
  354. Catch ex As FileNotFoundException
  355. Log(ex, $"未找到需要重命名的 Mod({If(ModEntity.Path, "null")})", LogLevel.Feedback)
  356. RefreshList(True)
  357. Return
  358. Catch ex As Exception
  359. Log(ex, $"重命名 Mod 失败({If(ModEntity.Path, "null")})")
  360. IsSuccessful = False
  361. End Try
  362. '更改 Loader 中的列表
  363. Dim NewModEntity As New McMod(NewPath)
  364. NewModEntity.FromJson(ModEntity.ToJson)
  365. Dim IndexOfLoader As Integer = McModLoader.Output.IndexOf(ModEntity)
  366. McModLoader.Output.RemoveAt(IndexOfLoader)
  367. McModLoader.Output.Insert(IndexOfLoader, NewModEntity)
  368. '更改 UI 中的列表
  369. Dim NewItem As MyLocalModItem = McModListItem(NewModEntity)
  370. ModItems(ModEntity.RawFileName) = NewItem
  371. If ModEntity.CanUpdate Then
  372. Dim IndexOfUi As Integer = PanUpdateList.Children.IndexOf(PanUpdateList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry Is ModEntity))
  373. If IndexOfUi = -1 Then Continue For '因为未知原因 Mod 的状态已经切换完了
  374. PanUpdateList.Children.RemoveAt(IndexOfUi)
  375. PanUpdateList.Children.Insert(IndexOfUi, NewItem)
  376. ElseIf ModEntity.State.Equals(McMod.McModState.Fine) Then
  377. Dim IndexOfUi As Integer = PanDefaultList.Children.IndexOf(PanDefaultList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry Is ModEntity))
  378. If IndexOfUi = -1 Then Continue For '因为未知原因 Mod 的状态已经切换完了
  379. PanDefaultList.Children.RemoveAt(IndexOfUi)
  380. PanDefaultList.Children.Insert(IndexOfUi, NewItem)
  381. ElseIf ModEntity.State.Equals(McMod.McModState.Disabled) Then
  382. Dim IndexOfUi As Integer = PanDisabledList.Children.IndexOf(PanDisabledList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry Is ModEntity))
  383. If IndexOfUi = -1 Then Continue For '因为未知原因 Mod 的状态已经切换完了
  384. PanDisabledList.Children.RemoveAt(IndexOfUi)
  385. PanDisabledList.Children.Insert(IndexOfUi, NewItem)
  386. Else
  387. Dim IndexOfUi As Integer = PanUnavaliableList.Children.IndexOf(PanUnavaliableList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry Is ModEntity))
  388. If IndexOfUi = -1 Then Continue For '因为未知原因 Mod 的状态已经切换完了
  389. PanUnavaliableList.Children.RemoveAt(IndexOfUi)
  390. PanUnavaliableList.Children.Insert(IndexOfUi, NewItem)
  391. End If
  392. Next
  393. RefreshTitle() '改变数量显示
  394. If Not IsSuccessful Then
  395. Hint("由于文件被占用,Mod 的状态切换失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
  396. RefreshList(True)
  397. Else
  398. RefreshBottomBar()
  399. End If
  400. End Sub
  401. '更新
  402. Private Sub BtnSelectUpdate_Click() Handles BtnSelectUpdate.Click
  403. UpdateMods(McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName) AndAlso m.CanUpdate))
  404. End Sub
  405. ''' <summary>
  406. ''' 记录正在进行 Mod 更新的 mods 文件夹路径。
  407. ''' </summary>
  408. Public Shared UpdatingVersions As New List(Of String)
  409. Public Sub New()
  410. ' 此调用是设计器所必需的。
  411. InitializeComponent()
  412. ' 在 InitializeComponent() 调用之后添加任何初始化。
  413. End Sub
  414. Public Sub UpdateMods(ModList As IEnumerable(Of McMod))
  415. '更新前警告
  416. If Not Setup.Get("HintUpdateMod") Then
  417. If MyMsgBox($"新版本 Mod 可能不兼容老版本的存档或者其他 Mod,这可能导致游戏崩溃,甚至存档损坏!{vbCrLf}除非整合包作者要求你更新,否则不要私自更新整合包里的 Mod!{vbCrLf}在更新 Mod 前,请先备份存档,并检查它的更新日志!{vbCrLf}更新时,老版本的 Mod 会被移动到回收站,以防万一。{vbCrLf}{vbCrLf}请在认真阅读上述警告后再继续!", "Mod 更新警告", "我已了解上述风险,继续更新", "取消", IsWarn:=True) = 1 Then
  418. Setup.Set("HintUpdateMod", True)
  419. Else
  420. Exit Sub
  421. End If
  422. End If
  423. Try
  424. '构造下载信息
  425. ModList = ModList.ToList() '防止刷新影响迭代器
  426. Dim FileList As New List(Of NetFile)
  427. Dim FileCopyList As New Dictionary(Of String, String)
  428. For Each Entry As McMod In ModList
  429. Dim File As CompFile = Entry.UpdateFile
  430. If Not File.Available Then Continue For
  431. '确认更新后的文件名
  432. Dim CurrentReplaceName = Entry.CompFile.FileName.Replace(".jar", "").Replace(".old", "").Replace(".disabled", "")
  433. Dim NewestReplaceName = Entry.UpdateFile.FileName.Replace(".jar", "").Replace(".old", "").Replace(".disabled", "")
  434. Dim CurrentSegs = CurrentReplaceName.Split("-"c).ToList()
  435. Dim NewestSegs = NewestReplaceName.Split("-"c).ToList()
  436. Dim Shortened As Boolean = False
  437. For Each Seg In CurrentSegs.ToList()
  438. If Not NewestSegs.Contains(Seg) Then Continue For
  439. CurrentSegs.Remove(Seg)
  440. NewestSegs.Remove(Seg)
  441. Shortened = True
  442. Next
  443. If Shortened AndAlso CurrentSegs.Any() AndAlso NewestSegs.Any() Then
  444. CurrentReplaceName = Join(CurrentSegs, "-")
  445. NewestReplaceName = Join(NewestSegs, "-")
  446. End If
  447. '添加到下载列表
  448. Dim TempAddress As String = PathTemp & "DownloadedMods\" & Entry.FileName.Replace(CurrentReplaceName, NewestReplaceName)
  449. Dim RealAddress As String = GetPathFromFullPath(Entry.Path) & Entry.FileName.Replace(CurrentReplaceName, NewestReplaceName)
  450. FileList.Add(File.ToNetFile(TempAddress))
  451. FileCopyList(TempAddress) = RealAddress
  452. Next
  453. '构造加载器
  454. Dim InstallLoaders As New List(Of LoaderBase)
  455. Dim FinishedFileNames As New List(Of String)
  456. InstallLoaders.Add(New LoaderDownload("下载新版 Mod 文件", FileList) With {.ProgressWeight = ModList.Count * 1.5}) '每个 Mod 需要 1.5s
  457. InstallLoaders.Add(New LoaderTask(Of Integer, Integer)("替换旧版 Mod 文件",
  458. Sub()
  459. Try
  460. For Each Entry As McMod In ModList
  461. If File.Exists(Entry.Path) Then
  462. My.Computer.FileSystem.DeleteFile(Entry.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
  463. Else
  464. Log($"[Mod] 未找到更新前的 Mod 文件,跳过对它的删除:{Entry.Path}", LogLevel.Debug)
  465. End If
  466. Next
  467. For Each Entry As KeyValuePair(Of String, String) In FileCopyList
  468. If File.Exists(Entry.Value) Then
  469. My.Computer.FileSystem.DeleteFile(Entry.Value, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
  470. Log($"[Mod] 更新后的 Mod 文件已存在,将会把它放入回收站:{Entry.Value}", LogLevel.Debug)
  471. End If
  472. If Directory.Exists(GetPathFromFullPath(Entry.Value)) Then
  473. File.Move(Entry.Key, Entry.Value)
  474. FinishedFileNames.Add(GetFileNameFromPath(Entry.Value))
  475. Else
  476. Log($"[Mod] 更新后的目标文件夹已被删除:{Entry.Value}", LogLevel.Debug)
  477. End If
  478. Next
  479. Catch ex As OperationCanceledException
  480. Log(ex, "替换旧版 Mod 文件时被主动取消")
  481. End Try
  482. End Sub))
  483. '结束处理
  484. Dim Loader As New LoaderCombo(Of IEnumerable(Of McMod))("Mod 更新:" & PageVersionLeft.Version.Name, InstallLoaders)
  485. Dim PathMods As String = PageVersionLeft.Version.PathIndie & "mods\"
  486. Loader.OnStateChanged =
  487. Sub()
  488. '结果提示
  489. Select Case Loader.State
  490. Case LoadState.Finished
  491. Hint(If(FinishedFileNames.Count > 1, $"已成功更新 {FinishedFileNames.Count} 个 Mod!", $"已成功更新:{FinishedFileNames.Single}"), HintType.Finish)
  492. Case LoadState.Failed
  493. Hint("Mod 更新失败:" & GetExceptionSummary(Loader.Error), HintType.Critical)
  494. Case LoadState.Aborted
  495. Hint("Mod 更新已中止!", HintType.Info)
  496. Case Else
  497. Exit Sub
  498. End Select
  499. UpdatingVersions.Remove(PathMods)
  500. '清理缓存
  501. RunInNewThread(
  502. Sub()
  503. Try
  504. For Each TempFile In FileCopyList.Keys
  505. If File.Exists(TempFile) Then File.Delete(TempFile)
  506. Next
  507. Catch ex As Exception
  508. Log(ex, "清理 Mod 更新缓存失败")
  509. End Try
  510. End Sub, "Clean Mod Update Cache", ThreadPriority.BelowNormal)
  511. End Sub
  512. '启动加载器
  513. Log($"[Mod] 开始更新 {ModList.Count} 个 Mod")
  514. UpdatingVersions.Add(PathMods)
  515. Loader.Start()
  516. LoaderTaskbarAdd(Loader)
  517. FrmMain.BtnExtraDownload.ShowRefresh()
  518. FrmMain.BtnExtraDownload.Ribble()
  519. RefreshList(True)
  520. Catch ex As Exception
  521. Log(ex, "初始化 Mod 更新失败")
  522. End Try
  523. End Sub
  524. '删除
  525. Private Sub BtnSelectDelete_Click() Handles BtnSelectDelete.Click
  526. DeleteMods(McModLoader.Output.Where(Function(m) SelectedMods.Contains(m.RawFileName)))
  527. End Sub
  528. Private Sub DeleteMods(ModList As IEnumerable(Of McMod))
  529. Try
  530. Dim IsSuccessful As Boolean = True
  531. Dim IsShiftPressed As Boolean = My.Computer.Keyboard.ShiftKeyDown
  532. '确认需要删除的文件
  533. ModList = ModList.SelectMany(
  534. Function(Target As McMod)
  535. If Target.State = McMod.McModState.Fine Then
  536. Return {Target.Path, Target.Path & If(File.Exists(Target.Path & ".old"), ".old", ".disabled")}
  537. Else
  538. Return {Target.Path, Target.RawPath}
  539. End If
  540. End Function).Distinct.Where(Function(m) File.Exists(m)).Select(Function(m) New McMod(m)).ToList()
  541. '实际删除文件
  542. For Each ModEntity In ModList
  543. '删除
  544. Try
  545. If IsShiftPressed Then
  546. File.Delete(ModEntity.Path)
  547. Else
  548. My.Computer.FileSystem.DeleteFile(ModEntity.Path, FileIO.UIOption.OnlyErrorDialogs, FileIO.RecycleOption.SendToRecycleBin)
  549. End If
  550. Catch ex As OperationCanceledException
  551. Log(ex, "删除 Mod 被主动取消")
  552. RefreshList(True)
  553. Return
  554. Catch ex As Exception
  555. Log(ex, $"删除 Mod 失败({ModEntity.Path})", LogLevel.Msgbox)
  556. IsSuccessful = False
  557. End Try
  558. '取消选中
  559. SelectedMods.Remove(ModEntity.RawFileName)
  560. '更改 Loader 和 UI 中的列表
  561. McModLoader.Output.Remove(ModEntity)
  562. ModItems.Remove(ModEntity.RawFileName)
  563. Dim IndexOfUi As Integer = PanDefaultList.Children.IndexOf(PanDefaultList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry.Equals(ModEntity)))
  564. If IndexOfUi >= 0 Then PanDefaultList.Children.RemoveAt(IndexOfUi)
  565. IndexOfUi = PanUpdateList.Children.IndexOf(PanUpdateList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry.Equals(ModEntity)))
  566. If IndexOfUi >= 0 Then PanUpdateList.Children.RemoveAt(IndexOfUi)
  567. IndexOfUi = PanDisabledList.Children.IndexOf(PanDisabledList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry.Equals(ModEntity)))
  568. If IndexOfUi >= 0 Then PanDisabledList.Children.RemoveAt(IndexOfUi)
  569. IndexOfUi = PanUnavaliableList.Children.IndexOf(PanUnavaliableList.Children.OfType(Of MyLocalModItem).FirstOrDefault(Function(i) i.Entry.Equals(ModEntity)))
  570. If IndexOfUi >= 0 Then PanUnavaliableList.Children.RemoveAt(IndexOfUi)
  571. Next
  572. RefreshTitle()
  573. If Not IsSuccessful Then
  574. Hint("由于文件被占用,Mod 删除失败,请尝试关闭正在运行的游戏后再试!", HintType.Critical)
  575. RefreshList(True)
  576. ElseIf PanDefaultList.Children.Count + PanUpdateList.Children.Count + PanDisabledList.Children.Count + PanUnavaliableList.Children.Count = 0 Then
  577. RefreshList(True) '删除了全部文件
  578. Else
  579. RefreshBottomBar()
  580. End If
  581. '显示结果提示
  582. If Not IsSuccessful Then Exit Sub
  583. If IsShiftPressed Then
  584. If ModList.Count = 1 Then
  585. Hint($"已彻底删除 {ModList.Single.FileName}!", HintType.Finish)
  586. Else
  587. Hint($"已彻底删除 {ModList.Count} 个文件!", HintType.Finish)
  588. End If
  589. Else
  590. If ModList.Count = 1 Then
  591. Hint($"已将 {ModList.Single.FileName} 删除到回收站!", HintType.Finish)
  592. Else
  593. Hint($"已将 {ModList.Count} 个文件删除到回收站!", HintType.Finish)
  594. End If
  595. End If
  596. Catch ex As OperationCanceledException
  597. Log(ex, "删除 Mod 被主动取消")
  598. RefreshList(True)
  599. Catch ex As Exception
  600. Log(ex, "删除 Mod 出现未知错误", LogLevel.Feedback)
  601. RefreshList(True)
  602. End Try
  603. End Sub
  604. '取消选择
  605. Private Sub BtnSelectCancel_Click() Handles BtnSelectCancel.Click
  606. ChangeAllSelected(False)
  607. End Sub
  608. #End Region
  609. #Region "单个 Mod 项"
  610. '详情
  611. Public Sub Info_Click(sender As Object, e As EventArgs)
  612. Try
  613. Dim ModEntry As McMod = CType(If(TypeOf sender Is MyIconButton, sender.Tag, sender), MyLocalModItem).Entry
  614. '加载失败信息
  615. If ModEntry.State = McMod.McModState.Unavaliable Then
  616. MyMsgBox("无法读取此 Mod 的信息。" & vbCrLf & vbCrLf & "详细的错误信息:" & GetExceptionDetail(ModEntry.FileUnavailableReason), "Mod 读取失败")
  617. Return
  618. End If
  619. If ModEntry.Comp IsNot Nothing Then
  620. '跳转到 Mod 下载页面
  621. FrmMain.PageChange(New FormMain.PageStackData With {.Page = FormMain.PageType.CompDetail,
  622. .Additional = {ModEntry.Comp, New List(Of String), PageVersionLeft.Version.Version.McName,
  623. If(PageVersionLeft.Version.Version.HasForge, CompModLoaderType.Forge,
  624. If(PageVersionLeft.Version.Version.HasNeoForge, CompModLoaderType.NeoForge,
  625. If(PageVersionLeft.Version.Version.HasFabric, CompModLoaderType.Fabric, CompModLoaderType.Any)))}})
  626. Else
  627. '获取信息
  628. Dim ContentLines As New List(Of String)
  629. If ModEntry.Description IsNot Nothing Then ContentLines.Add(ModEntry.Description & vbCrLf)
  630. If ModEntry.Authors IsNot Nothing Then ContentLines.Add("作者:" & ModEntry.Authors)
  631. ContentLines.Add("文件:" & ModEntry.FileName & "(" & GetString(New FileInfo(ModEntry.Path).Length) & ")")
  632. If ModEntry.Version IsNot Nothing Then ContentLines.Add("版本:" & ModEntry.Version)
  633. Dim DebugInfo As New List(Of String)
  634. If ModEntry.ModId IsNot Nothing Then
  635. DebugInfo.Add("Mod ID:" & ModEntry.ModId)
  636. End If
  637. If ModEntry.Dependencies.Any Then
  638. DebugInfo.Add("依赖于:")
  639. For Each Dep In ModEntry.Dependencies
  640. DebugInfo.Add(" - " & Dep.Key & If(Dep.Value Is Nothing, "", ",版本:" & Dep.Value))
  641. Next
  642. End If
  643. If DebugInfo.Any Then
  644. ContentLines.Add("")
  645. ContentLines.AddRange(DebugInfo)
  646. End If
  647. '获取用于搜索的 Mod 名称
  648. Dim ModOriginalName As String = ModEntry.Name.Replace(" ", "+")
  649. Dim ModSearchName As String = ModOriginalName.Substring(0, 1)
  650. For i = 1 To ModOriginalName.Count - 1
  651. Dim IsLastLower As Boolean = ModOriginalName(i - 1).ToString.ToLower.Equals(ModOriginalName(i - 1).ToString)
  652. Dim IsCurrentLower As Boolean = ModOriginalName(i).ToString.ToLower.Equals(ModOriginalName(i).ToString)
  653. If IsLastLower AndAlso Not IsCurrentLower Then
  654. '上一个字母为小写,这一个字母为大写
  655. ModSearchName += "+"
  656. End If
  657. ModSearchName += ModOriginalName(i)
  658. Next
  659. ModSearchName = ModSearchName.Replace("++", "+").Replace("pti+Fine", "ptiFine")
  660. '显示
  661. If ModEntry.Url Is Nothing Then
  662. If MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "百科搜索", "返回") = 1 Then
  663. OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0")
  664. End If
  665. Else
  666. Select Case MyMsgBox(Join(ContentLines, vbCrLf), ModEntry.Name, "打开官网", "百科搜索", "返回")
  667. Case 1
  668. OpenWebsite(ModEntry.Url)
  669. Case 2
  670. OpenWebsite("https://www.mcmod.cn/s?key=" & ModSearchName & "&site=all&filter=0")
  671. End Select
  672. End If
  673. End If
  674. Catch ex As Exception
  675. Log(ex, "获取 Mod 详情失败", LogLevel.Feedback)
  676. End Try
  677. End Sub
  678. '打开文件所在的位置
  679. Public Sub Open_Click(sender As MyIconButton, e As EventArgs)
  680. Try
  681. Dim ListItem As MyLocalModItem = sender.Tag
  682. OpenExplorer("/select,""" & ListItem.Entry.Path & """")
  683. Catch ex As Exception
  684. Log(ex, "打开 Mod 文件位置失败", LogLevel.Feedback)
  685. End Try
  686. End Sub
  687. '删除
  688. Public Sub Delete_Click(sender As MyIconButton, e As EventArgs)
  689. Dim ListItem As MyLocalModItem = sender.Tag
  690. DeleteMods({ListItem.Entry})
  691. End Sub
  692. '启用 / 禁用
  693. Public Sub ED_Click(sender As MyIconButton, e As EventArgs)
  694. Dim ListItem As MyLocalModItem = sender.Tag
  695. EDMods({ListItem.Entry}, ListItem.Entry.State = McMod.McModState.Disabled)
  696. End Sub
  697. #End Region
  698. #Region "搜索"
  699. Public ReadOnly Property IsSearching As Boolean
  700. Get
  701. Return Not String.IsNullOrWhiteSpace(SearchBox.Text)
  702. End Get
  703. End Property
  704. Public Sub SearchRun() Handles SearchBox.TextChanged
  705. ChangeAllSelected(False)
  706. If IsSearching Then
  707. '构造请求
  708. Dim QueryList As New List(Of SearchEntry(Of McMod))
  709. For Each Entry As McMod In McModLoader.Output
  710. Dim SearchSource As New List(Of KeyValuePair(Of String, Double))
  711. SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Name, 1))
  712. SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.FileName, 1))
  713. If Entry.Version IsNot Nothing Then
  714. SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Version, 0.2))
  715. End If
  716. If Entry.Description IsNot Nothing AndAlso Entry.Description <> "" Then
  717. SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Description, 0.4))
  718. End If
  719. If Entry.Comp IsNot Nothing Then
  720. If Entry.Comp.RawName <> Entry.Name Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.RawName, 1))
  721. If Entry.Comp.TranslatedName <> Entry.Comp.RawName Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.TranslatedName, 1))
  722. If Entry.Comp.Description <> Entry.Description Then SearchSource.Add(New KeyValuePair(Of String, Double)(Entry.Comp.Description, 0.4))
  723. SearchSource.Add(New KeyValuePair(Of String, Double)(String.Join("", Entry.Comp.Tags), 0.2))
  724. End If
  725. QueryList.Add(New SearchEntry(Of McMod) With {.Item = Entry, .SearchSource = SearchSource})
  726. Next
  727. '进行搜索
  728. Dim SearchResult = Search(QueryList, SearchBox.Text, MaxBlurCount:=6, MinBlurSimilarity:=0.35)
  729. RefreshResult(SearchResult.Select(Function(r) r.Item).ToList)
  730. Else
  731. '退出搜索状态
  732. RefreshResult(McModLoader.Output)
  733. End If
  734. End Sub
  735. #End Region
  736. End Class