Files
gregExtractor/Services/HookClassifier.cs
T

122 lines
4.6 KiB
C#

using gregExtractor.Models;
namespace gregExtractor.Services;
public sealed class HookClassifier
{
private static readonly Dictionary<string, string[]> MethodKeywordMap = new(StringComparer.OrdinalIgnoreCase)
{
["Persistence"] = ["save", "load", "autosave"],
["Economy"] = ["coin", "money", "shop", "buy", "xp", "reputation", "checkout"],
["Networking"] = ["connect", "device", "cable", "ip", "network", "lacp"],
["Hardware"] = ["power", "repair", "broken", "server", "switch"],
["Lifecycle"] = ["pause", "resume", "day", "scene", "quit", "loadlevel"],
["VisualUI"] = ["ui", "menu", "panel", "screen", "button", "hud"],
};
private static readonly Dictionary<string, string[]> ClassKeywordMap = new(StringComparer.OrdinalIgnoreCase)
{
["Persistence"] = ["save"],
["Economy"] = ["player", "shop", "economy"],
["Networking"] = ["network", "cable", "packet", "switch", "server"],
["Hardware"] = ["server", "rack", "item", "asset"],
["Lifecycle"] = ["loading", "pause", "time", "scene", "playermanager"],
["VisualUI"] = ["menu", "ui", "hud", "panel"],
};
public HookClassifier(IReadOnlyDictionary<string, HookGroupConfig> groups)
{
Groups = groups;
}
public IReadOnlyDictionary<string, HookGroupConfig> Groups { get; }
public static HookClassifier LoadFromFile(string jsonPath, IProgress<string>? progress = null)
{
if (!File.Exists(jsonPath))
{
progress?.Report($"hook_groups.json not found at '{jsonPath}'. Classifier starts with empty groups.");
return new HookClassifier(new Dictionary<string, HookGroupConfig>(StringComparer.OrdinalIgnoreCase));
}
try
{
string json = File.ReadAllText(jsonPath);
Dictionary<string, HookGroupConfig>? model = JsonSerializer.Deserialize<Dictionary<string, HookGroupConfig>>(
json,
new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
});
var normalized = new Dictionary<string, HookGroupConfig>(StringComparer.OrdinalIgnoreCase);
if (model != null)
{
foreach ((string key, HookGroupConfig value) in model)
{
normalized[key] = new HookGroupConfig(
value.Description ?? string.Empty,
value.Classes ?? Array.Empty<string>(),
value.Methods ?? Array.Empty<string>());
}
}
return new HookClassifier(normalized);
}
catch (Exception exception)
{
progress?.Report($"Failed to parse hook_groups.json: {exception.Message}");
return new HookClassifier(new Dictionary<string, HookGroupConfig>(StringComparer.OrdinalIgnoreCase));
}
}
public string Classify(string className, string methodName)
{
// 1) Exact class + method match
foreach ((string groupName, HookGroupConfig groupConfig) in Groups)
{
bool classMatch = groupConfig.Classes.Any(c => EqualsIgnoreCase(c, className));
bool methodMatch = groupConfig.Methods.Any(m => EqualsIgnoreCase(m, methodName));
if (classMatch && methodMatch)
return groupName;
}
// 2) Class match only
foreach ((string groupName, HookGroupConfig groupConfig) in Groups)
{
if (groupConfig.Classes.Any(c => EqualsIgnoreCase(c, className)))
return groupName;
}
// 3) Method match only
foreach ((string groupName, HookGroupConfig groupConfig) in Groups)
{
if (groupConfig.Methods.Any(m => EqualsIgnoreCase(m, methodName)))
return groupName;
}
// 4) Method keyword heuristic
foreach ((string group, string[] keywords) in MethodKeywordMap)
{
if (keywords.Any(keyword => methodName.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
return group;
}
// 5) Class keyword heuristic
foreach ((string group, string[] keywords) in ClassKeywordMap)
{
if (keywords.Any(keyword => className.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
return group;
}
// 6) Fallback
return "Uncategorized";
}
private static bool EqualsIgnoreCase(string left, string right)
{
return string.Equals(left?.Trim(), right?.Trim(), StringComparison.OrdinalIgnoreCase);
}
}