Files
gregExtractor/GUI/ViewModels/CoverageViewModel.cs
T

248 lines
7.0 KiB
C#

using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using gregExtractor.Models;
using gregExtractor.Services;
using gregExtractor.Utils;
namespace gregExtractor.GUI.ViewModels;
public sealed class CoverageViewModel : ObservableObject
{
private readonly CoverageAnalyzerService _analyzerService = new();
private string _il2CppPath = string.Empty;
private string _gameHooksPath = Path.Combine(Directory.GetCurrentDirectory(), "game_hooks.json");
private string _frameworkSourceDirsText = CoveragePathResolver.GetDefaultSourcesText(Directory.GetCurrentDirectory());
private string _outputBasePath = Path.Combine(Directory.GetCurrentDirectory(), "coverage_report");
private bool _isAnalyzing;
private double _progress;
private int _totalHooks;
private int _coveredCount;
private int _plannedCount;
private int _uncoveredCount;
private double _coveragePercent;
public CoverageViewModel()
{
AutoDetectPathCommand = new RelayCommand(AutoDetectPath);
BrowseFolderCommand = new AsyncRelayCommand<string?>(BrowseFolderAsync);
AnalyzeCoverageCommand = new AsyncRelayCommand(AnalyzeAsync, () => !IsAnalyzing);
OpenMarkdownReportCommand = new RelayCommand(OpenMarkdownReport, () => File.Exists(CoverageAnalyzerService.GetReportPaths(OutputBasePath).MarkdownPath));
AutoDetectPath();
}
public Func<string?, Task<string?>>? BrowseFolderHandler { get; set; }
public string Il2CppPath
{
get => _il2CppPath;
set => SetProperty(ref _il2CppPath, value);
}
public string GameHooksPath
{
get => _gameHooksPath;
set => SetProperty(ref _gameHooksPath, value);
}
public string FrameworkSourceDirsText
{
get => _frameworkSourceDirsText;
set => SetProperty(ref _frameworkSourceDirsText, value);
}
public string OutputBasePath
{
get => _outputBasePath;
set
{
if (SetProperty(ref _outputBasePath, value))
OpenMarkdownReportCommand.NotifyCanExecuteChanged();
}
}
public bool IsAnalyzing
{
get => _isAnalyzing;
set
{
if (!SetProperty(ref _isAnalyzing, value))
return;
AnalyzeCoverageCommand.NotifyCanExecuteChanged();
OnPropertyChanged(nameof(IsLoading));
}
}
public bool IsLoading => IsAnalyzing;
public double Progress
{
get => _progress;
set => SetProperty(ref _progress, value);
}
public int TotalHooks
{
get => _totalHooks;
set => SetProperty(ref _totalHooks, value);
}
public int CoveredCount
{
get => _coveredCount;
set => SetProperty(ref _coveredCount, value);
}
public int PlannedCount
{
get => _plannedCount;
set => SetProperty(ref _plannedCount, value);
}
public int UncoveredCount
{
get => _uncoveredCount;
set => SetProperty(ref _uncoveredCount, value);
}
public double CoveragePercent
{
get => _coveragePercent;
set => SetProperty(ref _coveragePercent, value);
}
public ObservableCollection<HookCoverageEntry> Entries { get; } = new();
public ObservableCollection<string> LogLines { get; } = new();
public IRelayCommand AutoDetectPathCommand { get; }
public IAsyncRelayCommand<string?> BrowseFolderCommand { get; }
public IAsyncRelayCommand AnalyzeCoverageCommand { get; }
public IRelayCommand OpenMarkdownReportCommand { get; }
private void AutoDetectPath()
{
Il2CppPath = SteamLocator.FindIl2CppAssembliesPath() ?? string.Empty;
if (string.IsNullOrWhiteSpace(Il2CppPath))
Log("Auto-detect for IL2CPP path failed.");
else
Log($"Auto-detected IL2CPP path: {Il2CppPath}");
}
private async Task BrowseFolderAsync(string? target)
{
if (BrowseFolderHandler is null)
return;
string? selected = await BrowseFolderHandler(target).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(selected))
return;
switch (target)
{
case "Il2Cpp":
Il2CppPath = selected;
break;
case "SourceDirs":
FrameworkSourceDirsText = selected;
break;
case "Output":
OutputBasePath = Path.Combine(selected, "coverage_report");
break;
}
}
private async Task AnalyzeAsync()
{
if (string.IsNullOrWhiteSpace(Il2CppPath) || !Directory.Exists(Il2CppPath))
{
Log("IL2CPP path is invalid.");
return;
}
if (string.IsNullOrWhiteSpace(GameHooksPath) || !File.Exists(GameHooksPath))
{
Log("game_hooks.json path is invalid.");
return;
}
string[] sourceDirs = CoveragePathResolver.ResolveSourceDirs(FrameworkSourceDirsText, Directory.GetCurrentDirectory());
if (sourceDirs.Length == 0)
{
Log("No valid source directories found. Provide ';' separated paths.");
return;
}
IsAnalyzing = true;
Progress = 0;
Entries.Clear();
try
{
var progress = new Progress<string>(message =>
{
Log(message);
Progress = Math.Min(99, Progress + 1);
});
CoverageReport report = await Task.Run(() => _analyzerService.Analyze(new AnalyzeOptions(
Il2CppDir: Il2CppPath,
GameHooksJsonPath: GameHooksPath,
FrameworkSourceDirs: sourceDirs,
OutputBasePath: OutputBasePath,
Progress: progress)))
.ConfigureAwait(false);
TotalHooks = report.TotalHooks;
CoveredCount = report.CoveredCount;
PlannedCount = report.PlannedCount;
UncoveredCount = report.UncoveredCount;
CoveragePercent = report.CoveragePercent;
foreach (HookCoverageEntry entry in report.Entries)
Entries.Add(entry);
Progress = 100;
Log($"Coverage analysis done. Coverage: {report.CoveragePercent:F2}%");
OpenMarkdownReportCommand.NotifyCanExecuteChanged();
}
catch (Exception exception)
{
Log($"Coverage analysis failed: {exception.Message}");
}
finally
{
IsAnalyzing = false;
}
}
private void OpenMarkdownReport()
{
string markdownPath = CoverageAnalyzerService.GetReportPaths(OutputBasePath).MarkdownPath;
if (!File.Exists(markdownPath))
return;
Process.Start(new ProcessStartInfo
{
FileName = markdownPath,
UseShellExecute = true,
});
}
private void Log(string message)
{
string line = $"[{DateTime.Now:HH:mm:ss}] {message}";
if (LogLines.Count > 1000)
LogLines.RemoveAt(0);
LogLines.Add(line);
}
}