PageVersionMod.xaml.vb 39 KB

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