Application.xaml.vb 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. Imports System.Reflection
  2. Imports System.Windows.Threading
  3. Imports Microsoft.Win32
  4. Public Class Application
  5. '开始
  6. Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
  7. Try
  8. '提升主线程优先级
  9. Thread.CurrentThread.Priority = ThreadPriority.Highest
  10. '执行开发版测试
  11. If BuildType = BuildTypes.Debug Then
  12. Try
  13. ModDevelop.Start()
  14. Catch ex As Exception
  15. Logger.Error(ex, "开发者模式测试出错", LogBehavior.Alert)
  16. End Try
  17. End If
  18. '检查 .NET Framework 版本
  19. Try
  20. Using ndpKey As RegistryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\")
  21. If ndpKey?.GetValue("Release") IsNot Nothing AndAlso CInt(ndpKey.GetValue("Release")) < 528040 Then OldEnvironmentAssert()
  22. End Using
  23. Catch ex As Exception
  24. Logger.Warn(ex, "检查 .NET Framework 版本失败")
  25. End Try
  26. '修复 WPF 字体加载(#3467)
  27. Try
  28. Dim _unused As New FormattedText("", Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Fonts.SystemTypefaces.First, 96, New MyColor, DPI)
  29. Catch ex As UriFormatException
  30. Environment.SetEnvironmentVariable("windir", Environment.GetEnvironmentVariable("SystemRoot"), EnvironmentVariableTarget.User)
  31. Dim _unused As New FormattedText("", Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, Fonts.SystemTypefaces.First, 96, New MyColor, DPI)
  32. End Try
  33. '检查参数调用
  34. If e.Args.Length > 0 Then
  35. If e.Args(0) = "--update" Then
  36. '自动更新
  37. UpdateReplace(e.Args(1), e.Args(2).Trim(""""), e.Args(3).Trim(""""), e.Args(4))
  38. Environment.Exit(ProcessReturnValues.TaskDone)
  39. ElseIf e.Args(0) = "--gpu" Then
  40. '调整显卡设置
  41. Try
  42. SetGPUPreference(e.Args(1).Trim(""""))
  43. Environment.Exit(ProcessReturnValues.TaskDone)
  44. Catch ex As Exception
  45. Environment.Exit(ProcessReturnValues.Fail)
  46. End Try
  47. ElseIf e.Args(0).StartsWithF("--memory") Then
  48. '内存优化
  49. Dim Ram = My.Computer.Info.AvailablePhysicalMemory
  50. Try
  51. PageOtherTest.MemoryOptimizeInternal(False)
  52. Catch ex As Exception
  53. MsgBox(ex.Message, MsgBoxStyle.Critical, "内存优化失败")
  54. Environment.Exit(-1)
  55. End Try
  56. If My.Computer.Info.AvailablePhysicalMemory < Ram Then '避免 ULong 相减出现负数
  57. Environment.Exit(0)
  58. Else
  59. Environment.Exit((My.Computer.Info.AvailablePhysicalMemory - Ram) / 1024) '返回清理的内存量(K)
  60. End If
  61. End If
  62. End If
  63. '初始化文件结构
  64. Try
  65. DirectoryUtils.Create(PathExeFolder & "PCL\Pictures\")
  66. DirectoryUtils.Create(PathExeFolder & "PCL\Musics\")
  67. CheckPermissionWithException(PathExeFolder & "PCL\")
  68. Catch ex As Exception
  69. MsgBox($"PCL 没有对当前文件夹的权限({PathExeFolder}PCL\),请尝试:" & vbCrLf &
  70. "1. 将 PCL 移动到其他文件夹" & If(PathExeFolder.StartsWithF("C:", True), ",例如 C 盘和桌面以外的其他位置。", "。") & vbCrLf &
  71. "2. 删除当前目录中的 PCL 文件夹,然后再试。" & vbCrLf &
  72. "3. 右键 PCL 选择属性,打开 兼容性 中的 以管理员身份运行此程序。",
  73. MsgBoxStyle.Critical, "运行环境错误")
  74. Environment.[Exit](ProcessReturnValues.Cancel)
  75. End Try
  76. RetryCacheCheck:
  77. Try
  78. DirectoryUtils.Create(PathTemp)
  79. CheckPermissionWithException(PathTemp)
  80. Catch ex As Exception
  81. If PathTemp = Path.GetTempPath() & "PCL\" Then
  82. MyMsgBox("PCL 无法访问缓存文件夹,可能导致程序出错或无法正常使用!" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用")
  83. Else
  84. MyMsgBox("手动设置的缓存文件夹不可用,PCL 将使用默认缓存文件夹。" & vbCrLf & vbCrLf & "错误原因:" & ex.GetDisplay(True), "缓存文件夹不可用")
  85. Settings.Set("SystemSystemCache", "")
  86. PathTemp = Path.GetTempPath() & "PCL\"
  87. GoTo RetryCacheCheck
  88. End If
  89. End Try
  90. DirectoryUtils.Create(PathTemp & "Cache\")
  91. DirectoryUtils.Create(PathAppdata)
  92. #If DEBUG Then
  93. Dim FileLockPath = Path.Combine(PathAppdata.TrimEnd("\"), "PCL.dev.Lock")
  94. #Else
  95. Dim FileLockPath = Path.Combine(PathAppdata.TrimEnd("\"), "PCL.Lock")
  96. #End If
  97. '要求单例
  98. If BuildType <> BuildTypes.Debug Then
  99. If Not GetProgramLock(FileLockPath) Then
  100. Dim IsLocked = False
  101. Dim ShouldWaitForExit As Boolean = e.Args.Length > 0 AndAlso e.Args(0) = "--wait" '要求等待已有的 PCL 退出
  102. If Not ShouldWaitForExit Then
  103. DropToTopByLock(FileLockPath)
  104. Beep()
  105. Environment.Exit(ProcessReturnValues.Cancel)
  106. End If
  107. For i = 0 To 10
  108. If GetProgramLock(FileLockPath) Then
  109. IsLocked = True
  110. Exit For
  111. End If
  112. Thread.Sleep(500)
  113. Next
  114. If Not IsLocked Then
  115. DropToTopByLock(FileLockPath)
  116. DropToTopByLock(FileLockPath)
  117. Beep()
  118. Environment.Exit(ProcessReturnValues.Cancel)
  119. End If
  120. End If
  121. End If
  122. '设置 ToolTipService 默认值
  123. ToolTipService.InitialShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(300))
  124. ToolTipService.BetweenShowDelayProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(400))
  125. ToolTipService.ShowDurationProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(9999999))
  126. ToolTipService.PlacementProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(Primitives.PlacementMode.Bottom))
  127. ToolTipService.HorizontalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(8.0))
  128. ToolTipService.VerticalOffsetProperty.OverrideMetadata(GetType(DependencyObject), New FrameworkPropertyMetadata(4.0))
  129. '设置网络配置默认值
  130. ServicePointManager.Expect100Continue = False
  131. ServicePointManager.DefaultConnectionLimit = 10000
  132. ServicePointManager.UseNagleAlgorithm = False
  133. ServicePointManager.EnableDnsRoundRobin = True
  134. ServicePointManager.ReusePort = True
  135. '设置初始窗口
  136. If Settings.Get(Of Boolean)("UiLauncherLogo") Then
  137. FrmStart = New SplashScreen("Images\icon.ico")
  138. FrmStart.Show(False, True)
  139. End If
  140. '日志初始化
  141. MeloongCore.Main.Init(New PclLogger With {.logFolder = PathUtils.CurrentFolder & "PCL", .MinLevel = If(ModeDebug, LogLevel.Trace, LogLevel.Info)})
  142. Logger.Info($"程序版本:{VersionDisplay} ({VersionCode}{If(CommitHash = "", "", $",#{CommitHash}")})")
  143. If BuildType = BuildTypes.Snapshot Then
  144. Logger.Info($"识别码:{Identify}{If(ThemeCheckOne(9), ",已解锁反馈主题", ",未解锁反馈主题")}")
  145. Else
  146. Logger.Info($"识别码:{Identify}")
  147. End If
  148. Logger.Info($"程序路径:{PathExe}")
  149. Logger.Info($"系统编码:{Encoding.Default.HeaderName} ({Encoding.Default.CodePage}, GBK={IsGBKEncoding})")
  150. Logger.Info($"管理员权限:{SystemUtils.HasAdminRole()}")
  151. '检测异常环境
  152. If PathExeFolder.Contains(Path.GetTempPath()) OrElse PathExeFolder.Contains("AppData\Local\Temp\") Then
  153. MyMsgBox("请将 PCL 从压缩包中解压后再使用!" & vbCrLf & "如果不会解压,可以在网上寻找教程。", "需要解压!", "我知道了", IsWarn:=True, ForceWait:=True)
  154. FormMain.EndProgramForce(ProcessReturnValues.Cancel)
  155. End If
  156. If Is32BitSystem Then
  157. MyMsgBox("PCL 和新版 Minecraft 均不再支持 32 位系统,部分功能将无法使用。" & vbCrLf & "非常建议重装为 64 位系统后再进行游戏!", "环境警告", "我知道了", IsWarn:=True)
  158. End If
  159. '计时
  160. Logger.Info($"第一阶段加载用时:{GetTimeMs() - ApplicationStartTick} ms")
  161. ApplicationStartTick = GetTimeMs()
  162. AniControlEnabled += 1
  163. Catch ex As Exception
  164. Dim FilePath As String = Nothing
  165. Try
  166. FilePath = PathExe
  167. Catch
  168. End Try
  169. MsgBox(ex.GetDisplay(True) & vbCrLf & "PCL 所在路径:" & If(String.IsNullOrEmpty(FilePath), "获取失败", FilePath), MsgBoxStyle.Critical, "PCL 初始化错误")
  170. FormMain.EndProgramForce(ProcessReturnValues.Exception)
  171. End Try
  172. End Sub
  173. Private Shared KernelLock As Mutex
  174. ''' <summary>
  175. ''' 尝试获取单例锁
  176. ''' </summary>
  177. ''' <param name="LockPath">文件锁位置</param>
  178. ''' <returns>一个值用于指示是否获得文件锁</returns>
  179. Private Function GetProgramLock(LockPath As String) As Boolean
  180. Try
  181. Dim IsLocked = False
  182. KernelLock = New Mutex(True, "Plain Craft Launcher", IsLocked)
  183. If Not IsLocked Then Return False
  184. Logger.Info("获取单例锁成功")
  185. File.WriteAllText(LockPath, Process.GetCurrentProcess().Id.ToString())
  186. Return IsLocked
  187. Catch ex As Exception
  188. End Try
  189. Return False
  190. End Function
  191. ''' <summary>
  192. ''' 从文件锁中尝试拖出进程
  193. ''' </summary>
  194. Public Sub DropToTopByLock(LockPath As String)
  195. Dim Content = File.ReadAllBytes(LockPath)
  196. Dim Pid As Integer
  197. If Not Integer.TryParse(Encoding.UTF8.GetString(Content), Pid) Then Return
  198. ShowWindowToTop(Process.GetProcessById(Pid).MainWindowHandle)
  199. End Sub
  200. '结束
  201. Private Sub Application_SessionEnding(sender As Object, e As SessionEndingCancelEventArgs) Handles Me.SessionEnding
  202. FrmMain?.EndProgram(False)
  203. End Sub
  204. '异常
  205. Private Sub Application_DispatcherUnhandledException(sender As Object, e As DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException
  206. On Error Resume Next
  207. '触发页面的 Dispatcher
  208. If FrmMain?.PageLeft IsNot Nothing AndAlso TypeOf FrmMain.PageLeft Is IDispatcherUnhandledException Then
  209. CType(FrmMain.PageLeft, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
  210. If e.Handled Then Return
  211. End If
  212. If FrmMain?.PageRight IsNot Nothing AndAlso TypeOf FrmMain.PageRight Is IDispatcherUnhandledException Then
  213. CType(FrmMain.PageRight, IDispatcherUnhandledException).DispatcherUnhandledException(sender, e)
  214. If e.Handled Then Return
  215. End If
  216. '正常处理
  217. e.Handled = True
  218. If IsProgramEnding Then Return
  219. FeedbackInfo()
  220. Dim Detail As String = e.Exception.GetDisplay(True)
  221. If Detail.Contains("System.Windows.Threading.Dispatcher.Invoke") OrElse Detail.Contains("MS.Internal.AppModel.ITaskbarList.HrInit") OrElse Detail.Contains("未能加载文件或程序集") OrElse
  222. Detail.Contains(".NET Framework") Then ' “自动错误判断” 的结果分析
  223. OldEnvironmentAssert(e.Exception)
  224. Else
  225. Logger.Error(e.Exception, "程序出现未知错误", LogBehavior.AlertThenCrash)
  226. End If
  227. End Sub
  228. Private Shared Sub OldEnvironmentAssert(Optional ex As Exception = Nothing)
  229. OpenWebsite("https://dotnet.microsoft.com/zh-cn/download/dotnet-framework/thank-you/net48-offline-installer")
  230. If ex Is Nothing Then
  231. Logger.Error($"你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.8!{vbCrLf}若安装失败,可以先卸载高版本的 .NET Framework 然后再试。", LogBehavior.AlertThenCrash)
  232. Else
  233. Logger.Error(ex, $"你的 .NET Framework 版本过低或损坏,请下载并重新安装 .NET Framework 4.8!{vbCrLf}若安装失败,可以先卸载高版本的 .NET Framework 然后再试。", LogBehavior.AlertThenCrash)
  234. End If
  235. End Sub
  236. '动态 DLL 加载
  237. Private Sub New() '这里必须尽早调用,且不能使用任何库,否则加载 MeloongCore 就会导致崩溃
  238. Static Prefixes As String() = {"NAudio", "Newtonsoft.Json", "Ookii.Dialogs.Wpf", "Imazen.WebP", "CacheCow.Common", "CacheCow.Client.FileStore", "CacheCow.Client", "System.Net.Http.Formatting", "PCLCS", "MeloongCore.Wpf", "MeloongCore"}
  239. Static LoadedAssemblies As New ConcurrentDictionary(Of String, Lazy(Of Assembly))(StringComparer.Ordinal) '缓存
  240. AddHandler AppDomain.CurrentDomain.AssemblyResolve,
  241. Function(sender As Object, Args As ResolveEventArgs) As Assembly
  242. Dim Prefix As String = Prefixes.FirstOrDefault(Function(p) Args.Name.StartsWith(p, StringComparison.Ordinal))
  243. If Prefix Is Nothing Then Return Nothing
  244. Dim LazyAssembly = LoadedAssemblies.GetOrAdd(Prefix, Function(p) New Lazy(Of Assembly)(
  245. Function()
  246. Dim LoadedAssembly = Assembly.Load(DirectCast(My.Resources.ResourceManager.GetObject(p), Byte()))
  247. If p = "Imazen.WebP" Then ExtractLibwebp() 'WebP 特判
  248. Return LoadedAssembly
  249. End Function, LazyThreadSafetyMode.ExecutionAndPublication))
  250. Return LazyAssembly.Value
  251. End Function
  252. End Sub
  253. Private Shared Sub ExtractLibwebp() '这个方法会调用 ModBase,进而调用 MeloongCore,所以不能放在 New 里
  254. SetDllDirectory(PathPure.TrimEnd("\"c))
  255. Try
  256. ExtractResources(PathPure & "libwebp.dll", "libwebp64")
  257. Catch ex As Exception
  258. Logger.Warn(ex, "写入 libwebp.dll 失败") '防止同时加载多个图片时,同时写入文件导致文件占用,进而导致崩溃
  259. End Try
  260. End Sub
  261. Private Declare Function SetDllDirectory Lib "kernel32" Alias "SetDllDirectoryA" (lpPathName As String) As Boolean
  262. '切换窗口
  263. '控件模板事件
  264. Private Sub MyIconButton_Click(sender As Object, e As EventArgs)
  265. Select Case Settings.Get(Of McLoginType)("LoginType")
  266. Case McLoginType.Ms
  267. '微软
  268. Dim MsJson As JObject = GetJson(Settings.Get(Of String)("LoginMsJson"))
  269. MsJson.Remove(sender.Tag)
  270. Settings.Set("LoginMsJson", MsJson.ToString(Newtonsoft.Json.Formatting.None))
  271. If FrmLoginMs.ComboAccounts.SelectedItem Is sender.Parent Then FrmLoginMs.ComboAccounts.SelectedIndex = 0
  272. FrmLoginMs.ComboAccounts.Items.Remove(sender.Parent)
  273. Case McLoginType.Legacy
  274. '离线
  275. Dim Names As New List(Of String)
  276. Names.AddRange(Settings.Get(Of String)("LoginLegacyName").ToString.Split("¨"))
  277. Names.Remove(sender.Tag)
  278. Settings.Set("LoginLegacyName", Names.Join("¨"c))
  279. FrmLoginLegacy.ComboName.ItemsSource = Names
  280. FrmLoginLegacy.ComboName.Text = If(Names.Any, Names(0), "")
  281. Case Else
  282. '第三方
  283. Dim Token As String = Settings.Get(Of McLoginType)("LoginType").ToString()
  284. Dim Dict As New Dictionary(Of String, String)
  285. Dim Names As New List(Of String)
  286. Dim Passs As New List(Of String)
  287. If Not Settings.Get(Of String)("Login" & Token & "Email") = "" Then Names.AddRange(Settings.Get(Of String)("Login" & Token & "Email").Split("¨"))
  288. If Not Settings.Get(Of String)("Login" & Token & "Pass") = "" Then Passs.AddRange(Settings.Get(Of String)("Login" & Token & "Pass").Split("¨"))
  289. For i = 0 To Names.Count - 1
  290. Dict.Add(Names(i), Passs(i))
  291. Next
  292. Dict.Remove(sender.Tag)
  293. Settings.Set("Login" & Token & "Email", Dict.Keys.Join("¨"c))
  294. Settings.Set("Login" & Token & "Pass", Dict.Values.Join("¨"c))
  295. Select Case Token
  296. Case "Nide"
  297. FrmLoginNide.ComboName.ItemsSource = Dict.Keys
  298. FrmLoginNide.ComboName.Text = If(Dict.Keys.Any, Dict.Keys(0), "")
  299. FrmLoginNide.TextPass.Password = If(Dict.Values.Any, Dict.Values(0), "")
  300. Case "Auth"
  301. FrmLoginAuth.ComboName.ItemsSource = Dict.Keys
  302. FrmLoginAuth.ComboName.Text = If(Dict.Keys.Any, Dict.Keys(0), "")
  303. FrmLoginAuth.TextPass.Password = If(Dict.Values.Any, Dict.Values(0), "")
  304. End Select
  305. End Select
  306. End Sub
  307. Public Shared ShowingTooltips As New List(Of Border)
  308. Private Sub TooltipLoaded(sender As Border, e As EventArgs)
  309. ShowingTooltips.Add(sender)
  310. End Sub
  311. Private Sub TooltipUnloaded(sender As Border, e As RoutedEventArgs)
  312. ShowingTooltips.Remove(sender)
  313. End Sub
  314. End Class