PageVersionMod.xaml.vb 37 KB

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