ModCrash.vb 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931
  1. Public Class CrashAnalyzer
  2. Private Shared IsAnalyzeCacheCleared As Boolean = False
  3. Private Shared IsAnalyzeCacheClearedLock As New Object
  4. '构造函数
  5. Private TempFolder As String
  6. Public Sub New(UUID As Integer)
  7. '清理缓存
  8. SyncLock IsAnalyzeCacheClearedLock
  9. If Not IsAnalyzeCacheCleared Then
  10. Try
  11. DeleteDirectory(PathTemp & "CrashAnalyzer\")
  12. Catch ex As Exception
  13. Log(ex, "清理崩溃分析缓存失败")
  14. End Try
  15. IsAnalyzeCacheCleared = True
  16. End If
  17. End SyncLock
  18. '构建文件结构
  19. TempFolder = PathTemp & "CrashAnalyzer\" & UUID & RandomInteger(0, 99999999) & "\"
  20. DeleteDirectory(TempFolder)
  21. Directory.CreateDirectory(TempFolder & "Temp\")
  22. Directory.CreateDirectory(TempFolder & "Report\")
  23. Log("[Crash] 崩溃分析暂存文件夹:" & TempFolder)
  24. End Sub
  25. '1:准备用于分析的 Log 文件
  26. Private AnalyzeRawFiles As New List(Of KeyValuePair(Of String, String())) '暂存的日志文件:文件完整路径 -> 文件内容
  27. ''' <summary>
  28. ''' 将可用于分析的日志存储到 AnalyzeRawFiles。
  29. ''' </summary>
  30. ''' <param name="LatestLog">从 PCL 捕获到的最后 200 行程序输出。</param>
  31. Public Sub Collect(VersionPathIndie As String, Optional LatestLog As IList(Of String) = Nothing)
  32. Log("[Crash] 步骤 1:收集日志文件")
  33. '简单收集可能的日志文件路径
  34. Dim PossibleLogs As New List(Of String)
  35. Try
  36. Dim DirInfo As New DirectoryInfo(VersionPathIndie & "crash-reports\")
  37. If DirInfo.Exists Then
  38. For Each File In DirInfo.EnumerateFiles
  39. PossibleLogs.Add(File.FullName)
  40. Next
  41. End If
  42. Catch ex As Exception
  43. Log(ex, "收集 Minecraft 崩溃日志文件夹下的日志失败")
  44. End Try
  45. Try
  46. For Each File In New DirectoryInfo(VersionPathIndie).Parent.Parent.EnumerateFiles
  47. If If(File.Extension, "") <> ".log" Then Continue For
  48. PossibleLogs.Add(File.FullName)
  49. Next
  50. Catch ex As Exception
  51. Log(ex, "收集 Minecraft 主文件夹下的日志失败")
  52. End Try
  53. Try
  54. For Each File In New DirectoryInfo(VersionPathIndie).EnumerateFiles
  55. If If(File.Extension, "") <> ".log" Then Continue For
  56. PossibleLogs.Add(File.FullName)
  57. Next
  58. Catch ex As Exception
  59. Log(ex, "收集 Minecraft 隔离文件夹下的日志失败")
  60. End Try
  61. PossibleLogs.Add(VersionPathIndie & "logs\latest.log") 'Minecraft 日志
  62. PossibleLogs.Add(VersionPathIndie & "logs\debug.log") 'Minecraft Debug 日志
  63. PossibleLogs = PossibleLogs.Distinct.ToList
  64. '确定最新的日志文件
  65. Dim RightLogs As New List(Of String)
  66. For Each LogFile In PossibleLogs
  67. Try
  68. Dim Info As New FileInfo(LogFile)
  69. If Not Info.Exists Then Continue For
  70. Dim Time = Math.Abs((Info.LastWriteTime - Date.Now).TotalMinutes)
  71. If Time < 3 AndAlso Info.Length > 0 Then
  72. RightLogs.Add(LogFile)
  73. Log("[Crash] 可能可用的日志文件:" & LogFile & "(" & Math.Round(Time, 1) & " 分钟)")
  74. End If
  75. Catch ex As Exception
  76. Log(ex, "确认崩溃日志时间失败(" & LogFile & ")")
  77. End Try
  78. Next
  79. If RightLogs.Count = 0 Then Log("[Crash] 未发现可能可用的日志文件")
  80. '将可能可用的日志文件导出
  81. For Each FilePath In RightLogs
  82. Try
  83. If FilePath.Contains("crash-") Then
  84. AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(FilePath, ReadFile(FilePath).Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Split(vbCr)))
  85. Else
  86. AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(FilePath, ReadFile(FilePath, Encoding.UTF8).Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Split(vbCr)))
  87. End If
  88. Catch ex As Exception
  89. Log(ex, "读取可能的崩溃日志文件失败(" & FilePath & ")")
  90. End Try
  91. Next
  92. If LatestLog IsNot Nothing AndAlso LatestLog.Count > 0 Then
  93. Dim RawOutput As String = Join(LatestLog, vbCrLf)
  94. Log("[Crash] 以下为游戏输出的最后一段内容:" & vbCrLf & RawOutput)
  95. WriteFile(TempFolder & "RawOutput.log", RawOutput)
  96. AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TempFolder & "RawOutput.log", LatestLog.ToArray))
  97. LatestLog.Clear()
  98. End If
  99. Log("[Crash] 步骤 1:收集日志文件完成,收集到 " & AnalyzeRawFiles.Count & " 个文件")
  100. End Sub
  101. ''' <summary>
  102. ''' 从文件路径直接导入日志文件或崩溃报告压缩包。
  103. ''' </summary>
  104. Public Sub Import(FilePath As String)
  105. Log("[Crash] 步骤 1:自主导入日志文件")
  106. '解压压缩包
  107. Try
  108. Dim Info As New FileInfo(FilePath)
  109. If Not Info.Exists OrElse Info.Length = 0 Then Exit Try
  110. If Not FilePath.ToLower.EndsWith(".jar") AndAlso ExtractFile(FilePath, TempFolder & "Temp\") Then
  111. '解压成功
  112. Log("[Crash] 已解压导入的日志文件:" & FilePath)
  113. Else
  114. '解压失败
  115. CopyFile(FilePath, TempFolder & "Temp\" & GetFileNameFromPath(FilePath))
  116. Log("[Crash] 已复制导入的日志文件:" & FilePath)
  117. End If
  118. Catch ex As Exception
  119. Log(ex, "解压导入文件中的压缩包失败")
  120. End Try
  121. '导入其中的日志文件
  122. For Each TargetFile As FileInfo In New DirectoryInfo(TempFolder & "Temp\").EnumerateFiles
  123. Try
  124. If Not TargetFile.Exists OrElse TargetFile.Length = 0 Then Continue For
  125. Dim Ext As String = TargetFile.Extension.ToLower
  126. If Ext = ".log" OrElse Ext = ".txt" Then
  127. If TargetFile.Name.StartsWith("crash-") Then
  128. AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName).Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Split(vbCr)))
  129. Else
  130. AnalyzeRawFiles.Add(New KeyValuePair(Of String, String())(TargetFile.FullName, ReadFile(TargetFile.FullName, Encoding.UTF8).Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Split(vbCr)))
  131. End If
  132. End If
  133. Catch ex As Exception
  134. Log(ex, "导入单个日志文件失败")
  135. End Try
  136. Next
  137. Log("[Crash] 步骤 1:自主导入日志文件,收集到 " & AnalyzeRawFiles.Count & " 个文件")
  138. End Sub
  139. '2:确认实际用于分析的 Log 文本
  140. Private Enum AnalyzeFileType
  141. HsErr
  142. MinecraftLog
  143. ExtraLog
  144. CrashReport
  145. End Enum
  146. Private DirectFile As KeyValuePair(Of String, String())? = Nothing '在弹窗中选择直接打开的文件
  147. ''' <summary>
  148. ''' 从 AnalyzeRawFiles 中提取实际有用的文本片段存储到 AnalyzeFiles,并整理可用于生成报告的文件。
  149. ''' 返回找到的用于分析的项目数。
  150. ''' </summary>
  151. Public Function Prepare() As Integer
  152. Log("[Crash] 步骤 2:准备日志文本")
  153. '对日志文件进行分类
  154. DirectFile = Nothing
  155. Dim TotalFiles As New List(Of KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String())))
  156. For Each LogFile In AnalyzeRawFiles
  157. Dim FileName As String = GetFileNameFromPath(LogFile.Key)
  158. Dim TargetType As AnalyzeFileType
  159. If FileName.StartsWith("hs_err") Then
  160. TargetType = AnalyzeFileType.HsErr
  161. DirectFile = LogFile
  162. ElseIf FileName.StartsWith("crash-") Then
  163. TargetType = AnalyzeFileType.CrashReport
  164. DirectFile = LogFile
  165. ElseIf FileName = "latest.log" OrElse FileName = "latest log.txt" OrElse
  166. FileName = "debug.log" OrElse FileName = "debug log.txt" OrElse
  167. FileName = "游戏崩溃前的输出.txt" OrElse FileName = "rawoutput.log" Then
  168. TargetType = AnalyzeFileType.MinecraftLog
  169. If DirectFile Is Nothing Then DirectFile = LogFile
  170. ElseIf FileName = "启动器日志.txt" OrElse FileName = "PCL2 启动器日志.txt" OrElse FileName = "PCL 启动器日志.txt" OrElse FileName = "log1.txt" Then
  171. If LogFile.Value.Any(Function(s) s.Contains("以下为游戏输出的最后一段内容")) Then
  172. TargetType = AnalyzeFileType.MinecraftLog
  173. If DirectFile Is Nothing Then DirectFile = LogFile
  174. Else
  175. TargetType = AnalyzeFileType.ExtraLog
  176. End If
  177. ElseIf FileName.EndsWith(".log") OrElse FileName.EndsWith(".txt") Then
  178. TargetType = AnalyzeFileType.ExtraLog
  179. Else
  180. Log("[Crash] " & FileName & " 分类为 Ignore")
  181. Continue For
  182. End If
  183. If LogFile.Value.Count = 0 Then
  184. Log("[Crash] " & FileName & " 由于内容为空跳过")
  185. Else
  186. TotalFiles.Add(New KeyValuePair(Of AnalyzeFileType, KeyValuePair(Of String, String()))(TargetType, LogFile))
  187. Log("[Crash] " & FileName & " 分类为 " & GetStringFromEnum(TargetType))
  188. End If
  189. Next
  190. '将分类后的文件分别写入
  191. For Each SelectType In {AnalyzeFileType.MinecraftLog, AnalyzeFileType.HsErr, AnalyzeFileType.ExtraLog, AnalyzeFileType.CrashReport}
  192. '获取该种类的所有文件 {文件路径 -> 文件内容行}
  193. Dim SelectedFiles As New List(Of KeyValuePair(Of String, String()))
  194. For Each File In TotalFiles
  195. If SelectType = File.Key Then SelectedFiles.Add(File.Value)
  196. Next
  197. If SelectedFiles.Count = 0 Then Continue For
  198. Try
  199. '根据文件类别判断
  200. Select Case SelectType
  201. Case AnalyzeFileType.HsErr, AnalyzeFileType.CrashReport
  202. '获取文件的修改日期
  203. Dim DatedFiles As New SortedList(Of Date, KeyValuePair(Of String, String()))()
  204. For Each File In SelectedFiles
  205. Try
  206. DatedFiles.Add(New FileInfo(File.Key).LastWriteTime, File)
  207. Catch ex As Exception
  208. Log(ex, "获取日志文件修改时间失败")
  209. DatedFiles.Add(New Date(1900, 1, 1), File)
  210. End Try
  211. Next
  212. '输出最新的文件
  213. Dim NewestFile As KeyValuePair(Of String, String()) = DatedFiles.Last.Value
  214. OutputFiles.Add(NewestFile.Key)
  215. If SelectType = AnalyzeFileType.HsErr Then
  216. LogHs = GetHeadTailLines(NewestFile.Value, 200, 100)
  217. Log("[Crash] 输出报告:" & NewestFile.Key & ",作为虚拟机错误信息")
  218. Log("[Crash] 导入分析:" & NewestFile.Key & ",作为虚拟机错误信息")
  219. Else
  220. LogCrash = GetHeadTailLines(NewestFile.Value, 300, 700)
  221. Log("[Crash] 输出报告:" & NewestFile.Key & ",作为 Minecraft 崩溃报告")
  222. Log("[Crash] 导入分析:" & NewestFile.Key & ",作为 Minecraft 崩溃报告")
  223. End If
  224. Case AnalyzeFileType.MinecraftLog
  225. LogMc = ""
  226. '创建文件名词典
  227. Dim FileNameDict As New Dictionary(Of String, KeyValuePair(Of String, String()))
  228. For Each SelectedFile In SelectedFiles
  229. FileNameDict(GetFileNameFromPath(SelectedFile.Key).ToLower) = SelectedFile
  230. OutputFiles.Add(SelectedFile.Key)
  231. Log("[Crash] 输出报告:" & SelectedFile.Key & ",作为 Minecraft 或启动器日志")
  232. Next
  233. '选择一份最佳的来自启动器的游戏日志
  234. For Each FileName As String In {"rawoutput.log", "启动器日志.txt", "log1.txt", "游戏崩溃前的输出.txt", "PCL2 启动器日志.txt", "PCL 启动器日志.txt"}
  235. If FileNameDict.ContainsKey(FileName) Then
  236. Dim CurrentLog = FileNameDict(FileName)
  237. '截取 “以下为游戏输出的最后一段内容” 后的内容
  238. Dim HasLauncherMark As Boolean = False
  239. For Each Line In CurrentLog.Value
  240. If HasLauncherMark Then
  241. LogMc += Line & vbLf
  242. ElseIf Line.Contains("以下为游戏输出的最后一段内容") Then
  243. HasLauncherMark = True
  244. Log("[Crash] 找到 PCL 输出的游戏实时日志头")
  245. End If
  246. Next
  247. '导入后 500 行
  248. If Not HasLauncherMark Then LogMc += GetHeadTailLines(CurrentLog.Value, 0, 500)
  249. LogMc = LogMc.TrimEnd(vbCrLf.ToCharArray)
  250. Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为启动器日志")
  251. Exit For
  252. End If
  253. Next
  254. '选择一份最佳的 Minecraft Log
  255. For Each FileName As String In {"latest.log", "latest log.txt", "debug.log", "debug log.txt"}
  256. If FileNameDict.ContainsKey(FileName) Then
  257. Dim CurrentLog = FileNameDict(FileName)
  258. LogMc += GetHeadTailLines(CurrentLog.Value, 250, 500)
  259. Log("[Crash] 导入分析:" & CurrentLog.Key & ",作为 Minecraft 日志")
  260. Exit For
  261. End If
  262. Next
  263. '检查错误
  264. If LogMc = "" Then
  265. LogMc = Nothing
  266. Throw New Exception("无法找到匹配的 Minecraft Log")
  267. End If
  268. Case AnalyzeFileType.ExtraLog
  269. '全部丢过去
  270. For Each SelectedFile In SelectedFiles
  271. OutputFiles.Add(SelectedFile.Key)
  272. Log("[Crash] 输出报告:" & SelectedFile.Key & ",作为额外日志")
  273. Next
  274. End Select
  275. Catch ex As Exception
  276. Log(ex, "分类处理日志文件时出错")
  277. End Try
  278. Next
  279. '获取种类数
  280. Dim ResultCount As Integer = If(LogMc Is Nothing, 0, 1) + If(LogHs Is Nothing, 0, 1) + If(LogCrash Is Nothing, 0, 1)
  281. If ResultCount = 0 Then
  282. Log("[Crash] 步骤 2:准备日志文本完成,没有任何可供分析的日志")
  283. Else
  284. Log(("[Crash] 步骤 2:准备日志文本完成,找到" & If(LogMc Is Nothing, "", "游戏日志、") & If(LogHs Is Nothing, "", "虚拟机日志、") & If(LogCrash Is Nothing, "", "崩溃日志、")).TrimEnd("、") & "用作分析")
  285. End If
  286. Return ResultCount
  287. End Function
  288. ''' <summary>
  289. ''' 输出字符串的前后某些行,并统一行尾为 vbLf (\r)、删除空行。
  290. ''' </summary>
  291. Private Function GetHeadTailLines(Raw As String(), HeadLines As Integer, TailLines As Integer) As String
  292. If Raw.Length <= HeadLines + TailLines Then Return Join(Raw, vbLf)
  293. Dim Result As New StringBuilder
  294. For i = 0 To Raw.Count - 1
  295. If i < HeadLines OrElse Raw.Count - i < TailLines Then
  296. If Raw(i) = "" Then Continue For
  297. Result.Append(Raw(i) & vbLf)
  298. End If
  299. Next
  300. Return Result.ToString
  301. End Function
  302. '3:根据文本分析崩溃原因
  303. Private LogMc As String = Nothing, LogHs As String = Nothing, LogCrash As String = Nothing
  304. Private LogAll As String
  305. '可能导致崩溃的原因与附加信息
  306. Private CrashReasons As New Dictionary(Of CrashReason, List(Of String))
  307. ''' <summary>
  308. ''' 导致崩溃的原因枚举。
  309. ''' </summary>
  310. Private Enum CrashReason
  311. Mod文件被解压
  312. 内存不足
  313. 使用JDK
  314. 显卡不支持OpenGL
  315. 使用OpenJ9
  316. Java版本过高
  317. Java版本不兼容
  318. 显卡驱动不支持导致无法设置像素格式
  319. 极短的程序输出
  320. Intel驱动不兼容导致EXCEPTION_ACCESS_VIOLATION 'https://bugs.mojang.com/browse/MC-32606
  321. AMD驱动不兼容导致EXCEPTION_ACCESS_VIOLATION 'https://bugs.mojang.com/browse/MC-31618
  322. Nvidia驱动不兼容导致EXCEPTION_ACCESS_VIOLATION
  323. 玩家手动触发调试崩溃
  324. 光影或资源包导致OpenGL1282错误
  325. 文件或内容校验失败
  326. 确定Mod导致游戏崩溃
  327. Mod配置文件导致游戏崩溃
  328. ModMixin失败
  329. Mod加载器报错
  330. Mod初始化失败
  331. 崩溃日志堆栈分析发现关键字
  332. 崩溃日志堆栈分析发现Mod名称
  333. MC日志堆栈分析发现关键字
  334. OptiFine导致无法加载世界 'https://www.minecraftforum.net/forums/support/java-edition-support/3051132-exception-ticking-world
  335. 特定方块导致崩溃
  336. 特定实体导致崩溃
  337. 材质过大或显卡配置不足
  338. 没有可用的分析文件
  339. 使用32位Java导致JVM无法分配足够多的内存
  340. Mod重复安装
  341. OptiFine与Forge不兼容
  342. Fabric报错
  343. Fabric报错并给出解决方案
  344. Forge报错
  345. 低版本Forge与高版本Java不兼容
  346. 版本Json中存在多个Forge
  347. Mod过多导致超出ID限制
  348. End Enum
  349. ''' <summary>
  350. ''' 根据 AnalyzeLogs 与可能的版本信息分析崩溃原因。
  351. ''' </summary>
  352. Public Sub Analyze(Optional Version As McVersion = Nothing)
  353. Log("[Crash] 步骤 3:分析崩溃原因")
  354. LogAll = (If(LogMc, "") & If(LogHs, "") & If(LogCrash, "")).ToLower
  355. '1. 精准日志匹配 1
  356. AnalyzeCrit1()
  357. If CrashReasons.Count > 0 Then GoTo Done
  358. '2. 堆栈分析
  359. If LogAll.Contains("forge") OrElse LogAll.Contains("fabric") OrElse LogAll.Contains("liteloader") Then
  360. '来源崩溃日志的分析
  361. If LogCrash IsNot Nothing Then
  362. Log("[Crash] 开始进行崩溃日志堆栈分析")
  363. Dim StackLogs As String = LogCrash.Replace("System Details", "¨").Split("¨").First
  364. Dim Keywords = AnalyzeStackKeyword(StackLogs)
  365. If Keywords.Count > 0 Then
  366. Dim Names = AnalyzeModName(Keywords)
  367. If Names Is Nothing Then
  368. AppendReason(CrashReason.崩溃日志堆栈分析发现关键字, Keywords)
  369. Else
  370. AppendReason(CrashReason.崩溃日志堆栈分析发现Mod名称, Names)
  371. End If
  372. GoTo Done
  373. End If
  374. End If
  375. '来自 Minecraft 日志的分析
  376. If LogMc IsNot Nothing Then
  377. Dim Fatals = RegexSearch(LogMc, "/FATAL] [\w\W]+?(?=[\n]+\[)")
  378. Log("[Crash] 开始进行 Minecraft 日志堆栈分析,发现 " & Fatals.Count & " 个报错项")
  379. If Fatals.Count > 0 Then
  380. Dim Keywords As New List(Of String)
  381. For Each Fatal In Fatals
  382. Keywords.AddRange(AnalyzeStackKeyword(Fatal))
  383. Next
  384. If Keywords.Count > 0 Then
  385. AppendReason(CrashReason.MC日志堆栈分析发现关键字, Keywords.Distinct.ToList)
  386. GoTo Done
  387. End If
  388. End If
  389. End If
  390. Else
  391. Log("[Crash] 可能并未安装 Mod,不进行堆栈分析")
  392. End If
  393. '3. 精准日志匹配 2
  394. AnalyzeCrit2()
  395. '输出到日志
  396. Done:
  397. If CrashReasons.Count = 0 Then
  398. Log("[Crash] 步骤 3:分析崩溃原因完成,未找到可能的原因")
  399. Else
  400. Log("[Crash] 步骤 3:分析崩溃原因完成,找到 " & CrashReasons.Count & " 条可能的原因")
  401. For Each Reason In CrashReasons
  402. Log("[Crash] - " & GetStringFromEnum(Reason.Key) & If(Reason.Value.Count > 0, "(" & Join(Reason.Value, ";") & ")", ""))
  403. Next
  404. End If
  405. End Sub
  406. ''' <summary>
  407. ''' 增加一个可能的崩溃原因。
  408. ''' </summary>
  409. Private Sub AppendReason(Reason As CrashReason, Optional Additional As ICollection(Of String) = Nothing)
  410. If CrashReasons.ContainsKey(Reason) Then
  411. If Additional IsNot Nothing Then
  412. CrashReasons(Reason).AddRange(Additional)
  413. CrashReasons(Reason) = CrashReasons(Reason).Distinct.ToList
  414. End If
  415. Else
  416. CrashReasons.Add(Reason, New List(Of String)(If(Additional, {})))
  417. End If
  418. Log("[Crash] 可能的崩溃原因:" & GetStringFromEnum(Reason) & If(Additional IsNot Nothing AndAlso Additional.Count > 0, "(" & Join(Additional, ";") & ")", ""))
  419. End Sub
  420. Private Sub AppendReason(Reason As CrashReason, Additional As String)
  421. AppendReason(Reason, If(String.IsNullOrEmpty(Additional), Nothing, New List(Of String) From {Additional}))
  422. End Sub
  423. '具体的分析代码
  424. ''' <summary>
  425. ''' 进行精准日志匹配。匹配优先级高于堆栈分析的崩溃。
  426. ''' </summary>
  427. Private Sub AnalyzeCrit1()
  428. '空白分析
  429. If LogMc Is Nothing AndAlso LogHs Is Nothing AndAlso LogCrash Is Nothing Then
  430. AppendReason(CrashReason.没有可用的分析文件)
  431. Exit Sub
  432. End If
  433. '崩溃报告分析,高优先级
  434. If LogCrash IsNot Nothing Then
  435. If LogCrash.Contains("Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass") Then AppendReason(CrashReason.Java版本过高)
  436. End If
  437. '游戏日志分析
  438. If LogMc IsNot Nothing Then
  439. If LogMc.Contains("Found multiple arguments for option fml.forgeVersion, but you asked for only one") Then AppendReason(CrashReason.版本Json中存在多个Forge)
  440. If LogMc.Contains("The driver does not appear to support OpenGL") Then AppendReason(CrashReason.显卡不支持OpenGL)
  441. If LogMc.Contains("java.lang.ClassCastException: java.base/jdk") Then AppendReason(CrashReason.使用JDK)
  442. If LogMc.Contains("java.lang.ClassCastException: class jdk.") Then AppendReason(CrashReason.使用JDK)
  443. If LogMc.Contains("TRANSFORMER/net.optifine/net.optifine.reflect.Reflector.<clinit>(Reflector.java") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
  444. If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.texture.SpriteContents.<init>(net.minecraft.resources.ResourceLocation, ") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
  445. If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.block.model.BakedQuad.<init>(int[], int, net.minecraft.core.Direction, net.minecraft.client.renderer.texture.TextureAtlasSprite, boolean, boolean)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
  446. If LogMc.Contains("java.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager.addRegionTicket(net.minecraft.server.level.TicketType, net.minecraft.world.level.ChunkPos, int, java.lang.Object, boolean)'") Then AppendReason(CrashReason.OptiFine与Forge不兼容)
  447. If LogMc.Contains("Open J9 is not supported") OrElse LogMc.Contains("OpenJ9 is incompatible") OrElse LogMc.Contains(".J9VMInternals.") Then AppendReason(CrashReason.使用OpenJ9)
  448. If LogMc.Contains("java.lang.NoSuchFieldException: ucp") Then AppendReason(CrashReason.Java版本过高)
  449. If LogMc.Contains("because module java.base does not export") Then AppendReason(CrashReason.Java版本过高)
  450. If LogMc.Contains("java.lang.ClassNotFoundException: jdk.nashorn.api.scripting.NashornScriptEngineFactory") Then AppendReason(CrashReason.Java版本过高)
  451. If LogMc.Contains("java.lang.ClassNotFoundException: java.lang.invoke.LambdaMetafactory") Then AppendReason(CrashReason.Java版本过高)
  452. If LogMc.Contains("The directories below appear to be extracted jar files. Fix this before you continue.") Then AppendReason(CrashReason.Mod文件被解压)
  453. If LogMc.Contains("Extracted mod jars found, loading will NOT continue") Then AppendReason(CrashReason.Mod文件被解压)
  454. If LogMc.Contains("Couldn't set pixel format") Then AppendReason(CrashReason.显卡驱动不支持导致无法设置像素格式)
  455. If LogMc.Contains("java.lang.OutOfMemoryError") Then AppendReason(CrashReason.内存不足)
  456. If LogMc.Contains("java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier") Then AppendReason(CrashReason.低版本Forge与高版本Java不兼容)
  457. If LogMc.Contains("1282: Invalid operation") Then AppendReason(CrashReason.光影或资源包导致OpenGL1282错误)
  458. If LogMc.Contains("signer information does not match signer information of other classes in the same package") Then AppendReason(CrashReason.文件或内容校验失败, If(RegexSeek(LogMc, "(?<=class "")[^']+(?=""'s signer information)"), "").TrimEnd(vbCrLf))
  459. If LogMc.Contains("Maybe try a lower resolution resourcepack?") Then AppendReason(CrashReason.材质过大或显卡配置不足)
  460. If LogMc.Contains("java.lang.NoSuchMethodError: net.minecraft.world.server.ChunkManager$ProxyTicketManager.shouldForceTicks(J)Z") AndAlso LogMc.Contains("OptiFine") Then AppendReason(CrashReason.OptiFine导致无法加载世界)
  461. If LogMc.Contains("Unsupported class file major version") Then AppendReason(CrashReason.Java版本不兼容)
  462. If LogMc.Contains("java.lang.UnsupportedClassVersionError: net/fabricmc/loader/impl/launch/knot/KnotClient : Unsupported major.minor version") Then AppendReason(CrashReason.Java版本不兼容)
  463. If LogMc.Contains("Could not reserve enough space") Then
  464. If LogMc.Contains("for 1048576KB object heap") Then
  465. AppendReason(CrashReason.使用32位Java导致JVM无法分配足够多的内存)
  466. Else
  467. AppendReason(CrashReason.内存不足)
  468. End If
  469. End If
  470. 'Mod 重复安装
  471. If LogMc.Contains("DuplicateModsFoundException") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(LogMc, "(?<=\n\t[\w]+ : [A-Z]{1}:[^\n]+(/|\\))[^/\\\n]+?.jar", RegularExpressions.RegexOptions.IgnoreCase))
  472. If LogMc.Contains("Found a duplicate mod") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "Found a duplicate mod[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
  473. If LogMc.Contains("ModResolutionException: Duplicate") Then AppendReason(CrashReason.Mod重复安装, RegexSearch(If(RegexSeek(LogMc, "ModResolutionException: Duplicate[^\n]+"), ""), "[^\\/]+.jar", RegularExpressions.RegexOptions.IgnoreCase))
  474. 'Mod 导致的崩溃
  475. If LogMc.Contains("Mixin prepare failed ") OrElse LogMc.Contains("Mixin apply failed ") OrElse LogMc.Contains("mixin.injection.throwables.") OrElse LogMc.Contains(".mixins.json] FAILED during )") Then
  476. Dim ModId As String = RegexSeek(LogMc, "(?<=in )[^./ ]+(?=.mixins.json.+failed injection check)")
  477. If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<=in mixins.)[^./ ]+(?=.json.+failed injection check)")
  478. If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in )[^./ ]+(?=.mixins.json)")
  479. If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= failed .+ in mixins.)[^./ ]+(?=.json)")
  480. If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[)[^./ ]+(?=.mixins.json\] FAILED during )")
  481. If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in config \[mixins.)[^./ ]+(?=.json\] FAILED during )")
  482. If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in callback )[^./ ]+(?=.mixins.json:)")
  483. If ModId Is Nothing Then ModId = RegexSeek(LogMc, "(?<= in callback mixins.)[^./ ]+(?=.json:)")
  484. AppendReason(CrashReason.ModMixin失败, TryAnalyzeModName(If(ModId, "").TrimEnd((vbCrLf & " ").ToCharArray)))
  485. End If
  486. If LogMc.Contains("Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(RegexSeek(LogMc, "(?<=Caught exception from )[^\n]+?")?.TrimEnd((vbCrLf & " ").ToCharArray)))
  487. '仅当没有其他分析结果时显示
  488. If CrashReasons.Count = 0 Then
  489. If LogMc.Contains("An exception was thrown, the game will display an error screen and halt.") Then AppendReason(CrashReason.Forge报错, RegexSeek(LogMc, "(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")?.Trim(vbCrLf))
  490. If LogMc.Contains("A potential solution has been determined:") Then AppendReason(CrashReason.Fabric报错并给出解决方案, Join(RegexSearch(If(RegexSeek(LogMc, "(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+"), ""), "(?<=(\t)+)[^\n]+"), vbLf))
  491. End If
  492. End If
  493. '虚拟机日志分析
  494. If LogHs IsNot Nothing Then
  495. If LogHs.Contains("The system is out of physical RAM or swap space") Then AppendReason(CrashReason.内存不足)
  496. If LogHs.Contains("Out of Memory Error") Then AppendReason(CrashReason.内存不足)
  497. If LogHs.Contains("EXCEPTION_ACCESS_VIOLATION") Then
  498. If LogHs.Contains("# C [ig") Then AppendReason(CrashReason.Intel驱动不兼容导致EXCEPTION_ACCESS_VIOLATION)
  499. If LogHs.Contains("# C [atio") Then AppendReason(CrashReason.AMD驱动不兼容导致EXCEPTION_ACCESS_VIOLATION)
  500. If LogHs.Contains("# C [nvoglv") Then AppendReason(CrashReason.Nvidia驱动不兼容导致EXCEPTION_ACCESS_VIOLATION)
  501. End If
  502. End If
  503. '崩溃报告分析
  504. If LogCrash IsNot Nothing Then
  505. If LogCrash.Contains("maximum id range exceeded") Then AppendReason(CrashReason.Mod过多导致超出ID限制)
  506. If LogCrash.Contains("java.lang.OutOfMemoryError") Then AppendReason(CrashReason.内存不足)
  507. If LogCrash.Contains("Pixel format not accelerated") Then AppendReason(CrashReason.显卡驱动不支持导致无法设置像素格式)
  508. If LogCrash.Contains("Manually triggered debug crash") Then AppendReason(CrashReason.玩家手动触发调试崩溃)
  509. 'Mod 导致的崩溃
  510. If LogCrash.Contains("-- MOD ") Then
  511. Dim LogLeft = LogCrash.Split("-- MOD").Last
  512. If LogLeft.Contains("Failure message: MISSING") Then
  513. AppendReason(CrashReason.确定Mod导致游戏崩溃, If(RegexSeek(LogCrash, "(?<=Mod File: ).+"), "").TrimEnd((vbCrLf & " ").ToCharArray))
  514. Else
  515. AppendReason(CrashReason.Mod加载器报错, If(RegexSeek(LogCrash, "(?<=Failure message: )[\w\W]+?(?=\tMod)"), "").Replace(vbTab, " ").TrimEnd((vbCrLf & " ").ToCharArray))
  516. End If
  517. End If
  518. If LogCrash.Contains("Multiple entries with same key: ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Multiple entries with same key: )[^=]+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
  519. If LogCrash.Contains("due to errors, provided by ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=due to errors, provided by ')[^']+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
  520. If LogCrash.Contains("LoaderExceptionModCrash: Caught exception from ") Then AppendReason(CrashReason.确定Mod导致游戏崩溃, TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=LoaderExceptionModCrash: Caught exception from )[^\n]+"), "").TrimEnd((vbCrLf & " ").ToCharArray)))
  521. If LogCrash.Contains("Failed loading config file ") Then AppendReason(CrashReason.Mod配置文件导致游戏崩溃, {TryAnalyzeModName(If(RegexSeek(LogCrash, "(?<=Failed loading config file .+ for modid )[^\n]+"), "").TrimEnd(vbCrLf)).First, If(RegexSeek(LogCrash, "(?<=Failed loading config file ).+(?= of type)"), "").TrimEnd(vbCrLf)})
  522. End If
  523. End Sub
  524. ''' <summary>
  525. ''' 进行精准日志匹配。匹配优先级低于堆栈分析的崩溃。
  526. ''' </summary>
  527. Private Sub AnalyzeCrit2()
  528. '游戏日志分析
  529. If LogMc IsNot Nothing Then
  530. If Not (LogMc.Contains("at net.") OrElse LogMc.Contains("INFO]")) AndAlso
  531. LogHs Is Nothing AndAlso LogCrash Is Nothing AndAlso LogMc.Length < 100 Then
  532. AppendReason(CrashReason.极短的程序输出, LogMc)
  533. End If
  534. 'Mixin 失败可以导致大量 Mod 实例创建失败
  535. If LogMc.Contains("Failed to create mod instance.") Then AppendReason(CrashReason.Mod初始化失败, TryAnalyzeModName(If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModID: )[^,]+"), If(RegexSeek(LogMc, "(?<=Failed to create mod instance. ModId )[^\n]+(?= for )"), "")).TrimEnd(vbCrLf)))
  536. '注意:Fabric 的 Warnings were found! 不一定是崩溃原因,它可能是单纯的警报
  537. End If
  538. '崩溃报告分析
  539. If LogCrash IsNot Nothing Then
  540. If LogCrash.Contains(vbTab & "Block location: World: ") Then AppendReason(CrashReason.特定方块导致崩溃, If(RegexSeek(LogCrash, "(?<=\tBlock: Block\{)[^\}]+"), "") & " " & If(RegexSeek(LogCrash, "(?<=\tBlock location: World: )\([^\)]+\)"), ""))
  541. If LogCrash.Contains(vbTab & "Entity's Exact location: ") Then AppendReason(CrashReason.特定实体导致崩溃, If(RegexSeek(LogCrash, "(?<=\tEntity Type: )[^\n]+(?= \()"), "") & " (" & If(RegexSeek(LogCrash, "(?<=\tEntity's Exact location: )[^\n]+"), "").TrimEnd(vbCrLf.ToCharArray) & ")")
  542. End If
  543. End Sub
  544. ''' <summary>
  545. ''' 从堆栈中提取 Mod ID 关键字。若失败则返回空列表。
  546. ''' </summary>
  547. Private Function AnalyzeStackKeyword(ErrorStack As String) As List(Of String)
  548. '进行正则匹配
  549. Dim StackSearchResults As List(Of String) = RegexSearch(If(ErrorStack, "") & vbCrLf, "(?<=\n[^{]+)[a-zA-Z]+\w+\.[a-zA-Z]+[\w\.]+(?=\.[\w\.$]+\.)")
  550. '检查堆栈开头
  551. Dim PossibleStacks As New List(Of String)
  552. For Each Stack As String In StackSearchResults
  553. 'If Not Stack.Contains(".") Then Continue For
  554. For Each IgnoreStack In {
  555. "java", "sun", "javax", "jdk", "oolloo",
  556. "org.lwjgl", "com.sun", "net.minecraftforge", "com.mojang", "net.minecraft", "cpw.mods", "com.google", "org.apache", "org.spongepowered", "net.fabricmc", "com.mumfrey",
  557. "com.electronwill.nightconfig", "it.unimi.dsi",
  558. "MojangTricksIntelDriversForPerformance_javaw"}
  559. If Stack.StartsWith(IgnoreStack) Then GoTo NextStack
  560. Next
  561. PossibleStacks.Add(Stack.Trim) '.Split("$").First)
  562. NextStack:
  563. Next
  564. PossibleStacks = PossibleStacks.Distinct.ToList
  565. Log("[Crash] 找到 " & PossibleStacks.Count & " 条可能的堆栈信息")
  566. If PossibleStacks.Count = 0 Then Return New List(Of String)
  567. For Each Stack As String In PossibleStacks
  568. Log("[Crash] - " & Stack)
  569. Next
  570. '检查堆栈关键词
  571. Dim PossibleWords As New List(Of String)
  572. For Each Stack As String In PossibleStacks
  573. Dim Splited = Stack.Split(".")
  574. For i = 0 To Math.Min(3, Splited.Count - 1) '最多取前 4 节
  575. Dim Word As String = Splited(i)
  576. If Word.Length <= 2 OrElse Word.StartsWith("func_") Then Continue For
  577. If {"com", "org", "net", "asm", "fml", "mod", "jar", "sun", "lib", "map", "gui", "dev", "nio", "api", "dsi", "top",
  578. "core", "init", "mods", "main", "file", "game", "load", "read", "done", "util", "tile", "item", "base",
  579. "forge", "setup", "block", "model", "mixin", "event", "unimi", "netty",
  580. "common", "server", "config", "loader", "launch", "entity", "assist", "client", "plugin", "modapi", "mojang", "shader", "events", "github", "recipe",
  581. "preinit", "preload", "machine", "reflect", "channel", "general", "handler", "content",
  582. "fastutil", "optifine", "scheduler", "minecraft", "transformers", "universal", "internal", "multipart", "minecraftforge", "override", "blockentity"
  583. }.Contains(Word.ToLower) Then Continue For
  584. PossibleWords.Add(Word.Trim)
  585. Next
  586. Next
  587. PossibleWords = PossibleWords.Distinct.ToList
  588. Log("[Crash] 从堆栈信息中找到 " & PossibleWords.Count & " 个可能的 Mod ID 关键词")
  589. If PossibleWords.Count > 0 Then Log("[Crash] - " & Join(PossibleWords, ", "))
  590. If PossibleWords.Count > 10 Then
  591. Log("[Crash] 关键词过多,考虑匹配出错,不纳入考虑")
  592. Return New List(Of String)
  593. Else
  594. Return PossibleWords
  595. End If
  596. End Function
  597. ''' <summary>
  598. ''' 根据 Mod 关键词与详细信息(崩溃报告第二部分)与,获取实际的 Mod 名称。
  599. ''' 若失败则返回 Nothing。
  600. ''' </summary>
  601. Private Function AnalyzeModName(Keywords As List(Of String)) As List(Of String)
  602. Dim ModFileNames As New List(Of String)
  603. '预处理关键词(分割括号)
  604. Dim RealKeywords As New List(Of String)
  605. For Each Keyword In Keywords
  606. For Each SubKeyword In Keyword.Split("(")
  607. RealKeywords.Add(SubKeyword.Trim(" )".ToCharArray))
  608. Next
  609. Next
  610. Keywords = RealKeywords
  611. '获取崩溃报告对应部分
  612. If LogCrash Is Nothing Then Return Nothing
  613. If Not LogCrash.Contains("A detailed walkthrough of the error") Then Return Nothing
  614. Dim Details As String = LogCrash.Replace("A detailed walkthrough of the error", "¨")
  615. Dim IsFabricDetail As Boolean = Details.Contains("Fabric Mods") '是否为 Fabric 信息格式
  616. If IsFabricDetail Then
  617. Details = Details.Replace("Fabric Mods", "¨")
  618. Log("[Crash] 检测到 Fabric Mod 信息格式")
  619. End If
  620. Details = Details.Split("¨").Last
  621. '[Forge] 获取所有包含 .jar 的行
  622. '[Fabric] 获取所有包含 Mod 信息的行
  623. Dim ModNameLines As New List(Of String)
  624. For Each Line In Details.Split(vbLf)
  625. If Line.ToLower.Contains(".jar") OrElse
  626. (IsFabricDetail AndAlso Line.StartsWith(vbTab & vbTab) AndAlso Not RegexCheck(Line, "\t\tfabric[\w-]*: Fabric")) Then ModNameLines.Add(Line)
  627. Next
  628. Log("[Crash] 找到 " & ModNameLines.Count & " 个可能的 Mod 项目行")
  629. '获取 Mod ID 与关键词的匹配行
  630. Dim HintLines As New List(Of String)
  631. For Each KeyWord As String In Keywords
  632. For Each ModString As String In ModNameLines
  633. Dim RealModString As String = ModString.ToLower.Replace("_", "")
  634. If Not RealModString.Contains(KeyWord.ToLower.Replace("_", "")) Then Continue For
  635. If RealModString.Contains("minecraft.jar") OrElse RealModString.Contains(" forge-") OrElse RealModString.Contains(" mixin-") Then Continue For
  636. HintLines.Add(ModString.Trim(vbCrLf.ToCharArray))
  637. Exit For
  638. Next
  639. Next
  640. HintLines = HintLines.Distinct.ToList
  641. Log("[Crash] 找到 " & HintLines.Count & " 个可能的崩溃 Mod 匹配行")
  642. For Each ModName As String In HintLines
  643. Log("[Crash] - " & ModName)
  644. Next
  645. '从 Mod 匹配行中提取 .jar 文件的名称
  646. For Each Line As String In HintLines
  647. Dim Name As String
  648. If IsFabricDetail Then
  649. Name = RegexSeek(Line, "(?<=: )[^\n]+(?= [^\n]+)")
  650. Else
  651. Name = RegexSeek(Line, "(?<=\()[^\t]+.jar(?=\))|(?<=(\t\t)|(\| ))[^\t\|]+.jar", RegularExpressions.RegexOptions.IgnoreCase)
  652. End If
  653. If Name IsNot Nothing Then ModFileNames.Add(Name)
  654. Next
  655. ModFileNames = ModFileNames.Distinct.ToList
  656. Log("[Crash] 找到 " & ModFileNames.Count & " 个可能的崩溃 Mod 文件名")
  657. For Each ModFileName As String In ModFileNames
  658. Log("[Crash] - " & ModFileName)
  659. Next
  660. Return If(ModFileNames.Count = 0, Nothing, ModFileNames)
  661. End Function
  662. ''' <summary>
  663. ''' 尝试获取 Mod 名称,若失败则返回原关键字。
  664. ''' </summary>
  665. Private Function TryAnalyzeModName(Keywords As String) As List(Of String)
  666. Dim RawList As New List(Of String) From {If(Keywords, "")}
  667. If String.IsNullOrEmpty(Keywords) Then Return RawList
  668. Return If(AnalyzeModName(RawList), RawList)
  669. End Function
  670. '4:根据原因输出信息
  671. Private OutputFiles As New List(Of String)
  672. ''' <summary>
  673. ''' 弹出崩溃弹窗,并指导导出崩溃报告。
  674. ''' </summary>
  675. Public Sub Output(IsHandAnalyze As Boolean, Optional ExtraFiles As List(Of String) = Nothing)
  676. '弹窗提示
  677. FrmMain.ShowWindowToTop()
  678. Dim ShowLog As Action =
  679. Sub()
  680. If File.Exists(DirectFile.Value.Key) Then
  681. ShellOnly(DirectFile.Value.Key)
  682. Else
  683. Dim FilePath As String = PathTemp & "Crash.txt"
  684. WriteFile(FilePath, Join(DirectFile.Value.Value, vbCrLf))
  685. ShellOnly(FilePath)
  686. End If
  687. End Sub
  688. Select Case MyMsgBox(GetAnalyzeResult(IsHandAnalyze), If(IsHandAnalyze, "错误报告分析结果", "Minecraft 出现错误"),
  689. "确定", If(IsHandAnalyze OrElse DirectFile Is Nothing, "", "查看日志"), If(IsHandAnalyze, "", "导出错误报告"),
  690. Button2Action:=If(IsHandAnalyze OrElse DirectFile Is Nothing, Nothing, ShowLog))
  691. Case 3
  692. Dim FileAddress As String = Nothing
  693. Try
  694. '获取文件路径
  695. RunInUiWait(Sub() FileAddress = SelectAs("选择保存位置", "错误报告-" & Date.Now.ToString("G").Replace("/", "-").Replace(":", ".").Replace(" ", "_") & ".zip", "Minecraft 错误报告(*.zip)|*.zip"))
  696. If String.IsNullOrEmpty(FileAddress) Then Exit Sub
  697. Directory.CreateDirectory(GetPathFromFullPath(FileAddress))
  698. If File.Exists(FileAddress) Then File.Delete(FileAddress)
  699. '输出诊断信息
  700. FeedbackInfo()
  701. LogFlush()
  702. '复制文件
  703. If ExtraFiles IsNot Nothing Then OutputFiles.AddRange(ExtraFiles)
  704. For Each OutputFile In OutputFiles
  705. Dim FileName As String = GetFileNameFromPath(OutputFile)
  706. Select Case FileName
  707. Case "LatestLaunch.bat"
  708. FileName = "启动脚本.bat"
  709. Case "Log1.txt"
  710. FileName = "PCL 启动器日志.txt"
  711. Case "RawOutput.log"
  712. FileName = "游戏崩溃前的输出.txt"
  713. End Select
  714. If File.Exists(OutputFile) Then
  715. WriteFile(TempFolder & "Report\" & FileName, SecretFilter(ReadFile(OutputFile)))
  716. End If
  717. Next
  718. '导出报告
  719. Compression.ZipFile.CreateFromDirectory(TempFolder & "Report\", FileAddress)
  720. DeleteDirectory(TempFolder & "Report\")
  721. Hint("错误报告已导出!", HintType.Finish)
  722. Catch ex As Exception
  723. Log(ex, "导出错误报告失败", LogLevel.Feedback)
  724. Exit Sub
  725. End Try
  726. OpenExplorer("/select,""" & FileAddress & """")
  727. End Select
  728. End Sub
  729. ''' <summary>
  730. ''' 获取崩溃分析的结果描述。
  731. ''' </summary>
  732. Private Function GetAnalyzeResult(IsHandAnalyze As Boolean) As String
  733. '没有结果的处理
  734. If CrashReasons.Count = 0 Then
  735. If IsHandAnalyze Then
  736. Return "很抱歉,PCL 无法确定错误原因。"
  737. Else
  738. Return "很抱歉,你的游戏出现了一些问题……" & vbCrLf &
  739. "如果要寻求帮助,请向他人发送错误报告文件,而不是发送这个窗口的截图。" & vbCrLf &
  740. "你也可以查看错误报告,其中可能会有出错的原因。"
  741. End If
  742. End If
  743. '根据不同原因判断
  744. Dim Results As New List(Of String)
  745. For Each Reason In CrashReasons
  746. Dim Additional As List(Of String) = Reason.Value
  747. Select Case Reason.Key
  748. Case CrashReason.Mod文件被解压
  749. Results.Add("由于 Mod 文件被解压了,导致游戏无法继续运行。\n直接把整个 Mod 文件放进 Mod 文件夹中即可,若解压就会导致游戏出错。\n\n请删除 Mod 文件夹中已被解压的 Mod,然后再启动游戏。")
  750. Case CrashReason.内存不足
  751. Results.Add("Minecraft 内存不足,导致其无法继续运行。\n这很可能是由于你为游戏分配的内存不足,或是游戏的配置要求过高。\n\n你可以在启动设置中增加为游戏分配的内存,删除配置要求较高的材质、Mod、光影。\n如果这依然不奏效,请在开始游戏前尽量关闭其他软件,或者……换台电脑?\h")
  752. Case CrashReason.使用OpenJ9
  753. Results.Add("游戏因为使用 Open J9 而崩溃了。\n请在启动设置的 Java 选择一项中改用非 OpenJ9 的 Java,然后再启动游戏。")
  754. Case CrashReason.使用JDK
  755. Results.Add("游戏似乎因为使用 JDK,或 Java 版本过高而崩溃了。\n请在启动设置的 Java 选择一项中改用 JRE 8(Java 8),然后再启动游戏。\n如果你没有安装 JRE 8,你可以从网络中下载、安装一个。")
  756. Case CrashReason.Java版本过高
  757. Results.Add("游戏似乎因为你所使用的 Java 版本过高而崩溃了。\n请在启动设置的 Java 选择一项中改用较低版本的 Java,然后再启动游戏。\n如果没有,可以从网络中下载、安装一个。")
  758. Case CrashReason.Java版本不兼容
  759. Results.Add("游戏不兼容你当前使用的 Java。\n如果没有合适的 Java,可以从网络中下载、安装一个。")
  760. Case CrashReason.使用32位Java导致JVM无法分配足够多的内存
  761. If Environment.Is64BitOperatingSystem Then
  762. Results.Add("你似乎正在使用 32 位 Java,这会导致 Minecraft 无法使用 1GB 以上的内存,进而造成崩溃。\n\n请在启动设置的 Java 选择一项中改用 64 位的 Java 再启动游戏,然后再启动游戏。\n如果你没有安装 64 位的 Java,你可以从网络中下载、安装一个。")
  763. Else
  764. Results.Add("你正在使用 32 位的操作系统,这会导致 Minecraft 无法使用 1GB 以上的内存,进而造成崩溃。\n\n你或许只能重装 64 位的操作系统来解决此问题。\n如果你的电脑内存在 2GB 以内,那或许只能换台电脑了……\h")
  765. End If
  766. Case CrashReason.崩溃日志堆栈分析发现关键字, CrashReason.MC日志堆栈分析发现关键字
  767. If Additional.Count = 1 Then
  768. Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了一个可疑的关键词:" & Additional.First & "。\n\n如果你知道它对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h")
  769. Else
  770. Results.Add("你的游戏遇到了一些问题,这可能是某些 Mod 所引起的,PCL 找到了以下可疑的关键词:\n - " & Join(Additional, ", ") & "\n\n如果你知道这些关键词对应的 Mod,那么有可能就是它引起的错误,你也可以查看错误报告获取详情。\h")
  771. End If
  772. Case CrashReason.崩溃日志堆栈分析发现Mod名称
  773. If Additional.Count = 1 Then
  774. Results.Add("名为 " & Additional.First & " 的 Mod 可能导致了游戏出错。\n\e\h")
  775. Else
  776. Results.Add("可能是以下 Mod 导致了游戏出错:\n - " & Join(Additional, "\n - ") & "\n\e\h")
  777. End If
  778. Case CrashReason.确定Mod导致游戏崩溃
  779. If Additional.Count = 1 Then
  780. Results.Add("名称或 ID 为 " & Additional.First & " 的 Mod 导致了游戏出错。\n\e\h")
  781. Else
  782. Results.Add("以下 Mod 导致了游戏出错:\n - " & Join(Additional, "\n - ") & "\n\e\h")
  783. End If
  784. Case CrashReason.ModMixin失败
  785. If Additional.Count = 1 Then
  786. Results.Add("名称或 ID 为 " & Additional.First & " 的 Mod 注入失败,导致游戏出错。\n这一般代表着该 Mod 存在 Bug,或与当前环境不兼容。\n\e\h")
  787. Else
  788. Results.Add("以下 Mod 导致了游戏出错:\n - " & Join(Additional, "\n - ") & "\n这一般代表着这些 Mod 存在 Bug,或与当前环境不兼容。\n\e\h")
  789. End If
  790. Case CrashReason.Mod配置文件导致游戏崩溃
  791. If Additional(1) Is Nothing Then
  792. Results.Add("名称或 ID 为 " & Additional.First & " 的 Mod 导致了游戏出错。\n\e\h")
  793. Else
  794. Results.Add("名称或 ID 为 " & Additional.First & " 的 Mod 导致了游戏出错:\n其配置文件 " & Additional(1) & " 存在异常,无法读取。")
  795. End If
  796. Case CrashReason.Mod初始化失败
  797. If Additional.Count = 1 Then
  798. Results.Add("名为 " & Additional.First & " 的 Mod 初始化失败,导致游戏无法继续加载。\n\e\h")
  799. Else
  800. Results.Add("以下 Mod 初始化失败,导致游戏无法继续加载:\n - " & Join(Additional, "\n - ") & "\n\e\h")
  801. End If
  802. Case CrashReason.特定方块导致崩溃
  803. If Additional.Count = 1 Then
  804. Results.Add("游戏似乎因为方块 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并观察游戏的运行情况:\n - 若正常运行,则是该方块导致出错,你或许需要使用一些方式删除此方块。\n - 若仍然出错,问题就可能来自其他原因……\h")
  805. Else
  806. Results.Add("游戏似乎因为世界中的某些方块出现了问题。\n\n你可以创建一个新世界,并观察游戏的运行情况:\n - 若正常运行,则是某些方块导致出错,你或许需要删除该世界。\n - 若仍然出错,问题就可能来自其他原因……\h")
  807. End If
  808. Case CrashReason.Mod重复安装
  809. If Additional.Count >= 2 Then
  810. Results.Add("你重复安装了多个相同的 Mod:\n - " & Join(Additional, "\n - ") & "\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。")
  811. Else
  812. Results.Add("你可能重复安装了多个相同的 Mod,导致游戏无法继续加载。\n\n每个 Mod 只能出现一次,请删除重复的 Mod,然后再启动游戏。\e\h")
  813. End If
  814. Case CrashReason.特定实体导致崩溃
  815. If Additional.Count = 1 Then
  816. Results.Add("游戏似乎因为实体 " & Additional.First & " 出现了问题。\n\n你可以创建一个新世界,并生成一个该实体,然后观察游戏的运行情况:\n - 若正常运行,则是该实体导致出错,你或许需要使用一些方式删除此实体。\n - 若仍然出错,问题就可能来自其他原因……\h")
  817. Else
  818. Results.Add("游戏似乎因为世界中的某些实体出现了问题。\n\n你可以创建一个新世界,并生成各种实体,观察游戏的运行情况:\n - 若正常运行,则是某些实体导致出错,你或许需要删除该世界。\n - 若仍然出错,问题就可能来自其他原因……\h")
  819. End If
  820. Case CrashReason.OptiFine与Forge不兼容
  821. Results.Add("由于 OptiFine 与当前版本的 Forge 不兼容,导致了游戏崩溃。\n\n请前往 OptiFine 官网(https://optifine.net/downloads)查看 OptiFine 所兼容的 Forge 版本,并严格按照对应版本重新安装游戏。")
  822. Case CrashReason.低版本Forge与高版本Java不兼容
  823. Results.Add("由于低版本 Forge 与当前 Java 不兼容,导致了游戏崩溃。\n\n请尝试以下解决方案:\n - 更新 Forge 到 36.2.26 或更高版本\n - 换用版本低于 8.0.320 的 Java")
  824. Case CrashReason.版本Json中存在多个Forge
  825. Results.Add("可能由于使用其他启动器修改了 Forge 版本,当前版本的文件存在异常,导致了游戏崩溃。\n请尝试重新全新安装 Forge,而非使用其他启动器修改 Forge 版本。")
  826. Case CrashReason.玩家手动触发调试崩溃
  827. Results.Add("* 事实上,你的游戏没有任何问题,这是你自己触发的崩溃。\n* 你难道没有更重要的事要做吗?")
  828. Case CrashReason.极短的程序输出
  829. Results.Add($"程序返回了以下信息:\n{Additional.First}\n\h")
  830. Case CrashReason.OptiFine导致无法加载世界 'https://www.minecraftforum.net/forums/support/java-edition-support/3051132-exception-ticking-world
  831. Results.Add("你所使用的 OptiFine 可能导致了你的游戏出现问题。\n\n该问题只在特定 OptiFine 版本中出现,你可以尝试更换 OptiFine 的版本。\h")
  832. Case CrashReason.显卡驱动不支持导致无法设置像素格式, CrashReason.Intel驱动不兼容导致EXCEPTION_ACCESS_VIOLATION, CrashReason.AMD驱动不兼容导致EXCEPTION_ACCESS_VIOLATION, CrashReason.Nvidia驱动不兼容导致EXCEPTION_ACCESS_VIOLATION, CrashReason.显卡不支持OpenGL
  833. If LogAll.Contains("hd graphics ") Then
  834. Results.Add("你的显卡驱动存在问题,或未使用独立显卡,导致游戏无法正常运行。\n\n如果你的电脑存在独立显卡,请使用独立显卡而非 Intel 核显启动 PCL 与 Minecraft。\n如果问题依然存在,请尝试升级你的显卡驱动到最新版本,或回退到出厂版本。\n如果还是不行,还可以尝试使用 8.0.51 或更低版本的 Java。\h")
  835. Else
  836. Results.Add("你的显卡驱动存在问题,导致游戏无法正常运行。\n\n请尝试升级你的显卡驱动到最新版本,或回退到出厂版本,然后再启动游戏。\n如果还是不行,可以尝试使用 8.0.51 或更低版本的 Java。\n如果问题依然存在,那么你可能需要换个更好的显卡……\h")
  837. End If
  838. Case CrashReason.材质过大或显卡配置不足
  839. Results.Add("你所使用的材质分辨率过高,或显卡配置不足,导致游戏无法继续运行。\n\n如果你正在使用高清材质,请将它移除。\n如果你没有使用材质,那么你可能需要更新显卡驱动,或者换个更好的显卡……\h")
  840. Case CrashReason.光影或资源包导致OpenGL1282错误
  841. Results.Add("你所使用的光影或材质导致游戏出现了一些问题……\n\n请尝试删除你所添加的这些额外资源。\h")
  842. Case CrashReason.Mod过多导致超出ID限制
  843. Results.Add("你所安装的 Mod 过多,超出了游戏的 ID 限制,导致了游戏崩溃。\n请尝试安装 JEID 等修复 Mod,或删除部分大型 Mod。")
  844. Case CrashReason.文件或内容校验失败
  845. Results.Add("部分文件或内容校验失败,导致游戏出现了问题。\n\n请尝试删除游戏(包括 Mod)并重新下载,或尝试在重新下载时使用 VPN。\h")
  846. Case CrashReason.Fabric报错
  847. If Additional.Count = 1 Then
  848. Results.Add("Fabric 提供了以下错误信息:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
  849. Else
  850. Results.Add("Fabric 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
  851. End If
  852. Case CrashReason.Mod加载器报错
  853. If Additional.Count = 1 Then
  854. Results.Add("Mod 加载器提供了以下错误信息:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
  855. Else
  856. Results.Add("Mod 加载器可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
  857. End If
  858. Case CrashReason.Fabric报错并给出解决方案
  859. If Additional.Count = 1 Then
  860. Results.Add("Fabric 提供了以下解决方案:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
  861. Else
  862. Results.Add("Fabric 可能已经提供了解决方案,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
  863. End If
  864. Case CrashReason.Forge报错
  865. If Additional.Count = 1 Then
  866. Results.Add("Forge 提供了以下错误信息:\n" & Additional.First & "\n\n请根据上述信息进行对应处理,如果看不懂英文可以使用翻译软件。")
  867. Else
  868. Results.Add("Forge 可能已经提供了错误信息,请根据错误报告中的日志信息进行对应处理,如果看不懂英文可以使用翻译软件。\n如果没有看到报错信息,可以查看错误报告了解错误具体是如何发生的。\h")
  869. End If
  870. Case CrashReason.没有可用的分析文件
  871. Results.Add("你的游戏出现了一些问题,但 PCL 未能找到相关记录文件,因此无法进行分析。\h")
  872. Case Else
  873. Results.Add("PCL 获取到了没有详细信息的错误原因(" & CrashReasons.First.Key & "),请向 PCL 作者提交反馈以获取详情。\h")
  874. End Select
  875. Next
  876. '整理多个原因
  877. IsHandAnalyze = False
  878. Return Join(Results, "\n\n此外,").
  879. Replace("\n", vbCrLf).
  880. Replace("\h", "").
  881. Replace("\e", If(IsHandAnalyze, "", vbCrLf & "你可以查看错误报告了解错误具体是如何发生的。")).
  882. Replace(vbCrLf, vbCr).Replace(vbLf, vbCr).Replace(vbCr, vbCrLf).
  883. Trim(vbCrLf.ToCharArray) &
  884. If(Results.Any(Function(r) r.EndsWith("\h")),
  885. If(IsHandAnalyze, "", vbCrLf & "如果要寻求帮助,请向他人发送错误报告文件,而不是发送这个窗口的截图。"), "")
  886. End Function
  887. End Class