2f61e5dc00
- Updated HookBrowserViewModel, MainViewModel, SettingsViewModel, and SyncViewModel to inherit from BaseViewModel. - Introduced BaseViewModel with methods for running actions on the UI thread. - Enhanced SyncViewModel to handle property changes on the UI thread. - Refactored ExtractorService to return ExtractorResult instead of IReadOnlyList<HookDefinition>. - Added ExtractorResult class to encapsulate extraction results, including success status and error messages. - Implemented UIProgress class for reporting progress on the UI thread. - Updated hook_groups.json with additional classes and methods for various categories. - Improved error handling and logging in ExtractorService and SyncViewModel.
297 lines
8.9 KiB
C#
297 lines
8.9 KiB
C#
using Avalonia.Threading;
|
|
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 : BaseViewModel
|
|
{
|
|
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()
|
|
{
|
|
Log($"[Coverage] Initial FrameworkSourceDirsText: '{_frameworkSourceDirsText}'");
|
|
|
|
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();
|
|
|
|
Log($"[Coverage] After AutoDetect: Il2CppPath='{Il2CppPath}'");
|
|
}
|
|
|
|
public Func<string?, Task<string?>>? BrowseFolderHandler { get; set; }
|
|
|
|
public string Il2CppPath
|
|
{
|
|
get => _il2CppPath;
|
|
set => SetProperty(ref _il2CppPath, value?.Trim() ?? string.Empty);
|
|
}
|
|
|
|
public string GameHooksPath
|
|
{
|
|
get => _gameHooksPath;
|
|
set => SetProperty(ref _gameHooksPath, value?.Trim() ?? string.Empty);
|
|
}
|
|
|
|
public string FrameworkSourceDirsText
|
|
{
|
|
get => _frameworkSourceDirsText;
|
|
set => SetProperty(ref _frameworkSourceDirsText, value?.Trim() ?? string.Empty);
|
|
}
|
|
|
|
public string OutputBasePath
|
|
{
|
|
get => _outputBasePath;
|
|
set
|
|
{
|
|
if (SetProperty(ref _outputBasePath, value))
|
|
OpenMarkdownReportCommand.NotifyCanExecuteChanged();
|
|
}
|
|
}
|
|
|
|
public bool IsAnalyzing
|
|
{
|
|
get => _isAnalyzing;
|
|
set
|
|
{
|
|
if (Dispatcher.UIThread.CheckAccess())
|
|
{
|
|
if (!SetProperty(ref _isAnalyzing, value))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Dispatcher.UIThread.Post(() => 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()
|
|
{
|
|
string detected = SteamLocator.FindIl2CppAssembliesPath() ?? string.Empty;
|
|
Log($"[SteamLocator] Found: '{detected}'");
|
|
|
|
Il2CppPath = detected;
|
|
|
|
if (string.IsNullOrWhiteSpace(Il2CppPath))
|
|
Log("Auto-detect: No IL2CPP path found.");
|
|
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()
|
|
{
|
|
Log($"[Coverage] Input IL2CPP path: '{Il2CppPath}'");
|
|
|
|
string trimmedPath = Il2CppPath?.Trim() ?? string.Empty;
|
|
Log($"[Coverage] Trimmed path: '{trimmedPath}'");
|
|
|
|
if (string.IsNullOrWhiteSpace(trimmedPath))
|
|
{
|
|
Log("[Coverage] IL2CPP path is empty.");
|
|
return;
|
|
}
|
|
|
|
bool pathExists = Directory.Exists(trimmedPath);
|
|
Log($"[Coverage] Path exists: {pathExists}");
|
|
|
|
if (!pathExists)
|
|
{
|
|
Log($"[Coverage] IL2CPP directory not found.");
|
|
Log($"[Coverage] Checked path: {trimmedPath}");
|
|
return;
|
|
}
|
|
|
|
bool dllExists = File.Exists(Path.Combine(trimmedPath, "Assembly-CSharp.dll"));
|
|
Log($"[Coverage] Assembly-CSharp.dll exists: {dllExists}");
|
|
|
|
if (!dllExists)
|
|
{
|
|
Log($"[Coverage] Assembly-CSharp.dll not found in: {trimmedPath}");
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(GameHooksPath) || !File.Exists(GameHooksPath))
|
|
{
|
|
Log($"game_hooks.json path invalid: '{GameHooksPath}'");
|
|
return;
|
|
}
|
|
|
|
Log($"[Coverage] Framework source dirs text: '{FrameworkSourceDirsText}'");
|
|
|
|
string[] sourceDirs = CoveragePathResolver.ResolveSourceDirs(FrameworkSourceDirsText, Directory.GetCurrentDirectory());
|
|
Log($"[Coverage] Resolved {sourceDirs.Length} source dirs: {string.Join(", ", sourceDirs)}");
|
|
|
|
if (sourceDirs.Length == 0)
|
|
{
|
|
Log("ERROR: No valid source directories found.");
|
|
Log(" - Add paths separated by ';' in the Framework Source field");
|
|
Log(" - Or ensure default paths exist: Hooks/, framework/, ../gregCore/Hooks/");
|
|
return;
|
|
}
|
|
|
|
IsAnalyzing = true;
|
|
Progress = 0;
|
|
|
|
RunOnUIThread(() => Entries.Clear());
|
|
|
|
try
|
|
{
|
|
var progress = new UIProgress<string>(msg =>
|
|
{
|
|
Log(msg);
|
|
Progress = Math.Min(99, Progress + 1);
|
|
});
|
|
|
|
var report = await Task.Run(() => _analyzerService.Analyze(new AnalyzeOptions(
|
|
Il2CppDir: trimmedPath,
|
|
GameHooksJsonPath: GameHooksPath,
|
|
FrameworkSourceDirs: sourceDirs,
|
|
OutputBasePath: OutputBasePath,
|
|
Progress: progress)))
|
|
.ConfigureAwait(false);
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
{
|
|
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 ex)
|
|
{
|
|
Log($"Coverage analysis failed: {ex.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);
|
|
}
|
|
} |