Compare commits

..
13 Commits
Author SHA1 Message Date
Alexandru Macocian c169ca1684 Menu to manage guildwars executables. 2021-04-14 15:01:43 +02:00
Alexandru Macocian 88214ea166 Darken views with text to improve readability.
Better define the reason why guildwars failed to launch.
Auto set to default when adding first account.
Auto set another default when removing the default account.
2021-04-14 14:12:38 +02:00
Alexandru Macocian 4bdcd1a811 Changed version control to check all subversions. 2021-04-14 13:50:15 +02:00
Alexandru Macocian fa4f665a3e Fixed a bug with updating account details. 2021-04-13 21:28:49 +02:00
Alexandru Macocian e59e022c0a Updated to 0.5.0 2021-04-13 21:13:38 +02:00
Alexandru Macocian d096dc696a Multi-account management.
Split settings into multiple categories.
2021-04-13 21:09:41 +02:00
Alexandru Macocian 071007c3e5 Create application manifest.
Require highest available rights.
Return dialog message when registry is failed to be set.
2021-04-12 19:35:08 +02:00
Alexandru Macocian 57192d5043 Experimental multi-launch support 2021-04-12 19:09:00 +02:00
amacocianandGitHub 6af72683a8 Update README.md 2021-04-12 13:26:54 +02:00
amacocianandGitHub 3af9567506 Update README.md 2021-04-12 13:09:02 +02:00
Alexandru Macocian 1d7adf3944 Merge branch 'master' of https://github.com/AlexMacocian/Daybreak 2021-04-10 19:00:46 +02:00
Alexandru Macocian fb7399adf8 Add support for experimental features. 2021-04-10 19:00:39 +02:00
amacocianandGitHub 6ebbffdda1 Update README.md 2021-04-10 11:25:13 +02:00
58 changed files with 2171 additions and 240 deletions
+79
View File
@@ -0,0 +1,79 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
namespace Daybreak.Behaviors
{
public class ScaleFontWithSize : Behavior<TextBlock>
{
public static readonly DependencyProperty MaxFontSizeProperty = DependencyProperty.Register("MaxFontSize", typeof(double), typeof(ScaleFontWithSize), new PropertyMetadata(12d));
public double MaxFontSize
{
get
{
return (double)this.GetValue(MaxFontSizeProperty);
}
set
{
this.SetValue(MaxFontSizeProperty, value);
}
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SizeChanged += (_, __) => this.CalculateFontSize();
DependencyPropertyDescriptor.FromProperty(
TextBlock.TextProperty, typeof(TextBlock)).AddValueChanged(this.AssociatedObject, (_, __) => this.CalculateFontSize());
DependencyPropertyDescriptor.FromProperty(
TextBlock.FontSizeProperty, typeof(TextBlock)).AddValueChanged(this.AssociatedObject, (_, __) => this.CalculateFontSize());
}
private void CalculateFontSize()
{
var textMeasurement = this.MeasureText(this.AssociatedObject.FontSize);
var maximumTextMeasurement = this.MeasureText(this.MaxFontSize);
var desiredWidthFontSize = this.MaxFontSize;
var desiredHeightFontSize = this.MaxFontSize;
if (Math.Round(textMeasurement.Height) != Math.Round(this.AssociatedObject.ActualHeight))
{
var scale = this.AssociatedObject.ActualHeight / maximumTextMeasurement.Height;
desiredHeightFontSize = (this.MaxFontSize * scale) - 1;
desiredHeightFontSize = desiredHeightFontSize <= this.MaxFontSize ? desiredHeightFontSize : this.MaxFontSize;
}
if (Math.Round(textMeasurement.Width) != Math.Round(this.AssociatedObject.ActualWidth))
{
var scale = this.AssociatedObject.ActualWidth / maximumTextMeasurement.Width;
desiredWidthFontSize = (this.MaxFontSize * scale) - 1;
desiredWidthFontSize = desiredWidthFontSize <= this.MaxFontSize ? desiredWidthFontSize : this.MaxFontSize;
}
var desiredFontSize = Math.Min(desiredHeightFontSize, desiredWidthFontSize);
if ((int)desiredFontSize != (int)this.AssociatedObject.FontSize && desiredFontSize > 0)
{
this.AssociatedObject.FontSize = desiredFontSize;
return;
}
}
private Size MeasureText(double fontSize)
{
var formattedText = new FormattedText(this.AssociatedObject.Text, CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(this.AssociatedObject.FontFamily, this.AssociatedObject.FontStyle, this.AssociatedObject.FontWeight, this.AssociatedObject.FontStretch),
fontSize, Brushes.Black, VisualTreeHelper.GetDpi(this.AssociatedObject).PixelsPerDip);
return new Size(formattedText.Width, formattedText.Height);
}
}
}
@@ -1,24 +1,24 @@
using Newtonsoft.Json;
using Daybreak.Models;
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Daybreak.Configuration
{
public sealed class ApplicationConfiguration
{
[JsonProperty("GamePath")]
public string GamePath { get; set; }
[JsonProperty("ToolboxPath")]
public string ToolboxPath { get; set; }
[JsonProperty("CharacterName")]
public string CharacterName { get; set; }
[JsonProperty("LeftBrowserDefault")]
public string LeftBrowserDefault { get; set; }
[JsonProperty("RightBrowserDefault")]
public string RightBrowserDefault { get; set; }
[JsonProperty("ProtectedUsername")]
public string ProtectedUsername { get; set; }
[JsonProperty("ProtectedPassword")]
public string ProtectedPassword { get; set; }
[JsonProperty("GuildwarsPaths")]
public List<GuildwarsPath> GuildwarsPaths { get; set; } = new();
[JsonProperty("ProtectedLoginCredentials")]
public List<ProtectedLoginCredentials> ProtectedLoginCredentials { get; set; } = new();
[JsonProperty("AddressBarReadonly")]
public bool AddressBarReadonly { get; set; } = true;
[JsonProperty("ExperimentalFeatures")]
public ExperimentalFeatures ExperimentalFeatures { get; set; } = new();
}
}
@@ -0,0 +1,7 @@
namespace Daybreak.Configuration
{
public sealed class ExperimentalFeatures
{
public bool MultiLaunchSupport { get; set; }
}
}
@@ -1,14 +1,14 @@
using Daybreak.Services.ApplicationDetection;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.ApplicationLifetime;
using Daybreak.Services.Bloogum;
using Daybreak.Services.Configuration;
using Daybreak.Services.Credentials;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Services.Screenshots;
using Daybreak.Services.Updater;
using Daybreak.Services.ViewManagement;
using Daybreak.Views;
using Microsoft.Web.WebView2.Core;
using Slim;
using System.Extensions;
@@ -26,11 +26,12 @@ namespace Daybreak.Configuration
serviceProducer.RegisterSingleton<ILogger, Logger>();
serviceProducer.RegisterSingleton<ApplicationLifetimeManager>();
serviceProducer.RegisterSingleton<ViewManager>();
serviceProducer.RegisterSingleton<IApplicationDetector, ApplicationDetector>();
serviceProducer.RegisterSingleton<IApplicationLauncher, ApplicationLauncher>();
serviceProducer.RegisterSingleton<IScreenshotProvider, ScreenshotProvider>();
serviceProducer.RegisterSingleton<IConfigurationManager, ConfigurationManager>();
serviceProducer.RegisterSingleton<IBloogumClient, BloogumClient>();
serviceProducer.RegisterSingleton<IApplicationUpdater, ApplicationUpdater>();
serviceProducer.RegisterSingleton<IMutexHandler, MutexHandler>();
}
public static void RegisterLifetimeServices(IApplicationLifetimeProducer applicationLifetimeProducer)
{
@@ -48,6 +49,10 @@ namespace Daybreak.Configuration
viewProducer.RegisterView<SettingsView>();
viewProducer.RegisterView<AskUpdateView>();
viewProducer.RegisterView<UpdateView>();
viewProducer.RegisterView<SettingsCategoryView>();
viewProducer.RegisterView<AccountsView>();
viewProducer.RegisterView<ExperimentalSettingsView>();
viewProducer.RegisterView<ExecutablesView>();
}
}
}
+49
View File
@@ -0,0 +1,49 @@
<UserControl x:Class="Daybreak.Controls.AccountTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
x:Name="_this"
mc:Ignorable="d"
xmlns:converters="clr-namespace:Daybreak.Converters"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter"></converters:InverseBooleanConverter>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock FontSize="16" Text="Username:" Foreground="White" Margin="5" Grid.Row="0" HorizontalAlignment="Right"></TextBlock>
<TextBlock FontSize="16" Text="Password:" Foreground="White" Margin="5" Grid.Row="1" HorizontalAlignment="Right"></TextBlock>
<TextBlock FontSize="16" Text="Character name:" Foreground="White" Margin="5" Grid.Row="2" HorizontalAlignment="Right"></TextBlock>
<TextBox Text="{Binding ElementName=_this, Path=Username, Mode=TwoWay}" Foreground="White" Background="Transparent"
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="0" Grid.Column="1"
FontSize="16" TextChanged="UsernameTextbox_TextChanged" Margin="5"></TextBox>
<PasswordBox x:Name="PasswordBox" Foreground="White" Background="Transparent"
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="1" Grid.Column="1"
FontSize="16" PasswordChanged="Passwordbox_PasswordChanged" Margin="5"></PasswordBox>
<TextBox Text="{Binding ElementName=_this, Path=CharacterName, Mode=TwoWay}" Foreground="White" Background="Transparent"
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="2" Grid.Column="1"
FontSize="16" TextChanged="CharacterNameTextbox_TextChanged" Margin="5"></TextBox>
</Grid>
<local:BinButton Grid.Column="2" Height="30" Width="30" Foreground="White" VerticalAlignment="Top" Margin="5, 15, 5, 5"
Clicked="BinButton_Clicked"></local:BinButton>
<local:StarGlyph Grid.Column="2" Height="30" Width="30" Foreground="White" VerticalAlignment="Top" Margin="5, 55, 5, 5"
Clicked="StarGlyph_Clicked" IsEnabled="{Binding ElementName=_this, Path=IsDefault, Mode=OneWay, Converter={StaticResource InverseBooleanConverter}}"></local:StarGlyph>
</Grid>
</UserControl>
+90
View File
@@ -0,0 +1,90 @@
using Daybreak.Models;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for AccountTemplate.xaml
/// </summary>
public partial class AccountTemplate : UserControl
{
public static readonly DependencyProperty UsernameProperty = DependencyPropertyExtensions.Register<AccountTemplate, string>(nameof(Username));
public static readonly DependencyProperty CharacterNameProperty = DependencyPropertyExtensions.Register<AccountTemplate, string>(nameof(CharacterName));
public static readonly DependencyProperty PasswordProperty = DependencyPropertyExtensions.Register<AccountTemplate, string>(nameof(Password));
public static readonly DependencyProperty IsDefaultProperty = DependencyPropertyExtensions.Register<AccountTemplate, bool>(nameof(IsDefault));
public event EventHandler RemoveClicked;
public event EventHandler DefaultClicked;
public string Username
{
get => this.GetTypedValue<string>(UsernameProperty);
set => this.SetValue(UsernameProperty, value);
}
public string Password
{
get => this.GetTypedValue<string>(PasswordProperty);
set => this.SetValue(PasswordProperty, value);
}
public string CharacterName
{
get => this.GetTypedValue<string>(CharacterNameProperty);
set => this.SetValue(CharacterNameProperty, value);
}
public bool IsDefault
{
get => this.GetTypedValue<bool>(IsDefaultProperty);
set => this.SetValue(IsDefaultProperty, value);
}
public AccountTemplate()
{
this.InitializeComponent();
this.DataContextChanged += AccountTemplate_DataContextChanged;
}
private void AccountTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is LoginCredentials loginCredentials)
{
this.PasswordBox.Password = loginCredentials.Password;
this.Username = loginCredentials.Username;
this.CharacterName = loginCredentials.CharacterName;
this.IsDefault = loginCredentials.Default;
}
}
private void BinButton_Clicked(object sender, EventArgs e)
{
this.RemoveClicked?.Invoke(this, e);
}
private void UsernameTextbox_TextChanged(object sender, EventArgs e)
{
this.Username = sender.As<TextBox>()?.Text;
this.DataContext.As<LoginCredentials>().Username = this.Username;
}
private void CharacterNameTextbox_TextChanged(object sender, EventArgs e)
{
this.CharacterName = sender.As<TextBox>()?.Text;
this.DataContext.As<LoginCredentials>().CharacterName = this.CharacterName;
}
private void Passwordbox_PasswordChanged(object sender, EventArgs e)
{
this.Password = sender.As<PasswordBox>()?.Password;
this.DataContext.As<LoginCredentials>().Password = this.Password;
}
private void StarGlyph_Clicked(object sender, EventArgs e)
{
this.DefaultClicked?.Invoke(this, e);
}
}
}
+22
View File
@@ -0,0 +1,22 @@
<UserControl x:Class="Daybreak.Controls.AddButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Grid>
<Ellipse x:Name="BackgroundEllipse" Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Visibility="Visible" Opacity="0.1" />
<Path Data="m13,26a13,13 0 1 1 13,-13a13,13 0 0 1 -13,13zm0,-24a11,11 0 1 0 11,11a11,11 0 0 0 -11,-11z"
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Path>
<Path Data="m13,20a1,1 0 0 1 -1,-1l0,-12a1,1 0 0 1 2,0l0,12a1,1 0 0 1 -1,1z"
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Path>
<Path Data="m19,14l-12,0a1,1 0 0 1 0,-2l12,0a1,1 0 0 1 0,2z"
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Path>
<Ellipse Fill="Transparent" MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"></Ellipse>
</Grid>
</Viewbox>
</UserControl>
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Windows.Controls;
using System.Windows.Input;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for AddButton.xaml
/// </summary>
public partial class AddButton : UserControl
{
public event EventHandler Clicked;
public AddButton()
{
InitializeComponent();
}
private void Ellipse_MouseEnter(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Opacity = 0.6;
}
private void Ellipse_MouseLeave(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Opacity = 0;
}
private void Ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
Clicked?.Invoke(this, e);
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
<UserControl x:Class="Daybreak.Controls.AvatarGlyph"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Grid>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m5.7,104.4c10.6,-10.6 24.6,-16.4 39.6,-16.4s29,5.8 39.6,16.4l5.7,-5.7c-12.1,-12 -28.2,-18.7 -45.3,-18.7s-33.2,6.7 -45.3,18.7l5.7,5.7z"></Path>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m11.3,34c0,18.7 15.3,34 34,34s34,-15.3 34,-34s-15.3,-34 -34,-34s-34,15.3 -34,34zm60,0c0,14.3 -11.7,26 -26,26s-26,-11.7 -26,-26s11.7,-26 26,-26s26,11.7 26,26z"></Path>
</Grid>
</Viewbox>
</UserControl>
+28
View File
@@ -0,0 +1,28 @@
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;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for AvatarGlyph.xaml
/// </summary>
public partial class AvatarGlyph : UserControl
{
public AvatarGlyph()
{
InitializeComponent();
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<UserControl x:Class="Daybreak.Controls.BinButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Grid>
<Ellipse x:Name="BackgroundEllipse" Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Width="80" Height="80" Opacity="0.6"
Visibility="Hidden"></Ellipse>
<Path Data="m40,7l-2,0l0,-5l-18,0l0,5l-2,0l0,-6a1,1 0 0 1 1,-1l20,0a1,1 0 0 1 1,1l0,6z"
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Margin="11"></Path>
<Path Data="m58,14l-2,0l0,-3l-54,0l0,3l-2,0l0,-4a1,1 0 0 1 1,-1l56,0a1,1 0 0 1 1,1l0,4z"
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Margin="11"></Path>
<Path Data="m51,64l-44,0a1,1 0 0 1 -1,-1l0,-48l2,0l0,47l42,0l0,-47l2,0l0,48a1,1 0 0 1 -1,1z"
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Margin="11"></Path>
<Rectangle Margin="38, 32, 0, 0" Width="2" Height="32" Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
VerticalAlignment="Top" HorizontalAlignment="Left"></Rectangle>
<Rectangle Margin="26, 35, 0, 0" Width="2" Height="32" Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
VerticalAlignment="Top" HorizontalAlignment="Left"></Rectangle>
<Rectangle Margin="50, 35, 0, 0" Width="2" Height="32" Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
VerticalAlignment="Top" HorizontalAlignment="Left"></Rectangle>
<Ellipse Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" StrokeThickness="3" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Fill="Transparent"
MouseLeftButtonDown="Ellipse_MouseLeftButtonDown" MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave"></Ellipse>
</Grid>
</Viewbox>
</UserControl>
+36
View File
@@ -0,0 +1,36 @@
using System;
using System.Windows.Controls;
using System.Windows.Input;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for BinButton.xaml
/// </summary>
public partial class BinButton : UserControl
{
public event EventHandler Clicked;
public BinButton()
{
InitializeComponent();
}
private void Ellipse_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
this.Clicked?.Invoke(this, e);
}
}
private void Ellipse_MouseEnter(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Visibility = System.Windows.Visibility.Visible;
}
private void Ellipse_MouseLeave(object sender, MouseEventArgs e)
{
this.BackgroundEllipse.Visibility = System.Windows.Visibility.Hidden;
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<UserControl x:Class="Daybreak.Controls.ExperimentGlyph"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m23.8,22.5l-7.8,-12.6l0,-7.9l1,0c0.6,0 1,-0.4 1,-1s-0.4,-1 -1,-1l-3,0l0,9.9l3.5,6.1l-11,0l3.5,-6.1l0,-9.9l-3,0c-0.6,0 -1,0.4 -1,1s0.4,1 1,1l1,0l0,7.9l-7.8,12.7c-0.5,0.7 0,1.4 0.8,1.4l22,0c0.8,0 1.3,-0.5 0.8,-1.5zm-20.8,-0.5l2.9,-5l12.2,0l2.9,5l-18,0z"></Path>
</Viewbox>
</UserControl>
+28
View File
@@ -0,0 +1,28 @@
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;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for ExperimentGlyph.xaml
/// </summary>
public partial class ExperimentGlyph : UserControl
{
public ExperimentGlyph()
{
InitializeComponent();
}
}
}
+14
View File
@@ -0,0 +1,14 @@
<UserControl x:Class="Daybreak.Controls.FileGlyph"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m52,0l-40,0c-6.627,0 -12,5.373 -12,12l0,72c0,6.627 5.373,12 12,12l55.875,0c6.627,0 12.125,-5.373 12.125,-12l0,-56c-4,-4 -22,-22 -28,-28zm0,11.178l16.709,16.822l-16.709,0l0,-16.822zm15.875,76.822l-55.875,0c-2.206,0 -4,-1.794 -4,-4l0,-72c0,-2.206 1.794,-4 4,-4l32,0l0,20l0,8l8,0l20,0l0,48c0,2.168 -1.889,4 -4.125,4z"></Path>
</Viewbox>
</UserControl>
+15
View File
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for FileGlyph.xaml
/// </summary>
public partial class FileGlyph : UserControl
{
public FileGlyph()
{
InitializeComponent();
}
}
}
+11 -8
View File
@@ -8,14 +8,17 @@
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Rectangle x:Name="BackgroundEllipse" Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Visibility="Visible" Opacity="0.1" />
<Viewbox>
<StackPanel Orientation="Horizontal">
<Ellipse Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Height="1" Width="1"></Ellipse>
<Ellipse Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Height="1" Width="1"></Ellipse>
<Ellipse Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Height="1" Width="1"></Ellipse>
</StackPanel>
<Ellipse x:Name="BackgroundEllipse" Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Visibility="Visible" Opacity="0.1" />
<Viewbox Stretch="Fill">
<Grid>
<Ellipse Height="4" Width="4" StrokeThickness="0.2" Stroke="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Ellipse>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Ellipse Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Height="1" Width="1"></Ellipse>
<Ellipse Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Height="1" Width="1"></Ellipse>
<Ellipse Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}" Height="1" Width="1"></Ellipse>
</StackPanel>
</Grid>
</Viewbox>
<Rectangle Fill="Transparent" MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"></Rectangle>
<Ellipse Fill="Transparent" MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"></Ellipse>
</Grid>
</UserControl>
+19
View File
@@ -0,0 +1,19 @@
<UserControl x:Class="Daybreak.Controls.GoldenArrowGlyph"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Viewbox>
<Grid>
<Grid.RenderTransform>
<RotateTransform Angle="180" CenterX="900" CenterY="595"></RotateTransform>
</Grid.RenderTransform>
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
Data="m771.53119,1071.05017c-81,-64 -151,-120 -154,-126c-4,-5 -15,-7 -25,-3c-11,4 -77,12 -149,16c-146,10 -203,26 -326,92c-63,35 -82,41 -96,32c-22,-13 -28,-55 -14,-95c7,-20 7,-39 -1,-60c-8,-25 -7,-38 5,-65c9,-19 21,-34 27,-34c5,0 10,-15 10,-34c0,-49 32,-75 83,-67c4,0 7,-10 7,-24c0,-31 41,-75 70,-75c11,0 20,-6 20,-14c0,-41 73,-60 136,-36c23,9 30,7 50,-15c13,-14 27,-25 32,-25c5,0 14,-11 20,-25c6,-14 17,-25 23,-25c7,0 22,-10 34,-22c12,-13 38,-36 58,-53c34,-28 37,-34 38,-90l1,-60l149,-133c82,-74 154,-132 160,-130c6,2 73,62 149,133l138,130l1,60c1,56 4,62 38,90c20,17 46,40 58,53c12,12 27,22 34,22c6,0 17,11 23,25c6,14 15,25 20,25c5,0 19,11 32,25c20,22 27,24 50,15c63,-24 136,-5 136,36c0,8 8,14 19,14c32,0 71,39 71,71c0,16 3,28 8,28c50,-8 82,18 82,67c0,19 4,34 9,34c5,0 16,16 26,36c13,27 14,42 6,65c-7,20 -7,38 0,58c14,40 8,82 -14,95c-14,9 -33,3 -96,-32c-123,-66 -180,-82 -326,-92c-71,-4 -138,-11 -148,-15c-11,-5 -22,-2 -30,7c-7,9 -76,65 -154,126l-141,111l-149,-116zm277,-45c99,-78 119,-97 119,-120c1,-32 1,-32 78,-14c32,8 114,19 183,25c134,11 208,31 297,81c61,35 63,35 63,15c0,-8 -12,-22 -26,-31l-25,-17l25,-24c14,-13 26,-30 26,-38c0,-19 -27,-44 -56,-51c-24,-6 -24,-7 -9,-31c27,-40 13,-55 -44,-47l-50,6l11,-35c23,-76 -11,-87 -77,-26c-25,23 -48,39 -51,36c-3,-3 6,-27 20,-53c14,-27 26,-57 26,-68c0,-16 -6,-18 -41,-13c-22,3 -58,17 -80,31c-21,14 -41,26 -44,26c-3,0 -5,-20 -5,-45c0,-42 -2,-45 -19,-35c-32,16 -41,12 -41,-20c0,-36 -13,-38 -34,-8c-15,22 -16,21 -16,-27c0,-47 -1,-48 -17,-32c-17,17 -18,16 -28,-24c-6,-24 -17,-45 -24,-47c-11,-4 -12,5 -5,44c15,83 11,140 -11,164c-13,14 -18,33 -16,55c8,101 -156,220 -241,174c-14,-8 -23,-8 -31,0c-15,15 -61,14 -101,-3c-74,-31 -155,-132 -142,-179c3,-13 -2,-29 -13,-42c-26,-28 -32,-84 -17,-150c13,-52 8,-74 -13,-61c-5,4 -12,24 -16,47c-7,39 -8,40 -26,23c-18,-17 -19,-16 -19,32c0,47 -1,48 -16,26c-21,-30 -34,-28 -34,8c0,32 -9,36 -41,20c-17,-10 -19,-7 -19,35c0,25 -2,45 -5,45c-3,0 -23,-12 -44,-26c-22,-14 -58,-28 -80,-31c-35,-5 -41,-3 -41,13c0,11 12,41 26,68c14,26 23,50 20,53c-3,3 -26,-13 -51,-36c-66,-61 -100,-50 -77,26l11,35l-43,-6c-23,-4 -50,-4 -60,0c-15,6 -15,9 3,39l19,33l-26,7c-59,15 -69,55 -25,91c27,21 27,23 8,30c-24,9 -35,23 -35,46c0,14 10,11 57,-17c92,-53 173,-76 313,-87c69,-5 150,-15 181,-23c72,-19 69,-20 69,13c1,23 20,42 118,119c64,50 123,92 130,92c7,1 66,-41 132,-91zm-149,-229c7,-21 15,-39 19,-39c4,0 14,18 23,40c13,34 20,40 45,40c37,0 108,-52 135,-98l19,-32l-31,-31c-17,-17 -31,-32 -31,-34c0,-1 17,-6 37,-10c45,-8 53,-18 53,-66c0,-68 -45,-159 -78,-159c-9,1 -32,14 -51,30c-19,16 -36,28 -37,27c-2,-2 4,-27 13,-56c13,-47 13,-55 0,-68c-22,-23 -91,-34 -139,-24c-59,14 -69,31 -52,86c15,51 7,56 -33,25c-14,-11 -31,-20 -38,-20c-20,0 -52,43 -69,93c-29,88 -18,121 45,131l42,7l-34,35l-33,34l19,36c20,37 84,88 120,96c32,7 44,-2 56,-43zm-154,-439c11,0 23,-11 29,-28c32,-85 252,-85 292,1c7,15 21,27 31,27c10,0 30,9 45,21l26,20l0,-55l0,-56l-106,-102c-58,-57 -114,-108 -124,-115c-14,-10 -34,3 -143,100l-127,112l0,67l0,66l29,-29c16,-16 38,-29 48,-29z" />
</Grid>
</Viewbox>
</UserControl>
@@ -0,0 +1,28 @@
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;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for GoldenArrowGlyph.xaml
/// </summary>
public partial class GoldenArrowGlyph : UserControl
{
public GoldenArrowGlyph()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,43 @@
<UserControl x:Class="Daybreak.Controls.GuildwarsPathTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:converters="clr-namespace:Daybreak.Converters"
xmlns:local="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<converters:InverseBooleanConverter x:Key="InverseBooleanConverter"></converters:InverseBooleanConverter>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock FontSize="22" Text="Path:" Foreground="White" Margin="5" Grid.Row="0" HorizontalAlignment="Right"></TextBlock>
<TextBox Foreground="White" Background="Transparent" Text="{Binding ElementName=_this, Path=Path, Mode=TwoWay}"
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="0" Grid.Column="1"
FontSize="22" TextChanged="TextBox_TextChanged" Margin="5"></TextBox>
</Grid>
<WrapPanel Grid.Column="1">
<local:FilePickerGlyph Width="30" Height="30" Foreground="White" VerticalAlignment="Top" Margin="5"
Clicked="FilePickerGlyph_Clicked"></local:FilePickerGlyph>
<local:BinButton Grid.Column="2" Height="30" Width="30" Foreground="White" VerticalAlignment="Top" Margin="5"
Clicked="BinButton_Clicked"></local:BinButton>
<local:StarGlyph Grid.Column="2" Height="30" Width="30" Foreground="White" VerticalAlignment="Top" Margin="5"
Clicked="StarGlyph_Clicked" IsEnabled="{Binding ElementName=_this, Path=IsDefault, Mode=OneWay, Converter={StaticResource InverseBooleanConverter}}"></local:StarGlyph>
</WrapPanel>
</Grid>
</UserControl>
@@ -0,0 +1,80 @@
using Daybreak.Models;
using Microsoft.Win32;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for GuildwarsPathTemplate.xaml
/// </summary>
public partial class GuildwarsPathTemplate : UserControl
{
public static readonly DependencyProperty PathProperty = DependencyPropertyExtensions.Register<GuildwarsPathTemplate, string>(nameof(Path));
public static readonly DependencyProperty IsDefaultProperty = DependencyPropertyExtensions.Register<GuildwarsPathTemplate, bool>(nameof(IsDefault));
public event EventHandler RemoveClicked;
public event EventHandler DefaultClicked;
public string Path
{
get => this.GetTypedValue<string>(PathProperty);
set => this.SetValue(PathProperty, value);
}
public bool IsDefault
{
get => this.GetTypedValue<bool>(IsDefaultProperty);
set => this.SetValue(IsDefaultProperty, value);
}
public GuildwarsPathTemplate()
{
this.InitializeComponent();
this.DataContextChanged += GuildwarsPathTemplate_DataContextChanged;
}
private void GuildwarsPathTemplate_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is GuildwarsPath guildwarsPath)
{
this.IsDefault = guildwarsPath.Default;
this.Path = guildwarsPath.Path;
}
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
this.Path = sender.As<TextBox>()?.Text;
this.DataContext.As<GuildwarsPath>().Path = this.Path;
}
private void StarGlyph_Clicked(object sender, EventArgs e)
{
this.DefaultClicked?.Invoke(this, e);
}
private void BinButton_Clicked(object sender, EventArgs e)
{
this.RemoveClicked?.Invoke(this, e);
}
private void FilePickerGlyph_Clicked(object sender, EventArgs e)
{
var filePicker = new OpenFileDialog()
{
CheckFileExists = true,
CheckPathExists = true,
DefaultExt = "exe",
Multiselect = false
};
if (filePicker.ShowDialog() is true)
{
this.Path = filePicker.FileName;
this.DataContext.As<GuildwarsPath>().Path = filePicker.FileName;
}
}
}
}
+1 -1
View File
@@ -21,7 +21,7 @@
VerticalAlignment="Top" HorizontalAlignment="Left" StrokeThickness="3" Width="47.25" Height="3" />
<Rectangle Stroke="{Binding ElementName=_this, Path=Foreground}" Margin="39.375, 85.625, 0, 0"
VerticalAlignment="Top" HorizontalAlignment="Left" StrokeThickness="3" Width="47.25" Height="3" />
<Ellipse Stroke="{Binding ElementName=_this, Path=Foreground}" Width="128" Height="128" StrokeThickness="5" Fill="Transparent"
<Ellipse Stroke="{Binding ElementName=_this, Path=Foreground}" Width="128" Height="128" StrokeThickness="8" Fill="Transparent"
MouseEnter="Ellipse_MouseEnter" MouseLeave="Ellipse_MouseLeave" MouseLeftButtonDown="Ellipse_MouseLeftButtonDown"></Ellipse>
</Grid>
</Viewbox>
+45
View File
@@ -0,0 +1,45 @@
<UserControl x:Class="Daybreak.Controls.TileButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Controls"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:Daybreak.Behaviors"
xmlns:converters="clr-namespace:Daybreak.Converters"
x:Name="_this"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<converters:TileButtonHighlightConverter x:Key="HighlightConverter"></converters:TileButtonHighlightConverter>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<ContentPresenter x:Name="Content" Margin="5"
Content="{Binding ElementName=_this, Path=InnerContent}"></ContentPresenter>
<Border BorderBrush="{Binding ElementName=_this, Path=BorderBrush}"
BorderThickness="{Binding ElementName=_this, Path=BorderThickness}"
Opacity="{Binding ElementName=_this, Path=Highlighted, Converter={StaticResource HighlightConverter}}"
Grid.RowSpan="2">
</Border>
<TextBlock Grid.Row="1" Text="{Binding ElementName=_this, Path=Title}"
FontSize="{Binding ElementName=_this, Path=FontSize}"
FontFamily="{Binding ElementName=_this, Path=FontFamily}"
Foreground="{Binding ElementName=_this, Path=Foreground}"
VerticalAlignment="Center" HorizontalAlignment="Stretch" TextWrapping="Wrap"
TextAlignment="Center">
<i:Interaction.Behaviors>
<behaviors:ScaleFontWithSize MaxFontSize="22"></behaviors:ScaleFontWithSize>
</i:Interaction.Behaviors>
</TextBlock>
<Rectangle Fill="Transparent"
MouseEnter="Grid_MouseEnter"
MouseLeave="Grid_MouseLeave"
MouseLeftButtonDown="Rectangle_MouseLeftButtonDown"
Grid.RowSpan="2">
</Rectangle>
</Grid>
</UserControl>
+68
View File
@@ -0,0 +1,68 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Daybreak.Controls
{
/// <summary>
/// Interaction logic for TileButton.xaml
/// </summary>
public partial class TileButton : UserControl
{
public event EventHandler Clicked;
public static readonly DependencyProperty HighlightedProperty =
DependencyProperty.Register("Highlighted", typeof(bool), typeof(TileButton), null);
public static readonly DependencyProperty HighlightColorProperty =
DependencyProperty.Register("HighlightColor", typeof(Brush), typeof(TileButton), null);
public static readonly DependencyProperty InnerContentProperty =
DependencyProperty.Register("InnerContent", typeof(FrameworkElement), typeof(TileButton), null);
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(TileButton), null);
public TileButton()
{
this.InitializeComponent();
}
public bool Highlighted
{
get => (bool)this.GetValue(HighlightedProperty);
set => this.SetValue(HighlightedProperty, value);
}
public string Title
{
get => this.GetValue(TitleProperty) as string;
set => this.SetValue(TitleProperty, value);
}
public FrameworkElement InnerContent
{
get => this.GetValue(InnerContentProperty) as FrameworkElement;
set => this.SetValue(InnerContentProperty, value);
}
public Brush HighlightColor
{
get => this.GetValue(HighlightColorProperty) as Brush;
set => this.SetValue(HighlightColorProperty, value);
}
private void Grid_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
this.Highlighted = true;
}
private void Grid_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
this.Highlighted = false;
}
private void Rectangle_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
Clicked?.Invoke(this, e);
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Windows.Data;
namespace Daybreak.Converters
{
public class InverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (targetType != typeof(bool))
{
throw new InvalidOperationException("The target must be a boolean");
}
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace Daybreak.Converters
{
public class TileButtonHighlightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof(double) &&
value is bool boolean)
{
return boolean ? 1 : 0.4;
}
else
{
throw new NotImplementedException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType == typeof(bool) &&
value is double doubleValue)
{
return doubleValue == 1;
}
else
{
throw new NotImplementedException();
}
}
}
}
-6
View File
@@ -1,6 +0,0 @@
{
"GamePath": "",
"CharacterName": "",
"LeftBrowserDefault": "https://gwpvx.fandom.com/wiki/Special:RecentChanges?hidebots=1&hidecategorization=1&limit=50&days=7&enhanced=1&urlversion=2",
"RightBrowserDefault": "https://wiki.guildwars.com/wiki/Quick_access_links"
}
+3 -7
View File
@@ -9,13 +9,15 @@
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<Version>0.3.0</Version>
<Version>0.6.0</Version>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.774.44" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="Slim" Version="1.2.1" />
<PackageReference Include="System.Windows.Interactivity.WPF" Version="2.0.20525" />
<PackageReference Include="SystemExtensions.NetStandard" Version="1.1.3" />
<PackageReference Include="WCL" Version="1.0.2" />
<PackageReference Include="WpfExtended" Version="0.1.1" />
@@ -30,12 +32,6 @@
</Compile>
</ItemGroup>
<ItemGroup>
<None Update="Daybreak.config.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\MaximizeButton.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
@@ -0,0 +1,24 @@
using System;
using System.Runtime.Serialization;
namespace Daybreak.Exceptions
{
public sealed class CredentialsNotFoundException : Exception
{
public CredentialsNotFoundException()
{
}
public CredentialsNotFoundException(string message) : base(message)
{
}
public CredentialsNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
public CredentialsNotFoundException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
@@ -0,0 +1,24 @@
using System;
using System.Runtime.Serialization;
namespace Daybreak.Exceptions
{
public sealed class ExecutableNotFoundException : Exception
{
public ExecutableNotFoundException()
{
}
public ExecutableNotFoundException(string message) : base(message)
{
}
public ExecutableNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
public ExecutableNotFoundException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
+1 -1
View File
@@ -120,7 +120,7 @@ namespace Daybreak.Launch
private void SettingsButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<SettingsView>();
this.viewManager.ShowView<SettingsCategoryView>();
}
private void CreditTextBox_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+12
View File
@@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace Daybreak.Models
{
public sealed class GuildwarsPath
{
[JsonProperty("path")]
public string Path { get; set; }
[JsonProperty("default")]
public bool Default { get; set; }
}
}
+3 -1
View File
@@ -3,6 +3,8 @@
public sealed class LoginCredentials
{
public string Username { get; set; }
public SecureString Password { get; set; }
public string Password { get; set; }
public string CharacterName { get; set; }
public bool Default { get; set; }
}
}
@@ -0,0 +1,16 @@
using Newtonsoft.Json;
namespace Daybreak.Models
{
public sealed class ProtectedLoginCredentials
{
[JsonProperty("ProtectedUsername")]
public string ProtectedUsername { get; set; }
[JsonProperty("ProtectedPassword")]
public string ProtectedPassword { get; set; }
[JsonProperty("CharacterName")]
public string CharacterName { get; set; }
[JsonProperty("Default")]
public bool Default { get; set; }
}
}
@@ -1,85 +0,0 @@
using Daybreak.Services.Configuration;
using Daybreak.Services.Credentials;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Extensions;
using System.IO;
using System.Linq;
namespace Daybreak.Services.ApplicationDetection
{
public class ApplicationDetector : IApplicationDetector
{
private const string ToolboxProcessName = "GWToolbox";
private const string ProcessName = "gw";
private readonly IConfigurationManager configurationManager;
private readonly ICredentialManager credentialManager;
public bool IsGuildwarsRunning => GuildwarsProcessDetected();
public bool IsToolboxRunning => GuildwarsToolboxProcessDetected();
public ApplicationDetector(
IConfigurationManager configurationManager,
ICredentialManager credentialManager)
{
this.credentialManager = credentialManager.ThrowIfNull(nameof(credentialManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
}
public void LaunchGuildwars()
{
var configuration = this.configurationManager.GetConfiguration();
var executable = configuration.GamePath;
if (File.Exists(executable) is false)
{
throw new InvalidOperationException($"Guildwars executable doesn't exist at {executable}");
}
if (string.IsNullOrEmpty(configuration.CharacterName))
{
throw new InvalidOperationException($"No character name set");
}
var auth = this.credentialManager.GetCredentials();
auth.Do(
onSome: (credentials) =>
{
if (Process.Start(executable, new List<string> { "-email", credentials.Username, "-password", credentials.Password, "-character", configuration.CharacterName }) is null)
{
throw new InvalidOperationException($"Unable to launch {executable}");
}
},
onNone: () =>
{
throw new InvalidOperationException($"No credentials available");
});
}
public void LaunchGuildwarsToolbox()
{
var configuration = this.configurationManager.GetConfiguration();
var executable = configuration.ToolboxPath;
if (File.Exists(executable) is false)
{
throw new InvalidOperationException($"Guildwars executable doesn't exist at {executable}");
}
if (Process.Start(executable) is null)
{
throw new InvalidOperationException($"Unable to launch {executable}");
}
}
private static bool GuildwarsProcessDetected()
{
return Process.GetProcessesByName(ProcessName).FirstOrDefault() is not null;
}
private static bool GuildwarsToolboxProcessDetected()
{
return Process.GetProcessesByName(ToolboxProcessName).FirstOrDefault() is not null;
}
}
}
@@ -1,10 +0,0 @@
namespace Daybreak.Services.ApplicationDetection
{
public interface IApplicationDetector
{
bool IsGuildwarsRunning { get; }
bool IsToolboxRunning { get; }
void LaunchGuildwars();
void LaunchGuildwarsToolbox();
}
}
@@ -0,0 +1,204 @@
using Daybreak.Exceptions;
using Daybreak.Services.Configuration;
using Daybreak.Services.Credentials;
using Daybreak.Services.Logging;
using Daybreak.Services.Mutex;
using Daybreak.Utils;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Extensions;
using System.IO;
using System.Linq;
using System.Security;
using System.Threading.Tasks;
namespace Daybreak.Services.ApplicationLauncher
{
public class ApplicationLauncher : IApplicationLauncher
{
private const string ToolboxProcessName = "GWToolbox";
private const string ProcessName = "gw";
private const string ArenaNetMutex = "AN-Mute";
private readonly IConfigurationManager configurationManager;
private readonly ICredentialManager credentialManager;
private readonly IMutexHandler mutexHandler;
private readonly ILogger logger;
public bool IsGuildwarsRunning => this.GuildwarsProcessDetected();
public bool IsToolboxRunning => GuildwarsToolboxProcessDetected();
public ApplicationLauncher(
IConfigurationManager configurationManager,
ICredentialManager credentialManager,
IMutexHandler mutexHandler,
ILogger logger)
{
this.logger = logger.ThrowIfNull(nameof(logger));
this.mutexHandler = mutexHandler.ThrowIfNull(nameof(mutexHandler));
this.credentialManager = credentialManager.ThrowIfNull(nameof(credentialManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
}
public async Task LaunchGuildwars()
{
var configuration = this.configurationManager.GetConfiguration();
var auth = await this.credentialManager.GetDefaultCredentials().ConfigureAwait(false);
auth.Do(
onSome: (credentials) =>
{
if (configuration.ExperimentalFeatures.MultiLaunchSupport is true)
{
ClearGwLocks();
}
LaunchGuildwarsProcess(credentials.Username, credentials.Password, credentials.CharacterName);
},
onNone: () =>
{
throw new CredentialsNotFoundException($"No credentials available");
});
}
public Task LaunchGuildwarsToolbox()
{
return Task.Run(() =>
{
var configuration = this.configurationManager.GetConfiguration();
var executable = configuration.ToolboxPath;
if (File.Exists(executable) is false)
{
throw new ExecutableNotFoundException($"Guildwars executable doesn't exist at {executable}");
}
if (Process.Start(executable) is null)
{
throw new InvalidOperationException($"Unable to launch {executable}");
}
});
}
private void LaunchGuildwarsProcess(string email, Models.SecureString password, string character)
{
var executable = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (executable is null)
{
throw new ExecutableNotFoundException($"No executable selected");
}
if (File.Exists(executable.Path) is false)
{
throw new ExecutableNotFoundException($"Guildwars executable doesn't exist at {executable}");
}
var args = new List<string>()
{
"-email",
email,
"-password",
password
};
if (!string.IsNullOrWhiteSpace(character))
{
args.Add("-character");
args.Add(character);
}
if (Process.Start(executable.Path, args) is null)
{
throw new InvalidOperationException($"Unable to launch {executable}");
}
}
private bool GuildwarsProcessDetected()
{
if (this.configurationManager.GetConfiguration().ExperimentalFeatures.MultiLaunchSupport is true)
{
try
{
var path = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (path is null)
{
return false;
}
using var stream = File.OpenWrite(path.Path);
return false;
}
catch
{
return true;
}
}
return Process.GetProcessesByName(ProcessName).FirstOrDefault() is not null;
}
private void ClearGwLocks()
{
this.SetRegistryGuildwarsPath();
foreach (var process in Process.GetProcessesByName(ProcessName))
{
this.mutexHandler.CloseMutex(process, ArenaNetMutex);
}
}
private void SetRegistryGuildwarsPath()
{
var path = this.configurationManager.GetConfiguration().GuildwarsPaths.Where(path => path.Default).FirstOrDefault();
if (path is null)
{
throw new ExecutableNotFoundException("No executable currently selected");
}
var gamePath = path.Path;
try
{
var registryKey = GetGuildwarsRegistryKey(true);
registryKey.SetValue("Path", gamePath);
registryKey.SetValue("Src", gamePath);
registryKey.Close();
}
catch (SecurityException ex)
{
this.logger.LogCritical($"Multi-launch requires administrator rights. Details: {ex}");
}
}
private static RegistryKey GetGuildwarsRegistryKey(bool write)
{
var gwKey = Registry.CurrentUser.OpenSubKey("SOFTWARE")?.OpenSubKey("ArenaNet")?.OpenSubKey("Guild Wars", write);
if (gwKey is not null)
{
return gwKey;
}
gwKey = Registry.CurrentUser.OpenSubKey("SOFTWARE")?.OpenSubKey("WOW6432Node")?.OpenSubKey("ArenaNet")?.OpenSubKey("Guild Wars", write);
if (gwKey is not null)
{
return gwKey;
}
gwKey = Registry.LocalMachine.OpenSubKey("SOFTWARE")?.OpenSubKey("ArenaNet")?.OpenSubKey("Guild Wars", write);
if (gwKey is not null)
{
return gwKey;
}
gwKey = Registry.LocalMachine.OpenSubKey("SOFTWARE")?.OpenSubKey("WOW6432Node")?.OpenSubKey("ArenaNet")?.OpenSubKey("Guild Wars", write);
if (gwKey is not null)
{
return gwKey;
}
throw new InvalidOperationException("Could not find registry key for guildwars.");
}
private static bool GuildwarsToolboxProcessDetected()
{
return Process.GetProcessesByName(ToolboxProcessName).FirstOrDefault() is not null;
}
}
}
@@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace Daybreak.Services.ApplicationLauncher
{
public interface IApplicationLauncher
{
bool IsGuildwarsRunning { get; }
bool IsToolboxRunning { get; }
Task LaunchGuildwars();
Task LaunchGuildwarsToolbox();
}
}
@@ -3,9 +3,12 @@ using Daybreak.Services.Configuration;
using Daybreak.Services.Logging;
using Daybreak.Utils;
using System;
using System.Collections.Generic;
using System.Extensions;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace Daybreak.Services.Credentials
{
@@ -23,43 +26,123 @@ namespace Daybreak.Services.Credentials
this.logger = logger.ThrowIfNull(nameof(logger));
}
public Optional<LoginCredentials> GetCredentials()
public Task<Optional<LoginCredentials>> GetDefaultCredentials()
{
this.logger.LogInformation("Retrieving credentials");
var config = this.configurationManager.GetConfiguration();
if (string.IsNullOrEmpty(config.ProtectedUsername) ||
string.IsNullOrEmpty(config.ProtectedPassword))
return Task.Run(async () =>
{
this.logger.LogInformation("No credentials found");
return Optional.None<LoginCredentials>();
}
this.logger.LogInformation("Retrieving default credentials");
var defaultCredentials = (await this.GetCredentialList())
.Where(creds => creds.Default)
.ToList();
if (defaultCredentials.Count == 0)
{
this.logger.LogWarning("No default credentials");
return Optional.None<LoginCredentials>();
}
if (defaultCredentials.Count > 1)
{
this.logger.LogError("Multiple credentials set as default");
return Optional.None<LoginCredentials>();
}
return defaultCredentials.FirstOrDefault();
});
}
public Task<List<LoginCredentials>> GetCredentialList()
{
return Task.Run(() =>
{
this.logger.LogInformation("Retrieving credentials");
var config = this.configurationManager.GetConfiguration();
if (config.ProtectedLoginCredentials is null || config.ProtectedLoginCredentials.Count == 0)
{
this.logger.LogInformation("No credentials found");
return new List<LoginCredentials>();
}
return config
.ProtectedLoginCredentials
.Select(UnprotectCredentials)
.Where(CredentialsUnprotected)
.Select(ExtractCredentials)
.ToList();
});
}
public Task StoreCredentials(List<LoginCredentials> loginCredentials)
{
return Task.Run(() =>
{
this.logger.LogInformation("Storing credentials");
var config = this.configurationManager.GetConfiguration();
config.ProtectedLoginCredentials = loginCredentials
.Select(ProtectCredentials)
.Where(CredentialsProtected)
.Select(ExtractProtectedCredentials)
.ToList();
this.configurationManager.SaveConfiguration(config);
});
}
private Optional<LoginCredentials> UnprotectCredentials(ProtectedLoginCredentials protectedLoginCredentials)
{
try
{
var usrbytes = Convert.FromBase64String(config.ProtectedUsername);
var psdBytes = Convert.FromBase64String(config.ProtectedPassword);
var usrbytes = Convert.FromBase64String(protectedLoginCredentials.ProtectedUsername);
var psdBytes = Convert.FromBase64String(protectedLoginCredentials.ProtectedPassword);
return new LoginCredentials
{
Username = Encoding.UTF8.GetString(ProtectedData.Unprotect(usrbytes, Entropy, DataProtectionScope.LocalMachine)),
Password = Encoding.UTF8.GetString(ProtectedData.Unprotect(psdBytes, Entropy, DataProtectionScope.LocalMachine))
Password = Encoding.UTF8.GetString(ProtectedData.Unprotect(psdBytes, Entropy, DataProtectionScope.LocalMachine)),
CharacterName = protectedLoginCredentials.CharacterName,
Default = protectedLoginCredentials.Default
};
}
catch(Exception e)
catch (Exception e)
{
this.logger.LogError($"Unable to retrieve credentials. Details: {e}");
return Optional.None<LoginCredentials>();
}
}
public void StoreCredentials(LoginCredentials loginCredentials)
private Optional<ProtectedLoginCredentials> ProtectCredentials(LoginCredentials loginCredentials)
{
this.logger.LogInformation("Storing credentials");
var usrBytes = Encoding.UTF8.GetBytes(loginCredentials.Username);
var psdBytes = Encoding.UTF8.GetBytes(loginCredentials.Password);
var config = this.configurationManager.GetConfiguration();
config.ProtectedUsername = Convert.ToBase64String(ProtectedData.Protect(usrBytes, Entropy, DataProtectionScope.LocalMachine));
config.ProtectedPassword = Convert.ToBase64String(ProtectedData.Protect(psdBytes, Entropy, DataProtectionScope.LocalMachine));
this.configurationManager.SaveConfiguration(config);
try
{
var usrBytes = Encoding.UTF8.GetBytes(loginCredentials.Username);
var psdBytes = Encoding.UTF8.GetBytes(loginCredentials.Password);
return new ProtectedLoginCredentials
{
ProtectedUsername = Convert.ToBase64String(ProtectedData.Protect(usrBytes, Entropy, DataProtectionScope.LocalMachine)),
ProtectedPassword = Convert.ToBase64String(ProtectedData.Protect(psdBytes, Entropy, DataProtectionScope.LocalMachine)),
CharacterName = loginCredentials.CharacterName,
Default = loginCredentials.Default
};
}
catch
{
return Optional.None<ProtectedLoginCredentials>();
}
}
private bool CredentialsUnprotected(Optional<LoginCredentials> optional)
{
return optional
.Switch(onSome: _ => true, onNone: () => false)
.ExtractValue();
}
private bool CredentialsProtected(Optional<ProtectedLoginCredentials> optional)
{
return optional
.Switch(onSome: _ => true, onNone: () => false)
.ExtractValue();
}
private ProtectedLoginCredentials ExtractProtectedCredentials(Optional<ProtectedLoginCredentials> optional)
{
return optional.ExtractValue();
}
private LoginCredentials ExtractCredentials(Optional<LoginCredentials> optional)
{
return optional.ExtractValue();
}
}
}
@@ -1,11 +1,14 @@
using Daybreak.Models;
using System.Collections.Generic;
using System.Extensions;
using System.Threading.Tasks;
namespace Daybreak.Services.Credentials
{
public interface ICredentialManager
{
void StoreCredentials(LoginCredentials loginCredentials);
Optional<LoginCredentials> GetCredentials();
Task StoreCredentials(List<LoginCredentials> loginCredentials);
Task<List<LoginCredentials>> GetCredentialList();
Task<Optional<LoginCredentials>> GetDefaultCredentials();
}
}
+9
View File
@@ -0,0 +1,9 @@
using System.Diagnostics;
namespace Daybreak.Services.Mutex
{
public interface IMutexHandler
{
void CloseMutex(Process process, string mutexName);
}
}
+139
View File
@@ -0,0 +1,139 @@
using Pepa.Wpf.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Daybreak.Services.Mutex
{
public sealed class MutexHandler : IMutexHandler
{
public void CloseMutex(Process process, string mutexName)
{
CloseHandle(process, mutexName);
}
private static List<NativeMethods.SystemHandleInformation> GetHandles(Process targetProcess, IntPtr systemHandle)
{
var processHandles = new List<NativeMethods.SystemHandleInformation>();
var basePointer = systemHandle.ToInt64();
NativeMethods.SystemHandleInformation currentHandleInfo;
for (int i = 0; i < Marshal.ReadInt32(systemHandle); i++)
{
var currentOffset = IntPtr.Size + i * Marshal.SizeOf(typeof(NativeMethods.SystemHandleInformation));
currentHandleInfo = (NativeMethods.SystemHandleInformation)Marshal.PtrToStructure(new IntPtr(basePointer + currentOffset), typeof(NativeMethods.SystemHandleInformation));
if (currentHandleInfo.OwnerPID == (uint)targetProcess.Id)
{
processHandles.Add(currentHandleInfo);
}
}
return processHandles;
}
private static void CloseHandle(Process targetProcess, string handleName)
{
var systemHandles = GetAllHandles();
if (systemHandles == IntPtr.Zero)
{
return;
}
List<NativeMethods.SystemHandleInformation> processHandles = GetHandles(targetProcess, systemHandles);
Marshal.FreeHGlobal(systemHandles);
var processHandle = NativeMethods.OpenProcess(NativeMethods.ProcessAccessFlags.DupHandle, false, (uint)targetProcess.Id);
foreach (var handleInfo in processHandles)
{
if (GetHandleName(handleInfo, processHandle).Contains(handleName))
{
if (CloseOwnedHandle(handleInfo.OwnerPID, new IntPtr(handleInfo.HandleValue)))
{
NativeMethods.CloseHandle(processHandle);
return;
}
}
}
NativeMethods.CloseHandle(processHandle);
return;
}
private static string GetHandleName(NativeMethods.SystemHandleInformation targetHandleInfo, IntPtr processHandle)
{
if (targetHandleInfo.AccessMask.ToInt64() == 0x0012019F)
{
return string.Empty;
}
var thisProcess = Process.GetCurrentProcess().Handle;
NativeMethods.DuplicateHandle(processHandle, new IntPtr(targetHandleInfo.HandleValue), thisProcess, out var handle, 0, false, NativeMethods.DuplicateOptions.DUPLICATE_SAME_ACCESS);
var bufferSize = GetHandleNameLength(handle);
var stringBuffer = Marshal.AllocHGlobal(bufferSize);
NativeMethods.NtQueryObject(handle, NativeMethods.ObjectInformationClass.ObjectNameInformation, stringBuffer, bufferSize, out _);
NativeMethods.CloseHandle(handle);
var handleName = ConvertToString(stringBuffer);
Marshal.FreeHGlobal(stringBuffer);
return handleName;
}
private static IntPtr GetAllHandles()
{
int bufferSize = 0x10000;
var pSysInfoBuffer = Marshal.AllocHGlobal(bufferSize);
var queryResult = NativeMethods.NtQuerySystemInformation(NativeMethods.SystemInformationClass.SystemHandleInformation,
pSysInfoBuffer, bufferSize, out _);
while (queryResult == NativeMethods.NtStatus.STATUS_INFO_LENGTH_MISMATCH)
{
Marshal.FreeHGlobal(pSysInfoBuffer);
bufferSize *= 2;
pSysInfoBuffer = Marshal.AllocHGlobal(bufferSize);
queryResult = NativeMethods.NtQuerySystemInformation(NativeMethods.SystemInformationClass.SystemHandleInformation,
pSysInfoBuffer, bufferSize, out _);
}
if (queryResult == NativeMethods.NtStatus.STATUS_SUCCESS)
{
return pSysInfoBuffer;
}
else
{
Marshal.FreeHGlobal(pSysInfoBuffer);
return IntPtr.Zero;
}
}
private static int GetHandleNameLength(IntPtr handle)
{
var infoBufferSize = Marshal.SizeOf(typeof(NativeMethods.ObjectBasicInformation));
var pInfoBuffer = Marshal.AllocHGlobal(infoBufferSize);
NativeMethods.NtQueryObject(handle, NativeMethods.ObjectInformationClass.ObjectBasicInformation, pInfoBuffer, infoBufferSize, out _);
NativeMethods.ObjectBasicInformation objInfo = (NativeMethods.ObjectBasicInformation)Marshal.PtrToStructure(pInfoBuffer, typeof(NativeMethods.ObjectBasicInformation));
Marshal.FreeHGlobal(pInfoBuffer);
if (objInfo.NameInformationLength == 0)
{
return 0x100;
}
else
{
return (int)objInfo.NameInformationLength;
}
}
private static string ConvertToString(IntPtr stringBuffer)
{
var baseAddress = stringBuffer.ToInt64();
var offset = IntPtr.Size * 2;
var handleName = Marshal.PtrToStringUni(new IntPtr(baseAddress + offset));
return handleName;
}
private static bool CloseOwnedHandle(uint processId, IntPtr handleToClose)
{
var processHandle = NativeMethods.OpenProcess(NativeMethods.ProcessAccessFlags.All, false, processId);
var success = NativeMethods.DuplicateHandle(processHandle, handleToClose, IntPtr.Zero, out _, 0, false, NativeMethods.DuplicateOptions.DUPLICATE_CLOSE_SOURCE);
NativeMethods.CloseHandle(processHandle);
return success;
}
}
}
@@ -93,7 +93,7 @@ namespace Daybreak.Services.Updater
public async Task<bool> UpdateAvailable()
{
var version = string.Join('.', this.CurrentVersion.Split('.').Take(3));
var version = string.Join('.', this.CurrentVersion.Split('.'));
var maybeLatestVersion = await this.GetLatestVersion();
return maybeLatestVersion.Switch(
onSome: latestVersion => string.Compare(version, latestVersion, true) < 0,
+94 -1
View File
@@ -1,13 +1,106 @@
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace Pepa.Wpf.Utilities
{
static class NativeMethods
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct SystemHandleInformation
{
public uint OwnerPID;
public byte ObjectType;
public byte HandleFlags;
public ushort HandleValue;
public UIntPtr ObjectPointer;
public IntPtr AccessMask;
}
[StructLayout(LayoutKind.Sequential)]
public struct ObjectBasicInformation
{
public uint Attributes;
public uint GrantedAccess;
public uint HandleCount;
public uint PointerCount;
public uint PagedPoolUsage;
public uint NonPagedPoolUsage;
public uint Reserved1;
public uint Reserved2;
public uint Reserved3;
public uint NameInformationLength;
public uint TypeInformationLength;
public uint SecurityDescriptorLength;
public FILETIME CreateTime;
}
[StructLayout(LayoutKind.Sequential)]
public struct IoStatusBlock
{
public uint Status;
public ulong Information;
}
[Flags]
public enum DuplicateOptions : uint
{
DUPLICATE_CLOSE_SOURCE = 0x00000001,
DUPLICATE_SAME_ACCESS = 0x00000002
}
[Flags]
public enum ProcessAccessFlags : uint
{
All = 0x001F0FFF,
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000
}
[Flags]
public enum NtStatus : uint
{
STATUS_SUCCESS = 0x00000000,
STATUS_INFO_LENGTH_MISMATCH = 0xC0000004
}
[Flags]
public enum ObjectInformationClass : uint
{
ObjectBasicInformation = 0,
ObjectNameInformation = 1,
ObjectTypeInformation = 2,
ObjectAllTypesInformation = 3,
ObjectHandleInformation = 4
}
[Flags]
public enum SystemInformationClass : uint
{
SystemHandleInformation = 16
}
[Flags]
public enum FileInformationClass
{
FileNameInformation = 9
}
public const int WM_SYSCOMMAND = 0x112;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll")]
public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, IntPtr hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, DuplicateOptions dwOptions);
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, uint dwProcessID);
[DllImport("ntdll.dll", SetLastError = true)]
public static extern NtStatus NtQueryInformationFile(IntPtr FileHandle, ref IoStatusBlock IoStatusBlock, IntPtr FileInformation, int FileInformationLength, FileInformationClass FileInformationClass);
[DllImport("ntdll.dll")]
public static extern NtStatus NtQueryObject(IntPtr ObjectHandle, ObjectInformationClass ObjectInformationClass, IntPtr ObjectInformation, int ObjectInformationLength, out int ReturnLength);
[DllImport("ntdll.dll")]
public static extern NtStatus NtQuerySystemInformation(SystemInformationClass SystemInformationClass, IntPtr SystemInformation, int SystemInformationLength, out int ReturnLength);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
<UserControl x:Class="Daybreak.Views.AccountsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Views"
mc:Ignorable="d"
xmlns:controls="clr-namespace:Daybreak.Controls"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="#A0202020">
<TextBlock HorizontalAlignment="Center" Text="Accounts settings" FontSize="22" Foreground="White"></TextBlock>
<controls:BackButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Left" Margin="5"
Clicked="BackButton_Clicked" VerticalAlignment="Top"></controls:BackButton>
<controls:AddButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Right" Margin="5, 5, 45, 5"
Clicked="AddButton_Clicked" VerticalAlignment="Top"></controls:AddButton>
<controls:SaveButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Right" Margin="5"
Clicked="SaveButton_Clicked" VerticalAlignment="Top"></controls:SaveButton>
<ListView ItemsSource="{Binding ElementName=_this, Path=Accounts, Mode=OneWay}" Background="Transparent" Margin="0, 40, 0, 0">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<controls:AccountTemplate Username="{Binding Username, Mode=TwoWay}"
Password="{Binding Password, Mode=TwoWay}"
CharacterName="{Binding CharacterName, Mode=TwoWay}"
RemoveClicked="AccountTemplate_RemoveClicked"
IsDefault="{Binding Default, Mode=TwoWay}"
DefaultClicked="AccountTemplate_DefaultClicked"></controls:AccountTemplate>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</UserControl>
+88
View File
@@ -0,0 +1,88 @@
using Daybreak.Controls;
using Daybreak.Models;
using Daybreak.Services.Credentials;
using Daybreak.Services.ViewManagement;
using System;
using System.Collections.ObjectModel;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Data;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for AccountsView.xaml
/// </summary>
public partial class AccountsView : UserControl
{
private readonly ICredentialManager credentialManager;
private readonly IViewManager viewManager;
public ObservableCollection<LoginCredentials> Accounts { get; } = new();
public AccountsView(
ICredentialManager credentialManager,
IViewManager viewManager)
{
this.credentialManager = credentialManager.ThrowIfNull(nameof(credentialManager));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.GetCredentials();
}
private async void GetCredentials()
{
var creds = await this.credentialManager.GetCredentialList().ConfigureAwait(true);
this.Accounts.AddRange(creds);
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<SettingsCategoryView>();
}
private void AddButton_Clicked(object sender, EventArgs e)
{
var newCredentials = new LoginCredentials();
this.Accounts.Add(newCredentials);
if (this.Accounts.Count == 1)
{
this.SetAccountAsDefault(newCredentials);
}
}
private async void SaveButton_Clicked(object sender, EventArgs e)
{
await this.credentialManager.StoreCredentials(this.Accounts.ToList()).ConfigureAwait(true);
this.viewManager.ShowView<SettingsCategoryView>();
}
private void AccountTemplate_RemoveClicked(object sender, EventArgs e)
{
var creds = sender.As<AccountTemplate>()?.DataContext?.As<LoginCredentials>();
this.Accounts.Remove(creds);
if (this.Accounts.Count > 0 && creds.Default is true)
{
this.SetAccountAsDefault(this.Accounts.First());
}
}
private void AccountTemplate_DefaultClicked(object sender, EventArgs e)
{
var creds = sender.As<AccountTemplate>()?.DataContext?.As<LoginCredentials>();
this.SetAccountAsDefault(creds);
}
private void SetAccountAsDefault(LoginCredentials loginCredentials)
{
foreach (var cred in this.Accounts)
{
cred.Default = false;
}
loginCredentials.Default = true;
var view = CollectionViewSource.GetDefaultView(this.Accounts);
view.Refresh();
}
}
}
-3
View File
@@ -12,16 +12,13 @@ namespace Daybreak.Views
/// </summary>
public partial class AskUpdateView : UserControl
{
private readonly IApplicationUpdater applicationUpdater;
private readonly ILogger logger;
private readonly IViewManager viewManager;
public AskUpdateView(
IApplicationUpdater applicationUpdater,
ILogger logger,
IViewManager viewManager)
{
this.applicationUpdater = applicationUpdater.ThrowIfNull(nameof(applicationUpdater));
this.logger = logger.ThrowIfNull(nameof(logger));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
+36
View File
@@ -0,0 +1,36 @@
<UserControl x:Class="Daybreak.Views.ExecutablesView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Views"
mc:Ignorable="d"
xmlns:controls="clr-namespace:Daybreak.Controls"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="#A0202020">
<TextBlock HorizontalAlignment="Center" Text="Executables settings" FontSize="22" Foreground="White"></TextBlock>
<controls:BackButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Left" Margin="5"
Clicked="BackButton_Clicked" VerticalAlignment="Top"></controls:BackButton>
<controls:AddButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Right" Margin="5, 5, 45, 5"
Clicked="AddButton_Clicked" VerticalAlignment="Top"></controls:AddButton>
<controls:SaveButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Right" Margin="5"
Clicked="SaveButton_Clicked" VerticalAlignment="Top"></controls:SaveButton>
<ListView ItemsSource="{Binding ElementName=_this, Path=Paths, Mode=OneWay}" Background="Transparent" Margin="0, 40, 0, 0">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<controls:GuildwarsPathTemplate
Path="{Binding Path, Mode=TwoWay}"
IsDefault="{Binding Default, Mode=TwoWay}"
DefaultClicked="GuildwarsPathTemplate_DefaultClicked"
RemoveClicked="GuildwarsPathTemplate_RemoveClicked"></controls:GuildwarsPathTemplate>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</UserControl>
+89
View File
@@ -0,0 +1,89 @@
using Daybreak.Controls;
using Daybreak.Models;
using Daybreak.Services.Configuration;
using Daybreak.Services.ViewManagement;
using Microsoft.Win32;
using System;
using System.Collections.ObjectModel;
using System.Extensions;
using System.Linq;
using System.Windows.Controls;
using System.Windows.Data;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for ExecutablesView.xaml
/// </summary>
public partial class ExecutablesView : UserControl
{
private readonly IConfigurationManager configurationManager;
private readonly IViewManager viewManager;
public ObservableCollection<GuildwarsPath> Paths { get; } = new();
public ExecutablesView(
IConfigurationManager configurationManager,
IViewManager viewManager)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.GetPaths();
}
private void GetPaths()
{
this.Paths.AddRange(this.configurationManager.GetConfiguration().GuildwarsPaths);
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<SettingsCategoryView>();
}
private void AddButton_Clicked(object sender, EventArgs e)
{
var newPath = new GuildwarsPath();
this.Paths.Add(newPath);
if (this.Paths.Count == 1)
{
this.SetPathAsDefault(newPath);
}
}
private void SaveButton_Clicked(object sender, EventArgs e)
{
var config = this.configurationManager.GetConfiguration();
config.GuildwarsPaths = this.Paths.ToList();
this.configurationManager.SaveConfiguration(config);
this.viewManager.ShowView<SettingsCategoryView>();
}
private void GuildwarsPathTemplate_DefaultClicked(object sender, EventArgs e)
{
var path = sender.As<GuildwarsPathTemplate>()?.DataContext?.As<GuildwarsPath>();
this.SetPathAsDefault(path);
}
private void GuildwarsPathTemplate_RemoveClicked(object sender, EventArgs e)
{
var path = sender.As<GuildwarsPathTemplate>()?.DataContext?.As<GuildwarsPath>();
this.Paths.Remove(path);
if (this.Paths.Count > 0 && path.Default is true)
{
this.SetPathAsDefault(this.Paths.First());
}
}
private void SetPathAsDefault(GuildwarsPath gwPath)
{
foreach (var path in this.Paths)
{
path.Default = false;
}
gwPath.Default = true;
var view = CollectionViewSource.GetDefaultView(this.Paths);
view.Refresh();
}
}
}
@@ -0,0 +1,94 @@
<UserControl x:Class="Daybreak.Views.ExperimentalSettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Views"
xmlns:controls="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<Style x:Key="AnimatedSwitch" TargetType="{x:Type ToggleButton}">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="#FAFAFB" />
<Setter Property="BorderBrush" Value="#EAEAEB" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Viewbox Stretch="Uniform">
<Canvas Name="Layer_1" Width="35" Height="20" Canvas.Left="10" Canvas.Top="0">
<Ellipse Canvas.Left="0" Width="20" Height="20" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="0.5"/>
<Ellipse Canvas.Left="15" Width="20" Height="20" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="0.5"/>
<Border Canvas.Left="10" Width="15" Height="20" Name="rect416927" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0,0.5,0,0.5"/>
<Ellipse x:Name="ellipse" Canvas.Left="0" Width="20" Height="20" Fill="White" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="0.3">
<Ellipse.RenderTransform>
<TranslateTransform X="0" Y="0" />
</Ellipse.RenderTransform>
<Ellipse.BitmapEffect>
<DropShadowBitmapEffect Softness="0.1" ShadowDepth="0.7" Direction="270" Color="#BBBBBB"/>
</Ellipse.BitmapEffect>
</Ellipse>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True" >
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" To="DodgerBlue" Duration="0:0:0.2" />
<ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" To="#41C955" Duration="0:0:0.2" />
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Ellipse.RenderTransform).(TranslateTransform.X)" Storyboard.TargetName="ellipse">
<SplineDoubleKeyFrame KeyTime="0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="0:0:0.4" Value="15" KeySpline="0, 1, 0.6, 1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" To="#FAFAFB" Duration="0:0:0.2" />
<ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" To="#EAEAEB" Duration="0:0:0.2" />
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Ellipse.RenderTransform).(TranslateTransform.X)" Storyboard.TargetName="ellipse">
<SplineDoubleKeyFrame KeyTime="0" Value="15"/>
<SplineDoubleKeyFrame KeyTime="0:0:0.3" Value="0" KeySpline="0, 0.5, 0.5, 1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<controls:BackButton Foreground="White" Height="30" Width="30" Grid.Column="0" Margin="5"
Clicked="BackButton_Clicked" VerticalAlignment="Top" HorizontalAlignment="Left"></controls:BackButton>
<controls:SaveButton Foreground="White" Height="30" Width="30" Grid.Column="0" Margin="5"
Clicked="SaveButton_Clicked" VerticalAlignment="Top" HorizontalAlignment="Right"></controls:SaveButton>
<StackPanel Margin="50, 0, 50, 0" HorizontalAlignment="Center">
<TextBlock FontSize="22" Foreground="White" TextWrapping="Wrap"
Text="Experimental settings" HorizontalAlignment="Center"></TextBlock>
<TextBlock FontSize="16" Foreground="White" TextWrapping="Wrap"
Text="Warning! Functionality under development! These settings might have unintended consequences, including breaking the GuildWars TOS."></TextBlock>
</StackPanel>
<Rectangle Fill="White" Height="1" VerticalAlignment="Bottom"></Rectangle>
<Grid Grid.Row="1" HorizontalAlignment="Center" Margin="0, 15, 0, 0">
<WrapPanel>
<TextBlock Text="Multi-launch support" Foreground="White" FontSize="22" VerticalAlignment="Center" Margin="0, 0, 15, 0"></TextBlock>
<ToggleButton IsChecked="{Binding ElementName=_this, Path=MultiLaunch, Mode=TwoWay}" Style="{StaticResource AnimatedSwitch}"
Height="30" Width="60"></ToggleButton>
</WrapPanel>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,61 @@
using Daybreak.Services.Configuration;
using Daybreak.Services.ViewManagement;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for ExperimentalSettingsView.xaml
/// </summary>
public partial class ExperimentalSettingsView : UserControl
{
public static readonly DependencyProperty MultiLaunchProperty = DependencyPropertyExtensions.Register<ExperimentalSettingsView, bool>(nameof(MultiLaunch));
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
public bool MultiLaunch
{
get => this.GetTypedValue<bool>(MultiLaunchProperty);
set => this.SetValue(MultiLaunchProperty, value);
}
public ExperimentalSettingsView(
IViewManager viewManager,
IConfigurationManager configurationManager)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.InitializeComponent();
this.LoadExperimentalSettings();
}
private void LoadExperimentalSettings()
{
var config = this.configurationManager.GetConfiguration();
this.MultiLaunch = config.ExperimentalFeatures.MultiLaunchSupport;
}
private void SaveExperimentalSettings()
{
var config = this.configurationManager.GetConfiguration();
config.ExperimentalFeatures.MultiLaunchSupport = this.MultiLaunch;
this.configurationManager.SaveConfiguration(config);
}
private void SaveButton_Clicked(object sender, EventArgs e)
{
this.SaveExperimentalSettings();
this.viewManager.ShowView<SettingsCategoryView>();
}
private void BackButton_Clicked(object sender, EventArgs e)
{
this.viewManager.ShowView<SettingsCategoryView>();
}
}
}
+12 -7
View File
@@ -1,5 +1,6 @@
using Daybreak.Controls;
using Daybreak.Services.ApplicationDetection;
using Daybreak.Exceptions;
using Daybreak.Services.ApplicationLauncher;
using Daybreak.Services.Configuration;
using Daybreak.Services.ViewManagement;
using System;
@@ -29,7 +30,7 @@ namespace Daybreak.Views
public static readonly DependencyProperty LeftBrowserFavoriteAddressProperty =
DependencyPropertyExtensions.Register<MainView, string>(nameof(LeftBrowserFavoriteAddress));
private readonly IApplicationDetector applicationDetector;
private readonly IApplicationLauncher applicationDetector;
private readonly IViewManager viewManager;
private readonly IConfigurationManager configurationManager;
private readonly CancellationTokenSource cancellationTokenSource = new();
@@ -69,7 +70,7 @@ namespace Daybreak.Views
}
public MainView(
IApplicationDetector applicationDetector,
IApplicationLauncher applicationDetector,
IViewManager viewManager,
IConfigurationManager configurationManager)
{
@@ -110,15 +111,19 @@ namespace Daybreak.Views
this.cancellationTokenSource.Cancel();
}
private void LaunchButton_Clicked(object sender, EventArgs e)
private async void LaunchButton_Clicked(object sender, EventArgs e)
{
try
{
this.applicationDetector.LaunchGuildwars();
await this.applicationDetector.LaunchGuildwars();
}
catch
catch (CredentialsNotFoundException)
{
this.viewManager.ShowView<SettingsView>();
this.viewManager.ShowView<AccountsView>();
}
catch (ExecutableNotFoundException)
{
this.viewManager.ShowView<ExecutablesView>();
}
}
+42
View File
@@ -0,0 +1,42 @@
<UserControl x:Class="Daybreak.Views.SettingsCategoryView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Daybreak.Views"
xmlns:controls="clr-namespace:Daybreak.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="#A0202020">
<controls:BackButton Foreground="White" Height="30" Width="30" Grid.Column="0" Margin="5"
Clicked="BackButton_Clicked" VerticalAlignment="Top" HorizontalAlignment="Left"></controls:BackButton>
<WrapPanel Orientation="Vertical" VerticalAlignment="Bottom">
<WrapPanel Orientation="Horizontal">
<controls:TileButton Grid.Row="5" Title="Account settings" Foreground="White" BorderBrush="White" BorderThickness="2"
HighlightColor="White" Clicked="AccountButton_Clicked" Width="150" Height="150">
<controls:TileButton.InnerContent>
<controls:AvatarGlyph></controls:AvatarGlyph>
</controls:TileButton.InnerContent>
</controls:TileButton>
<controls:TileButton Grid.Row="5" Grid.Column="1" Title="Launcher settings" Foreground="White" BorderBrush="White" BorderThickness="2"
HighlightColor="White" Clicked="LauncherButton_Clicked" Width="150" Height="150">
<controls:TileButton.InnerContent>
<controls:GoldenArrowGlyph></controls:GoldenArrowGlyph>
</controls:TileButton.InnerContent>
</controls:TileButton>
<controls:TileButton Grid.Row="5" Grid.Column="2" Title="Experimental settings" Foreground="White" BorderBrush="White" BorderThickness="2"
HighlightColor="White" Clicked="ExperimentalButton_Clicked" Height="150" Width="150">
<controls:TileButton.InnerContent>
<controls:ExperimentGlyph></controls:ExperimentGlyph>
</controls:TileButton.InnerContent>
</controls:TileButton>
<controls:TileButton Grid.Row="5" Grid.Column="2" Title="Guildwars settings" Foreground="White" BorderBrush="White" BorderThickness="2"
HighlightColor="White" Clicked="FileButton_Clicked" Height="150" Width="150">
<controls:TileButton.InnerContent>
<controls:FileGlyph></controls:FileGlyph>
</controls:TileButton.InnerContent>
</controls:TileButton>
</WrapPanel>
</WrapPanel>
</Grid>
</UserControl>
@@ -0,0 +1,46 @@
using Daybreak.Services.ViewManagement;
using System.Extensions;
using System.Windows.Controls;
namespace Daybreak.Views
{
/// <summary>
/// Interaction logic for SettingsCategoryView.xaml
/// </summary>
public partial class SettingsCategoryView : UserControl
{
private readonly IViewManager viewManager;
public SettingsCategoryView(
IViewManager viewManager)
{
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
InitializeComponent();
}
private void AccountButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<AccountsView>();
}
private void LauncherButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<SettingsView>();
}
private void ExperimentalButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<ExperimentalSettingsView>();
}
private void FileButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<ExecutablesView>();
}
private void BackButton_Clicked(object sender, System.EventArgs e)
{
this.viewManager.ShowView<MainView>();
}
}
}
+80 -24
View File
@@ -8,7 +8,65 @@
mc:Ignorable="d"
x:Name="_this"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Background="#80202020">
<UserControl.Resources>
<ResourceDictionary>
<Style x:Key="AnimatedSwitch" TargetType="{x:Type ToggleButton}">
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="#FAFAFB" />
<Setter Property="BorderBrush" Value="#EAEAEB" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Viewbox Stretch="Uniform">
<Canvas Name="Layer_1" Width="35" Height="20" Canvas.Left="10" Canvas.Top="0">
<Ellipse Canvas.Left="0" Width="20" Height="20" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="0.5"/>
<Ellipse Canvas.Left="15" Width="20" Height="20" Fill="{TemplateBinding Background}" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="0.5"/>
<Border Canvas.Left="10" Width="15" Height="20" Name="rect416927" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0,0.5,0,0.5"/>
<Ellipse x:Name="ellipse" Canvas.Left="0" Width="20" Height="20" Fill="White" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="0.3">
<Ellipse.RenderTransform>
<TranslateTransform X="0" Y="0" />
</Ellipse.RenderTransform>
<Ellipse.BitmapEffect>
<DropShadowBitmapEffect Softness="0.1" ShadowDepth="0.7" Direction="270" Color="#BBBBBB"/>
</Ellipse.BitmapEffect>
</Ellipse>
</Canvas>
</Viewbox>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True" >
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" To="DodgerBlue" Duration="0:0:0.2" />
<ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" To="#41C955" Duration="0:0:0.2" />
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Ellipse.RenderTransform).(TranslateTransform.X)" Storyboard.TargetName="ellipse">
<SplineDoubleKeyFrame KeyTime="0" Value="0"/>
<SplineDoubleKeyFrame KeyTime="0:0:0.4" Value="15" KeySpline="0, 1, 0.6, 1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="Background.Color" To="#FAFAFB" Duration="0:0:0.2" />
<ColorAnimation Storyboard.TargetProperty="BorderBrush.Color" To="#EAEAEB" Duration="0:0:0.2" />
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(Ellipse.RenderTransform).(TranslateTransform.X)" Storyboard.TargetName="ellipse">
<SplineDoubleKeyFrame KeyTime="0" Value="15"/>
<SplineDoubleKeyFrame KeyTime="0:0:0.3" Value="0" KeySpline="0, 0.5, 0.5, 1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid Background="#A0202020">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
@@ -17,38 +75,36 @@
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="Launcher settings" FontSize="22" Foreground="White" HorizontalAlignment="Center" Grid.ColumnSpan="2"></TextBlock>
<controls:BackButton Foreground="White" Height="30" Width="30" Grid.Column="0" HorizontalAlignment="Left" Margin="5"
Clicked="BackButton_Clicked"></controls:BackButton>
<controls:SaveButton Foreground="White" Height="30" Width="30" Grid.Column="1" HorizontalAlignment="Right" Margin="5"
Clicked="SaveButton_Clicked"></controls:SaveButton>
<TextBlock Text="Username: " FontSize="22" Foreground="White" Grid.Row="1"></TextBlock>
<TextBox x:Name="UsernameTextbox" Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="1" FontSize="22"
Background="Transparent" Foreground="White"></TextBox>
<TextBlock Text="Password: " FontSize="22" Foreground="White" Grid.Row="2"></TextBlock>
<PasswordBox x:Name="PasswordBox" Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="2"
FontSize="22" Foreground="White" Background="Transparent"></PasswordBox>
<TextBlock Text="Character name: " FontSize="22" Foreground="White" Grid.Row="3"></TextBlock>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="3"
x:Name="CharacterTextbox" FontSize="22" Background="Transparent" Foreground="White"></TextBox>
<TextBlock Text="Game path: " FontSize="22" Foreground="White" Grid.Row="4"></TextBlock>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="4"
x:Name="GamePathTextbox" FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"></TextBox>
<controls:FilePickerGlyph Grid.Row="4" Grid.Column="1" Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="1"
Clicked="GameFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
<TextBlock Text="Toolbox path: " FontSize="22" Foreground="White" Grid.Row="5"></TextBlock>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="5"
x:Name="ToolboxPathTextbox" FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"></TextBox>
<controls:FilePickerGlyph Grid.Row="5" Grid.Column="1" Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="1"
<TextBlock Text="Toolbox path: " FontSize="22" Foreground="White" Grid.Row="2"></TextBlock>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="2"
FontSize="22" Margin="0, 0, 30, 0" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=ToolboxPath, Mode=TwoWay}"></TextBox>
<controls:FilePickerGlyph Grid.Row="2" Grid.Column="1" Height="30" Width="30" HorizontalAlignment="Right"
Foreground="White" BorderBrush="Gray" BorderThickness="0"
Clicked="ToolboxFilePickerGlyph_Clicked"></controls:FilePickerGlyph>
<TextBlock Text="Address bar readonly: " FontSize="22" Foreground="White" Grid.Row="6"/>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="6"
x:Name="AddressBarReadonlyTextbox" FontSize="22" Background="Transparent" Foreground="White"></TextBox>
<TextBlock Text="Address bar readonly: " FontSize="22" Foreground="White" Grid.Row="3"/>
<ToggleButton Grid.Column="1" HorizontalAlignment="Center" HorizontalContentAlignment="Stretch" Grid.Row="3" Style="{StaticResource AnimatedSwitch}" Height="30" Width="40"
FontSize="22" Background="Transparent" Foreground="White" IsChecked="{Binding ElementName=_this, Path=AddressBarReadonly, Mode=TwoWay}"></ToggleButton>
<TextBlock Text="Left browser homepage: " FontSize="22" Foreground="White" Grid.Row="4"></TextBlock>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="4"
FontSize="22" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=LeftBrowserUrl, Mode=TwoWay}"
TextChanged="LeftBrowserUrl_TextChanged"></TextBox>
<TextBlock Text="Right browser homepage: " FontSize="22" Foreground="White" Grid.Row="5"></TextBlock>
<TextBox Grid.Column="1" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Grid.Row="5"
FontSize="22" Background="Transparent" Foreground="White"
Text="{Binding ElementName=_this, Path=RightBrowserUrl, Mode=TwoWay}"
TextChanged="RightBrowserUrl_TextChanged"></TextBox>
</Grid>
</UserControl>
+55 -45
View File
@@ -1,10 +1,11 @@
using Daybreak.Models;
using Daybreak.Services.Configuration;
using Daybreak.Services.Credentials;
using Daybreak.Services.Configuration;
using Daybreak.Services.ViewManagement;
using Microsoft.Win32;
using System;
using System.Extensions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Extensions;
namespace Daybreak.Views
{
@@ -13,17 +14,44 @@ namespace Daybreak.Views
/// </summary>
public partial class SettingsView : UserControl
{
public static readonly DependencyProperty ToolboxPathProperty =
DependencyPropertyExtensions.Register<SettingsView, string>(nameof(ToolboxPath));
public static readonly DependencyProperty AddressBarReadonlyProperty =
DependencyPropertyExtensions.Register<SettingsView, bool>(nameof(AddressBarReadonly));
public static readonly DependencyProperty LeftBrowserUrlProperty =
DependencyPropertyExtensions.Register<SettingsView, string>(nameof(LeftBrowserUrl));
public static readonly DependencyProperty RightBrowserUrlProperty =
DependencyPropertyExtensions.Register<SettingsView, string>(nameof(RightBrowserUrl));
private readonly IConfigurationManager configurationManager;
private readonly ICredentialManager credentialManager;
private readonly IViewManager viewManager;
public string ToolboxPath
{
get => this.GetTypedValue<string>(ToolboxPathProperty);
set => this.SetValue(ToolboxPathProperty, value);
}
public bool AddressBarReadonly
{
get => this.GetTypedValue<bool>(AddressBarReadonlyProperty);
set => this.SetValue(AddressBarReadonlyProperty, value);
}
public string LeftBrowserUrl
{
get => this.GetTypedValue<string>(LeftBrowserUrlProperty);
set => this.SetValue(LeftBrowserUrlProperty, value);
}
public string RightBrowserUrl
{
get => this.GetTypedValue<string>(RightBrowserUrlProperty);
set => this.SetValue(RightBrowserUrlProperty, value);
}
public SettingsView(
IConfigurationManager configurationManager,
ICredentialManager credentialManager,
IViewManager viewManager)
{
this.configurationManager = configurationManager.ThrowIfNull(nameof(configurationManager));
this.credentialManager = credentialManager.ThrowIfNull(nameof(credentialManager));
this.viewManager = viewManager.ThrowIfNull(nameof(viewManager));
this.InitializeComponent();
this.LoadSettings();
@@ -32,37 +60,24 @@ namespace Daybreak.Views
private void LoadSettings()
{
var config = this.configurationManager.GetConfiguration();
var creds = this.credentialManager.GetCredentials();
creds.DoAny(
onSome: (credentials) =>
{
this.UsernameTextbox.Text = credentials.Username;
this.PasswordBox.Password = credentials.Password;
});
this.AddressBarReadonlyTextbox.Text = config.AddressBarReadonly.ToString();
this.CharacterTextbox.Text = config.CharacterName;
this.GamePathTextbox.Text = config.GamePath;
this.ToolboxPathTextbox.Text = config.ToolboxPath;
this.AddressBarReadonly = config.AddressBarReadonly;
this.ToolboxPath = config.ToolboxPath;
this.LeftBrowserUrl = config.LeftBrowserDefault;
this.RightBrowserUrl = config.RightBrowserDefault;
}
private void SaveButton_Clicked(object sender, System.EventArgs e)
private void SaveButton_Clicked(object sender, EventArgs e)
{
var currentConfig = this.configurationManager.GetConfiguration();
currentConfig.CharacterName = this.CharacterTextbox.Text;
currentConfig.GamePath = this.GamePathTextbox.Text;
currentConfig.ToolboxPath = this.ToolboxPathTextbox.Text;
if (bool.TryParse(this.AddressBarReadonlyTextbox.Text, out var addressBarReadonly))
{
currentConfig.AddressBarReadonly = addressBarReadonly;
}
currentConfig.ToolboxPath = this.ToolboxPath;
currentConfig.AddressBarReadonly = this.AddressBarReadonly;
currentConfig.LeftBrowserDefault = this.LeftBrowserUrl;
currentConfig.RightBrowserDefault = this.RightBrowserUrl;
this.configurationManager.SaveConfiguration(currentConfig);
this.credentialManager.StoreCredentials(new LoginCredentials { Username = this.UsernameTextbox.Text, Password = this.PasswordBox.Password });
this.viewManager.ShowView<MainView>();
this.viewManager.ShowView<SettingsCategoryView>();
}
private void GameFilePickerGlyph_Clicked(object sender, System.EventArgs e)
private void ToolboxFilePickerGlyph_Clicked(object sender, EventArgs e)
{
var filePicker = new OpenFileDialog()
{
@@ -73,28 +88,23 @@ namespace Daybreak.Views
};
if (filePicker.ShowDialog() is true)
{
this.GamePathTextbox.Text = filePicker.FileName;
this.ToolboxPath = filePicker.FileName;
}
}
private void ToolboxFilePickerGlyph_Clicked(object sender, System.EventArgs e)
private void BackButton_Clicked(object sender, EventArgs e)
{
var filePicker = new OpenFileDialog()
{
CheckFileExists = true,
CheckPathExists = true,
DefaultExt = "exe",
Multiselect = false
};
if (filePicker.ShowDialog() is true)
{
this.ToolboxPathTextbox.Text = filePicker.FileName;
}
this.viewManager.ShowView<SettingsCategoryView>();
}
private void BackButton_Clicked(object sender, System.EventArgs e)
private void LeftBrowserUrl_TextChanged(object sender, TextChangedEventArgs e)
{
this.viewManager.ShowView<MainView>();
this.LeftBrowserUrl = sender.As<TextBox>().Text;
}
private void RightBrowserUrl_TextChanged(object sender, TextChangedEventArgs e)
{
this.RightBrowserUrl = sender.As<TextBox>().Text;
}
}
}
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
</assembly>
+19 -5
View File
@@ -1,12 +1,26 @@
# Daybreak
Custom client for Guildwars.
Requires standalone version https://developer.microsoft.com/microsoft-edge/webview2.
Requires webview2 runtime https://go.microsoft.com/fwlink/p/?LinkId=2124703.
![Alt Text](https://media1.giphy.com/media/Z32o0OZ5pZHDOIodzD/giphy.gif)
![Alt Text](https://media0.giphy.com/media/aQ8Wl7lsuhT0AblCPI/giphy.gif)
![Alt Text](https://media2.giphy.com/media/s06PtxgeAAZtoJhTx6/giphy.gif)
![Showcase 1](https://media1.giphy.com/media/Z32o0OZ5pZHDOIodzD/giphy.gif)
![Showcase 2](https://media0.giphy.com/media/aQ8Wl7lsuhT0AblCPI/giphy.gif)
![Showcase 3](https://media2.giphy.com/media/s06PtxgeAAZtoJhTx6/giphy.gif)
## Features
# Examples/Usage
To modify any settings, press the settings button on the titlebar
![Settings button](https://i.imgur.com/0QSTvNF.png)
When launching, if any of the required settings are not valid (missing username/password/character name), the launcher will open the settings page.
![Settings page](https://i.imgur.com/Pzs8N6S.png)
By default, the browser address bar is set to readonly. To allow any link to be typed in the address bar, change the "Address bar readonly" setting from settings page to "True".
When in the main view, the browsers open the default/prefferred page. To change the prefferred page, navigate to it using one of the browsers and press the star button. The current loaded page will become the default for the selected browser.
![Browser default selection](https://i.imgur.com/nDnyIIL.png)
To display other images than the ones retrieved from "http://bloogum.net/guildwars", place images in the Screenshots folder, next to the Daybreak.exe executable. If the folder doesn't exist yet, either create it or run the launcher once so that it gets created automatically.
# Features
Automatically detect if guildwars is running or not. Includes the ability to launch guildwars from the client.
Manages username and password combination.