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.
326 lines
11 KiB
C#
326 lines
11 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 enum HookDiffKind
|
|
{
|
|
Added,
|
|
Changed,
|
|
Removed,
|
|
}
|
|
|
|
public sealed record HookDiffEntry(
|
|
HookDiffKind Kind,
|
|
string Group,
|
|
string ClassName,
|
|
string MethodName,
|
|
string Signature)
|
|
{
|
|
public string Display => $"{ClassName}::{MethodName}";
|
|
}
|
|
|
|
public sealed class SyncViewModel : BaseViewModel
|
|
{
|
|
private readonly ExtractorService _extractorService = new();
|
|
private readonly DiffService _diffService = new();
|
|
private readonly FrameworkSyncService _syncService = new();
|
|
|
|
private string _frameworkSourcePath = string.Empty;
|
|
private bool _isDryRun = true;
|
|
private bool _useGitDiff;
|
|
private bool _isRunning;
|
|
private SyncResult? _lastResult;
|
|
private HookDiff? _lastDiff;
|
|
|
|
public SyncViewModel()
|
|
{
|
|
FrameworkSourcePath = Path.Combine(Directory.GetCurrentDirectory(), "..", "gregCore", "src");
|
|
|
|
BrowseSourcePathCommand = new AsyncRelayCommand(BrowseSourcePathAsync);
|
|
CalculateDiffCommand = new AsyncRelayCommand(CalculateDiffAsync, () => !IsRunning);
|
|
RunSyncCommand = new AsyncRelayCommand(RunSyncAsync, () => !IsRunning);
|
|
OpenBackupFolderCommand = new RelayCommand(OpenBackupFolder, () => Directory.Exists(FrameworkSourcePath));
|
|
}
|
|
|
|
public Func<string?, Task<string?>>? BrowseFolderHandler { get; set; }
|
|
|
|
public string FrameworkSourcePath
|
|
{
|
|
get => _frameworkSourcePath;
|
|
set => SetProperty(ref _frameworkSourcePath, value?.Trim() ?? string.Empty);
|
|
}
|
|
|
|
public bool IsDryRun
|
|
{
|
|
get => _isDryRun;
|
|
set => SetProperty(ref _isDryRun, value);
|
|
}
|
|
|
|
public bool UseGitDiff
|
|
{
|
|
get => _useGitDiff;
|
|
set => SetProperty(ref _useGitDiff, value);
|
|
}
|
|
|
|
public ObservableCollection<HookDiffEntry> DiffEntries { get; } = new();
|
|
|
|
public ObservableCollection<string> SyncLog { get; } = new();
|
|
|
|
public ObservableCollection<string> Warnings { get; } = new();
|
|
|
|
public bool IsRunning
|
|
{
|
|
get => _isRunning;
|
|
set
|
|
{
|
|
if (Dispatcher.UIThread.CheckAccess())
|
|
{
|
|
if (!SetProperty(ref _isRunning, value))
|
|
return;
|
|
|
|
CalculateDiffCommand.NotifyCanExecuteChanged();
|
|
RunSyncCommand.NotifyCanExecuteChanged();
|
|
OnPropertyChanged(nameof(IsLoading));
|
|
}
|
|
else
|
|
{
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
SetProperty(ref _isRunning, value);
|
|
CalculateDiffCommand.NotifyCanExecuteChanged();
|
|
RunSyncCommand.NotifyCanExecuteChanged();
|
|
OnPropertyChanged(nameof(IsLoading));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsLoading => IsRunning;
|
|
|
|
public SyncResult? LastResult
|
|
{
|
|
get => _lastResult;
|
|
set => SetProperty(ref _lastResult, value);
|
|
}
|
|
|
|
public IAsyncRelayCommand BrowseSourcePathCommand { get; }
|
|
|
|
public IAsyncRelayCommand CalculateDiffCommand { get; }
|
|
|
|
public IAsyncRelayCommand RunSyncCommand { get; }
|
|
|
|
public IRelayCommand OpenBackupFolderCommand { get; }
|
|
|
|
private async Task BrowseSourcePathAsync()
|
|
{
|
|
if (BrowseFolderHandler is null)
|
|
return;
|
|
|
|
string? selected = await BrowseFolderHandler("FrameworkSourcePath").ConfigureAwait(false);
|
|
if (string.IsNullOrWhiteSpace(selected))
|
|
return;
|
|
|
|
FrameworkSourcePath = selected;
|
|
}
|
|
|
|
private async Task CalculateDiffAsync()
|
|
{
|
|
await ComputeDiffAsync(CancellationToken.None).ConfigureAwait(false);
|
|
}
|
|
|
|
private async Task RunSyncAsync()
|
|
{
|
|
if (!Directory.Exists(FrameworkSourcePath))
|
|
{
|
|
AddLog("Framework-Source-Pfad ist ungueltig.");
|
|
return;
|
|
}
|
|
|
|
HookDiff diff = _lastDiff ?? await ComputeDiffAsync(CancellationToken.None).ConfigureAwait(false);
|
|
|
|
if (diff.Added.Count == 0 && diff.Changed.Count == 0 && diff.Removed.Count == 0)
|
|
{
|
|
AddLog("Keine Aenderungen erkannt. Sync uebersprungen.");
|
|
return;
|
|
}
|
|
|
|
IsRunning = true;
|
|
|
|
RunOnUIThread(() => Warnings.Clear());
|
|
|
|
try
|
|
{
|
|
HookDefinition[] allHooks = await LoadNewHooksAsync(CancellationToken.None).ConfigureAwait(false);
|
|
var progress = new UIProgress<string>(AddLog);
|
|
|
|
var result = await Task.Run(() => _syncService.Sync(new SyncOptions(
|
|
FrameworkSourceDir: FrameworkSourcePath,
|
|
Diff: diff,
|
|
AllHooks: allHooks.ToList(),
|
|
DryRun: IsDryRun,
|
|
Progress: progress)), CancellationToken.None).ConfigureAwait(false);
|
|
|
|
LastResult = result;
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
{
|
|
foreach (string warning in result.Warnings)
|
|
Warnings.Add(warning);
|
|
});
|
|
|
|
AddLog($"Sync abgeschlossen. Dateien: {result.FilesWritten.Count}, +{result.LinesAdded}/-{result.LinesRemoved} Zeilen");
|
|
|
|
if (!IsDryRun)
|
|
{
|
|
string hooksPath = Path.Combine(Directory.GetCurrentDirectory(), "game_hooks.json");
|
|
await File.WriteAllTextAsync(hooksPath, JsonSerializer.Serialize(allHooks, new JsonSerializerOptions { WriteIndented = true }), CancellationToken.None).ConfigureAwait(false);
|
|
AddLog($"game_hooks.json aktualisiert: {hooksPath}");
|
|
}
|
|
|
|
if (UseGitDiff)
|
|
await AppendGitDiffStatAsync(CancellationToken.None).ConfigureAwait(false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AddLog($"Sync fehlgeschlagen: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
IsRunning = false;
|
|
}
|
|
}
|
|
|
|
private async Task<HookDiff> ComputeDiffAsync(CancellationToken cancellationToken)
|
|
{
|
|
IsRunning = true;
|
|
|
|
RunOnUIThread(() =>
|
|
{
|
|
DiffEntries.Clear();
|
|
Warnings.Clear();
|
|
});
|
|
|
|
try
|
|
{
|
|
HookDefinition[] oldHooks = await LoadOldHooksAsync(cancellationToken).ConfigureAwait(false);
|
|
HookDefinition[] newHooks = await LoadNewHooksAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
HookDiff diff = _diffService.Diff(oldHooks.ToList(), newHooks.ToList());
|
|
_lastDiff = diff;
|
|
|
|
await Dispatcher.UIThread.InvokeAsync(() =>
|
|
{
|
|
foreach (HookDefinition hook in diff.Added)
|
|
DiffEntries.Add(ToEntry(HookDiffKind.Added, hook));
|
|
|
|
foreach (HookDefinition hook in diff.Changed)
|
|
DiffEntries.Add(ToEntry(HookDiffKind.Changed, hook));
|
|
|
|
foreach (HookDefinition hook in diff.Removed)
|
|
DiffEntries.Add(ToEntry(HookDiffKind.Removed, hook));
|
|
});
|
|
|
|
AddLog($"Diff berechnet: +{diff.Added.Count} ~{diff.Changed.Count} -{diff.Removed.Count}");
|
|
return diff;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AddLog($"Diff-Berechnung fehlgeschlagen: {ex.Message}");
|
|
return new HookDiff(Array.Empty<HookDefinition>(), Array.Empty<HookDefinition>(), Array.Empty<HookDefinition>());
|
|
}
|
|
finally
|
|
{
|
|
IsRunning = false;
|
|
}
|
|
}
|
|
|
|
private async Task<HookDefinition[]> LoadOldHooksAsync(CancellationToken cancellationToken)
|
|
{
|
|
string hooksPath = Path.Combine(Directory.GetCurrentDirectory(), "game_hooks.json");
|
|
if (!File.Exists(hooksPath))
|
|
return Array.Empty<HookDefinition>();
|
|
|
|
string json = await File.ReadAllTextAsync(hooksPath, cancellationToken).ConfigureAwait(false);
|
|
return JsonSerializer.Deserialize<HookDefinition[]>(json) ?? Array.Empty<HookDefinition>();
|
|
}
|
|
|
|
private async Task<HookDefinition[]> LoadNewHooksAsync(CancellationToken cancellationToken)
|
|
{
|
|
HookClassifier classifier = HookClassifier.LoadFromFile(null, new UIProgress<string>(AddLog));
|
|
|
|
string? il2CppPath = SteamLocator.FindIl2CppAssembliesPath();
|
|
if (string.IsNullOrWhiteSpace(il2CppPath) || !Directory.Exists(il2CppPath))
|
|
throw new DirectoryNotFoundException("IL2CPP-Pfad konnte nicht automatisch gefunden werden.");
|
|
|
|
il2CppPath = il2CppPath.Trim();
|
|
|
|
var result = await _extractorService
|
|
.ExtractAsync(il2CppPath, classifier, new UIProgress<string>(AddLog), cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
if (!result.IsSuccess)
|
|
throw new InvalidOperationException(result.Error ?? "Extraction failed");
|
|
|
|
return result.Hooks.ToArray();
|
|
}
|
|
|
|
private async Task AppendGitDiffStatAsync(CancellationToken cancellationToken)
|
|
{
|
|
var process = new Process
|
|
{
|
|
StartInfo = new ProcessStartInfo
|
|
{
|
|
FileName = "git",
|
|
Arguments = "diff --stat",
|
|
WorkingDirectory = FrameworkSourcePath,
|
|
UseShellExecute = false,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true,
|
|
CreateNoWindow = true,
|
|
},
|
|
};
|
|
|
|
process.Start();
|
|
string output = await process.StandardOutput.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
|
string error = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
|
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
if (!string.IsNullOrWhiteSpace(output))
|
|
AddLog(output.Trim());
|
|
|
|
if (process.ExitCode != 0 && !string.IsNullOrWhiteSpace(error))
|
|
AddLog(error.Trim());
|
|
}
|
|
|
|
private void OpenBackupFolder()
|
|
{
|
|
if (!Directory.Exists(FrameworkSourcePath))
|
|
return;
|
|
|
|
Process.Start(new ProcessStartInfo
|
|
{
|
|
FileName = FrameworkSourcePath,
|
|
UseShellExecute = true,
|
|
});
|
|
}
|
|
|
|
private static HookDiffEntry ToEntry(HookDiffKind kind, HookDefinition hook)
|
|
{
|
|
string signature = $"{hook.ReturnType} {hook.MethodName}({string.Join(", ", hook.Parameters.Select(p => $"{p.Type} {p.Name}"))})";
|
|
return new HookDiffEntry(kind, hook.Group, hook.ClassName, hook.MethodName, signature);
|
|
}
|
|
|
|
private void AddLog(string message)
|
|
{
|
|
string line = $"[{DateTime.Now:HH:mm:ss}] {message}";
|
|
if (SyncLog.Count > 1000)
|
|
SyncLog.RemoveAt(0);
|
|
|
|
SyncLog.Add(line);
|
|
}
|
|
} |