feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,121 @@
using PCL.Core.Logging;
namespace PCL;
internal sealed class CrashDetector
{
private readonly CrashEvidenceExtractor _extractor = new();
private readonly CrashStackAnalyzer _stackAnalyzer = new();
public CrashAnalysisResult Analyze(CrashLogSet logs, McInstance? instance)
{
LogWrapper.Info("Crash", "步骤 3:分析崩溃原因");
var result = new CrashAnalysisResult();
if (!logs.HasAnalyzableLog)
{
result.Add(new CrashFinding(CrashCause.NoAnalyzableFile, CrashConfidence.High) { ShouldStop = true });
_LogSummary(result);
return result;
}
var normalizedLogs = _Normalize(logs);
var modIndex = CrashModIndex.Create(normalizedLogs, instance);
_RunPhase(DetectionPhase.Fatal, normalizedLogs, modIndex, result);
if (_ShouldStop(result))
{
_LogSummary(result);
return result;
}
_RunPhase(DetectionPhase.Primary, normalizedLogs, modIndex, result);
if (_ShouldStop(result))
{
_LogSummary(result);
return result;
}
var stackFinding = _stackAnalyzer.Analyze(normalizedLogs, modIndex);
if (stackFinding is not null)
result.Add(stackFinding);
if (_ShouldStop(result))
{
_LogSummary(result);
return result;
}
_RunPhase(DetectionPhase.Secondary, normalizedLogs, modIndex, result);
_LogSummary(result);
return result;
}
private static CrashLogSet _Normalize(CrashLogSet logs)
{
var all = logs.All;
if (all.Contains("quilt", StringComparison.OrdinalIgnoreCase) &&
all.Contains("Mod Table Version", StringComparison.Ordinal))
{
LogWrapper.Info("Crash", "处理 Quilt Mod Table 后再继续分析");
all = CrashText.BeforeFirst(all, "| Index") + CrashText.AfterFirst(all, "Mod Table Version:");
}
return new CrashLogSet
{
Game = logs.Game,
Debug = logs.Debug,
CrashReport = logs.CrashReport,
HsErr = logs.HsErr,
All = all
};
}
private void _RunPhase(
DetectionPhase phase,
CrashLogSet logs,
CrashModIndex modIndex,
CrashAnalysisResult result)
{
var input = new CrashRuleInput { Logs = logs, ModIndex = modIndex };
foreach (var rule in CrashRuleCatalog.Rules.Where(rule => rule.Phase == phase))
{
var finding = rule.Evaluate(input);
if (finding is not null)
_AddAndLog(result, finding);
}
foreach (var finding in _extractor.Extract(phase, logs, modIndex))
_AddAndLog(result, finding);
}
private static void _AddAndLog(CrashAnalysisResult result, CrashFinding finding)
{
result.Add(finding);
_LogFinding(finding);
}
private static bool _ShouldStop(CrashAnalysisResult result)
{
return result.Findings.Any(finding => finding.ShouldStop);
}
private static void _LogFinding(CrashFinding finding)
{
var evidence = string.Join("", finding.Details);
LogWrapper.Info(
"Crash",
$"可能的崩溃原因:{finding.Cause}{(string.IsNullOrEmpty(evidence) ? "" : "" + evidence + "")}");
}
private static void _LogSummary(CrashAnalysisResult result)
{
if (!result.Any)
{
LogWrapper.Info("Crash", "步骤 3:分析崩溃原因完成,未找到可能的原因");
return;
}
LogWrapper.Info("Crash", $"步骤 3:分析崩溃原因完成,找到 {result.Findings.Count} 条可能的原因");
foreach (var finding in result.Findings)
_LogFinding(finding);
}
}
@@ -0,0 +1,714 @@
using System.Text.RegularExpressions;
namespace PCL;
internal sealed class CrashEvidenceExtractor
{
public IEnumerable<CrashFinding> Extract(
DetectionPhase phase,
CrashLogSet logs,
CrashModIndex modIndex)
{
return phase switch
{
DetectionPhase.Fatal => _ExtractFatal(logs, modIndex),
DetectionPhase.Primary => _ExtractPrimary(logs, modIndex),
DetectionPhase.Secondary => _ExtractSecondary(logs, modIndex),
_ => []
};
}
private static IEnumerable<CrashFinding> _ExtractFatal(
CrashLogSet logs,
CrashModIndex modIndex)
{
foreach (var finding in _ExtractOutOfMemory(logs))
yield return finding;
var memoryReservation = _ExtractJvmMemoryReservation(logs);
if (memoryReservation is not null)
yield return memoryReservation;
foreach (var finding in _ExtractAccessViolation(logs))
yield return finding;
var signer = _ExtractSignerValidationFailure(logs);
if (signer is not null)
yield return signer;
var optifineMissingMods = _ExtractOptiFineMissingMods(logs);
if (optifineMissingMods is not null)
yield return optifineMissingMods;
foreach (var finding in _ExtractConfirmedModCrash(logs, modIndex))
yield return finding;
foreach (var finding in _ExtractDuplicateMods(logs))
yield return finding;
foreach (var finding in _ExtractMissingDependency(logs))
yield return finding;
}
private static IEnumerable<CrashFinding> _ExtractPrimary(
CrashLogSet logs,
CrashModIndex modIndex)
{
foreach (var finding in _ExtractMixinFailure(logs, modIndex))
yield return finding;
foreach (var finding in _ExtractForgeError(logs))
yield return finding;
foreach (var finding in _ExtractFabricSolution(logs))
yield return finding;
foreach (var finding in _ExtractFabricProvidedModCrash(logs, modIndex))
yield return finding;
foreach (var finding in _ExtractSuspectedMods(logs, modIndex))
yield return finding;
}
private static IEnumerable<CrashFinding> _ExtractSecondary(
CrashLogSet logs,
CrashModIndex modIndex)
{
var shortOutput = _ExtractShortOutput(logs);
if (shortOutput is not null)
yield return shortOutput;
var modLoader = _ExtractModLoaderError(logs);
if (modLoader is not null)
yield return modLoader;
var modInitialization = _ExtractModInitializationFailure(logs, modIndex);
if (modInitialization is not null)
yield return modInitialization;
foreach (var finding in _ExtractSpecificBlockAndEntity(logs))
yield return finding;
}
private static IEnumerable<CrashFinding> _ExtractOutOfMemory(CrashLogSet logs)
{
var evidence = _FindPatterns(
logs,
[
(CrashLogKind.Game, "java.lang.OutOfMemoryError"),
(CrashLogKind.Game, "an out of memory error"),
(CrashLogKind.HsErr, "The system is out of physical RAM or swap space"),
(CrashLogKind.HsErr, "Out of Memory Error"),
(CrashLogKind.CrashReport, "java.lang.OutOfMemoryError")
])
.ToList();
if (evidence.Count == 0)
yield break;
yield return new CrashFinding(
CrashCause.OutOfMemory,
CrashConfidence.High,
evidence)
{
ShouldStop = true
};
}
private static CrashFinding? _ExtractJvmMemoryReservation(CrashLogSet logs)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog) ||
!gameLog.Contains("Could not reserve enough space", StringComparison.Ordinal))
return null;
var cause = gameLog.Contains("for 1048576KB object heap", StringComparison.Ordinal)
? CrashCause.X86JavaMemoryLimit
: CrashCause.OutOfMemory;
return new CrashFinding(
cause,
CrashConfidence.High,
[
_Evidence(
"Could not reserve enough space",
CrashLogKind.Game,
"jvm-message")
])
{
ShouldStop = true
};
}
private static IEnumerable<CrashFinding> _ExtractAccessViolation(CrashLogSet logs)
{
var hsLog = logs.HsErr?.Text;
if (string.IsNullOrEmpty(hsLog) ||
!hsLog.Contains("EXCEPTION_ACCESS_VIOLATION", StringComparison.Ordinal))
yield break;
if (hsLog.Contains("# C [ig", StringComparison.Ordinal))
yield return _DriverFinding(CrashCause.IntelDriverAccessViolation, "# C [ig");
if (hsLog.Contains("# C [atio", StringComparison.Ordinal))
yield return _DriverFinding(CrashCause.AmdDriverAccessViolation, "# C [atio");
if (hsLog.Contains("# C [nvoglv", StringComparison.Ordinal))
yield return _DriverFinding(CrashCause.NvidiaDriverAccessViolation, "# C [nvoglv");
}
private static CrashFinding _DriverFinding(
CrashCause cause,
string pattern)
{
return new CrashFinding(
cause,
CrashConfidence.High,
[_Evidence(pattern, CrashLogKind.HsErr, "driver-signature")])
{
ShouldStop = true
};
}
private static CrashFinding? _ExtractSignerValidationFailure(CrashLogSet logs)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog) ||
!gameLog.Contains(
"signer information does not match signer information of other classes in the same package",
StringComparison.Ordinal))
return null;
var detail = (CrashRegex.First(gameLog, "(?<=class \")[^']+(?=\"'s signer information)") ?? "")
.TrimEnd('\r', '\n');
return new CrashFinding(
CrashCause.FileOrContentValidationFailed,
CrashConfidence.High,
[_Evidence(detail, CrashLogKind.Game, "class")])
{
ShouldStop = true
};
}
private static CrashFinding? _ExtractOptiFineMissingMods(CrashLogSet logs)
{
var crash = logs.CrashReport?.Text;
if (string.IsNullOrEmpty(crash) ||
!crash.Contains("has mods that were not found", StringComparison.Ordinal) ||
!CrashRegex.IsMatch(crash, @"The Mod File [^\n]+optifine\\OptiFine[^\n]+ has mods that were not found"))
return null;
return new CrashFinding(
CrashCause.OptiFineForgeIncompatible,
CrashConfidence.High,
[_Evidence("OptiFine", CrashLogKind.CrashReport, "mod")])
{
ShouldStop = true
};
}
private static IEnumerable<CrashFinding> _ExtractConfirmedModCrash(
CrashLogSet logs,
CrashModIndex modIndex)
{
var gameLog = logs.Game?.Text;
if (!string.IsNullOrEmpty(gameLog) &&
gameLog.Contains("Caught exception from ", StringComparison.Ordinal))
{
var hint = CrashRegex.First(gameLog, @"(?<=Caught exception from )[^\n]+?")
?.TrimEnd('\r', '\n', ' ');
yield return _ModFinding(
CrashCause.ConfirmedModCrash,
modIndex.ResolveToDisplayNames([hint ?? ""]),
CrashLogKind.Game,
true);
}
var crash = logs.CrashReport?.Text;
if (string.IsNullOrEmpty(crash))
yield break;
if (crash.Contains("-- MOD ", StringComparison.Ordinal))
{
var modSection = CrashText.Between(crash, "-- MOD ", "Failure message:");
if (modSection.Contains(".jar", StringComparison.OrdinalIgnoreCase))
{
var fileName = (CrashRegex.First(modSection, "(?<=Mod File: ).+") ?? "")
.TrimEnd('\r', '\n', ' ');
yield return _ModFinding(
CrashCause.ConfirmedModCrash,
[fileName],
CrashLogKind.CrashReport,
true);
}
else
{
var message = (CrashRegex.First(crash, @"(?<=Failure message: )[\w\W]+?(?=\tMod)") ?? "")
.Replace("\t", " ")
.TrimEnd('\r', '\n', ' ');
yield return _ModFinding(
CrashCause.ModLoaderError,
[message],
CrashLogKind.CrashReport,
true,
"loader-message");
}
}
if (crash.Contains("Multiple entries with same key: ", StringComparison.Ordinal))
{
var hint = (CrashRegex.First(crash, "(?<=Multiple entries with same key: )[^=]+") ?? "")
.TrimEnd('\r', '\n', ' ');
yield return _ModFinding(
CrashCause.ConfirmedModCrash,
modIndex.ResolveToDisplayNames([hint]),
CrashLogKind.CrashReport,
true);
}
if (crash.Contains("LoaderExceptionModCrash: Caught exception from ", StringComparison.Ordinal))
{
var hint = (CrashRegex.First(crash, @"(?<=LoaderExceptionModCrash: Caught exception from )[^\n]+") ?? "")
.TrimEnd('\r', '\n', ' ');
yield return _ModFinding(
CrashCause.ConfirmedModCrash,
modIndex.ResolveToDisplayNames([hint]),
CrashLogKind.CrashReport,
true);
}
if (crash.Contains("Failed loading config file ", StringComparison.Ordinal))
{
var mod = (CrashRegex.First(crash, @"(?<=Failed loading config file .+ for modid )[^\n]+") ?? "")
.TrimEnd('\r', '\n');
var config = (CrashRegex.First(crash, "(?<=Failed loading config file ).+(?= of type)") ?? "")
.TrimEnd('\r', '\n');
var resolved = modIndex.ResolveToDisplayNames([mod]);
var evidence = new List<CrashEvidence>();
evidence.AddRange(resolved.Select(item => _Evidence(item, CrashLogKind.CrashReport, "mod")));
if (!string.IsNullOrWhiteSpace(config))
evidence.Add(_Evidence(config, CrashLogKind.CrashReport, "config"));
yield return new CrashFinding(
CrashCause.ModConfigCrash,
CrashConfidence.High,
evidence)
{
ShouldStop = true
};
}
}
private static IEnumerable<CrashFinding> _ExtractDuplicateMods(CrashLogSet logs)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog))
yield break;
if (gameLog.Contains("DuplicateModsFoundException", StringComparison.Ordinal))
yield return _ModFinding(
CrashCause.DuplicateMods,
CrashRegex.All(
gameLog,
@"(?<=\n\t[\w]+ : [A-Z]:[^\n]+(/|\\))[^/\\\n]+?.jar",
RegexOptions.IgnoreCase),
CrashLogKind.Game,
true);
if (gameLog.Contains("Found a duplicate mod", StringComparison.Ordinal))
yield return _ModFinding(
CrashCause.DuplicateMods,
CrashRegex.All(
CrashRegex.First(gameLog, @"Found a duplicate mod[^\n]+") ?? "",
@"[^\\/]+.jar",
RegexOptions.IgnoreCase),
CrashLogKind.Game,
true);
if (gameLog.Contains("Found duplicate mods", StringComparison.Ordinal))
yield return _ModFinding(
CrashCause.DuplicateMods,
CrashRegex.All(gameLog, @"(?<=Mod ID: ')\w+?(?=' from mod files:)")
.Distinct()
.ToList(),
CrashLogKind.Game,
true);
if (gameLog.Contains("ModResolutionException: Duplicate", StringComparison.Ordinal))
yield return _ModFinding(
CrashCause.DuplicateMods,
CrashRegex.All(
CrashRegex.First(gameLog, @"ModResolutionException: Duplicate[^\n]+") ?? "",
@"[^\\/]+.jar",
RegexOptions.IgnoreCase),
CrashLogKind.Game,
true);
}
private static IEnumerable<CrashFinding> _ExtractMissingDependency(CrashLogSet logs)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog))
yield break;
if (gameLog.Contains("Incompatible mods found!", StringComparison.Ordinal))
yield return _ModFinding(
CrashCause.IncompatibleMods,
[CrashRegex.First(gameLog, @"(?<=Incompatible mods found![\s\S]+: )[\s\S]+?(?=\tat )") ?? ""],
CrashLogKind.Game,
true,
"loader-message");
if (gameLog.Contains("Missing or unsupported mandatory dependencies:", StringComparison.Ordinal))
{
var details = CrashRegex.All(
gameLog,
@"(?<=Missing or unsupported mandatory dependencies:)([\n\r]+\t(.*))+",
RegexOptions.IgnoreCase)
.Select(item => item.Trim('\r', '\n', '\t', ' '))
.Distinct()
.ToList();
yield return _ModFinding(
CrashCause.MissingDependencyOrWrongMcVersion,
details,
CrashLogKind.Game,
true,
"loader-message");
}
}
private static IEnumerable<CrashFinding> _ExtractMixinFailure(
CrashLogSet logs,
CrashModIndex modIndex)
{
return from source in new[] { CrashLogKind.Game, CrashLogKind.CrashReport }
let text = logs.GetText(source)
where !string.IsNullOrEmpty(text) && _LooksLikeMixinFailure(text)
let modHints = _ExtractMixinModHints(text)
.Distinct(StringComparer.Ordinal)
.ToList()
let resolved = modIndex.ResolveToDisplayNames(modHints)
let hasResolved = modHints.Count > 0 && !modHints.SequenceEqual(resolved)
let evidence = (resolved.Count != 0 ? resolved : modHints)
.Select(item => _Evidence(item, source, hasResolved ? "mod" : "mod-hint"))
select new CrashFinding(
CrashCause.ModMixinFailed,
hasResolved ? CrashConfidence.High : CrashConfidence.Medium,
evidence)
{
ShouldStop = true
};
}
private static bool _LooksLikeMixinFailure(string text)
{
return text.Contains("Mixin prepare failed ", StringComparison.Ordinal) ||
text.Contains("Mixin apply failed ", StringComparison.Ordinal) ||
text.Contains("MixinApplyError", StringComparison.Ordinal) ||
text.Contains("MixinTransformerError", StringComparison.Ordinal) ||
text.Contains("mixin.injection.throwables.", StringComparison.Ordinal) ||
text.Contains(".json] FAILED during )", StringComparison.Ordinal);
}
private static IEnumerable<string> _ExtractMixinModHints(string text)
{
var patterns = new[]
{
new Regex(
@"(?<=from mod )[^.\/ ]+(?=\] from)",
RegexOptions.Compiled),
new Regex(
@"(?<=for mod )[^.\/ ]+(?= failed)",
RegexOptions.Compiled),
new Regex(
@"(?<=^[^\t]+[ \[{(]{1})[^ \[{(]+\.[^ ]+(?=\.json)",
RegexOptions.Compiled | RegexOptions.Multiline)
};
foreach (var pattern in patterns)
foreach (Match match in pattern.Matches(text))
{
var value = match.Value
.Replace("mixins", "mixin")
.Replace(".mixin", "")
.Replace("mixin.", "")
.Trim();
if (!string.IsNullOrEmpty(value))
yield return value;
}
}
private static IEnumerable<CrashFinding> _ExtractForgeError(CrashLogSet logs)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog) ||
!gameLog.Contains(
"An exception was thrown, the game will display an error screen and halt.",
StringComparison.Ordinal))
yield break;
var message = CrashRegex.First(
gameLog,
@"(?<=the game will display an error screen and halt.[\n\r]+[^\n]+?Exception: )[\s\S]+?(?=\n\tat)")
?.Trim('\r', '\n') ?? "";
yield return _ModFinding(
CrashCause.ForgeError,
[message],
CrashLogKind.Game,
true,
"loader-message");
}
private static IEnumerable<CrashFinding> _ExtractFabricSolution(CrashLogSet logs)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog))
yield break;
var solution = "";
if (gameLog.Contains("A potential solution has been determined:", StringComparison.Ordinal))
solution = string.Join(
"\n",
CrashRegex.All(
CrashRegex.First(gameLog, @"(?<=A potential solution has been determined:\n)(\s+ - [^\n]+\n)+") ??
"",
@"(?<=\s+)[^\n]+"));
else if (gameLog.Contains(
"A potential solution has been determined, this may resolve your problem:",
StringComparison.Ordinal))
solution = string.Join(
"\n",
CrashRegex.All(
CrashRegex.First(
gameLog,
@"(?<=A potential solution has been determined, this may resolve your problem:\n)(\s+ - [^\n]+\n)+") ??
"",
@"(?<=\s+)[^\n]+"));
else if (gameLog.Contains("确定了一种可能的解决方法,这样做可能会解决你的问题:", StringComparison.Ordinal))
solution = string.Join(
"\n",
CrashRegex.All(
CrashRegex.First(gameLog, @"(?<=确定了一种可能的解决方法,这样做可能会解决你的问题:\n)(\s+ - [^\n]+\n)+") ?? "",
@"(?<=\s+)[^\n]+"));
if (!string.IsNullOrWhiteSpace(solution))
yield return _ModFinding(
CrashCause.FabricSolutionProvided,
[solution],
CrashLogKind.Game,
true,
"loader-message");
}
private static IEnumerable<CrashFinding> _ExtractFabricProvidedModCrash(
CrashLogSet logs,
CrashModIndex modIndex)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog) ||
!gameLog.Contains("due to errors, provided by ", StringComparison.Ordinal) ||
_LooksLikeMixinFailure(gameLog))
yield break;
var hint = (CrashRegex.First(gameLog, "(?<=due to errors, provided by ')[^']+") ?? "")
.TrimEnd('\r', '\n', ' ');
yield return _ModFinding(
CrashCause.ConfirmedModCrash,
modIndex.ResolveToDisplayNames([hint]),
CrashLogKind.Game,
true);
}
private static IEnumerable<CrashFinding> _ExtractSuspectedMods(
CrashLogSet logs,
CrashModIndex modIndex)
{
var crash = logs.CrashReport?.Text;
if (string.IsNullOrEmpty(crash) ||
!crash.Contains("Suspected Mod", StringComparison.Ordinal))
yield break;
var suspectsRaw = CrashText.Between(crash, "Suspected Mod", "Stacktrace");
if (suspectsRaw.StartsWith("s: None", StringComparison.Ordinal))
yield break;
var suspects = CrashRegex.All(suspectsRaw, @"(?<=\n\t[^(\t]+\()[^)\n]+");
if (suspects.Count != 0)
yield return _ModFinding(
CrashCause.SuspectedModCrash,
modIndex.ResolveToDisplayNames(suspects),
CrashLogKind.CrashReport,
true);
}
private static CrashFinding? _ExtractShortOutput(CrashLogSet logs)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog) ||
logs.HsErr is not null ||
logs.CrashReport is not null ||
gameLog.Contains("at net.", StringComparison.Ordinal) ||
gameLog.Contains("INFO]", StringComparison.Ordinal) ||
gameLog.Length >= 100)
return null;
return _ModFinding(
CrashCause.VeryShortOutput,
[gameLog],
CrashLogKind.Game,
false,
"raw-output");
}
private static CrashFinding? _ExtractModLoaderError(CrashLogSet logs)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog) ||
!gameLog.Contains("Mod resolution failed", StringComparison.Ordinal))
return null;
return new CrashFinding(
CrashCause.ModLoaderError,
CrashConfidence.High);
}
private static CrashFinding? _ExtractModInitializationFailure(
CrashLogSet logs,
CrashModIndex modIndex)
{
var gameLog = logs.Game?.Text;
if (string.IsNullOrEmpty(gameLog) ||
!gameLog.Contains("Failed to create mod instance.", StringComparison.Ordinal))
return null;
var hint = (CrashRegex.First(gameLog, "(?<=Failed to create mod instance. ModID: )[^,]+") ??
CrashRegex.First(gameLog, @"(?<=Failed to create mod instance. ModId )[^\n]+(?= for )") ?? "")
.TrimEnd('\r', '\n');
return _ModFinding(
CrashCause.ModInitializationFailed,
modIndex.ResolveToDisplayNames([hint]),
CrashLogKind.Game,
false);
}
private static IEnumerable<CrashFinding> _ExtractSpecificBlockAndEntity(CrashLogSet logs)
{
var crash = logs.CrashReport?.Text;
if (string.IsNullOrEmpty(crash))
yield break;
if (crash.Contains("\tBlock location: World: ", StringComparison.Ordinal))
{
var value =
(CrashRegex.First(crash, @"(?<=\tBlock: Block\{)[^\}]+") ?? "") +
" " +
(CrashRegex.First(crash, @"(?<=\tBlock location: World: )\([^\)]+\)") ?? "");
yield return _ModFinding(
CrashCause.SpecificBlockCrash,
[value],
CrashLogKind.CrashReport,
false,
"block");
}
if (crash.Contains("\tEntity's Exact location: ", StringComparison.Ordinal))
{
var value =
(CrashRegex.First(crash, @"(?<=\tEntity Type: )[^\n]+(?= \()") ?? "") +
" (" +
(CrashRegex.First(crash, @"(?<=\tEntity's Exact location: )[^\n]+") ?? "")
.TrimEnd('\r', '\n') +
")";
yield return _ModFinding(
CrashCause.SpecificEntityCrash,
[value],
CrashLogKind.CrashReport,
false,
"entity");
}
}
private static IEnumerable<CrashEvidence> _FindPatterns(
CrashLogSet logs,
IEnumerable<(CrashLogKind Source, string Pattern)> patterns)
{
foreach (var (source, pattern) in patterns)
{
var text = logs.GetText(source);
if (text?.Contains(pattern, StringComparison.Ordinal) == true)
yield return _Evidence(pattern, source, "pattern", pattern);
}
}
private static CrashFinding _ModFinding(
CrashCause cause,
IEnumerable<string?> values,
CrashLogKind source,
bool shouldStop,
string displayKind = "mod")
{
return new CrashFinding(
cause,
CrashConfidence.High,
values
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => _Evidence(value!, source, displayKind)))
{
ShouldStop = shouldStop
};
}
private static CrashEvidence _Evidence(
string value,
CrashLogKind source,
string displayKind,
string? pattern = null)
{
return new CrashEvidence
{
Value = value,
Source = source,
Pattern = pattern,
DisplayKind = displayKind
};
}
}
@@ -0,0 +1,255 @@
using System.IO;
using System.Text.RegularExpressions;
using PCL.Core.Logging;
namespace PCL;
internal sealed class CrashModInfo
{
public string? ModId { get; init; }
public string? DisplayName { get; init; }
public string? FileName { get; init; }
public string? Version { get; init; }
public string Source { get; init; } = "";
public string DisplayNameOrFileName =>
!string.IsNullOrWhiteSpace(DisplayName) ? DisplayName :
!string.IsNullOrWhiteSpace(FileName) ? FileName :
ModId ?? "";
}
internal sealed class CrashModIndex
{
private readonly List<CrashModInfo> _mods = [];
public static CrashModIndex Create(CrashLogSet logs, McInstance? instance)
{
var index = new CrashModIndex();
index._ReadCrashReport(logs.CrashReport?.Text);
index._ReadForgeDebugLog(logs.Debug?.Text);
index._ReadLoaderMessages(logs.Game?.Text);
if (instance is not null)
index._ReadInstanceModFiles(instance);
index._NormalizeAndDeduplicate();
return index;
}
public IEnumerable<CrashModInfo> ResolveMany(IEnumerable<string> hints)
{
var found = new List<CrashModInfo>();
foreach (var hint in hints)
foreach (var mod in Resolve(hint))
if (!found.Any(item => _SameDisplay(item, mod)))
found.Add(mod);
return found;
}
public IEnumerable<CrashModInfo> Resolve(string? hint)
{
if (string.IsNullOrWhiteSpace(hint))
return [];
var normalizedHint = _Normalize(hint);
if (string.IsNullOrWhiteSpace(normalizedHint))
return [];
var exact = _mods
.Where(mod =>
string.Equals(_Normalize(mod.ModId), normalizedHint, StringComparison.Ordinal) ||
string.Equals(_Normalize(mod.FileName), normalizedHint, StringComparison.Ordinal) ||
string.Equals(_Normalize(mod.DisplayName), normalizedHint, StringComparison.Ordinal))
.ToList();
if (exact.Count != 0)
return exact;
return _mods
.Where(mod =>
_Normalize(mod.ModId).Contains(normalizedHint, StringComparison.Ordinal) ||
_Normalize(mod.FileName).Contains(normalizedHint, StringComparison.Ordinal) ||
_Normalize(mod.DisplayName).Contains(normalizedHint, StringComparison.Ordinal))
.ToList();
}
public List<string> ResolveToDisplayNames(IEnumerable<string> hints)
{
var hintList = hints
.Where(item => !string.IsNullOrWhiteSpace(item))
.Select(item => item.Trim())
.Distinct()
.ToList();
var mods = ResolveMany(hintList)
.Select(mod => mod.DisplayNameOrFileName)
.Where(item => !string.IsNullOrWhiteSpace(item))
.Distinct(StringComparer.Ordinal)
.ToList();
return mods.Count != 0 ? mods : hintList;
}
private void _ReadCrashReport(string? text)
{
if (string.IsNullOrEmpty(text) ||
!text.Contains("A detailed walkthrough of the error", StringComparison.Ordinal))
return;
var details = text.Replace("A detailed walkthrough of the error", "¨");
var isFabric = details.Contains("Fabric Mods", StringComparison.Ordinal);
if (isFabric)
{
details = details.Replace("Fabric Mods", "¨");
LogWrapper.Info("Crash", "崩溃报告中检测到 Fabric Mod 信息格式");
}
var isQuilt = details.Contains("quilt-loader", StringComparison.Ordinal);
if (isQuilt)
{
details = details.Replace("Mod Table Version", "¨");
LogWrapper.Info("Crash", "崩溃报告中检测到 Quilt Mod 信息格式");
}
details = CrashText.AfterLast(details, "¨");
foreach (var rawLine in details.Split('\n'))
{
var line = rawLine.Trim('\r', '\n');
if (string.IsNullOrWhiteSpace(line))
continue;
if (isFabric && line.StartsWith(
"\t\t",
StringComparison.Ordinal) && !CrashRegex.IsMatch(line, @"\t\tfabric[\w-]*: Fabric"))
{
_Add(new CrashModInfo
{
ModId = CrashRegex.First(line, @"(?<=\t\t)[^:]+"),
DisplayName = CrashRegex.First(line, @"(?<=: )[^\n]+(?= [^\n]+)"),
Version = CrashRegex.First(line, @"(?<= )[\w\.-]+$"),
Source = "crash-report-fabric"
});
continue;
}
if (line.Contains(".jar", StringComparison.OrdinalIgnoreCase) &&
line.Length - line.Replace(".jar", "", StringComparison.OrdinalIgnoreCase).Length == 4)
_Add(new CrashModInfo
{
FileName = CrashRegex.First(line,
@"(?<=\()[^\t]+.jar(?=\))|(?<=(\t\t)|(\| ))[^\t\|]+.jar",
RegexOptions.IgnoreCase),
DisplayName = CrashRegex.First(line, @"(?<=\t)[^\t\|]+(?=\s+\()"),
ModId = CrashRegex.First(line, @"(?<=\| )[\w\.-]+(?= \|)"),
Source = "crash-report-forge"
});
}
}
private void _ReadForgeDebugLog(string? text)
{
if (string.IsNullOrEmpty(text))
return;
foreach (var line in CrashRegex.All(text, "(?<=valid mod file ).*", RegexOptions.Multiline))
_Add(new CrashModInfo
{
FileName = CrashRegex.First(line, ".*(?= with)"),
ModId = CrashRegex.First(line, @"(?<=with \{)[^\}]+"),
Version = CrashRegex.First(line, @"(?<=versions \{)[^\}]+"),
Source = "debug-log"
});
}
private void _ReadLoaderMessages(string? text)
{
if (string.IsNullOrEmpty(text))
return;
foreach (var line in CrashRegex.All(text, @"(?<=ModID: )[^,\n]+|(?<=ModId )[^\n]+(?= for )",
RegexOptions.IgnoreCase))
_Add(new CrashModInfo
{
ModId = line.Trim(),
Source = "loader-message"
});
}
private void _ReadInstanceModFiles(McInstance instance)
{
foreach (var directory in _GetCandidateModDirectories(instance))
try
{
var info = new DirectoryInfo(directory);
if (!info.Exists)
continue;
foreach (var file in info.EnumerateFiles("*.jar"))
_Add(new CrashModInfo
{
FileName = file.Name,
DisplayName = Path.GetFileNameWithoutExtension(file.Name),
Source = "instance-mods"
});
}
catch (Exception ex)
{
LogWrapper.Warn(ex, "Crash", "读取实例 Mod 文件列表失败(" + directory + "");
}
}
private static IEnumerable<string> _GetCandidateModDirectories(McInstance instance)
{
yield return Path.Combine(instance.PathInstance, "mods");
string? pathIndie = null;
try
{
pathIndie = instance.PathIndie;
}
catch (Exception ex)
{
LogWrapper.Warn(ex, "Crash", "读取实例隔离路径失败");
}
if (!string.IsNullOrEmpty(pathIndie))
yield return Path.Combine(pathIndie, "mods");
}
private void _Add(CrashModInfo mod)
{
if (string.IsNullOrWhiteSpace(mod.ModId) &&
string.IsNullOrWhiteSpace(mod.DisplayName) &&
string.IsNullOrWhiteSpace(mod.FileName))
return;
_mods.Add(mod);
}
private void _NormalizeAndDeduplicate()
{
var distinct = new List<CrashModInfo>();
foreach (var mod in _mods
.Where(mod => !distinct.Any(item => _SameDisplay(item, mod))))
distinct.Add(mod);
_mods.Clear();
_mods.AddRange(distinct);
LogWrapper.Info("Crash", "构建 Mod 索引,找到 " + _mods.Count + " 个候选 Mod");
}
private static bool _SameDisplay(CrashModInfo a, CrashModInfo b)
{
return string.Equals(_Normalize(a.ModId), _Normalize(b.ModId), StringComparison.Ordinal) &&
string.Equals(_Normalize(a.FileName), _Normalize(b.FileName), StringComparison.Ordinal) &&
string.Equals(_Normalize(a.DisplayName), _Normalize(b.DisplayName), StringComparison.Ordinal);
}
private static string _Normalize(string? value)
{
return (value ?? string.Empty)
.ToLowerInvariant()
.Replace("_", "")
.Replace("-", "")
.Replace(" ", "")
.Replace(".jar", "");
}
}
@@ -0,0 +1,21 @@
namespace PCL;
internal sealed class CrashRule
{
public required CrashCause Cause { get; init; }
public required DetectionPhase Phase { get; init; }
public CrashConfidence Confidence { get; init; } = CrashConfidence.High;
public bool StopOnMatch { get; init; }
public required Func<CrashRuleInput, CrashFinding?> Evaluate { get; init; }
}
internal sealed class CrashRuleInput
{
public required CrashLogSet Logs { get; init; }
public required CrashModIndex ModIndex { get; init; }
}
@@ -0,0 +1,268 @@
namespace PCL;
internal static class CrashRuleCatalog
{
public static IReadOnlyList<CrashRule> Rules { get; } =
[
_Contains(
DetectionPhase.Fatal,
CrashLogKind.CrashReport,
"Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass",
CrashCause.JavaTooNew),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"Found multiple arguments for option fml.forgeVersion, but you asked for only one",
CrashCause.MultipleForgeInInstanceJson),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"The driver does not appear to support OpenGL",
CrashCause.UnsupportedOpenGl),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"java.lang.ClassCastException: java.base/jdk",
CrashCause.UsingJdk),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"java.lang.ClassCastException: class jdk.",
CrashCause.UsingJdk),
_ContainsAny(
DetectionPhase.Fatal,
CrashLogKind.Game,
[
"TRANSFORMER/net.optifine/net.optifine.reflect.Reflector.<clinit>(Reflector.java",
"java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.texture.SpriteContents.<init>",
"java.lang.NoSuchMethodError: 'java.lang.String com.mojang.blaze3d.systems.RenderSystem.getBackendDescription",
"java.lang.NoSuchMethodError: 'void net.minecraft.client.renderer.block.model.BakedQuad.<init>",
"java.lang.NoSuchMethodError: 'void net.minecraftforge.client.gui.overlay.ForgeGui.renderSelectedItemName",
"java.lang.NoSuchMethodError: 'void net.minecraft.server.level.DistanceManager",
"java.lang.NoSuchMethodError: 'net.minecraft.network.chat.FormattedText net.minecraft.client.gui.Font.ellipsize"
],
CrashCause.OptiFineForgeIncompatible),
_ContainsAny(
DetectionPhase.Fatal,
CrashLogKind.Game,
[
"Open J9 is not supported",
"OpenJ9 is incompatible",
".J9VMInternals."
],
CrashCause.UsingOpenJ9),
_ContainsAny(
DetectionPhase.Fatal,
CrashLogKind.Game,
[
"java.lang.NoSuchFieldException: ucp",
"because module java.base does not export",
"java.lang.ClassNotFoundException: jdk.nashorn.api.scripting.NashornScriptEngineFactory",
"java.lang.ClassNotFoundException: java.lang.invoke.LambdaMetafactory"
],
CrashCause.JavaTooNew),
_ContainsAny(
DetectionPhase.Fatal,
CrashLogKind.Game,
[
"The directories below appear to be extracted jar files. Fix this before you continue.",
"Extracted mod jars found, loading will NOT continue"
],
CrashCause.ExtractedModFile),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"java.lang.ClassNotFoundException: org.spongepowered.asm.launch.MixinTweaker",
CrashCause.MissingMixinBootstrap),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"Couldn't set pixel format",
CrashCause.PixelFormatNotSupported),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"java.lang.RuntimeException: Shaders Mod detected. Please remove it, OptiFine has built-in support for shaders.",
CrashCause.ShadersModWithOptiFine),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"java.lang.NoSuchMethodError: sun.security.util.ManifestEntryVerifier",
CrashCause.OldForgeNewJavaIncompatible),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"1282: Invalid operation",
CrashCause.OpenGl1282),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"Maybe try a lower resolution resourcepack?",
CrashCause.ResourcePackTooLarge),
_ContainsAll(
DetectionPhase.Fatal,
CrashLogKind.Game,
[
"java.lang.NoSuchMethodError: net.minecraft.world.server.ChunkManager$ProxyTicketManager.shouldForceTicks(J)Z",
"OptiFine"
],
CrashCause.OptiFineWorldLoadCrash),
_ContainsAny(
DetectionPhase.Fatal,
CrashLogKind.Game,
["Unsupported class file major version", "Unsupported major.minor version"], CrashCause.JavaIncompatible),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"com.electronwill.nightconfig.core.io.ParsingException: Not enough data available",
CrashCause.NightConfigBug),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"Cannot find launch target fmlclient, unable to launch",
CrashCause.IncompleteForgeInstallation),
_ContainsAll(
DetectionPhase.Fatal,
CrashLogKind.Game,
[
"Invalid paths argument, contained no existing paths",
@"libraries\net\minecraftforge\fmlcore"
],
CrashCause.IncompleteForgeInstallation),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"Invalid module name: '' is not a Java identifier",
CrashCause.InvalidModFileName),
_ContainsAny(
DetectionPhase.Fatal,
CrashLogKind.Game,
[
"has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to",
"java.lang.RuntimeException: java.lang.NoSuchMethodException: no such method: sun.misc.Unsafe.defineAnonymousClass(Class,byte[],Object[])Class/invokeVirtual",
"java.lang.IllegalArgumentException: The requested compatibility level JAVA_11 could not be set. Level is not supported by the active JRE or ASM version"
],
CrashCause.ModRequiresJava11),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.Game,
"Invalid maximum heap size",
CrashCause.X86JavaMemoryLimit),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.CrashReport,
"maximum id range exceeded",
CrashCause.TooManyModsIdLimit),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.CrashReport,
"Pixel format not accelerated",
CrashCause.PixelFormatNotSupported),
_Contains(
DetectionPhase.Fatal,
CrashLogKind.CrashReport,
"Manually triggered debug crash",
CrashCause.ManualDebugCrash)
];
private static CrashRule _Contains(
DetectionPhase phase,
CrashLogKind source,
string pattern,
CrashCause cause,
bool stopOnMatch = true)
{
return new CrashRule
{
Phase = phase,
Cause = cause,
StopOnMatch = stopOnMatch,
Evaluate = input =>
{
var text = input.Logs.GetText(source);
return text?.Contains(pattern, StringComparison.Ordinal) != true
? null
: _CreateFinding(cause, CrashConfidence.High, source, pattern, stopOnMatch);
}
};
}
private static CrashRule _ContainsAny(
DetectionPhase phase,
CrashLogKind source,
IReadOnlyList<string> patterns,
CrashCause cause,
bool stopOnMatch = true)
{
return new CrashRule
{
Phase = phase,
Cause = cause,
StopOnMatch = stopOnMatch,
Evaluate = input =>
{
var text = input.Logs.GetText(source);
if (string.IsNullOrEmpty(text))
return null;
var pattern = patterns.FirstOrDefault(item => text.Contains(item, StringComparison.Ordinal));
return pattern is null
? null
: _CreateFinding(cause, CrashConfidence.High, source, pattern, stopOnMatch);
}
};
}
private static CrashRule _ContainsAll(
DetectionPhase phase,
CrashLogKind source,
IReadOnlyList<string> patterns,
CrashCause cause,
bool stopOnMatch = true)
{
return new CrashRule
{
Phase = phase,
Cause = cause,
StopOnMatch = stopOnMatch,
Evaluate = input =>
{
var text = input.Logs.GetText(source);
if (string.IsNullOrEmpty(text) ||
patterns.Any(pattern => !text.Contains(pattern, StringComparison.Ordinal)))
return null;
return _CreateFinding(
cause,
CrashConfidence.High,
source,
string.Join(" | ", patterns),
stopOnMatch);
}
};
}
private static CrashFinding _CreateFinding(
CrashCause cause,
CrashConfidence confidence,
CrashLogKind source,
string pattern,
bool stopOnMatch)
{
return new CrashFinding(
cause,
confidence,
[
new CrashEvidence
{
Source = source,
Pattern = pattern,
Value = pattern,
DisplayKind = "pattern"
}
])
{
ShouldStop = stopOnMatch
};
}
}
@@ -0,0 +1,199 @@
using System.Text.RegularExpressions;
using PCL.Core.Logging;
namespace PCL;
internal sealed partial class CrashStackAnalyzer
{
private static readonly HashSet<string> IgnoredPrefixes = new(StringComparer.Ordinal)
{
"java", "sun", "javax", "jdk", "com.sun",
"com.mojang", "net.minecraft", "MojangTricksIntelDriversForPerformance_javaw",
"net.minecraftforge", "cpw.mods", "net.fabricmc", "org.quiltmc", "org.spongepowered", "com.mumfrey",
"org.lwjgl", "paulscode.sound", "com.google", "org.apache", "com.electronwill.nightconfig", "it.unimi.dsi",
"oolloo"
};
private static readonly HashSet<string> IgnoredWords = new(StringComparer.OrdinalIgnoreCase)
{
"com", "org", "net", "top", "dev", "gitlab", "github",
"sun", "lib", "nio", "api", "asm", "reflect", "internal", "microsoft",
"mcp", "fml", "forge", "fabricmc", "neoforge", "neoforged", "minecraftforge", "minecraft", "mojang",
"mod", "mods", "modapi", "jar", "loader", "launch", "preinit", "preload", "init", "setup", "plugin",
"mixin", "mixins", "injection", "transformer", "transformers", "spongepowered",
"game", "world", "server", "client", "common", "entity", "block", "item", "tile", "blockentity",
"gui", "model", "render", "shader", "optifine",
"config", "data", "file", "read", "recipe", "content", "general",
"event", "events", "handler", "listeners", "assist", "override",
"netty", "packet", "channel",
"task", "pool", "scheduler", "systems", "system", "modules", "service", "platform",
"core", "main", "base", "util", "impl", "done", "map", "load", "machine",
"dsi", "unimi", "fastutil", "lwjgl", "oshi", "electronwill",
"compat", "universal", "multipart"
};
public CrashFinding? Analyze(
CrashLogSet logs,
CrashModIndex modIndex)
{
if (!_ShouldAnalyzeStack(logs.All))
{
LogWrapper.Info("Crash", "可能并未安装 Mod,不进行堆栈分析");
return null;
}
var stackText = _CollectStackText(logs);
var packages = _ExtractPackages(stackText).ToList();
var keywords = _ExtractKeywords(packages).ToList();
if (keywords.Count is 0 or > 10)
{
if (keywords.Count > 10)
LogWrapper.Info("Crash", "关键词过多,考虑匹配出错,不纳入考虑");
return null;
}
var mods = modIndex.ResolveMany(keywords).ToList();
if (mods.Count != 0)
return new CrashFinding(
CrashCause.StackModNameFound,
CrashConfidence.Medium,
mods.Select(mod => new CrashEvidence
{
Source = CrashLogKind.All,
Value = mod.DisplayNameOrFileName,
DisplayKind = "mod"
}))
{
ShouldStop = true
};
return new CrashFinding(
CrashCause.StackKeywordFound,
CrashConfidence.Low,
keywords.Select(keyword => new CrashEvidence
{
Source = CrashLogKind.All,
Value = keyword,
DisplayKind = "keyword"
}))
{
ShouldStop = true
};
}
private static bool _ShouldAnalyzeStack(string allLogs)
{
return allLogs.Contains("forge", StringComparison.OrdinalIgnoreCase) ||
allLogs.Contains("fabric", StringComparison.OrdinalIgnoreCase) ||
allLogs.Contains("quilt", StringComparison.OrdinalIgnoreCase) ||
allLogs.Contains("liteloader", StringComparison.OrdinalIgnoreCase);
}
private static string _CollectStackText(CrashLogSet logs)
{
var result = new List<string>();
if (logs.CrashReport?.Text is not null)
{
LogWrapper.Info("Crash", "开始进行崩溃日志堆栈分析");
result.Add(CrashText.BeforeFirst(logs.CrashReport.Text, "System Details"));
}
if (logs.Game?.Text is not null)
{
var fatals = CrashRegex.All(logs.Game.Text, @"/FATAL] .+?(?=[\n]+\[)");
if (logs.Game.Text.Contains("Unreported exception thrown!", StringComparison.Ordinal))
fatals.Add(
CrashText.Between(logs.Game.Text,
"Unreported exception thrown!",
"at oolloo.jlw.Wrapper"));
LogWrapper.Info("Crash", "开始进行 Minecraft 日志堆栈分析,发现 " + fatals.Count + " 个报错项");
result.AddRange(fatals);
}
if (logs.HsErr?.Text is not null)
{
LogWrapper.Info("Crash", "开始进行虚拟机堆栈分析");
result.Add(CrashText.Between(logs.HsErr.Text, "T H R E A D", "Registers:"));
}
return "\n" + string.Join("\n", result) + "\n";
}
private static IEnumerable<string> _ExtractPackages(string stackText)
{
var packages = new List<string>();
packages.AddRange(
PackageRegex()
.Matches(stackText)
.Select(match => match.Value));
packages.AddRange(
MixinStackRegex()
.Matches(stackText)
.Select(match => match.Value.Replace("$", ".")));
var possibleStacks = packages
.Select(item => item.Trim())
.Where(item => !string.IsNullOrWhiteSpace(item))
.Distinct(StringComparer.Ordinal)
.Where(item => !IgnoredPrefixes.Any(prefix => item.StartsWith(prefix, StringComparison.Ordinal)))
.ToList();
LogWrapper.Info("Crash", "找到 " + possibleStacks.Count + " 条可能的堆栈信息");
foreach (var stack in possibleStacks)
LogWrapper.Info("Crash", " - " + stack);
return possibleStacks;
}
private static IEnumerable<string> _ExtractKeywords(IEnumerable<string> packages)
{
var words = new List<string>();
foreach (var package in packages)
{
var split = package.Split('.');
for (var i = 0; i <= Math.Min(3, split.Length - 1); i++)
{
var word = split[i].Trim();
if (word.Length <= 2 ||
word.StartsWith("func_", StringComparison.Ordinal) ||
IgnoredWords.Contains(word))
continue;
words.Add(word);
}
}
var result = words
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
LogWrapper.Info("Crash", "从堆栈信息中找到 " + result.Count + " 个可能的 Mod ID 关键词");
if (result.Count != 0)
LogWrapper.Info("Crash", " - " + string.Join(", ", result));
return result;
}
[GeneratedRegex(
@"(?<=\n[^{]+)[a-zA-Z_]+\w+\.[a-zA-Z_]+[\w\.]+(?=\.[\w\.$]+\.)",
RegexOptions.Compiled)]
private static partial Regex PackageRegex();
[GeneratedRegex(
@"(?<=at [^(]+?\.\w+\$\w+\$)[\w\$]+?(?=\$\w+\()",
RegexOptions.Compiled)]
private static partial Regex MixinStackRegex();
}
@@ -0,0 +1,8 @@
namespace PCL;
internal enum DetectionPhase
{
Fatal,
Primary,
Secondary
}