Application.xaml.vb 17 KB

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