using PCL.Core.App.Localization; using PCL.Core.Logging; using PCL.Core.Utils; using PCL.Core.Utils.Exts; namespace PCL; internal sealed class CrashResultFormatter { public CrashDialogContent Format( CrashAnalysisContext context, bool isHandAnalyze) { var result = context.Result ?? new CrashAnalysisResult(); if (!result.Any) return _CreateUnknownContent(isHandAnalyze); var specs = result.Findings .Select(finding => _CreateMessageSpec(finding, context.LogAll)) .ToList(); var text = _JoinParagraphs(specs.Select(_Render)); if (!isHandAnalyze) text = _AppendFollowUps(text, specs); return new CrashDialogContent( _NormalizeLineEndings(text).Trim('\r', '\n'), specs[0].SuggestedAction); } private static CrashDialogContent _CreateUnknownContent(bool isHandAnalyze) { var spec = isHandAnalyze ? _Spec("Crash.Reason.Unknown.Manual") : _Spec("Crash.Reason.Unknown.Auto", "Crash.Suggestion.ExportReport"); return new CrashDialogContent( _NormalizeLineEndings(_Render(spec)).Trim('\r', '\n'), CrashSuggestedAction.None); } private static string _AppendFollowUps( string text, IReadOnlyCollection specs) { var followUps = new List(); if (specs.Any(spec => spec.AppendReportHint)) followUps.Add(Lang.Text("Crash.Suggestion.ViewReport")); if (specs.Any(spec => spec.AppendHelpHint)) followUps.Add(Lang.Text("Crash.Suggestion.ExportReport")); var launcherOutdatedSuggestion = _GetLauncherOutdatedSuggestion(); if (!string.IsNullOrWhiteSpace(launcherOutdatedSuggestion)) followUps.Add(launcherOutdatedSuggestion); return followUps.Count == 0 ? text : Lang.Text( "Crash.Presentation.WithFollowUps", text, _JoinParagraphs(followUps)); } private static string _Render(CrashMessageSpec spec) { var reason = Lang.Text(spec.ReasonKey, spec.ReasonArgs); if (spec.SuggestionKey is null) return Lang.Text("Crash.Presentation.ReasonOnly", reason); return Lang.Text( "Crash.Presentation.WithSuggestion", reason, Lang.Text(spec.SuggestionKey, spec.SuggestionArgs)); } private static CrashMessageSpec _CreateMessageSpec( CrashFinding finding, string combinedLogText) { var additional = finding.Details.ToList(); switch (finding.Cause) { case CrashCause.ExtractedModFile: return _Spec( "Crash.Reason.ExtractedModFile", "Crash.Suggestion.ExtractedModFile"); case CrashCause.OutOfMemory: return _Spec( "Crash.Reason.OutOfMemory", "Crash.Suggestion.OutOfMemory", help: true); case CrashCause.UsingOpenJ9: return _Spec( "Crash.Reason.UsingOpenJ9", "Crash.Suggestion.UsingOpenJ9"); case CrashCause.UsingJdk: return _Spec( "Crash.Reason.UsingJdk", "Crash.Suggestion.UsingJdk"); case CrashCause.JavaTooNew: return _Spec( "Crash.Reason.JavaTooNew", "Crash.Suggestion.JavaTooNew"); case CrashCause.JavaIncompatible: return _Spec( "Crash.Reason.JavaIncompatible", "Crash.Suggestion.JavaIncompatible"); case CrashCause.InvalidModFileName: return _Spec( "Crash.Reason.InvalidModFileName", "Crash.Suggestion.InvalidModFileName"); case CrashCause.MissingMixinBootstrap: return _Spec( "Crash.Reason.MissingMixinBootstrap", "Crash.Suggestion.MissingMixinBootstrap"); case CrashCause.X86JavaMemoryLimit: return Environment.Is64BitOperatingSystem ? _Spec( "Crash.Reason.X86JavaMemoryLimit.OnX64Os", "Crash.Suggestion.X86JavaMemoryLimit.OnX64Os") : _Spec( "Crash.Reason.X86JavaMemoryLimit.OnX86Os", "Crash.Suggestion.X86JavaMemoryLimit.OnX86Os", help: true); case CrashCause.MissingDependencyOrWrongMcVersion: return _FormatMissingDependency(additional); case CrashCause.StackKeywordFound: return additional.Count == 1 ? _Spec( "Crash.Reason.StackKeyword.Single", "Crash.Suggestion.StackKeyword", _Args(additional[0]), help: true) : _Spec( "Crash.Reason.StackKeyword.Multiple", "Crash.Suggestion.StackKeyword", _Args(_BulletList(additional)), help: true); case CrashCause.StackModNameFound or CrashCause.SuspectedModCrash: return additional.Count == 1 ? _Spec( "Crash.Reason.SuspectedMod.Single", "Crash.Suggestion.DisableThisMod", _Args(additional[0]), report: true, help: true) : _Spec( "Crash.Reason.SuspectedMod.Multiple", "Crash.Suggestion.DisableTheseMods", _Args(_BulletList(additional)), report: true, help: true); case CrashCause.ConfirmedModCrash: return additional.Count == 1 ? _Spec( "Crash.Reason.ConfirmedMod.Single", "Crash.Suggestion.DisableThisMod", _Args(additional[0]), report: true, help: true) : _Spec( "Crash.Reason.ConfirmedMod.Multiple", "Crash.Suggestion.DisableTheseMods", _Args(_BulletList(additional)), report: true, help: true); case CrashCause.ModMixinFailed: if (additional.Count == 0) return _Spec( "Crash.Reason.ModMixinFailed.None", "Crash.Suggestion.ModMixinFailed", report: true, help: true); return additional.Count == 1 ? _Spec( "Crash.Reason.ModMixinFailed.Single", "Crash.Suggestion.DisableThisMod", _Args(additional[0]), report: true, help: true) : _Spec( "Crash.Reason.ModMixinFailed.Multiple", "Crash.Suggestion.DisableTheseMods", _Args(_BulletList(additional)), report: true, help: true); case CrashCause.ModConfigCrash: return additional.Count > 1 && additional[1] is not null ? _Spec( "Crash.Reason.ModConfigCrash.WithConfig", "Crash.Suggestion.ModConfigCrash", _Args(additional[0], additional[1])) : _Spec( "Crash.Reason.ModConfigCrash.Simple", "Crash.Suggestion.DisableThisMod", _Args(additional.FirstOrDefault() ?? string.Empty), report: true, help: true); case CrashCause.ModInitializationFailed: return additional.Count == 1 ? _Spec( "Crash.Reason.ModInitializationFailed.Single", "Crash.Suggestion.DisableThisMod", _Args(additional[0]), report: true, help: true) : _Spec( "Crash.Reason.ModInitializationFailed.Multiple", "Crash.Suggestion.DisableTheseMods", _Args(_BulletList(additional)), report: true, help: true); case CrashCause.SpecificBlockCrash: return additional.Count == 1 ? _Spec( "Crash.Reason.SpecificBlock.Single", "Crash.Suggestion.SpecificBlock.Single", _Args(additional[0]), help: true) : _Spec( "Crash.Reason.SpecificBlock.Multiple", "Crash.Suggestion.SpecificBlock.Multiple", help: true); case CrashCause.DuplicateMods: return additional.Count >= 2 ? _Spec( "Crash.Reason.DuplicateMods.Known", "Crash.Suggestion.DuplicateMods", _Args(_BulletList(additional))) : _Spec( "Crash.Reason.DuplicateMods.Unknown", "Crash.Suggestion.DuplicateMods", report: true, help: true); case CrashCause.SpecificEntityCrash: return additional.Count == 1 ? _Spec( "Crash.Reason.SpecificEntity.Single", "Crash.Suggestion.SpecificEntity.Single", _Args(additional[0]), help: true) : _Spec( "Crash.Reason.SpecificEntity.Multiple", "Crash.Suggestion.SpecificEntity.Multiple", help: true); case CrashCause.OptiFineForgeIncompatible: return _Spec( "Crash.Reason.OptiFineForgeIncompatible", "Crash.Suggestion.OptiFineForgeIncompatible"); case CrashCause.ShadersModWithOptiFine: return _Spec( "Crash.Reason.ShadersModWithOptiFine", "Crash.Suggestion.ShadersModWithOptiFine"); case CrashCause.OldForgeNewJavaIncompatible: return _Spec( "Crash.Reason.OldForgeNewJavaIncompatible", "Crash.Suggestion.OldForgeNewJavaIncompatible"); case CrashCause.MultipleForgeInInstanceJson: return _Spec( "Crash.Reason.MultipleForgeInInstanceJson", "Crash.Suggestion.MultipleForgeInInstanceJson"); case CrashCause.ManualDebugCrash: return _Spec("Crash.Reason.ManualDebugCrash"); case CrashCause.ModRequiresJava11: return _Spec( "Crash.Reason.ModRequiresJava11", "Crash.Suggestion.ModRequiresJava11"); case CrashCause.VeryShortOutput: return _Spec( "Crash.Reason.VeryShortOutput", "Crash.Suggestion.ExportReport", _Args(additional.FirstOrDefault() ?? string.Empty)); case CrashCause.OptiFineWorldLoadCrash: return _Spec( "Crash.Reason.OptiFineWorldLoadCrash", "Crash.Suggestion.OptiFineWorldLoadCrash", help: true); case CrashCause.PixelFormatNotSupported or CrashCause.IntelDriverAccessViolation or CrashCause.AmdDriverAccessViolation or CrashCause.NvidiaDriverAccessViolation or CrashCause.UnsupportedOpenGl: return combinedLogText.Contains("hd graphics ") ? _Spec( "Crash.Reason.GraphicsDriver.IntelOrIntegrated", "Crash.Suggestion.GraphicsDriver.IntelOrIntegrated", help: true) : _Spec( "Crash.Reason.GraphicsDriver.Generic", "Crash.Suggestion.GraphicsDriver.Generic", help: true); case CrashCause.ResourcePackTooLarge: return _Spec( "Crash.Reason.ResourcePackTooLarge", "Crash.Suggestion.ResourcePackTooLarge", help: true); case CrashCause.NightConfigBug: return _Spec( "Crash.Reason.NightConfigBug", "Crash.Suggestion.NightConfigBug", help: true); case CrashCause.OpenGl1282: return _Spec( "Crash.Reason.OpenGl1282", "Crash.Suggestion.OpenGl1282", help: true); case CrashCause.TooManyModsIdLimit: return _Spec( "Crash.Reason.TooManyModsIdLimit", "Crash.Suggestion.TooManyModsIdLimit"); case CrashCause.FileOrContentValidationFailed: return _Spec( "Crash.Reason.FileOrContentValidationFailed", "Crash.Suggestion.FileOrContentValidationFailed", help: true); case CrashCause.IncompleteForgeInstallation: return _Spec( "Crash.Reason.IncompleteForgeInstallation", "Crash.Suggestion.IncompleteForgeInstallation", help: true); case CrashCause.FabricError: return additional.Count == 1 ? _Spec( "Crash.Reason.FabricError.WithDetail", "Crash.Suggestion.FollowLoaderInstructions", _Args(additional[0])) : _Spec( "Crash.Reason.FabricError.Generic", "Crash.Suggestion.FollowLoaderInstructions", help: true); case CrashCause.IncompatibleMods: return _FormatIncompatibleMods(additional); case CrashCause.ModLoaderError: return additional.Count == 1 ? _Spec( "Crash.Reason.ModLoaderError.WithDetail", "Crash.Suggestion.FollowLoaderInstructions", _Args(additional[0])) : _Spec( "Crash.Reason.ModLoaderError.Generic", "Crash.Suggestion.FollowLoaderInstructions", help: true); case CrashCause.FabricSolutionProvided: return additional.Count == 1 ? _Spec( "Crash.Reason.FabricSolution.WithDetail", "Crash.Suggestion.FollowLoaderInstructions", _Args(additional[0])) : _Spec( "Crash.Reason.FabricSolution.Generic", "Crash.Suggestion.FollowLoaderInstructions", help: true); case CrashCause.ForgeError: return additional.Count == 1 ? _Spec( "Crash.Reason.ForgeError.WithDetail", "Crash.Suggestion.FollowLoaderInstructions", _Args(additional[0])) : _Spec( "Crash.Reason.ForgeError.Generic", "Crash.Suggestion.FollowLoaderInstructions", help: true); case CrashCause.NoAnalyzableFile: return _Spec( "Crash.Reason.NoAnalyzableFile", "Crash.Suggestion.ExportReport", help: true); default: return _Spec( "Crash.Reason.UnknownFinding", "Crash.Suggestion.ExportReport", _Args(finding.Cause.ToString()), help: true); } } private static CrashMessageSpec _FormatMissingDependency(List additional) { if (additional.Count == 0) return _Spec( "Crash.Reason.MissingDependency.Generic", "Crash.Suggestion.FollowLoaderInstructions", help: true); var info = _BulletList(additional); return info.IsMatch(RegexPatterns.IncompatibleModLoaderErrorHint) ? _Spec( "Crash.Reason.ModLoaderIncompatible", "Crash.Suggestion.ModLoaderIncompatible", _Args(info), action: CrashSuggestedAction.OpenInstanceSettings) : _Spec( "Crash.Reason.MissingDependency.WithDetail", "Crash.Suggestion.FollowLoaderInstructions", _Args(info)); } private static CrashMessageSpec _FormatIncompatibleMods(List additional) { if (additional.Count != 1) return _Spec( "Crash.Reason.IncompatibleMods.Generic", "Crash.Suggestion.FollowLoaderInstructions", help: true); var info = additional[0]; return info.IsMatch(RegexPatterns.IncompatibleModLoaderErrorHint) ? _Spec( "Crash.Reason.ModLoaderIncompatible", "Crash.Suggestion.ModLoaderIncompatible", _Args(info), action: CrashSuggestedAction.OpenInstanceSettings) : _Spec( "Crash.Reason.IncompatibleMods.WithDetail", "Crash.Suggestion.FollowLoaderInstructions", _Args(info)); } private static CrashMessageSpec _Spec( string reasonKey, string? suggestionKey = null, object?[]? reasonArgs = null, object?[]? suggestionArgs = null, bool report = false, bool help = false, CrashSuggestedAction action = CrashSuggestedAction.None) { return new CrashMessageSpec( reasonKey, reasonArgs ?? [], suggestionKey, suggestionArgs ?? [], report, help, action); } private static object?[] _Args(params object?[] args) { return args; } private static string _BulletList(IEnumerable items) { return string.Join( Environment.NewLine, items.Select(item => Lang.Text("Crash.Presentation.ListItem", item))); } private static string _JoinParagraphs(IEnumerable items) { return string.Join( Environment.NewLine + Environment.NewLine, items.Where(item => !string.IsNullOrWhiteSpace(item))); } private static string _NormalizeLineEndings(string text) { return text .Replace("\r\n", "\r") .Replace("\n", "\r") .Replace("\r", "\r\n"); } private static string? _GetLauncherOutdatedSuggestion() { try { return UpdateManager.GetVersionStatus() == UpdateEnums.VersionStatus.Latest ? null : Lang.Text("Crash.Suggestion.LauncherOutdated"); } catch (Exception ex) { LogWrapper.Error(ex, "Crash", "确认启动器更新失败"); return null; } } }