PageVersionMod.xaml.vb 37 KB

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