PageVersionMod.xaml.vb 37 KB

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