From b9e9b0c4139bb873d40e29761c53c1abc3f22c15 Mon Sep 17 00:00:00 2001 From: kenjiuno Date: Fri, 29 Dec 2023 23:58:52 +0900 Subject: [PATCH 01/34] WIP --- OpenKh.Patcher/Metadata.cs | 9 +- .../SimpleAsyncActionCommand.cs | 70 ++ .../Services/GetActiveWindowService.cs | 14 + .../Services/GetDiffService.cs | 14 + .../Services/GetDiffToolsService.cs | 59 ++ .../Services/YamlGeneratorService.cs | 93 +++ .../UserControls/FolderSelectorControl.xaml | 20 + .../FolderSelectorControl.xaml.cs | 51 ++ .../UserControls/SaveFileSelectorControl.xaml | 20 + .../SaveFileSelectorControl.xaml.cs | 73 ++ .../TaskStatusObserverControl.xaml | 10 + .../TaskStatusObserverControl.xaml.cs | 85 +++ .../ViewModels/MainViewModel.cs | 15 +- .../Views/MainWindow.xaml | 5 + .../Views/SelectDiffToolVM.cs | 86 +++ .../Views/SelectDiffToolWindow.xaml | 36 + .../Views/SelectDiffToolWindow.xaml.cs | 33 + .../Views/YamlGeneratorVM.cs | 94 +++ .../Views/YamlGeneratorWindow.xaml | 31 + .../Views/YamlGeneratorWindow.xaml.cs | 34 + OpenKh.sln | 672 ------------------ 21 files changed, 847 insertions(+), 677 deletions(-) create mode 100644 OpenKh.Tools.Common.Wpf/SimpleAsyncActionCommand.cs create mode 100644 OpenKh.Tools.ModsManager/Services/GetActiveWindowService.cs create mode 100644 OpenKh.Tools.ModsManager/Services/GetDiffService.cs create mode 100644 OpenKh.Tools.ModsManager/Services/GetDiffToolsService.cs create mode 100644 OpenKh.Tools.ModsManager/Services/YamlGeneratorService.cs create mode 100644 OpenKh.Tools.ModsManager/UserControls/FolderSelectorControl.xaml create mode 100644 OpenKh.Tools.ModsManager/UserControls/FolderSelectorControl.xaml.cs create mode 100644 OpenKh.Tools.ModsManager/UserControls/SaveFileSelectorControl.xaml create mode 100644 OpenKh.Tools.ModsManager/UserControls/SaveFileSelectorControl.xaml.cs create mode 100644 OpenKh.Tools.ModsManager/UserControls/TaskStatusObserverControl.xaml create mode 100644 OpenKh.Tools.ModsManager/UserControls/TaskStatusObserverControl.xaml.cs create mode 100644 OpenKh.Tools.ModsManager/Views/SelectDiffToolVM.cs create mode 100644 OpenKh.Tools.ModsManager/Views/SelectDiffToolWindow.xaml create mode 100644 OpenKh.Tools.ModsManager/Views/SelectDiffToolWindow.xaml.cs create mode 100644 OpenKh.Tools.ModsManager/Views/YamlGeneratorVM.cs create mode 100644 OpenKh.Tools.ModsManager/Views/YamlGeneratorWindow.xaml create mode 100644 OpenKh.Tools.ModsManager/Views/YamlGeneratorWindow.xaml.cs diff --git a/OpenKh.Patcher/Metadata.cs b/OpenKh.Patcher/Metadata.cs index dd9f15a44..d62e88a28 100644 --- a/OpenKh.Patcher/Metadata.cs +++ b/OpenKh.Patcher/Metadata.cs @@ -35,8 +35,13 @@ public class Dependency public static Metadata Read(Stream stream) => deserializer.Deserialize(new StreamReader(stream)); - public void Write(Stream stream) => - serializer.Serialize(new StreamWriter(stream), this); + public void Write(Stream stream) + { + using (var writer = new StreamWriter(stream)) + { + serializer.Serialize(writer, this); + } + } public override string ToString() => serializer.Serialize(this); } diff --git a/OpenKh.Tools.Common.Wpf/SimpleAsyncActionCommand.cs b/OpenKh.Tools.Common.Wpf/SimpleAsyncActionCommand.cs new file mode 100644 index 000000000..d8871a640 --- /dev/null +++ b/OpenKh.Tools.Common.Wpf/SimpleAsyncActionCommand.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Input; + +namespace OpenKh.Tools.Common.Wpf +{ + public class SimpleAsyncActionCommand : ICommand + { + private readonly Func _asyncAction; + private readonly Action _newTask; + private Task _task = null; + public bool _isEnabled = true; + + public event EventHandler CanExecuteChanged; + public event PropertyChangedEventHandler PropertyChanged; + + public SimpleAsyncActionCommand( + Func asyncAction, + Action newTask = null + ) + { + _asyncAction = asyncAction; + _newTask = newTask; + } + + public bool IsEnabled + { + get => _isEnabled; + set + { + _isEnabled = value; + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } + + public bool CanExecute(object parameter) + { + return _isEnabled && (_task == null || _task.IsCompleted); + } + + public void Execute(object parameter) + { + if (CanExecute(parameter)) + { + async Task AwaitAsync(Task task) + { + _task = task; + _newTask?.Invoke(_task); + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + + try + { + await task; + } + finally + { + CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + } + + var task = AwaitAsync(_asyncAction((T)parameter)); + } + } + } +} diff --git a/OpenKh.Tools.ModsManager/Services/GetActiveWindowService.cs b/OpenKh.Tools.ModsManager/Services/GetActiveWindowService.cs new file mode 100644 index 000000000..24253198b --- /dev/null +++ b/OpenKh.Tools.ModsManager/Services/GetActiveWindowService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace OpenKh.Tools.ModsManager.Services +{ + public class GetActiveWindowService + { + public Window GetActiveWindow() => Application.Current.Windows.OfType().SingleOrDefault(x => x.IsActive); + } +} diff --git a/OpenKh.Tools.ModsManager/Services/GetDiffService.cs b/OpenKh.Tools.ModsManager/Services/GetDiffService.cs new file mode 100644 index 000000000..d89afd888 --- /dev/null +++ b/OpenKh.Tools.ModsManager/Services/GetDiffService.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModsManager.Services +{ + public class GetDiffService + { + public string Name { get; set; } + public Func> DiffAsync { get; set; } + } +} diff --git a/OpenKh.Tools.ModsManager/Services/GetDiffToolsService.cs b/OpenKh.Tools.ModsManager/Services/GetDiffToolsService.cs new file mode 100644 index 000000000..fce986e9e --- /dev/null +++ b/OpenKh.Tools.ModsManager/Services/GetDiffToolsService.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModsManager.Services +{ + public class GetDiffToolsService + { + /// `.yml` + public IEnumerable GetDiffServices(string extension) + { + IEnumerable TryToUse(string name, string exe) + { + if (File.Exists(exe)) + { + yield return new GetDiffService + { + Name = name, + DiffAsync = async (rawInput, rawOutput) => + { + var tempInput = Path.GetTempFileName() + extension; + var tempOutput = Path.GetTempFileName() + extension; + try + { + await File.WriteAllBytesAsync(tempInput, rawInput); + await File.WriteAllBytesAsync(tempOutput, rawOutput); + + var process = System.Diagnostics.Process.Start( + exe, + $"\"{tempInput}\" \"{tempOutput}\"" + ); + await process.WaitForExitAsync(); + + return null; + } + finally + { + File.Delete(tempInput); + File.Delete(tempOutput); + } + } + }; + } + } + + foreach (var one in new GetDiffService[0] + .Concat(TryToUse("WinMerge.exe", @"C:\Program Files\WinMerge\WinMergeU.exe")) + .Concat(TryToUse("TortoiseGitMerge.exe", @"C:\Program Files\TortoiseGit\bin\TortoiseGitMerge.exe")) + .Concat(TryToUse("TortoiseMerge.exe", @"C:\Program Files\TortoiseSVN\bin\TortoiseMerge.exe")) + ) + { + yield return one; + } + } + } +} diff --git a/OpenKh.Tools.ModsManager/Services/YamlGeneratorService.cs b/OpenKh.Tools.ModsManager/Services/YamlGeneratorService.cs new file mode 100644 index 000000000..fb9f38635 --- /dev/null +++ b/OpenKh.Tools.ModsManager/Services/YamlGeneratorService.cs @@ -0,0 +1,93 @@ +using OpenKh.Patcher; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModsManager.Services +{ + public class YamlGeneratorService + { + /// Return final yml blob if it is good to save. Otherwise return `null` if it is not fit to save. + public delegate Task GetDiffAsyncDelegate(byte[] rawInput, byte[] rawOutput); + + public async Task GenerateAsync( + string ymlFile, + GetDiffAsyncDelegate diffAsync + ) + { + var rawInput = await File.ReadAllBytesAsync(ymlFile); + + var mod = await Task.Run(() => File.Exists(ymlFile)) + ? Metadata.Read(new MemoryStream(rawInput, false)) + : new Metadata(); + + if (mod.Assets == null) + { + mod.Assets = new List(); + } + + mod.Assets.Clear(); + + var sourceDir = Path.GetDirectoryName(ymlFile); + + var assets = new List(); + + void TryToSearchDir(string firstDir) + { + var firstDirPath = Path.Combine(sourceDir, firstDir); + if (Directory.Exists(firstDirPath)) + { + assets.AddRange( + Directory.EnumerateFiles(firstDirPath, "*", SearchOption.AllDirectories) + .Select( + filePath => + { + var relativePath = Path.GetRelativePath(firstDir, filePath); + return new AssetFile + { + Name = relativePath, + Method = "copy", + Source = new List( + new AssetFile[] + { + new AssetFile + { + Name = relativePath, + } + } + ), + }; + } + ) + ); + } + } + + TryToSearchDir("field2d"); + TryToSearchDir("menu"); + + foreach (var asset in assets) + { + if (!mod.Assets.Any(it => it.Name == asset.Name)) + { + mod.Assets.Add(asset); + } + } + + { + var temp = new MemoryStream(); + mod.Write(temp); + var rawOutput = temp.ToArray(); + + var rawFinalOutput = await diffAsync(rawInput, rawOutput); + if (rawFinalOutput != null) + { + await File.WriteAllBytesAsync(ymlFile, rawFinalOutput); + } + } + } + } +} diff --git a/OpenKh.Tools.ModsManager/UserControls/FolderSelectorControl.xaml b/OpenKh.Tools.ModsManager/UserControls/FolderSelectorControl.xaml new file mode 100644 index 000000000..268e16379 --- /dev/null +++ b/OpenKh.Tools.ModsManager/UserControls/FolderSelectorControl.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/OpenKh.Tools.ModsManager/UserControls/FolderSelectorControl.xaml.cs b/OpenKh.Tools.ModsManager/UserControls/FolderSelectorControl.xaml.cs new file mode 100644 index 000000000..ae995d9f8 --- /dev/null +++ b/OpenKh.Tools.ModsManager/UserControls/FolderSelectorControl.xaml.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Xe.Tools.Wpf.Dialogs; + +namespace OpenKh.Tools.ModsManager.UserControls +{ + /// + /// Interaction logic for FolderSelectorControl.xaml + /// + public partial class FolderSelectorControl : UserControl + { + public FolderSelectorControl() + { + InitializeComponent(); + } + + public static readonly DependencyProperty FolderPathProperty = DependencyProperty.Register( + nameof(FolderPath), + typeof(string), + typeof(FolderSelectorControl), + new PropertyMetadata(string.Empty) + ); + + public string FolderPath + { + get => (string)GetValue(FolderPathProperty); + set => SetValue(FolderPathProperty, value); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + FileDialog.OnFolder( + path => FolderPath = path, + FolderPath, + Window.GetWindow(this) + ); + } + } +} diff --git a/OpenKh.Tools.ModsManager/UserControls/SaveFileSelectorControl.xaml b/OpenKh.Tools.ModsManager/UserControls/SaveFileSelectorControl.xaml new file mode 100644 index 000000000..70f82775d --- /dev/null +++ b/OpenKh.Tools.ModsManager/UserControls/SaveFileSelectorControl.xaml @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/OpenKh.Tools.ModsManager/UserControls/SaveFileSelectorControl.xaml.cs b/OpenKh.Tools.ModsManager/UserControls/SaveFileSelectorControl.xaml.cs new file mode 100644 index 000000000..9b4e52596 --- /dev/null +++ b/OpenKh.Tools.ModsManager/UserControls/SaveFileSelectorControl.xaml.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using Xe.Tools.Wpf.Dialogs; + +namespace OpenKh.Tools.ModsManager.UserControls +{ + /// + /// Interaction logic for SaveFileSelectorControl.xaml + /// + public partial class SaveFileSelectorControl : UserControl + { + public SaveFileSelectorControl() + { + InitializeComponent(); + } + + public static readonly DependencyProperty FilePathProperty = DependencyProperty.Register( + nameof(FilePath), + typeof(string), + typeof(SaveFileSelectorControl), + new PropertyMetadata(string.Empty) + ); + + public string FilePath + { + get => (string)GetValue(FilePathProperty); + set => SetValue(FilePathProperty, value); + } + + public static readonly DependencyProperty FilterProperty = DependencyProperty.Register( + nameof(Filter), + typeof(string), + typeof(SaveFileSelectorControl), + new PropertyMetadata(string.Empty) + ); + + public string Filter + { + get => (string)GetValue(FilterProperty); + set => SetValue(FilterProperty, value); + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + IEnumerable filters = + (Filter.Length != 0) + ? Filter.Split('|') + .Chunk(2) + .Select(pair => FileDialogFilter.ByPatterns(pair[0], pair[1].Split(';').AsEnumerable())) + .ToArray() + : null; + + FileDialog.OnSave( + path => FilePath = path, + filters, + FilePath, + Window.GetWindow(this) + ); + } + } +} diff --git a/OpenKh.Tools.ModsManager/UserControls/TaskStatusObserverControl.xaml b/OpenKh.Tools.ModsManager/UserControls/TaskStatusObserverControl.xaml new file mode 100644 index 000000000..50fee0236 --- /dev/null +++ b/OpenKh.Tools.ModsManager/UserControls/TaskStatusObserverControl.xaml @@ -0,0 +1,10 @@ + + diff --git a/OpenKh.Tools.ModsManager/UserControls/TaskStatusObserverControl.xaml.cs b/OpenKh.Tools.ModsManager/UserControls/TaskStatusObserverControl.xaml.cs new file mode 100644 index 000000000..eb894f665 --- /dev/null +++ b/OpenKh.Tools.ModsManager/UserControls/TaskStatusObserverControl.xaml.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using YamlDotNet.Core.Tokens; + +namespace OpenKh.Tools.ModsManager.UserControls +{ + /// + /// Interaction logic for TaskStatusObserverControl.xaml + /// + public partial class TaskStatusObserverControl : UserControl + { + public static readonly DependencyProperty TaskProperty = DependencyProperty.Register( + nameof(Task), + typeof(Task), + typeof(TaskStatusObserverControl), + new PropertyMetadata(null, TaskPropertyChangedCallback) + ); + + private static void TaskPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((TaskStatusObserverControl)d).TaskPropertyChangedCallback((Task)e.NewValue); + } + + private void TaskPropertyChangedCallback(Task value) + { + if (value == null) + { + _label.Content = "(Task result here)"; + } + else + { + async void AwaitAsync(Task task) + { + try + { + _label.Content = "(Awaiting task result)"; + + await task; + + if (ReferenceEquals(task, Task)) + { + _label.Content = $"Done on {DateTime.Now}"; + } + } + catch (Exception ex) + { + if (ReferenceEquals(task, Task)) + { + _label.Content = $"Error: {ex.Message}"; + _label.ToolTip = ex + ""; + } + } + } + + AwaitAsync(value); + } + } + + public Task Task + { + get => (Task)GetValue(TaskProperty); + set + { + SetValue(TaskProperty, value); + } + } + + public TaskStatusObserverControl() + { + InitializeComponent(); + } + } +} diff --git a/OpenKh.Tools.ModsManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModsManager/ViewModels/MainViewModel.cs index 4a148d8d2..5d37ef07e 100644 --- a/OpenKh.Tools.ModsManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModsManager/ViewModels/MainViewModel.cs @@ -30,6 +30,7 @@ public class MainViewModel : BaseNotifyPropertyChanged, IChangeModEnableState private static string ApplicationName = Utilities.GetApplicationName(); private static string ApplicationVersion = Utilities.GetApplicationVersion(); private Window Window => Application.Current.Windows.OfType().FirstOrDefault(x => x.IsActive); + private GetActiveWindowService _getActiveWindowService = new GetActiveWindowService(); private DebuggingWindow _debuggingWindow = new DebuggingWindow(); private ModViewModel _selectedValue; @@ -89,6 +90,7 @@ public class MainViewModel : BaseNotifyPropertyChanged, IChangeModEnableState public RelayCommand CheckOpenkhUpdateCommand { get; set; } public RelayCommand OpenPresetMenuCommand { get; set; } public RelayCommand CheckForModUpdatesCommand { get; set; } + public RelayCommand YamlGeneratorCommand { get; set; } public ModViewModel SelectedValue { @@ -505,6 +507,13 @@ await ModsService.InstallMod(name, isZipFile, isLuaFile, progress => _ => UpdateOpenkhAsync() ); + YamlGeneratorCommand = new RelayCommand( + _ => + { + new YamlGeneratorWindow() { Owner = _getActiveWindowService.GetActiveWindow(), }.Show(); + } + ); + _pcsx2Injector = new Pcsx2Injector(new OperationDispatcher()); FetchUpdates(); @@ -957,7 +966,7 @@ private async Task FetchUpdates() foreach (var mod in ModsList) { if (mod.UpdateCount > 0) - await ModsService.Update(mod.Source); + await ModsService.Update(mod.Source); } ReloadModsList(); } @@ -1007,8 +1016,8 @@ await progressWindowService.ShowAsync( MessageBox.Show(message, "OpenKh"); } - } - + } + public void UpdatePanaceaSettings() { if (PanaceaInstalled) diff --git a/OpenKh.Tools.ModsManager/Views/MainWindow.xaml b/OpenKh.Tools.ModsManager/Views/MainWindow.xaml index 204a9dd06..2f0146350 100644 --- a/OpenKh.Tools.ModsManager/Views/MainWindow.xaml +++ b/OpenKh.Tools.ModsManager/Views/MainWindow.xaml @@ -4,6 +4,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:OpenKh.Tools.ModsManager.Views" xmlns:my="clr-namespace:OpenKh.Tools.ModsManager.Services" + xmlns:vm="clr-namespace:OpenKh.Tools.ModsManager.ViewModels" + d:DataContext="{d:DesignInstance Type=vm:MainViewModel}" mc:Ignorable="d" Title="{Binding Title}" Height="{my:SettingBinding Height}" @@ -141,6 +143,9 @@ + + + Kingdom Hearts 2 diff --git a/OpenKh.Tools.ModsManager/Views/SelectDiffToolVM.cs b/OpenKh.Tools.ModsManager/Views/SelectDiffToolVM.cs new file mode 100644 index 000000000..655ffbae3 --- /dev/null +++ b/OpenKh.Tools.ModsManager/Views/SelectDiffToolVM.cs @@ -0,0 +1,86 @@ +using OpenKh.Tools.Common.Wpf; +using OpenKh.Tools.ModsManager.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; +using Xe.Tools; + +namespace OpenKh.Tools.ModsManager.Views +{ + public class SelectDiffToolVM : BaseNotifyPropertyChanged + { + public ColorThemeService ColorTheme => ColorThemeService.Instance; + public ICommand ProceedCommand { get; set; } + public Action OnSubmit { get; set; } = _ => { }; + public Action Close { get; set; } = () => { }; + public Action OnClosed { get; set; } = () => { }; + + private Action OnToolSelected { get; set; } = _ => { }; + + #region Tools + private IEnumerable _tools = Enumerable.Empty(); + public IEnumerable Tools + { + get => _tools; + set + { + _tools = value; + OnPropertyChanged(nameof(Tools)); + SelectedTool = _tools?.FirstOrDefault(); + } + } + #endregion + + #region SelectedTool + private GetDiffService _selectedTool; + public GetDiffService SelectedTool + { + get => _selectedTool; + set + { + _selectedTool = value; + OnPropertyChanged(nameof(SelectedTool)); + OnToolSelected(_selectedTool); + } + } + #endregion + + #region ProceedingTask + private Task _proceedingTask; + public Task ProceedingTask + { + get => _proceedingTask; + set + { + _proceedingTask = value; + OnPropertyChanged(nameof(ProceedingTask)); + } + } + #endregion + + public SelectDiffToolVM() + { + GetDiffService tool = null; + + SimpleAsyncActionCommand proceedCommand; + ProceedCommand = proceedCommand = new SimpleAsyncActionCommand( + async _ => + { + OnSubmit(tool); + Close(); + await Task.Yield(); + }, + task => ProceedingTask = task + ); + + OnToolSelected = it => + { + tool = it; + proceedCommand.IsEnabled = it != null; + }; + } + } +} diff --git a/OpenKh.Tools.ModsManager/Views/SelectDiffToolWindow.xaml b/OpenKh.Tools.ModsManager/Views/SelectDiffToolWindow.xaml new file mode 100644 index 000000000..6d697662f --- /dev/null +++ b/OpenKh.Tools.ModsManager/Views/SelectDiffToolWindow.xaml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + +