PageVersionMod.xaml.vb 40 KB

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