GUI: Implement plugin interface

This commit is contained in:
Katy Coe
2020-12-19 20:49:39 +01:00
parent 3ee658d5ac
commit 637952f360
11 changed files with 621 additions and 15 deletions

View File

@@ -1,5 +1,8 @@
// Copyright (c) 2020 Katy Coe - https://www.djkaty.com - https://github.com/djkaty
// All rights reserved
/*
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved.
*/
using System;
using System.Collections.Generic;
@@ -22,7 +25,7 @@ namespace Il2CppInspectorGUI
/// </summary>
public partial class App : Application, INotifyPropertyChanged
{
// Catch unhandled exceptions for debugging startup failures
// Catch unhandled exceptions for debugging startup failures and plugins
public App() : base() {
var np = Environment.NewLine + Environment.NewLine;
@@ -30,7 +33,11 @@ namespace Il2CppInspectorGUI
MessageBox.Show(e.Exception.GetType() + ": " + e.Exception.Message
+ np + e.Exception.StackTrace
+ np + "More details may follow in subsequent error boxes",
"Oopsie... Il2CppInspector could not start up");
"Il2CppInspector encountered a fatal error");
if (e.Exception is FileNotFoundException fe)
MessageBox.Show("Missing file: " + fe.FileName, "Additional error information");
var ex = e.Exception;
while (ex.InnerException != null) {
MessageBox.Show(ex.GetType() + ": " + ex.Message + np
@@ -68,7 +75,16 @@ namespace Il2CppInspectorGUI
// Initialization entry point
protected override void OnStartup(StartupEventArgs e) {
// Set contents of load options window
ResetLoadOptions();
// Set handlers for plugin manager
PluginManager.ErrorHandler += (s, e) => {
MessageBox.Show($"The plugin {e.Plugin.Name} encountered an error while executing {e.Operation}: {e.Exception.Message}."
+ Environment.NewLine + Environment.NewLine + "The application will continue but may not behave as expected.", "Plugin error");
};
PluginManager.StatusHandler += (s, e) => StatusUpdate(e.Plugin, "Plugin " + e.Plugin.Name + ": " + e.Text);
}
public void ResetLoadOptions() {

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2020 Katy Coe - https://www.djkaty.com - https://github.com/djkaty
// All rights reserved
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Windows;
using System.Windows.Data;
namespace Il2CppInspector.GUI
{
// Adapted from https://stackoverflow.com/a/37307169 and https://stackoverflow.com/a/28316967
internal class EqualityConverter : IMultiValueConverter
{
public object TrueValue { get; set; }
public object FalseValue { get; set; }
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (values.Length < 2)
return FalseValue;
return values[0].Equals(values[1]) ? TrueValue : FalseValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}

View File

@@ -16,7 +16,16 @@ namespace Il2CppInspector.GUI
if (value == null || targetType != typeof(string))
return DependencyProperty.UnsetValue;
return ((ulong) value).ToString("x16");
return value switch {
ulong n => n.ToString("x16"),
long n => n.ToString("x16"),
uint n => n.ToString("x8"),
int n => n.ToString("x8"),
ushort n => n.ToString("x4"),
short n => n.ToString("x4"),
byte n => n.ToString("x2"),
_ => throw new NotImplementedException("Unknown number format")
};
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {

View File

@@ -4,7 +4,8 @@
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<PublishTrimmed>true</PublishTrimmed>
<!-- Plugins may require bass class library assemblies we're not using so disable trimming -->
<PublishTrimmed>false</PublishTrimmed>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<UseWPF>true</UseWPF>
<AssemblyName>Il2CppInspector</AssemblyName>

View File

@@ -1,5 +1,8 @@
// Copyright (c) 2020 Katy Coe - https://www.djkaty.com - https://github.com/djkaty
// All rights reserved
/*
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved.
*/
using System;
using System.Collections.Generic;

View File

@@ -434,8 +434,13 @@
</TextBlock>
</Button>
<!-- Load options -->
<Button Grid.Row="2" Name="btnLoadOptions" Style="{StaticResource LightBoxButton}" Click="BtnLoadOptions_Click" Margin="0,0,10,10" Padding="5" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontSize="18" Width="180" Content="Import options..."/>
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
<!-- Plugin options -->
<Button Name="btnPluginOptions" Style="{StaticResource LightBoxButton}" Click="BtnPluginOptions_Click" Margin="0,0,10,10" Padding="5" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontSize="18" Width="180" Content="Manage plugins..."/>
<!-- Load options -->
<Button Name="btnLoadOptions" Style="{StaticResource LightBoxButton}" Click="BtnLoadOptions_Click" Margin="0,0,10,10" Padding="5" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontSize="18" Width="180" Content="Import options..."/>
</StackPanel>
</Grid>
<Button Name="btnSelectBinaryFile" Style="{StaticResource LightBoxButton}" Margin="100" Click="BtnSelectBinaryFile_OnClick" Visibility="Hidden">
<TextBlock TextAlignment="Center">

View File

@@ -1,5 +1,8 @@
// Copyright (c) 2020 Katy Coe - https://www.djkaty.com - https://github.com/djkaty
// All rights reserved
/*
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved.
*/
using System;
using System.Collections.Generic;
@@ -71,6 +74,15 @@ namespace Il2CppInspectorGUI
Process.Start(new ProcessStartInfo {FileName = e.Uri.ToString(), UseShellExecute = true});
}
/// <summary>
/// Open Manage Plugins dialog
/// </summary>
private void BtnPluginOptions_Click(object sender, RoutedEventArgs e) {
var configDlg = new PluginManagerDialog();
configDlg.Owner = this;
configDlg.ShowDialog();
}
/// <summary>
/// Open Load Options dialog
/// </summary>
@@ -109,7 +121,7 @@ namespace Il2CppInspectorGUI
else {
areaBusyIndicator.Visibility = Visibility.Hidden;
grdFirstPage.Visibility = Visibility.Visible;
MessageBox.Show(this, app.LastException.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(this, app.LastException.Message + Environment.NewLine + app.LastException.StackTrace, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -145,7 +157,7 @@ namespace Il2CppInspectorGUI
else {
areaBusyIndicator.Visibility = Visibility.Hidden;
btnSelectBinaryFile.Visibility = Visibility.Visible;
MessageBox.Show(this, app.LastException.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(this, app.LastException.Message + Environment.NewLine + app.LastException.StackTrace, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -183,7 +195,7 @@ namespace Il2CppInspectorGUI
else {
areaBusyIndicator.Visibility = Visibility.Hidden;
grdFirstPage.Visibility = Visibility.Visible;
MessageBox.Show(this, app.LastException.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
MessageBox.Show(this, app.LastException.Message + Environment.NewLine + app.LastException.StackTrace, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
}

View File

@@ -0,0 +1,186 @@
<Window x:Class="Il2CppInspector.GUI.PluginConfigurationDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Il2CppInspector.GUI"
xmlns:pluginapi="clr-namespace:Il2CppInspector.PluginAPI.V100;assembly=Il2CppInspector.Common"
mc:Ignorable="d"
Title="{Binding Path=Plugin.Name, StringFormat=Configuration for {0}}" Height="400" Width="700"
ResizeMode="NoResize"
WindowStartupLocation="CenterOwner"
SizeToContent="Height" MaxHeight="800">
<Window.Resources>
<local:OptionTemplateSelector x:Key="OptionTemplateSelector"/>
<local:HexStringValueConverter x:Key="HexStringValueConverter" />
<local:EqualityConverter x:Key="EqualityVisibilityConverter" TrueValue="{x:Static Visibility.Visible}" FalseValue="{x:Static Visibility.Collapsed}" />
<BooleanToVisibilityConverter x:Key="VisibleIfTrueConverter" />
<!-- Option layouts -->
<DataTemplate x:Key="TextTemplate">
<DockPanel>
<TextBlock DockPanel.Dock="Left" Width="250" VerticalAlignment="Center">
<TextBlock Text="{Binding Path=Description}"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<TextBox DockPanel.Dock="Right" VerticalAlignment="Center" Padding="2" Margin="0,4,4,4" Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="FilePathTemplate">
<DockPanel>
<Button Name="btnFilePathSelector" DockPanel.Dock="Right" Width="70" Margin="4" Click="btnFilePathSelector_Click">Browse</Button>
<TextBlock DockPanel.Dock="Left" Width="250" VerticalAlignment="Center">
<TextBlock Text="{Binding Description}"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<TextBlock Name="txtFilePathSelector" DockPanel.Dock="Right" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="3" ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Text}" Text="{Binding Value}"/>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="NumberTemplate">
<StackPanel>
<!-- Decimal number -->
<DockPanel>
<DockPanel.Visibility>
<MultiBinding Converter="{StaticResource EqualityVisibilityConverter}">
<Binding Path="Style" />
<Binding Source="{x:Static pluginapi:PluginOptionNumberStyle.Decimal}" Mode="OneWay" />
</MultiBinding>
</DockPanel.Visibility>
<TextBlock DockPanel.Dock="Left" Width="250" VerticalAlignment="Center">
<TextBlock Text="{Binding Description}"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<TextBox Name="txtDecimalString" DockPanel.Dock="Right" VerticalAlignment="Center" Padding="2" Margin="0,4,4,4" Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</DockPanel>
<!-- Hex number -->
<DockPanel>
<DockPanel.Visibility>
<MultiBinding Converter="{StaticResource EqualityVisibilityConverter}">
<Binding Path="Style" />
<Binding Source="{x:Static pluginapi:PluginOptionNumberStyle.Hex}" Mode="OneWay" />
</MultiBinding>
</DockPanel.Visibility>
<TextBlock DockPanel.Dock="Left" Width="250" VerticalAlignment="Center">
<TextBlock Text="{Binding Description}"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<DockPanel HorizontalAlignment="Stretch">
<Label Margin="0,3,3,5">0x</Label>
<TextBox Name="txtHexString" Padding="2" Margin="0,6,4,6" Text="{Binding Value, Converter={StaticResource HexStringValueConverter}, UpdateSourceTrigger=PropertyChanged}" PreviewTextInput="txtHexString_PreviewTextInput"/>
</DockPanel>
</DockPanel>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="BooleanTemplate">
<DockPanel Margin="250,0,0,0">
<TextBlock DockPanel.Dock="Left" VerticalAlignment="Top" Margin="0,10,2,6"
Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
<CheckBox DockPanel.Dock="Right" VerticalAlignment="Center" Margin="0,10,2,6" IsChecked="{Binding Value}">
<TextBlock VerticalAlignment="Center" TextWrapping="Wrap" Text="{Binding Description}" />
</CheckBox>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="ChoiceTemplate">
<StackPanel>
<!-- Drop-down box version -->
<DockPanel>
<DockPanel.Visibility>
<MultiBinding Converter="{StaticResource EqualityVisibilityConverter}">
<Binding Path="Style" />
<Binding Source="{x:Static pluginapi:PluginOptionChoiceStyle.Dropdown}" Mode="OneWay" />
</MultiBinding>
</DockPanel.Visibility>
<TextBlock DockPanel.Dock="Left" Width="250" VerticalAlignment="Center">
<TextBlock Text="{Binding Description}"></TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource VisibleIfTrueConverter}}" Text="*" Foreground="Red"/>
</TextBlock>
<ComboBox DockPanel.Dock="Right" Margin="4,8,4,8" ItemsSource="{Binding Choices}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding Value}"></ComboBox>
</DockPanel>
<!-- Radio buttons version -->
<DockPanel>
<DockPanel.Visibility>
<MultiBinding Converter="{StaticResource EqualityVisibilityConverter}">
<Binding Path="Style" />
<Binding Source="{x:Static pluginapi:PluginOptionChoiceStyle.List}" Mode="OneWay" />
</MultiBinding>
</DockPanel.Visibility>
<GroupBox Header="{Binding Description}" Margin="5" Padding="5">
<ListBox ItemsSource="{Binding Choices}" SelectedItem="{Binding Value}" BorderBrush="Transparent">
<ListBox.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding Header, RelativeSource={RelativeSource AncestorType=GroupBox}}"
Content="{Binding}"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Focusable="False"
IsHitTestVisible="False"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</GroupBox>
</DockPanel>
</StackPanel>
</DataTemplate>
<!-- Configure ListBox to display configuration controls nicely -->
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Transparent"/>
<!-- Force ListBox to width of window -->
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="2,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<!-- Title -->
<Label Grid.Row="0" Foreground="{StaticResource WindowsBlue}" FontSize="20" HorizontalAlignment="Left" VerticalAlignment="Center"
Content="{Binding Plugin.Name}" ContentStringFormat="Configuration for {0}" Padding="8,0,0,0"/>
<!-- Options editor -->
<ListBox Grid.Row="1" Name="lstOptions" ItemsSource="{Binding Plugin.Options}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" Padding="8" BorderBrush="Transparent">
<ListBox.ItemTemplateSelector>
<local:OptionTemplateSelector TextTemplate="{StaticResource TextTemplate}"
FilePathTemplate="{StaticResource FilePathTemplate}"
NumberTemplate="{StaticResource NumberTemplate}"
BooleanTemplate="{StaticResource BooleanTemplate}"
ChoiceTemplate="{StaticResource ChoiceTemplate}">
</local:OptionTemplateSelector>
</ListBox.ItemTemplateSelector>
</ListBox>
<!-- Accept button -->
<StackPanel Grid.Row="2" VerticalAlignment="Bottom" Margin="10">
<Button Name="okButton" Click="okButton_Click" Style="{StaticResource LightBoxButton}" IsDefault="True" Width="150" FontSize="18">OK</Button>
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,93 @@
/*
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Text;
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.Shapes;
using Microsoft.Win32;
using Il2CppInspectorGUI;
using System.Windows.Forms.Design;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Linq;
using Il2CppInspector.PluginAPI.V100;
using static Il2CppInspector.PluginManager;
namespace Il2CppInspector.GUI
{
// Class which selects the correct control to display for each plugin option
public class OptionTemplateSelector : DataTemplateSelector
{
public DataTemplate TextTemplate { get; set; }
public DataTemplate FilePathTemplate { get; set; }
public DataTemplate NumberTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate ChoiceTemplate { get; set; }
// Use some fancy reflection to get the right template property
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
var option = (IPluginOption) item;
return (DataTemplate) GetType().GetProperty(option.GetType().Name.Split("`")[0]["PluginOption".Length..] + "Template").GetValue(this);
}
}
/// <summary>
/// Interaction logic for PluginConfigurationDialog.xaml
/// </summary>
public partial class PluginConfigurationDialog : Window
{
// Item to configure
public IPlugin Plugin { get; }
public PluginConfigurationDialog(IPlugin plugin) {
InitializeComponent();
DataContext = this;
Plugin = plugin;
}
private void okButton_Click(object sender, RoutedEventArgs e) {
// Closes dialog box automatically
DialogResult = true;
}
// Select a file path
private void btnFilePathSelector_Click(object sender, RoutedEventArgs e) {
var option = (PluginOptionFilePath) ((Button) sender).DataContext;
var openFileDialog = new OpenFileDialog {
Title = option.Description,
Filter = "All files (*.*)|*.*",
FileName = option.Value,
CheckFileExists = true
};
if (openFileDialog.ShowDialog() == true) {
option.Value = openFileDialog.FileName;
// This spaghetti saves us from implementing INotifyPropertyChanged on Plugin.Options
// (we don't want to expose WPF stuff in our SDK)
// Will break if we change the format of the FilePathDataTemplate too much
var tb = ((DockPanel) ((Button) sender).Parent).Children.OfType<TextBlock>().First(n => n.Name == "txtFilePathSelector");
tb.Text = option.Value;
}
}
// Only allow hex characters in hex string
private void txtHexString_PreviewTextInput(object sender, TextCompositionEventArgs e) {
e.Handled = !Regex.IsMatch(e.Text, @"[A-Fa-f0-9]");
}
}
}

View File

@@ -0,0 +1,139 @@
<Window x:Class="Il2CppInspector.GUI.PluginManagerDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Il2CppInspector.GUI"
mc:Ignorable="d"
Title="Plugin Manager" Height="450" Width="800"
WindowStartupLocation="CenterOwner">
<Window.Resources>
<!-- Configure ListBox to display plugins nicely -->
<Style x:Key="ConfigItemStyle" TargetType="{x:Type ListBoxItem}">
<!-- Alternating background colours -->
<Style.Triggers>
<Trigger Property="ItemsControl.AlternationIndex" Value="0">
<Setter Property="Background" Value="AliceBlue" />
</Trigger>
<Trigger Property="ItemsControl.AlternationIndex" Value="1">
<Setter Property="Background" Value="AliceBlue" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Background" Value="LightGray"/>
</Trigger>
</Style.Triggers>
<!-- Force ListBox to width of window -->
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="2,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<!-- Remove highlight on hover and selection; set border on selection instead -->
<Trigger Property="IsSelected" Value="True">
<Setter Property="BorderBrush" Value="{StaticResource MicrosoftBlue}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
<RowDefinition Height="70" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="60" />
</Grid.ColumnDefinitions>
<!-- Title -->
<Label Grid.Row="0" Foreground="{StaticResource WindowsBlue}" FontSize="20" HorizontalAlignment="Left" VerticalAlignment="Center"
Content="Plugin Manager" Padding="8,0,0,0"/>
<!-- Plugins editor -->
<ListBox Grid.Row="1" Name="lstPlugins" ItemsSource="{Binding ManagedPlugins}" ScrollViewer.HorizontalScrollBarVisibility="Disabled" BorderBrush="Transparent" AlternationCount="2" SelectionChanged="lstPlugins_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="130" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="60" />
</Grid.RowDefinitions>
<CheckBox Grid.Column="0" Margin="6" VerticalAlignment="Center" IsChecked="{Binding Enabled}">
<CheckBox.LayoutTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5" />
</CheckBox.LayoutTransform>
</CheckBox>
<TextBlock Grid.Column="1" Margin="0,10,0,10" VerticalAlignment="Center">
<TextBlock Text="{Binding Plugin.Name}" FontSize="14" Foreground="{StaticResource WindowsBlue}" />
<TextBlock Text="{Binding Plugin.Author, StringFormat={}by {0}}" />
<TextBlock Text="{Binding Plugin.Version, StringFormat={}[{0}]}" />
<LineBreak/>
<TextBlock Text="{Binding Plugin.Description}" TextWrapping="Wrap" />
</TextBlock>
<Button Grid.Column="2" Margin="15" Padding="5" VerticalAlignment="Center" Content="Configure..." Click="btnConfig_Click">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding Plugin.Options}" Value="{x:Null}">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style BasedOn="{StaticResource ConfigItemStyle}" TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Available}" Value="False">
<Setter Property="ListBoxItem.IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<!-- Reordering controls -->
<StackPanel Grid.Row="1" Grid.Column="1" Margin="10,0,0,0">
<Button Name="btnTop" FontFamily="Segoe MDL2 Assets" Style="{StaticResource LightBoxButton}" FontSize="14" Padding="5,8,5,8" Margin="2" Content="&#xE110;&#xE110;" Click="btnTop_Click"/>
<Button Name="btnUp" FontFamily="Segoe MDL2 Assets" Style="{StaticResource LightBoxButton}" FontSize="20" Padding="5" Margin="2" Content="&#xE110;" Click="btnUp_Click"/>
<Button Name="btnDown" FontFamily="Segoe MDL2 Assets" Style="{StaticResource LightBoxButton}" FontSize="20" Padding="5" Margin="2" Content="&#xE1FD;" Click="btnDown_Click"/>
<Button Name="btnBottom" FontFamily="Segoe MDL2 Assets" Style="{StaticResource LightBoxButton}" FontSize="14" Padding="5,8,5,8" Margin="2" Content="&#xE1FD;&#xE1FD;" Click="btnBottom_Click"/>
</StackPanel>
<!-- Bottom buttons -->
<DockPanel Grid.Row="2" Grid.ColumnSpan="2" VerticalAlignment="Bottom" Margin="10">
<!-- Accept button -->
<Button DockPanel.Dock="Right" Name="okButton" Click="okButton_Click" Style="{StaticResource LightBoxButton}" IsDefault="True" Width="150" FontSize="18" Padding="5">OK</Button>
<!-- Refresh button -->
<Button DockPanel.Dock="Left" Name="refreshButton" Click="refreshButton_Click" Style="{StaticResource LightBoxButton}" HorizontalAlignment="Left" Width="150" FontSize="18" Padding="5">Refresh</Button>
<!-- Get Plugins button -->
<Button DockPanel.Dock="Left" Name="getPluginsButton" Click="getPluginsButton_Click" Style="{StaticResource LightBoxButton}" HorizontalAlignment="Left" Margin="10,0,0,0" Width="150" FontSize="18" Padding="5">Get Plugins...</Button>
</DockPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,112 @@
/*
Copyright 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Text;
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.Shapes;
namespace Il2CppInspector.GUI
{
/// <summary>
/// Interaction logic for PluginManagerDialog.xaml
/// </summary>
public partial class PluginManagerDialog : Window
{
public PluginManagerDialog() {
InitializeComponent();
DataContext = PluginManager.AsInstance;
// Set default re-order button state
lstPlugins_SelectionChanged(null, null);
}
private void okButton_Click(object sender, RoutedEventArgs e) {
DialogResult = true;
}
// Reload list of plugins and reset settings
private void refreshButton_Click(object sender, RoutedEventArgs e) {
PluginManager.Reload();
}
// Open configuration for specific plugin
private void btnConfig_Click(object sender, RoutedEventArgs e) {
var plugin = (ManagedPlugin) ((Button) sender).DataContext;
var configDlg = new PluginConfigurationDialog(plugin.Plugin);
configDlg.Owner = this;
configDlg.ShowDialog();
}
// Re-ordering controls
private void lstPlugins_SelectionChanged(object sender, SelectionChangedEventArgs e) {
var index = lstPlugins.SelectedIndex;
btnTop.IsEnabled = btnUp.IsEnabled = index > 0;
btnBottom.IsEnabled = btnDown.IsEnabled = index > -1 && index < lstPlugins.Items.Count - 1;
}
private void btnUp_Click(object sender, RoutedEventArgs e) {
var plugins = PluginManager.AsInstance.ManagedPlugins;
var index = lstPlugins.SelectedIndex;
var item = (ManagedPlugin) lstPlugins.SelectedItem;
plugins.Remove(item);
plugins.Insert(index - 1, item);
lstPlugins.SelectedIndex = index - 1;
}
private void btnDown_Click(object sender, RoutedEventArgs e) {
var plugins = PluginManager.AsInstance.ManagedPlugins;
var index = lstPlugins.SelectedIndex;
var item = (ManagedPlugin) lstPlugins.SelectedItem;
plugins.Remove(item);
plugins.Insert(index + 1, item);
lstPlugins.SelectedIndex = index + 1;
}
private void btnTop_Click(object sender, RoutedEventArgs e) {
var plugins = PluginManager.AsInstance.ManagedPlugins;
var index = lstPlugins.SelectedIndex;
var item = (ManagedPlugin) lstPlugins.SelectedItem;
plugins.Remove(item);
plugins.Insert(0, item);
lstPlugins.SelectedIndex = 0;
}
private void btnBottom_Click(object sender, RoutedEventArgs e) {
var plugins = PluginManager.AsInstance.ManagedPlugins;
var index = lstPlugins.SelectedIndex;
var item = (ManagedPlugin) lstPlugins.SelectedItem;
plugins.Remove(item);
plugins.Add(item);
lstPlugins.SelectedIndex = plugins.Count - 1;
}
/// Get plugins button
private void getPluginsButton_Click(object sender, RoutedEventArgs e) {
Process.Start(new ProcessStartInfo { FileName = @"https://github.com/djkaty/Il2CppInspectorPlugins", UseShellExecute = true });
}
}
}