148 lines
5.3 KiB
C#
148 lines
5.3 KiB
C#
using gregExtractor.Models;
|
|
using gregExtractor.Services;
|
|
using gregExtractor.Utils;
|
|
|
|
namespace gregExtractor.Commands;
|
|
|
|
public sealed class CoverageCommand : AsyncCommand<CoverageCommand.Settings>
|
|
{
|
|
public sealed class Settings : CommandSettings
|
|
{
|
|
[CommandOption("--path <IL2CPP_PATH>")]
|
|
public string? Path { get; init; }
|
|
|
|
[CommandOption("--hooks <GAME_HOOKS_JSON>")]
|
|
public string? HooksPath { get; init; }
|
|
|
|
[CommandOption("--sources <SOURCE_DIRS>")]
|
|
public string? SourceDirs { get; init; }
|
|
|
|
[CommandOption("--out <OUTPUT_BASE_PATH>")]
|
|
public string? OutputBasePath { get; init; }
|
|
|
|
[CommandOption("--open")]
|
|
public bool OpenAfterGenerate { get; init; }
|
|
}
|
|
|
|
public override async Task<int> ExecuteAsync(CommandContext context, Settings settings)
|
|
{
|
|
AnsiConsole.Write(new FigletText("gregCoverage").Color(Color.Cyan1));
|
|
|
|
string workingDirectory = Directory.GetCurrentDirectory();
|
|
|
|
string? il2CppPath = settings.Path;
|
|
if (string.IsNullOrWhiteSpace(il2CppPath))
|
|
il2CppPath = SteamLocator.FindIl2CppAssembliesPath();
|
|
|
|
if (string.IsNullOrWhiteSpace(il2CppPath))
|
|
{
|
|
AnsiConsole.MarkupLine("[yellow]Could not auto-detect Il2CppAssemblies path.[/]");
|
|
il2CppPath = AnsiConsole.Ask<string>("Enter IL2CPP directory path:");
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(il2CppPath) || !Directory.Exists(il2CppPath))
|
|
{
|
|
AnsiConsole.MarkupLine("[red]Invalid IL2CPP path.[/]");
|
|
return -1;
|
|
}
|
|
|
|
string hooksPath = string.IsNullOrWhiteSpace(settings.HooksPath)
|
|
? Path.Combine(workingDirectory, "game_hooks.json")
|
|
: settings.HooksPath;
|
|
|
|
if (!File.Exists(hooksPath))
|
|
{
|
|
AnsiConsole.MarkupLine($"[red]Hooks file not found:[/] {Markup.Escape(hooksPath)}");
|
|
return -1;
|
|
}
|
|
|
|
string[] sourceDirs = CoveragePathResolver.ResolveSourceDirs(settings.SourceDirs, workingDirectory);
|
|
if (sourceDirs.Length == 0)
|
|
{
|
|
AnsiConsole.MarkupLine("[red]No valid framework source directories found.[/]");
|
|
AnsiConsole.MarkupLine("[grey]Use --sources \"<dir1>;<dir2>\"[/]");
|
|
return -1;
|
|
}
|
|
|
|
var analyzer = new CoverageAnalyzerService();
|
|
var messages = new List<string>();
|
|
var progress = new Progress<string>(message => messages.Add(message));
|
|
|
|
CoverageReport report = default!;
|
|
try
|
|
{
|
|
await AnsiConsole.Status()
|
|
.Spinner(Spinner.Known.Dots)
|
|
.StartAsync("Analyzing coverage...", async _ =>
|
|
{
|
|
report = await Task.Run(() => analyzer.Analyze(new AnalyzeOptions(
|
|
Il2CppDir: il2CppPath!,
|
|
GameHooksJsonPath: hooksPath,
|
|
FrameworkSourceDirs: sourceDirs,
|
|
OutputBasePath: settings.OutputBasePath,
|
|
Progress: progress)))
|
|
.ConfigureAwait(false);
|
|
})
|
|
.ConfigureAwait(false);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
AnsiConsole.MarkupLine($"[red]Coverage analysis failed:[/] {Markup.Escape(exception.Message)}");
|
|
return -1;
|
|
}
|
|
|
|
foreach (string message in messages)
|
|
AnsiConsole.MarkupLine($"[grey]{Markup.Escape(message)}[/]");
|
|
|
|
var summary = new Table().RoundedBorder();
|
|
summary.AddColumn("Metric");
|
|
summary.AddColumn("Value");
|
|
summary.AddRow("Total Hooks", report.TotalHooks.ToString());
|
|
summary.AddRow("Covered", report.CoveredCount.ToString());
|
|
summary.AddRow("Planned", report.PlannedCount.ToString());
|
|
summary.AddRow("Uncovered", report.UncoveredCount.ToString());
|
|
summary.AddRow("Coverage", $"{report.CoveragePercent:F2}%");
|
|
AnsiConsole.Write(summary);
|
|
|
|
HookCoverageEntry[] uncoveredTop = report.Entries
|
|
.Where(entry => entry.CoverageStatus == CoverageStatus.Uncovered)
|
|
.Take(10)
|
|
.ToArray();
|
|
|
|
if (uncoveredTop.Length > 0)
|
|
{
|
|
var uncoveredTable = new Table().RoundedBorder();
|
|
uncoveredTable.Title = new TableTitle("Top 10 Uncovered");
|
|
uncoveredTable.AddColumn("Group");
|
|
uncoveredTable.AddColumn("Class");
|
|
uncoveredTable.AddColumn("Method");
|
|
|
|
foreach (HookCoverageEntry entry in uncoveredTop)
|
|
{
|
|
uncoveredTable.AddRow(
|
|
entry.HookDefinition.Group,
|
|
entry.HookDefinition.ClassName,
|
|
entry.HookDefinition.MethodName);
|
|
}
|
|
|
|
AnsiConsole.Write(uncoveredTable);
|
|
}
|
|
|
|
(string jsonPath, string mdPath) = CoverageAnalyzerService.GetReportPaths(settings.OutputBasePath);
|
|
AnsiConsole.MarkupLine($"[green]coverage_report.json[/] -> {Markup.Escape(jsonPath)}");
|
|
AnsiConsole.MarkupLine($"[green]coverage_report.md[/] -> {Markup.Escape(mdPath)}");
|
|
|
|
if (settings.OpenAfterGenerate && File.Exists(mdPath))
|
|
{
|
|
Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = mdPath,
|
|
UseShellExecute = true,
|
|
});
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
}
|