PageVersionMod.xaml.vb 36 KB

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