Files
Marvin 2f61e5dc00 Refactor ViewModels to inherit from BaseViewModel and improve UI thread handling
- 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.
2026-04-20 04:26:06 +02:00

217 lines
6.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 ExtractorViewModel : BaseViewModel
{
private readonly ExtractorService _extractorService = new();
private HookClassifier _classifier;
private string _gamePath = string.Empty;
private string _il2CppPath = string.Empty;
private bool _isLoading;
private double _progress;
public ExtractorViewModel()
{
_classifier = HookClassifier.LoadFromFile(null, new UIProgress<string>(AddLog));
foreach ((string groupName, HookGroupConfig config) in _classifier.Groups)
HookGroups.Add(new HookGroupViewModel(groupName, config.Description));
AutoDetectPathCommand = new RelayCommand(AutoDetectPaths);
BrowsePathCommand = new AsyncRelayCommand<string?>(BrowsePathAsync);
StartAnalysisCommand = new AsyncRelayCommand(StartAnalysisAsync, () => !IsLoading);
AutoDetectPaths();
}
public Func<string?, Task<string?>>? BrowseFolderHandler { get; set; }
public string GamePath
{
get => _gamePath;
set => SetProperty(ref _gamePath, value);
}
public string Il2CppPath
{
get => _il2CppPath;
set => SetProperty(ref _il2CppPath, value);
}
public bool IsLoading
{
get => _isLoading;
set
{
if (Dispatcher.UIThread.CheckAccess())
{
if (!SetProperty(ref _isLoading, value))
return;
}
else
{
Dispatcher.UIThread.Post(() =>
{
SetProperty(ref _isLoading, value);
});
return;
}
StartAnalysisCommand.NotifyCanExecuteChanged();
}
}
public double Progress
{
get => _progress;
set => SetProperty(ref _progress, value);
}
public ObservableCollection<HookGroupViewModel> HookGroups { get; } = new();
public ObservableCollection<string> LogLines { get; } = new();
public int TotalHooks => HookGroups.Sum(x => x.Count);
public int GroupedHooks => HookGroups.Where(x => !x.GroupName.Equals("Uncategorized", StringComparison.OrdinalIgnoreCase)).Sum(x => x.Count);
public int UnknownHooks => HookGroups.Where(x => x.GroupName.Equals("Uncategorized", StringComparison.OrdinalIgnoreCase)).Sum(x => x.Count);
public int GroupCount => HookGroups.Count;
public IRelayCommand AutoDetectPathCommand { get; }
public IAsyncRelayCommand<string?> BrowsePathCommand { get; }
public IAsyncRelayCommand StartAnalysisCommand { get; }
private void AutoDetectPaths()
{
GamePath = SteamLocator.FindGamePath() ?? string.Empty;
Il2CppPath = SteamLocator.FindIl2CppAssembliesPath() ?? string.Empty;
if (string.IsNullOrWhiteSpace(Il2CppPath))
AddLog("Kein IL2CPP-Pfad automatisch erkannt.");
else
AddLog($"IL2CPP-Pfad erkannt: {Il2CppPath}");
}
private async Task BrowsePathAsync(string? target)
{
if (BrowseFolderHandler is null)
return;
string? selected = await BrowseFolderHandler(target).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(selected))
return;
if (string.Equals(target, "GamePath", StringComparison.OrdinalIgnoreCase))
GamePath = selected;
else
Il2CppPath = selected;
}
private async Task StartAnalysisAsync()
{
if (string.IsNullOrWhiteSpace(Il2CppPath) || !Directory.Exists(Il2CppPath))
{
AddLog("IL2CPP-Pfad ungueltig.");
return;
}
IsLoading = true;
Progress = 0;
var progress = new UIProgress<string>(msg =>
{
AddLog(msg);
Progress = Math.Min(98, Progress + 1);
});
try
{
var result = await _extractorService
.ExtractAsync(Il2CppPath, _classifier, progress, CancellationToken.None)
.ConfigureAwait(false);
if (result.IsCancelled)
{
AddLog("Scan abgebrochen.");
IsLoading = false;
return;
}
if (!result.IsSuccess)
{
AddLog(result.Error ?? "Unbekannter Fehler");
IsLoading = false;
return;
}
var hooks = result.Hooks;
string hooksPath = Path.Combine(Directory.GetCurrentDirectory(), "game_hooks.json");
await File.WriteAllTextAsync(hooksPath, JsonSerializer.Serialize(hooks, new JsonSerializerOptions { WriteIndented = true }), CancellationToken.None)
.ConfigureAwait(false);
string unknownPath = Path.Combine(Directory.GetCurrentDirectory(), "unknown_hooks.json");
HookDefinition[] unknown = hooks.Where(h => h.Group.Equals("Uncategorized", StringComparison.OrdinalIgnoreCase)).ToArray();
await File.WriteAllTextAsync(unknownPath, JsonSerializer.Serialize(unknown, new JsonSerializerOptions { WriteIndented = true }), CancellationToken.None)
.ConfigureAwait(false);
await Dispatcher.UIThread.InvokeAsync(() => ApplyGroups(hooks));
Progress = 100;
AddLog($"Analyse abgeschlossen. Hooks: {hooks.Count}");
IsLoading = false;
OnPropertyChanged(nameof(TotalHooks));
OnPropertyChanged(nameof(GroupedHooks));
OnPropertyChanged(nameof(UnknownHooks));
OnPropertyChanged(nameof(GroupCount));
}
catch (Exception ex)
{
AddLog($"Kritischer Fehler: {ex.Message}");
IsLoading = false;
}
}
private void ApplyGroups(IEnumerable<HookDefinition> hooks)
{
Dictionary<string, HookDefinition[]> grouped = hooks
.GroupBy(x => x.Group)
.ToDictionary(x => x.Key, x => x.ToArray(), StringComparer.OrdinalIgnoreCase);
foreach (HookGroupViewModel group in HookGroups)
{
grouped.TryGetValue(group.GroupName, out HookDefinition[]? values);
group.ReplaceHooks(values ?? Array.Empty<HookDefinition>());
}
foreach ((string groupName, HookDefinition[] values) in grouped)
{
if (HookGroups.Any(x => x.GroupName.Equals(groupName, StringComparison.OrdinalIgnoreCase)))
continue;
var vm = new HookGroupViewModel(groupName, "Detected at runtime") { IsSelected = true };
vm.ReplaceHooks(values);
HookGroups.Add(vm);
}
}
public void AddLog(string message)
{
string line = $"[{DateTime.Now:HH:mm:ss}] {message}";
if (LogLines.Count > 1500)
LogLines.RemoveAt(0);
LogLines.Add(line);
}
}