PageVersionMod.xaml.vb 37 KB

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