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.
217 lines
6.9 KiB
C#
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);
|
|
}
|
|
} |