From 976d4c0b1baeb10c08e5a8a17bac9200b152f353 Mon Sep 17 00:00:00 2001 From: YangSpring114 Date: Fri, 3 May 2024 13:46:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B8=B8=E6=88=8F=E5=B4=A9?= =?UTF-8?q?=E6=BA=83=E5=88=86=E6=9E=90=E5=99=A8=EF=BC=8C=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MinecraftLaunch.Test/Program.cs | 29 +- MinecraftLaunch/Classes/Enums/CrashCauses.cs | 75 ++++ .../Models/Event/LogReceivedEventArgs.cs | 6 +- .../Classes/Models/Launch/CrashReport.cs | 9 + .../Components/Analyzer/GameLogAnalyzer.cs | 415 ++++++++++++++++++ ...ameLogResolver.cs => GameCrashAnalyzer.cs} | 0 .../Components/Watcher/GameProcessWatcher.cs | 2 +- MinecraftLaunch/MinecraftLaunch.csproj | 2 +- 8 files changed, 532 insertions(+), 6 deletions(-) create mode 100644 MinecraftLaunch/Classes/Enums/CrashCauses.cs create mode 100644 MinecraftLaunch/Classes/Models/Launch/CrashReport.cs create mode 100644 MinecraftLaunch/Components/Analyzer/GameLogAnalyzer.cs rename MinecraftLaunch/Components/Resolver/{GameLogResolver.cs => GameCrashAnalyzer.cs} (100%) diff --git a/MinecraftLaunch.Test/Program.cs b/MinecraftLaunch.Test/Program.cs index c9e018a..7b3db3b 100644 --- a/MinecraftLaunch.Test/Program.cs +++ b/MinecraftLaunch.Test/Program.cs @@ -1,6 +1,8 @@ -using MinecraftLaunch.Components.Resolver; +using MinecraftLaunch; +using MinecraftLaunch.Components.Resolver; +using MinecraftLaunch.Components.Analyzer; using MinecraftLaunch.Components.Installer; -using MinecraftLaunch; +using MinecraftLaunch.Components.Authenticator; MirrorDownloadManager.IsUseMirrorDownloadSource = true; @@ -103,4 +105,27 @@ #endregion +#region MicrosoftAuthenticator + +//MicrosoftAuthenticator microsoftAuthenticator = new("Your Client ID"); +//await microsoftAuthenticator.DeviceFlowAuthAsync(x => { +// Console.WriteLine(x.UserCode); +// Console.WriteLine(x.VerificationUrl); +//}); + +//var account = await microsoftAuthenticator.AuthenticateAsync(); + +#endregion + +#region Crash Analysis +GameResolver gameResolver = new("C:\\Users\\w\\Desktop\\总整包\\MC\\mc启动器\\BakaXL\\.minecraft"); + +var crashAnalyzer = new GameCrashAnalyzer(gameResolver.GetGameEntity("1.20.1"), true); +var reports = crashAnalyzer.AnalysisLogs(); + +foreach (var report in reports) { + Console.WriteLine(report); +} +#endregion + Console.ReadKey(); \ No newline at end of file diff --git a/MinecraftLaunch/Classes/Enums/CrashCauses.cs b/MinecraftLaunch/Classes/Enums/CrashCauses.cs new file mode 100644 index 0000000..3865af9 --- /dev/null +++ b/MinecraftLaunch/Classes/Enums/CrashCauses.cs @@ -0,0 +1,75 @@ +namespace MinecraftLaunch.Classes.Enums; + +public enum CrashCauses { + #region Memory + + NoEnoughMemory, + NoEnoughMemory32, + + #endregion + + #region Java + + JdkUse, + OpenJ9Use, + JavaVersionTooHigh, + UnsupportedJavaVersion, + + #endregion + + #region GPU + + UnsupportedNvDriver, + UnsupportedAmdDriver, + UnableToSetPixelFormat, + UnsupportedIntelDriver, + + #endregion + + #region Mod + + DuplicateMod, + ModIdExceeded, + ModInitFailed, + ModMixinFailed, + ModLoaderError, + DecompressedMod, + IncorrectModConfig, + ModCausedGameCrash, + + #endregion + + #region OpenGL + + OpenGl1282Error, + GpuDoesNotSupportOpenGl, + + #endregion + + #region Shaders + + TextureTooLargeOrLowEndGpu, + FailedToLoadWorldBecauseOptiFine, + + #endregion + + #region AffiliatedComponent + + ForgeError, + FabricError, + FabricErrorWithSolution, + MultipleForgeInVersionJson, + IncompatibleForgeAndOptifine, + LegacyForgeDoesNotSupportNewerJava, + + #endregion + + LogFileNotFound, + BlockCausedGameCrash, + EntityCausedGameCrash, + ContentValidationFailed, + ManuallyTriggeredDebugCrash, + IncorrectPathEncodingOrMainClassNotFound, + + Other +} diff --git a/MinecraftLaunch/Classes/Models/Event/LogReceivedEventArgs.cs b/MinecraftLaunch/Classes/Models/Event/LogReceivedEventArgs.cs index d14a378..1b1bae2 100644 --- a/MinecraftLaunch/Classes/Models/Event/LogReceivedEventArgs.cs +++ b/MinecraftLaunch/Classes/Models/Event/LogReceivedEventArgs.cs @@ -3,9 +3,11 @@ namespace MinecraftLaunch.Classes.Models.Event; -public sealed class LogReceivedEventArgs(string log, string time, string source, LogType logType) : EventArgs { - public string Text => log; +public sealed class LogReceivedEventArgs(string original, string log, string time, string source, LogType logType) : EventArgs { + public string Log => log; public string Time => time; public string Source => source; + public string Original => original; + public LogType LogType => logType; } \ No newline at end of file diff --git a/MinecraftLaunch/Classes/Models/Launch/CrashReport.cs b/MinecraftLaunch/Classes/Models/Launch/CrashReport.cs new file mode 100644 index 0000000..e7aaee4 --- /dev/null +++ b/MinecraftLaunch/Classes/Models/Launch/CrashReport.cs @@ -0,0 +1,9 @@ +using MinecraftLaunch.Classes.Enums; + +namespace MinecraftLaunch.Classes.Models.Launch; + +public sealed record CrashReport { + public string Original { get; set; } + public CrashCauses CrashCauses { get; set; } + public IReadOnlyCollection Details { get; set; } +} diff --git a/MinecraftLaunch/Components/Analyzer/GameLogAnalyzer.cs b/MinecraftLaunch/Components/Analyzer/GameLogAnalyzer.cs new file mode 100644 index 0000000..8bb81b9 --- /dev/null +++ b/MinecraftLaunch/Components/Analyzer/GameLogAnalyzer.cs @@ -0,0 +1,415 @@ +using MinecraftLaunch.Extensions; +using MinecraftLaunch.Classes.Enums; +using MinecraftLaunch.Classes.Models.Game; +using MinecraftLaunch.Classes.Models.Launch; + +using System.Collections.Immutable; +using System.Text.RegularExpressions; + +namespace MinecraftLaunch.Components.Analyzer; + +// Reference: https://github.com/Hex-Dragon/PCL2 +// Reference: https://github.com/huanghongxun/HMCL +// Reference: https://github.com/Corona-Studio/ProjBobcat + +/// +/// 游戏崩溃分析器 +/// +public sealed partial class GameCrashAnalyzer(GameEntry gameEntry, bool isIndependencyCore) { + private IEnumerable _gameLogs; + private List _crashLogs = new(); + + private readonly GameEntry _gameEntry = gameEntry; + private readonly bool _isIndependencyCore = isIndependencyCore; + private readonly Dictionary _logCrashCauses = new() { + { ".J9VMInternals.", CrashCauses.OpenJ9Use }, + { "OpenJ9 is incompatible", CrashCauses.OpenJ9Use }, + { "Out of Memory Error", CrashCauses.NoEnoughMemory }, + { "Open J9 is not supported", CrashCauses.OpenJ9Use }, + { "1282: Invalid operation", CrashCauses.OpenGl1282Error }, + { "maximum id range exceeded", CrashCauses.ModIdExceeded }, + { "Caught exception from ", CrashCauses.ModCausedGameCrash }, + { "java.lang.OutOfMemoryError", CrashCauses.NoEnoughMemory }, + { "java.lang.ClassCastException: class jdk.", CrashCauses.JdkUse }, + { "Couldn't set pixel format", CrashCauses.UnableToSetPixelFormat }, + { "java.lang.ClassCastException: java.base/jdk", CrashCauses.JdkUse }, + { "Pixel format not accelerated", CrashCauses.UnableToSetPixelFormat }, + { "java.lang.NoSuchFieldException: ucp", CrashCauses.JavaVersionTooHigh }, + { "Manually triggered debug crash", CrashCauses.ManuallyTriggeredDebugCrash }, + { "Unsupported class file major version", CrashCauses.UnsupportedJavaVersion }, + { "because module java.base does not export", CrashCauses.JavaVersionTooHigh }, + { "The system is out of physical RAM or swap space", CrashCauses.NoEnoughMemory }, + { "Extracted mod jars found, loading will NOT continue", CrashCauses.DecompressedMod }, + { "The driver does not appear to support OpenGL", CrashCauses.GpuDoesNotSupportOpenGl }, + { "Maybe try a lower resolution resourcepack?", CrashCauses.TextureTooLargeOrLowEndGpu }, + { "java.lang.ClassNotFoundException: java.lang.invoke.LambdaMetafactory", CrashCauses.JavaVersionTooHigh }, + { + "TRANSFORMER/net.optifine/net.optifine.reflect.Reflector.(Reflector.java", + CrashCauses.IncompatibleForgeAndOptifine + }, + { + "Found multiple arguments for option fml.forgeVersion, but you asked for only one", + CrashCauses.MultipleForgeInVersionJson + }, + { + "java.lang.ClassNotFoundException: jdk.nashorn.api.scripting.NashornScriptEngineFactory", + CrashCauses.JavaVersionTooHigh + }, + { + "The directories below appear to be extracted jar files. Fix this before you continue.", + CrashCauses.DecompressedMod + }, + { + "java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier", + CrashCauses.LegacyForgeDoesNotSupportNewerJava + }, + { + "java.lang.UnsupportedClassVersionError: net/fabricmc/loader/impl/launch/knot/KnotClient : Unsupported major.minor version", + CrashCauses.UnsupportedJavaVersion + }, + { + "java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.block.model.BakedQuad.(int[], int, net.minecraft.core.Direction, net.minecraft.client.renderer.texture.TextureAtlasSprite, boolean, boolean)'", + CrashCauses.IncompatibleForgeAndOptifine + }, + { + "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)'", + CrashCauses.IncompatibleForgeAndOptifine + } + }; + + #region Regex + + [GeneratedRegex(@"(?<=\tBlock: Block\{)[^\}]+")] + private static partial Regex BlockMatch(); + + [GeneratedRegex(@"(?<=\tBlock location: World: )\([^\)]+\)")] + private static partial Regex BlockLocationMatch(); + + [GeneratedRegex(@"(?<=\tEntity Type: )[^\n]+(?= \()")] + private static partial Regex EntityMatch(); + + [GeneratedRegex(@"(?<=\tEntity's Exact location: )[^\n]+")] + private static partial Regex EntityLocationMatch(); + + [GeneratedRegex("(?<=Failed to create mod instance. ModID: )[^,]+")] + private static partial Regex ModInstanceMatch1(); + + [GeneratedRegex(@"(?<=Failed to create mod instance. ModId )[^\n]+(?= for )")] + private static partial Regex ModInstanceMatch2(); + + [GeneratedRegex(@"(?<=\]: Warnings were found! ?[\n]+)[\w\W]+?(?=[\n]+\[)")] + private static partial Regex WarningsMatch(); + + [GeneratedRegex("(?<=class \")[^']+(?=\"'s signer information)")] + private static partial Regex PackSignerMatch(); + + [GeneratedRegex(@"(?<=the game will display an error screen and halt[\s\S]+?Exception: )[\s\S]+?(?=\n\tat)")] + private static partial Regex ForgeErrorMatch(); + + [GeneratedRegex(@"(?<=A potential solution has been determined:\n)((\t)+ - [^\n]+\n)+")] + private static partial Regex FabricSolutionMatch(); + + [GeneratedRegex(@"(?<=\n\t[\w]+ : [A-Z]{1}:[^\n]+(/|\\))[^/\\\n]+?.jar", RegexOptions.IgnoreCase)] + private static partial Regex GameModMatch1(); + + [GeneratedRegex(@"Found a duplicate mod[^\n]+", RegexOptions.IgnoreCase)] + private static partial Regex GameModMatch2(); + + [GeneratedRegex(@"ModResolutionException: Duplicate[^\n]+", RegexOptions.IgnoreCase)] + private static partial Regex GameModMatch3(); + + [GeneratedRegex("(?<=in )[^./ ]+(?=.mixins.json.+failed injection check)")] + private static partial Regex ModIdMatch1(); + + [GeneratedRegex("(?<= failed .+ in )[^./ ]+(?=.mixins.json)")] + private static partial Regex ModIdMatch2(); + + [GeneratedRegex(@"(?<= in config \[)[^./ ]+(?=.mixins.json\] FAILED during )")] + private static partial Regex ModIdMatch3(); + + [GeneratedRegex("(?<= in callback )[^./ ]+(?=.mixins.json:)")] + private static partial Regex ModIdMatch4(); + + [GeneratedRegex(@"^[^\n.]+.\w+.[^\n]+\n\[$")] + private static partial Regex MainClassMatch1(); + + [GeneratedRegex(@"^\[[^\]]+\] [^\n.]+.\w+.[^\n]+\n\[")] + private static partial Regex MainClassMatch2(); + + [GeneratedRegex("(?<=Mod File: ).+")] + private static partial Regex ModFileMatch(); + + [GeneratedRegex(@"(?<=Failure message: )[\w\W]+?(?=\tMod)")] + private static partial Regex ModLoaderMatch(); + + [GeneratedRegex("(?<=Multiple entries with same key: )[^=]+")] + private static partial Regex MultipleEntriesMatch(); + + [GeneratedRegex("(?<=due to errors, provided by ')[^']+")] + private static partial Regex ProvidedByMatch(); + + [GeneratedRegex(@"(?<=LoaderExceptionModCrash: Caught exception from )[^\n]+")] + private static partial Regex ModCausedCrashMatch(); + + [GeneratedRegex(@"(?<=Failed loading config file .+ for modid )[^\n]+")] + private static partial Regex ConfigFileMatch1(); + + [GeneratedRegex("(?<=Failed loading config file ).+(?= of type)")] + private static partial Regex ConfigFileMatch2(); + + #endregion + + /// + /// 分析日志 + /// + public IEnumerable AnalysisLogs() { + GetAllLogs(); + + var result = FuzzyProcessLogs() + .Union(SpecificProcessGameLogs()) + .Union(SpecificProcessCrashLogs()) + .GroupBy(x => x.CrashCauses) + .Select(y => y.First()); + + foreach (var report in result) { + yield return report; + } + } + + /// + /// 获取所有日志 + /// + private void GetAllLogs() { + var gamePath = Path.Combine(_isIndependencyCore + ? _gameEntry.OfVersionDirectoryPath(_isIndependencyCore) + : _gameEntry.GameFolderPath); + + _gameLogs = ReadAllLine(Path.Combine(gamePath, "logs", "latest.log").ToFileInfo()); + + var crashes = new List(); + var crashReports = new DirectoryInfo(Path.Combine(gamePath, "crash-reports")); + if (crashReports.Exists) { + crashes.AddRange(crashReports.EnumerateFiles().Where(fi => fi.Extension is ".log" or ".txt")); + } + + foreach (var item in crashes) { + _crashLogs.AddRange(ReadAllLine(item)); + } + + string[] ReadAllLine(FileInfo file) { + return file.Exists ? File.ReadAllLines(file.FullName) : default; + } + } + + /// + /// 模糊处理日志 + /// + private IEnumerable FuzzyProcessLogs() { + var allLogs = _gameLogs.Union(_crashLogs).ToImmutableArray(); + return allLogs.SelectMany(log => _logCrashCauses, (log, item) => new { log, item }) + .Where(t => t.log.Contains(t.item.Key)) + .Select(t => new CrashReport { + Original = t.log, + CrashCauses = t.item.Value + }); + } + + /// + /// 精确处理游戏日志 + /// + private IEnumerable SpecificProcessGameLogs() { + foreach (var log in _gameLogs.ToImmutableArray()) { + if (MainClassMatch1().IsMatch(log) || (MainClassMatch2().IsMatch(log) && !log.Contains("at net."))) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.IncorrectPathEncodingOrMainClassNotFound + }; + } + + if (log.Contains("]: Warnings were found!")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.FabricError, + Details = [WarningsMatch().Match(log).Value] + }; + } + + if (log.Contains("Failed to create mod instance.")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ModInitFailed, + Details = [ModInstanceMatch1().Match(log).Value, ModInstanceMatch2().Match(log).Value] + }; + } + + if (log.Contains("java.lang.NoSuchMethodError: net.minecraft.world.server.ChunkManager$ProxyTicketManager.shouldForceTicks(J)Z") + && log.Contains("OptiFine")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.FailedToLoadWorldBecauseOptiFine + }; + } + + if (log.Contains("Could not reserve enough space")) { + if (log.Contains("for 1048576KB object heap")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.NoEnoughMemory32 + }; + + } else { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.NoEnoughMemory + }; + } + } + + if (log.Contains("signer information does not match signer information of other classes in the same package")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ContentValidationFailed, + Details = [PackSignerMatch().Match(log).Value] + }; + } + + if (log.Contains("An exception was thrown, the game will display an error screen and halt.")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ForgeError, + Details = [ForgeErrorMatch().Match(log).Value] + }; + } + + if (log.Contains("A potential solution has been determined:")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.FabricErrorWithSolution, + Details = [FabricSolutionMatch().Match(log).Value] + }; + } + + if (log.Contains("DuplicateModsFoundException")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.DuplicateMod, + Details = [GameModMatch1().Match(log).Value] + }; + } + + if (log.Contains("Found a duplicate mod")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.DuplicateMod, + Details = [GameModMatch2().Match(log).Value] + }; + } + + if (log.Contains("ModResolutionException: Duplicate")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.DuplicateMod, + Details = [GameModMatch3().Match(log).Value] + }; + } + + if (log.Contains("Mixin prepare failed ") + || log.Contains("Mixin apply failed ") + || log.Contains("mixin.injection.throwables.") + || log.Contains(".mixins.json] FAILED during )")) { + var modId = ModIdMatch1().Match(log).Value; + + if (string.IsNullOrEmpty(modId)) { + modId = ModIdMatch2().Match(log).Value; + } + + if (string.IsNullOrEmpty(modId)) { + modId = ModIdMatch3().Match(log).Value; + } + + if (string.IsNullOrEmpty(modId)) { + modId = ModIdMatch4().Match(log).Value; + } + + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ModMixinFailed, + Details = string.IsNullOrEmpty(modId) ? null : [modId] + }; + } + } + } + + /// + /// 精确处理崩溃日志 + /// + /// + private IEnumerable SpecificProcessCrashLogs() { + foreach (var log in _crashLogs.ToImmutableArray()) { + if (log.Contains("-- MOD ")) { + var modLogs = log.Split("-- MOD").Last(); + if (modLogs.Contains("Failure message: MISSING")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ModLoaderError, + Details = [ModFileMatch().Match(log).Value], + }; + } else { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ModCausedGameCrash, + Details = [ModLoaderMatch().Match(log).Value] + }; + } + } + + if (log.Contains("Multiple entries with same key: ")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ModCausedGameCrash, + Details = [MultipleEntriesMatch().Match(log).Value] + }; + } + + if (log.Contains("due to errors, provided by ")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ModCausedGameCrash, + Details = [ProvidedByMatch().Match(log).Value] + }; + } + + if (log.Contains("LoaderExceptionModCrash: Caught exception from ")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ModCausedGameCrash, + Details = [ModCausedCrashMatch().Match(log).Value] + }; + } + + if (log.Contains("Failed loading config file ")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.ModCausedGameCrash, + Details = [ConfigFileMatch1().Match(log).Value, ConfigFileMatch2().Match(log).Value] + }; + } + + if (log.Contains("Block location: World: ")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.BlockCausedGameCrash, + Details = [BlockMatch().Match(log).Value, BlockLocationMatch().Match(log).Value] + }; + } + + if (log.Contains("Entity's Exact location: ")) { + yield return new CrashReport { + Original = log, + CrashCauses = CrashCauses.EntityCausedGameCrash, + Details = [EntityMatch().Match(log).Value, EntityLocationMatch().Match(log).Value] + }; + } + } + } +} \ No newline at end of file diff --git a/MinecraftLaunch/Components/Resolver/GameLogResolver.cs b/MinecraftLaunch/Components/Resolver/GameCrashAnalyzer.cs similarity index 100% rename from MinecraftLaunch/Components/Resolver/GameLogResolver.cs rename to MinecraftLaunch/Components/Resolver/GameCrashAnalyzer.cs diff --git a/MinecraftLaunch/Components/Watcher/GameProcessWatcher.cs b/MinecraftLaunch/Components/Watcher/GameProcessWatcher.cs index 837dce2..e897c58 100644 --- a/MinecraftLaunch/Components/Watcher/GameProcessWatcher.cs +++ b/MinecraftLaunch/Components/Watcher/GameProcessWatcher.cs @@ -44,7 +44,7 @@ private void OnExited(object sender, EventArgs e) { private void OnOutputDataReceived(object sender, DataReceivedEventArgs e) { if (!string.IsNullOrEmpty(e.Data)) { var log = _gameLogResolver.Resolve(e.Data); - OutputLogReceived?.Invoke(this, new(log.Log, log.Time, log.Time, log.LogType)); + OutputLogReceived?.Invoke(this, new(e.Data, log.Log, log.Time, log.Time, log.LogType)); } } } \ No newline at end of file diff --git a/MinecraftLaunch/MinecraftLaunch.csproj b/MinecraftLaunch/MinecraftLaunch.csproj index 3f2b51e..7b41117 100644 --- a/MinecraftLaunch/MinecraftLaunch.csproj +++ b/MinecraftLaunch/MinecraftLaunch.csproj @@ -1,6 +1,6 @@  - 3.0.07 + 3.1.0-preview01